Code

Refactor the int_map interface into new enum_map interface
[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
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 /*
164  * String helpers
165  */
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170         if (srclen > dstlen - 1)
171                 srclen = dstlen - 1;
173         strncpy(dst, src, srclen);
174         dst[srclen] = 0;
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183         string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static size_t
192 string_expand_length(const char *line, int tabsize)
194         size_t size, pos;
196         for (pos = 0; line[pos]; pos++) {
197                 if (line[pos] == '\t' && tabsize > 0)
198                         size += tabsize - (size % tabsize);
199                 else
200                         size++;
201         }
202         return size;
205 static void
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
208         size_t size, pos;
210         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211                 if (src[pos] == '\t') {
212                         size_t expanded = tabsize - (size % tabsize);
214                         if (expanded + size >= dstlen - 1)
215                                 expanded = dstlen - size - 1;
216                         memcpy(dst + size, "        ", expanded);
217                         size += expanded;
218                 } else {
219                         dst[size++] = src[pos];
220                 }
221         }
223         dst[size] = 0;
226 static char *
227 chomp_string(char *name)
229         int namelen;
231         while (isspace(*name))
232                 name++;
234         namelen = strlen(name) - 1;
235         while (namelen > 0 && isspace(name[namelen]))
236                 name[namelen--] = 0;
238         return name;
241 static bool
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
244         va_list args;
245         size_t pos = bufpos ? *bufpos : 0;
247         va_start(args, fmt);
248         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
249         va_end(args);
251         if (bufpos)
252                 *bufpos = pos;
254         return pos >= bufsize ? FALSE : TRUE;
257 #define string_format(buf, fmt, args...) \
258         string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261         string_nformat(buf, sizeof(buf), from, fmt, args)
263 static int
264 string_enum_compare(const char *str1, const char *str2, int len)
266         size_t i;
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270         /* Diff-Header == DIFF_HEADER */
271         for (i = 0; i < len; i++) {
272                 if (toupper(str1[i]) == toupper(str2[i]))
273                         continue;
275                 if (string_enum_sep(str1[i]) &&
276                     string_enum_sep(str2[i]))
277                         continue;
279                 return str1[i] - str2[i];
280         }
282         return 0;
285 struct enum_map {
286         const char *name;
287         int namelen;
288         int value;
289 };
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
293 static bool
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
296         size_t namelen = strlen(name);
297         int i;
299         for (i = 0; i < map_size; i++)
300                 if (namelen == map[i].namelen &&
301                     !string_enum_compare(name, map[i].name, namelen)) {
302                         *value = map[i].value;
303                         return TRUE;
304                 }
306         return FALSE;
309 #define map_enum(attr, map, name) \
310         map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313         strncmp(str1, str2, STRING_SIZE(str2))
315 static inline int
316 suffixcmp(const char *str, int slen, const char *suffix)
318         size_t len = slen >= 0 ? slen : strlen(str);
319         size_t suffixlen = strlen(suffix);
321         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
325 static bool
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
328         int valuelen;
330         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331                 bool advance = cmd[valuelen] != 0;
333                 cmd[valuelen] = 0;
334                 argv[(*argc)++] = chomp_string(cmd);
335                 cmd = chomp_string(cmd + valuelen + advance);
336         }
338         if (*argc < SIZEOF_ARG)
339                 argv[*argc] = NULL;
340         return *argc < SIZEOF_ARG;
343 static void
344 argv_from_env(const char **argv, const char *name)
346         char *env = argv ? getenv(name) : NULL;
347         int argc = 0;
349         if (env && *env)
350                 env = strdup(env);
351         if (env && !argv_from_string(argv, &argc, env))
352                 die("Too many arguments in the `%s` environment variable", name);
356 /*
357  * Executing external commands.
358  */
360 enum io_type {
361         IO_FD,                  /* File descriptor based IO. */
362         IO_BG,                  /* Execute command in the background. */
363         IO_FG,                  /* Execute command with same std{in,out,err}. */
364         IO_RD,                  /* Read only fork+exec IO. */
365         IO_WR,                  /* Write only fork+exec IO. */
366         IO_AP,                  /* Append fork+exec output to file. */
367 };
369 struct io {
370         enum io_type type;      /* The requested type of pipe. */
371         const char *dir;        /* Directory from which to execute. */
372         pid_t pid;              /* Pipe for reading or writing. */
373         int pipe;               /* Pipe end for reading or writing. */
374         int error;              /* Error status. */
375         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
376         char *buf;              /* Read buffer. */
377         size_t bufalloc;        /* Allocated buffer size. */
378         size_t bufsize;         /* Buffer content size. */
379         char *bufpos;           /* Current buffer position. */
380         unsigned int eof:1;     /* Has end of file been reached. */
381 };
383 static void
384 reset_io(struct io *io)
386         io->pipe = -1;
387         io->pid = 0;
388         io->buf = io->bufpos = NULL;
389         io->bufalloc = io->bufsize = 0;
390         io->error = 0;
391         io->eof = 0;
394 static void
395 init_io(struct io *io, const char *dir, enum io_type type)
397         reset_io(io);
398         io->type = type;
399         io->dir = dir;
402 static bool
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404                 enum format_flags flags)
406         init_io(io, dir, IO_RD);
407         return format_argv(io->argv, argv, flags);
410 static bool
411 io_open(struct io *io, const char *name)
413         init_io(io, NULL, IO_FD);
414         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415         return io->pipe != -1;
418 static bool
419 kill_io(struct io *io)
421         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
424 static bool
425 done_io(struct io *io)
427         pid_t pid = io->pid;
429         if (io->pipe != -1)
430                 close(io->pipe);
431         free(io->buf);
432         reset_io(io);
434         while (pid > 0) {
435                 int status;
436                 pid_t waiting = waitpid(pid, &status, 0);
438                 if (waiting < 0) {
439                         if (errno == EINTR)
440                                 continue;
441                         report("waitpid failed (%s)", strerror(errno));
442                         return FALSE;
443                 }
445                 return waiting == pid &&
446                        !WIFSIGNALED(status) &&
447                        WIFEXITED(status) &&
448                        !WEXITSTATUS(status);
449         }
451         return TRUE;
454 static bool
455 start_io(struct io *io)
457         int pipefds[2] = { -1, -1 };
459         if (io->type == IO_FD)
460                 return TRUE;
462         if ((io->type == IO_RD || io->type == IO_WR) &&
463             pipe(pipefds) < 0)
464                 return FALSE;
465         else if (io->type == IO_AP)
466                 pipefds[1] = io->pipe;
468         if ((io->pid = fork())) {
469                 if (pipefds[!(io->type == IO_WR)] != -1)
470                         close(pipefds[!(io->type == IO_WR)]);
471                 if (io->pid != -1) {
472                         io->pipe = pipefds[!!(io->type == IO_WR)];
473                         return TRUE;
474                 }
476         } else {
477                 if (io->type != IO_FG) {
478                         int devnull = open("/dev/null", O_RDWR);
479                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
480                         int writefd = (io->type == IO_RD || io->type == IO_AP)
481                                                         ? pipefds[1] : devnull;
483                         dup2(readfd,  STDIN_FILENO);
484                         dup2(writefd, STDOUT_FILENO);
485                         dup2(devnull, STDERR_FILENO);
487                         close(devnull);
488                         if (pipefds[0] != -1)
489                                 close(pipefds[0]);
490                         if (pipefds[1] != -1)
491                                 close(pipefds[1]);
492                 }
494                 if (io->dir && *io->dir && chdir(io->dir) == -1)
495                         die("Failed to change directory: %s", strerror(errno));
497                 execvp(io->argv[0], (char *const*) io->argv);
498                 die("Failed to execute program: %s", strerror(errno));
499         }
501         if (pipefds[!!(io->type == IO_WR)] != -1)
502                 close(pipefds[!!(io->type == IO_WR)]);
503         return FALSE;
506 static bool
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
509         init_io(io, dir, type);
510         if (!format_argv(io->argv, argv, FORMAT_NONE))
511                 return FALSE;
512         return start_io(io);
515 static int
516 run_io_do(struct io *io)
518         return start_io(io) && done_io(io);
521 static int
522 run_io_bg(const char **argv)
524         struct io io = {};
526         init_io(&io, NULL, IO_BG);
527         if (!format_argv(io.argv, argv, FORMAT_NONE))
528                 return FALSE;
529         return run_io_do(&io);
532 static bool
533 run_io_fg(const char **argv, const char *dir)
535         struct io io = {};
537         init_io(&io, dir, IO_FG);
538         if (!format_argv(io.argv, argv, FORMAT_NONE))
539                 return FALSE;
540         return run_io_do(&io);
543 static bool
544 run_io_append(const char **argv, enum format_flags flags, int fd)
546         struct io io = {};
548         init_io(&io, NULL, IO_AP);
549         io.pipe = fd;
550         if (format_argv(io.argv, argv, flags))
551                 return run_io_do(&io);
552         close(fd);
553         return FALSE;
556 static bool
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
559         return init_io_rd(io, argv, NULL, flags) && start_io(io);
562 static bool
563 io_eof(struct io *io)
565         return io->eof;
568 static int
569 io_error(struct io *io)
571         return io->error;
574 static bool
575 io_strerror(struct io *io)
577         return strerror(io->error);
580 static bool
581 io_can_read(struct io *io)
583         struct timeval tv = { 0, 500 };
584         fd_set fds;
586         FD_ZERO(&fds);
587         FD_SET(io->pipe, &fds);
589         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
592 static ssize_t
593 io_read(struct io *io, void *buf, size_t bufsize)
595         do {
596                 ssize_t readsize = read(io->pipe, buf, bufsize);
598                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
599                         continue;
600                 else if (readsize == -1)
601                         io->error = errno;
602                 else if (readsize == 0)
603                         io->eof = 1;
604                 return readsize;
605         } while (1);
608 static char *
609 io_get(struct io *io, int c, bool can_read)
611         char *eol;
612         ssize_t readsize;
614         if (!io->buf) {
615                 io->buf = io->bufpos = malloc(BUFSIZ);
616                 if (!io->buf)
617                         return NULL;
618                 io->bufalloc = BUFSIZ;
619                 io->bufsize = 0;
620         }
622         while (TRUE) {
623                 if (io->bufsize > 0) {
624                         eol = memchr(io->bufpos, c, io->bufsize);
625                         if (eol) {
626                                 char *line = io->bufpos;
628                                 *eol = 0;
629                                 io->bufpos = eol + 1;
630                                 io->bufsize -= io->bufpos - line;
631                                 return line;
632                         }
633                 }
635                 if (io_eof(io)) {
636                         if (io->bufsize) {
637                                 io->bufpos[io->bufsize] = 0;
638                                 io->bufsize = 0;
639                                 return io->bufpos;
640                         }
641                         return NULL;
642                 }
644                 if (!can_read)
645                         return NULL;
647                 if (io->bufsize > 0 && io->bufpos > io->buf)
648                         memmove(io->buf, io->bufpos, io->bufsize);
650                 io->bufpos = io->buf;
651                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
652                 if (io_error(io))
653                         return NULL;
654                 io->bufsize += readsize;
655         }
658 static bool
659 io_write(struct io *io, const void *buf, size_t bufsize)
661         size_t written = 0;
663         while (!io_error(io) && written < bufsize) {
664                 ssize_t size;
666                 size = write(io->pipe, buf + written, bufsize - written);
667                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
668                         continue;
669                 else if (size == -1)
670                         io->error = errno;
671                 else
672                         written += size;
673         }
675         return written == bufsize;
678 static bool
679 run_io_buf(const char **argv, char buf[], size_t bufsize)
681         struct io io = {};
682         bool error;
684         if (!run_io_rd(&io, argv, FORMAT_NONE))
685                 return FALSE;
687         io.buf = io.bufpos = buf;
688         io.bufalloc = bufsize;
689         error = !io_get(&io, '\n', TRUE) && io_error(&io);
690         io.buf = NULL;
692         return done_io(&io) || error;
695 static int
696 io_load(struct io *io, const char *separators,
697         int (*read_property)(char *, size_t, char *, size_t))
699         char *name;
700         int state = OK;
702         if (!start_io(io))
703                 return ERR;
705         while (state == OK && (name = io_get(io, '\n', TRUE))) {
706                 char *value;
707                 size_t namelen;
708                 size_t valuelen;
710                 name = chomp_string(name);
711                 namelen = strcspn(name, separators);
713                 if (name[namelen]) {
714                         name[namelen] = 0;
715                         value = chomp_string(name + namelen + 1);
716                         valuelen = strlen(value);
718                 } else {
719                         value = "";
720                         valuelen = 0;
721                 }
723                 state = read_property(name, namelen, value, valuelen);
724         }
726         if (state != ERR && io_error(io))
727                 state = ERR;
728         done_io(io);
730         return state;
733 static int
734 run_io_load(const char **argv, const char *separators,
735             int (*read_property)(char *, size_t, char *, size_t))
737         struct io io = {};
739         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
740                 ? io_load(&io, separators, read_property) : ERR;
744 /*
745  * User requests
746  */
748 #define REQ_INFO \
749         /* XXX: Keep the view request first and in sync with views[]. */ \
750         REQ_GROUP("View switching") \
751         REQ_(VIEW_MAIN,         "Show main view"), \
752         REQ_(VIEW_DIFF,         "Show diff view"), \
753         REQ_(VIEW_LOG,          "Show log view"), \
754         REQ_(VIEW_TREE,         "Show tree view"), \
755         REQ_(VIEW_BLOB,         "Show blob view"), \
756         REQ_(VIEW_BLAME,        "Show blame view"), \
757         REQ_(VIEW_HELP,         "Show help page"), \
758         REQ_(VIEW_PAGER,        "Show pager view"), \
759         REQ_(VIEW_STATUS,       "Show status view"), \
760         REQ_(VIEW_STAGE,        "Show stage view"), \
761         \
762         REQ_GROUP("View manipulation") \
763         REQ_(ENTER,             "Enter current line and scroll"), \
764         REQ_(NEXT,              "Move to next"), \
765         REQ_(PREVIOUS,          "Move to previous"), \
766         REQ_(PARENT,            "Move to parent"), \
767         REQ_(VIEW_NEXT,         "Move focus to next view"), \
768         REQ_(REFRESH,           "Reload and refresh"), \
769         REQ_(MAXIMIZE,          "Maximize the current view"), \
770         REQ_(VIEW_CLOSE,        "Close the current view"), \
771         REQ_(QUIT,              "Close all views and quit"), \
772         \
773         REQ_GROUP("View specific requests") \
774         REQ_(STATUS_UPDATE,     "Update file status"), \
775         REQ_(STATUS_REVERT,     "Revert file changes"), \
776         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
777         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
778         \
779         REQ_GROUP("Cursor navigation") \
780         REQ_(MOVE_UP,           "Move cursor one line up"), \
781         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
782         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
783         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
784         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
785         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
786         \
787         REQ_GROUP("Scrolling") \
788         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
789         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
790         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
791         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
792         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
793         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
794         \
795         REQ_GROUP("Searching") \
796         REQ_(SEARCH,            "Search the view"), \
797         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
798         REQ_(FIND_NEXT,         "Find next search match"), \
799         REQ_(FIND_PREV,         "Find previous search match"), \
800         \
801         REQ_GROUP("Option manipulation") \
802         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
803         REQ_(TOGGLE_DATE,       "Toggle date display"), \
804         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
805         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
806         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
807         \
808         REQ_GROUP("Misc") \
809         REQ_(PROMPT,            "Bring up the prompt"), \
810         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
811         REQ_(SHOW_VERSION,      "Show version information"), \
812         REQ_(STOP_LOADING,      "Stop all loading views"), \
813         REQ_(EDIT,              "Open in editor"), \
814         REQ_(NONE,              "Do nothing")
817 /* User action requests. */
818 enum request {
819 #define REQ_GROUP(help)
820 #define REQ_(req, help) REQ_##req
822         /* Offset all requests to avoid conflicts with ncurses getch values. */
823         REQ_OFFSET = KEY_MAX + 1,
824         REQ_INFO
826 #undef  REQ_GROUP
827 #undef  REQ_
828 };
830 struct request_info {
831         enum request request;
832         const char *name;
833         int namelen;
834         const char *help;
835 };
837 static struct request_info req_info[] = {
838 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
839 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
840         REQ_INFO
841 #undef  REQ_GROUP
842 #undef  REQ_
843 };
845 static enum request
846 get_request(const char *name)
848         int namelen = strlen(name);
849         int i;
851         for (i = 0; i < ARRAY_SIZE(req_info); i++)
852                 if (req_info[i].namelen == namelen &&
853                     !string_enum_compare(req_info[i].name, name, namelen))
854                         return req_info[i].request;
856         return REQ_NONE;
860 /*
861  * Options
862  */
864 /* Option and state variables. */
865 static bool opt_date                    = TRUE;
866 static bool opt_author                  = TRUE;
867 static bool opt_line_number             = FALSE;
868 static bool opt_line_graphics           = TRUE;
869 static bool opt_rev_graph               = FALSE;
870 static bool opt_show_refs               = TRUE;
871 static int opt_num_interval             = NUMBER_INTERVAL;
872 static int opt_tab_size                 = TAB_SIZE;
873 static int opt_author_cols              = AUTHOR_COLS-1;
874 static char opt_path[SIZEOF_STR]        = "";
875 static char opt_file[SIZEOF_STR]        = "";
876 static char opt_ref[SIZEOF_REF]         = "";
877 static char opt_head[SIZEOF_REF]        = "";
878 static char opt_head_rev[SIZEOF_REV]    = "";
879 static char opt_remote[SIZEOF_REF]      = "";
880 static char opt_encoding[20]            = "UTF-8";
881 static bool opt_utf8                    = TRUE;
882 static char opt_codeset[20]             = "UTF-8";
883 static iconv_t opt_iconv                = ICONV_NONE;
884 static char opt_search[SIZEOF_STR]      = "";
885 static char opt_cdup[SIZEOF_STR]        = "";
886 static char opt_prefix[SIZEOF_STR]      = "";
887 static char opt_git_dir[SIZEOF_STR]     = "";
888 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
889 static char opt_editor[SIZEOF_STR]      = "";
890 static FILE *opt_tty                    = NULL;
892 #define is_initial_commit()     (!*opt_head_rev)
893 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
896 /*
897  * Line-oriented content detection.
898  */
900 #define LINE_INFO \
901 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
902 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
903 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
904 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
905 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
906 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
909 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
910 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
911 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
912 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
913 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
915 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
916 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
917 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
918 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
919 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
920 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
921 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
922 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
923 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
924 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
925 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
926 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
927 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
929 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
930 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
931 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
932 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
933 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
934 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
935 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
936 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
937 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
938 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
939 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
940 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
941 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
942 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
943 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
944 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
945 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
946 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
947 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
948 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
949 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
950 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
951 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
952 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
954 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
955 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
957 enum line_type {
958 #define LINE(type, line, fg, bg, attr) \
959         LINE_##type
960         LINE_INFO,
961         LINE_NONE
962 #undef  LINE
963 };
965 struct line_info {
966         const char *name;       /* Option name. */
967         int namelen;            /* Size of option name. */
968         const char *line;       /* The start of line to match. */
969         int linelen;            /* Size of string to match. */
970         int fg, bg, attr;       /* Color and text attributes for the lines. */
971 };
973 static struct line_info line_info[] = {
974 #define LINE(type, line, fg, bg, attr) \
975         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
976         LINE_INFO
977 #undef  LINE
978 };
980 static enum line_type
981 get_line_type(const char *line)
983         int linelen = strlen(line);
984         enum line_type type;
986         for (type = 0; type < ARRAY_SIZE(line_info); type++)
987                 /* Case insensitive search matches Signed-off-by lines better. */
988                 if (linelen >= line_info[type].linelen &&
989                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
990                         return type;
992         return LINE_DEFAULT;
995 static inline int
996 get_line_attr(enum line_type type)
998         assert(type < ARRAY_SIZE(line_info));
999         return COLOR_PAIR(type) | line_info[type].attr;
1002 static struct line_info *
1003 get_line_info(const char *name)
1005         size_t namelen = strlen(name);
1006         enum line_type type;
1008         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1009                 if (namelen == line_info[type].namelen &&
1010                     !string_enum_compare(line_info[type].name, name, namelen))
1011                         return &line_info[type];
1013         return NULL;
1016 static void
1017 init_colors(void)
1019         int default_bg = line_info[LINE_DEFAULT].bg;
1020         int default_fg = line_info[LINE_DEFAULT].fg;
1021         enum line_type type;
1023         start_color();
1025         if (assume_default_colors(default_fg, default_bg) == ERR) {
1026                 default_bg = COLOR_BLACK;
1027                 default_fg = COLOR_WHITE;
1028         }
1030         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1031                 struct line_info *info = &line_info[type];
1032                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1033                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1035                 init_pair(type, fg, bg);
1036         }
1039 struct line {
1040         enum line_type type;
1042         /* State flags */
1043         unsigned int selected:1;
1044         unsigned int dirty:1;
1045         unsigned int cleareol:1;
1047         void *data;             /* User data */
1048 };
1051 /*
1052  * Keys
1053  */
1055 struct keybinding {
1056         int alias;
1057         enum request request;
1058 };
1060 static struct keybinding default_keybindings[] = {
1061         /* View switching */
1062         { 'm',          REQ_VIEW_MAIN },
1063         { 'd',          REQ_VIEW_DIFF },
1064         { 'l',          REQ_VIEW_LOG },
1065         { 't',          REQ_VIEW_TREE },
1066         { 'f',          REQ_VIEW_BLOB },
1067         { 'B',          REQ_VIEW_BLAME },
1068         { 'p',          REQ_VIEW_PAGER },
1069         { 'h',          REQ_VIEW_HELP },
1070         { 'S',          REQ_VIEW_STATUS },
1071         { 'c',          REQ_VIEW_STAGE },
1073         /* View manipulation */
1074         { 'q',          REQ_VIEW_CLOSE },
1075         { KEY_TAB,      REQ_VIEW_NEXT },
1076         { KEY_RETURN,   REQ_ENTER },
1077         { KEY_UP,       REQ_PREVIOUS },
1078         { KEY_DOWN,     REQ_NEXT },
1079         { 'R',          REQ_REFRESH },
1080         { KEY_F(5),     REQ_REFRESH },
1081         { 'O',          REQ_MAXIMIZE },
1083         /* Cursor navigation */
1084         { 'k',          REQ_MOVE_UP },
1085         { 'j',          REQ_MOVE_DOWN },
1086         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1087         { KEY_END,      REQ_MOVE_LAST_LINE },
1088         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1089         { ' ',          REQ_MOVE_PAGE_DOWN },
1090         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1091         { 'b',          REQ_MOVE_PAGE_UP },
1092         { '-',          REQ_MOVE_PAGE_UP },
1094         /* Scrolling */
1095         { KEY_LEFT,     REQ_SCROLL_LEFT },
1096         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1097         { KEY_IC,       REQ_SCROLL_LINE_UP },
1098         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1099         { 'w',          REQ_SCROLL_PAGE_UP },
1100         { 's',          REQ_SCROLL_PAGE_DOWN },
1102         /* Searching */
1103         { '/',          REQ_SEARCH },
1104         { '?',          REQ_SEARCH_BACK },
1105         { 'n',          REQ_FIND_NEXT },
1106         { 'N',          REQ_FIND_PREV },
1108         /* Misc */
1109         { 'Q',          REQ_QUIT },
1110         { 'z',          REQ_STOP_LOADING },
1111         { 'v',          REQ_SHOW_VERSION },
1112         { 'r',          REQ_SCREEN_REDRAW },
1113         { '.',          REQ_TOGGLE_LINENO },
1114         { 'D',          REQ_TOGGLE_DATE },
1115         { 'A',          REQ_TOGGLE_AUTHOR },
1116         { 'g',          REQ_TOGGLE_REV_GRAPH },
1117         { 'F',          REQ_TOGGLE_REFS },
1118         { ':',          REQ_PROMPT },
1119         { 'u',          REQ_STATUS_UPDATE },
1120         { '!',          REQ_STATUS_REVERT },
1121         { 'M',          REQ_STATUS_MERGE },
1122         { '@',          REQ_STAGE_NEXT },
1123         { ',',          REQ_PARENT },
1124         { 'e',          REQ_EDIT },
1125 };
1127 #define KEYMAP_INFO \
1128         KEYMAP_(GENERIC), \
1129         KEYMAP_(MAIN), \
1130         KEYMAP_(DIFF), \
1131         KEYMAP_(LOG), \
1132         KEYMAP_(TREE), \
1133         KEYMAP_(BLOB), \
1134         KEYMAP_(BLAME), \
1135         KEYMAP_(PAGER), \
1136         KEYMAP_(HELP), \
1137         KEYMAP_(STATUS), \
1138         KEYMAP_(STAGE)
1140 enum keymap {
1141 #define KEYMAP_(name) KEYMAP_##name
1142         KEYMAP_INFO
1143 #undef  KEYMAP_
1144 };
1146 static struct enum_map keymap_table[] = {
1147 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1148         KEYMAP_INFO
1149 #undef  KEYMAP_
1150 };
1152 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1154 struct keybinding_table {
1155         struct keybinding *data;
1156         size_t size;
1157 };
1159 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1161 static void
1162 add_keybinding(enum keymap keymap, enum request request, int key)
1164         struct keybinding_table *table = &keybindings[keymap];
1166         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1167         if (!table->data)
1168                 die("Failed to allocate keybinding");
1169         table->data[table->size].alias = key;
1170         table->data[table->size++].request = request;
1173 /* Looks for a key binding first in the given map, then in the generic map, and
1174  * lastly in the default keybindings. */
1175 static enum request
1176 get_keybinding(enum keymap keymap, int key)
1178         size_t i;
1180         for (i = 0; i < keybindings[keymap].size; i++)
1181                 if (keybindings[keymap].data[i].alias == key)
1182                         return keybindings[keymap].data[i].request;
1184         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1185                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1186                         return keybindings[KEYMAP_GENERIC].data[i].request;
1188         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1189                 if (default_keybindings[i].alias == key)
1190                         return default_keybindings[i].request;
1192         return (enum request) key;
1196 struct key {
1197         const char *name;
1198         int value;
1199 };
1201 static struct key key_table[] = {
1202         { "Enter",      KEY_RETURN },
1203         { "Space",      ' ' },
1204         { "Backspace",  KEY_BACKSPACE },
1205         { "Tab",        KEY_TAB },
1206         { "Escape",     KEY_ESC },
1207         { "Left",       KEY_LEFT },
1208         { "Right",      KEY_RIGHT },
1209         { "Up",         KEY_UP },
1210         { "Down",       KEY_DOWN },
1211         { "Insert",     KEY_IC },
1212         { "Delete",     KEY_DC },
1213         { "Hash",       '#' },
1214         { "Home",       KEY_HOME },
1215         { "End",        KEY_END },
1216         { "PageUp",     KEY_PPAGE },
1217         { "PageDown",   KEY_NPAGE },
1218         { "F1",         KEY_F(1) },
1219         { "F2",         KEY_F(2) },
1220         { "F3",         KEY_F(3) },
1221         { "F4",         KEY_F(4) },
1222         { "F5",         KEY_F(5) },
1223         { "F6",         KEY_F(6) },
1224         { "F7",         KEY_F(7) },
1225         { "F8",         KEY_F(8) },
1226         { "F9",         KEY_F(9) },
1227         { "F10",        KEY_F(10) },
1228         { "F11",        KEY_F(11) },
1229         { "F12",        KEY_F(12) },
1230 };
1232 static int
1233 get_key_value(const char *name)
1235         int i;
1237         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1238                 if (!strcasecmp(key_table[i].name, name))
1239                         return key_table[i].value;
1241         if (strlen(name) == 1 && isprint(*name))
1242                 return (int) *name;
1244         return ERR;
1247 static const char *
1248 get_key_name(int key_value)
1250         static char key_char[] = "'X'";
1251         const char *seq = NULL;
1252         int key;
1254         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1255                 if (key_table[key].value == key_value)
1256                         seq = key_table[key].name;
1258         if (seq == NULL &&
1259             key_value < 127 &&
1260             isprint(key_value)) {
1261                 key_char[1] = (char) key_value;
1262                 seq = key_char;
1263         }
1265         return seq ? seq : "(no key)";
1268 static const char *
1269 get_key(enum request request)
1271         static char buf[BUFSIZ];
1272         size_t pos = 0;
1273         char *sep = "";
1274         int i;
1276         buf[pos] = 0;
1278         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1279                 struct keybinding *keybinding = &default_keybindings[i];
1281                 if (keybinding->request != request)
1282                         continue;
1284                 if (!string_format_from(buf, &pos, "%s%s", sep,
1285                                         get_key_name(keybinding->alias)))
1286                         return "Too many keybindings!";
1287                 sep = ", ";
1288         }
1290         return buf;
1293 struct run_request {
1294         enum keymap keymap;
1295         int key;
1296         const char *argv[SIZEOF_ARG];
1297 };
1299 static struct run_request *run_request;
1300 static size_t run_requests;
1302 static enum request
1303 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1305         struct run_request *req;
1307         if (argc >= ARRAY_SIZE(req->argv) - 1)
1308                 return REQ_NONE;
1310         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1311         if (!req)
1312                 return REQ_NONE;
1314         run_request = req;
1315         req = &run_request[run_requests];
1316         req->keymap = keymap;
1317         req->key = key;
1318         req->argv[0] = NULL;
1320         if (!format_argv(req->argv, argv, FORMAT_NONE))
1321                 return REQ_NONE;
1323         return REQ_NONE + ++run_requests;
1326 static struct run_request *
1327 get_run_request(enum request request)
1329         if (request <= REQ_NONE)
1330                 return NULL;
1331         return &run_request[request - REQ_NONE - 1];
1334 static void
1335 add_builtin_run_requests(void)
1337         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1338         const char *gc[] = { "git", "gc", NULL };
1339         struct {
1340                 enum keymap keymap;
1341                 int key;
1342                 int argc;
1343                 const char **argv;
1344         } reqs[] = {
1345                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1346                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1347         };
1348         int i;
1350         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1351                 enum request req;
1353                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1354                 if (req != REQ_NONE)
1355                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1356         }
1359 /*
1360  * User config file handling.
1361  */
1363 static struct enum_map color_map[] = {
1364 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1365         COLOR_MAP(DEFAULT),
1366         COLOR_MAP(BLACK),
1367         COLOR_MAP(BLUE),
1368         COLOR_MAP(CYAN),
1369         COLOR_MAP(GREEN),
1370         COLOR_MAP(MAGENTA),
1371         COLOR_MAP(RED),
1372         COLOR_MAP(WHITE),
1373         COLOR_MAP(YELLOW),
1374 };
1376 static struct enum_map attr_map[] = {
1377 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1378         ATTR_MAP(NORMAL),
1379         ATTR_MAP(BLINK),
1380         ATTR_MAP(BOLD),
1381         ATTR_MAP(DIM),
1382         ATTR_MAP(REVERSE),
1383         ATTR_MAP(STANDOUT),
1384         ATTR_MAP(UNDERLINE),
1385 };
1387 #define set_color(color, name)          map_enum(color, color_map, name)
1388 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1390 static int   config_lineno;
1391 static bool  config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attribute] */
1395 static int
1396 option_color_command(int argc, const char *argv[])
1398         struct line_info *info;
1400         if (argc != 3 && argc != 4) {
1401                 config_msg = "Wrong number of arguments given to color command";
1402                 return ERR;
1403         }
1405         info = get_line_info(argv[0]);
1406         if (!info) {
1407                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408                         info = get_line_info("delimiter");
1410                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411                         info = get_line_info("date");
1413                 } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
1414                         info = get_line_info("author");
1416                 } else {
1417                         config_msg = "Unknown color name";
1418                         return ERR;
1419                 }
1420         }
1422         if (!set_color(&info->fg, argv[1]) ||
1423             !set_color(&info->bg, argv[2])) {
1424                 config_msg = "Unknown color";
1425                 return ERR;
1426         }
1428         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1429                 config_msg = "Unknown attribute";
1430                 return ERR;
1431         }
1433         return OK;
1436 static int parse_bool(bool *opt, const char *arg)
1438         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1439                 ? TRUE : FALSE;
1440         return OK;
1443 static int
1444 parse_int(int *opt, const char *arg, int min, int max)
1446         int value = atoi(arg);
1448         if (min <= value && value <= max)
1449                 *opt = value;
1450         return OK;
1453 static int
1454 parse_string(char *opt, const char *arg, size_t optsize)
1456         int arglen = strlen(arg);
1458         switch (arg[0]) {
1459         case '\"':
1460         case '\'':
1461                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1462                         config_msg = "Unmatched quotation";
1463                         return ERR;
1464                 }
1465                 arg += 1; arglen -= 2;
1466         default:
1467                 string_ncopy_do(opt, optsize, arg, strlen(arg));
1468                 return OK;
1469         }
1472 /* Wants: name = value */
1473 static int
1474 option_set_command(int argc, const char *argv[])
1476         if (argc != 3) {
1477                 config_msg = "Wrong number of arguments given to set command";
1478                 return ERR;
1479         }
1481         if (strcmp(argv[1], "=")) {
1482                 config_msg = "No value assigned";
1483                 return ERR;
1484         }
1486         if (!strcmp(argv[0], "show-author"))
1487                 return parse_bool(&opt_author, argv[2]);
1489         if (!strcmp(argv[0], "show-date"))
1490                 return parse_bool(&opt_date, argv[2]);
1492         if (!strcmp(argv[0], "show-rev-graph"))
1493                 return parse_bool(&opt_rev_graph, argv[2]);
1495         if (!strcmp(argv[0], "show-refs"))
1496                 return parse_bool(&opt_show_refs, argv[2]);
1498         if (!strcmp(argv[0], "show-line-numbers"))
1499                 return parse_bool(&opt_line_number, argv[2]);
1501         if (!strcmp(argv[0], "line-graphics"))
1502                 return parse_bool(&opt_line_graphics, argv[2]);
1504         if (!strcmp(argv[0], "line-number-interval"))
1505                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1507         if (!strcmp(argv[0], "author-width"))
1508                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1510         if (!strcmp(argv[0], "tab-size"))
1511                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1513         if (!strcmp(argv[0], "commit-encoding"))
1514                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1516         config_msg = "Unknown variable name";
1517         return ERR;
1520 /* Wants: mode request key */
1521 static int
1522 option_bind_command(int argc, const char *argv[])
1524         enum request request;
1525         int keymap;
1526         int key;
1528         if (argc < 3) {
1529                 config_msg = "Wrong number of arguments given to bind command";
1530                 return ERR;
1531         }
1533         if (set_keymap(&keymap, argv[0]) == ERR) {
1534                 config_msg = "Unknown key map";
1535                 return ERR;
1536         }
1538         key = get_key_value(argv[1]);
1539         if (key == ERR) {
1540                 config_msg = "Unknown key";
1541                 return ERR;
1542         }
1544         request = get_request(argv[2]);
1545         if (request == REQ_NONE) {
1546                 struct {
1547                         const char *name;
1548                         enum request request;
1549                 } obsolete[] = {
1550                         { "cherry-pick",        REQ_NONE },
1551                         { "screen-resize",      REQ_NONE },
1552                         { "tree-parent",        REQ_PARENT },
1553                 };
1554                 size_t namelen = strlen(argv[2]);
1555                 int i;
1557                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1558                         if (namelen != strlen(obsolete[i].name) ||
1559                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1560                                 continue;
1561                         if (obsolete[i].request != REQ_NONE)
1562                                 add_keybinding(keymap, obsolete[i].request, key);
1563                         config_msg = "Obsolete request name";
1564                         return ERR;
1565                 }
1566         }
1567         if (request == REQ_NONE && *argv[2]++ == '!')
1568                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1569         if (request == REQ_NONE) {
1570                 config_msg = "Unknown request name";
1571                 return ERR;
1572         }
1574         add_keybinding(keymap, request, key);
1576         return OK;
1579 static int
1580 set_option(const char *opt, char *value)
1582         const char *argv[SIZEOF_ARG];
1583         int argc = 0;
1585         if (!argv_from_string(argv, &argc, value)) {
1586                 config_msg = "Too many option arguments";
1587                 return ERR;
1588         }
1590         if (!strcmp(opt, "color"))
1591                 return option_color_command(argc, argv);
1593         if (!strcmp(opt, "set"))
1594                 return option_set_command(argc, argv);
1596         if (!strcmp(opt, "bind"))
1597                 return option_bind_command(argc, argv);
1599         config_msg = "Unknown option command";
1600         return ERR;
1603 static int
1604 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1606         int status = OK;
1608         config_lineno++;
1609         config_msg = "Internal error";
1611         /* Check for comment markers, since read_properties() will
1612          * only ensure opt and value are split at first " \t". */
1613         optlen = strcspn(opt, "#");
1614         if (optlen == 0)
1615                 return OK;
1617         if (opt[optlen] != 0) {
1618                 config_msg = "No option value";
1619                 status = ERR;
1621         }  else {
1622                 /* Look for comment endings in the value. */
1623                 size_t len = strcspn(value, "#");
1625                 if (len < valuelen) {
1626                         valuelen = len;
1627                         value[valuelen] = 0;
1628                 }
1630                 status = set_option(opt, value);
1631         }
1633         if (status == ERR) {
1634                 warn("Error on line %d, near '%.*s': %s",
1635                      config_lineno, (int) optlen, opt, config_msg);
1636                 config_errors = TRUE;
1637         }
1639         /* Always keep going if errors are encountered. */
1640         return OK;
1643 static void
1644 load_option_file(const char *path)
1646         struct io io = {};
1648         /* It's OK that the file doesn't exist. */
1649         if (!io_open(&io, path))
1650                 return;
1652         config_lineno = 0;
1653         config_errors = FALSE;
1655         if (io_load(&io, " \t", read_option) == ERR ||
1656             config_errors == TRUE)
1657                 warn("Errors while loading %s.", path);
1660 static int
1661 load_options(void)
1663         const char *home = getenv("HOME");
1664         const char *tigrc_user = getenv("TIGRC_USER");
1665         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1666         char buf[SIZEOF_STR];
1668         add_builtin_run_requests();
1670         if (!tigrc_system) {
1671                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1672                         return ERR;
1673                 tigrc_system = buf;
1674         }
1675         load_option_file(tigrc_system);
1677         if (!tigrc_user) {
1678                 if (!home || !string_format(buf, "%s/.tigrc", home))
1679                         return ERR;
1680                 tigrc_user = buf;
1681         }
1682         load_option_file(tigrc_user);
1684         return OK;
1688 /*
1689  * The viewer
1690  */
1692 struct view;
1693 struct view_ops;
1695 /* The display array of active views and the index of the current view. */
1696 static struct view *display[2];
1697 static unsigned int current_view;
1699 #define foreach_displayed_view(view, i) \
1700         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1702 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1704 /* Current head and commit ID */
1705 static char ref_blob[SIZEOF_REF]        = "";
1706 static char ref_commit[SIZEOF_REF]      = "HEAD";
1707 static char ref_head[SIZEOF_REF]        = "HEAD";
1709 struct view {
1710         const char *name;       /* View name */
1711         const char *cmd_env;    /* Command line set via environment */
1712         const char *id;         /* Points to either of ref_{head,commit,blob} */
1714         struct view_ops *ops;   /* View operations */
1716         enum keymap keymap;     /* What keymap does this view have */
1717         bool git_dir;           /* Whether the view requires a git directory. */
1719         char ref[SIZEOF_REF];   /* Hovered commit reference */
1720         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1722         int height, width;      /* The width and height of the main window */
1723         WINDOW *win;            /* The main window */
1724         WINDOW *title;          /* The title window living below the main window */
1726         /* Navigation */
1727         unsigned long offset;   /* Offset of the window top */
1728         unsigned long yoffset;  /* Offset from the window side. */
1729         unsigned long lineno;   /* Current line number */
1730         unsigned long p_offset; /* Previous offset of the window top */
1731         unsigned long p_yoffset;/* Previous offset from the window side */
1732         unsigned long p_lineno; /* Previous current line number */
1733         bool p_restore;         /* Should the previous position be restored. */
1735         /* Searching */
1736         char grep[SIZEOF_STR];  /* Search string */
1737         regex_t *regex;         /* Pre-compiled regexp */
1739         /* If non-NULL, points to the view that opened this view. If this view
1740          * is closed tig will switch back to the parent view. */
1741         struct view *parent;
1743         /* Buffering */
1744         size_t lines;           /* Total number of lines */
1745         struct line *line;      /* Line index */
1746         size_t line_alloc;      /* Total number of allocated lines */
1747         unsigned int digits;    /* Number of digits in the lines member. */
1749         /* Drawing */
1750         struct line *curline;   /* Line currently being drawn. */
1751         enum line_type curtype; /* Attribute currently used for drawing. */
1752         unsigned long col;      /* Column when drawing. */
1753         bool has_scrolled;      /* View was scrolled. */
1754         bool can_hscroll;       /* View can be scrolled horizontally. */
1756         /* Loading */
1757         struct io io;
1758         struct io *pipe;
1759         time_t start_time;
1760         time_t update_secs;
1761 };
1763 struct view_ops {
1764         /* What type of content being displayed. Used in the title bar. */
1765         const char *type;
1766         /* Default command arguments. */
1767         const char **argv;
1768         /* Open and reads in all view content. */
1769         bool (*open)(struct view *view);
1770         /* Read one line; updates view->line. */
1771         bool (*read)(struct view *view, char *data);
1772         /* Draw one line; @lineno must be < view->height. */
1773         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1774         /* Depending on view handle a special requests. */
1775         enum request (*request)(struct view *view, enum request request, struct line *line);
1776         /* Search for regexp in a line. */
1777         bool (*grep)(struct view *view, struct line *line);
1778         /* Select line */
1779         void (*select)(struct view *view, struct line *line);
1780 };
1782 static struct view_ops blame_ops;
1783 static struct view_ops blob_ops;
1784 static struct view_ops diff_ops;
1785 static struct view_ops help_ops;
1786 static struct view_ops log_ops;
1787 static struct view_ops main_ops;
1788 static struct view_ops pager_ops;
1789 static struct view_ops stage_ops;
1790 static struct view_ops status_ops;
1791 static struct view_ops tree_ops;
1793 #define VIEW_STR(name, env, ref, ops, map, git) \
1794         { name, #env, ref, ops, map, git }
1796 #define VIEW_(id, name, ops, git, ref) \
1797         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1800 static struct view views[] = {
1801         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1802         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1803         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1804         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1805         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1806         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1807         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1808         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1809         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1810         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1811 };
1813 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1814 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1816 #define foreach_view(view, i) \
1817         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1819 #define view_is_displayed(view) \
1820         (view == display[0] || view == display[1])
1823 enum line_graphic {
1824         LINE_GRAPHIC_VLINE
1825 };
1827 static int line_graphics[] = {
1828         /* LINE_GRAPHIC_VLINE: */ '|'
1829 };
1831 static inline void
1832 set_view_attr(struct view *view, enum line_type type)
1834         if (!view->curline->selected && view->curtype != type) {
1835                 wattrset(view->win, get_line_attr(type));
1836                 wchgat(view->win, -1, 0, type, NULL);
1837                 view->curtype = type;
1838         }
1841 static int
1842 draw_chars(struct view *view, enum line_type type, const char *string,
1843            int max_len, bool use_tilde)
1845         int len = 0;
1846         int col = 0;
1847         int trimmed = FALSE;
1848         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1850         if (max_len <= 0)
1851                 return 0;
1853         if (opt_utf8) {
1854                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1855         } else {
1856                 col = len = strlen(string);
1857                 if (len > max_len) {
1858                         if (use_tilde) {
1859                                 max_len -= 1;
1860                         }
1861                         col = len = max_len;
1862                         trimmed = TRUE;
1863                 }
1864         }
1866         set_view_attr(view, type);
1867         if (len > 0)
1868                 waddnstr(view->win, string, len);
1869         if (trimmed && use_tilde) {
1870                 set_view_attr(view, LINE_DELIMITER);
1871                 waddch(view->win, '~');
1872                 col++;
1873         }
1875         if (view->col + col >= view->width + view->yoffset)
1876                 view->can_hscroll = TRUE;
1878         return col;
1881 static int
1882 draw_space(struct view *view, enum line_type type, int max, int spaces)
1884         static char space[] = "                    ";
1885         int col = 0;
1887         spaces = MIN(max, spaces);
1889         while (spaces > 0) {
1890                 int len = MIN(spaces, sizeof(space) - 1);
1892                 col += draw_chars(view, type, space, spaces, FALSE);
1893                 spaces -= len;
1894         }
1896         return col;
1899 static bool
1900 draw_lineno(struct view *view, unsigned int lineno)
1902         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1903         char number[10];
1904         int digits3 = view->digits < 3 ? 3 : view->digits;
1905         int max_number = MIN(digits3, STRING_SIZE(number));
1906         int max = view->width - view->col;
1907         int col;
1909         if (max < max_number)
1910                 max_number = max;
1912         lineno += view->offset + 1;
1913         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1914                 static char fmt[] = "%1ld";
1916                 if (view->digits <= 9)
1917                         fmt[1] = '0' + digits3;
1919                 if (!string_format(number, fmt, lineno))
1920                         number[0] = 0;
1921                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1922         } else {
1923                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1924         }
1926         if (col < max && skip <= col) {
1927                 set_view_attr(view, LINE_DEFAULT);
1928                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1929         }
1930         col++;
1932         view->col += col;
1933         if (col < max && skip <= col)
1934                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1935         view->col++;
1937         return view->width + view->yoffset <= view->col;
1940 static bool
1941 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1943         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1944         return view->width - view->col <= 0;
1947 static bool
1948 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1950         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1951         int max = view->width - view->col;
1952         int i;
1954         if (max < size)
1955                 size = max;
1957         set_view_attr(view, type);
1958         /* Using waddch() instead of waddnstr() ensures that
1959          * they'll be rendered correctly for the cursor line. */
1960         for (i = skip; i < size; i++)
1961                 waddch(view->win, graphic[i]);
1963         view->col += size;
1964         if (size < max && skip <= size)
1965                 waddch(view->win, ' ');
1966         view->col++;
1968         return view->width - view->col <= 0;
1971 static bool
1972 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1974         int max = MIN(view->width - view->col, len);
1975         int col;
1977         if (text)
1978                 col = draw_chars(view, type, text, max - 1, trim);
1979         else
1980                 col = draw_space(view, type, max - 1, max - 1);
1982         view->col += col;
1983         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1984         return view->width + view->yoffset <= view->col;
1987 static bool
1988 draw_date(struct view *view, struct tm *time)
1990         char buf[DATE_COLS];
1991         char *date;
1992         int timelen = 0;
1994         if (time)
1995                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1996         date = timelen ? buf : NULL;
1998         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2001 static bool
2002 draw_author(struct view *view, const char *author)
2004         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2006         if (!trim) {
2007                 static char initials[10];
2008                 size_t pos;
2010 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2012                 memset(initials, 0, sizeof(initials));
2013                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2014                         while (is_initial_sep(*author))
2015                                 author++;
2016                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2017                         while (*author && !is_initial_sep(author[1]))
2018                                 author++;
2019                 }
2021                 author = initials;
2022         }
2024         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2027 static bool
2028 draw_mode(struct view *view, mode_t mode)
2030         static const char dir_mode[]    = "drwxr-xr-x";
2031         static const char link_mode[]   = "lrwxrwxrwx";
2032         static const char exe_mode[]    = "-rwxr-xr-x";
2033         static const char file_mode[]   = "-rw-r--r--";
2034         const char *str;
2036         if (S_ISDIR(mode))
2037                 str = dir_mode;
2038         else if (S_ISLNK(mode))
2039                 str = link_mode;
2040         else if (mode & S_IXUSR)
2041                 str = exe_mode;
2042         else
2043                 str = file_mode;
2045         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2048 static bool
2049 draw_view_line(struct view *view, unsigned int lineno)
2051         struct line *line;
2052         bool selected = (view->offset + lineno == view->lineno);
2054         assert(view_is_displayed(view));
2056         if (view->offset + lineno >= view->lines)
2057                 return FALSE;
2059         line = &view->line[view->offset + lineno];
2061         wmove(view->win, lineno, 0);
2062         if (line->cleareol)
2063                 wclrtoeol(view->win);
2064         view->col = 0;
2065         view->curline = line;
2066         view->curtype = LINE_NONE;
2067         line->selected = FALSE;
2068         line->dirty = line->cleareol = 0;
2070         if (selected) {
2071                 set_view_attr(view, LINE_CURSOR);
2072                 line->selected = TRUE;
2073                 view->ops->select(view, line);
2074         }
2076         return view->ops->draw(view, line, lineno);
2079 static void
2080 redraw_view_dirty(struct view *view)
2082         bool dirty = FALSE;
2083         int lineno;
2085         for (lineno = 0; lineno < view->height; lineno++) {
2086                 if (view->offset + lineno >= view->lines)
2087                         break;
2088                 if (!view->line[view->offset + lineno].dirty)
2089                         continue;
2090                 dirty = TRUE;
2091                 if (!draw_view_line(view, lineno))
2092                         break;
2093         }
2095         if (!dirty)
2096                 return;
2097         wnoutrefresh(view->win);
2100 static void
2101 redraw_view_from(struct view *view, int lineno)
2103         assert(0 <= lineno && lineno < view->height);
2105         if (lineno == 0)
2106                 view->can_hscroll = FALSE;
2108         for (; lineno < view->height; lineno++) {
2109                 if (!draw_view_line(view, lineno))
2110                         break;
2111         }
2113         wnoutrefresh(view->win);
2116 static void
2117 redraw_view(struct view *view)
2119         werase(view->win);
2120         redraw_view_from(view, 0);
2124 static void
2125 update_view_title(struct view *view)
2127         char buf[SIZEOF_STR];
2128         char state[SIZEOF_STR];
2129         size_t bufpos = 0, statelen = 0;
2131         assert(view_is_displayed(view));
2133         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2134                 unsigned int view_lines = view->offset + view->height;
2135                 unsigned int lines = view->lines
2136                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2137                                    : 0;
2139                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2140                                    view->ops->type,
2141                                    view->lineno + 1,
2142                                    view->lines,
2143                                    lines);
2145         }
2147         if (view->pipe) {
2148                 time_t secs = time(NULL) - view->start_time;
2150                 /* Three git seconds are a long time ... */
2151                 if (secs > 2)
2152                         string_format_from(state, &statelen, " loading %lds", secs);
2153         }
2155         string_format_from(buf, &bufpos, "[%s]", view->name);
2156         if (*view->ref && bufpos < view->width) {
2157                 size_t refsize = strlen(view->ref);
2158                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2160                 if (minsize < view->width)
2161                         refsize = view->width - minsize + 7;
2162                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2163         }
2165         if (statelen && bufpos < view->width) {
2166                 string_format_from(buf, &bufpos, "%s", state);
2167         }
2169         if (view == display[current_view])
2170                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2171         else
2172                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2174         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2175         wclrtoeol(view->title);
2176         wnoutrefresh(view->title);
2179 static void
2180 resize_display(void)
2182         int offset, i;
2183         struct view *base = display[0];
2184         struct view *view = display[1] ? display[1] : display[0];
2186         /* Setup window dimensions */
2188         getmaxyx(stdscr, base->height, base->width);
2190         /* Make room for the status window. */
2191         base->height -= 1;
2193         if (view != base) {
2194                 /* Horizontal split. */
2195                 view->width   = base->width;
2196                 view->height  = SCALE_SPLIT_VIEW(base->height);
2197                 base->height -= view->height;
2199                 /* Make room for the title bar. */
2200                 view->height -= 1;
2201         }
2203         /* Make room for the title bar. */
2204         base->height -= 1;
2206         offset = 0;
2208         foreach_displayed_view (view, i) {
2209                 if (!view->win) {
2210                         view->win = newwin(view->height, 0, offset, 0);
2211                         if (!view->win)
2212                                 die("Failed to create %s view", view->name);
2214                         scrollok(view->win, FALSE);
2216                         view->title = newwin(1, 0, offset + view->height, 0);
2217                         if (!view->title)
2218                                 die("Failed to create title window");
2220                 } else {
2221                         wresize(view->win, view->height, view->width);
2222                         mvwin(view->win,   offset, 0);
2223                         mvwin(view->title, offset + view->height, 0);
2224                 }
2226                 offset += view->height + 1;
2227         }
2230 static void
2231 redraw_display(bool clear)
2233         struct view *view;
2234         int i;
2236         foreach_displayed_view (view, i) {
2237                 if (clear)
2238                         wclear(view->win);
2239                 redraw_view(view);
2240                 update_view_title(view);
2241         }
2244 static void
2245 toggle_view_option(bool *option, const char *help)
2247         *option = !*option;
2248         redraw_display(FALSE);
2249         report("%sabling %s", *option ? "En" : "Dis", help);
2252 /*
2253  * Navigation
2254  */
2256 /* Scrolling backend */
2257 static void
2258 do_scroll_view(struct view *view, int lines)
2260         bool redraw_current_line = FALSE;
2262         /* The rendering expects the new offset. */
2263         view->offset += lines;
2265         assert(0 <= view->offset && view->offset < view->lines);
2266         assert(lines);
2268         /* Move current line into the view. */
2269         if (view->lineno < view->offset) {
2270                 view->lineno = view->offset;
2271                 redraw_current_line = TRUE;
2272         } else if (view->lineno >= view->offset + view->height) {
2273                 view->lineno = view->offset + view->height - 1;
2274                 redraw_current_line = TRUE;
2275         }
2277         assert(view->offset <= view->lineno && view->lineno < view->lines);
2279         /* Redraw the whole screen if scrolling is pointless. */
2280         if (view->height < ABS(lines)) {
2281                 redraw_view(view);
2283         } else {
2284                 int line = lines > 0 ? view->height - lines : 0;
2285                 int end = line + ABS(lines);
2287                 scrollok(view->win, TRUE);
2288                 wscrl(view->win, lines);
2289                 scrollok(view->win, FALSE);
2291                 while (line < end && draw_view_line(view, line))
2292                         line++;
2294                 if (redraw_current_line)
2295                         draw_view_line(view, view->lineno - view->offset);
2296                 wnoutrefresh(view->win);
2297         }
2299         view->has_scrolled = TRUE;
2300         report("");
2303 /* Scroll frontend */
2304 static void
2305 scroll_view(struct view *view, enum request request)
2307         int lines = 1;
2309         assert(view_is_displayed(view));
2311         switch (request) {
2312         case REQ_SCROLL_LEFT:
2313                 if (view->yoffset == 0) {
2314                         report("Cannot scroll beyond the first column");
2315                         return;
2316                 }
2317                 if (view->yoffset <= SCROLL_INTERVAL)
2318                         view->yoffset = 0;
2319                 else
2320                         view->yoffset -= SCROLL_INTERVAL;
2321                 redraw_view_from(view, 0);
2322                 report("");
2323                 return;
2324         case REQ_SCROLL_RIGHT:
2325                 if (!view->can_hscroll) {
2326                         report("Cannot scroll beyond the last column");
2327                         return;
2328                 }
2329                 view->yoffset += SCROLL_INTERVAL;
2330                 redraw_view(view);
2331                 report("");
2332                 return;
2333         case REQ_SCROLL_PAGE_DOWN:
2334                 lines = view->height;
2335         case REQ_SCROLL_LINE_DOWN:
2336                 if (view->offset + lines > view->lines)
2337                         lines = view->lines - view->offset;
2339                 if (lines == 0 || view->offset + view->height >= view->lines) {
2340                         report("Cannot scroll beyond the last line");
2341                         return;
2342                 }
2343                 break;
2345         case REQ_SCROLL_PAGE_UP:
2346                 lines = view->height;
2347         case REQ_SCROLL_LINE_UP:
2348                 if (lines > view->offset)
2349                         lines = view->offset;
2351                 if (lines == 0) {
2352                         report("Cannot scroll beyond the first line");
2353                         return;
2354                 }
2356                 lines = -lines;
2357                 break;
2359         default:
2360                 die("request %d not handled in switch", request);
2361         }
2363         do_scroll_view(view, lines);
2366 /* Cursor moving */
2367 static void
2368 move_view(struct view *view, enum request request)
2370         int scroll_steps = 0;
2371         int steps;
2373         switch (request) {
2374         case REQ_MOVE_FIRST_LINE:
2375                 steps = -view->lineno;
2376                 break;
2378         case REQ_MOVE_LAST_LINE:
2379                 steps = view->lines - view->lineno - 1;
2380                 break;
2382         case REQ_MOVE_PAGE_UP:
2383                 steps = view->height > view->lineno
2384                       ? -view->lineno : -view->height;
2385                 break;
2387         case REQ_MOVE_PAGE_DOWN:
2388                 steps = view->lineno + view->height >= view->lines
2389                       ? view->lines - view->lineno - 1 : view->height;
2390                 break;
2392         case REQ_MOVE_UP:
2393                 steps = -1;
2394                 break;
2396         case REQ_MOVE_DOWN:
2397                 steps = 1;
2398                 break;
2400         default:
2401                 die("request %d not handled in switch", request);
2402         }
2404         if (steps <= 0 && view->lineno == 0) {
2405                 report("Cannot move beyond the first line");
2406                 return;
2408         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2409                 report("Cannot move beyond the last line");
2410                 return;
2411         }
2413         /* Move the current line */
2414         view->lineno += steps;
2415         assert(0 <= view->lineno && view->lineno < view->lines);
2417         /* Check whether the view needs to be scrolled */
2418         if (view->lineno < view->offset ||
2419             view->lineno >= view->offset + view->height) {
2420                 scroll_steps = steps;
2421                 if (steps < 0 && -steps > view->offset) {
2422                         scroll_steps = -view->offset;
2424                 } else if (steps > 0) {
2425                         if (view->lineno == view->lines - 1 &&
2426                             view->lines > view->height) {
2427                                 scroll_steps = view->lines - view->offset - 1;
2428                                 if (scroll_steps >= view->height)
2429                                         scroll_steps -= view->height - 1;
2430                         }
2431                 }
2432         }
2434         if (!view_is_displayed(view)) {
2435                 view->offset += scroll_steps;
2436                 assert(0 <= view->offset && view->offset < view->lines);
2437                 view->ops->select(view, &view->line[view->lineno]);
2438                 return;
2439         }
2441         /* Repaint the old "current" line if we be scrolling */
2442         if (ABS(steps) < view->height)
2443                 draw_view_line(view, view->lineno - steps - view->offset);
2445         if (scroll_steps) {
2446                 do_scroll_view(view, scroll_steps);
2447                 return;
2448         }
2450         /* Draw the current line */
2451         draw_view_line(view, view->lineno - view->offset);
2453         wnoutrefresh(view->win);
2454         report("");
2458 /*
2459  * Searching
2460  */
2462 static void search_view(struct view *view, enum request request);
2464 static void
2465 select_view_line(struct view *view, unsigned long lineno)
2467         if (lineno - view->offset >= view->height) {
2468                 view->offset = lineno;
2469                 view->lineno = lineno;
2470                 if (view_is_displayed(view))
2471                         redraw_view(view);
2473         } else {
2474                 unsigned long old_lineno = view->lineno - view->offset;
2476                 view->lineno = lineno;
2477                 if (view_is_displayed(view)) {
2478                         draw_view_line(view, old_lineno);
2479                         draw_view_line(view, view->lineno - view->offset);
2480                         wnoutrefresh(view->win);
2481                 } else {
2482                         view->ops->select(view, &view->line[view->lineno]);
2483                 }
2484         }
2487 static void
2488 find_next(struct view *view, enum request request)
2490         unsigned long lineno = view->lineno;
2491         int direction;
2493         if (!*view->grep) {
2494                 if (!*opt_search)
2495                         report("No previous search");
2496                 else
2497                         search_view(view, request);
2498                 return;
2499         }
2501         switch (request) {
2502         case REQ_SEARCH:
2503         case REQ_FIND_NEXT:
2504                 direction = 1;
2505                 break;
2507         case REQ_SEARCH_BACK:
2508         case REQ_FIND_PREV:
2509                 direction = -1;
2510                 break;
2512         default:
2513                 return;
2514         }
2516         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2517                 lineno += direction;
2519         /* Note, lineno is unsigned long so will wrap around in which case it
2520          * will become bigger than view->lines. */
2521         for (; lineno < view->lines; lineno += direction) {
2522                 if (view->ops->grep(view, &view->line[lineno])) {
2523                         select_view_line(view, lineno);
2524                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2525                         return;
2526                 }
2527         }
2529         report("No match found for '%s'", view->grep);
2532 static void
2533 search_view(struct view *view, enum request request)
2535         int regex_err;
2537         if (view->regex) {
2538                 regfree(view->regex);
2539                 *view->grep = 0;
2540         } else {
2541                 view->regex = calloc(1, sizeof(*view->regex));
2542                 if (!view->regex)
2543                         return;
2544         }
2546         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2547         if (regex_err != 0) {
2548                 char buf[SIZEOF_STR] = "unknown error";
2550                 regerror(regex_err, view->regex, buf, sizeof(buf));
2551                 report("Search failed: %s", buf);
2552                 return;
2553         }
2555         string_copy(view->grep, opt_search);
2557         find_next(view, request);
2560 /*
2561  * Incremental updating
2562  */
2564 static void
2565 reset_view(struct view *view)
2567         int i;
2569         for (i = 0; i < view->lines; i++)
2570                 free(view->line[i].data);
2571         free(view->line);
2573         view->p_offset = view->offset;
2574         view->p_yoffset = view->yoffset;
2575         view->p_lineno = view->lineno;
2577         view->line = NULL;
2578         view->offset = 0;
2579         view->yoffset = 0;
2580         view->lines  = 0;
2581         view->lineno = 0;
2582         view->line_alloc = 0;
2583         view->vid[0] = 0;
2584         view->update_secs = 0;
2587 static void
2588 free_argv(const char *argv[])
2590         int argc;
2592         for (argc = 0; argv[argc]; argc++)
2593                 free((void *) argv[argc]);
2596 static bool
2597 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2599         char buf[SIZEOF_STR];
2600         int argc;
2601         bool noreplace = flags == FORMAT_NONE;
2603         free_argv(dst_argv);
2605         for (argc = 0; src_argv[argc]; argc++) {
2606                 const char *arg = src_argv[argc];
2607                 size_t bufpos = 0;
2609                 while (arg) {
2610                         char *next = strstr(arg, "%(");
2611                         int len = next - arg;
2612                         const char *value;
2614                         if (!next || noreplace) {
2615                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2616                                         noreplace = TRUE;
2617                                 len = strlen(arg);
2618                                 value = "";
2620                         } else if (!prefixcmp(next, "%(directory)")) {
2621                                 value = opt_path;
2623                         } else if (!prefixcmp(next, "%(file)")) {
2624                                 value = opt_file;
2626                         } else if (!prefixcmp(next, "%(ref)")) {
2627                                 value = *opt_ref ? opt_ref : "HEAD";
2629                         } else if (!prefixcmp(next, "%(head)")) {
2630                                 value = ref_head;
2632                         } else if (!prefixcmp(next, "%(commit)")) {
2633                                 value = ref_commit;
2635                         } else if (!prefixcmp(next, "%(blob)")) {
2636                                 value = ref_blob;
2638                         } else {
2639                                 report("Unknown replacement: `%s`", next);
2640                                 return FALSE;
2641                         }
2643                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2644                                 return FALSE;
2646                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2647                 }
2649                 dst_argv[argc] = strdup(buf);
2650                 if (!dst_argv[argc])
2651                         break;
2652         }
2654         dst_argv[argc] = NULL;
2656         return src_argv[argc] == NULL;
2659 static bool
2660 restore_view_position(struct view *view)
2662         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2663                 return FALSE;
2665         /* Changing the view position cancels the restoring. */
2666         /* FIXME: Changing back to the first line is not detected. */
2667         if (view->offset != 0 || view->lineno != 0) {
2668                 view->p_restore = FALSE;
2669                 return FALSE;
2670         }
2672         if (view->p_lineno >= view->lines) {
2673                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2674                 if (view->p_offset >= view->p_lineno) {
2675                         unsigned long half = view->height / 2;
2677                         if (view->p_lineno > half)
2678                                 view->p_offset = view->p_lineno - half;
2679                         else
2680                                 view->p_offset = 0;
2681                 }
2682         }
2684         if (view_is_displayed(view) &&
2685             view->offset != view->p_offset &&
2686             view->lineno != view->p_lineno)
2687                 werase(view->win);
2689         view->offset = view->p_offset;
2690         view->yoffset = view->p_yoffset;
2691         view->lineno = view->p_lineno;
2692         view->p_restore = FALSE;
2694         return TRUE;
2697 static void
2698 end_update(struct view *view, bool force)
2700         if (!view->pipe)
2701                 return;
2702         while (!view->ops->read(view, NULL))
2703                 if (!force)
2704                         return;
2705         set_nonblocking_input(FALSE);
2706         if (force)
2707                 kill_io(view->pipe);
2708         done_io(view->pipe);
2709         view->pipe = NULL;
2712 static void
2713 setup_update(struct view *view, const char *vid)
2715         set_nonblocking_input(TRUE);
2716         reset_view(view);
2717         string_copy_rev(view->vid, vid);
2718         view->pipe = &view->io;
2719         view->start_time = time(NULL);
2722 static bool
2723 prepare_update(struct view *view, const char *argv[], const char *dir,
2724                enum format_flags flags)
2726         if (view->pipe)
2727                 end_update(view, TRUE);
2728         return init_io_rd(&view->io, argv, dir, flags);
2731 static bool
2732 prepare_update_file(struct view *view, const char *name)
2734         if (view->pipe)
2735                 end_update(view, TRUE);
2736         return io_open(&view->io, name);
2739 static bool
2740 begin_update(struct view *view, bool refresh)
2742         if (view->pipe)
2743                 end_update(view, TRUE);
2745         if (refresh) {
2746                 if (!start_io(&view->io))
2747                         return FALSE;
2749         } else {
2750                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2751                         opt_path[0] = 0;
2753                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2754                         return FALSE;
2756                 /* Put the current ref_* value to the view title ref
2757                  * member. This is needed by the blob view. Most other
2758                  * views sets it automatically after loading because the
2759                  * first line is a commit line. */
2760                 string_copy_rev(view->ref, view->id);
2761         }
2763         setup_update(view, view->id);
2765         return TRUE;
2768 #define ITEM_CHUNK_SIZE 256
2769 static void *
2770 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2772         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2773         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2775         if (mem == NULL || num_chunks != num_chunks_new) {
2776                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2777                 mem = realloc(mem, *size * item_size);
2778         }
2780         return mem;
2783 static struct line *
2784 realloc_lines(struct view *view, size_t line_size)
2786         size_t alloc = view->line_alloc;
2787         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2788                                          sizeof(*view->line));
2790         if (!tmp)
2791                 return NULL;
2793         view->line = tmp;
2794         view->line_alloc = alloc;
2795         return view->line;
2798 static bool
2799 update_view(struct view *view)
2801         char out_buffer[BUFSIZ * 2];
2802         char *line;
2803         /* Clear the view and redraw everything since the tree sorting
2804          * might have rearranged things. */
2805         bool redraw = view->lines == 0;
2806         bool can_read = TRUE;
2808         if (!view->pipe)
2809                 return TRUE;
2811         if (!io_can_read(view->pipe)) {
2812                 if (view->lines == 0) {
2813                         time_t secs = time(NULL) - view->start_time;
2815                         if (secs > 1 && secs > view->update_secs) {
2816                                 if (view->update_secs == 0)
2817                                         redraw_view(view);
2818                                 update_view_title(view);
2819                                 view->update_secs = secs;
2820                         }
2821                 }
2822                 return TRUE;
2823         }
2825         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2826                 if (opt_iconv != ICONV_NONE) {
2827                         ICONV_CONST char *inbuf = line;
2828                         size_t inlen = strlen(line) + 1;
2830                         char *outbuf = out_buffer;
2831                         size_t outlen = sizeof(out_buffer);
2833                         size_t ret;
2835                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2836                         if (ret != (size_t) -1)
2837                                 line = out_buffer;
2838                 }
2840                 if (!view->ops->read(view, line)) {
2841                         report("Allocation failure");
2842                         end_update(view, TRUE);
2843                         return FALSE;
2844                 }
2845         }
2847         {
2848                 unsigned long lines = view->lines;
2849                 int digits;
2851                 for (digits = 0; lines; digits++)
2852                         lines /= 10;
2854                 /* Keep the displayed view in sync with line number scaling. */
2855                 if (digits != view->digits) {
2856                         view->digits = digits;
2857                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2858                                 redraw = TRUE;
2859                 }
2860         }
2862         if (io_error(view->pipe)) {
2863                 report("Failed to read: %s", io_strerror(view->pipe));
2864                 end_update(view, TRUE);
2866         } else if (io_eof(view->pipe)) {
2867                 report("");
2868                 end_update(view, FALSE);
2869         }
2871         if (restore_view_position(view))
2872                 redraw = TRUE;
2874         if (!view_is_displayed(view))
2875                 return TRUE;
2877         if (redraw)
2878                 redraw_view_from(view, 0);
2879         else
2880                 redraw_view_dirty(view);
2882         /* Update the title _after_ the redraw so that if the redraw picks up a
2883          * commit reference in view->ref it'll be available here. */
2884         update_view_title(view);
2885         return TRUE;
2888 static struct line *
2889 add_line_data(struct view *view, void *data, enum line_type type)
2891         struct line *line;
2893         if (!realloc_lines(view, view->lines + 1))
2894                 return NULL;
2896         line = &view->line[view->lines++];
2897         memset(line, 0, sizeof(*line));
2898         line->type = type;
2899         line->data = data;
2900         line->dirty = 1;
2902         return line;
2905 static struct line *
2906 add_line_text(struct view *view, const char *text, enum line_type type)
2908         char *data = text ? strdup(text) : NULL;
2910         return data ? add_line_data(view, data, type) : NULL;
2913 static struct line *
2914 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2916         char buf[SIZEOF_STR];
2917         va_list args;
2919         va_start(args, fmt);
2920         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2921                 buf[0] = 0;
2922         va_end(args);
2924         return buf[0] ? add_line_text(view, buf, type) : NULL;
2927 /*
2928  * View opening
2929  */
2931 enum open_flags {
2932         OPEN_DEFAULT = 0,       /* Use default view switching. */
2933         OPEN_SPLIT = 1,         /* Split current view. */
2934         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2935         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2936         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2937         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2938         OPEN_PREPARED = 32,     /* Open already prepared command. */
2939 };
2941 static void
2942 open_view(struct view *prev, enum request request, enum open_flags flags)
2944         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2945         bool split = !!(flags & OPEN_SPLIT);
2946         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2947         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2948         struct view *view = VIEW(request);
2949         int nviews = displayed_views();
2950         struct view *base_view = display[0];
2952         if (view == prev && nviews == 1 && !reload) {
2953                 report("Already in %s view", view->name);
2954                 return;
2955         }
2957         if (view->git_dir && !opt_git_dir[0]) {
2958                 report("The %s view is disabled in pager view", view->name);
2959                 return;
2960         }
2962         if (split) {
2963                 display[1] = view;
2964                 if (!backgrounded)
2965                         current_view = 1;
2966         } else if (!nomaximize) {
2967                 /* Maximize the current view. */
2968                 memset(display, 0, sizeof(display));
2969                 current_view = 0;
2970                 display[current_view] = view;
2971         }
2973         /* Resize the view when switching between split- and full-screen,
2974          * or when switching between two different full-screen views. */
2975         if (nviews != displayed_views() ||
2976             (nviews == 1 && base_view != display[0]))
2977                 resize_display();
2979         if (view->ops->open) {
2980                 if (view->pipe)
2981                         end_update(view, TRUE);
2982                 if (!view->ops->open(view)) {
2983                         report("Failed to load %s view", view->name);
2984                         return;
2985                 }
2986                 restore_view_position(view);
2988         } else if ((reload || strcmp(view->vid, view->id)) &&
2989                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2990                 report("Failed to load %s view", view->name);
2991                 return;
2992         }
2994         if (split && prev->lineno - prev->offset >= prev->height) {
2995                 /* Take the title line into account. */
2996                 int lines = prev->lineno - prev->offset - prev->height + 1;
2998                 /* Scroll the view that was split if the current line is
2999                  * outside the new limited view. */
3000                 do_scroll_view(prev, lines);
3001         }
3003         if (prev && view != prev) {
3004                 if (split && !backgrounded) {
3005                         /* "Blur" the previous view. */
3006                         update_view_title(prev);
3007                 }
3009                 view->parent = prev;
3010         }
3012         if (view->pipe && view->lines == 0) {
3013                 /* Clear the old view and let the incremental updating refill
3014                  * the screen. */
3015                 werase(view->win);
3016                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3017                 report("");
3018         } else if (view_is_displayed(view)) {
3019                 redraw_view(view);
3020                 report("");
3021         }
3023         /* If the view is backgrounded the above calls to report()
3024          * won't redraw the view title. */
3025         if (backgrounded)
3026                 update_view_title(view);
3029 static void
3030 open_external_viewer(const char *argv[], const char *dir)
3032         def_prog_mode();           /* save current tty modes */
3033         endwin();                  /* restore original tty modes */
3034         run_io_fg(argv, dir);
3035         fprintf(stderr, "Press Enter to continue");
3036         getc(opt_tty);
3037         reset_prog_mode();
3038         redraw_display(TRUE);
3041 static void
3042 open_mergetool(const char *file)
3044         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3046         open_external_viewer(mergetool_argv, opt_cdup);
3049 static void
3050 open_editor(bool from_root, const char *file)
3052         const char *editor_argv[] = { "vi", file, NULL };
3053         const char *editor;
3055         editor = getenv("GIT_EDITOR");
3056         if (!editor && *opt_editor)
3057                 editor = opt_editor;
3058         if (!editor)
3059                 editor = getenv("VISUAL");
3060         if (!editor)
3061                 editor = getenv("EDITOR");
3062         if (!editor)
3063                 editor = "vi";
3065         editor_argv[0] = editor;
3066         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3069 static void
3070 open_run_request(enum request request)
3072         struct run_request *req = get_run_request(request);
3073         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3075         if (!req) {
3076                 report("Unknown run request");
3077                 return;
3078         }
3080         if (format_argv(argv, req->argv, FORMAT_ALL))
3081                 open_external_viewer(argv, NULL);
3082         free_argv(argv);
3085 /*
3086  * User request switch noodle
3087  */
3089 static int
3090 view_driver(struct view *view, enum request request)
3092         int i;
3094         if (request == REQ_NONE) {
3095                 doupdate();
3096                 return TRUE;
3097         }
3099         if (request > REQ_NONE) {
3100                 open_run_request(request);
3101                 /* FIXME: When all views can refresh always do this. */
3102                 if (view == VIEW(REQ_VIEW_STATUS) ||
3103                     view == VIEW(REQ_VIEW_MAIN) ||
3104                     view == VIEW(REQ_VIEW_LOG) ||
3105                     view == VIEW(REQ_VIEW_STAGE))
3106                         request = REQ_REFRESH;
3107                 else
3108                         return TRUE;
3109         }
3111         if (view && view->lines) {
3112                 request = view->ops->request(view, request, &view->line[view->lineno]);
3113                 if (request == REQ_NONE)
3114                         return TRUE;
3115         }
3117         switch (request) {
3118         case REQ_MOVE_UP:
3119         case REQ_MOVE_DOWN:
3120         case REQ_MOVE_PAGE_UP:
3121         case REQ_MOVE_PAGE_DOWN:
3122         case REQ_MOVE_FIRST_LINE:
3123         case REQ_MOVE_LAST_LINE:
3124                 move_view(view, request);
3125                 break;
3127         case REQ_SCROLL_LEFT:
3128         case REQ_SCROLL_RIGHT:
3129         case REQ_SCROLL_LINE_DOWN:
3130         case REQ_SCROLL_LINE_UP:
3131         case REQ_SCROLL_PAGE_DOWN:
3132         case REQ_SCROLL_PAGE_UP:
3133                 scroll_view(view, request);
3134                 break;
3136         case REQ_VIEW_BLAME:
3137                 if (!opt_file[0]) {
3138                         report("No file chosen, press %s to open tree view",
3139                                get_key(REQ_VIEW_TREE));
3140                         break;
3141                 }
3142                 open_view(view, request, OPEN_DEFAULT);
3143                 break;
3145         case REQ_VIEW_BLOB:
3146                 if (!ref_blob[0]) {
3147                         report("No file chosen, press %s to open tree view",
3148                                get_key(REQ_VIEW_TREE));
3149                         break;
3150                 }
3151                 open_view(view, request, OPEN_DEFAULT);
3152                 break;
3154         case REQ_VIEW_PAGER:
3155                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3156                         report("No pager content, press %s to run command from prompt",
3157                                get_key(REQ_PROMPT));
3158                         break;
3159                 }
3160                 open_view(view, request, OPEN_DEFAULT);
3161                 break;
3163         case REQ_VIEW_STAGE:
3164                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3165                         report("No stage content, press %s to open the status view and choose file",
3166                                get_key(REQ_VIEW_STATUS));
3167                         break;
3168                 }
3169                 open_view(view, request, OPEN_DEFAULT);
3170                 break;
3172         case REQ_VIEW_STATUS:
3173                 if (opt_is_inside_work_tree == FALSE) {
3174                         report("The status view requires a working tree");
3175                         break;
3176                 }
3177                 open_view(view, request, OPEN_DEFAULT);
3178                 break;
3180         case REQ_VIEW_MAIN:
3181         case REQ_VIEW_DIFF:
3182         case REQ_VIEW_LOG:
3183         case REQ_VIEW_TREE:
3184         case REQ_VIEW_HELP:
3185                 open_view(view, request, OPEN_DEFAULT);
3186                 break;
3188         case REQ_NEXT:
3189         case REQ_PREVIOUS:
3190                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3192                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3193                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3194                    (view == VIEW(REQ_VIEW_DIFF) &&
3195                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3196                    (view == VIEW(REQ_VIEW_STAGE) &&
3197                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3198                    (view == VIEW(REQ_VIEW_BLOB) &&
3199                      view->parent == VIEW(REQ_VIEW_TREE))) {
3200                         int line;
3202                         view = view->parent;
3203                         line = view->lineno;
3204                         move_view(view, request);
3205                         if (view_is_displayed(view))
3206                                 update_view_title(view);
3207                         if (line != view->lineno)
3208                                 view->ops->request(view, REQ_ENTER,
3209                                                    &view->line[view->lineno]);
3211                 } else {
3212                         move_view(view, request);
3213                 }
3214                 break;
3216         case REQ_VIEW_NEXT:
3217         {
3218                 int nviews = displayed_views();
3219                 int next_view = (current_view + 1) % nviews;
3221                 if (next_view == current_view) {
3222                         report("Only one view is displayed");
3223                         break;
3224                 }
3226                 current_view = next_view;
3227                 /* Blur out the title of the previous view. */
3228                 update_view_title(view);
3229                 report("");
3230                 break;
3231         }
3232         case REQ_REFRESH:
3233                 report("Refreshing is not yet supported for the %s view", view->name);
3234                 break;
3236         case REQ_MAXIMIZE:
3237                 if (displayed_views() == 2)
3238                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3239                 break;
3241         case REQ_TOGGLE_LINENO:
3242                 toggle_view_option(&opt_line_number, "line numbers");
3243                 break;
3245         case REQ_TOGGLE_DATE:
3246                 toggle_view_option(&opt_date, "date display");
3247                 break;
3249         case REQ_TOGGLE_AUTHOR:
3250                 toggle_view_option(&opt_author, "author display");
3251                 break;
3253         case REQ_TOGGLE_REV_GRAPH:
3254                 toggle_view_option(&opt_rev_graph, "revision graph display");
3255                 break;
3257         case REQ_TOGGLE_REFS:
3258                 toggle_view_option(&opt_show_refs, "reference display");
3259                 break;
3261         case REQ_SEARCH:
3262         case REQ_SEARCH_BACK:
3263                 search_view(view, request);
3264                 break;
3266         case REQ_FIND_NEXT:
3267         case REQ_FIND_PREV:
3268                 find_next(view, request);
3269                 break;
3271         case REQ_STOP_LOADING:
3272                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3273                         view = &views[i];
3274                         if (view->pipe)
3275                                 report("Stopped loading the %s view", view->name),
3276                         end_update(view, TRUE);
3277                 }
3278                 break;
3280         case REQ_SHOW_VERSION:
3281                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3282                 return TRUE;
3284         case REQ_SCREEN_REDRAW:
3285                 redraw_display(TRUE);
3286                 break;
3288         case REQ_EDIT:
3289                 report("Nothing to edit");
3290                 break;
3292         case REQ_ENTER:
3293                 report("Nothing to enter");
3294                 break;
3296         case REQ_VIEW_CLOSE:
3297                 /* XXX: Mark closed views by letting view->parent point to the
3298                  * view itself. Parents to closed view should never be
3299                  * followed. */
3300                 if (view->parent &&
3301                     view->parent->parent != view->parent) {
3302                         memset(display, 0, sizeof(display));
3303                         current_view = 0;
3304                         display[current_view] = view->parent;
3305                         view->parent = view;
3306                         resize_display();
3307                         redraw_display(FALSE);
3308                         report("");
3309                         break;
3310                 }
3311                 /* Fall-through */
3312         case REQ_QUIT:
3313                 return FALSE;
3315         default:
3316                 report("Unknown key, press 'h' for help");
3317                 return TRUE;
3318         }
3320         return TRUE;
3324 /*
3325  * View backend utilities
3326  */
3328 /* Parse author lines where the name may be empty:
3329  *      author  <email@address.tld> 1138474660 +0100
3330  */
3331 static void
3332 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3334         char *nameend = strchr(ident, '<');
3335         char *emailend = strchr(ident, '>');
3337         if (nameend && emailend)
3338                 *nameend = *emailend = 0;
3339         ident = chomp_string(ident);
3340         if (!*ident) {
3341                 if (nameend)
3342                         ident = chomp_string(nameend + 1);
3343                 if (!*ident)
3344                         ident = "Unknown";
3345         }
3347         string_ncopy_do(author, authorsize, ident, strlen(ident));
3349         /* Parse epoch and timezone */
3350         if (emailend && emailend[1] == ' ') {
3351                 char *secs = emailend + 2;
3352                 char *zone = strchr(secs, ' ');
3353                 time_t time = (time_t) atol(secs);
3355                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3356                         long tz;
3358                         zone++;
3359                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3360                         tz += ('0' - zone[2]) * 60 * 60;
3361                         tz += ('0' - zone[3]) * 60;
3362                         tz += ('0' - zone[4]) * 60;
3364                         if (zone[0] == '-')
3365                                 tz = -tz;
3367                         time -= tz;
3368                 }
3370                 gmtime_r(&time, tm);
3371         }
3374 static enum input_status
3375 select_commit_parent_handler(void *data, char *buf, int c)
3377         size_t parents = *(size_t *) data;
3378         int parent = 0;
3380         if (!isdigit(c))
3381                 return INPUT_SKIP;
3383         if (*buf)
3384                 parent = atoi(buf) * 10;
3385         parent += c - '0';
3387         if (parent > parents)
3388                 return INPUT_SKIP;
3389         return INPUT_OK;
3392 static bool
3393 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3395         char buf[SIZEOF_STR * 4];
3396         const char *revlist_argv[] = {
3397                 "git", "rev-list", "-1", "--parents", id, NULL
3398         };
3399         int parents;
3401         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3402             !*chomp_string(buf) ||
3403             (parents = (strlen(buf) / 40) - 1) < 0) {
3404                 report("Failed to get parent information");
3405                 return FALSE;
3407         } else if (parents == 0) {
3408                 report("The selected commit has no parents");
3409                 return FALSE;
3410         }
3412         if (parents > 1) {
3413                 char prompt[SIZEOF_STR];
3414                 char *result;
3416                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3417                         return FALSE;
3418                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3419                 if (!result)
3420                         return FALSE;
3421                 parents = atoi(result);
3422         }
3424         string_copy_rev(rev, &buf[41 * parents]);
3425         return TRUE;
3428 /*
3429  * Pager backend
3430  */
3432 static bool
3433 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3435         char text[SIZEOF_STR];
3437         if (opt_line_number && draw_lineno(view, lineno))
3438                 return TRUE;
3440         string_expand(text, sizeof(text), line->data, opt_tab_size);
3441         draw_text(view, line->type, text, TRUE);
3442         return TRUE;
3445 static bool
3446 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3448         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3449         char refbuf[SIZEOF_STR];
3450         char *ref = NULL;
3452         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3453                 ref = chomp_string(refbuf);
3455         if (!ref || !*ref)
3456                 return TRUE;
3458         /* This is the only fatal call, since it can "corrupt" the buffer. */
3459         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3460                 return FALSE;
3462         return TRUE;
3465 static void
3466 add_pager_refs(struct view *view, struct line *line)
3468         char buf[SIZEOF_STR];
3469         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3470         struct ref **refs;
3471         size_t bufpos = 0, refpos = 0;
3472         const char *sep = "Refs: ";
3473         bool is_tag = FALSE;
3475         assert(line->type == LINE_COMMIT);
3477         refs = get_refs(commit_id);
3478         if (!refs) {
3479                 if (view == VIEW(REQ_VIEW_DIFF))
3480                         goto try_add_describe_ref;
3481                 return;
3482         }
3484         do {
3485                 struct ref *ref = refs[refpos];
3486                 const char *fmt = ref->tag    ? "%s[%s]" :
3487                                   ref->remote ? "%s<%s>" : "%s%s";
3489                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3490                         return;
3491                 sep = ", ";
3492                 if (ref->tag)
3493                         is_tag = TRUE;
3494         } while (refs[refpos++]->next);
3496         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3497 try_add_describe_ref:
3498                 /* Add <tag>-g<commit_id> "fake" reference. */
3499                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3500                         return;
3501         }
3503         if (bufpos == 0)
3504                 return;
3506         add_line_text(view, buf, LINE_PP_REFS);
3509 static bool
3510 pager_read(struct view *view, char *data)
3512         struct line *line;
3514         if (!data)
3515                 return TRUE;
3517         line = add_line_text(view, data, get_line_type(data));
3518         if (!line)
3519                 return FALSE;
3521         if (line->type == LINE_COMMIT &&
3522             (view == VIEW(REQ_VIEW_DIFF) ||
3523              view == VIEW(REQ_VIEW_LOG)))
3524                 add_pager_refs(view, line);
3526         return TRUE;
3529 static enum request
3530 pager_request(struct view *view, enum request request, struct line *line)
3532         int split = 0;
3534         if (request != REQ_ENTER)
3535                 return request;
3537         if (line->type == LINE_COMMIT &&
3538            (view == VIEW(REQ_VIEW_LOG) ||
3539             view == VIEW(REQ_VIEW_PAGER))) {
3540                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3541                 split = 1;
3542         }
3544         /* Always scroll the view even if it was split. That way
3545          * you can use Enter to scroll through the log view and
3546          * split open each commit diff. */
3547         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3549         /* FIXME: A minor workaround. Scrolling the view will call report("")
3550          * but if we are scrolling a non-current view this won't properly
3551          * update the view title. */
3552         if (split)
3553                 update_view_title(view);
3555         return REQ_NONE;
3558 static bool
3559 pager_grep(struct view *view, struct line *line)
3561         regmatch_t pmatch;
3562         char *text = line->data;
3564         if (!*text)
3565                 return FALSE;
3567         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3568                 return FALSE;
3570         return TRUE;
3573 static void
3574 pager_select(struct view *view, struct line *line)
3576         if (line->type == LINE_COMMIT) {
3577                 char *text = (char *)line->data + STRING_SIZE("commit ");
3579                 if (view != VIEW(REQ_VIEW_PAGER))
3580                         string_copy_rev(view->ref, text);
3581                 string_copy_rev(ref_commit, text);
3582         }
3585 static struct view_ops pager_ops = {
3586         "line",
3587         NULL,
3588         NULL,
3589         pager_read,
3590         pager_draw,
3591         pager_request,
3592         pager_grep,
3593         pager_select,
3594 };
3596 static const char *log_argv[SIZEOF_ARG] = {
3597         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3598 };
3600 static enum request
3601 log_request(struct view *view, enum request request, struct line *line)
3603         switch (request) {
3604         case REQ_REFRESH:
3605                 load_refs();
3606                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3607                 return REQ_NONE;
3608         default:
3609                 return pager_request(view, request, line);
3610         }
3613 static struct view_ops log_ops = {
3614         "line",
3615         log_argv,
3616         NULL,
3617         pager_read,
3618         pager_draw,
3619         log_request,
3620         pager_grep,
3621         pager_select,
3622 };
3624 static const char *diff_argv[SIZEOF_ARG] = {
3625         "git", "show", "--pretty=fuller", "--no-color", "--root",
3626                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3627 };
3629 static struct view_ops diff_ops = {
3630         "line",
3631         diff_argv,
3632         NULL,
3633         pager_read,
3634         pager_draw,
3635         pager_request,
3636         pager_grep,
3637         pager_select,
3638 };
3640 /*
3641  * Help backend
3642  */
3644 static bool
3645 help_open(struct view *view)
3647         char buf[SIZEOF_STR];
3648         size_t bufpos;
3649         int i;
3651         if (view->lines > 0)
3652                 return TRUE;
3654         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3656         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3657                 const char *key;
3659                 if (req_info[i].request == REQ_NONE)
3660                         continue;
3662                 if (!req_info[i].request) {
3663                         add_line_text(view, "", LINE_DEFAULT);
3664                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3665                         continue;
3666                 }
3668                 key = get_key(req_info[i].request);
3669                 if (!*key)
3670                         key = "(no key defined)";
3672                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3673                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3674                         if (buf[bufpos] == '_')
3675                                 buf[bufpos] = '-';
3676                 }
3678                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3679                                 key, buf, req_info[i].help);
3680         }
3682         if (run_requests) {
3683                 add_line_text(view, "", LINE_DEFAULT);
3684                 add_line_text(view, "External commands:", LINE_DEFAULT);
3685         }
3687         for (i = 0; i < run_requests; i++) {
3688                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3689                 const char *key;
3690                 int argc;
3692                 if (!req)
3693                         continue;
3695                 key = get_key_name(req->key);
3696                 if (!*key)
3697                         key = "(no key defined)";
3699                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3700                         if (!string_format_from(buf, &bufpos, "%s%s",
3701                                                 argc ? " " : "", req->argv[argc]))
3702                                 return REQ_NONE;
3704                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3705                                 keymap_table[req->keymap].name, key, buf);
3706         }
3708         return TRUE;
3711 static struct view_ops help_ops = {
3712         "line",
3713         NULL,
3714         help_open,
3715         NULL,
3716         pager_draw,
3717         pager_request,
3718         pager_grep,
3719         pager_select,
3720 };
3723 /*
3724  * Tree backend
3725  */
3727 struct tree_stack_entry {
3728         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3729         unsigned long lineno;           /* Line number to restore */
3730         char *name;                     /* Position of name in opt_path */
3731 };
3733 /* The top of the path stack. */
3734 static struct tree_stack_entry *tree_stack = NULL;
3735 unsigned long tree_lineno = 0;
3737 static void
3738 pop_tree_stack_entry(void)
3740         struct tree_stack_entry *entry = tree_stack;
3742         tree_lineno = entry->lineno;
3743         entry->name[0] = 0;
3744         tree_stack = entry->prev;
3745         free(entry);
3748 static void
3749 push_tree_stack_entry(const char *name, unsigned long lineno)
3751         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3752         size_t pathlen = strlen(opt_path);
3754         if (!entry)
3755                 return;
3757         entry->prev = tree_stack;
3758         entry->name = opt_path + pathlen;
3759         tree_stack = entry;
3761         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3762                 pop_tree_stack_entry();
3763                 return;
3764         }
3766         /* Move the current line to the first tree entry. */
3767         tree_lineno = 1;
3768         entry->lineno = lineno;
3771 /* Parse output from git-ls-tree(1):
3772  *
3773  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3774  */
3776 #define SIZEOF_TREE_ATTR \
3777         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3779 #define SIZEOF_TREE_MODE \
3780         STRING_SIZE("100644 ")
3782 #define TREE_ID_OFFSET \
3783         STRING_SIZE("100644 blob ")
3785 struct tree_entry {
3786         char id[SIZEOF_REV];
3787         mode_t mode;
3788         struct tm time;                 /* Date from the author ident. */
3789         char author[75];                /* Author of the commit. */
3790         char name[1];
3791 };
3793 static const char *
3794 tree_path(struct line *line)
3796         return ((struct tree_entry *) line->data)->name;
3800 static int
3801 tree_compare_entry(struct line *line1, struct line *line2)
3803         if (line1->type != line2->type)
3804                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3805         return strcmp(tree_path(line1), tree_path(line2));
3808 static struct line *
3809 tree_entry(struct view *view, enum line_type type, const char *path,
3810            const char *mode, const char *id)
3812         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3813         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3815         if (!entry || !line) {
3816                 free(entry);
3817                 return NULL;
3818         }
3820         strncpy(entry->name, path, strlen(path));
3821         if (mode)
3822                 entry->mode = strtoul(mode, NULL, 8);
3823         if (id)
3824                 string_copy_rev(entry->id, id);
3826         return line;
3829 static bool
3830 tree_read_date(struct view *view, char *text, bool *read_date)
3832         static char author_name[SIZEOF_STR];
3833         static struct tm author_time;
3835         if (!text && *read_date) {
3836                 *read_date = FALSE;
3837                 return TRUE;
3839         } else if (!text) {
3840                 char *path = *opt_path ? opt_path : ".";
3841                 /* Find next entry to process */
3842                 const char *log_file[] = {
3843                         "git", "log", "--no-color", "--pretty=raw",
3844                                 "--cc", "--raw", view->id, "--", path, NULL
3845                 };
3846                 struct io io = {};
3848                 if (!view->lines) {
3849                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3850                         report("Tree is empty");
3851                         return TRUE;
3852                 }
3854                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3855                         report("Failed to load tree data");
3856                         return TRUE;
3857                 }
3859                 done_io(view->pipe);
3860                 view->io = io;
3861                 *read_date = TRUE;
3862                 return FALSE;
3864         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3865                 parse_author_line(text + STRING_SIZE("author "),
3866                                   author_name, sizeof(author_name), &author_time);
3868         } else if (*text == ':') {
3869                 char *pos;
3870                 size_t annotated = 1;
3871                 size_t i;
3873                 pos = strchr(text, '\t');
3874                 if (!pos)
3875                         return TRUE;
3876                 text = pos + 1;
3877                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3878                         text += strlen(opt_prefix);
3879                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3880                         text += strlen(opt_path);
3881                 pos = strchr(text, '/');
3882                 if (pos)
3883                         *pos = 0;
3885                 for (i = 1; i < view->lines; i++) {
3886                         struct line *line = &view->line[i];
3887                         struct tree_entry *entry = line->data;
3889                         annotated += !!*entry->author;
3890                         if (*entry->author || strcmp(entry->name, text))
3891                                 continue;
3893                         string_copy(entry->author, author_name);
3894                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3895                         line->dirty = 1;
3896                         break;
3897                 }
3899                 if (annotated == view->lines)
3900                         kill_io(view->pipe);
3901         }
3902         return TRUE;
3905 static bool
3906 tree_read(struct view *view, char *text)
3908         static bool read_date = FALSE;
3909         struct tree_entry *data;
3910         struct line *entry, *line;
3911         enum line_type type;
3912         size_t textlen = text ? strlen(text) : 0;
3913         char *path = text + SIZEOF_TREE_ATTR;
3915         if (read_date || !text)
3916                 return tree_read_date(view, text, &read_date);
3918         if (textlen <= SIZEOF_TREE_ATTR)
3919                 return FALSE;
3920         if (view->lines == 0 &&
3921             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3922                 return FALSE;
3924         /* Strip the path part ... */
3925         if (*opt_path) {
3926                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3927                 size_t striplen = strlen(opt_path);
3929                 if (pathlen > striplen)
3930                         memmove(path, path + striplen,
3931                                 pathlen - striplen + 1);
3933                 /* Insert "link" to parent directory. */
3934                 if (view->lines == 1 &&
3935                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3936                         return FALSE;
3937         }
3939         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3940         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3941         if (!entry)
3942                 return FALSE;
3943         data = entry->data;
3945         /* Skip "Directory ..." and ".." line. */
3946         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3947                 if (tree_compare_entry(line, entry) <= 0)
3948                         continue;
3950                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3952                 line->data = data;
3953                 line->type = type;
3954                 for (; line <= entry; line++)
3955                         line->dirty = line->cleareol = 1;
3956                 return TRUE;
3957         }
3959         if (tree_lineno > view->lineno) {
3960                 view->lineno = tree_lineno;
3961                 tree_lineno = 0;
3962         }
3964         return TRUE;
3967 static bool
3968 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3970         struct tree_entry *entry = line->data;
3972         if (line->type == LINE_TREE_HEAD) {
3973                 if (draw_text(view, line->type, "Directory path /", TRUE))
3974                         return TRUE;
3975         } else {
3976                 if (draw_mode(view, entry->mode))
3977                         return TRUE;
3979                 if (opt_author && draw_author(view, entry->author))
3980                         return TRUE;
3982                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3983                         return TRUE;
3984         }
3985         if (draw_text(view, line->type, entry->name, TRUE))
3986                 return TRUE;
3987         return TRUE;
3990 static void
3991 open_blob_editor()
3993         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3994         int fd = mkstemp(file);
3996         if (fd == -1)
3997                 report("Failed to create temporary file");
3998         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3999                 report("Failed to save blob data to file");
4000         else
4001                 open_editor(FALSE, file);
4002         if (fd != -1)
4003                 unlink(file);
4006 static enum request
4007 tree_request(struct view *view, enum request request, struct line *line)
4009         enum open_flags flags;
4011         switch (request) {
4012         case REQ_VIEW_BLAME:
4013                 if (line->type != LINE_TREE_FILE) {
4014                         report("Blame only supported for files");
4015                         return REQ_NONE;
4016                 }
4018                 string_copy(opt_ref, view->vid);
4019                 return request;
4021         case REQ_EDIT:
4022                 if (line->type != LINE_TREE_FILE) {
4023                         report("Edit only supported for files");
4024                 } else if (!is_head_commit(view->vid)) {
4025                         open_blob_editor();
4026                 } else {
4027                         open_editor(TRUE, opt_file);
4028                 }
4029                 return REQ_NONE;
4031         case REQ_PARENT:
4032                 if (!*opt_path) {
4033                         /* quit view if at top of tree */
4034                         return REQ_VIEW_CLOSE;
4035                 }
4036                 /* fake 'cd  ..' */
4037                 line = &view->line[1];
4038                 break;
4040         case REQ_ENTER:
4041                 break;
4043         default:
4044                 return request;
4045         }
4047         /* Cleanup the stack if the tree view is at a different tree. */
4048         while (!*opt_path && tree_stack)
4049                 pop_tree_stack_entry();
4051         switch (line->type) {
4052         case LINE_TREE_DIR:
4053                 /* Depending on whether it is a subdirectory or parent link
4054                  * mangle the path buffer. */
4055                 if (line == &view->line[1] && *opt_path) {
4056                         pop_tree_stack_entry();
4058                 } else {
4059                         const char *basename = tree_path(line);
4061                         push_tree_stack_entry(basename, view->lineno);
4062                 }
4064                 /* Trees and subtrees share the same ID, so they are not not
4065                  * unique like blobs. */
4066                 flags = OPEN_RELOAD;
4067                 request = REQ_VIEW_TREE;
4068                 break;
4070         case LINE_TREE_FILE:
4071                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4072                 request = REQ_VIEW_BLOB;
4073                 break;
4075         default:
4076                 return REQ_NONE;
4077         }
4079         open_view(view, request, flags);
4080         if (request == REQ_VIEW_TREE)
4081                 view->lineno = tree_lineno;
4083         return REQ_NONE;
4086 static void
4087 tree_select(struct view *view, struct line *line)
4089         struct tree_entry *entry = line->data;
4091         if (line->type == LINE_TREE_FILE) {
4092                 string_copy_rev(ref_blob, entry->id);
4093                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4095         } else if (line->type != LINE_TREE_DIR) {
4096                 return;
4097         }
4099         string_copy_rev(view->ref, entry->id);
4102 static const char *tree_argv[SIZEOF_ARG] = {
4103         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4104 };
4106 static struct view_ops tree_ops = {
4107         "file",
4108         tree_argv,
4109         NULL,
4110         tree_read,
4111         tree_draw,
4112         tree_request,
4113         pager_grep,
4114         tree_select,
4115 };
4117 static bool
4118 blob_read(struct view *view, char *line)
4120         if (!line)
4121                 return TRUE;
4122         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4125 static enum request
4126 blob_request(struct view *view, enum request request, struct line *line)
4128         switch (request) {
4129         case REQ_EDIT:
4130                 open_blob_editor();
4131                 return REQ_NONE;
4132         default:
4133                 return pager_request(view, request, line);
4134         }
4137 static const char *blob_argv[SIZEOF_ARG] = {
4138         "git", "cat-file", "blob", "%(blob)", NULL
4139 };
4141 static struct view_ops blob_ops = {
4142         "line",
4143         blob_argv,
4144         NULL,
4145         blob_read,
4146         pager_draw,
4147         blob_request,
4148         pager_grep,
4149         pager_select,
4150 };
4152 /*
4153  * Blame backend
4154  *
4155  * Loading the blame view is a two phase job:
4156  *
4157  *  1. File content is read either using opt_file from the
4158  *     filesystem or using git-cat-file.
4159  *  2. Then blame information is incrementally added by
4160  *     reading output from git-blame.
4161  */
4163 static const char *blame_head_argv[] = {
4164         "git", "blame", "--incremental", "--", "%(file)", NULL
4165 };
4167 static const char *blame_ref_argv[] = {
4168         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4169 };
4171 static const char *blame_cat_file_argv[] = {
4172         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4173 };
4175 struct blame_commit {
4176         char id[SIZEOF_REV];            /* SHA1 ID. */
4177         char title[128];                /* First line of the commit message. */
4178         char author[75];                /* Author of the commit. */
4179         struct tm time;                 /* Date from the author ident. */
4180         char filename[128];             /* Name of file. */
4181         bool has_previous;              /* Was a "previous" line detected. */
4182 };
4184 struct blame {
4185         struct blame_commit *commit;
4186         char text[1];
4187 };
4189 static bool
4190 blame_open(struct view *view)
4192         if (*opt_ref || !io_open(&view->io, opt_file)) {
4193                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4194                         return FALSE;
4195         }
4197         setup_update(view, opt_file);
4198         string_format(view->ref, "%s ...", opt_file);
4200         return TRUE;
4203 static struct blame_commit *
4204 get_blame_commit(struct view *view, const char *id)
4206         size_t i;
4208         for (i = 0; i < view->lines; i++) {
4209                 struct blame *blame = view->line[i].data;
4211                 if (!blame->commit)
4212                         continue;
4214                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4215                         return blame->commit;
4216         }
4218         {
4219                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4221                 if (commit)
4222                         string_ncopy(commit->id, id, SIZEOF_REV);
4223                 return commit;
4224         }
4227 static bool
4228 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4230         const char *pos = *posref;
4232         *posref = NULL;
4233         pos = strchr(pos + 1, ' ');
4234         if (!pos || !isdigit(pos[1]))
4235                 return FALSE;
4236         *number = atoi(pos + 1);
4237         if (*number < min || *number > max)
4238                 return FALSE;
4240         *posref = pos;
4241         return TRUE;
4244 static struct blame_commit *
4245 parse_blame_commit(struct view *view, const char *text, int *blamed)
4247         struct blame_commit *commit;
4248         struct blame *blame;
4249         const char *pos = text + SIZEOF_REV - 1;
4250         size_t lineno;
4251         size_t group;
4253         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4254                 return NULL;
4256         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4257             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4258                 return NULL;
4260         commit = get_blame_commit(view, text);
4261         if (!commit)
4262                 return NULL;
4264         *blamed += group;
4265         while (group--) {
4266                 struct line *line = &view->line[lineno + group - 1];
4268                 blame = line->data;
4269                 blame->commit = commit;
4270                 line->dirty = 1;
4271         }
4273         return commit;
4276 static bool
4277 blame_read_file(struct view *view, const char *line, bool *read_file)
4279         if (!line) {
4280                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4281                 struct io io = {};
4283                 if (view->lines == 0 && !view->parent)
4284                         die("No blame exist for %s", view->vid);
4286                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4287                         report("Failed to load blame data");
4288                         return TRUE;
4289                 }
4291                 done_io(view->pipe);
4292                 view->io = io;
4293                 *read_file = FALSE;
4294                 return FALSE;
4296         } else {
4297                 size_t linelen = string_expand_length(line, opt_tab_size);
4298                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4300                 if (!blame)
4301                         return FALSE;
4303                 blame->commit = NULL;
4304                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4305                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4306         }
4309 static bool
4310 match_blame_header(const char *name, char **line)
4312         size_t namelen = strlen(name);
4313         bool matched = !strncmp(name, *line, namelen);
4315         if (matched)
4316                 *line += namelen;
4318         return matched;
4321 static bool
4322 blame_read(struct view *view, char *line)
4324         static struct blame_commit *commit = NULL;
4325         static int blamed = 0;
4326         static time_t author_time;
4327         static bool read_file = TRUE;
4329         if (read_file)
4330                 return blame_read_file(view, line, &read_file);
4332         if (!line) {
4333                 /* Reset all! */
4334                 commit = NULL;
4335                 blamed = 0;
4336                 read_file = TRUE;
4337                 string_format(view->ref, "%s", view->vid);
4338                 if (view_is_displayed(view)) {
4339                         update_view_title(view);
4340                         redraw_view_from(view, 0);
4341                 }
4342                 return TRUE;
4343         }
4345         if (!commit) {
4346                 commit = parse_blame_commit(view, line, &blamed);
4347                 string_format(view->ref, "%s %2d%%", view->vid,
4348                               view->lines ? blamed * 100 / view->lines : 0);
4350         } else if (match_blame_header("author ", &line)) {
4351                 string_ncopy(commit->author, line, strlen(line));
4353         } else if (match_blame_header("author-time ", &line)) {
4354                 author_time = (time_t) atol(line);
4356         } else if (match_blame_header("author-tz ", &line)) {
4357                 long tz;
4359                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4360                 tz += ('0' - line[2]) * 60 * 60;
4361                 tz += ('0' - line[3]) * 60;
4362                 tz += ('0' - line[4]) * 60;
4364                 if (line[0] == '-')
4365                         tz = -tz;
4367                 author_time -= tz;
4368                 gmtime_r(&author_time, &commit->time);
4370         } else if (match_blame_header("summary ", &line)) {
4371                 string_ncopy(commit->title, line, strlen(line));
4373         } else if (match_blame_header("previous ", &line)) {
4374                 commit->has_previous = TRUE;
4376         } else if (match_blame_header("filename ", &line)) {
4377                 string_ncopy(commit->filename, line, strlen(line));
4378                 commit = NULL;
4379         }
4381         return TRUE;
4384 static bool
4385 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4387         struct blame *blame = line->data;
4388         struct tm *time = NULL;
4389         const char *id = NULL, *author = NULL;
4391         if (blame->commit && *blame->commit->filename) {
4392                 id = blame->commit->id;
4393                 author = blame->commit->author;
4394                 time = &blame->commit->time;
4395         }
4397         if (opt_date && draw_date(view, time))
4398                 return TRUE;
4400         if (opt_author && draw_author(view, author))
4401                 return TRUE;
4403         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4404                 return TRUE;
4406         if (draw_lineno(view, lineno))
4407                 return TRUE;
4409         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4410         return TRUE;
4413 static bool
4414 check_blame_commit(struct blame *blame)
4416         if (!blame->commit)
4417                 report("Commit data not loaded yet");
4418         else if (!strcmp(blame->commit->id, NULL_ID))
4419                 report("No commit exist for the selected line");
4420         else
4421                 return TRUE;
4422         return FALSE;
4425 static enum request
4426 blame_request(struct view *view, enum request request, struct line *line)
4428         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4429         struct blame *blame = line->data;
4431         switch (request) {
4432         case REQ_VIEW_BLAME:
4433                 if (check_blame_commit(blame)) {
4434                         string_copy(opt_ref, blame->commit->id);
4435                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4436                 }
4437                 break;
4439         case REQ_PARENT:
4440                 if (check_blame_commit(blame) &&
4441                     select_commit_parent(blame->commit->id, opt_ref))
4442                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4443                 break;
4445         case REQ_ENTER:
4446                 if (!blame->commit) {
4447                         report("No commit loaded yet");
4448                         break;
4449                 }
4451                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4452                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4453                         break;
4455                 if (!strcmp(blame->commit->id, NULL_ID)) {
4456                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4457                         const char *diff_index_argv[] = {
4458                                 "git", "diff-index", "--root", "--patch-with-stat",
4459                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4460                         };
4462                         if (!blame->commit->has_previous) {
4463                                 diff_index_argv[1] = "diff";
4464                                 diff_index_argv[2] = "--no-color";
4465                                 diff_index_argv[6] = "--";
4466                                 diff_index_argv[7] = "/dev/null";
4467                         }
4469                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4470                                 report("Failed to allocate diff command");
4471                                 break;
4472                         }
4473                         flags |= OPEN_PREPARED;
4474                 }
4476                 open_view(view, REQ_VIEW_DIFF, flags);
4477                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4478                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4479                 break;
4481         default:
4482                 return request;
4483         }
4485         return REQ_NONE;
4488 static bool
4489 blame_grep(struct view *view, struct line *line)
4491         struct blame *blame = line->data;
4492         struct blame_commit *commit = blame->commit;
4493         regmatch_t pmatch;
4495 #define MATCH(text, on)                                                 \
4496         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4498         if (commit) {
4499                 char buf[DATE_COLS + 1];
4501                 if (MATCH(commit->title, 1) ||
4502                     MATCH(commit->author, opt_author) ||
4503                     MATCH(commit->id, opt_date))
4504                         return TRUE;
4506                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4507                     MATCH(buf, 1))
4508                         return TRUE;
4509         }
4511         return MATCH(blame->text, 1);
4513 #undef MATCH
4516 static void
4517 blame_select(struct view *view, struct line *line)
4519         struct blame *blame = line->data;
4520         struct blame_commit *commit = blame->commit;
4522         if (!commit)
4523                 return;
4525         if (!strcmp(commit->id, NULL_ID))
4526                 string_ncopy(ref_commit, "HEAD", 4);
4527         else
4528                 string_copy_rev(ref_commit, commit->id);
4531 static struct view_ops blame_ops = {
4532         "line",
4533         NULL,
4534         blame_open,
4535         blame_read,
4536         blame_draw,
4537         blame_request,
4538         blame_grep,
4539         blame_select,
4540 };
4542 /*
4543  * Status backend
4544  */
4546 struct status {
4547         char status;
4548         struct {
4549                 mode_t mode;
4550                 char rev[SIZEOF_REV];
4551                 char name[SIZEOF_STR];
4552         } old;
4553         struct {
4554                 mode_t mode;
4555                 char rev[SIZEOF_REV];
4556                 char name[SIZEOF_STR];
4557         } new;
4558 };
4560 static char status_onbranch[SIZEOF_STR];
4561 static struct status stage_status;
4562 static enum line_type stage_line_type;
4563 static size_t stage_chunks;
4564 static int *stage_chunk;
4566 /* This should work even for the "On branch" line. */
4567 static inline bool
4568 status_has_none(struct view *view, struct line *line)
4570         return line < view->line + view->lines && !line[1].data;
4573 /* Get fields from the diff line:
4574  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4575  */
4576 static inline bool
4577 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4579         const char *old_mode = buf +  1;
4580         const char *new_mode = buf +  8;
4581         const char *old_rev  = buf + 15;
4582         const char *new_rev  = buf + 56;
4583         const char *status   = buf + 97;
4585         if (bufsize < 98 ||
4586             old_mode[-1] != ':' ||
4587             new_mode[-1] != ' ' ||
4588             old_rev[-1]  != ' ' ||
4589             new_rev[-1]  != ' ' ||
4590             status[-1]   != ' ')
4591                 return FALSE;
4593         file->status = *status;
4595         string_copy_rev(file->old.rev, old_rev);
4596         string_copy_rev(file->new.rev, new_rev);
4598         file->old.mode = strtoul(old_mode, NULL, 8);
4599         file->new.mode = strtoul(new_mode, NULL, 8);
4601         file->old.name[0] = file->new.name[0] = 0;
4603         return TRUE;
4606 static bool
4607 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4609         struct status *unmerged = NULL;
4610         char *buf;
4611         struct io io = {};
4613         if (!run_io(&io, argv, NULL, IO_RD))
4614                 return FALSE;
4616         add_line_data(view, NULL, type);
4618         while ((buf = io_get(&io, 0, TRUE))) {
4619                 struct status *file = unmerged;
4621                 if (!file) {
4622                         file = calloc(1, sizeof(*file));
4623                         if (!file || !add_line_data(view, file, type))
4624                                 goto error_out;
4625                 }
4627                 /* Parse diff info part. */
4628                 if (status) {
4629                         file->status = status;
4630                         if (status == 'A')
4631                                 string_copy(file->old.rev, NULL_ID);
4633                 } else if (!file->status || file == unmerged) {
4634                         if (!status_get_diff(file, buf, strlen(buf)))
4635                                 goto error_out;
4637                         buf = io_get(&io, 0, TRUE);
4638                         if (!buf)
4639                                 break;
4641                         /* Collapse all modified entries that follow an
4642                          * associated unmerged entry. */
4643                         if (unmerged == file) {
4644                                 unmerged->status = 'U';
4645                                 unmerged = NULL;
4646                         } else if (file->status == 'U') {
4647                                 unmerged = file;
4648                         }
4649                 }
4651                 /* Grab the old name for rename/copy. */
4652                 if (!*file->old.name &&
4653                     (file->status == 'R' || file->status == 'C')) {
4654                         string_ncopy(file->old.name, buf, strlen(buf));
4656                         buf = io_get(&io, 0, TRUE);
4657                         if (!buf)
4658                                 break;
4659                 }
4661                 /* git-ls-files just delivers a NUL separated list of
4662                  * file names similar to the second half of the
4663                  * git-diff-* output. */
4664                 string_ncopy(file->new.name, buf, strlen(buf));
4665                 if (!*file->old.name)
4666                         string_copy(file->old.name, file->new.name);
4667                 file = NULL;
4668         }
4670         if (io_error(&io)) {
4671 error_out:
4672                 done_io(&io);
4673                 return FALSE;
4674         }
4676         if (!view->line[view->lines - 1].data)
4677                 add_line_data(view, NULL, LINE_STAT_NONE);
4679         done_io(&io);
4680         return TRUE;
4683 /* Don't show unmerged entries in the staged section. */
4684 static const char *status_diff_index_argv[] = {
4685         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4686                              "--cached", "-M", "HEAD", NULL
4687 };
4689 static const char *status_diff_files_argv[] = {
4690         "git", "diff-files", "-z", NULL
4691 };
4693 static const char *status_list_other_argv[] = {
4694         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4695 };
4697 static const char *status_list_no_head_argv[] = {
4698         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4699 };
4701 static const char *update_index_argv[] = {
4702         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4703 };
4705 /* Restore the previous line number to stay in the context or select a
4706  * line with something that can be updated. */
4707 static void
4708 status_restore(struct view *view)
4710         if (view->p_lineno >= view->lines)
4711                 view->p_lineno = view->lines - 1;
4712         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4713                 view->p_lineno++;
4714         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4715                 view->p_lineno--;
4717         /* If the above fails, always skip the "On branch" line. */
4718         if (view->p_lineno < view->lines)
4719                 view->lineno = view->p_lineno;
4720         else
4721                 view->lineno = 1;
4723         if (view->lineno < view->offset)
4724                 view->offset = view->lineno;
4725         else if (view->offset + view->height <= view->lineno)
4726                 view->offset = view->lineno - view->height + 1;
4728         view->p_restore = FALSE;
4731 /* First parse staged info using git-diff-index(1), then parse unstaged
4732  * info using git-diff-files(1), and finally untracked files using
4733  * git-ls-files(1). */
4734 static bool
4735 status_open(struct view *view)
4737         reset_view(view);
4739         add_line_data(view, NULL, LINE_STAT_HEAD);
4740         if (is_initial_commit())
4741                 string_copy(status_onbranch, "Initial commit");
4742         else if (!*opt_head)
4743                 string_copy(status_onbranch, "Not currently on any branch");
4744         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4745                 return FALSE;
4747         run_io_bg(update_index_argv);
4749         if (is_initial_commit()) {
4750                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4751                         return FALSE;
4752         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4753                 return FALSE;
4754         }
4756         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4757             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4758                 return FALSE;
4760         /* Restore the exact position or use the specialized restore
4761          * mode? */
4762         if (!view->p_restore)
4763                 status_restore(view);
4764         return TRUE;
4767 static bool
4768 status_draw(struct view *view, struct line *line, unsigned int lineno)
4770         struct status *status = line->data;
4771         enum line_type type;
4772         const char *text;
4774         if (!status) {
4775                 switch (line->type) {
4776                 case LINE_STAT_STAGED:
4777                         type = LINE_STAT_SECTION;
4778                         text = "Changes to be committed:";
4779                         break;
4781                 case LINE_STAT_UNSTAGED:
4782                         type = LINE_STAT_SECTION;
4783                         text = "Changed but not updated:";
4784                         break;
4786                 case LINE_STAT_UNTRACKED:
4787                         type = LINE_STAT_SECTION;
4788                         text = "Untracked files:";
4789                         break;
4791                 case LINE_STAT_NONE:
4792                         type = LINE_DEFAULT;
4793                         text = "  (no files)";
4794                         break;
4796                 case LINE_STAT_HEAD:
4797                         type = LINE_STAT_HEAD;
4798                         text = status_onbranch;
4799                         break;
4801                 default:
4802                         return FALSE;
4803                 }
4804         } else {
4805                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4807                 buf[0] = status->status;
4808                 if (draw_text(view, line->type, buf, TRUE))
4809                         return TRUE;
4810                 type = LINE_DEFAULT;
4811                 text = status->new.name;
4812         }
4814         draw_text(view, type, text, TRUE);
4815         return TRUE;
4818 static enum request
4819 status_enter(struct view *view, struct line *line)
4821         struct status *status = line->data;
4822         const char *oldpath = status ? status->old.name : NULL;
4823         /* Diffs for unmerged entries are empty when passing the new
4824          * path, so leave it empty. */
4825         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4826         const char *info;
4827         enum open_flags split;
4828         struct view *stage = VIEW(REQ_VIEW_STAGE);
4830         if (line->type == LINE_STAT_NONE ||
4831             (!status && line[1].type == LINE_STAT_NONE)) {
4832                 report("No file to diff");
4833                 return REQ_NONE;
4834         }
4836         switch (line->type) {
4837         case LINE_STAT_STAGED:
4838                 if (is_initial_commit()) {
4839                         const char *no_head_diff_argv[] = {
4840                                 "git", "diff", "--no-color", "--patch-with-stat",
4841                                         "--", "/dev/null", newpath, NULL
4842                         };
4844                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4845                                 return REQ_QUIT;
4846                 } else {
4847                         const char *index_show_argv[] = {
4848                                 "git", "diff-index", "--root", "--patch-with-stat",
4849                                         "-C", "-M", "--cached", "HEAD", "--",
4850                                         oldpath, newpath, NULL
4851                         };
4853                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4854                                 return REQ_QUIT;
4855                 }
4857                 if (status)
4858                         info = "Staged changes to %s";
4859                 else
4860                         info = "Staged changes";
4861                 break;
4863         case LINE_STAT_UNSTAGED:
4864         {
4865                 const char *files_show_argv[] = {
4866                         "git", "diff-files", "--root", "--patch-with-stat",
4867                                 "-C", "-M", "--", oldpath, newpath, NULL
4868                 };
4870                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4871                         return REQ_QUIT;
4872                 if (status)
4873                         info = "Unstaged changes to %s";
4874                 else
4875                         info = "Unstaged changes";
4876                 break;
4877         }
4878         case LINE_STAT_UNTRACKED:
4879                 if (!newpath) {
4880                         report("No file to show");
4881                         return REQ_NONE;
4882                 }
4884                 if (!suffixcmp(status->new.name, -1, "/")) {
4885                         report("Cannot display a directory");
4886                         return REQ_NONE;
4887                 }
4889                 if (!prepare_update_file(stage, newpath))
4890                         return REQ_QUIT;
4891                 info = "Untracked file %s";
4892                 break;
4894         case LINE_STAT_HEAD:
4895                 return REQ_NONE;
4897         default:
4898                 die("line type %d not handled in switch", line->type);
4899         }
4901         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4902         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4903         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4904                 if (status) {
4905                         stage_status = *status;
4906                 } else {
4907                         memset(&stage_status, 0, sizeof(stage_status));
4908                 }
4910                 stage_line_type = line->type;
4911                 stage_chunks = 0;
4912                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4913         }
4915         return REQ_NONE;
4918 static bool
4919 status_exists(struct status *status, enum line_type type)
4921         struct view *view = VIEW(REQ_VIEW_STATUS);
4922         unsigned long lineno;
4924         for (lineno = 0; lineno < view->lines; lineno++) {
4925                 struct line *line = &view->line[lineno];
4926                 struct status *pos = line->data;
4928                 if (line->type != type)
4929                         continue;
4930                 if (!pos && (!status || !status->status) && line[1].data) {
4931                         select_view_line(view, lineno);
4932                         return TRUE;
4933                 }
4934                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4935                         select_view_line(view, lineno);
4936                         return TRUE;
4937                 }
4938         }
4940         return FALSE;
4944 static bool
4945 status_update_prepare(struct io *io, enum line_type type)
4947         const char *staged_argv[] = {
4948                 "git", "update-index", "-z", "--index-info", NULL
4949         };
4950         const char *others_argv[] = {
4951                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4952         };
4954         switch (type) {
4955         case LINE_STAT_STAGED:
4956                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4958         case LINE_STAT_UNSTAGED:
4959                 return run_io(io, others_argv, opt_cdup, IO_WR);
4961         case LINE_STAT_UNTRACKED:
4962                 return run_io(io, others_argv, NULL, IO_WR);
4964         default:
4965                 die("line type %d not handled in switch", type);
4966                 return FALSE;
4967         }
4970 static bool
4971 status_update_write(struct io *io, struct status *status, enum line_type type)
4973         char buf[SIZEOF_STR];
4974         size_t bufsize = 0;
4976         switch (type) {
4977         case LINE_STAT_STAGED:
4978                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4979                                         status->old.mode,
4980                                         status->old.rev,
4981                                         status->old.name, 0))
4982                         return FALSE;
4983                 break;
4985         case LINE_STAT_UNSTAGED:
4986         case LINE_STAT_UNTRACKED:
4987                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4988                         return FALSE;
4989                 break;
4991         default:
4992                 die("line type %d not handled in switch", type);
4993         }
4995         return io_write(io, buf, bufsize);
4998 static bool
4999 status_update_file(struct status *status, enum line_type type)
5001         struct io io = {};
5002         bool result;
5004         if (!status_update_prepare(&io, type))
5005                 return FALSE;
5007         result = status_update_write(&io, status, type);
5008         done_io(&io);
5009         return result;
5012 static bool
5013 status_update_files(struct view *view, struct line *line)
5015         struct io io = {};
5016         bool result = TRUE;
5017         struct line *pos = view->line + view->lines;
5018         int files = 0;
5019         int file, done;
5021         if (!status_update_prepare(&io, line->type))
5022                 return FALSE;
5024         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5025                 files++;
5027         for (file = 0, done = 0; result && file < files; line++, file++) {
5028                 int almost_done = file * 100 / files;
5030                 if (almost_done > done) {
5031                         done = almost_done;
5032                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5033                                       file, files, done);
5034                         update_view_title(view);
5035                 }
5036                 result = status_update_write(&io, line->data, line->type);
5037         }
5039         done_io(&io);
5040         return result;
5043 static bool
5044 status_update(struct view *view)
5046         struct line *line = &view->line[view->lineno];
5048         assert(view->lines);
5050         if (!line->data) {
5051                 /* This should work even for the "On branch" line. */
5052                 if (line < view->line + view->lines && !line[1].data) {
5053                         report("Nothing to update");
5054                         return FALSE;
5055                 }
5057                 if (!status_update_files(view, line + 1)) {
5058                         report("Failed to update file status");
5059                         return FALSE;
5060                 }
5062         } else if (!status_update_file(line->data, line->type)) {
5063                 report("Failed to update file status");
5064                 return FALSE;
5065         }
5067         return TRUE;
5070 static bool
5071 status_revert(struct status *status, enum line_type type, bool has_none)
5073         if (!status || type != LINE_STAT_UNSTAGED) {
5074                 if (type == LINE_STAT_STAGED) {
5075                         report("Cannot revert changes to staged files");
5076                 } else if (type == LINE_STAT_UNTRACKED) {
5077                         report("Cannot revert changes to untracked files");
5078                 } else if (has_none) {
5079                         report("Nothing to revert");
5080                 } else {
5081                         report("Cannot revert changes to multiple files");
5082                 }
5083                 return FALSE;
5085         } else {
5086                 char mode[10] = "100644";
5087                 const char *reset_argv[] = {
5088                         "git", "update-index", "--cacheinfo", mode,
5089                                 status->old.rev, status->old.name, NULL
5090                 };
5091                 const char *checkout_argv[] = {
5092                         "git", "checkout", "--", status->old.name, NULL
5093                 };
5095                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5096                         return FALSE;
5097                 string_format(mode, "%o", status->old.mode);
5098                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5099                         run_io_fg(checkout_argv, opt_cdup);
5100         }
5103 static enum request
5104 status_request(struct view *view, enum request request, struct line *line)
5106         struct status *status = line->data;
5108         switch (request) {
5109         case REQ_STATUS_UPDATE:
5110                 if (!status_update(view))
5111                         return REQ_NONE;
5112                 break;
5114         case REQ_STATUS_REVERT:
5115                 if (!status_revert(status, line->type, status_has_none(view, line)))
5116                         return REQ_NONE;
5117                 break;
5119         case REQ_STATUS_MERGE:
5120                 if (!status || status->status != 'U') {
5121                         report("Merging only possible for files with unmerged status ('U').");
5122                         return REQ_NONE;
5123                 }
5124                 open_mergetool(status->new.name);
5125                 break;
5127         case REQ_EDIT:
5128                 if (!status)
5129                         return request;
5130                 if (status->status == 'D') {
5131                         report("File has been deleted.");
5132                         return REQ_NONE;
5133                 }
5135                 open_editor(status->status != '?', status->new.name);
5136                 break;
5138         case REQ_VIEW_BLAME:
5139                 if (status) {
5140                         string_copy(opt_file, status->new.name);
5141                         opt_ref[0] = 0;
5142                 }
5143                 return request;
5145         case REQ_ENTER:
5146                 /* After returning the status view has been split to
5147                  * show the stage view. No further reloading is
5148                  * necessary. */
5149                 status_enter(view, line);
5150                 return REQ_NONE;
5152         case REQ_REFRESH:
5153                 /* Simply reload the view. */
5154                 break;
5156         default:
5157                 return request;
5158         }
5160         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5162         return REQ_NONE;
5165 static void
5166 status_select(struct view *view, struct line *line)
5168         struct status *status = line->data;
5169         char file[SIZEOF_STR] = "all files";
5170         const char *text;
5171         const char *key;
5173         if (status && !string_format(file, "'%s'", status->new.name))
5174                 return;
5176         if (!status && line[1].type == LINE_STAT_NONE)
5177                 line++;
5179         switch (line->type) {
5180         case LINE_STAT_STAGED:
5181                 text = "Press %s to unstage %s for commit";
5182                 break;
5184         case LINE_STAT_UNSTAGED:
5185                 text = "Press %s to stage %s for commit";
5186                 break;
5188         case LINE_STAT_UNTRACKED:
5189                 text = "Press %s to stage %s for addition";
5190                 break;
5192         case LINE_STAT_HEAD:
5193         case LINE_STAT_NONE:
5194                 text = "Nothing to update";
5195                 break;
5197         default:
5198                 die("line type %d not handled in switch", line->type);
5199         }
5201         if (status && status->status == 'U') {
5202                 text = "Press %s to resolve conflict in %s";
5203                 key = get_key(REQ_STATUS_MERGE);
5205         } else {
5206                 key = get_key(REQ_STATUS_UPDATE);
5207         }
5209         string_format(view->ref, text, key, file);
5212 static bool
5213 status_grep(struct view *view, struct line *line)
5215         struct status *status = line->data;
5216         enum { S_STATUS, S_NAME, S_END } state;
5217         char buf[2] = "?";
5218         regmatch_t pmatch;
5220         if (!status)
5221                 return FALSE;
5223         for (state = S_STATUS; state < S_END; state++) {
5224                 const char *text;
5226                 switch (state) {
5227                 case S_NAME:    text = status->new.name;        break;
5228                 case S_STATUS:
5229                         buf[0] = status->status;
5230                         text = buf;
5231                         break;
5233                 default:
5234                         return FALSE;
5235                 }
5237                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5238                         return TRUE;
5239         }
5241         return FALSE;
5244 static struct view_ops status_ops = {
5245         "file",
5246         NULL,
5247         status_open,
5248         NULL,
5249         status_draw,
5250         status_request,
5251         status_grep,
5252         status_select,
5253 };
5256 static bool
5257 stage_diff_write(struct io *io, struct line *line, struct line *end)
5259         while (line < end) {
5260                 if (!io_write(io, line->data, strlen(line->data)) ||
5261                     !io_write(io, "\n", 1))
5262                         return FALSE;
5263                 line++;
5264                 if (line->type == LINE_DIFF_CHUNK ||
5265                     line->type == LINE_DIFF_HEADER)
5266                         break;
5267         }
5269         return TRUE;
5272 static struct line *
5273 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5275         for (; view->line < line; line--)
5276                 if (line->type == type)
5277                         return line;
5279         return NULL;
5282 static bool
5283 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5285         const char *apply_argv[SIZEOF_ARG] = {
5286                 "git", "apply", "--whitespace=nowarn", NULL
5287         };
5288         struct line *diff_hdr;
5289         struct io io = {};
5290         int argc = 3;
5292         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5293         if (!diff_hdr)
5294                 return FALSE;
5296         if (!revert)
5297                 apply_argv[argc++] = "--cached";
5298         if (revert || stage_line_type == LINE_STAT_STAGED)
5299                 apply_argv[argc++] = "-R";
5300         apply_argv[argc++] = "-";
5301         apply_argv[argc++] = NULL;
5302         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5303                 return FALSE;
5305         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5306             !stage_diff_write(&io, chunk, view->line + view->lines))
5307                 chunk = NULL;
5309         done_io(&io);
5310         run_io_bg(update_index_argv);
5312         return chunk ? TRUE : FALSE;
5315 static bool
5316 stage_update(struct view *view, struct line *line)
5318         struct line *chunk = NULL;
5320         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5321                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5323         if (chunk) {
5324                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5325                         report("Failed to apply chunk");
5326                         return FALSE;
5327                 }
5329         } else if (!stage_status.status) {
5330                 view = VIEW(REQ_VIEW_STATUS);
5332                 for (line = view->line; line < view->line + view->lines; line++)
5333                         if (line->type == stage_line_type)
5334                                 break;
5336                 if (!status_update_files(view, line + 1)) {
5337                         report("Failed to update files");
5338                         return FALSE;
5339                 }
5341         } else if (!status_update_file(&stage_status, stage_line_type)) {
5342                 report("Failed to update file");
5343                 return FALSE;
5344         }
5346         return TRUE;
5349 static bool
5350 stage_revert(struct view *view, struct line *line)
5352         struct line *chunk = NULL;
5354         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5355                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5357         if (chunk) {
5358                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5359                         return FALSE;
5361                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5362                         report("Failed to revert chunk");
5363                         return FALSE;
5364                 }
5365                 return TRUE;
5367         } else {
5368                 return status_revert(stage_status.status ? &stage_status : NULL,
5369                                      stage_line_type, FALSE);
5370         }
5374 static void
5375 stage_next(struct view *view, struct line *line)
5377         int i;
5379         if (!stage_chunks) {
5380                 static size_t alloc = 0;
5381                 int *tmp;
5383                 for (line = view->line; line < view->line + view->lines; line++) {
5384                         if (line->type != LINE_DIFF_CHUNK)
5385                                 continue;
5387                         tmp = realloc_items(stage_chunk, &alloc,
5388                                             stage_chunks, sizeof(*tmp));
5389                         if (!tmp) {
5390                                 report("Allocation failure");
5391                                 return;
5392                         }
5394                         stage_chunk = tmp;
5395                         stage_chunk[stage_chunks++] = line - view->line;
5396                 }
5397         }
5399         for (i = 0; i < stage_chunks; i++) {
5400                 if (stage_chunk[i] > view->lineno) {
5401                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5402                         report("Chunk %d of %d", i + 1, stage_chunks);
5403                         return;
5404                 }
5405         }
5407         report("No next chunk found");
5410 static enum request
5411 stage_request(struct view *view, enum request request, struct line *line)
5413         switch (request) {
5414         case REQ_STATUS_UPDATE:
5415                 if (!stage_update(view, line))
5416                         return REQ_NONE;
5417                 break;
5419         case REQ_STATUS_REVERT:
5420                 if (!stage_revert(view, line))
5421                         return REQ_NONE;
5422                 break;
5424         case REQ_STAGE_NEXT:
5425                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5426                         report("File is untracked; press %s to add",
5427                                get_key(REQ_STATUS_UPDATE));
5428                         return REQ_NONE;
5429                 }
5430                 stage_next(view, line);
5431                 return REQ_NONE;
5433         case REQ_EDIT:
5434                 if (!stage_status.new.name[0])
5435                         return request;
5436                 if (stage_status.status == 'D') {
5437                         report("File has been deleted.");
5438                         return REQ_NONE;
5439                 }
5441                 open_editor(stage_status.status != '?', stage_status.new.name);
5442                 break;
5444         case REQ_REFRESH:
5445                 /* Reload everything ... */
5446                 break;
5448         case REQ_VIEW_BLAME:
5449                 if (stage_status.new.name[0]) {
5450                         string_copy(opt_file, stage_status.new.name);
5451                         opt_ref[0] = 0;
5452                 }
5453                 return request;
5455         case REQ_ENTER:
5456                 return pager_request(view, request, line);
5458         default:
5459                 return request;
5460         }
5462         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5463         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5465         /* Check whether the staged entry still exists, and close the
5466          * stage view if it doesn't. */
5467         if (!status_exists(&stage_status, stage_line_type)) {
5468                 status_restore(VIEW(REQ_VIEW_STATUS));
5469                 return REQ_VIEW_CLOSE;
5470         }
5472         if (stage_line_type == LINE_STAT_UNTRACKED) {
5473                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5474                         report("Cannot display a directory");
5475                         return REQ_NONE;
5476                 }
5478                 if (!prepare_update_file(view, stage_status.new.name)) {
5479                         report("Failed to open file: %s", strerror(errno));
5480                         return REQ_NONE;
5481                 }
5482         }
5483         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5485         return REQ_NONE;
5488 static struct view_ops stage_ops = {
5489         "line",
5490         NULL,
5491         NULL,
5492         pager_read,
5493         pager_draw,
5494         stage_request,
5495         pager_grep,
5496         pager_select,
5497 };
5500 /*
5501  * Revision graph
5502  */
5504 struct commit {
5505         char id[SIZEOF_REV];            /* SHA1 ID. */
5506         char title[128];                /* First line of the commit message. */
5507         char author[75];                /* Author of the commit. */
5508         struct tm time;                 /* Date from the author ident. */
5509         struct ref **refs;              /* Repository references. */
5510         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5511         size_t graph_size;              /* The width of the graph array. */
5512         bool has_parents;               /* Rewritten --parents seen. */
5513 };
5515 /* Size of rev graph with no  "padding" columns */
5516 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5518 struct rev_graph {
5519         struct rev_graph *prev, *next, *parents;
5520         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5521         size_t size;
5522         struct commit *commit;
5523         size_t pos;
5524         unsigned int boundary:1;
5525 };
5527 /* Parents of the commit being visualized. */
5528 static struct rev_graph graph_parents[4];
5530 /* The current stack of revisions on the graph. */
5531 static struct rev_graph graph_stacks[4] = {
5532         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5533         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5534         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5535         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5536 };
5538 static inline bool
5539 graph_parent_is_merge(struct rev_graph *graph)
5541         return graph->parents->size > 1;
5544 static inline void
5545 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5547         struct commit *commit = graph->commit;
5549         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5550                 commit->graph[commit->graph_size++] = symbol;
5553 static void
5554 clear_rev_graph(struct rev_graph *graph)
5556         graph->boundary = 0;
5557         graph->size = graph->pos = 0;
5558         graph->commit = NULL;
5559         memset(graph->parents, 0, sizeof(*graph->parents));
5562 static void
5563 done_rev_graph(struct rev_graph *graph)
5565         if (graph_parent_is_merge(graph) &&
5566             graph->pos < graph->size - 1 &&
5567             graph->next->size == graph->size + graph->parents->size - 1) {
5568                 size_t i = graph->pos + graph->parents->size - 1;
5570                 graph->commit->graph_size = i * 2;
5571                 while (i < graph->next->size - 1) {
5572                         append_to_rev_graph(graph, ' ');
5573                         append_to_rev_graph(graph, '\\');
5574                         i++;
5575                 }
5576         }
5578         clear_rev_graph(graph);
5581 static void
5582 push_rev_graph(struct rev_graph *graph, const char *parent)
5584         int i;
5586         /* "Collapse" duplicate parents lines.
5587          *
5588          * FIXME: This needs to also update update the drawn graph but
5589          * for now it just serves as a method for pruning graph lines. */
5590         for (i = 0; i < graph->size; i++)
5591                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5592                         return;
5594         if (graph->size < SIZEOF_REVITEMS) {
5595                 string_copy_rev(graph->rev[graph->size++], parent);
5596         }
5599 static chtype
5600 get_rev_graph_symbol(struct rev_graph *graph)
5602         chtype symbol;
5604         if (graph->boundary)
5605                 symbol = REVGRAPH_BOUND;
5606         else if (graph->parents->size == 0)
5607                 symbol = REVGRAPH_INIT;
5608         else if (graph_parent_is_merge(graph))
5609                 symbol = REVGRAPH_MERGE;
5610         else if (graph->pos >= graph->size)
5611                 symbol = REVGRAPH_BRANCH;
5612         else
5613                 symbol = REVGRAPH_COMMIT;
5615         return symbol;
5618 static void
5619 draw_rev_graph(struct rev_graph *graph)
5621         struct rev_filler {
5622                 chtype separator, line;
5623         };
5624         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5625         static struct rev_filler fillers[] = {
5626                 { ' ',  '|' },
5627                 { '`',  '.' },
5628                 { '\'', ' ' },
5629                 { '/',  ' ' },
5630         };
5631         chtype symbol = get_rev_graph_symbol(graph);
5632         struct rev_filler *filler;
5633         size_t i;
5635         if (opt_line_graphics)
5636                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5638         filler = &fillers[DEFAULT];
5640         for (i = 0; i < graph->pos; i++) {
5641                 append_to_rev_graph(graph, filler->line);
5642                 if (graph_parent_is_merge(graph->prev) &&
5643                     graph->prev->pos == i)
5644                         filler = &fillers[RSHARP];
5646                 append_to_rev_graph(graph, filler->separator);
5647         }
5649         /* Place the symbol for this revision. */
5650         append_to_rev_graph(graph, symbol);
5652         if (graph->prev->size > graph->size)
5653                 filler = &fillers[RDIAG];
5654         else
5655                 filler = &fillers[DEFAULT];
5657         i++;
5659         for (; i < graph->size; i++) {
5660                 append_to_rev_graph(graph, filler->separator);
5661                 append_to_rev_graph(graph, filler->line);
5662                 if (graph_parent_is_merge(graph->prev) &&
5663                     i < graph->prev->pos + graph->parents->size)
5664                         filler = &fillers[RSHARP];
5665                 if (graph->prev->size > graph->size)
5666                         filler = &fillers[LDIAG];
5667         }
5669         if (graph->prev->size > graph->size) {
5670                 append_to_rev_graph(graph, filler->separator);
5671                 if (filler->line != ' ')
5672                         append_to_rev_graph(graph, filler->line);
5673         }
5676 /* Prepare the next rev graph */
5677 static void
5678 prepare_rev_graph(struct rev_graph *graph)
5680         size_t i;
5682         /* First, traverse all lines of revisions up to the active one. */
5683         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5684                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5685                         break;
5687                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5688         }
5690         /* Interleave the new revision parent(s). */
5691         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5692                 push_rev_graph(graph->next, graph->parents->rev[i]);
5694         /* Lastly, put any remaining revisions. */
5695         for (i = graph->pos + 1; i < graph->size; i++)
5696                 push_rev_graph(graph->next, graph->rev[i]);
5699 static void
5700 update_rev_graph(struct view *view, struct rev_graph *graph)
5702         /* If this is the finalizing update ... */
5703         if (graph->commit)
5704                 prepare_rev_graph(graph);
5706         /* Graph visualization needs a one rev look-ahead,
5707          * so the first update doesn't visualize anything. */
5708         if (!graph->prev->commit)
5709                 return;
5711         if (view->lines > 2)
5712                 view->line[view->lines - 3].dirty = 1;
5713         if (view->lines > 1)
5714                 view->line[view->lines - 2].dirty = 1;
5715         draw_rev_graph(graph->prev);
5716         done_rev_graph(graph->prev->prev);
5720 /*
5721  * Main view backend
5722  */
5724 static const char *main_argv[SIZEOF_ARG] = {
5725         "git", "log", "--no-color", "--pretty=raw", "--parents",
5726                       "--topo-order", "%(head)", NULL
5727 };
5729 static bool
5730 main_draw(struct view *view, struct line *line, unsigned int lineno)
5732         struct commit *commit = line->data;
5734         if (!*commit->author)
5735                 return FALSE;
5737         if (opt_date && draw_date(view, &commit->time))
5738                 return TRUE;
5740         if (opt_author && draw_author(view, commit->author))
5741                 return TRUE;
5743         if (opt_rev_graph && commit->graph_size &&
5744             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5745                 return TRUE;
5747         if (opt_show_refs && commit->refs) {
5748                 size_t i = 0;
5750                 do {
5751                         enum line_type type;
5753                         if (commit->refs[i]->head)
5754                                 type = LINE_MAIN_HEAD;
5755                         else if (commit->refs[i]->ltag)
5756                                 type = LINE_MAIN_LOCAL_TAG;
5757                         else if (commit->refs[i]->tag)
5758                                 type = LINE_MAIN_TAG;
5759                         else if (commit->refs[i]->tracked)
5760                                 type = LINE_MAIN_TRACKED;
5761                         else if (commit->refs[i]->remote)
5762                                 type = LINE_MAIN_REMOTE;
5763                         else
5764                                 type = LINE_MAIN_REF;
5766                         if (draw_text(view, type, "[", TRUE) ||
5767                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5768                             draw_text(view, type, "]", TRUE))
5769                                 return TRUE;
5771                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5772                                 return TRUE;
5773                 } while (commit->refs[i++]->next);
5774         }
5776         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5777         return TRUE;
5780 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5781 static bool
5782 main_read(struct view *view, char *line)
5784         static struct rev_graph *graph = graph_stacks;
5785         enum line_type type;
5786         struct commit *commit;
5788         if (!line) {
5789                 int i;
5791                 if (!view->lines && !view->parent)
5792                         die("No revisions match the given arguments.");
5793                 if (view->lines > 0) {
5794                         commit = view->line[view->lines - 1].data;
5795                         view->line[view->lines - 1].dirty = 1;
5796                         if (!*commit->author) {
5797                                 view->lines--;
5798                                 free(commit);
5799                                 graph->commit = NULL;
5800                         }
5801                 }
5802                 update_rev_graph(view, graph);
5804                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5805                         clear_rev_graph(&graph_stacks[i]);
5806                 return TRUE;
5807         }
5809         type = get_line_type(line);
5810         if (type == LINE_COMMIT) {
5811                 commit = calloc(1, sizeof(struct commit));
5812                 if (!commit)
5813                         return FALSE;
5815                 line += STRING_SIZE("commit ");
5816                 if (*line == '-') {
5817                         graph->boundary = 1;
5818                         line++;
5819                 }
5821                 string_copy_rev(commit->id, line);
5822                 commit->refs = get_refs(commit->id);
5823                 graph->commit = commit;
5824                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5826                 while ((line = strchr(line, ' '))) {
5827                         line++;
5828                         push_rev_graph(graph->parents, line);
5829                         commit->has_parents = TRUE;
5830                 }
5831                 return TRUE;
5832         }
5834         if (!view->lines)
5835                 return TRUE;
5836         commit = view->line[view->lines - 1].data;
5838         switch (type) {
5839         case LINE_PARENT:
5840                 if (commit->has_parents)
5841                         break;
5842                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5843                 break;
5845         case LINE_AUTHOR:
5846                 parse_author_line(line + STRING_SIZE("author "),
5847                                   commit->author, sizeof(commit->author),
5848                                   &commit->time);
5849                 update_rev_graph(view, graph);
5850                 graph = graph->next;
5851                 break;
5853         default:
5854                 /* Fill in the commit title if it has not already been set. */
5855                 if (commit->title[0])
5856                         break;
5858                 /* Require titles to start with a non-space character at the
5859                  * offset used by git log. */
5860                 if (strncmp(line, "    ", 4))
5861                         break;
5862                 line += 4;
5863                 /* Well, if the title starts with a whitespace character,
5864                  * try to be forgiving.  Otherwise we end up with no title. */
5865                 while (isspace(*line))
5866                         line++;
5867                 if (*line == '\0')
5868                         break;
5869                 /* FIXME: More graceful handling of titles; append "..." to
5870                  * shortened titles, etc. */
5872                 string_expand(commit->title, sizeof(commit->title), line, 1);
5873                 view->line[view->lines - 1].dirty = 1;
5874         }
5876         return TRUE;
5879 static enum request
5880 main_request(struct view *view, enum request request, struct line *line)
5882         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5884         switch (request) {
5885         case REQ_ENTER:
5886                 open_view(view, REQ_VIEW_DIFF, flags);
5887                 break;
5888         case REQ_REFRESH:
5889                 load_refs();
5890                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5891                 break;
5892         default:
5893                 return request;
5894         }
5896         return REQ_NONE;
5899 static bool
5900 grep_refs(struct ref **refs, regex_t *regex)
5902         regmatch_t pmatch;
5903         size_t i = 0;
5905         if (!refs)
5906                 return FALSE;
5907         do {
5908                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5909                         return TRUE;
5910         } while (refs[i++]->next);
5912         return FALSE;
5915 static bool
5916 main_grep(struct view *view, struct line *line)
5918         struct commit *commit = line->data;
5919         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5920         char buf[DATE_COLS + 1];
5921         regmatch_t pmatch;
5923         for (state = S_TITLE; state < S_END; state++) {
5924                 char *text;
5926                 switch (state) {
5927                 case S_TITLE:   text = commit->title;   break;
5928                 case S_AUTHOR:
5929                         if (!opt_author)
5930                                 continue;
5931                         text = commit->author;
5932                         break;
5933                 case S_DATE:
5934                         if (!opt_date)
5935                                 continue;
5936                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5937                                 continue;
5938                         text = buf;
5939                         break;
5940                 case S_REFS:
5941                         if (!opt_show_refs)
5942                                 continue;
5943                         if (grep_refs(commit->refs, view->regex) == TRUE)
5944                                 return TRUE;
5945                         continue;
5946                 default:
5947                         return FALSE;
5948                 }
5950                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5951                         return TRUE;
5952         }
5954         return FALSE;
5957 static void
5958 main_select(struct view *view, struct line *line)
5960         struct commit *commit = line->data;
5962         string_copy_rev(view->ref, commit->id);
5963         string_copy_rev(ref_commit, view->ref);
5966 static struct view_ops main_ops = {
5967         "commit",
5968         main_argv,
5969         NULL,
5970         main_read,
5971         main_draw,
5972         main_request,
5973         main_grep,
5974         main_select,
5975 };
5978 /*
5979  * Unicode / UTF-8 handling
5980  *
5981  * NOTE: Much of the following code for dealing with Unicode is derived from
5982  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5983  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
5984  */
5986 static inline int
5987 unicode_width(unsigned long c)
5989         if (c >= 0x1100 &&
5990            (c <= 0x115f                         /* Hangul Jamo */
5991             || c == 0x2329
5992             || c == 0x232a
5993             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5994                                                 /* CJK ... Yi */
5995             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5996             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5997             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5998             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5999             || (c >= 0xffe0  && c <= 0xffe6)
6000             || (c >= 0x20000 && c <= 0x2fffd)
6001             || (c >= 0x30000 && c <= 0x3fffd)))
6002                 return 2;
6004         if (c == '\t')
6005                 return opt_tab_size;
6007         return 1;
6010 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6011  * Illegal bytes are set one. */
6012 static const unsigned char utf8_bytes[256] = {
6013         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,
6014         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,
6015         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,
6016         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,
6017         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,
6018         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,
6019         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,
6020         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,
6021 };
6023 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6024 static inline unsigned long
6025 utf8_to_unicode(const char *string, size_t length)
6027         unsigned long unicode;
6029         switch (length) {
6030         case 1:
6031                 unicode  =   string[0];
6032                 break;
6033         case 2:
6034                 unicode  =  (string[0] & 0x1f) << 6;
6035                 unicode +=  (string[1] & 0x3f);
6036                 break;
6037         case 3:
6038                 unicode  =  (string[0] & 0x0f) << 12;
6039                 unicode += ((string[1] & 0x3f) << 6);
6040                 unicode +=  (string[2] & 0x3f);
6041                 break;
6042         case 4:
6043                 unicode  =  (string[0] & 0x0f) << 18;
6044                 unicode += ((string[1] & 0x3f) << 12);
6045                 unicode += ((string[2] & 0x3f) << 6);
6046                 unicode +=  (string[3] & 0x3f);
6047                 break;
6048         case 5:
6049                 unicode  =  (string[0] & 0x0f) << 24;
6050                 unicode += ((string[1] & 0x3f) << 18);
6051                 unicode += ((string[2] & 0x3f) << 12);
6052                 unicode += ((string[3] & 0x3f) << 6);
6053                 unicode +=  (string[4] & 0x3f);
6054                 break;
6055         case 6:
6056                 unicode  =  (string[0] & 0x01) << 30;
6057                 unicode += ((string[1] & 0x3f) << 24);
6058                 unicode += ((string[2] & 0x3f) << 18);
6059                 unicode += ((string[3] & 0x3f) << 12);
6060                 unicode += ((string[4] & 0x3f) << 6);
6061                 unicode +=  (string[5] & 0x3f);
6062                 break;
6063         default:
6064                 die("Invalid Unicode length");
6065         }
6067         /* Invalid characters could return the special 0xfffd value but NUL
6068          * should be just as good. */
6069         return unicode > 0xffff ? 0 : unicode;
6072 /* Calculates how much of string can be shown within the given maximum width
6073  * and sets trimmed parameter to non-zero value if all of string could not be
6074  * shown. If the reserve flag is TRUE, it will reserve at least one
6075  * trailing character, which can be useful when drawing a delimiter.
6076  *
6077  * Returns the number of bytes to output from string to satisfy max_width. */
6078 static size_t
6079 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6081         const char *string = *start;
6082         const char *end = strchr(string, '\0');
6083         unsigned char last_bytes = 0;
6084         size_t last_ucwidth = 0;
6086         *width = 0;
6087         *trimmed = 0;
6089         while (string < end) {
6090                 int c = *(unsigned char *) string;
6091                 unsigned char bytes = utf8_bytes[c];
6092                 size_t ucwidth;
6093                 unsigned long unicode;
6095                 if (string + bytes > end)
6096                         break;
6098                 /* Change representation to figure out whether
6099                  * it is a single- or double-width character. */
6101                 unicode = utf8_to_unicode(string, bytes);
6102                 /* FIXME: Graceful handling of invalid Unicode character. */
6103                 if (!unicode)
6104                         break;
6106                 ucwidth = unicode_width(unicode);
6107                 if (skip > 0) {
6108                         skip -= ucwidth <= skip ? ucwidth : skip;
6109                         *start += bytes;
6110                 }
6111                 *width  += ucwidth;
6112                 if (*width > max_width) {
6113                         *trimmed = 1;
6114                         *width -= ucwidth;
6115                         if (reserve && *width == max_width) {
6116                                 string -= last_bytes;
6117                                 *width -= last_ucwidth;
6118                         }
6119                         break;
6120                 }
6122                 string  += bytes;
6123                 last_bytes = ucwidth ? bytes : 0;
6124                 last_ucwidth = ucwidth;
6125         }
6127         return string - *start;
6131 /*
6132  * Status management
6133  */
6135 /* Whether or not the curses interface has been initialized. */
6136 static bool cursed = FALSE;
6138 /* Terminal hacks and workarounds. */
6139 static bool use_scroll_redrawwin;
6140 static bool use_scroll_status_wclear;
6142 /* The status window is used for polling keystrokes. */
6143 static WINDOW *status_win;
6145 /* Reading from the prompt? */
6146 static bool input_mode = FALSE;
6148 static bool status_empty = FALSE;
6150 /* Update status and title window. */
6151 static void
6152 report(const char *msg, ...)
6154         struct view *view = display[current_view];
6156         if (input_mode)
6157                 return;
6159         if (!view) {
6160                 char buf[SIZEOF_STR];
6161                 va_list args;
6163                 va_start(args, msg);
6164                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6165                         buf[sizeof(buf) - 1] = 0;
6166                         buf[sizeof(buf) - 2] = '.';
6167                         buf[sizeof(buf) - 3] = '.';
6168                         buf[sizeof(buf) - 4] = '.';
6169                 }
6170                 va_end(args);
6171                 die("%s", buf);
6172         }
6174         if (!status_empty || *msg) {
6175                 va_list args;
6177                 va_start(args, msg);
6179                 wmove(status_win, 0, 0);
6180                 if (view->has_scrolled && use_scroll_status_wclear)
6181                         wclear(status_win);
6182                 if (*msg) {
6183                         vwprintw(status_win, msg, args);
6184                         status_empty = FALSE;
6185                 } else {
6186                         status_empty = TRUE;
6187                 }
6188                 wclrtoeol(status_win);
6189                 wnoutrefresh(status_win);
6191                 va_end(args);
6192         }
6194         update_view_title(view);
6197 /* Controls when nodelay should be in effect when polling user input. */
6198 static void
6199 set_nonblocking_input(bool loading)
6201         static unsigned int loading_views;
6203         if ((loading == FALSE && loading_views-- == 1) ||
6204             (loading == TRUE  && loading_views++ == 0))
6205                 nodelay(status_win, loading);
6208 static void
6209 init_display(void)
6211         const char *term;
6212         int x, y;
6214         /* Initialize the curses library */
6215         if (isatty(STDIN_FILENO)) {
6216                 cursed = !!initscr();
6217                 opt_tty = stdin;
6218         } else {
6219                 /* Leave stdin and stdout alone when acting as a pager. */
6220                 opt_tty = fopen("/dev/tty", "r+");
6221                 if (!opt_tty)
6222                         die("Failed to open /dev/tty");
6223                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6224         }
6226         if (!cursed)
6227                 die("Failed to initialize curses");
6229         nonl();         /* Disable conversion and detect newlines from input. */
6230         cbreak();       /* Take input chars one at a time, no wait for \n */
6231         noecho();       /* Don't echo input */
6232         leaveok(stdscr, FALSE);
6234         if (has_colors())
6235                 init_colors();
6237         getmaxyx(stdscr, y, x);
6238         status_win = newwin(1, 0, y - 1, 0);
6239         if (!status_win)
6240                 die("Failed to create status window");
6242         /* Enable keyboard mapping */
6243         keypad(status_win, TRUE);
6244         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6246         TABSIZE = opt_tab_size;
6247         if (opt_line_graphics) {
6248                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6249         }
6251         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6252         if (term && !strcmp(term, "gnome-terminal")) {
6253                 /* In the gnome-terminal-emulator, the message from
6254                  * scrolling up one line when impossible followed by
6255                  * scrolling down one line causes corruption of the
6256                  * status line. This is fixed by calling wclear. */
6257                 use_scroll_status_wclear = TRUE;
6258                 use_scroll_redrawwin = FALSE;
6260         } else if (term && !strcmp(term, "xrvt-xpm")) {
6261                 /* No problems with full optimizations in xrvt-(unicode)
6262                  * and aterm. */
6263                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6265         } else {
6266                 /* When scrolling in (u)xterm the last line in the
6267                  * scrolling direction will update slowly. */
6268                 use_scroll_redrawwin = TRUE;
6269                 use_scroll_status_wclear = FALSE;
6270         }
6273 static int
6274 get_input(int prompt_position)
6276         struct view *view;
6277         int i, key, cursor_y, cursor_x;
6279         if (prompt_position)
6280                 input_mode = TRUE;
6282         while (TRUE) {
6283                 foreach_view (view, i) {
6284                         update_view(view);
6285                         if (view_is_displayed(view) && view->has_scrolled &&
6286                             use_scroll_redrawwin)
6287                                 redrawwin(view->win);
6288                         view->has_scrolled = FALSE;
6289                 }
6291                 /* Update the cursor position. */
6292                 if (prompt_position) {
6293                         getbegyx(status_win, cursor_y, cursor_x);
6294                         cursor_x = prompt_position;
6295                 } else {
6296                         view = display[current_view];
6297                         getbegyx(view->win, cursor_y, cursor_x);
6298                         cursor_x = view->width - 1;
6299                         cursor_y += view->lineno - view->offset;
6300                 }
6301                 setsyx(cursor_y, cursor_x);
6303                 /* Refresh, accept single keystroke of input */
6304                 doupdate();
6305                 key = wgetch(status_win);
6307                 /* wgetch() with nodelay() enabled returns ERR when
6308                  * there's no input. */
6309                 if (key == ERR) {
6311                 } else if (key == KEY_RESIZE) {
6312                         int height, width;
6314                         getmaxyx(stdscr, height, width);
6316                         wresize(status_win, 1, width);
6317                         mvwin(status_win, height - 1, 0);
6318                         wnoutrefresh(status_win);
6319                         resize_display();
6320                         redraw_display(TRUE);
6322                 } else {
6323                         input_mode = FALSE;
6324                         return key;
6325                 }
6326         }
6329 static char *
6330 prompt_input(const char *prompt, input_handler handler, void *data)
6332         enum input_status status = INPUT_OK;
6333         static char buf[SIZEOF_STR];
6334         size_t pos = 0;
6336         buf[pos] = 0;
6338         while (status == INPUT_OK || status == INPUT_SKIP) {
6339                 int key;
6341                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6342                 wclrtoeol(status_win);
6344                 key = get_input(pos + 1);
6345                 switch (key) {
6346                 case KEY_RETURN:
6347                 case KEY_ENTER:
6348                 case '\n':
6349                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6350                         break;
6352                 case KEY_BACKSPACE:
6353                         if (pos > 0)
6354                                 buf[--pos] = 0;
6355                         else
6356                                 status = INPUT_CANCEL;
6357                         break;
6359                 case KEY_ESC:
6360                         status = INPUT_CANCEL;
6361                         break;
6363                 default:
6364                         if (pos >= sizeof(buf)) {
6365                                 report("Input string too long");
6366                                 return NULL;
6367                         }
6369                         status = handler(data, buf, key);
6370                         if (status == INPUT_OK)
6371                                 buf[pos++] = (char) key;
6372                 }
6373         }
6375         /* Clear the status window */
6376         status_empty = FALSE;
6377         report("");
6379         if (status == INPUT_CANCEL)
6380                 return NULL;
6382         buf[pos++] = 0;
6384         return buf;
6387 static enum input_status
6388 prompt_yesno_handler(void *data, char *buf, int c)
6390         if (c == 'y' || c == 'Y')
6391                 return INPUT_STOP;
6392         if (c == 'n' || c == 'N')
6393                 return INPUT_CANCEL;
6394         return INPUT_SKIP;
6397 static bool
6398 prompt_yesno(const char *prompt)
6400         char prompt2[SIZEOF_STR];
6402         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6403                 return FALSE;
6405         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6408 static enum input_status
6409 read_prompt_handler(void *data, char *buf, int c)
6411         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6414 static char *
6415 read_prompt(const char *prompt)
6417         return prompt_input(prompt, read_prompt_handler, NULL);
6420 /*
6421  * Repository properties
6422  */
6424 static struct ref *refs = NULL;
6425 static size_t refs_alloc = 0;
6426 static size_t refs_size = 0;
6428 /* Id <-> ref store */
6429 static struct ref ***id_refs = NULL;
6430 static size_t id_refs_alloc = 0;
6431 static size_t id_refs_size = 0;
6433 static int
6434 compare_refs(const void *ref1_, const void *ref2_)
6436         const struct ref *ref1 = *(const struct ref **)ref1_;
6437         const struct ref *ref2 = *(const struct ref **)ref2_;
6439         if (ref1->tag != ref2->tag)
6440                 return ref2->tag - ref1->tag;
6441         if (ref1->ltag != ref2->ltag)
6442                 return ref2->ltag - ref2->ltag;
6443         if (ref1->head != ref2->head)
6444                 return ref2->head - ref1->head;
6445         if (ref1->tracked != ref2->tracked)
6446                 return ref2->tracked - ref1->tracked;
6447         if (ref1->remote != ref2->remote)
6448                 return ref2->remote - ref1->remote;
6449         return strcmp(ref1->name, ref2->name);
6452 static struct ref **
6453 get_refs(const char *id)
6455         struct ref ***tmp_id_refs;
6456         struct ref **ref_list = NULL;
6457         size_t ref_list_alloc = 0;
6458         size_t ref_list_size = 0;
6459         size_t i;
6461         for (i = 0; i < id_refs_size; i++)
6462                 if (!strcmp(id, id_refs[i][0]->id))
6463                         return id_refs[i];
6465         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6466                                     sizeof(*id_refs));
6467         if (!tmp_id_refs)
6468                 return NULL;
6470         id_refs = tmp_id_refs;
6472         for (i = 0; i < refs_size; i++) {
6473                 struct ref **tmp;
6475                 if (strcmp(id, refs[i].id))
6476                         continue;
6478                 tmp = realloc_items(ref_list, &ref_list_alloc,
6479                                     ref_list_size + 1, sizeof(*ref_list));
6480                 if (!tmp) {
6481                         if (ref_list)
6482                                 free(ref_list);
6483                         return NULL;
6484                 }
6486                 ref_list = tmp;
6487                 ref_list[ref_list_size] = &refs[i];
6488                 /* XXX: The properties of the commit chains ensures that we can
6489                  * safely modify the shared ref. The repo references will
6490                  * always be similar for the same id. */
6491                 ref_list[ref_list_size]->next = 1;
6493                 ref_list_size++;
6494         }
6496         if (ref_list) {
6497                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6498                 ref_list[ref_list_size - 1]->next = 0;
6499                 id_refs[id_refs_size++] = ref_list;
6500         }
6502         return ref_list;
6505 static int
6506 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6508         struct ref *ref;
6509         bool tag = FALSE;
6510         bool ltag = FALSE;
6511         bool remote = FALSE;
6512         bool tracked = FALSE;
6513         bool check_replace = FALSE;
6514         bool head = FALSE;
6516         if (!prefixcmp(name, "refs/tags/")) {
6517                 if (!suffixcmp(name, namelen, "^{}")) {
6518                         namelen -= 3;
6519                         name[namelen] = 0;
6520                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6521                                 check_replace = TRUE;
6522                 } else {
6523                         ltag = TRUE;
6524                 }
6526                 tag = TRUE;
6527                 namelen -= STRING_SIZE("refs/tags/");
6528                 name    += STRING_SIZE("refs/tags/");
6530         } else if (!prefixcmp(name, "refs/remotes/")) {
6531                 remote = TRUE;
6532                 namelen -= STRING_SIZE("refs/remotes/");
6533                 name    += STRING_SIZE("refs/remotes/");
6534                 tracked  = !strcmp(opt_remote, name);
6536         } else if (!prefixcmp(name, "refs/heads/")) {
6537                 namelen -= STRING_SIZE("refs/heads/");
6538                 name    += STRING_SIZE("refs/heads/");
6539                 head     = !strncmp(opt_head, name, namelen);
6541         } else if (!strcmp(name, "HEAD")) {
6542                 string_ncopy(opt_head_rev, id, idlen);
6543                 return OK;
6544         }
6546         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6547                 /* it's an annotated tag, replace the previous SHA1 with the
6548                  * resolved commit id; relies on the fact git-ls-remote lists
6549                  * the commit id of an annotated tag right before the commit id
6550                  * it points to. */
6551                 refs[refs_size - 1].ltag = ltag;
6552                 string_copy_rev(refs[refs_size - 1].id, id);
6554                 return OK;
6555         }
6556         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6557         if (!refs)
6558                 return ERR;
6560         ref = &refs[refs_size++];
6561         ref->name = malloc(namelen + 1);
6562         if (!ref->name)
6563                 return ERR;
6565         strncpy(ref->name, name, namelen);
6566         ref->name[namelen] = 0;
6567         ref->head = head;
6568         ref->tag = tag;
6569         ref->ltag = ltag;
6570         ref->remote = remote;
6571         ref->tracked = tracked;
6572         string_copy_rev(ref->id, id);
6574         return OK;
6577 static int
6578 load_refs(void)
6580         static const char *ls_remote_argv[SIZEOF_ARG] = {
6581                 "git", "ls-remote", ".", NULL
6582         };
6583         static bool init = FALSE;
6585         if (!init) {
6586                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6587                 init = TRUE;
6588         }
6590         if (!*opt_git_dir)
6591                 return OK;
6593         while (refs_size > 0)
6594                 free(refs[--refs_size].name);
6595         while (id_refs_size > 0)
6596                 free(id_refs[--id_refs_size]);
6598         return run_io_load(ls_remote_argv, "\t", read_ref);
6601 static void
6602 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6604         const char *argv[SIZEOF_ARG] = { name, "=" };
6605         int argc = 1 + (cmd == option_set_command);
6606         int error = ERR;
6608         if (!argv_from_string(argv, &argc, value))
6609                 config_msg = "Too many option arguments";
6610         else
6611                 error = cmd(argc, argv);
6613         if (error == ERR)
6614                 warn("Option 'tig.%s': %s", name, config_msg);
6617 static int
6618 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6620         if (!strcmp(name, "i18n.commitencoding"))
6621                 string_ncopy(opt_encoding, value, valuelen);
6623         if (!strcmp(name, "core.editor"))
6624                 string_ncopy(opt_editor, value, valuelen);
6626         if (!prefixcmp(name, "tig.color."))
6627                 set_repo_config_option(name + 10, value, option_color_command);
6629         else if (!prefixcmp(name, "tig.bind."))
6630                 set_repo_config_option(name + 9, value, option_bind_command);
6632         else if (!prefixcmp(name, "tig."))
6633                 set_repo_config_option(name + 4, value, option_set_command);
6635         /* branch.<head>.remote */
6636         if (*opt_head &&
6637             !strncmp(name, "branch.", 7) &&
6638             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6639             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6640                 string_ncopy(opt_remote, value, valuelen);
6642         if (*opt_head && *opt_remote &&
6643             !strncmp(name, "branch.", 7) &&
6644             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6645             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6646                 size_t from = strlen(opt_remote);
6648                 if (!prefixcmp(value, "refs/heads/")) {
6649                         value += STRING_SIZE("refs/heads/");
6650                         valuelen -= STRING_SIZE("refs/heads/");
6651                 }
6653                 if (!string_format_from(opt_remote, &from, "/%s", value))
6654                         opt_remote[0] = 0;
6655         }
6657         return OK;
6660 static int
6661 load_git_config(void)
6663         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6665         return run_io_load(config_list_argv, "=", read_repo_config_option);
6668 static int
6669 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6671         if (!opt_git_dir[0]) {
6672                 string_ncopy(opt_git_dir, name, namelen);
6674         } else if (opt_is_inside_work_tree == -1) {
6675                 /* This can be 3 different values depending on the
6676                  * version of git being used. If git-rev-parse does not
6677                  * understand --is-inside-work-tree it will simply echo
6678                  * the option else either "true" or "false" is printed.
6679                  * Default to true for the unknown case. */
6680                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6682         } else if (*name == '.') {
6683                 string_ncopy(opt_cdup, name, namelen);
6685         } else {
6686                 string_ncopy(opt_prefix, name, namelen);
6687         }
6689         return OK;
6692 static int
6693 load_repo_info(void)
6695         const char *head_argv[] = {
6696                 "git", "symbolic-ref", "HEAD", NULL
6697         };
6698         const char *rev_parse_argv[] = {
6699                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6700                         "--show-cdup", "--show-prefix", NULL
6701         };
6703         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6704                 chomp_string(opt_head);
6705                 if (!prefixcmp(opt_head, "refs/heads/")) {
6706                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6708                         memmove(opt_head, offset, strlen(offset) + 1);
6709                 }
6710         }
6712         return run_io_load(rev_parse_argv, "=", read_repo_info);
6716 /*
6717  * Main
6718  */
6720 static const char usage[] =
6721 "tig " TIG_VERSION " (" __DATE__ ")\n"
6722 "\n"
6723 "Usage: tig        [options] [revs] [--] [paths]\n"
6724 "   or: tig show   [options] [revs] [--] [paths]\n"
6725 "   or: tig blame  [rev] path\n"
6726 "   or: tig status\n"
6727 "   or: tig <      [git command output]\n"
6728 "\n"
6729 "Options:\n"
6730 "  -v, --version   Show version and exit\n"
6731 "  -h, --help      Show help message and exit";
6733 static void __NORETURN
6734 quit(int sig)
6736         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6737         if (cursed)
6738                 endwin();
6739         exit(0);
6742 static void __NORETURN
6743 die(const char *err, ...)
6745         va_list args;
6747         endwin();
6749         va_start(args, err);
6750         fputs("tig: ", stderr);
6751         vfprintf(stderr, err, args);
6752         fputs("\n", stderr);
6753         va_end(args);
6755         exit(1);
6758 static void
6759 warn(const char *msg, ...)
6761         va_list args;
6763         va_start(args, msg);
6764         fputs("tig warning: ", stderr);
6765         vfprintf(stderr, msg, args);
6766         fputs("\n", stderr);
6767         va_end(args);
6770 static enum request
6771 parse_options(int argc, const char *argv[])
6773         enum request request = REQ_VIEW_MAIN;
6774         const char *subcommand;
6775         bool seen_dashdash = FALSE;
6776         /* XXX: This is vulnerable to the user overriding options
6777          * required for the main view parser. */
6778         const char *custom_argv[SIZEOF_ARG] = {
6779                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6780                         "--topo-order", NULL
6781         };
6782         int i, j = 6;
6784         if (!isatty(STDIN_FILENO)) {
6785                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6786                 return REQ_VIEW_PAGER;
6787         }
6789         if (argc <= 1)
6790                 return REQ_NONE;
6792         subcommand = argv[1];
6793         if (!strcmp(subcommand, "status")) {
6794                 if (argc > 2)
6795                         warn("ignoring arguments after `%s'", subcommand);
6796                 return REQ_VIEW_STATUS;
6798         } else if (!strcmp(subcommand, "blame")) {
6799                 if (argc <= 2 || argc > 4)
6800                         die("invalid number of options to blame\n\n%s", usage);
6802                 i = 2;
6803                 if (argc == 4) {
6804                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6805                         i++;
6806                 }
6808                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6809                 return REQ_VIEW_BLAME;
6811         } else if (!strcmp(subcommand, "show")) {
6812                 request = REQ_VIEW_DIFF;
6814         } else {
6815                 subcommand = NULL;
6816         }
6818         if (subcommand) {
6819                 custom_argv[1] = subcommand;
6820                 j = 2;
6821         }
6823         for (i = 1 + !!subcommand; i < argc; i++) {
6824                 const char *opt = argv[i];
6826                 if (seen_dashdash || !strcmp(opt, "--")) {
6827                         seen_dashdash = TRUE;
6829                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6830                         printf("tig version %s\n", TIG_VERSION);
6831                         quit(0);
6833                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6834                         printf("%s\n", usage);
6835                         quit(0);
6836                 }
6838                 custom_argv[j++] = opt;
6839                 if (j >= ARRAY_SIZE(custom_argv))
6840                         die("command too long");
6841         }
6843         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6844                 die("Failed to format arguments"); 
6846         return request;
6849 int
6850 main(int argc, const char *argv[])
6852         enum request request = parse_options(argc, argv);
6853         struct view *view;
6854         size_t i;
6856         signal(SIGINT, quit);
6858         if (setlocale(LC_ALL, "")) {
6859                 char *codeset = nl_langinfo(CODESET);
6861                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6862         }
6864         if (load_repo_info() == ERR)
6865                 die("Failed to load repo info.");
6867         if (load_options() == ERR)
6868                 die("Failed to load user config.");
6870         if (load_git_config() == ERR)
6871                 die("Failed to load repo config.");
6873         /* Require a git repository unless when running in pager mode. */
6874         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6875                 die("Not a git repository");
6877         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6878                 opt_utf8 = FALSE;
6880         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6881                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6882                 if (opt_iconv == ICONV_NONE)
6883                         die("Failed to initialize character set conversion");
6884         }
6886         if (load_refs() == ERR)
6887                 die("Failed to load refs.");
6889         foreach_view (view, i)
6890                 argv_from_env(view->ops->argv, view->cmd_env);
6892         init_display();
6894         if (request != REQ_NONE)
6895                 open_view(NULL, request, OPEN_PREPARED);
6896         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6898         while (view_driver(display[current_view], request)) {
6899                 int key = get_input(0);
6901                 view = display[current_view];
6902                 request = get_keybinding(view->keymap, key);
6904                 /* Some low-level request handling. This keeps access to
6905                  * status_win restricted. */
6906                 switch (request) {
6907                 case REQ_PROMPT:
6908                 {
6909                         char *cmd = read_prompt(":");
6911                         if (cmd) {
6912                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6913                                 const char *argv[SIZEOF_ARG] = { "git" };
6914                                 int argc = 1;
6916                                 /* When running random commands, initially show the
6917                                  * command in the title. However, it maybe later be
6918                                  * overwritten if a commit line is selected. */
6919                                 string_ncopy(next->ref, cmd, strlen(cmd));
6921                                 if (!argv_from_string(argv, &argc, cmd)) {
6922                                         report("Too many arguments");
6923                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6924                                         report("Failed to format command");
6925                                 } else {
6926                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6927                                 }
6928                         }
6930                         request = REQ_NONE;
6931                         break;
6932                 }
6933                 case REQ_SEARCH:
6934                 case REQ_SEARCH_BACK:
6935                 {
6936                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6937                         char *search = read_prompt(prompt);
6939                         if (search)
6940                                 string_ncopy(opt_search, search, strlen(search));
6941                         else if (*opt_search)
6942                                 request = request == REQ_SEARCH ?
6943                                         REQ_FIND_NEXT :
6944                                         REQ_FIND_PREV;
6945                         else
6946                                 request = REQ_NONE;
6947                         break;
6948                 }
6949                 default:
6950                         break;
6951                 }
6952         }
6954         quit(0);
6956         return 0;