Code

Use temporary variable in refs loop in main_draw
[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);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
145 enum format_flags {
146         FORMAT_ALL,             /* Perform replacement in all arguments. */
147         FORMAT_DASH,            /* Perform replacement up until "--". */
148         FORMAT_NONE             /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154         INPUT_OK,
155         INPUT_SKIP,
156         INPUT_STOP,
157         INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 /*
166  * Allocation helpers ... Entering macro hell to never be seen again.
167  */
169 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
170 static type *                                                                   \
171 name(type **mem, size_t size, size_t increase)                                  \
172 {                                                                               \
173         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
174         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
175         type *tmp = *mem;                                                       \
176                                                                                 \
177         if (mem == NULL || num_chunks != num_chunks_new) {                      \
178                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
179                 if (tmp)                                                        \
180                         *mem = tmp;                                             \
181         }                                                                       \
182                                                                                 \
183         return tmp;                                                             \
186 /*
187  * String helpers
188  */
190 static inline void
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193         if (srclen > dstlen - 1)
194                 srclen = dstlen - 1;
196         strncpy(dst, src, srclen);
197         dst[srclen] = 0;
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206         string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 static void
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
217         size_t size, pos;
219         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220                 if (src[pos] == '\t') {
221                         size_t expanded = tabsize - (size % tabsize);
223                         if (expanded + size >= dstlen - 1)
224                                 expanded = dstlen - size - 1;
225                         memcpy(dst + size, "        ", expanded);
226                         size += expanded;
227                 } else {
228                         dst[size++] = src[pos];
229                 }
230         }
232         dst[size] = 0;
235 static char *
236 chomp_string(char *name)
238         int namelen;
240         while (isspace(*name))
241                 name++;
243         namelen = strlen(name) - 1;
244         while (namelen > 0 && isspace(name[namelen]))
245                 name[namelen--] = 0;
247         return name;
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
253         va_list args;
254         size_t pos = bufpos ? *bufpos : 0;
256         va_start(args, fmt);
257         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258         va_end(args);
260         if (bufpos)
261                 *bufpos = pos;
263         return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267         string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270         string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
275         size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279         /* Diff-Header == DIFF_HEADER */
280         for (i = 0; i < len; i++) {
281                 if (toupper(str1[i]) == toupper(str2[i]))
282                         continue;
284                 if (string_enum_sep(str1[i]) &&
285                     string_enum_sep(str2[i]))
286                         continue;
288                 return str1[i] - str2[i];
289         }
291         return 0;
294 struct enum_map {
295         const char *name;
296         int namelen;
297         int value;
298 };
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
302 static bool
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
305         size_t namelen = strlen(name);
306         int i;
308         for (i = 0; i < map_size; i++)
309                 if (namelen == map[i].namelen &&
310                     !string_enum_compare(name, map[i].name, namelen)) {
311                         *value = map[i].value;
312                         return TRUE;
313                 }
315         return FALSE;
318 #define map_enum(attr, map, name) \
319         map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322         strncmp(str1, str2, STRING_SIZE(str2))
324 static inline int
325 suffixcmp(const char *str, int slen, const char *suffix)
327         size_t len = slen >= 0 ? slen : strlen(str);
328         size_t suffixlen = strlen(suffix);
330         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
334 static const char *
335 mkdate(const time_t *time)
337         static char buf[DATE_COLS + 1];
338         struct tm tm;
340         gmtime_r(time, &tm);
341         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
345 static bool
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
348         int valuelen;
350         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351                 bool advance = cmd[valuelen] != 0;
353                 cmd[valuelen] = 0;
354                 argv[(*argc)++] = chomp_string(cmd);
355                 cmd = chomp_string(cmd + valuelen + advance);
356         }
358         if (*argc < SIZEOF_ARG)
359                 argv[*argc] = NULL;
360         return *argc < SIZEOF_ARG;
363 static void
364 argv_from_env(const char **argv, const char *name)
366         char *env = argv ? getenv(name) : NULL;
367         int argc = 0;
369         if (env && *env)
370                 env = strdup(env);
371         if (env && !argv_from_string(argv, &argc, env))
372                 die("Too many arguments in the `%s` environment variable", name);
376 /*
377  * Executing external commands.
378  */
380 enum io_type {
381         IO_FD,                  /* File descriptor based IO. */
382         IO_BG,                  /* Execute command in the background. */
383         IO_FG,                  /* Execute command with same std{in,out,err}. */
384         IO_RD,                  /* Read only fork+exec IO. */
385         IO_WR,                  /* Write only fork+exec IO. */
386         IO_AP,                  /* Append fork+exec output to file. */
387 };
389 struct io {
390         enum io_type type;      /* The requested type of pipe. */
391         const char *dir;        /* Directory from which to execute. */
392         pid_t pid;              /* Pipe for reading or writing. */
393         int pipe;               /* Pipe end for reading or writing. */
394         int error;              /* Error status. */
395         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
396         char *buf;              /* Read buffer. */
397         size_t bufalloc;        /* Allocated buffer size. */
398         size_t bufsize;         /* Buffer content size. */
399         char *bufpos;           /* Current buffer position. */
400         unsigned int eof:1;     /* Has end of file been reached. */
401 };
403 static void
404 reset_io(struct io *io)
406         io->pipe = -1;
407         io->pid = 0;
408         io->buf = io->bufpos = NULL;
409         io->bufalloc = io->bufsize = 0;
410         io->error = 0;
411         io->eof = 0;
414 static void
415 init_io(struct io *io, const char *dir, enum io_type type)
417         reset_io(io);
418         io->type = type;
419         io->dir = dir;
422 static bool
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424                 enum format_flags flags)
426         init_io(io, dir, IO_RD);
427         return format_argv(io->argv, argv, flags);
430 static bool
431 io_open(struct io *io, const char *name)
433         init_io(io, NULL, IO_FD);
434         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
435         if (io->pipe == -1)
436                 io->error = errno;
437         return io->pipe != -1;
440 static bool
441 kill_io(struct io *io)
443         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
446 static bool
447 done_io(struct io *io)
449         pid_t pid = io->pid;
451         if (io->pipe != -1)
452                 close(io->pipe);
453         free(io->buf);
454         reset_io(io);
456         while (pid > 0) {
457                 int status;
458                 pid_t waiting = waitpid(pid, &status, 0);
460                 if (waiting < 0) {
461                         if (errno == EINTR)
462                                 continue;
463                         report("waitpid failed (%s)", strerror(errno));
464                         return FALSE;
465                 }
467                 return waiting == pid &&
468                        !WIFSIGNALED(status) &&
469                        WIFEXITED(status) &&
470                        !WEXITSTATUS(status);
471         }
473         return TRUE;
476 static bool
477 start_io(struct io *io)
479         int pipefds[2] = { -1, -1 };
481         if (io->type == IO_FD)
482                 return TRUE;
484         if ((io->type == IO_RD || io->type == IO_WR) &&
485             pipe(pipefds) < 0)
486                 return FALSE;
487         else if (io->type == IO_AP)
488                 pipefds[1] = io->pipe;
490         if ((io->pid = fork())) {
491                 if (pipefds[!(io->type == IO_WR)] != -1)
492                         close(pipefds[!(io->type == IO_WR)]);
493                 if (io->pid != -1) {
494                         io->pipe = pipefds[!!(io->type == IO_WR)];
495                         return TRUE;
496                 }
498         } else {
499                 if (io->type != IO_FG) {
500                         int devnull = open("/dev/null", O_RDWR);
501                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
502                         int writefd = (io->type == IO_RD || io->type == IO_AP)
503                                                         ? pipefds[1] : devnull;
505                         dup2(readfd,  STDIN_FILENO);
506                         dup2(writefd, STDOUT_FILENO);
507                         dup2(devnull, STDERR_FILENO);
509                         close(devnull);
510                         if (pipefds[0] != -1)
511                                 close(pipefds[0]);
512                         if (pipefds[1] != -1)
513                                 close(pipefds[1]);
514                 }
516                 if (io->dir && *io->dir && chdir(io->dir) == -1)
517                         die("Failed to change directory: %s", strerror(errno));
519                 execvp(io->argv[0], (char *const*) io->argv);
520                 die("Failed to execute program: %s", strerror(errno));
521         }
523         if (pipefds[!!(io->type == IO_WR)] != -1)
524                 close(pipefds[!!(io->type == IO_WR)]);
525         return FALSE;
528 static bool
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
531         init_io(io, dir, type);
532         if (!format_argv(io->argv, argv, FORMAT_NONE))
533                 return FALSE;
534         return start_io(io);
537 static int
538 run_io_do(struct io *io)
540         return start_io(io) && done_io(io);
543 static int
544 run_io_bg(const char **argv)
546         struct io io = {};
548         init_io(&io, NULL, IO_BG);
549         if (!format_argv(io.argv, argv, FORMAT_NONE))
550                 return FALSE;
551         return run_io_do(&io);
554 static bool
555 run_io_fg(const char **argv, const char *dir)
557         struct io io = {};
559         init_io(&io, dir, IO_FG);
560         if (!format_argv(io.argv, argv, FORMAT_NONE))
561                 return FALSE;
562         return run_io_do(&io);
565 static bool
566 run_io_append(const char **argv, enum format_flags flags, int fd)
568         struct io io = {};
570         init_io(&io, NULL, IO_AP);
571         io.pipe = fd;
572         if (format_argv(io.argv, argv, flags))
573                 return run_io_do(&io);
574         close(fd);
575         return FALSE;
578 static bool
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
581         return init_io_rd(io, argv, NULL, flags) && start_io(io);
584 static bool
585 io_eof(struct io *io)
587         return io->eof;
590 static int
591 io_error(struct io *io)
593         return io->error;
596 static char *
597 io_strerror(struct io *io)
599         return strerror(io->error);
602 static bool
603 io_can_read(struct io *io)
605         struct timeval tv = { 0, 500 };
606         fd_set fds;
608         FD_ZERO(&fds);
609         FD_SET(io->pipe, &fds);
611         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
614 static ssize_t
615 io_read(struct io *io, void *buf, size_t bufsize)
617         do {
618                 ssize_t readsize = read(io->pipe, buf, bufsize);
620                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
621                         continue;
622                 else if (readsize == -1)
623                         io->error = errno;
624                 else if (readsize == 0)
625                         io->eof = 1;
626                 return readsize;
627         } while (1);
630 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
632 static char *
633 io_get(struct io *io, int c, bool can_read)
635         char *eol;
636         ssize_t readsize;
638         while (TRUE) {
639                 if (io->bufsize > 0) {
640                         eol = memchr(io->bufpos, c, io->bufsize);
641                         if (eol) {
642                                 char *line = io->bufpos;
644                                 *eol = 0;
645                                 io->bufpos = eol + 1;
646                                 io->bufsize -= io->bufpos - line;
647                                 return line;
648                         }
649                 }
651                 if (io_eof(io)) {
652                         if (io->bufsize) {
653                                 io->bufpos[io->bufsize] = 0;
654                                 io->bufsize = 0;
655                                 return io->bufpos;
656                         }
657                         return NULL;
658                 }
660                 if (!can_read)
661                         return NULL;
663                 if (io->bufsize > 0 && io->bufpos > io->buf)
664                         memmove(io->buf, io->bufpos, io->bufsize);
666                 if (io->bufalloc == io->bufsize) {
667                         if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
668                                 return NULL;
669                         io->bufalloc += BUFSIZ;
670                 }
672                 io->bufpos = io->buf;
673                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
674                 if (io_error(io))
675                         return NULL;
676                 io->bufsize += readsize;
677         }
680 static bool
681 io_write(struct io *io, const void *buf, size_t bufsize)
683         size_t written = 0;
685         while (!io_error(io) && written < bufsize) {
686                 ssize_t size;
688                 size = write(io->pipe, buf + written, bufsize - written);
689                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
690                         continue;
691                 else if (size == -1)
692                         io->error = errno;
693                 else
694                         written += size;
695         }
697         return written == bufsize;
700 static bool
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
703         char *result = io_get(io, '\n', TRUE);
705         if (result) {
706                 result = chomp_string(result);
707                 string_ncopy_do(buf, bufsize, result, strlen(result));
708         }
710         return done_io(io) && result;
713 static bool
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
716         struct io io = {};
718         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
721 static int
722 io_load(struct io *io, const char *separators,
723         int (*read_property)(char *, size_t, char *, size_t))
725         char *name;
726         int state = OK;
728         if (!start_io(io))
729                 return ERR;
731         while (state == OK && (name = io_get(io, '\n', TRUE))) {
732                 char *value;
733                 size_t namelen;
734                 size_t valuelen;
736                 name = chomp_string(name);
737                 namelen = strcspn(name, separators);
739                 if (name[namelen]) {
740                         name[namelen] = 0;
741                         value = chomp_string(name + namelen + 1);
742                         valuelen = strlen(value);
744                 } else {
745                         value = "";
746                         valuelen = 0;
747                 }
749                 state = read_property(name, namelen, value, valuelen);
750         }
752         if (state != ERR && io_error(io))
753                 state = ERR;
754         done_io(io);
756         return state;
759 static int
760 run_io_load(const char **argv, const char *separators,
761             int (*read_property)(char *, size_t, char *, size_t))
763         struct io io = {};
765         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766                 ? io_load(&io, separators, read_property) : ERR;
770 /*
771  * User requests
772  */
774 #define REQ_INFO \
775         /* XXX: Keep the view request first and in sync with views[]. */ \
776         REQ_GROUP("View switching") \
777         REQ_(VIEW_MAIN,         "Show main view"), \
778         REQ_(VIEW_DIFF,         "Show diff view"), \
779         REQ_(VIEW_LOG,          "Show log view"), \
780         REQ_(VIEW_TREE,         "Show tree view"), \
781         REQ_(VIEW_BLOB,         "Show blob view"), \
782         REQ_(VIEW_BLAME,        "Show blame view"), \
783         REQ_(VIEW_BRANCH,       "Show branch view"), \
784         REQ_(VIEW_HELP,         "Show help page"), \
785         REQ_(VIEW_PAGER,        "Show pager view"), \
786         REQ_(VIEW_STATUS,       "Show status view"), \
787         REQ_(VIEW_STAGE,        "Show stage view"), \
788         \
789         REQ_GROUP("View manipulation") \
790         REQ_(ENTER,             "Enter current line and scroll"), \
791         REQ_(NEXT,              "Move to next"), \
792         REQ_(PREVIOUS,          "Move to previous"), \
793         REQ_(PARENT,            "Move to parent"), \
794         REQ_(VIEW_NEXT,         "Move focus to next view"), \
795         REQ_(REFRESH,           "Reload and refresh"), \
796         REQ_(MAXIMIZE,          "Maximize the current view"), \
797         REQ_(VIEW_CLOSE,        "Close the current view"), \
798         REQ_(QUIT,              "Close all views and quit"), \
799         \
800         REQ_GROUP("View specific requests") \
801         REQ_(STATUS_UPDATE,     "Update file status"), \
802         REQ_(STATUS_REVERT,     "Revert file changes"), \
803         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
804         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
805         \
806         REQ_GROUP("Cursor navigation") \
807         REQ_(MOVE_UP,           "Move cursor one line up"), \
808         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
809         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
810         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
811         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
812         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
813         \
814         REQ_GROUP("Scrolling") \
815         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
816         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
817         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
818         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
819         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
820         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
821         \
822         REQ_GROUP("Searching") \
823         REQ_(SEARCH,            "Search the view"), \
824         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
825         REQ_(FIND_NEXT,         "Find next search match"), \
826         REQ_(FIND_PREV,         "Find previous search match"), \
827         \
828         REQ_GROUP("Option manipulation") \
829         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
830         REQ_(TOGGLE_DATE,       "Toggle date display"), \
831         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
832         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
833         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
834         \
835         REQ_GROUP("Misc") \
836         REQ_(PROMPT,            "Bring up the prompt"), \
837         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
838         REQ_(SHOW_VERSION,      "Show version information"), \
839         REQ_(STOP_LOADING,      "Stop all loading views"), \
840         REQ_(EDIT,              "Open in editor"), \
841         REQ_(NONE,              "Do nothing")
844 /* User action requests. */
845 enum request {
846 #define REQ_GROUP(help)
847 #define REQ_(req, help) REQ_##req
849         /* Offset all requests to avoid conflicts with ncurses getch values. */
850         REQ_OFFSET = KEY_MAX + 1,
851         REQ_INFO
853 #undef  REQ_GROUP
854 #undef  REQ_
855 };
857 struct request_info {
858         enum request request;
859         const char *name;
860         int namelen;
861         const char *help;
862 };
864 static const struct request_info req_info[] = {
865 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
866 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
867         REQ_INFO
868 #undef  REQ_GROUP
869 #undef  REQ_
870 };
872 static enum request
873 get_request(const char *name)
875         int namelen = strlen(name);
876         int i;
878         for (i = 0; i < ARRAY_SIZE(req_info); i++)
879                 if (req_info[i].namelen == namelen &&
880                     !string_enum_compare(req_info[i].name, name, namelen))
881                         return req_info[i].request;
883         return REQ_NONE;
887 /*
888  * Options
889  */
891 /* Option and state variables. */
892 static bool opt_date                    = TRUE;
893 static bool opt_author                  = TRUE;
894 static bool opt_line_number             = FALSE;
895 static bool opt_line_graphics           = TRUE;
896 static bool opt_rev_graph               = FALSE;
897 static bool opt_show_refs               = TRUE;
898 static int opt_num_interval             = NUMBER_INTERVAL;
899 static double opt_hscroll               = 0.50;
900 static int opt_tab_size                 = TAB_SIZE;
901 static int opt_author_cols              = AUTHOR_COLS-1;
902 static char opt_path[SIZEOF_STR]        = "";
903 static char opt_file[SIZEOF_STR]        = "";
904 static char opt_ref[SIZEOF_REF]         = "";
905 static char opt_head[SIZEOF_REF]        = "";
906 static char opt_head_rev[SIZEOF_REV]    = "";
907 static char opt_remote[SIZEOF_REF]      = "";
908 static char opt_encoding[20]            = "UTF-8";
909 static bool opt_utf8                    = TRUE;
910 static char opt_codeset[20]             = "UTF-8";
911 static iconv_t opt_iconv                = ICONV_NONE;
912 static char opt_search[SIZEOF_STR]      = "";
913 static char opt_cdup[SIZEOF_STR]        = "";
914 static char opt_prefix[SIZEOF_STR]      = "";
915 static char opt_git_dir[SIZEOF_STR]     = "";
916 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
917 static char opt_editor[SIZEOF_STR]      = "";
918 static FILE *opt_tty                    = NULL;
920 #define is_initial_commit()     (!*opt_head_rev)
921 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
924 /*
925  * Line-oriented content detection.
926  */
928 #define LINE_INFO \
929 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
930 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
931 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
932 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
933 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
934 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
935 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
936 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
937 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
938 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
939 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
940 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
941 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
942 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
943 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
944 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
945 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
946 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
947 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
948 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
950 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
951 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
952 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
953 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
954 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
955 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
956 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
957 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
958 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
959 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
960 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
962 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
963 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
964 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
965 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
966 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
967 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
968 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
969 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
970 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
971 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
972 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
973 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
974 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
975 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
976 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
977 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
978 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
979 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
980 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
981 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
982 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
983 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
985 enum line_type {
986 #define LINE(type, line, fg, bg, attr) \
987         LINE_##type
988         LINE_INFO,
989         LINE_NONE
990 #undef  LINE
991 };
993 struct line_info {
994         const char *name;       /* Option name. */
995         int namelen;            /* Size of option name. */
996         const char *line;       /* The start of line to match. */
997         int linelen;            /* Size of string to match. */
998         int fg, bg, attr;       /* Color and text attributes for the lines. */
999 };
1001 static struct line_info line_info[] = {
1002 #define LINE(type, line, fg, bg, attr) \
1003         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1004         LINE_INFO
1005 #undef  LINE
1006 };
1008 static enum line_type
1009 get_line_type(const char *line)
1011         int linelen = strlen(line);
1012         enum line_type type;
1014         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1015                 /* Case insensitive search matches Signed-off-by lines better. */
1016                 if (linelen >= line_info[type].linelen &&
1017                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1018                         return type;
1020         return LINE_DEFAULT;
1023 static inline int
1024 get_line_attr(enum line_type type)
1026         assert(type < ARRAY_SIZE(line_info));
1027         return COLOR_PAIR(type) | line_info[type].attr;
1030 static struct line_info *
1031 get_line_info(const char *name)
1033         size_t namelen = strlen(name);
1034         enum line_type type;
1036         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1037                 if (namelen == line_info[type].namelen &&
1038                     !string_enum_compare(line_info[type].name, name, namelen))
1039                         return &line_info[type];
1041         return NULL;
1044 static void
1045 init_colors(void)
1047         int default_bg = line_info[LINE_DEFAULT].bg;
1048         int default_fg = line_info[LINE_DEFAULT].fg;
1049         enum line_type type;
1051         start_color();
1053         if (assume_default_colors(default_fg, default_bg) == ERR) {
1054                 default_bg = COLOR_BLACK;
1055                 default_fg = COLOR_WHITE;
1056         }
1058         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1059                 struct line_info *info = &line_info[type];
1060                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1061                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1063                 init_pair(type, fg, bg);
1064         }
1067 struct line {
1068         enum line_type type;
1070         /* State flags */
1071         unsigned int selected:1;
1072         unsigned int dirty:1;
1073         unsigned int cleareol:1;
1075         void *data;             /* User data */
1076 };
1079 /*
1080  * Keys
1081  */
1083 struct keybinding {
1084         int alias;
1085         enum request request;
1086 };
1088 static const struct keybinding default_keybindings[] = {
1089         /* View switching */
1090         { 'm',          REQ_VIEW_MAIN },
1091         { 'd',          REQ_VIEW_DIFF },
1092         { 'l',          REQ_VIEW_LOG },
1093         { 't',          REQ_VIEW_TREE },
1094         { 'f',          REQ_VIEW_BLOB },
1095         { 'B',          REQ_VIEW_BLAME },
1096         { 'H',          REQ_VIEW_BRANCH },
1097         { 'p',          REQ_VIEW_PAGER },
1098         { 'h',          REQ_VIEW_HELP },
1099         { 'S',          REQ_VIEW_STATUS },
1100         { 'c',          REQ_VIEW_STAGE },
1102         /* View manipulation */
1103         { 'q',          REQ_VIEW_CLOSE },
1104         { KEY_TAB,      REQ_VIEW_NEXT },
1105         { KEY_RETURN,   REQ_ENTER },
1106         { KEY_UP,       REQ_PREVIOUS },
1107         { KEY_DOWN,     REQ_NEXT },
1108         { 'R',          REQ_REFRESH },
1109         { KEY_F(5),     REQ_REFRESH },
1110         { 'O',          REQ_MAXIMIZE },
1112         /* Cursor navigation */
1113         { 'k',          REQ_MOVE_UP },
1114         { 'j',          REQ_MOVE_DOWN },
1115         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1116         { KEY_END,      REQ_MOVE_LAST_LINE },
1117         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1118         { ' ',          REQ_MOVE_PAGE_DOWN },
1119         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1120         { 'b',          REQ_MOVE_PAGE_UP },
1121         { '-',          REQ_MOVE_PAGE_UP },
1123         /* Scrolling */
1124         { KEY_LEFT,     REQ_SCROLL_LEFT },
1125         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1126         { KEY_IC,       REQ_SCROLL_LINE_UP },
1127         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1128         { 'w',          REQ_SCROLL_PAGE_UP },
1129         { 's',          REQ_SCROLL_PAGE_DOWN },
1131         /* Searching */
1132         { '/',          REQ_SEARCH },
1133         { '?',          REQ_SEARCH_BACK },
1134         { 'n',          REQ_FIND_NEXT },
1135         { 'N',          REQ_FIND_PREV },
1137         /* Misc */
1138         { 'Q',          REQ_QUIT },
1139         { 'z',          REQ_STOP_LOADING },
1140         { 'v',          REQ_SHOW_VERSION },
1141         { 'r',          REQ_SCREEN_REDRAW },
1142         { '.',          REQ_TOGGLE_LINENO },
1143         { 'D',          REQ_TOGGLE_DATE },
1144         { 'A',          REQ_TOGGLE_AUTHOR },
1145         { 'g',          REQ_TOGGLE_REV_GRAPH },
1146         { 'F',          REQ_TOGGLE_REFS },
1147         { ':',          REQ_PROMPT },
1148         { 'u',          REQ_STATUS_UPDATE },
1149         { '!',          REQ_STATUS_REVERT },
1150         { 'M',          REQ_STATUS_MERGE },
1151         { '@',          REQ_STAGE_NEXT },
1152         { ',',          REQ_PARENT },
1153         { 'e',          REQ_EDIT },
1154 };
1156 #define KEYMAP_INFO \
1157         KEYMAP_(GENERIC), \
1158         KEYMAP_(MAIN), \
1159         KEYMAP_(DIFF), \
1160         KEYMAP_(LOG), \
1161         KEYMAP_(TREE), \
1162         KEYMAP_(BLOB), \
1163         KEYMAP_(BLAME), \
1164         KEYMAP_(BRANCH), \
1165         KEYMAP_(PAGER), \
1166         KEYMAP_(HELP), \
1167         KEYMAP_(STATUS), \
1168         KEYMAP_(STAGE)
1170 enum keymap {
1171 #define KEYMAP_(name) KEYMAP_##name
1172         KEYMAP_INFO
1173 #undef  KEYMAP_
1174 };
1176 static const struct enum_map keymap_table[] = {
1177 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1178         KEYMAP_INFO
1179 #undef  KEYMAP_
1180 };
1182 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1184 struct keybinding_table {
1185         struct keybinding *data;
1186         size_t size;
1187 };
1189 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1191 static void
1192 add_keybinding(enum keymap keymap, enum request request, int key)
1194         struct keybinding_table *table = &keybindings[keymap];
1196         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1197         if (!table->data)
1198                 die("Failed to allocate keybinding");
1199         table->data[table->size].alias = key;
1200         table->data[table->size++].request = request;
1203 /* Looks for a key binding first in the given map, then in the generic map, and
1204  * lastly in the default keybindings. */
1205 static enum request
1206 get_keybinding(enum keymap keymap, int key)
1208         size_t i;
1210         for (i = 0; i < keybindings[keymap].size; i++)
1211                 if (keybindings[keymap].data[i].alias == key)
1212                         return keybindings[keymap].data[i].request;
1214         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1215                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1216                         return keybindings[KEYMAP_GENERIC].data[i].request;
1218         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1219                 if (default_keybindings[i].alias == key)
1220                         return default_keybindings[i].request;
1222         return (enum request) key;
1226 struct key {
1227         const char *name;
1228         int value;
1229 };
1231 static const struct key key_table[] = {
1232         { "Enter",      KEY_RETURN },
1233         { "Space",      ' ' },
1234         { "Backspace",  KEY_BACKSPACE },
1235         { "Tab",        KEY_TAB },
1236         { "Escape",     KEY_ESC },
1237         { "Left",       KEY_LEFT },
1238         { "Right",      KEY_RIGHT },
1239         { "Up",         KEY_UP },
1240         { "Down",       KEY_DOWN },
1241         { "Insert",     KEY_IC },
1242         { "Delete",     KEY_DC },
1243         { "Hash",       '#' },
1244         { "Home",       KEY_HOME },
1245         { "End",        KEY_END },
1246         { "PageUp",     KEY_PPAGE },
1247         { "PageDown",   KEY_NPAGE },
1248         { "F1",         KEY_F(1) },
1249         { "F2",         KEY_F(2) },
1250         { "F3",         KEY_F(3) },
1251         { "F4",         KEY_F(4) },
1252         { "F5",         KEY_F(5) },
1253         { "F6",         KEY_F(6) },
1254         { "F7",         KEY_F(7) },
1255         { "F8",         KEY_F(8) },
1256         { "F9",         KEY_F(9) },
1257         { "F10",        KEY_F(10) },
1258         { "F11",        KEY_F(11) },
1259         { "F12",        KEY_F(12) },
1260 };
1262 static int
1263 get_key_value(const char *name)
1265         int i;
1267         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1268                 if (!strcasecmp(key_table[i].name, name))
1269                         return key_table[i].value;
1271         if (strlen(name) == 1 && isprint(*name))
1272                 return (int) *name;
1274         return ERR;
1277 static const char *
1278 get_key_name(int key_value)
1280         static char key_char[] = "'X'";
1281         const char *seq = NULL;
1282         int key;
1284         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1285                 if (key_table[key].value == key_value)
1286                         seq = key_table[key].name;
1288         if (seq == NULL &&
1289             key_value < 127 &&
1290             isprint(key_value)) {
1291                 key_char[1] = (char) key_value;
1292                 seq = key_char;
1293         }
1295         return seq ? seq : "(no key)";
1298 static const char *
1299 get_key(enum request request)
1301         static char buf[BUFSIZ];
1302         size_t pos = 0;
1303         char *sep = "";
1304         int i;
1306         buf[pos] = 0;
1308         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1309                 const struct keybinding *keybinding = &default_keybindings[i];
1311                 if (keybinding->request != request)
1312                         continue;
1314                 if (!string_format_from(buf, &pos, "%s%s", sep,
1315                                         get_key_name(keybinding->alias)))
1316                         return "Too many keybindings!";
1317                 sep = ", ";
1318         }
1320         return buf;
1323 struct run_request {
1324         enum keymap keymap;
1325         int key;
1326         const char *argv[SIZEOF_ARG];
1327 };
1329 static struct run_request *run_request;
1330 static size_t run_requests;
1332 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1334 static enum request
1335 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1337         struct run_request *req;
1339         if (argc >= ARRAY_SIZE(req->argv) - 1)
1340                 return REQ_NONE;
1342         if (!realloc_run_requests(&run_request, run_requests, 1))
1343                 return REQ_NONE;
1345         req = &run_request[run_requests];
1346         req->keymap = keymap;
1347         req->key = key;
1348         req->argv[0] = NULL;
1350         if (!format_argv(req->argv, argv, FORMAT_NONE))
1351                 return REQ_NONE;
1353         return REQ_NONE + ++run_requests;
1356 static struct run_request *
1357 get_run_request(enum request request)
1359         if (request <= REQ_NONE)
1360                 return NULL;
1361         return &run_request[request - REQ_NONE - 1];
1364 static void
1365 add_builtin_run_requests(void)
1367         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1368         const char *gc[] = { "git", "gc", NULL };
1369         struct {
1370                 enum keymap keymap;
1371                 int key;
1372                 int argc;
1373                 const char **argv;
1374         } reqs[] = {
1375                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1376                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1377         };
1378         int i;
1380         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1381                 enum request req;
1383                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1384                 if (req != REQ_NONE)
1385                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1386         }
1389 /*
1390  * User config file handling.
1391  */
1393 static int   config_lineno;
1394 static bool  config_errors;
1395 static const char *config_msg;
1397 static const struct enum_map color_map[] = {
1398 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1399         COLOR_MAP(DEFAULT),
1400         COLOR_MAP(BLACK),
1401         COLOR_MAP(BLUE),
1402         COLOR_MAP(CYAN),
1403         COLOR_MAP(GREEN),
1404         COLOR_MAP(MAGENTA),
1405         COLOR_MAP(RED),
1406         COLOR_MAP(WHITE),
1407         COLOR_MAP(YELLOW),
1408 };
1410 static const struct enum_map attr_map[] = {
1411 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1412         ATTR_MAP(NORMAL),
1413         ATTR_MAP(BLINK),
1414         ATTR_MAP(BOLD),
1415         ATTR_MAP(DIM),
1416         ATTR_MAP(REVERSE),
1417         ATTR_MAP(STANDOUT),
1418         ATTR_MAP(UNDERLINE),
1419 };
1421 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1423 static int parse_step(double *opt, const char *arg)
1425         *opt = atoi(arg);
1426         if (!strchr(arg, '%'))
1427                 return OK;
1429         /* "Shift down" so 100% and 1 does not conflict. */
1430         *opt = (*opt - 1) / 100;
1431         if (*opt >= 1.0) {
1432                 *opt = 0.99;
1433                 config_msg = "Step value larger than 100%";
1434                 return ERR;
1435         }
1436         if (*opt < 0.0) {
1437                 *opt = 1;
1438                 config_msg = "Invalid step value";
1439                 return ERR;
1440         }
1441         return OK;
1444 static int
1445 parse_int(int *opt, const char *arg, int min, int max)
1447         int value = atoi(arg);
1449         if (min <= value && value <= max) {
1450                 *opt = value;
1451                 return OK;
1452         }
1454         config_msg = "Integer value out of bound";
1455         return ERR;
1458 static bool
1459 set_color(int *color, const char *name)
1461         if (map_enum(color, color_map, name))
1462                 return TRUE;
1463         if (!prefixcmp(name, "color"))
1464                 return parse_int(color, name + 5, 0, 255) == OK;
1465         return FALSE;
1468 /* Wants: object fgcolor bgcolor [attribute] */
1469 static int
1470 option_color_command(int argc, const char *argv[])
1472         struct line_info *info;
1474         if (argc != 3 && argc != 4) {
1475                 config_msg = "Wrong number of arguments given to color command";
1476                 return ERR;
1477         }
1479         info = get_line_info(argv[0]);
1480         if (!info) {
1481                 static const struct enum_map obsolete[] = {
1482                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1483                         ENUM_MAP("main-date",   LINE_DATE),
1484                         ENUM_MAP("main-author", LINE_AUTHOR),
1485                 };
1486                 int index;
1488                 if (!map_enum(&index, obsolete, argv[0])) {
1489                         config_msg = "Unknown color name";
1490                         return ERR;
1491                 }
1492                 info = &line_info[index];
1493         }
1495         if (!set_color(&info->fg, argv[1]) ||
1496             !set_color(&info->bg, argv[2])) {
1497                 config_msg = "Unknown color";
1498                 return ERR;
1499         }
1501         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1502                 config_msg = "Unknown attribute";
1503                 return ERR;
1504         }
1506         return OK;
1509 static int parse_bool(bool *opt, const char *arg)
1511         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1512                 ? TRUE : FALSE;
1513         return OK;
1516 static int
1517 parse_string(char *opt, const char *arg, size_t optsize)
1519         int arglen = strlen(arg);
1521         switch (arg[0]) {
1522         case '\"':
1523         case '\'':
1524                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1525                         config_msg = "Unmatched quotation";
1526                         return ERR;
1527                 }
1528                 arg += 1; arglen -= 2;
1529         default:
1530                 string_ncopy_do(opt, optsize, arg, arglen);
1531                 return OK;
1532         }
1535 /* Wants: name = value */
1536 static int
1537 option_set_command(int argc, const char *argv[])
1539         if (argc != 3) {
1540                 config_msg = "Wrong number of arguments given to set command";
1541                 return ERR;
1542         }
1544         if (strcmp(argv[1], "=")) {
1545                 config_msg = "No value assigned";
1546                 return ERR;
1547         }
1549         if (!strcmp(argv[0], "show-author"))
1550                 return parse_bool(&opt_author, argv[2]);
1552         if (!strcmp(argv[0], "show-date"))
1553                 return parse_bool(&opt_date, argv[2]);
1555         if (!strcmp(argv[0], "show-rev-graph"))
1556                 return parse_bool(&opt_rev_graph, argv[2]);
1558         if (!strcmp(argv[0], "show-refs"))
1559                 return parse_bool(&opt_show_refs, argv[2]);
1561         if (!strcmp(argv[0], "show-line-numbers"))
1562                 return parse_bool(&opt_line_number, argv[2]);
1564         if (!strcmp(argv[0], "line-graphics"))
1565                 return parse_bool(&opt_line_graphics, argv[2]);
1567         if (!strcmp(argv[0], "line-number-interval"))
1568                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1570         if (!strcmp(argv[0], "author-width"))
1571                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1573         if (!strcmp(argv[0], "horizontal-scroll"))
1574                 return parse_step(&opt_hscroll, argv[2]);
1576         if (!strcmp(argv[0], "tab-size"))
1577                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1579         if (!strcmp(argv[0], "commit-encoding"))
1580                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1582         config_msg = "Unknown variable name";
1583         return ERR;
1586 /* Wants: mode request key */
1587 static int
1588 option_bind_command(int argc, const char *argv[])
1590         enum request request;
1591         int keymap;
1592         int key;
1594         if (argc < 3) {
1595                 config_msg = "Wrong number of arguments given to bind command";
1596                 return ERR;
1597         }
1599         if (set_keymap(&keymap, argv[0]) == ERR) {
1600                 config_msg = "Unknown key map";
1601                 return ERR;
1602         }
1604         key = get_key_value(argv[1]);
1605         if (key == ERR) {
1606                 config_msg = "Unknown key";
1607                 return ERR;
1608         }
1610         request = get_request(argv[2]);
1611         if (request == REQ_NONE) {
1612                 static const struct enum_map obsolete[] = {
1613                         ENUM_MAP("cherry-pick",         REQ_NONE),
1614                         ENUM_MAP("screen-resize",       REQ_NONE),
1615                         ENUM_MAP("tree-parent",         REQ_PARENT),
1616                 };
1617                 int alias;
1619                 if (map_enum(&alias, obsolete, argv[2])) {
1620                         if (alias != REQ_NONE)
1621                                 add_keybinding(keymap, alias, key);
1622                         config_msg = "Obsolete request name";
1623                         return ERR;
1624                 }
1625         }
1626         if (request == REQ_NONE && *argv[2]++ == '!')
1627                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1628         if (request == REQ_NONE) {
1629                 config_msg = "Unknown request name";
1630                 return ERR;
1631         }
1633         add_keybinding(keymap, request, key);
1635         return OK;
1638 static int
1639 set_option(const char *opt, char *value)
1641         const char *argv[SIZEOF_ARG];
1642         int argc = 0;
1644         if (!argv_from_string(argv, &argc, value)) {
1645                 config_msg = "Too many option arguments";
1646                 return ERR;
1647         }
1649         if (!strcmp(opt, "color"))
1650                 return option_color_command(argc, argv);
1652         if (!strcmp(opt, "set"))
1653                 return option_set_command(argc, argv);
1655         if (!strcmp(opt, "bind"))
1656                 return option_bind_command(argc, argv);
1658         config_msg = "Unknown option command";
1659         return ERR;
1662 static int
1663 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1665         int status = OK;
1667         config_lineno++;
1668         config_msg = "Internal error";
1670         /* Check for comment markers, since read_properties() will
1671          * only ensure opt and value are split at first " \t". */
1672         optlen = strcspn(opt, "#");
1673         if (optlen == 0)
1674                 return OK;
1676         if (opt[optlen] != 0) {
1677                 config_msg = "No option value";
1678                 status = ERR;
1680         }  else {
1681                 /* Look for comment endings in the value. */
1682                 size_t len = strcspn(value, "#");
1684                 if (len < valuelen) {
1685                         valuelen = len;
1686                         value[valuelen] = 0;
1687                 }
1689                 status = set_option(opt, value);
1690         }
1692         if (status == ERR) {
1693                 warn("Error on line %d, near '%.*s': %s",
1694                      config_lineno, (int) optlen, opt, config_msg);
1695                 config_errors = TRUE;
1696         }
1698         /* Always keep going if errors are encountered. */
1699         return OK;
1702 static void
1703 load_option_file(const char *path)
1705         struct io io = {};
1707         /* It's OK that the file doesn't exist. */
1708         if (!io_open(&io, path))
1709                 return;
1711         config_lineno = 0;
1712         config_errors = FALSE;
1714         if (io_load(&io, " \t", read_option) == ERR ||
1715             config_errors == TRUE)
1716                 warn("Errors while loading %s.", path);
1719 static int
1720 load_options(void)
1722         const char *home = getenv("HOME");
1723         const char *tigrc_user = getenv("TIGRC_USER");
1724         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1725         char buf[SIZEOF_STR];
1727         add_builtin_run_requests();
1729         if (!tigrc_system)
1730                 tigrc_system = SYSCONFDIR "/tigrc";
1731         load_option_file(tigrc_system);
1733         if (!tigrc_user) {
1734                 if (!home || !string_format(buf, "%s/.tigrc", home))
1735                         return ERR;
1736                 tigrc_user = buf;
1737         }
1738         load_option_file(tigrc_user);
1740         return OK;
1744 /*
1745  * The viewer
1746  */
1748 struct view;
1749 struct view_ops;
1751 /* The display array of active views and the index of the current view. */
1752 static struct view *display[2];
1753 static unsigned int current_view;
1755 #define foreach_displayed_view(view, i) \
1756         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1758 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1760 /* Current head and commit ID */
1761 static char ref_blob[SIZEOF_REF]        = "";
1762 static char ref_commit[SIZEOF_REF]      = "HEAD";
1763 static char ref_head[SIZEOF_REF]        = "HEAD";
1765 struct view {
1766         const char *name;       /* View name */
1767         const char *cmd_env;    /* Command line set via environment */
1768         const char *id;         /* Points to either of ref_{head,commit,blob} */
1770         struct view_ops *ops;   /* View operations */
1772         enum keymap keymap;     /* What keymap does this view have */
1773         bool git_dir;           /* Whether the view requires a git directory. */
1775         char ref[SIZEOF_REF];   /* Hovered commit reference */
1776         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1778         int height, width;      /* The width and height of the main window */
1779         WINDOW *win;            /* The main window */
1780         WINDOW *title;          /* The title window living below the main window */
1782         /* Navigation */
1783         unsigned long offset;   /* Offset of the window top */
1784         unsigned long yoffset;  /* Offset from the window side. */
1785         unsigned long lineno;   /* Current line number */
1786         unsigned long p_offset; /* Previous offset of the window top */
1787         unsigned long p_yoffset;/* Previous offset from the window side */
1788         unsigned long p_lineno; /* Previous current line number */
1789         bool p_restore;         /* Should the previous position be restored. */
1791         /* Searching */
1792         char grep[SIZEOF_STR];  /* Search string */
1793         regex_t *regex;         /* Pre-compiled regexp */
1795         /* If non-NULL, points to the view that opened this view. If this view
1796          * is closed tig will switch back to the parent view. */
1797         struct view *parent;
1799         /* Buffering */
1800         size_t lines;           /* Total number of lines */
1801         struct line *line;      /* Line index */
1802         unsigned int digits;    /* Number of digits in the lines member. */
1804         /* Drawing */
1805         struct line *curline;   /* Line currently being drawn. */
1806         enum line_type curtype; /* Attribute currently used for drawing. */
1807         unsigned long col;      /* Column when drawing. */
1808         bool has_scrolled;      /* View was scrolled. */
1810         /* Loading */
1811         struct io io;
1812         struct io *pipe;
1813         time_t start_time;
1814         time_t update_secs;
1815 };
1817 struct view_ops {
1818         /* What type of content being displayed. Used in the title bar. */
1819         const char *type;
1820         /* Default command arguments. */
1821         const char **argv;
1822         /* Open and reads in all view content. */
1823         bool (*open)(struct view *view);
1824         /* Read one line; updates view->line. */
1825         bool (*read)(struct view *view, char *data);
1826         /* Draw one line; @lineno must be < view->height. */
1827         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1828         /* Depending on view handle a special requests. */
1829         enum request (*request)(struct view *view, enum request request, struct line *line);
1830         /* Search for regexp in a line. */
1831         bool (*grep)(struct view *view, struct line *line);
1832         /* Select line */
1833         void (*select)(struct view *view, struct line *line);
1834 };
1836 static struct view_ops blame_ops;
1837 static struct view_ops blob_ops;
1838 static struct view_ops diff_ops;
1839 static struct view_ops help_ops;
1840 static struct view_ops log_ops;
1841 static struct view_ops main_ops;
1842 static struct view_ops pager_ops;
1843 static struct view_ops stage_ops;
1844 static struct view_ops status_ops;
1845 static struct view_ops tree_ops;
1846 static struct view_ops branch_ops;
1848 #define VIEW_STR(name, env, ref, ops, map, git) \
1849         { name, #env, ref, ops, map, git }
1851 #define VIEW_(id, name, ops, git, ref) \
1852         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1855 static struct view views[] = {
1856         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1857         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1858         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1859         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1860         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1861         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1862         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
1863         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1864         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1865         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1866         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1867 };
1869 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1870 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1872 #define foreach_view(view, i) \
1873         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1875 #define view_is_displayed(view) \
1876         (view == display[0] || view == display[1])
1879 enum line_graphic {
1880         LINE_GRAPHIC_VLINE
1881 };
1883 static chtype line_graphics[] = {
1884         /* LINE_GRAPHIC_VLINE: */ '|'
1885 };
1887 static inline void
1888 set_view_attr(struct view *view, enum line_type type)
1890         if (!view->curline->selected && view->curtype != type) {
1891                 wattrset(view->win, get_line_attr(type));
1892                 wchgat(view->win, -1, 0, type, NULL);
1893                 view->curtype = type;
1894         }
1897 static int
1898 draw_chars(struct view *view, enum line_type type, const char *string,
1899            int max_len, bool use_tilde)
1901         int len = 0;
1902         int col = 0;
1903         int trimmed = FALSE;
1904         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1906         if (max_len <= 0)
1907                 return 0;
1909         if (opt_utf8) {
1910                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1911         } else {
1912                 col = len = strlen(string);
1913                 if (len > max_len) {
1914                         if (use_tilde) {
1915                                 max_len -= 1;
1916                         }
1917                         col = len = max_len;
1918                         trimmed = TRUE;
1919                 }
1920         }
1922         set_view_attr(view, type);
1923         if (len > 0)
1924                 waddnstr(view->win, string, len);
1925         if (trimmed && use_tilde) {
1926                 set_view_attr(view, LINE_DELIMITER);
1927                 waddch(view->win, '~');
1928                 col++;
1929         }
1931         return col;
1934 static int
1935 draw_space(struct view *view, enum line_type type, int max, int spaces)
1937         static char space[] = "                    ";
1938         int col = 0;
1940         spaces = MIN(max, spaces);
1942         while (spaces > 0) {
1943                 int len = MIN(spaces, sizeof(space) - 1);
1945                 col += draw_chars(view, type, space, len, FALSE);
1946                 spaces -= len;
1947         }
1949         return col;
1952 static bool
1953 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1955         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1956         return view->width + view->yoffset <= view->col;
1959 static bool
1960 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1962         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1963         int max = view->width + view->yoffset - view->col;
1964         int i;
1966         if (max < size)
1967                 size = max;
1969         set_view_attr(view, type);
1970         /* Using waddch() instead of waddnstr() ensures that
1971          * they'll be rendered correctly for the cursor line. */
1972         for (i = skip; i < size; i++)
1973                 waddch(view->win, graphic[i]);
1975         view->col += size;
1976         if (size < max && skip <= size)
1977                 waddch(view->win, ' ');
1978         view->col++;
1980         return view->width + view->yoffset <= view->col;
1983 static bool
1984 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1986         int max = MIN(view->width + view->yoffset - view->col, len);
1987         int col;
1989         if (text)
1990                 col = draw_chars(view, type, text, max - 1, trim);
1991         else
1992                 col = draw_space(view, type, max - 1, max - 1);
1994         view->col += col;
1995         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1996         return view->width + view->yoffset <= view->col;
1999 static bool
2000 draw_date(struct view *view, time_t *time)
2002         const char *date = mkdate(time);
2004         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2007 static bool
2008 draw_author(struct view *view, const char *author)
2010         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2012         if (!trim) {
2013                 static char initials[10];
2014                 size_t pos;
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018                 memset(initials, 0, sizeof(initials));
2019                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020                         while (is_initial_sep(*author))
2021                                 author++;
2022                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023                         while (*author && !is_initial_sep(author[1]))
2024                                 author++;
2025                 }
2027                 author = initials;
2028         }
2030         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2033 static bool
2034 draw_mode(struct view *view, mode_t mode)
2036         const char *str;
2038         if (S_ISDIR(mode))
2039                 str = "drwxr-xr-x";
2040         else if (S_ISLNK(mode))
2041                 str = "lrwxrwxrwx";
2042         else if (S_ISGITLINK(mode))
2043                 str = "m---------";
2044         else if (S_ISREG(mode) && mode & S_IXUSR)
2045                 str = "-rwxr-xr-x";
2046         else if (S_ISREG(mode))
2047                 str = "-rw-r--r--";
2048         else
2049                 str = "----------";
2051         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2054 static bool
2055 draw_lineno(struct view *view, unsigned int lineno)
2057         char number[10];
2058         int digits3 = view->digits < 3 ? 3 : view->digits;
2059         int max = MIN(view->width + view->yoffset - view->col, digits3);
2060         char *text = NULL;
2062         lineno += view->offset + 1;
2063         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2064                 static char fmt[] = "%1ld";
2066                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2067                 if (string_format(number, fmt, lineno))
2068                         text = number;
2069         }
2070         if (text)
2071                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2072         else
2073                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2074         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2077 static bool
2078 draw_view_line(struct view *view, unsigned int lineno)
2080         struct line *line;
2081         bool selected = (view->offset + lineno == view->lineno);
2083         assert(view_is_displayed(view));
2085         if (view->offset + lineno >= view->lines)
2086                 return FALSE;
2088         line = &view->line[view->offset + lineno];
2090         wmove(view->win, lineno, 0);
2091         if (line->cleareol)
2092                 wclrtoeol(view->win);
2093         view->col = 0;
2094         view->curline = line;
2095         view->curtype = LINE_NONE;
2096         line->selected = FALSE;
2097         line->dirty = line->cleareol = 0;
2099         if (selected) {
2100                 set_view_attr(view, LINE_CURSOR);
2101                 line->selected = TRUE;
2102                 view->ops->select(view, line);
2103         }
2105         return view->ops->draw(view, line, lineno);
2108 static void
2109 redraw_view_dirty(struct view *view)
2111         bool dirty = FALSE;
2112         int lineno;
2114         for (lineno = 0; lineno < view->height; lineno++) {
2115                 if (view->offset + lineno >= view->lines)
2116                         break;
2117                 if (!view->line[view->offset + lineno].dirty)
2118                         continue;
2119                 dirty = TRUE;
2120                 if (!draw_view_line(view, lineno))
2121                         break;
2122         }
2124         if (!dirty)
2125                 return;
2126         wnoutrefresh(view->win);
2129 static void
2130 redraw_view_from(struct view *view, int lineno)
2132         assert(0 <= lineno && lineno < view->height);
2134         for (; lineno < view->height; lineno++) {
2135                 if (!draw_view_line(view, lineno))
2136                         break;
2137         }
2139         wnoutrefresh(view->win);
2142 static void
2143 redraw_view(struct view *view)
2145         werase(view->win);
2146         redraw_view_from(view, 0);
2150 static void
2151 update_view_title(struct view *view)
2153         char buf[SIZEOF_STR];
2154         char state[SIZEOF_STR];
2155         size_t bufpos = 0, statelen = 0;
2157         assert(view_is_displayed(view));
2159         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2160                 unsigned int view_lines = view->offset + view->height;
2161                 unsigned int lines = view->lines
2162                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2163                                    : 0;
2165                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2166                                    view->ops->type,
2167                                    view->lineno + 1,
2168                                    view->lines,
2169                                    lines);
2171         }
2173         if (view->pipe) {
2174                 time_t secs = time(NULL) - view->start_time;
2176                 /* Three git seconds are a long time ... */
2177                 if (secs > 2)
2178                         string_format_from(state, &statelen, " loading %lds", secs);
2179         }
2181         string_format_from(buf, &bufpos, "[%s]", view->name);
2182         if (*view->ref && bufpos < view->width) {
2183                 size_t refsize = strlen(view->ref);
2184                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2186                 if (minsize < view->width)
2187                         refsize = view->width - minsize + 7;
2188                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2189         }
2191         if (statelen && bufpos < view->width) {
2192                 string_format_from(buf, &bufpos, "%s", state);
2193         }
2195         if (view == display[current_view])
2196                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2197         else
2198                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2200         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2201         wclrtoeol(view->title);
2202         wnoutrefresh(view->title);
2205 static void
2206 resize_display(void)
2208         int offset, i;
2209         struct view *base = display[0];
2210         struct view *view = display[1] ? display[1] : display[0];
2212         /* Setup window dimensions */
2214         getmaxyx(stdscr, base->height, base->width);
2216         /* Make room for the status window. */
2217         base->height -= 1;
2219         if (view != base) {
2220                 /* Horizontal split. */
2221                 view->width   = base->width;
2222                 view->height  = SCALE_SPLIT_VIEW(base->height);
2223                 base->height -= view->height;
2225                 /* Make room for the title bar. */
2226                 view->height -= 1;
2227         }
2229         /* Make room for the title bar. */
2230         base->height -= 1;
2232         offset = 0;
2234         foreach_displayed_view (view, i) {
2235                 if (!view->win) {
2236                         view->win = newwin(view->height, 0, offset, 0);
2237                         if (!view->win)
2238                                 die("Failed to create %s view", view->name);
2240                         scrollok(view->win, FALSE);
2242                         view->title = newwin(1, 0, offset + view->height, 0);
2243                         if (!view->title)
2244                                 die("Failed to create title window");
2246                 } else {
2247                         wresize(view->win, view->height, view->width);
2248                         mvwin(view->win,   offset, 0);
2249                         mvwin(view->title, offset + view->height, 0);
2250                 }
2252                 offset += view->height + 1;
2253         }
2256 static void
2257 redraw_display(bool clear)
2259         struct view *view;
2260         int i;
2262         foreach_displayed_view (view, i) {
2263                 if (clear)
2264                         wclear(view->win);
2265                 redraw_view(view);
2266                 update_view_title(view);
2267         }
2270 static void
2271 toggle_view_option(bool *option, const char *help)
2273         *option = !*option;
2274         redraw_display(FALSE);
2275         report("%sabling %s", *option ? "En" : "Dis", help);
2278 static void
2279 maximize_view(struct view *view)
2281         memset(display, 0, sizeof(display));
2282         current_view = 0;
2283         display[current_view] = view;
2284         resize_display();
2285         redraw_display(FALSE);
2286         report("");
2290 /*
2291  * Navigation
2292  */
2294 static bool
2295 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2297         if (lineno >= view->lines)
2298                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2300         if (offset > lineno || offset + view->height <= lineno) {
2301                 unsigned long half = view->height / 2;
2303                 if (lineno > half)
2304                         offset = lineno - half;
2305                 else
2306                         offset = 0;
2307         }
2309         if (offset != view->offset || lineno != view->lineno) {
2310                 view->offset = offset;
2311                 view->lineno = lineno;
2312                 return TRUE;
2313         }
2315         return FALSE;
2318 static int
2319 apply_step(double step, int value)
2321         if (step >= 1)
2322                 return (int) step;
2323         value *= step + 0.01;
2324         return value ? value : 1;
2327 /* Scrolling backend */
2328 static void
2329 do_scroll_view(struct view *view, int lines)
2331         bool redraw_current_line = FALSE;
2333         /* The rendering expects the new offset. */
2334         view->offset += lines;
2336         assert(0 <= view->offset && view->offset < view->lines);
2337         assert(lines);
2339         /* Move current line into the view. */
2340         if (view->lineno < view->offset) {
2341                 view->lineno = view->offset;
2342                 redraw_current_line = TRUE;
2343         } else if (view->lineno >= view->offset + view->height) {
2344                 view->lineno = view->offset + view->height - 1;
2345                 redraw_current_line = TRUE;
2346         }
2348         assert(view->offset <= view->lineno && view->lineno < view->lines);
2350         /* Redraw the whole screen if scrolling is pointless. */
2351         if (view->height < ABS(lines)) {
2352                 redraw_view(view);
2354         } else {
2355                 int line = lines > 0 ? view->height - lines : 0;
2356                 int end = line + ABS(lines);
2358                 scrollok(view->win, TRUE);
2359                 wscrl(view->win, lines);
2360                 scrollok(view->win, FALSE);
2362                 while (line < end && draw_view_line(view, line))
2363                         line++;
2365                 if (redraw_current_line)
2366                         draw_view_line(view, view->lineno - view->offset);
2367                 wnoutrefresh(view->win);
2368         }
2370         view->has_scrolled = TRUE;
2371         report("");
2374 /* Scroll frontend */
2375 static void
2376 scroll_view(struct view *view, enum request request)
2378         int lines = 1;
2380         assert(view_is_displayed(view));
2382         switch (request) {
2383         case REQ_SCROLL_LEFT:
2384                 if (view->yoffset == 0) {
2385                         report("Cannot scroll beyond the first column");
2386                         return;
2387                 }
2388                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2389                         view->yoffset = 0;
2390                 else
2391                         view->yoffset -= apply_step(opt_hscroll, view->width);
2392                 redraw_view_from(view, 0);
2393                 report("");
2394                 return;
2395         case REQ_SCROLL_RIGHT:
2396                 view->yoffset += apply_step(opt_hscroll, view->width);
2397                 redraw_view(view);
2398                 report("");
2399                 return;
2400         case REQ_SCROLL_PAGE_DOWN:
2401                 lines = view->height;
2402         case REQ_SCROLL_LINE_DOWN:
2403                 if (view->offset + lines > view->lines)
2404                         lines = view->lines - view->offset;
2406                 if (lines == 0 || view->offset + view->height >= view->lines) {
2407                         report("Cannot scroll beyond the last line");
2408                         return;
2409                 }
2410                 break;
2412         case REQ_SCROLL_PAGE_UP:
2413                 lines = view->height;
2414         case REQ_SCROLL_LINE_UP:
2415                 if (lines > view->offset)
2416                         lines = view->offset;
2418                 if (lines == 0) {
2419                         report("Cannot scroll beyond the first line");
2420                         return;
2421                 }
2423                 lines = -lines;
2424                 break;
2426         default:
2427                 die("request %d not handled in switch", request);
2428         }
2430         do_scroll_view(view, lines);
2433 /* Cursor moving */
2434 static void
2435 move_view(struct view *view, enum request request)
2437         int scroll_steps = 0;
2438         int steps;
2440         switch (request) {
2441         case REQ_MOVE_FIRST_LINE:
2442                 steps = -view->lineno;
2443                 break;
2445         case REQ_MOVE_LAST_LINE:
2446                 steps = view->lines - view->lineno - 1;
2447                 break;
2449         case REQ_MOVE_PAGE_UP:
2450                 steps = view->height > view->lineno
2451                       ? -view->lineno : -view->height;
2452                 break;
2454         case REQ_MOVE_PAGE_DOWN:
2455                 steps = view->lineno + view->height >= view->lines
2456                       ? view->lines - view->lineno - 1 : view->height;
2457                 break;
2459         case REQ_MOVE_UP:
2460                 steps = -1;
2461                 break;
2463         case REQ_MOVE_DOWN:
2464                 steps = 1;
2465                 break;
2467         default:
2468                 die("request %d not handled in switch", request);
2469         }
2471         if (steps <= 0 && view->lineno == 0) {
2472                 report("Cannot move beyond the first line");
2473                 return;
2475         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2476                 report("Cannot move beyond the last line");
2477                 return;
2478         }
2480         /* Move the current line */
2481         view->lineno += steps;
2482         assert(0 <= view->lineno && view->lineno < view->lines);
2484         /* Check whether the view needs to be scrolled */
2485         if (view->lineno < view->offset ||
2486             view->lineno >= view->offset + view->height) {
2487                 scroll_steps = steps;
2488                 if (steps < 0 && -steps > view->offset) {
2489                         scroll_steps = -view->offset;
2491                 } else if (steps > 0) {
2492                         if (view->lineno == view->lines - 1 &&
2493                             view->lines > view->height) {
2494                                 scroll_steps = view->lines - view->offset - 1;
2495                                 if (scroll_steps >= view->height)
2496                                         scroll_steps -= view->height - 1;
2497                         }
2498                 }
2499         }
2501         if (!view_is_displayed(view)) {
2502                 view->offset += scroll_steps;
2503                 assert(0 <= view->offset && view->offset < view->lines);
2504                 view->ops->select(view, &view->line[view->lineno]);
2505                 return;
2506         }
2508         /* Repaint the old "current" line if we be scrolling */
2509         if (ABS(steps) < view->height)
2510                 draw_view_line(view, view->lineno - steps - view->offset);
2512         if (scroll_steps) {
2513                 do_scroll_view(view, scroll_steps);
2514                 return;
2515         }
2517         /* Draw the current line */
2518         draw_view_line(view, view->lineno - view->offset);
2520         wnoutrefresh(view->win);
2521         report("");
2525 /*
2526  * Searching
2527  */
2529 static void search_view(struct view *view, enum request request);
2531 static bool
2532 grep_text(struct view *view, const char *text[])
2534         regmatch_t pmatch;
2535         size_t i;
2537         for (i = 0; text[i]; i++)
2538                 if (*text[i] &&
2539                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2540                         return TRUE;
2541         return FALSE;
2544 static void
2545 select_view_line(struct view *view, unsigned long lineno)
2547         unsigned long old_lineno = view->lineno;
2548         unsigned long old_offset = view->offset;
2550         if (goto_view_line(view, view->offset, lineno)) {
2551                 if (view_is_displayed(view)) {
2552                         if (old_offset != view->offset) {
2553                                 redraw_view(view);
2554                         } else {
2555                                 draw_view_line(view, old_lineno - view->offset);
2556                                 draw_view_line(view, view->lineno - view->offset);
2557                                 wnoutrefresh(view->win);
2558                         }
2559                 } else {
2560                         view->ops->select(view, &view->line[view->lineno]);
2561                 }
2562         }
2565 static void
2566 find_next(struct view *view, enum request request)
2568         unsigned long lineno = view->lineno;
2569         int direction;
2571         if (!*view->grep) {
2572                 if (!*opt_search)
2573                         report("No previous search");
2574                 else
2575                         search_view(view, request);
2576                 return;
2577         }
2579         switch (request) {
2580         case REQ_SEARCH:
2581         case REQ_FIND_NEXT:
2582                 direction = 1;
2583                 break;
2585         case REQ_SEARCH_BACK:
2586         case REQ_FIND_PREV:
2587                 direction = -1;
2588                 break;
2590         default:
2591                 return;
2592         }
2594         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2595                 lineno += direction;
2597         /* Note, lineno is unsigned long so will wrap around in which case it
2598          * will become bigger than view->lines. */
2599         for (; lineno < view->lines; lineno += direction) {
2600                 if (view->ops->grep(view, &view->line[lineno])) {
2601                         select_view_line(view, lineno);
2602                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2603                         return;
2604                 }
2605         }
2607         report("No match found for '%s'", view->grep);
2610 static void
2611 search_view(struct view *view, enum request request)
2613         int regex_err;
2615         if (view->regex) {
2616                 regfree(view->regex);
2617                 *view->grep = 0;
2618         } else {
2619                 view->regex = calloc(1, sizeof(*view->regex));
2620                 if (!view->regex)
2621                         return;
2622         }
2624         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2625         if (regex_err != 0) {
2626                 char buf[SIZEOF_STR] = "unknown error";
2628                 regerror(regex_err, view->regex, buf, sizeof(buf));
2629                 report("Search failed: %s", buf);
2630                 return;
2631         }
2633         string_copy(view->grep, opt_search);
2635         find_next(view, request);
2638 /*
2639  * Incremental updating
2640  */
2642 static void
2643 reset_view(struct view *view)
2645         int i;
2647         for (i = 0; i < view->lines; i++)
2648                 free(view->line[i].data);
2649         free(view->line);
2651         view->p_offset = view->offset;
2652         view->p_yoffset = view->yoffset;
2653         view->p_lineno = view->lineno;
2655         view->line = NULL;
2656         view->offset = 0;
2657         view->yoffset = 0;
2658         view->lines  = 0;
2659         view->lineno = 0;
2660         view->vid[0] = 0;
2661         view->update_secs = 0;
2664 static void
2665 free_argv(const char *argv[])
2667         int argc;
2669         for (argc = 0; argv[argc]; argc++)
2670                 free((void *) argv[argc]);
2673 static bool
2674 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2676         char buf[SIZEOF_STR];
2677         int argc;
2678         bool noreplace = flags == FORMAT_NONE;
2680         free_argv(dst_argv);
2682         for (argc = 0; src_argv[argc]; argc++) {
2683                 const char *arg = src_argv[argc];
2684                 size_t bufpos = 0;
2686                 while (arg) {
2687                         char *next = strstr(arg, "%(");
2688                         int len = next - arg;
2689                         const char *value;
2691                         if (!next || noreplace) {
2692                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2693                                         noreplace = TRUE;
2694                                 len = strlen(arg);
2695                                 value = "";
2697                         } else if (!prefixcmp(next, "%(directory)")) {
2698                                 value = opt_path;
2700                         } else if (!prefixcmp(next, "%(file)")) {
2701                                 value = opt_file;
2703                         } else if (!prefixcmp(next, "%(ref)")) {
2704                                 value = *opt_ref ? opt_ref : "HEAD";
2706                         } else if (!prefixcmp(next, "%(head)")) {
2707                                 value = ref_head;
2709                         } else if (!prefixcmp(next, "%(commit)")) {
2710                                 value = ref_commit;
2712                         } else if (!prefixcmp(next, "%(blob)")) {
2713                                 value = ref_blob;
2715                         } else {
2716                                 report("Unknown replacement: `%s`", next);
2717                                 return FALSE;
2718                         }
2720                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2721                                 return FALSE;
2723                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2724                 }
2726                 dst_argv[argc] = strdup(buf);
2727                 if (!dst_argv[argc])
2728                         break;
2729         }
2731         dst_argv[argc] = NULL;
2733         return src_argv[argc] == NULL;
2736 static bool
2737 restore_view_position(struct view *view)
2739         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2740                 return FALSE;
2742         /* Changing the view position cancels the restoring. */
2743         /* FIXME: Changing back to the first line is not detected. */
2744         if (view->offset != 0 || view->lineno != 0) {
2745                 view->p_restore = FALSE;
2746                 return FALSE;
2747         }
2749         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2750             view_is_displayed(view))
2751                 werase(view->win);
2753         view->yoffset = view->p_yoffset;
2754         view->p_restore = FALSE;
2756         return TRUE;
2759 static void
2760 end_update(struct view *view, bool force)
2762         if (!view->pipe)
2763                 return;
2764         while (!view->ops->read(view, NULL))
2765                 if (!force)
2766                         return;
2767         set_nonblocking_input(FALSE);
2768         if (force)
2769                 kill_io(view->pipe);
2770         done_io(view->pipe);
2771         view->pipe = NULL;
2774 static void
2775 setup_update(struct view *view, const char *vid)
2777         set_nonblocking_input(TRUE);
2778         reset_view(view);
2779         string_copy_rev(view->vid, vid);
2780         view->pipe = &view->io;
2781         view->start_time = time(NULL);
2784 static bool
2785 prepare_update(struct view *view, const char *argv[], const char *dir,
2786                enum format_flags flags)
2788         if (view->pipe)
2789                 end_update(view, TRUE);
2790         return init_io_rd(&view->io, argv, dir, flags);
2793 static bool
2794 prepare_update_file(struct view *view, const char *name)
2796         if (view->pipe)
2797                 end_update(view, TRUE);
2798         return io_open(&view->io, name);
2801 static bool
2802 begin_update(struct view *view, bool refresh)
2804         if (view->pipe)
2805                 end_update(view, TRUE);
2807         if (refresh) {
2808                 if (!start_io(&view->io))
2809                         return FALSE;
2811         } else {
2812                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2813                         opt_path[0] = 0;
2815                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2816                         return FALSE;
2818                 /* Put the current ref_* value to the view title ref
2819                  * member. This is needed by the blob view. Most other
2820                  * views sets it automatically after loading because the
2821                  * first line is a commit line. */
2822                 string_copy_rev(view->ref, view->id);
2823         }
2825         setup_update(view, view->id);
2827         return TRUE;
2830 static bool
2831 update_view(struct view *view)
2833         char out_buffer[BUFSIZ * 2];
2834         char *line;
2835         /* Clear the view and redraw everything since the tree sorting
2836          * might have rearranged things. */
2837         bool redraw = view->lines == 0;
2838         bool can_read = TRUE;
2840         if (!view->pipe)
2841                 return TRUE;
2843         if (!io_can_read(view->pipe)) {
2844                 if (view->lines == 0 && view_is_displayed(view)) {
2845                         time_t secs = time(NULL) - view->start_time;
2847                         if (secs > 1 && secs > view->update_secs) {
2848                                 if (view->update_secs == 0)
2849                                         redraw_view(view);
2850                                 update_view_title(view);
2851                                 view->update_secs = secs;
2852                         }
2853                 }
2854                 return TRUE;
2855         }
2857         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2858                 if (opt_iconv != ICONV_NONE) {
2859                         ICONV_CONST char *inbuf = line;
2860                         size_t inlen = strlen(line) + 1;
2862                         char *outbuf = out_buffer;
2863                         size_t outlen = sizeof(out_buffer);
2865                         size_t ret;
2867                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2868                         if (ret != (size_t) -1)
2869                                 line = out_buffer;
2870                 }
2872                 if (!view->ops->read(view, line)) {
2873                         report("Allocation failure");
2874                         end_update(view, TRUE);
2875                         return FALSE;
2876                 }
2877         }
2879         {
2880                 unsigned long lines = view->lines;
2881                 int digits;
2883                 for (digits = 0; lines; digits++)
2884                         lines /= 10;
2886                 /* Keep the displayed view in sync with line number scaling. */
2887                 if (digits != view->digits) {
2888                         view->digits = digits;
2889                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2890                                 redraw = TRUE;
2891                 }
2892         }
2894         if (io_error(view->pipe)) {
2895                 report("Failed to read: %s", io_strerror(view->pipe));
2896                 end_update(view, TRUE);
2898         } else if (io_eof(view->pipe)) {
2899                 report("");
2900                 end_update(view, FALSE);
2901         }
2903         if (restore_view_position(view))
2904                 redraw = TRUE;
2906         if (!view_is_displayed(view))
2907                 return TRUE;
2909         if (redraw)
2910                 redraw_view_from(view, 0);
2911         else
2912                 redraw_view_dirty(view);
2914         /* Update the title _after_ the redraw so that if the redraw picks up a
2915          * commit reference in view->ref it'll be available here. */
2916         update_view_title(view);
2917         return TRUE;
2920 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2922 static struct line *
2923 add_line_data(struct view *view, void *data, enum line_type type)
2925         struct line *line;
2927         if (!realloc_lines(&view->line, view->lines, 1))
2928                 return NULL;
2930         line = &view->line[view->lines++];
2931         memset(line, 0, sizeof(*line));
2932         line->type = type;
2933         line->data = data;
2934         line->dirty = 1;
2936         return line;
2939 static struct line *
2940 add_line_text(struct view *view, const char *text, enum line_type type)
2942         char *data = text ? strdup(text) : NULL;
2944         return data ? add_line_data(view, data, type) : NULL;
2947 static struct line *
2948 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2950         char buf[SIZEOF_STR];
2951         va_list args;
2953         va_start(args, fmt);
2954         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2955                 buf[0] = 0;
2956         va_end(args);
2958         return buf[0] ? add_line_text(view, buf, type) : NULL;
2961 /*
2962  * View opening
2963  */
2965 enum open_flags {
2966         OPEN_DEFAULT = 0,       /* Use default view switching. */
2967         OPEN_SPLIT = 1,         /* Split current view. */
2968         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2969         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2970         OPEN_PREPARED = 32,     /* Open already prepared command. */
2971 };
2973 static void
2974 open_view(struct view *prev, enum request request, enum open_flags flags)
2976         bool split = !!(flags & OPEN_SPLIT);
2977         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2978         bool nomaximize = !!(flags & OPEN_REFRESH);
2979         struct view *view = VIEW(request);
2980         int nviews = displayed_views();
2981         struct view *base_view = display[0];
2983         if (view == prev && nviews == 1 && !reload) {
2984                 report("Already in %s view", view->name);
2985                 return;
2986         }
2988         if (view->git_dir && !opt_git_dir[0]) {
2989                 report("The %s view is disabled in pager view", view->name);
2990                 return;
2991         }
2993         if (split) {
2994                 display[1] = view;
2995                 current_view = 1;
2996         } else if (!nomaximize) {
2997                 /* Maximize the current view. */
2998                 memset(display, 0, sizeof(display));
2999                 current_view = 0;
3000                 display[current_view] = view;
3001         }
3003         /* Resize the view when switching between split- and full-screen,
3004          * or when switching between two different full-screen views. */
3005         if (nviews != displayed_views() ||
3006             (nviews == 1 && base_view != display[0]))
3007                 resize_display();
3009         if (view->ops->open) {
3010                 if (view->pipe)
3011                         end_update(view, TRUE);
3012                 if (!view->ops->open(view)) {
3013                         report("Failed to load %s view", view->name);
3014                         return;
3015                 }
3016                 restore_view_position(view);
3018         } else if ((reload || strcmp(view->vid, view->id)) &&
3019                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3020                 report("Failed to load %s view", view->name);
3021                 return;
3022         }
3024         if (split && prev->lineno - prev->offset >= prev->height) {
3025                 /* Take the title line into account. */
3026                 int lines = prev->lineno - prev->offset - prev->height + 1;
3028                 /* Scroll the view that was split if the current line is
3029                  * outside the new limited view. */
3030                 do_scroll_view(prev, lines);
3031         }
3033         if (prev && view != prev) {
3034                 if (split) {
3035                         /* "Blur" the previous view. */
3036                         update_view_title(prev);
3037                 }
3039                 view->parent = prev;
3040         }
3042         if (view->pipe && view->lines == 0) {
3043                 /* Clear the old view and let the incremental updating refill
3044                  * the screen. */
3045                 werase(view->win);
3046                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3047                 report("");
3048         } else if (view_is_displayed(view)) {
3049                 redraw_view(view);
3050                 report("");
3051         }
3054 static void
3055 open_external_viewer(const char *argv[], const char *dir)
3057         def_prog_mode();           /* save current tty modes */
3058         endwin();                  /* restore original tty modes */
3059         run_io_fg(argv, dir);
3060         fprintf(stderr, "Press Enter to continue");
3061         getc(opt_tty);
3062         reset_prog_mode();
3063         redraw_display(TRUE);
3066 static void
3067 open_mergetool(const char *file)
3069         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3071         open_external_viewer(mergetool_argv, opt_cdup);
3074 static void
3075 open_editor(bool from_root, const char *file)
3077         const char *editor_argv[] = { "vi", file, NULL };
3078         const char *editor;
3080         editor = getenv("GIT_EDITOR");
3081         if (!editor && *opt_editor)
3082                 editor = opt_editor;
3083         if (!editor)
3084                 editor = getenv("VISUAL");
3085         if (!editor)
3086                 editor = getenv("EDITOR");
3087         if (!editor)
3088                 editor = "vi";
3090         editor_argv[0] = editor;
3091         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3094 static void
3095 open_run_request(enum request request)
3097         struct run_request *req = get_run_request(request);
3098         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3100         if (!req) {
3101                 report("Unknown run request");
3102                 return;
3103         }
3105         if (format_argv(argv, req->argv, FORMAT_ALL))
3106                 open_external_viewer(argv, NULL);
3107         free_argv(argv);
3110 /*
3111  * User request switch noodle
3112  */
3114 static int
3115 view_driver(struct view *view, enum request request)
3117         int i;
3119         if (request == REQ_NONE)
3120                 return TRUE;
3122         if (request > REQ_NONE) {
3123                 open_run_request(request);
3124                 /* FIXME: When all views can refresh always do this. */
3125                 if (view == VIEW(REQ_VIEW_STATUS) ||
3126                     view == VIEW(REQ_VIEW_MAIN) ||
3127                     view == VIEW(REQ_VIEW_LOG) ||
3128                     view == VIEW(REQ_VIEW_BRANCH) ||
3129                     view == VIEW(REQ_VIEW_STAGE))
3130                         request = REQ_REFRESH;
3131                 else
3132                         return TRUE;
3133         }
3135         if (view && view->lines) {
3136                 request = view->ops->request(view, request, &view->line[view->lineno]);
3137                 if (request == REQ_NONE)
3138                         return TRUE;
3139         }
3141         switch (request) {
3142         case REQ_MOVE_UP:
3143         case REQ_MOVE_DOWN:
3144         case REQ_MOVE_PAGE_UP:
3145         case REQ_MOVE_PAGE_DOWN:
3146         case REQ_MOVE_FIRST_LINE:
3147         case REQ_MOVE_LAST_LINE:
3148                 move_view(view, request);
3149                 break;
3151         case REQ_SCROLL_LEFT:
3152         case REQ_SCROLL_RIGHT:
3153         case REQ_SCROLL_LINE_DOWN:
3154         case REQ_SCROLL_LINE_UP:
3155         case REQ_SCROLL_PAGE_DOWN:
3156         case REQ_SCROLL_PAGE_UP:
3157                 scroll_view(view, request);
3158                 break;
3160         case REQ_VIEW_BLAME:
3161                 if (!opt_file[0]) {
3162                         report("No file chosen, press %s to open tree view",
3163                                get_key(REQ_VIEW_TREE));
3164                         break;
3165                 }
3166                 open_view(view, request, OPEN_DEFAULT);
3167                 break;
3169         case REQ_VIEW_BLOB:
3170                 if (!ref_blob[0]) {
3171                         report("No file chosen, press %s to open tree view",
3172                                get_key(REQ_VIEW_TREE));
3173                         break;
3174                 }
3175                 open_view(view, request, OPEN_DEFAULT);
3176                 break;
3178         case REQ_VIEW_PAGER:
3179                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3180                         report("No pager content, press %s to run command from prompt",
3181                                get_key(REQ_PROMPT));
3182                         break;
3183                 }
3184                 open_view(view, request, OPEN_DEFAULT);
3185                 break;
3187         case REQ_VIEW_STAGE:
3188                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3189                         report("No stage content, press %s to open the status view and choose file",
3190                                get_key(REQ_VIEW_STATUS));
3191                         break;
3192                 }
3193                 open_view(view, request, OPEN_DEFAULT);
3194                 break;
3196         case REQ_VIEW_STATUS:
3197                 if (opt_is_inside_work_tree == FALSE) {
3198                         report("The status view requires a working tree");
3199                         break;
3200                 }
3201                 open_view(view, request, OPEN_DEFAULT);
3202                 break;
3204         case REQ_VIEW_MAIN:
3205         case REQ_VIEW_DIFF:
3206         case REQ_VIEW_LOG:
3207         case REQ_VIEW_TREE:
3208         case REQ_VIEW_HELP:
3209         case REQ_VIEW_BRANCH:
3210                 open_view(view, request, OPEN_DEFAULT);
3211                 break;
3213         case REQ_NEXT:
3214         case REQ_PREVIOUS:
3215                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3217                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3218                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3219                    (view == VIEW(REQ_VIEW_DIFF) &&
3220                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3221                    (view == VIEW(REQ_VIEW_STAGE) &&
3222                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3223                    (view == VIEW(REQ_VIEW_BLOB) &&
3224                      view->parent == VIEW(REQ_VIEW_TREE))) {
3225                         int line;
3227                         view = view->parent;
3228                         line = view->lineno;
3229                         move_view(view, request);
3230                         if (view_is_displayed(view))
3231                                 update_view_title(view);
3232                         if (line != view->lineno)
3233                                 view->ops->request(view, REQ_ENTER,
3234                                                    &view->line[view->lineno]);
3236                 } else {
3237                         move_view(view, request);
3238                 }
3239                 break;
3241         case REQ_VIEW_NEXT:
3242         {
3243                 int nviews = displayed_views();
3244                 int next_view = (current_view + 1) % nviews;
3246                 if (next_view == current_view) {
3247                         report("Only one view is displayed");
3248                         break;
3249                 }
3251                 current_view = next_view;
3252                 /* Blur out the title of the previous view. */
3253                 update_view_title(view);
3254                 report("");
3255                 break;
3256         }
3257         case REQ_REFRESH:
3258                 report("Refreshing is not yet supported for the %s view", view->name);
3259                 break;
3261         case REQ_MAXIMIZE:
3262                 if (displayed_views() == 2)
3263                         maximize_view(view);
3264                 break;
3266         case REQ_TOGGLE_LINENO:
3267                 toggle_view_option(&opt_line_number, "line numbers");
3268                 break;
3270         case REQ_TOGGLE_DATE:
3271                 toggle_view_option(&opt_date, "date display");
3272                 break;
3274         case REQ_TOGGLE_AUTHOR:
3275                 toggle_view_option(&opt_author, "author display");
3276                 break;
3278         case REQ_TOGGLE_REV_GRAPH:
3279                 toggle_view_option(&opt_rev_graph, "revision graph display");
3280                 break;
3282         case REQ_TOGGLE_REFS:
3283                 toggle_view_option(&opt_show_refs, "reference display");
3284                 break;
3286         case REQ_SEARCH:
3287         case REQ_SEARCH_BACK:
3288                 search_view(view, request);
3289                 break;
3291         case REQ_FIND_NEXT:
3292         case REQ_FIND_PREV:
3293                 find_next(view, request);
3294                 break;
3296         case REQ_STOP_LOADING:
3297                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3298                         view = &views[i];
3299                         if (view->pipe)
3300                                 report("Stopped loading the %s view", view->name),
3301                         end_update(view, TRUE);
3302                 }
3303                 break;
3305         case REQ_SHOW_VERSION:
3306                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3307                 return TRUE;
3309         case REQ_SCREEN_REDRAW:
3310                 redraw_display(TRUE);
3311                 break;
3313         case REQ_EDIT:
3314                 report("Nothing to edit");
3315                 break;
3317         case REQ_ENTER:
3318                 report("Nothing to enter");
3319                 break;
3321         case REQ_VIEW_CLOSE:
3322                 /* XXX: Mark closed views by letting view->parent point to the
3323                  * view itself. Parents to closed view should never be
3324                  * followed. */
3325                 if (view->parent &&
3326                     view->parent->parent != view->parent) {
3327                         maximize_view(view->parent);
3328                         view->parent = view;
3329                         break;
3330                 }
3331                 /* Fall-through */
3332         case REQ_QUIT:
3333                 return FALSE;
3335         default:
3336                 report("Unknown key, press 'h' for help");
3337                 return TRUE;
3338         }
3340         return TRUE;
3344 /*
3345  * View backend utilities
3346  */
3348 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3350 /* Small author cache to reduce memory consumption. It uses binary
3351  * search to lookup or find place to position new entries. No entries
3352  * are ever freed. */
3353 static const char *
3354 get_author(const char *name)
3356         static const char **authors;
3357         static size_t authors_size;
3358         int from = 0, to = authors_size - 1;
3360         while (from <= to) {
3361                 size_t pos = (to + from) / 2;
3362                 int cmp = strcmp(name, authors[pos]);
3364                 if (!cmp)
3365                         return authors[pos];
3367                 if (cmp < 0)
3368                         to = pos - 1;
3369                 else
3370                         from = pos + 1;
3371         }
3373         if (!realloc_authors(&authors, authors_size, 1))
3374                 return NULL;
3375         name = strdup(name);
3376         if (!name)
3377                 return NULL;
3379         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3380         authors[from] = name;
3381         authors_size++;
3383         return name;
3386 static void
3387 parse_timezone(time_t *time, const char *zone)
3389         long tz;
3391         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3392         tz += ('0' - zone[2]) * 60 * 60;
3393         tz += ('0' - zone[3]) * 60;
3394         tz += ('0' - zone[4]);
3396         if (zone[0] == '-')
3397                 tz = -tz;
3399         *time -= tz;
3402 /* Parse author lines where the name may be empty:
3403  *      author  <email@address.tld> 1138474660 +0100
3404  */
3405 static void
3406 parse_author_line(char *ident, const char **author, time_t *time)
3408         char *nameend = strchr(ident, '<');
3409         char *emailend = strchr(ident, '>');
3411         if (nameend && emailend)
3412                 *nameend = *emailend = 0;
3413         ident = chomp_string(ident);
3414         if (!*ident) {
3415                 if (nameend)
3416                         ident = chomp_string(nameend + 1);
3417                 if (!*ident)
3418                         ident = "Unknown";
3419         }
3421         *author = get_author(ident);
3423         /* Parse epoch and timezone */
3424         if (emailend && emailend[1] == ' ') {
3425                 char *secs = emailend + 2;
3426                 char *zone = strchr(secs, ' ');
3428                 *time = (time_t) atol(secs);
3430                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3431                         parse_timezone(time, zone + 1);
3432         }
3435 static enum input_status
3436 select_commit_parent_handler(void *data, char *buf, int c)
3438         size_t parents = *(size_t *) data;
3439         int parent = 0;
3441         if (!isdigit(c))
3442                 return INPUT_SKIP;
3444         if (*buf)
3445                 parent = atoi(buf) * 10;
3446         parent += c - '0';
3448         if (parent > parents)
3449                 return INPUT_SKIP;
3450         return INPUT_OK;
3453 static bool
3454 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3456         char buf[SIZEOF_STR * 4];
3457         const char *revlist_argv[] = {
3458                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3459         };
3460         int parents;
3462         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3463             (parents = (strlen(buf) / 40) - 1) < 0) {
3464                 report("Failed to get parent information");
3465                 return FALSE;
3467         } else if (parents == 0) {
3468                 if (path)
3469                         report("Path '%s' does not exist in the parent", path);
3470                 else
3471                         report("The selected commit has no parents");
3472                 return FALSE;
3473         }
3475         if (parents > 1) {
3476                 char prompt[SIZEOF_STR];
3477                 char *result;
3479                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3480                         return FALSE;
3481                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3482                 if (!result)
3483                         return FALSE;
3484                 parents = atoi(result);
3485         }
3487         string_copy_rev(rev, &buf[41 * parents]);
3488         return TRUE;
3491 /*
3492  * Pager backend
3493  */
3495 static bool
3496 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3498         char text[SIZEOF_STR];
3500         if (opt_line_number && draw_lineno(view, lineno))
3501                 return TRUE;
3503         string_expand(text, sizeof(text), line->data, opt_tab_size);
3504         draw_text(view, line->type, text, TRUE);
3505         return TRUE;
3508 static bool
3509 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3511         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3512         char ref[SIZEOF_STR];
3514         if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3515                 return TRUE;
3517         /* This is the only fatal call, since it can "corrupt" the buffer. */
3518         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3519                 return FALSE;
3521         return TRUE;
3524 static void
3525 add_pager_refs(struct view *view, struct line *line)
3527         char buf[SIZEOF_STR];
3528         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3529         struct ref **refs;
3530         size_t bufpos = 0, refpos = 0;
3531         const char *sep = "Refs: ";
3532         bool is_tag = FALSE;
3534         assert(line->type == LINE_COMMIT);
3536         refs = get_refs(commit_id);
3537         if (!refs) {
3538                 if (view == VIEW(REQ_VIEW_DIFF))
3539                         goto try_add_describe_ref;
3540                 return;
3541         }
3543         do {
3544                 struct ref *ref = refs[refpos];
3545                 const char *fmt = ref->tag    ? "%s[%s]" :
3546                                   ref->remote ? "%s<%s>" : "%s%s";
3548                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3549                         return;
3550                 sep = ", ";
3551                 if (ref->tag)
3552                         is_tag = TRUE;
3553         } while (refs[refpos++]->next);
3555         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3556 try_add_describe_ref:
3557                 /* Add <tag>-g<commit_id> "fake" reference. */
3558                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3559                         return;
3560         }
3562         if (bufpos == 0)
3563                 return;
3565         add_line_text(view, buf, LINE_PP_REFS);
3568 static bool
3569 pager_read(struct view *view, char *data)
3571         struct line *line;
3573         if (!data)
3574                 return TRUE;
3576         line = add_line_text(view, data, get_line_type(data));
3577         if (!line)
3578                 return FALSE;
3580         if (line->type == LINE_COMMIT &&
3581             (view == VIEW(REQ_VIEW_DIFF) ||
3582              view == VIEW(REQ_VIEW_LOG)))
3583                 add_pager_refs(view, line);
3585         return TRUE;
3588 static enum request
3589 pager_request(struct view *view, enum request request, struct line *line)
3591         int split = 0;
3593         if (request != REQ_ENTER)
3594                 return request;
3596         if (line->type == LINE_COMMIT &&
3597            (view == VIEW(REQ_VIEW_LOG) ||
3598             view == VIEW(REQ_VIEW_PAGER))) {
3599                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3600                 split = 1;
3601         }
3603         /* Always scroll the view even if it was split. That way
3604          * you can use Enter to scroll through the log view and
3605          * split open each commit diff. */
3606         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3608         /* FIXME: A minor workaround. Scrolling the view will call report("")
3609          * but if we are scrolling a non-current view this won't properly
3610          * update the view title. */
3611         if (split)
3612                 update_view_title(view);
3614         return REQ_NONE;
3617 static bool
3618 pager_grep(struct view *view, struct line *line)
3620         const char *text[] = { line->data, NULL };
3622         return grep_text(view, text);
3625 static void
3626 pager_select(struct view *view, struct line *line)
3628         if (line->type == LINE_COMMIT) {
3629                 char *text = (char *)line->data + STRING_SIZE("commit ");
3631                 if (view != VIEW(REQ_VIEW_PAGER))
3632                         string_copy_rev(view->ref, text);
3633                 string_copy_rev(ref_commit, text);
3634         }
3637 static struct view_ops pager_ops = {
3638         "line",
3639         NULL,
3640         NULL,
3641         pager_read,
3642         pager_draw,
3643         pager_request,
3644         pager_grep,
3645         pager_select,
3646 };
3648 static const char *log_argv[SIZEOF_ARG] = {
3649         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3650 };
3652 static enum request
3653 log_request(struct view *view, enum request request, struct line *line)
3655         switch (request) {
3656         case REQ_REFRESH:
3657                 load_refs();
3658                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3659                 return REQ_NONE;
3660         default:
3661                 return pager_request(view, request, line);
3662         }
3665 static struct view_ops log_ops = {
3666         "line",
3667         log_argv,
3668         NULL,
3669         pager_read,
3670         pager_draw,
3671         log_request,
3672         pager_grep,
3673         pager_select,
3674 };
3676 static const char *diff_argv[SIZEOF_ARG] = {
3677         "git", "show", "--pretty=fuller", "--no-color", "--root",
3678                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3679 };
3681 static struct view_ops diff_ops = {
3682         "line",
3683         diff_argv,
3684         NULL,
3685         pager_read,
3686         pager_draw,
3687         pager_request,
3688         pager_grep,
3689         pager_select,
3690 };
3692 /*
3693  * Help backend
3694  */
3696 static bool
3697 help_open(struct view *view)
3699         char buf[SIZEOF_STR];
3700         size_t bufpos;
3701         int i;
3703         if (view->lines > 0)
3704                 return TRUE;
3706         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3708         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3709                 const char *key;
3711                 if (req_info[i].request == REQ_NONE)
3712                         continue;
3714                 if (!req_info[i].request) {
3715                         add_line_text(view, "", LINE_DEFAULT);
3716                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3717                         continue;
3718                 }
3720                 key = get_key(req_info[i].request);
3721                 if (!*key)
3722                         key = "(no key defined)";
3724                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3725                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3726                         if (buf[bufpos] == '_')
3727                                 buf[bufpos] = '-';
3728                 }
3730                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3731                                 key, buf, req_info[i].help);
3732         }
3734         if (run_requests) {
3735                 add_line_text(view, "", LINE_DEFAULT);
3736                 add_line_text(view, "External commands:", LINE_DEFAULT);
3737         }
3739         for (i = 0; i < run_requests; i++) {
3740                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3741                 const char *key;
3742                 int argc;
3744                 if (!req)
3745                         continue;
3747                 key = get_key_name(req->key);
3748                 if (!*key)
3749                         key = "(no key defined)";
3751                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3752                         if (!string_format_from(buf, &bufpos, "%s%s",
3753                                                 argc ? " " : "", req->argv[argc]))
3754                                 return REQ_NONE;
3756                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3757                                 keymap_table[req->keymap].name, key, buf);
3758         }
3760         return TRUE;
3763 static struct view_ops help_ops = {
3764         "line",
3765         NULL,
3766         help_open,
3767         NULL,
3768         pager_draw,
3769         pager_request,
3770         pager_grep,
3771         pager_select,
3772 };
3775 /*
3776  * Tree backend
3777  */
3779 struct tree_stack_entry {
3780         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3781         unsigned long lineno;           /* Line number to restore */
3782         char *name;                     /* Position of name in opt_path */
3783 };
3785 /* The top of the path stack. */
3786 static struct tree_stack_entry *tree_stack = NULL;
3787 unsigned long tree_lineno = 0;
3789 static void
3790 pop_tree_stack_entry(void)
3792         struct tree_stack_entry *entry = tree_stack;
3794         tree_lineno = entry->lineno;
3795         entry->name[0] = 0;
3796         tree_stack = entry->prev;
3797         free(entry);
3800 static void
3801 push_tree_stack_entry(const char *name, unsigned long lineno)
3803         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3804         size_t pathlen = strlen(opt_path);
3806         if (!entry)
3807                 return;
3809         entry->prev = tree_stack;
3810         entry->name = opt_path + pathlen;
3811         tree_stack = entry;
3813         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3814                 pop_tree_stack_entry();
3815                 return;
3816         }
3818         /* Move the current line to the first tree entry. */
3819         tree_lineno = 1;
3820         entry->lineno = lineno;
3823 /* Parse output from git-ls-tree(1):
3824  *
3825  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3826  */
3828 #define SIZEOF_TREE_ATTR \
3829         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3831 #define SIZEOF_TREE_MODE \
3832         STRING_SIZE("100644 ")
3834 #define TREE_ID_OFFSET \
3835         STRING_SIZE("100644 blob ")
3837 struct tree_entry {
3838         char id[SIZEOF_REV];
3839         mode_t mode;
3840         time_t time;                    /* Date from the author ident. */
3841         const char *author;             /* Author of the commit. */
3842         char name[1];
3843 };
3845 static const char *
3846 tree_path(struct line *line)
3848         return ((struct tree_entry *) line->data)->name;
3852 static int
3853 tree_compare_entry(struct line *line1, struct line *line2)
3855         if (line1->type != line2->type)
3856                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3857         return strcmp(tree_path(line1), tree_path(line2));
3860 static struct line *
3861 tree_entry(struct view *view, enum line_type type, const char *path,
3862            const char *mode, const char *id)
3864         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3865         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3867         if (!entry || !line) {
3868                 free(entry);
3869                 return NULL;
3870         }
3872         strncpy(entry->name, path, strlen(path));
3873         if (mode)
3874                 entry->mode = strtoul(mode, NULL, 8);
3875         if (id)
3876                 string_copy_rev(entry->id, id);
3878         return line;
3881 static bool
3882 tree_read_date(struct view *view, char *text, bool *read_date)
3884         static const char *author_name;
3885         static time_t author_time;
3887         if (!text && *read_date) {
3888                 *read_date = FALSE;
3889                 return TRUE;
3891         } else if (!text) {
3892                 char *path = *opt_path ? opt_path : ".";
3893                 /* Find next entry to process */
3894                 const char *log_file[] = {
3895                         "git", "log", "--no-color", "--pretty=raw",
3896                                 "--cc", "--raw", view->id, "--", path, NULL
3897                 };
3898                 struct io io = {};
3900                 if (!view->lines) {
3901                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3902                         report("Tree is empty");
3903                         return TRUE;
3904                 }
3906                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3907                         report("Failed to load tree data");
3908                         return TRUE;
3909                 }
3911                 done_io(view->pipe);
3912                 view->io = io;
3913                 *read_date = TRUE;
3914                 return FALSE;
3916         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3917                 parse_author_line(text + STRING_SIZE("author "),
3918                                   &author_name, &author_time);
3920         } else if (*text == ':') {
3921                 char *pos;
3922                 size_t annotated = 1;
3923                 size_t i;
3925                 pos = strchr(text, '\t');
3926                 if (!pos)
3927                         return TRUE;
3928                 text = pos + 1;
3929                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3930                         text += strlen(opt_prefix);
3931                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3932                         text += strlen(opt_path);
3933                 pos = strchr(text, '/');
3934                 if (pos)
3935                         *pos = 0;
3937                 for (i = 1; i < view->lines; i++) {
3938                         struct line *line = &view->line[i];
3939                         struct tree_entry *entry = line->data;
3941                         annotated += !!entry->author;
3942                         if (entry->author || strcmp(entry->name, text))
3943                                 continue;
3945                         entry->author = author_name;
3946                         entry->time = author_time;
3947                         line->dirty = 1;
3948                         break;
3949                 }
3951                 if (annotated == view->lines)
3952                         kill_io(view->pipe);
3953         }
3954         return TRUE;
3957 static bool
3958 tree_read(struct view *view, char *text)
3960         static bool read_date = FALSE;
3961         struct tree_entry *data;
3962         struct line *entry, *line;
3963         enum line_type type;
3964         size_t textlen = text ? strlen(text) : 0;
3965         char *path = text + SIZEOF_TREE_ATTR;
3967         if (read_date || !text)
3968                 return tree_read_date(view, text, &read_date);
3970         if (textlen <= SIZEOF_TREE_ATTR)
3971                 return FALSE;
3972         if (view->lines == 0 &&
3973             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3974                 return FALSE;
3976         /* Strip the path part ... */
3977         if (*opt_path) {
3978                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3979                 size_t striplen = strlen(opt_path);
3981                 if (pathlen > striplen)
3982                         memmove(path, path + striplen,
3983                                 pathlen - striplen + 1);
3985                 /* Insert "link" to parent directory. */
3986                 if (view->lines == 1 &&
3987                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3988                         return FALSE;
3989         }
3991         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3992         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3993         if (!entry)
3994                 return FALSE;
3995         data = entry->data;
3997         /* Skip "Directory ..." and ".." line. */
3998         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3999                 if (tree_compare_entry(line, entry) <= 0)
4000                         continue;
4002                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4004                 line->data = data;
4005                 line->type = type;
4006                 for (; line <= entry; line++)
4007                         line->dirty = line->cleareol = 1;
4008                 return TRUE;
4009         }
4011         if (tree_lineno > view->lineno) {
4012                 view->lineno = tree_lineno;
4013                 tree_lineno = 0;
4014         }
4016         return TRUE;
4019 static bool
4020 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4022         struct tree_entry *entry = line->data;
4024         if (line->type == LINE_TREE_HEAD) {
4025                 if (draw_text(view, line->type, "Directory path /", TRUE))
4026                         return TRUE;
4027         } else {
4028                 if (draw_mode(view, entry->mode))
4029                         return TRUE;
4031                 if (opt_author && draw_author(view, entry->author))
4032                         return TRUE;
4034                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4035                         return TRUE;
4036         }
4037         if (draw_text(view, line->type, entry->name, TRUE))
4038                 return TRUE;
4039         return TRUE;
4042 static void
4043 open_blob_editor()
4045         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4046         int fd = mkstemp(file);
4048         if (fd == -1)
4049                 report("Failed to create temporary file");
4050         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4051                 report("Failed to save blob data to file");
4052         else
4053                 open_editor(FALSE, file);
4054         if (fd != -1)
4055                 unlink(file);
4058 static enum request
4059 tree_request(struct view *view, enum request request, struct line *line)
4061         enum open_flags flags;
4063         switch (request) {
4064         case REQ_VIEW_BLAME:
4065                 if (line->type != LINE_TREE_FILE) {
4066                         report("Blame only supported for files");
4067                         return REQ_NONE;
4068                 }
4070                 string_copy(opt_ref, view->vid);
4071                 return request;
4073         case REQ_EDIT:
4074                 if (line->type != LINE_TREE_FILE) {
4075                         report("Edit only supported for files");
4076                 } else if (!is_head_commit(view->vid)) {
4077                         open_blob_editor();
4078                 } else {
4079                         open_editor(TRUE, opt_file);
4080                 }
4081                 return REQ_NONE;
4083         case REQ_PARENT:
4084                 if (!*opt_path) {
4085                         /* quit view if at top of tree */
4086                         return REQ_VIEW_CLOSE;
4087                 }
4088                 /* fake 'cd  ..' */
4089                 line = &view->line[1];
4090                 break;
4092         case REQ_ENTER:
4093                 break;
4095         default:
4096                 return request;
4097         }
4099         /* Cleanup the stack if the tree view is at a different tree. */
4100         while (!*opt_path && tree_stack)
4101                 pop_tree_stack_entry();
4103         switch (line->type) {
4104         case LINE_TREE_DIR:
4105                 /* Depending on whether it is a subdirectory or parent link
4106                  * mangle the path buffer. */
4107                 if (line == &view->line[1] && *opt_path) {
4108                         pop_tree_stack_entry();
4110                 } else {
4111                         const char *basename = tree_path(line);
4113                         push_tree_stack_entry(basename, view->lineno);
4114                 }
4116                 /* Trees and subtrees share the same ID, so they are not not
4117                  * unique like blobs. */
4118                 flags = OPEN_RELOAD;
4119                 request = REQ_VIEW_TREE;
4120                 break;
4122         case LINE_TREE_FILE:
4123                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4124                 request = REQ_VIEW_BLOB;
4125                 break;
4127         default:
4128                 return REQ_NONE;
4129         }
4131         open_view(view, request, flags);
4132         if (request == REQ_VIEW_TREE)
4133                 view->lineno = tree_lineno;
4135         return REQ_NONE;
4138 static bool
4139 tree_grep(struct view *view, struct line *line)
4141         struct tree_entry *entry = line->data;
4142         const char *text[] = {
4143                 entry->name,
4144                 opt_author ? entry->author : "",
4145                 opt_date ? mkdate(&entry->time) : "",
4146                 NULL
4147         };
4149         return grep_text(view, text);
4152 static void
4153 tree_select(struct view *view, struct line *line)
4155         struct tree_entry *entry = line->data;
4157         if (line->type == LINE_TREE_FILE) {
4158                 string_copy_rev(ref_blob, entry->id);
4159                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4161         } else if (line->type != LINE_TREE_DIR) {
4162                 return;
4163         }
4165         string_copy_rev(view->ref, entry->id);
4168 static const char *tree_argv[SIZEOF_ARG] = {
4169         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4170 };
4172 static struct view_ops tree_ops = {
4173         "file",
4174         tree_argv,
4175         NULL,
4176         tree_read,
4177         tree_draw,
4178         tree_request,
4179         tree_grep,
4180         tree_select,
4181 };
4183 static bool
4184 blob_read(struct view *view, char *line)
4186         if (!line)
4187                 return TRUE;
4188         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4191 static enum request
4192 blob_request(struct view *view, enum request request, struct line *line)
4194         switch (request) {
4195         case REQ_EDIT:
4196                 open_blob_editor();
4197                 return REQ_NONE;
4198         default:
4199                 return pager_request(view, request, line);
4200         }
4203 static const char *blob_argv[SIZEOF_ARG] = {
4204         "git", "cat-file", "blob", "%(blob)", NULL
4205 };
4207 static struct view_ops blob_ops = {
4208         "line",
4209         blob_argv,
4210         NULL,
4211         blob_read,
4212         pager_draw,
4213         blob_request,
4214         pager_grep,
4215         pager_select,
4216 };
4218 /*
4219  * Blame backend
4220  *
4221  * Loading the blame view is a two phase job:
4222  *
4223  *  1. File content is read either using opt_file from the
4224  *     filesystem or using git-cat-file.
4225  *  2. Then blame information is incrementally added by
4226  *     reading output from git-blame.
4227  */
4229 static const char *blame_head_argv[] = {
4230         "git", "blame", "--incremental", "--", "%(file)", NULL
4231 };
4233 static const char *blame_ref_argv[] = {
4234         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4235 };
4237 static const char *blame_cat_file_argv[] = {
4238         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4239 };
4241 struct blame_commit {
4242         char id[SIZEOF_REV];            /* SHA1 ID. */
4243         char title[128];                /* First line of the commit message. */
4244         const char *author;             /* Author of the commit. */
4245         time_t time;                    /* Date from the author ident. */
4246         char filename[128];             /* Name of file. */
4247         bool has_previous;              /* Was a "previous" line detected. */
4248 };
4250 struct blame {
4251         struct blame_commit *commit;
4252         unsigned long lineno;
4253         char text[1];
4254 };
4256 static bool
4257 blame_open(struct view *view)
4259         if (*opt_ref || !io_open(&view->io, opt_file)) {
4260                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4261                         return FALSE;
4262         }
4264         setup_update(view, opt_file);
4265         string_format(view->ref, "%s ...", opt_file);
4267         return TRUE;
4270 static struct blame_commit *
4271 get_blame_commit(struct view *view, const char *id)
4273         size_t i;
4275         for (i = 0; i < view->lines; i++) {
4276                 struct blame *blame = view->line[i].data;
4278                 if (!blame->commit)
4279                         continue;
4281                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4282                         return blame->commit;
4283         }
4285         {
4286                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4288                 if (commit)
4289                         string_ncopy(commit->id, id, SIZEOF_REV);
4290                 return commit;
4291         }
4294 static bool
4295 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4297         const char *pos = *posref;
4299         *posref = NULL;
4300         pos = strchr(pos + 1, ' ');
4301         if (!pos || !isdigit(pos[1]))
4302                 return FALSE;
4303         *number = atoi(pos + 1);
4304         if (*number < min || *number > max)
4305                 return FALSE;
4307         *posref = pos;
4308         return TRUE;
4311 static struct blame_commit *
4312 parse_blame_commit(struct view *view, const char *text, int *blamed)
4314         struct blame_commit *commit;
4315         struct blame *blame;
4316         const char *pos = text + SIZEOF_REV - 2;
4317         size_t orig_lineno = 0;
4318         size_t lineno;
4319         size_t group;
4321         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4322                 return NULL;
4324         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4325             !parse_number(&pos, &lineno, 1, view->lines) ||
4326             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4327                 return NULL;
4329         commit = get_blame_commit(view, text);
4330         if (!commit)
4331                 return NULL;
4333         *blamed += group;
4334         while (group--) {
4335                 struct line *line = &view->line[lineno + group - 1];
4337                 blame = line->data;
4338                 blame->commit = commit;
4339                 blame->lineno = orig_lineno + group - 1;
4340                 line->dirty = 1;
4341         }
4343         return commit;
4346 static bool
4347 blame_read_file(struct view *view, const char *line, bool *read_file)
4349         if (!line) {
4350                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4351                 struct io io = {};
4353                 if (view->lines == 0 && !view->parent)
4354                         die("No blame exist for %s", view->vid);
4356                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4357                         report("Failed to load blame data");
4358                         return TRUE;
4359                 }
4361                 done_io(view->pipe);
4362                 view->io = io;
4363                 *read_file = FALSE;
4364                 return FALSE;
4366         } else {
4367                 size_t linelen = strlen(line);
4368                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4370                 if (!blame)
4371                         return FALSE;
4373                 blame->commit = NULL;
4374                 strncpy(blame->text, line, linelen);
4375                 blame->text[linelen] = 0;
4376                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4377         }
4380 static bool
4381 match_blame_header(const char *name, char **line)
4383         size_t namelen = strlen(name);
4384         bool matched = !strncmp(name, *line, namelen);
4386         if (matched)
4387                 *line += namelen;
4389         return matched;
4392 static bool
4393 blame_read(struct view *view, char *line)
4395         static struct blame_commit *commit = NULL;
4396         static int blamed = 0;
4397         static bool read_file = TRUE;
4399         if (read_file)
4400                 return blame_read_file(view, line, &read_file);
4402         if (!line) {
4403                 /* Reset all! */
4404                 commit = NULL;
4405                 blamed = 0;
4406                 read_file = TRUE;
4407                 string_format(view->ref, "%s", view->vid);
4408                 if (view_is_displayed(view)) {
4409                         update_view_title(view);
4410                         redraw_view_from(view, 0);
4411                 }
4412                 return TRUE;
4413         }
4415         if (!commit) {
4416                 commit = parse_blame_commit(view, line, &blamed);
4417                 string_format(view->ref, "%s %2d%%", view->vid,
4418                               view->lines ? blamed * 100 / view->lines : 0);
4420         } else if (match_blame_header("author ", &line)) {
4421                 commit->author = get_author(line);
4423         } else if (match_blame_header("author-time ", &line)) {
4424                 commit->time = (time_t) atol(line);
4426         } else if (match_blame_header("author-tz ", &line)) {
4427                 parse_timezone(&commit->time, line);
4429         } else if (match_blame_header("summary ", &line)) {
4430                 string_ncopy(commit->title, line, strlen(line));
4432         } else if (match_blame_header("previous ", &line)) {
4433                 commit->has_previous = TRUE;
4435         } else if (match_blame_header("filename ", &line)) {
4436                 string_ncopy(commit->filename, line, strlen(line));
4437                 commit = NULL;
4438         }
4440         return TRUE;
4443 static bool
4444 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4446         struct blame *blame = line->data;
4447         time_t *time = NULL;
4448         const char *id = NULL, *author = NULL;
4449         char text[SIZEOF_STR];
4451         if (blame->commit && *blame->commit->filename) {
4452                 id = blame->commit->id;
4453                 author = blame->commit->author;
4454                 time = &blame->commit->time;
4455         }
4457         if (opt_date && draw_date(view, time))
4458                 return TRUE;
4460         if (opt_author && draw_author(view, author))
4461                 return TRUE;
4463         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4464                 return TRUE;
4466         if (draw_lineno(view, lineno))
4467                 return TRUE;
4469         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4470         draw_text(view, LINE_DEFAULT, text, TRUE);
4471         return TRUE;
4474 static bool
4475 check_blame_commit(struct blame *blame, bool check_null_id)
4477         if (!blame->commit)
4478                 report("Commit data not loaded yet");
4479         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4480                 report("No commit exist for the selected line");
4481         else
4482                 return TRUE;
4483         return FALSE;
4486 static void
4487 setup_blame_parent_line(struct view *view, struct blame *blame)
4489         const char *diff_tree_argv[] = {
4490                 "git", "diff-tree", "-U0", blame->commit->id,
4491                         "--", blame->commit->filename, NULL
4492         };
4493         struct io io = {};
4494         int parent_lineno = -1;
4495         int blamed_lineno = -1;
4496         char *line;
4498         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4499                 return;
4501         while ((line = io_get(&io, '\n', TRUE))) {
4502                 if (*line == '@') {
4503                         char *pos = strchr(line, '+');
4505                         parent_lineno = atoi(line + 4);
4506                         if (pos)
4507                                 blamed_lineno = atoi(pos + 1);
4509                 } else if (*line == '+' && parent_lineno != -1) {
4510                         if (blame->lineno == blamed_lineno - 1 &&
4511                             !strcmp(blame->text, line + 1)) {
4512                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4513                                 break;
4514                         }
4515                         blamed_lineno++;
4516                 }
4517         }
4519         done_io(&io);
4522 static enum request
4523 blame_request(struct view *view, enum request request, struct line *line)
4525         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4526         struct blame *blame = line->data;
4528         switch (request) {
4529         case REQ_VIEW_BLAME:
4530                 if (check_blame_commit(blame, TRUE)) {
4531                         string_copy(opt_ref, blame->commit->id);
4532                         string_copy(opt_file, blame->commit->filename);
4533                         if (blame->lineno)
4534                                 view->lineno = blame->lineno;
4535                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4536                 }
4537                 break;
4539         case REQ_PARENT:
4540                 if (check_blame_commit(blame, TRUE) &&
4541                     select_commit_parent(blame->commit->id, opt_ref,
4542                                          blame->commit->filename)) {
4543                         string_copy(opt_file, blame->commit->filename);
4544                         setup_blame_parent_line(view, blame);
4545                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4546                 }
4547                 break;
4549         case REQ_ENTER:
4550                 if (!check_blame_commit(blame, FALSE))
4551                         break;
4553                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4554                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4555                         break;
4557                 if (!strcmp(blame->commit->id, NULL_ID)) {
4558                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4559                         const char *diff_index_argv[] = {
4560                                 "git", "diff-index", "--root", "--patch-with-stat",
4561                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4562                         };
4564                         if (!blame->commit->has_previous) {
4565                                 diff_index_argv[1] = "diff";
4566                                 diff_index_argv[2] = "--no-color";
4567                                 diff_index_argv[6] = "--";
4568                                 diff_index_argv[7] = "/dev/null";
4569                         }
4571                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4572                                 report("Failed to allocate diff command");
4573                                 break;
4574                         }
4575                         flags |= OPEN_PREPARED;
4576                 }
4578                 open_view(view, REQ_VIEW_DIFF, flags);
4579                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4580                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4581                 break;
4583         default:
4584                 return request;
4585         }
4587         return REQ_NONE;
4590 static bool
4591 blame_grep(struct view *view, struct line *line)
4593         struct blame *blame = line->data;
4594         struct blame_commit *commit = blame->commit;
4595         const char *text[] = {
4596                 blame->text,
4597                 commit ? commit->title : "",
4598                 commit ? commit->id : "",
4599                 commit && opt_author ? commit->author : "",
4600                 commit && opt_date ? mkdate(&commit->time) : "",
4601                 NULL
4602         };
4604         return grep_text(view, text);
4607 static void
4608 blame_select(struct view *view, struct line *line)
4610         struct blame *blame = line->data;
4611         struct blame_commit *commit = blame->commit;
4613         if (!commit)
4614                 return;
4616         if (!strcmp(commit->id, NULL_ID))
4617                 string_ncopy(ref_commit, "HEAD", 4);
4618         else
4619                 string_copy_rev(ref_commit, commit->id);
4622 static struct view_ops blame_ops = {
4623         "line",
4624         NULL,
4625         blame_open,
4626         blame_read,
4627         blame_draw,
4628         blame_request,
4629         blame_grep,
4630         blame_select,
4631 };
4633 /*
4634  * Branch backend
4635  */
4637 struct branch {
4638         const char *author;             /* Author of the last commit. */
4639         time_t time;                    /* Date of the last activity. */
4640         struct ref *ref;                /* Name and commit ID information. */
4641 };
4643 static bool
4644 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4646         struct branch *branch = line->data;
4647         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4649         if (opt_date && draw_date(view, &branch->time))
4650                 return TRUE;
4652         if (opt_author && draw_author(view, branch->author))
4653                 return TRUE;
4655         draw_text(view, type, branch->ref->name, TRUE);
4656         return TRUE;
4659 static enum request
4660 branch_request(struct view *view, enum request request, struct line *line)
4662         switch (request) {
4663         case REQ_REFRESH:
4664                 load_refs();
4665                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4666                 return REQ_NONE;
4668         case REQ_ENTER:
4669                 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4670                 return REQ_NONE;
4672         default:
4673                 return request;
4674         }
4677 static bool
4678 branch_read(struct view *view, char *line)
4680         static char id[SIZEOF_REV];
4681         size_t i;
4683         if (!line)
4684                 return TRUE;
4686         switch (get_line_type(line)) {
4687         case LINE_COMMIT:
4688                 string_copy_rev(id, line + STRING_SIZE("commit "));
4689                 return TRUE;
4691         case LINE_AUTHOR:
4692                 for (i = 0; i < view->lines; i++) {
4693                         struct branch *branch = view->line[i].data;
4695                         if (strcmp(branch->ref->id, id))
4696                                 continue;
4698                         parse_author_line(line + STRING_SIZE("author "),
4699                                           &branch->author, &branch->time);
4700                         view->line[i].dirty = TRUE;
4701                 }
4702                 return TRUE;
4704         default:
4705                 return TRUE;
4706         }
4710 static bool
4711 branch_open_visitor(void *data, struct ref *ref)
4713         struct view *view = data;
4714         struct branch *branch;
4716         if (ref->tag || ref->ltag || ref->remote)
4717                 return TRUE;
4719         branch = calloc(1, sizeof(*branch));
4720         if (!branch)
4721                 return FALSE;
4723         branch->ref = ref;
4724         return !!add_line_data(view, branch, LINE_DEFAULT);
4727 static bool
4728 branch_open(struct view *view)
4730         const char *branch_log[] = {
4731                 "git", "log", "--no-color", "--pretty=raw",
4732                         "--simplify-by-decoration", "--all", NULL
4733         };
4735         if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4736                 report("Failed to load branch data");
4737                 return TRUE;
4738         }
4740         setup_update(view, view->id);
4741         foreach_ref(branch_open_visitor, view);
4743         return TRUE;
4746 static bool
4747 branch_grep(struct view *view, struct line *line)
4749         struct branch *branch = line->data;
4750         const char *text[] = {
4751                 branch->ref->name,
4752                 branch->author,
4753                 NULL
4754         };
4756         return grep_text(view, text);
4759 static void
4760 branch_select(struct view *view, struct line *line)
4762         struct branch *branch = line->data;
4764         string_copy_rev(view->ref, branch->ref->id);
4765         string_copy_rev(ref_commit, branch->ref->id);
4766         string_copy_rev(ref_head, branch->ref->id);
4769 static struct view_ops branch_ops = {
4770         "branch",
4771         NULL,
4772         branch_open,
4773         branch_read,
4774         branch_draw,
4775         branch_request,
4776         branch_grep,
4777         branch_select,
4778 };
4780 /*
4781  * Status backend
4782  */
4784 struct status {
4785         char status;
4786         struct {
4787                 mode_t mode;
4788                 char rev[SIZEOF_REV];
4789                 char name[SIZEOF_STR];
4790         } old;
4791         struct {
4792                 mode_t mode;
4793                 char rev[SIZEOF_REV];
4794                 char name[SIZEOF_STR];
4795         } new;
4796 };
4798 static char status_onbranch[SIZEOF_STR];
4799 static struct status stage_status;
4800 static enum line_type stage_line_type;
4801 static size_t stage_chunks;
4802 static int *stage_chunk;
4804 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4806 /* This should work even for the "On branch" line. */
4807 static inline bool
4808 status_has_none(struct view *view, struct line *line)
4810         return line < view->line + view->lines && !line[1].data;
4813 /* Get fields from the diff line:
4814  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4815  */
4816 static inline bool
4817 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4819         const char *old_mode = buf +  1;
4820         const char *new_mode = buf +  8;
4821         const char *old_rev  = buf + 15;
4822         const char *new_rev  = buf + 56;
4823         const char *status   = buf + 97;
4825         if (bufsize < 98 ||
4826             old_mode[-1] != ':' ||
4827             new_mode[-1] != ' ' ||
4828             old_rev[-1]  != ' ' ||
4829             new_rev[-1]  != ' ' ||
4830             status[-1]   != ' ')
4831                 return FALSE;
4833         file->status = *status;
4835         string_copy_rev(file->old.rev, old_rev);
4836         string_copy_rev(file->new.rev, new_rev);
4838         file->old.mode = strtoul(old_mode, NULL, 8);
4839         file->new.mode = strtoul(new_mode, NULL, 8);
4841         file->old.name[0] = file->new.name[0] = 0;
4843         return TRUE;
4846 static bool
4847 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4849         struct status *unmerged = NULL;
4850         char *buf;
4851         struct io io = {};
4853         if (!run_io(&io, argv, NULL, IO_RD))
4854                 return FALSE;
4856         add_line_data(view, NULL, type);
4858         while ((buf = io_get(&io, 0, TRUE))) {
4859                 struct status *file = unmerged;
4861                 if (!file) {
4862                         file = calloc(1, sizeof(*file));
4863                         if (!file || !add_line_data(view, file, type))
4864                                 goto error_out;
4865                 }
4867                 /* Parse diff info part. */
4868                 if (status) {
4869                         file->status = status;
4870                         if (status == 'A')
4871                                 string_copy(file->old.rev, NULL_ID);
4873                 } else if (!file->status || file == unmerged) {
4874                         if (!status_get_diff(file, buf, strlen(buf)))
4875                                 goto error_out;
4877                         buf = io_get(&io, 0, TRUE);
4878                         if (!buf)
4879                                 break;
4881                         /* Collapse all modified entries that follow an
4882                          * associated unmerged entry. */
4883                         if (unmerged == file) {
4884                                 unmerged->status = 'U';
4885                                 unmerged = NULL;
4886                         } else if (file->status == 'U') {
4887                                 unmerged = file;
4888                         }
4889                 }
4891                 /* Grab the old name for rename/copy. */
4892                 if (!*file->old.name &&
4893                     (file->status == 'R' || file->status == 'C')) {
4894                         string_ncopy(file->old.name, buf, strlen(buf));
4896                         buf = io_get(&io, 0, TRUE);
4897                         if (!buf)
4898                                 break;
4899                 }
4901                 /* git-ls-files just delivers a NUL separated list of
4902                  * file names similar to the second half of the
4903                  * git-diff-* output. */
4904                 string_ncopy(file->new.name, buf, strlen(buf));
4905                 if (!*file->old.name)
4906                         string_copy(file->old.name, file->new.name);
4907                 file = NULL;
4908         }
4910         if (io_error(&io)) {
4911 error_out:
4912                 done_io(&io);
4913                 return FALSE;
4914         }
4916         if (!view->line[view->lines - 1].data)
4917                 add_line_data(view, NULL, LINE_STAT_NONE);
4919         done_io(&io);
4920         return TRUE;
4923 /* Don't show unmerged entries in the staged section. */
4924 static const char *status_diff_index_argv[] = {
4925         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4926                              "--cached", "-M", "HEAD", NULL
4927 };
4929 static const char *status_diff_files_argv[] = {
4930         "git", "diff-files", "-z", NULL
4931 };
4933 static const char *status_list_other_argv[] = {
4934         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4935 };
4937 static const char *status_list_no_head_argv[] = {
4938         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4939 };
4941 static const char *update_index_argv[] = {
4942         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4943 };
4945 /* Restore the previous line number to stay in the context or select a
4946  * line with something that can be updated. */
4947 static void
4948 status_restore(struct view *view)
4950         if (view->p_lineno >= view->lines)
4951                 view->p_lineno = view->lines - 1;
4952         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4953                 view->p_lineno++;
4954         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4955                 view->p_lineno--;
4957         /* If the above fails, always skip the "On branch" line. */
4958         if (view->p_lineno < view->lines)
4959                 view->lineno = view->p_lineno;
4960         else
4961                 view->lineno = 1;
4963         if (view->lineno < view->offset)
4964                 view->offset = view->lineno;
4965         else if (view->offset + view->height <= view->lineno)
4966                 view->offset = view->lineno - view->height + 1;
4968         view->p_restore = FALSE;
4971 static void
4972 status_update_onbranch(void)
4974         static const char *paths[][2] = {
4975                 { "rebase-apply/rebasing",      "Rebasing" },
4976                 { "rebase-apply/applying",      "Applying mailbox" },
4977                 { "rebase-apply/",              "Rebasing mailbox" },
4978                 { "rebase-merge/interactive",   "Interactive rebase" },
4979                 { "rebase-merge/",              "Rebase merge" },
4980                 { "MERGE_HEAD",                 "Merging" },
4981                 { "BISECT_LOG",                 "Bisecting" },
4982                 { "HEAD",                       "On branch" },
4983         };
4984         char buf[SIZEOF_STR];
4985         struct stat stat;
4986         int i;
4988         if (is_initial_commit()) {
4989                 string_copy(status_onbranch, "Initial commit");
4990                 return;
4991         }
4993         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4994                 char *head = opt_head;
4996                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4997                     lstat(buf, &stat) < 0)
4998                         continue;
5000                 if (!*opt_head) {
5001                         struct io io = {};
5003                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5004                             io_open(&io, buf) &&
5005                             io_read_buf(&io, buf, sizeof(buf))) {
5006                                 head = buf;
5007                                 if (!prefixcmp(head, "refs/heads/"))
5008                                         head += STRING_SIZE("refs/heads/");
5009                         }
5010                 }
5012                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5013                         string_copy(status_onbranch, opt_head);
5014                 return;
5015         }
5017         string_copy(status_onbranch, "Not currently on any branch");
5020 /* First parse staged info using git-diff-index(1), then parse unstaged
5021  * info using git-diff-files(1), and finally untracked files using
5022  * git-ls-files(1). */
5023 static bool
5024 status_open(struct view *view)
5026         reset_view(view);
5028         add_line_data(view, NULL, LINE_STAT_HEAD);
5029         status_update_onbranch();
5031         run_io_bg(update_index_argv);
5033         if (is_initial_commit()) {
5034                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5035                         return FALSE;
5036         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5037                 return FALSE;
5038         }
5040         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5041             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5042                 return FALSE;
5044         /* Restore the exact position or use the specialized restore
5045          * mode? */
5046         if (!view->p_restore)
5047                 status_restore(view);
5048         return TRUE;
5051 static bool
5052 status_draw(struct view *view, struct line *line, unsigned int lineno)
5054         struct status *status = line->data;
5055         enum line_type type;
5056         const char *text;
5058         if (!status) {
5059                 switch (line->type) {
5060                 case LINE_STAT_STAGED:
5061                         type = LINE_STAT_SECTION;
5062                         text = "Changes to be committed:";
5063                         break;
5065                 case LINE_STAT_UNSTAGED:
5066                         type = LINE_STAT_SECTION;
5067                         text = "Changed but not updated:";
5068                         break;
5070                 case LINE_STAT_UNTRACKED:
5071                         type = LINE_STAT_SECTION;
5072                         text = "Untracked files:";
5073                         break;
5075                 case LINE_STAT_NONE:
5076                         type = LINE_DEFAULT;
5077                         text = "  (no files)";
5078                         break;
5080                 case LINE_STAT_HEAD:
5081                         type = LINE_STAT_HEAD;
5082                         text = status_onbranch;
5083                         break;
5085                 default:
5086                         return FALSE;
5087                 }
5088         } else {
5089                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5091                 buf[0] = status->status;
5092                 if (draw_text(view, line->type, buf, TRUE))
5093                         return TRUE;
5094                 type = LINE_DEFAULT;
5095                 text = status->new.name;
5096         }
5098         draw_text(view, type, text, TRUE);
5099         return TRUE;
5102 static enum request
5103 status_load_error(struct view *view, struct view *stage, const char *path)
5105         if (displayed_views() == 2 || display[current_view] != view)
5106                 maximize_view(view);
5107         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5108         return REQ_NONE;
5111 static enum request
5112 status_enter(struct view *view, struct line *line)
5114         struct status *status = line->data;
5115         const char *oldpath = status ? status->old.name : NULL;
5116         /* Diffs for unmerged entries are empty when passing the new
5117          * path, so leave it empty. */
5118         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5119         const char *info;
5120         enum open_flags split;
5121         struct view *stage = VIEW(REQ_VIEW_STAGE);
5123         if (line->type == LINE_STAT_NONE ||
5124             (!status && line[1].type == LINE_STAT_NONE)) {
5125                 report("No file to diff");
5126                 return REQ_NONE;
5127         }
5129         switch (line->type) {
5130         case LINE_STAT_STAGED:
5131                 if (is_initial_commit()) {
5132                         const char *no_head_diff_argv[] = {
5133                                 "git", "diff", "--no-color", "--patch-with-stat",
5134                                         "--", "/dev/null", newpath, NULL
5135                         };
5137                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5138                                 return status_load_error(view, stage, newpath);
5139                 } else {
5140                         const char *index_show_argv[] = {
5141                                 "git", "diff-index", "--root", "--patch-with-stat",
5142                                         "-C", "-M", "--cached", "HEAD", "--",
5143                                         oldpath, newpath, NULL
5144                         };
5146                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5147                                 return status_load_error(view, stage, newpath);
5148                 }
5150                 if (status)
5151                         info = "Staged changes to %s";
5152                 else
5153                         info = "Staged changes";
5154                 break;
5156         case LINE_STAT_UNSTAGED:
5157         {
5158                 const char *files_show_argv[] = {
5159                         "git", "diff-files", "--root", "--patch-with-stat",
5160                                 "-C", "-M", "--", oldpath, newpath, NULL
5161                 };
5163                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5164                         return status_load_error(view, stage, newpath);
5165                 if (status)
5166                         info = "Unstaged changes to %s";
5167                 else
5168                         info = "Unstaged changes";
5169                 break;
5170         }
5171         case LINE_STAT_UNTRACKED:
5172                 if (!newpath) {
5173                         report("No file to show");
5174                         return REQ_NONE;
5175                 }
5177                 if (!suffixcmp(status->new.name, -1, "/")) {
5178                         report("Cannot display a directory");
5179                         return REQ_NONE;
5180                 }
5182                 if (!prepare_update_file(stage, newpath))
5183                         return status_load_error(view, stage, newpath);
5184                 info = "Untracked file %s";
5185                 break;
5187         case LINE_STAT_HEAD:
5188                 return REQ_NONE;
5190         default:
5191                 die("line type %d not handled in switch", line->type);
5192         }
5194         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5195         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5196         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5197                 if (status) {
5198                         stage_status = *status;
5199                 } else {
5200                         memset(&stage_status, 0, sizeof(stage_status));
5201                 }
5203                 stage_line_type = line->type;
5204                 stage_chunks = 0;
5205                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5206         }
5208         return REQ_NONE;
5211 static bool
5212 status_exists(struct status *status, enum line_type type)
5214         struct view *view = VIEW(REQ_VIEW_STATUS);
5215         unsigned long lineno;
5217         for (lineno = 0; lineno < view->lines; lineno++) {
5218                 struct line *line = &view->line[lineno];
5219                 struct status *pos = line->data;
5221                 if (line->type != type)
5222                         continue;
5223                 if (!pos && (!status || !status->status) && line[1].data) {
5224                         select_view_line(view, lineno);
5225                         return TRUE;
5226                 }
5227                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5228                         select_view_line(view, lineno);
5229                         return TRUE;
5230                 }
5231         }
5233         return FALSE;
5237 static bool
5238 status_update_prepare(struct io *io, enum line_type type)
5240         const char *staged_argv[] = {
5241                 "git", "update-index", "-z", "--index-info", NULL
5242         };
5243         const char *others_argv[] = {
5244                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5245         };
5247         switch (type) {
5248         case LINE_STAT_STAGED:
5249                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5251         case LINE_STAT_UNSTAGED:
5252                 return run_io(io, others_argv, opt_cdup, IO_WR);
5254         case LINE_STAT_UNTRACKED:
5255                 return run_io(io, others_argv, NULL, IO_WR);
5257         default:
5258                 die("line type %d not handled in switch", type);
5259                 return FALSE;
5260         }
5263 static bool
5264 status_update_write(struct io *io, struct status *status, enum line_type type)
5266         char buf[SIZEOF_STR];
5267         size_t bufsize = 0;
5269         switch (type) {
5270         case LINE_STAT_STAGED:
5271                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5272                                         status->old.mode,
5273                                         status->old.rev,
5274                                         status->old.name, 0))
5275                         return FALSE;
5276                 break;
5278         case LINE_STAT_UNSTAGED:
5279         case LINE_STAT_UNTRACKED:
5280                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5281                         return FALSE;
5282                 break;
5284         default:
5285                 die("line type %d not handled in switch", type);
5286         }
5288         return io_write(io, buf, bufsize);
5291 static bool
5292 status_update_file(struct status *status, enum line_type type)
5294         struct io io = {};
5295         bool result;
5297         if (!status_update_prepare(&io, type))
5298                 return FALSE;
5300         result = status_update_write(&io, status, type);
5301         return done_io(&io) && result;
5304 static bool
5305 status_update_files(struct view *view, struct line *line)
5307         char buf[sizeof(view->ref)];
5308         struct io io = {};
5309         bool result = TRUE;
5310         struct line *pos = view->line + view->lines;
5311         int files = 0;
5312         int file, done;
5313         int cursor_y, cursor_x;
5315         if (!status_update_prepare(&io, line->type))
5316                 return FALSE;
5318         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5319                 files++;
5321         string_copy(buf, view->ref);
5322         getsyx(cursor_y, cursor_x);
5323         for (file = 0, done = 5; result && file < files; line++, file++) {
5324                 int almost_done = file * 100 / files;
5326                 if (almost_done > done) {
5327                         done = almost_done;
5328                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5329                                       file, files, done);
5330                         update_view_title(view);
5331                         setsyx(cursor_y, cursor_x);
5332                         doupdate();
5333                 }
5334                 result = status_update_write(&io, line->data, line->type);
5335         }
5336         string_copy(view->ref, buf);
5338         return done_io(&io) && result;
5341 static bool
5342 status_update(struct view *view)
5344         struct line *line = &view->line[view->lineno];
5346         assert(view->lines);
5348         if (!line->data) {
5349                 /* This should work even for the "On branch" line. */
5350                 if (line < view->line + view->lines && !line[1].data) {
5351                         report("Nothing to update");
5352                         return FALSE;
5353                 }
5355                 if (!status_update_files(view, line + 1)) {
5356                         report("Failed to update file status");
5357                         return FALSE;
5358                 }
5360         } else if (!status_update_file(line->data, line->type)) {
5361                 report("Failed to update file status");
5362                 return FALSE;
5363         }
5365         return TRUE;
5368 static bool
5369 status_revert(struct status *status, enum line_type type, bool has_none)
5371         if (!status || type != LINE_STAT_UNSTAGED) {
5372                 if (type == LINE_STAT_STAGED) {
5373                         report("Cannot revert changes to staged files");
5374                 } else if (type == LINE_STAT_UNTRACKED) {
5375                         report("Cannot revert changes to untracked files");
5376                 } else if (has_none) {
5377                         report("Nothing to revert");
5378                 } else {
5379                         report("Cannot revert changes to multiple files");
5380                 }
5381                 return FALSE;
5383         } else {
5384                 char mode[10] = "100644";
5385                 const char *reset_argv[] = {
5386                         "git", "update-index", "--cacheinfo", mode,
5387                                 status->old.rev, status->old.name, NULL
5388                 };
5389                 const char *checkout_argv[] = {
5390                         "git", "checkout", "--", status->old.name, NULL
5391                 };
5393                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5394                         return FALSE;
5395                 string_format(mode, "%o", status->old.mode);
5396                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5397                         run_io_fg(checkout_argv, opt_cdup);
5398         }
5401 static enum request
5402 status_request(struct view *view, enum request request, struct line *line)
5404         struct status *status = line->data;
5406         switch (request) {
5407         case REQ_STATUS_UPDATE:
5408                 if (!status_update(view))
5409                         return REQ_NONE;
5410                 break;
5412         case REQ_STATUS_REVERT:
5413                 if (!status_revert(status, line->type, status_has_none(view, line)))
5414                         return REQ_NONE;
5415                 break;
5417         case REQ_STATUS_MERGE:
5418                 if (!status || status->status != 'U') {
5419                         report("Merging only possible for files with unmerged status ('U').");
5420                         return REQ_NONE;
5421                 }
5422                 open_mergetool(status->new.name);
5423                 break;
5425         case REQ_EDIT:
5426                 if (!status)
5427                         return request;
5428                 if (status->status == 'D') {
5429                         report("File has been deleted.");
5430                         return REQ_NONE;
5431                 }
5433                 open_editor(status->status != '?', status->new.name);
5434                 break;
5436         case REQ_VIEW_BLAME:
5437                 if (status) {
5438                         string_copy(opt_file, status->new.name);
5439                         opt_ref[0] = 0;
5440                 }
5441                 return request;
5443         case REQ_ENTER:
5444                 /* After returning the status view has been split to
5445                  * show the stage view. No further reloading is
5446                  * necessary. */
5447                 return status_enter(view, line);
5449         case REQ_REFRESH:
5450                 /* Simply reload the view. */
5451                 break;
5453         default:
5454                 return request;
5455         }
5457         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5459         return REQ_NONE;
5462 static void
5463 status_select(struct view *view, struct line *line)
5465         struct status *status = line->data;
5466         char file[SIZEOF_STR] = "all files";
5467         const char *text;
5468         const char *key;
5470         if (status && !string_format(file, "'%s'", status->new.name))
5471                 return;
5473         if (!status && line[1].type == LINE_STAT_NONE)
5474                 line++;
5476         switch (line->type) {
5477         case LINE_STAT_STAGED:
5478                 text = "Press %s to unstage %s for commit";
5479                 break;
5481         case LINE_STAT_UNSTAGED:
5482                 text = "Press %s to stage %s for commit";
5483                 break;
5485         case LINE_STAT_UNTRACKED:
5486                 text = "Press %s to stage %s for addition";
5487                 break;
5489         case LINE_STAT_HEAD:
5490         case LINE_STAT_NONE:
5491                 text = "Nothing to update";
5492                 break;
5494         default:
5495                 die("line type %d not handled in switch", line->type);
5496         }
5498         if (status && status->status == 'U') {
5499                 text = "Press %s to resolve conflict in %s";
5500                 key = get_key(REQ_STATUS_MERGE);
5502         } else {
5503                 key = get_key(REQ_STATUS_UPDATE);
5504         }
5506         string_format(view->ref, text, key, file);
5509 static bool
5510 status_grep(struct view *view, struct line *line)
5512         struct status *status = line->data;
5514         if (status) {
5515                 const char buf[2] = { status->status, 0 };
5516                 const char *text[] = { status->new.name, buf, NULL };
5518                 return grep_text(view, text);
5519         }
5521         return FALSE;
5524 static struct view_ops status_ops = {
5525         "file",
5526         NULL,
5527         status_open,
5528         NULL,
5529         status_draw,
5530         status_request,
5531         status_grep,
5532         status_select,
5533 };
5536 static bool
5537 stage_diff_write(struct io *io, struct line *line, struct line *end)
5539         while (line < end) {
5540                 if (!io_write(io, line->data, strlen(line->data)) ||
5541                     !io_write(io, "\n", 1))
5542                         return FALSE;
5543                 line++;
5544                 if (line->type == LINE_DIFF_CHUNK ||
5545                     line->type == LINE_DIFF_HEADER)
5546                         break;
5547         }
5549         return TRUE;
5552 static struct line *
5553 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5555         for (; view->line < line; line--)
5556                 if (line->type == type)
5557                         return line;
5559         return NULL;
5562 static bool
5563 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5565         const char *apply_argv[SIZEOF_ARG] = {
5566                 "git", "apply", "--whitespace=nowarn", NULL
5567         };
5568         struct line *diff_hdr;
5569         struct io io = {};
5570         int argc = 3;
5572         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5573         if (!diff_hdr)
5574                 return FALSE;
5576         if (!revert)
5577                 apply_argv[argc++] = "--cached";
5578         if (revert || stage_line_type == LINE_STAT_STAGED)
5579                 apply_argv[argc++] = "-R";
5580         apply_argv[argc++] = "-";
5581         apply_argv[argc++] = NULL;
5582         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5583                 return FALSE;
5585         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5586             !stage_diff_write(&io, chunk, view->line + view->lines))
5587                 chunk = NULL;
5589         done_io(&io);
5590         run_io_bg(update_index_argv);
5592         return chunk ? TRUE : FALSE;
5595 static bool
5596 stage_update(struct view *view, struct line *line)
5598         struct line *chunk = NULL;
5600         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5601                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5603         if (chunk) {
5604                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5605                         report("Failed to apply chunk");
5606                         return FALSE;
5607                 }
5609         } else if (!stage_status.status) {
5610                 view = VIEW(REQ_VIEW_STATUS);
5612                 for (line = view->line; line < view->line + view->lines; line++)
5613                         if (line->type == stage_line_type)
5614                                 break;
5616                 if (!status_update_files(view, line + 1)) {
5617                         report("Failed to update files");
5618                         return FALSE;
5619                 }
5621         } else if (!status_update_file(&stage_status, stage_line_type)) {
5622                 report("Failed to update file");
5623                 return FALSE;
5624         }
5626         return TRUE;
5629 static bool
5630 stage_revert(struct view *view, struct line *line)
5632         struct line *chunk = NULL;
5634         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5635                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5637         if (chunk) {
5638                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5639                         return FALSE;
5641                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5642                         report("Failed to revert chunk");
5643                         return FALSE;
5644                 }
5645                 return TRUE;
5647         } else {
5648                 return status_revert(stage_status.status ? &stage_status : NULL,
5649                                      stage_line_type, FALSE);
5650         }
5654 static void
5655 stage_next(struct view *view, struct line *line)
5657         int i;
5659         if (!stage_chunks) {
5660                 for (line = view->line; line < view->line + view->lines; line++) {
5661                         if (line->type != LINE_DIFF_CHUNK)
5662                                 continue;
5664                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5665                                 report("Allocation failure");
5666                                 return;
5667                         }
5669                         stage_chunk[stage_chunks++] = line - view->line;
5670                 }
5671         }
5673         for (i = 0; i < stage_chunks; i++) {
5674                 if (stage_chunk[i] > view->lineno) {
5675                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5676                         report("Chunk %d of %d", i + 1, stage_chunks);
5677                         return;
5678                 }
5679         }
5681         report("No next chunk found");
5684 static enum request
5685 stage_request(struct view *view, enum request request, struct line *line)
5687         switch (request) {
5688         case REQ_STATUS_UPDATE:
5689                 if (!stage_update(view, line))
5690                         return REQ_NONE;
5691                 break;
5693         case REQ_STATUS_REVERT:
5694                 if (!stage_revert(view, line))
5695                         return REQ_NONE;
5696                 break;
5698         case REQ_STAGE_NEXT:
5699                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5700                         report("File is untracked; press %s to add",
5701                                get_key(REQ_STATUS_UPDATE));
5702                         return REQ_NONE;
5703                 }
5704                 stage_next(view, line);
5705                 return REQ_NONE;
5707         case REQ_EDIT:
5708                 if (!stage_status.new.name[0])
5709                         return request;
5710                 if (stage_status.status == 'D') {
5711                         report("File has been deleted.");
5712                         return REQ_NONE;
5713                 }
5715                 open_editor(stage_status.status != '?', stage_status.new.name);
5716                 break;
5718         case REQ_REFRESH:
5719                 /* Reload everything ... */
5720                 break;
5722         case REQ_VIEW_BLAME:
5723                 if (stage_status.new.name[0]) {
5724                         string_copy(opt_file, stage_status.new.name);
5725                         opt_ref[0] = 0;
5726                 }
5727                 return request;
5729         case REQ_ENTER:
5730                 return pager_request(view, request, line);
5732         default:
5733                 return request;
5734         }
5736         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5737         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5739         /* Check whether the staged entry still exists, and close the
5740          * stage view if it doesn't. */
5741         if (!status_exists(&stage_status, stage_line_type)) {
5742                 status_restore(VIEW(REQ_VIEW_STATUS));
5743                 return REQ_VIEW_CLOSE;
5744         }
5746         if (stage_line_type == LINE_STAT_UNTRACKED) {
5747                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5748                         report("Cannot display a directory");
5749                         return REQ_NONE;
5750                 }
5752                 if (!prepare_update_file(view, stage_status.new.name)) {
5753                         report("Failed to open file: %s", strerror(errno));
5754                         return REQ_NONE;
5755                 }
5756         }
5757         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5759         return REQ_NONE;
5762 static struct view_ops stage_ops = {
5763         "line",
5764         NULL,
5765         NULL,
5766         pager_read,
5767         pager_draw,
5768         stage_request,
5769         pager_grep,
5770         pager_select,
5771 };
5774 /*
5775  * Revision graph
5776  */
5778 struct commit {
5779         char id[SIZEOF_REV];            /* SHA1 ID. */
5780         char title[128];                /* First line of the commit message. */
5781         const char *author;             /* Author of the commit. */
5782         time_t time;                    /* Date from the author ident. */
5783         struct ref **refs;              /* Repository references. */
5784         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5785         size_t graph_size;              /* The width of the graph array. */
5786         bool has_parents;               /* Rewritten --parents seen. */
5787 };
5789 /* Size of rev graph with no  "padding" columns */
5790 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5792 struct rev_graph {
5793         struct rev_graph *prev, *next, *parents;
5794         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5795         size_t size;
5796         struct commit *commit;
5797         size_t pos;
5798         unsigned int boundary:1;
5799 };
5801 /* Parents of the commit being visualized. */
5802 static struct rev_graph graph_parents[4];
5804 /* The current stack of revisions on the graph. */
5805 static struct rev_graph graph_stacks[4] = {
5806         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5807         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5808         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5809         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5810 };
5812 static inline bool
5813 graph_parent_is_merge(struct rev_graph *graph)
5815         return graph->parents->size > 1;
5818 static inline void
5819 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5821         struct commit *commit = graph->commit;
5823         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5824                 commit->graph[commit->graph_size++] = symbol;
5827 static void
5828 clear_rev_graph(struct rev_graph *graph)
5830         graph->boundary = 0;
5831         graph->size = graph->pos = 0;
5832         graph->commit = NULL;
5833         memset(graph->parents, 0, sizeof(*graph->parents));
5836 static void
5837 done_rev_graph(struct rev_graph *graph)
5839         if (graph_parent_is_merge(graph) &&
5840             graph->pos < graph->size - 1 &&
5841             graph->next->size == graph->size + graph->parents->size - 1) {
5842                 size_t i = graph->pos + graph->parents->size - 1;
5844                 graph->commit->graph_size = i * 2;
5845                 while (i < graph->next->size - 1) {
5846                         append_to_rev_graph(graph, ' ');
5847                         append_to_rev_graph(graph, '\\');
5848                         i++;
5849                 }
5850         }
5852         clear_rev_graph(graph);
5855 static void
5856 push_rev_graph(struct rev_graph *graph, const char *parent)
5858         int i;
5860         /* "Collapse" duplicate parents lines.
5861          *
5862          * FIXME: This needs to also update update the drawn graph but
5863          * for now it just serves as a method for pruning graph lines. */
5864         for (i = 0; i < graph->size; i++)
5865                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5866                         return;
5868         if (graph->size < SIZEOF_REVITEMS) {
5869                 string_copy_rev(graph->rev[graph->size++], parent);
5870         }
5873 static chtype
5874 get_rev_graph_symbol(struct rev_graph *graph)
5876         chtype symbol;
5878         if (graph->boundary)
5879                 symbol = REVGRAPH_BOUND;
5880         else if (graph->parents->size == 0)
5881                 symbol = REVGRAPH_INIT;
5882         else if (graph_parent_is_merge(graph))
5883                 symbol = REVGRAPH_MERGE;
5884         else if (graph->pos >= graph->size)
5885                 symbol = REVGRAPH_BRANCH;
5886         else
5887                 symbol = REVGRAPH_COMMIT;
5889         return symbol;
5892 static void
5893 draw_rev_graph(struct rev_graph *graph)
5895         struct rev_filler {
5896                 chtype separator, line;
5897         };
5898         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5899         static struct rev_filler fillers[] = {
5900                 { ' ',  '|' },
5901                 { '`',  '.' },
5902                 { '\'', ' ' },
5903                 { '/',  ' ' },
5904         };
5905         chtype symbol = get_rev_graph_symbol(graph);
5906         struct rev_filler *filler;
5907         size_t i;
5909         if (opt_line_graphics)
5910                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5912         filler = &fillers[DEFAULT];
5914         for (i = 0; i < graph->pos; i++) {
5915                 append_to_rev_graph(graph, filler->line);
5916                 if (graph_parent_is_merge(graph->prev) &&
5917                     graph->prev->pos == i)
5918                         filler = &fillers[RSHARP];
5920                 append_to_rev_graph(graph, filler->separator);
5921         }
5923         /* Place the symbol for this revision. */
5924         append_to_rev_graph(graph, symbol);
5926         if (graph->prev->size > graph->size)
5927                 filler = &fillers[RDIAG];
5928         else
5929                 filler = &fillers[DEFAULT];
5931         i++;
5933         for (; i < graph->size; i++) {
5934                 append_to_rev_graph(graph, filler->separator);
5935                 append_to_rev_graph(graph, filler->line);
5936                 if (graph_parent_is_merge(graph->prev) &&
5937                     i < graph->prev->pos + graph->parents->size)
5938                         filler = &fillers[RSHARP];
5939                 if (graph->prev->size > graph->size)
5940                         filler = &fillers[LDIAG];
5941         }
5943         if (graph->prev->size > graph->size) {
5944                 append_to_rev_graph(graph, filler->separator);
5945                 if (filler->line != ' ')
5946                         append_to_rev_graph(graph, filler->line);
5947         }
5950 /* Prepare the next rev graph */
5951 static void
5952 prepare_rev_graph(struct rev_graph *graph)
5954         size_t i;
5956         /* First, traverse all lines of revisions up to the active one. */
5957         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5958                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5959                         break;
5961                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5962         }
5964         /* Interleave the new revision parent(s). */
5965         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5966                 push_rev_graph(graph->next, graph->parents->rev[i]);
5968         /* Lastly, put any remaining revisions. */
5969         for (i = graph->pos + 1; i < graph->size; i++)
5970                 push_rev_graph(graph->next, graph->rev[i]);
5973 static void
5974 update_rev_graph(struct view *view, struct rev_graph *graph)
5976         /* If this is the finalizing update ... */
5977         if (graph->commit)
5978                 prepare_rev_graph(graph);
5980         /* Graph visualization needs a one rev look-ahead,
5981          * so the first update doesn't visualize anything. */
5982         if (!graph->prev->commit)
5983                 return;
5985         if (view->lines > 2)
5986                 view->line[view->lines - 3].dirty = 1;
5987         if (view->lines > 1)
5988                 view->line[view->lines - 2].dirty = 1;
5989         draw_rev_graph(graph->prev);
5990         done_rev_graph(graph->prev->prev);
5994 /*
5995  * Main view backend
5996  */
5998 static const char *main_argv[SIZEOF_ARG] = {
5999         "git", "log", "--no-color", "--pretty=raw", "--parents",
6000                       "--topo-order", "%(head)", NULL
6001 };
6003 static bool
6004 main_draw(struct view *view, struct line *line, unsigned int lineno)
6006         struct commit *commit = line->data;
6008         if (!commit->author)
6009                 return FALSE;
6011         if (opt_date && draw_date(view, &commit->time))
6012                 return TRUE;
6014         if (opt_author && draw_author(view, commit->author))
6015                 return TRUE;
6017         if (opt_rev_graph && commit->graph_size &&
6018             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6019                 return TRUE;
6021         if (opt_show_refs && commit->refs) {
6022                 size_t i = 0;
6024                 do {
6025                         struct ref *ref = commit->refs[i];
6026                         enum line_type type;
6028                         if (ref->head)
6029                                 type = LINE_MAIN_HEAD;
6030                         else if (ref->ltag)
6031                                 type = LINE_MAIN_LOCAL_TAG;
6032                         else if (ref->tag)
6033                                 type = LINE_MAIN_TAG;
6034                         else if (ref->tracked)
6035                                 type = LINE_MAIN_TRACKED;
6036                         else if (ref->remote)
6037                                 type = LINE_MAIN_REMOTE;
6038                         else
6039                                 type = LINE_MAIN_REF;
6041                         if (draw_text(view, type, "[", TRUE) ||
6042                             draw_text(view, type, ref->name, TRUE) ||
6043                             draw_text(view, type, "]", TRUE))
6044                                 return TRUE;
6046                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6047                                 return TRUE;
6048                 } while (commit->refs[i++]->next);
6049         }
6051         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6052         return TRUE;
6055 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6056 static bool
6057 main_read(struct view *view, char *line)
6059         static struct rev_graph *graph = graph_stacks;
6060         enum line_type type;
6061         struct commit *commit;
6063         if (!line) {
6064                 int i;
6066                 if (!view->lines && !view->parent)
6067                         die("No revisions match the given arguments.");
6068                 if (view->lines > 0) {
6069                         commit = view->line[view->lines - 1].data;
6070                         view->line[view->lines - 1].dirty = 1;
6071                         if (!commit->author) {
6072                                 view->lines--;
6073                                 free(commit);
6074                                 graph->commit = NULL;
6075                         }
6076                 }
6077                 update_rev_graph(view, graph);
6079                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6080                         clear_rev_graph(&graph_stacks[i]);
6081                 return TRUE;
6082         }
6084         type = get_line_type(line);
6085         if (type == LINE_COMMIT) {
6086                 commit = calloc(1, sizeof(struct commit));
6087                 if (!commit)
6088                         return FALSE;
6090                 line += STRING_SIZE("commit ");
6091                 if (*line == '-') {
6092                         graph->boundary = 1;
6093                         line++;
6094                 }
6096                 string_copy_rev(commit->id, line);
6097                 commit->refs = get_refs(commit->id);
6098                 graph->commit = commit;
6099                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6101                 while ((line = strchr(line, ' '))) {
6102                         line++;
6103                         push_rev_graph(graph->parents, line);
6104                         commit->has_parents = TRUE;
6105                 }
6106                 return TRUE;
6107         }
6109         if (!view->lines)
6110                 return TRUE;
6111         commit = view->line[view->lines - 1].data;
6113         switch (type) {
6114         case LINE_PARENT:
6115                 if (commit->has_parents)
6116                         break;
6117                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6118                 break;
6120         case LINE_AUTHOR:
6121                 parse_author_line(line + STRING_SIZE("author "),
6122                                   &commit->author, &commit->time);
6123                 update_rev_graph(view, graph);
6124                 graph = graph->next;
6125                 break;
6127         default:
6128                 /* Fill in the commit title if it has not already been set. */
6129                 if (commit->title[0])
6130                         break;
6132                 /* Require titles to start with a non-space character at the
6133                  * offset used by git log. */
6134                 if (strncmp(line, "    ", 4))
6135                         break;
6136                 line += 4;
6137                 /* Well, if the title starts with a whitespace character,
6138                  * try to be forgiving.  Otherwise we end up with no title. */
6139                 while (isspace(*line))
6140                         line++;
6141                 if (*line == '\0')
6142                         break;
6143                 /* FIXME: More graceful handling of titles; append "..." to
6144                  * shortened titles, etc. */
6146                 string_expand(commit->title, sizeof(commit->title), line, 1);
6147                 view->line[view->lines - 1].dirty = 1;
6148         }
6150         return TRUE;
6153 static enum request
6154 main_request(struct view *view, enum request request, struct line *line)
6156         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6158         switch (request) {
6159         case REQ_ENTER:
6160                 open_view(view, REQ_VIEW_DIFF, flags);
6161                 break;
6162         case REQ_REFRESH:
6163                 load_refs();
6164                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6165                 break;
6166         default:
6167                 return request;
6168         }
6170         return REQ_NONE;
6173 static bool
6174 grep_refs(struct ref **refs, regex_t *regex)
6176         regmatch_t pmatch;
6177         size_t i = 0;
6179         if (!opt_show_refs || !refs)
6180                 return FALSE;
6181         do {
6182                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6183                         return TRUE;
6184         } while (refs[i++]->next);
6186         return FALSE;
6189 static bool
6190 main_grep(struct view *view, struct line *line)
6192         struct commit *commit = line->data;
6193         const char *text[] = {
6194                 commit->title,
6195                 opt_author ? commit->author : "",
6196                 opt_date ? mkdate(&commit->time) : "",
6197                 NULL
6198         };
6200         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6203 static void
6204 main_select(struct view *view, struct line *line)
6206         struct commit *commit = line->data;
6208         string_copy_rev(view->ref, commit->id);
6209         string_copy_rev(ref_commit, view->ref);
6212 static struct view_ops main_ops = {
6213         "commit",
6214         main_argv,
6215         NULL,
6216         main_read,
6217         main_draw,
6218         main_request,
6219         main_grep,
6220         main_select,
6221 };
6224 /*
6225  * Unicode / UTF-8 handling
6226  *
6227  * NOTE: Much of the following code for dealing with Unicode is derived from
6228  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6229  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6230  */
6232 static inline int
6233 unicode_width(unsigned long c)
6235         if (c >= 0x1100 &&
6236            (c <= 0x115f                         /* Hangul Jamo */
6237             || c == 0x2329
6238             || c == 0x232a
6239             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6240                                                 /* CJK ... Yi */
6241             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6242             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6243             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6244             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6245             || (c >= 0xffe0  && c <= 0xffe6)
6246             || (c >= 0x20000 && c <= 0x2fffd)
6247             || (c >= 0x30000 && c <= 0x3fffd)))
6248                 return 2;
6250         if (c == '\t')
6251                 return opt_tab_size;
6253         return 1;
6256 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6257  * Illegal bytes are set one. */
6258 static const unsigned char utf8_bytes[256] = {
6259         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,
6260         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,
6261         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,
6262         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,
6263         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,
6264         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,
6265         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,
6266         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,
6267 };
6269 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6270 static inline unsigned long
6271 utf8_to_unicode(const char *string, size_t length)
6273         unsigned long unicode;
6275         switch (length) {
6276         case 1:
6277                 unicode  =   string[0];
6278                 break;
6279         case 2:
6280                 unicode  =  (string[0] & 0x1f) << 6;
6281                 unicode +=  (string[1] & 0x3f);
6282                 break;
6283         case 3:
6284                 unicode  =  (string[0] & 0x0f) << 12;
6285                 unicode += ((string[1] & 0x3f) << 6);
6286                 unicode +=  (string[2] & 0x3f);
6287                 break;
6288         case 4:
6289                 unicode  =  (string[0] & 0x0f) << 18;
6290                 unicode += ((string[1] & 0x3f) << 12);
6291                 unicode += ((string[2] & 0x3f) << 6);
6292                 unicode +=  (string[3] & 0x3f);
6293                 break;
6294         case 5:
6295                 unicode  =  (string[0] & 0x0f) << 24;
6296                 unicode += ((string[1] & 0x3f) << 18);
6297                 unicode += ((string[2] & 0x3f) << 12);
6298                 unicode += ((string[3] & 0x3f) << 6);
6299                 unicode +=  (string[4] & 0x3f);
6300                 break;
6301         case 6:
6302                 unicode  =  (string[0] & 0x01) << 30;
6303                 unicode += ((string[1] & 0x3f) << 24);
6304                 unicode += ((string[2] & 0x3f) << 18);
6305                 unicode += ((string[3] & 0x3f) << 12);
6306                 unicode += ((string[4] & 0x3f) << 6);
6307                 unicode +=  (string[5] & 0x3f);
6308                 break;
6309         default:
6310                 die("Invalid Unicode length");
6311         }
6313         /* Invalid characters could return the special 0xfffd value but NUL
6314          * should be just as good. */
6315         return unicode > 0xffff ? 0 : unicode;
6318 /* Calculates how much of string can be shown within the given maximum width
6319  * and sets trimmed parameter to non-zero value if all of string could not be
6320  * shown. If the reserve flag is TRUE, it will reserve at least one
6321  * trailing character, which can be useful when drawing a delimiter.
6322  *
6323  * Returns the number of bytes to output from string to satisfy max_width. */
6324 static size_t
6325 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6327         const char *string = *start;
6328         const char *end = strchr(string, '\0');
6329         unsigned char last_bytes = 0;
6330         size_t last_ucwidth = 0;
6332         *width = 0;
6333         *trimmed = 0;
6335         while (string < end) {
6336                 int c = *(unsigned char *) string;
6337                 unsigned char bytes = utf8_bytes[c];
6338                 size_t ucwidth;
6339                 unsigned long unicode;
6341                 if (string + bytes > end)
6342                         break;
6344                 /* Change representation to figure out whether
6345                  * it is a single- or double-width character. */
6347                 unicode = utf8_to_unicode(string, bytes);
6348                 /* FIXME: Graceful handling of invalid Unicode character. */
6349                 if (!unicode)
6350                         break;
6352                 ucwidth = unicode_width(unicode);
6353                 if (skip > 0) {
6354                         skip -= ucwidth <= skip ? ucwidth : skip;
6355                         *start += bytes;
6356                 }
6357                 *width  += ucwidth;
6358                 if (*width > max_width) {
6359                         *trimmed = 1;
6360                         *width -= ucwidth;
6361                         if (reserve && *width == max_width) {
6362                                 string -= last_bytes;
6363                                 *width -= last_ucwidth;
6364                         }
6365                         break;
6366                 }
6368                 string  += bytes;
6369                 last_bytes = ucwidth ? bytes : 0;
6370                 last_ucwidth = ucwidth;
6371         }
6373         return string - *start;
6377 /*
6378  * Status management
6379  */
6381 /* Whether or not the curses interface has been initialized. */
6382 static bool cursed = FALSE;
6384 /* Terminal hacks and workarounds. */
6385 static bool use_scroll_redrawwin;
6386 static bool use_scroll_status_wclear;
6388 /* The status window is used for polling keystrokes. */
6389 static WINDOW *status_win;
6391 /* Reading from the prompt? */
6392 static bool input_mode = FALSE;
6394 static bool status_empty = FALSE;
6396 /* Update status and title window. */
6397 static void
6398 report(const char *msg, ...)
6400         struct view *view = display[current_view];
6402         if (input_mode)
6403                 return;
6405         if (!view) {
6406                 char buf[SIZEOF_STR];
6407                 va_list args;
6409                 va_start(args, msg);
6410                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6411                         buf[sizeof(buf) - 1] = 0;
6412                         buf[sizeof(buf) - 2] = '.';
6413                         buf[sizeof(buf) - 3] = '.';
6414                         buf[sizeof(buf) - 4] = '.';
6415                 }
6416                 va_end(args);
6417                 die("%s", buf);
6418         }
6420         if (!status_empty || *msg) {
6421                 va_list args;
6423                 va_start(args, msg);
6425                 wmove(status_win, 0, 0);
6426                 if (view->has_scrolled && use_scroll_status_wclear)
6427                         wclear(status_win);
6428                 if (*msg) {
6429                         vwprintw(status_win, msg, args);
6430                         status_empty = FALSE;
6431                 } else {
6432                         status_empty = TRUE;
6433                 }
6434                 wclrtoeol(status_win);
6435                 wnoutrefresh(status_win);
6437                 va_end(args);
6438         }
6440         update_view_title(view);
6443 /* Controls when nodelay should be in effect when polling user input. */
6444 static void
6445 set_nonblocking_input(bool loading)
6447         static unsigned int loading_views;
6449         if ((loading == FALSE && loading_views-- == 1) ||
6450             (loading == TRUE  && loading_views++ == 0))
6451                 nodelay(status_win, loading);
6454 static void
6455 init_display(void)
6457         const char *term;
6458         int x, y;
6460         /* Initialize the curses library */
6461         if (isatty(STDIN_FILENO)) {
6462                 cursed = !!initscr();
6463                 opt_tty = stdin;
6464         } else {
6465                 /* Leave stdin and stdout alone when acting as a pager. */
6466                 opt_tty = fopen("/dev/tty", "r+");
6467                 if (!opt_tty)
6468                         die("Failed to open /dev/tty");
6469                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6470         }
6472         if (!cursed)
6473                 die("Failed to initialize curses");
6475         nonl();         /* Disable conversion and detect newlines from input. */
6476         cbreak();       /* Take input chars one at a time, no wait for \n */
6477         noecho();       /* Don't echo input */
6478         leaveok(stdscr, FALSE);
6480         if (has_colors())
6481                 init_colors();
6483         getmaxyx(stdscr, y, x);
6484         status_win = newwin(1, 0, y - 1, 0);
6485         if (!status_win)
6486                 die("Failed to create status window");
6488         /* Enable keyboard mapping */
6489         keypad(status_win, TRUE);
6490         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6492         TABSIZE = opt_tab_size;
6493         if (opt_line_graphics) {
6494                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6495         }
6497         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6498         if (term && !strcmp(term, "gnome-terminal")) {
6499                 /* In the gnome-terminal-emulator, the message from
6500                  * scrolling up one line when impossible followed by
6501                  * scrolling down one line causes corruption of the
6502                  * status line. This is fixed by calling wclear. */
6503                 use_scroll_status_wclear = TRUE;
6504                 use_scroll_redrawwin = FALSE;
6506         } else if (term && !strcmp(term, "xrvt-xpm")) {
6507                 /* No problems with full optimizations in xrvt-(unicode)
6508                  * and aterm. */
6509                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6511         } else {
6512                 /* When scrolling in (u)xterm the last line in the
6513                  * scrolling direction will update slowly. */
6514                 use_scroll_redrawwin = TRUE;
6515                 use_scroll_status_wclear = FALSE;
6516         }
6519 static int
6520 get_input(int prompt_position)
6522         struct view *view;
6523         int i, key, cursor_y, cursor_x;
6525         if (prompt_position)
6526                 input_mode = TRUE;
6528         while (TRUE) {
6529                 foreach_view (view, i) {
6530                         update_view(view);
6531                         if (view_is_displayed(view) && view->has_scrolled &&
6532                             use_scroll_redrawwin)
6533                                 redrawwin(view->win);
6534                         view->has_scrolled = FALSE;
6535                 }
6537                 /* Update the cursor position. */
6538                 if (prompt_position) {
6539                         getbegyx(status_win, cursor_y, cursor_x);
6540                         cursor_x = prompt_position;
6541                 } else {
6542                         view = display[current_view];
6543                         getbegyx(view->win, cursor_y, cursor_x);
6544                         cursor_x = view->width - 1;
6545                         cursor_y += view->lineno - view->offset;
6546                 }
6547                 setsyx(cursor_y, cursor_x);
6549                 /* Refresh, accept single keystroke of input */
6550                 doupdate();
6551                 key = wgetch(status_win);
6553                 /* wgetch() with nodelay() enabled returns ERR when
6554                  * there's no input. */
6555                 if (key == ERR) {
6557                 } else if (key == KEY_RESIZE) {
6558                         int height, width;
6560                         getmaxyx(stdscr, height, width);
6562                         wresize(status_win, 1, width);
6563                         mvwin(status_win, height - 1, 0);
6564                         wnoutrefresh(status_win);
6565                         resize_display();
6566                         redraw_display(TRUE);
6568                 } else {
6569                         input_mode = FALSE;
6570                         return key;
6571                 }
6572         }
6575 static char *
6576 prompt_input(const char *prompt, input_handler handler, void *data)
6578         enum input_status status = INPUT_OK;
6579         static char buf[SIZEOF_STR];
6580         size_t pos = 0;
6582         buf[pos] = 0;
6584         while (status == INPUT_OK || status == INPUT_SKIP) {
6585                 int key;
6587                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6588                 wclrtoeol(status_win);
6590                 key = get_input(pos + 1);
6591                 switch (key) {
6592                 case KEY_RETURN:
6593                 case KEY_ENTER:
6594                 case '\n':
6595                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6596                         break;
6598                 case KEY_BACKSPACE:
6599                         if (pos > 0)
6600                                 buf[--pos] = 0;
6601                         else
6602                                 status = INPUT_CANCEL;
6603                         break;
6605                 case KEY_ESC:
6606                         status = INPUT_CANCEL;
6607                         break;
6609                 default:
6610                         if (pos >= sizeof(buf)) {
6611                                 report("Input string too long");
6612                                 return NULL;
6613                         }
6615                         status = handler(data, buf, key);
6616                         if (status == INPUT_OK)
6617                                 buf[pos++] = (char) key;
6618                 }
6619         }
6621         /* Clear the status window */
6622         status_empty = FALSE;
6623         report("");
6625         if (status == INPUT_CANCEL)
6626                 return NULL;
6628         buf[pos++] = 0;
6630         return buf;
6633 static enum input_status
6634 prompt_yesno_handler(void *data, char *buf, int c)
6636         if (c == 'y' || c == 'Y')
6637                 return INPUT_STOP;
6638         if (c == 'n' || c == 'N')
6639                 return INPUT_CANCEL;
6640         return INPUT_SKIP;
6643 static bool
6644 prompt_yesno(const char *prompt)
6646         char prompt2[SIZEOF_STR];
6648         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6649                 return FALSE;
6651         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6654 static enum input_status
6655 read_prompt_handler(void *data, char *buf, int c)
6657         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6660 static char *
6661 read_prompt(const char *prompt)
6663         return prompt_input(prompt, read_prompt_handler, NULL);
6666 /*
6667  * Repository properties
6668  */
6670 static struct ref *refs = NULL;
6671 static size_t refs_size = 0;
6673 /* Id <-> ref store */
6674 static struct ref ***id_refs = NULL;
6675 static size_t id_refs_size = 0;
6677 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6678 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6679 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6681 static int
6682 compare_refs(const void *ref1_, const void *ref2_)
6684         const struct ref *ref1 = *(const struct ref **)ref1_;
6685         const struct ref *ref2 = *(const struct ref **)ref2_;
6687         if (ref1->tag != ref2->tag)
6688                 return ref2->tag - ref1->tag;
6689         if (ref1->ltag != ref2->ltag)
6690                 return ref2->ltag - ref2->ltag;
6691         if (ref1->head != ref2->head)
6692                 return ref2->head - ref1->head;
6693         if (ref1->tracked != ref2->tracked)
6694                 return ref2->tracked - ref1->tracked;
6695         if (ref1->remote != ref2->remote)
6696                 return ref2->remote - ref1->remote;
6697         return strcmp(ref1->name, ref2->name);
6700 static void
6701 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6703         size_t i;
6705         for (i = 0; i < refs_size; i++)
6706                 if (!visitor(data, &refs[i]))
6707                         break;
6710 static struct ref **
6711 get_refs(const char *id)
6713         struct ref **ref_list = NULL;
6714         size_t ref_list_size = 0;
6715         size_t i;
6717         for (i = 0; i < id_refs_size; i++)
6718                 if (!strcmp(id, id_refs[i][0]->id))
6719                         return id_refs[i];
6721         if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6722                 return NULL;
6724         for (i = 0; i < refs_size; i++) {
6725                 if (strcmp(id, refs[i].id))
6726                         continue;
6728                 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6729                         break;
6731                 ref_list[ref_list_size] = &refs[i];
6732                 /* XXX: The properties of the commit chains ensures that we can
6733                  * safely modify the shared ref. The repo references will
6734                  * always be similar for the same id. */
6735                 ref_list[ref_list_size]->next = 1;
6736                 ref_list_size++;
6737         }
6739         if (ref_list) {
6740                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6741                 ref_list[ref_list_size - 1]->next = 0;
6742                 id_refs[id_refs_size++] = ref_list;
6743         }
6745         return ref_list;
6748 static int
6749 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6751         struct ref *ref;
6752         bool tag = FALSE;
6753         bool ltag = FALSE;
6754         bool remote = FALSE;
6755         bool tracked = FALSE;
6756         bool check_replace = FALSE;
6757         bool head = FALSE;
6759         if (!prefixcmp(name, "refs/tags/")) {
6760                 if (!suffixcmp(name, namelen, "^{}")) {
6761                         namelen -= 3;
6762                         name[namelen] = 0;
6763                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6764                                 check_replace = TRUE;
6765                 } else {
6766                         ltag = TRUE;
6767                 }
6769                 tag = TRUE;
6770                 namelen -= STRING_SIZE("refs/tags/");
6771                 name    += STRING_SIZE("refs/tags/");
6773         } else if (!prefixcmp(name, "refs/remotes/")) {
6774                 remote = TRUE;
6775                 namelen -= STRING_SIZE("refs/remotes/");
6776                 name    += STRING_SIZE("refs/remotes/");
6777                 tracked  = !strcmp(opt_remote, name);
6779         } else if (!prefixcmp(name, "refs/heads/")) {
6780                 namelen -= STRING_SIZE("refs/heads/");
6781                 name    += STRING_SIZE("refs/heads/");
6782                 head     = !strncmp(opt_head, name, namelen);
6784         } else if (!strcmp(name, "HEAD")) {
6785                 string_ncopy(opt_head_rev, id, idlen);
6786                 return OK;
6787         }
6789         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6790                 /* it's an annotated tag, replace the previous SHA1 with the
6791                  * resolved commit id; relies on the fact git-ls-remote lists
6792                  * the commit id of an annotated tag right before the commit id
6793                  * it points to. */
6794                 refs[refs_size - 1].ltag = ltag;
6795                 string_copy_rev(refs[refs_size - 1].id, id);
6797                 return OK;
6798         }
6800         if (!realloc_refs(&refs, refs_size, 1))
6801                 return ERR;
6803         ref = &refs[refs_size++];
6804         ref->name = malloc(namelen + 1);
6805         if (!ref->name)
6806                 return ERR;
6808         strncpy(ref->name, name, namelen);
6809         ref->name[namelen] = 0;
6810         ref->head = head;
6811         ref->tag = tag;
6812         ref->ltag = ltag;
6813         ref->remote = remote;
6814         ref->tracked = tracked;
6815         string_copy_rev(ref->id, id);
6817         return OK;
6820 static int
6821 load_refs(void)
6823         const char *head_argv[] = {
6824                 "git", "symbolic-ref", "HEAD", NULL
6825         };
6826         static const char *ls_remote_argv[SIZEOF_ARG] = {
6827                 "git", "ls-remote", opt_git_dir, NULL
6828         };
6829         static bool init = FALSE;
6831         if (!init) {
6832                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6833                 init = TRUE;
6834         }
6836         if (!*opt_git_dir)
6837                 return OK;
6839         if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6840             !prefixcmp(opt_head, "refs/heads/")) {
6841                 char *offset = opt_head + STRING_SIZE("refs/heads/");
6843                 memmove(opt_head, offset, strlen(offset) + 1);
6844         }
6846         while (refs_size > 0)
6847                 free(refs[--refs_size].name);
6848         while (id_refs_size > 0)
6849                 free(id_refs[--id_refs_size]);
6851         return run_io_load(ls_remote_argv, "\t", read_ref);
6854 static void
6855 set_remote_branch(const char *name, const char *value, size_t valuelen)
6857         if (!strcmp(name, ".remote")) {
6858                 string_ncopy(opt_remote, value, valuelen);
6860         } else if (*opt_remote && !strcmp(name, ".merge")) {
6861                 size_t from = strlen(opt_remote);
6863                 if (!prefixcmp(value, "refs/heads/"))
6864                         value += STRING_SIZE("refs/heads/");
6866                 if (!string_format_from(opt_remote, &from, "/%s", value))
6867                         opt_remote[0] = 0;
6868         }
6871 static void
6872 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6874         const char *argv[SIZEOF_ARG] = { name, "=" };
6875         int argc = 1 + (cmd == option_set_command);
6876         int error = ERR;
6878         if (!argv_from_string(argv, &argc, value))
6879                 config_msg = "Too many option arguments";
6880         else
6881                 error = cmd(argc, argv);
6883         if (error == ERR)
6884                 warn("Option 'tig.%s': %s", name, config_msg);
6887 static bool
6888 set_environment_variable(const char *name, const char *value)
6890         size_t len = strlen(name) + 1 + strlen(value) + 1;
6891         char *env = malloc(len);
6893         if (env &&
6894             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6895             putenv(env) == 0)
6896                 return TRUE;
6897         free(env);
6898         return FALSE;
6901 static void
6902 set_work_tree(const char *value)
6904         char cwd[SIZEOF_STR];
6906         if (!getcwd(cwd, sizeof(cwd)))
6907                 die("Failed to get cwd path: %s", strerror(errno));
6908         if (chdir(opt_git_dir) < 0)
6909                 die("Failed to chdir(%s): %s", strerror(errno));
6910         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6911                 die("Failed to get git path: %s", strerror(errno));
6912         if (chdir(cwd) < 0)
6913                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6914         if (chdir(value) < 0)
6915                 die("Failed to chdir(%s): %s", value, strerror(errno));
6916         if (!getcwd(cwd, sizeof(cwd)))
6917                 die("Failed to get cwd path: %s", strerror(errno));
6918         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6919                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6920         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6921                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6922         opt_is_inside_work_tree = TRUE;
6925 static int
6926 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6928         if (!strcmp(name, "i18n.commitencoding"))
6929                 string_ncopy(opt_encoding, value, valuelen);
6931         else if (!strcmp(name, "core.editor"))
6932                 string_ncopy(opt_editor, value, valuelen);
6934         else if (!strcmp(name, "core.worktree"))
6935                 set_work_tree(value);
6937         else if (!prefixcmp(name, "tig.color."))
6938                 set_repo_config_option(name + 10, value, option_color_command);
6940         else if (!prefixcmp(name, "tig.bind."))
6941                 set_repo_config_option(name + 9, value, option_bind_command);
6943         else if (!prefixcmp(name, "tig."))
6944                 set_repo_config_option(name + 4, value, option_set_command);
6946         else if (*opt_head && !prefixcmp(name, "branch.") &&
6947                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6948                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6950         return OK;
6953 static int
6954 load_git_config(void)
6956         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6958         return run_io_load(config_list_argv, "=", read_repo_config_option);
6961 static int
6962 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6964         if (!opt_git_dir[0]) {
6965                 string_ncopy(opt_git_dir, name, namelen);
6967         } else if (opt_is_inside_work_tree == -1) {
6968                 /* This can be 3 different values depending on the
6969                  * version of git being used. If git-rev-parse does not
6970                  * understand --is-inside-work-tree it will simply echo
6971                  * the option else either "true" or "false" is printed.
6972                  * Default to true for the unknown case. */
6973                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6975         } else if (*name == '.') {
6976                 string_ncopy(opt_cdup, name, namelen);
6978         } else {
6979                 string_ncopy(opt_prefix, name, namelen);
6980         }
6982         return OK;
6985 static int
6986 load_repo_info(void)
6988         const char *rev_parse_argv[] = {
6989                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6990                         "--show-cdup", "--show-prefix", NULL
6991         };
6993         return run_io_load(rev_parse_argv, "=", read_repo_info);
6997 /*
6998  * Main
6999  */
7001 static const char usage[] =
7002 "tig " TIG_VERSION " (" __DATE__ ")\n"
7003 "\n"
7004 "Usage: tig        [options] [revs] [--] [paths]\n"
7005 "   or: tig show   [options] [revs] [--] [paths]\n"
7006 "   or: tig blame  [rev] path\n"
7007 "   or: tig status\n"
7008 "   or: tig <      [git command output]\n"
7009 "\n"
7010 "Options:\n"
7011 "  -v, --version   Show version and exit\n"
7012 "  -h, --help      Show help message and exit";
7014 static void __NORETURN
7015 quit(int sig)
7017         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7018         if (cursed)
7019                 endwin();
7020         exit(0);
7023 static void __NORETURN
7024 die(const char *err, ...)
7026         va_list args;
7028         endwin();
7030         va_start(args, err);
7031         fputs("tig: ", stderr);
7032         vfprintf(stderr, err, args);
7033         fputs("\n", stderr);
7034         va_end(args);
7036         exit(1);
7039 static void
7040 warn(const char *msg, ...)
7042         va_list args;
7044         va_start(args, msg);
7045         fputs("tig warning: ", stderr);
7046         vfprintf(stderr, msg, args);
7047         fputs("\n", stderr);
7048         va_end(args);
7051 static enum request
7052 parse_options(int argc, const char *argv[])
7054         enum request request = REQ_VIEW_MAIN;
7055         const char *subcommand;
7056         bool seen_dashdash = FALSE;
7057         /* XXX: This is vulnerable to the user overriding options
7058          * required for the main view parser. */
7059         const char *custom_argv[SIZEOF_ARG] = {
7060                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7061                         "--topo-order", NULL
7062         };
7063         int i, j = 6;
7065         if (!isatty(STDIN_FILENO)) {
7066                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7067                 return REQ_VIEW_PAGER;
7068         }
7070         if (argc <= 1)
7071                 return REQ_NONE;
7073         subcommand = argv[1];
7074         if (!strcmp(subcommand, "status")) {
7075                 if (argc > 2)
7076                         warn("ignoring arguments after `%s'", subcommand);
7077                 return REQ_VIEW_STATUS;
7079         } else if (!strcmp(subcommand, "blame")) {
7080                 if (argc <= 2 || argc > 4)
7081                         die("invalid number of options to blame\n\n%s", usage);
7083                 i = 2;
7084                 if (argc == 4) {
7085                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7086                         i++;
7087                 }
7089                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7090                 return REQ_VIEW_BLAME;
7092         } else if (!strcmp(subcommand, "show")) {
7093                 request = REQ_VIEW_DIFF;
7095         } else {
7096                 subcommand = NULL;
7097         }
7099         if (subcommand) {
7100                 custom_argv[1] = subcommand;
7101                 j = 2;
7102         }
7104         for (i = 1 + !!subcommand; i < argc; i++) {
7105                 const char *opt = argv[i];
7107                 if (seen_dashdash || !strcmp(opt, "--")) {
7108                         seen_dashdash = TRUE;
7110                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7111                         printf("tig version %s\n", TIG_VERSION);
7112                         quit(0);
7114                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7115                         printf("%s\n", usage);
7116                         quit(0);
7117                 }
7119                 custom_argv[j++] = opt;
7120                 if (j >= ARRAY_SIZE(custom_argv))
7121                         die("command too long");
7122         }
7124         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7125                 die("Failed to format arguments"); 
7127         return request;
7130 int
7131 main(int argc, const char *argv[])
7133         enum request request = parse_options(argc, argv);
7134         struct view *view;
7135         size_t i;
7137         signal(SIGINT, quit);
7138         signal(SIGPIPE, SIG_IGN);
7140         if (setlocale(LC_ALL, "")) {
7141                 char *codeset = nl_langinfo(CODESET);
7143                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7144         }
7146         if (load_repo_info() == ERR)
7147                 die("Failed to load repo info.");
7149         if (load_options() == ERR)
7150                 die("Failed to load user config.");
7152         if (load_git_config() == ERR)
7153                 die("Failed to load repo config.");
7155         /* Require a git repository unless when running in pager mode. */
7156         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7157                 die("Not a git repository");
7159         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7160                 opt_utf8 = FALSE;
7162         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7163                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7164                 if (opt_iconv == ICONV_NONE)
7165                         die("Failed to initialize character set conversion");
7166         }
7168         if (load_refs() == ERR)
7169                 die("Failed to load refs.");
7171         foreach_view (view, i)
7172                 argv_from_env(view->ops->argv, view->cmd_env);
7174         init_display();
7176         if (request != REQ_NONE)
7177                 open_view(NULL, request, OPEN_PREPARED);
7178         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7180         while (view_driver(display[current_view], request)) {
7181                 int key = get_input(0);
7183                 view = display[current_view];
7184                 request = get_keybinding(view->keymap, key);
7186                 /* Some low-level request handling. This keeps access to
7187                  * status_win restricted. */
7188                 switch (request) {
7189                 case REQ_PROMPT:
7190                 {
7191                         char *cmd = read_prompt(":");
7193                         if (cmd && isdigit(*cmd)) {
7194                                 int lineno = view->lineno + 1;
7196                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7197                                         select_view_line(view, lineno - 1);
7198                                         report("");
7199                                 } else {
7200                                         report("Unable to parse '%s' as a line number", cmd);
7201                                 }
7203                         } else if (cmd) {
7204                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7205                                 const char *argv[SIZEOF_ARG] = { "git" };
7206                                 int argc = 1;
7208                                 /* When running random commands, initially show the
7209                                  * command in the title. However, it maybe later be
7210                                  * overwritten if a commit line is selected. */
7211                                 string_ncopy(next->ref, cmd, strlen(cmd));
7213                                 if (!argv_from_string(argv, &argc, cmd)) {
7214                                         report("Too many arguments");
7215                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7216                                         report("Failed to format command");
7217                                 } else {
7218                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7219                                 }
7220                         }
7222                         request = REQ_NONE;
7223                         break;
7224                 }
7225                 case REQ_SEARCH:
7226                 case REQ_SEARCH_BACK:
7227                 {
7228                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7229                         char *search = read_prompt(prompt);
7231                         if (search)
7232                                 string_ncopy(opt_search, search, strlen(search));
7233                         else if (*opt_search)
7234                                 request = request == REQ_SEARCH ?
7235                                         REQ_FIND_NEXT :
7236                                         REQ_FIND_PREV;
7237                         else
7238                                 request = REQ_NONE;
7239                         break;
7240                 }
7241                 default:
7242                         break;
7243                 }
7244         }
7246         quit(0);
7248         return 0;