Code

Refactor and unify timezone parsing
[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                 static struct enum_map obsolete[] = {
1408                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1409                         ENUM_MAP("main-date",   LINE_DATE),
1410                         ENUM_MAP("main-author", LINE_AUTHOR),
1411                 };
1412                 int index;
1414                 if (!map_enum(&index, obsolete, argv[0])) {
1415                         config_msg = "Unknown color name";
1416                         return ERR;
1417                 }
1418                 info = &line_info[index];
1419         }
1421         if (!set_color(&info->fg, argv[1]) ||
1422             !set_color(&info->bg, argv[2])) {
1423                 config_msg = "Unknown color";
1424                 return ERR;
1425         }
1427         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1428                 config_msg = "Unknown attribute";
1429                 return ERR;
1430         }
1432         return OK;
1435 static int parse_bool(bool *opt, const char *arg)
1437         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1438                 ? TRUE : FALSE;
1439         return OK;
1442 static int
1443 parse_int(int *opt, const char *arg, int min, int max)
1445         int value = atoi(arg);
1447         if (min <= value && value <= max)
1448                 *opt = value;
1449         return OK;
1452 static int
1453 parse_string(char *opt, const char *arg, size_t optsize)
1455         int arglen = strlen(arg);
1457         switch (arg[0]) {
1458         case '\"':
1459         case '\'':
1460                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1461                         config_msg = "Unmatched quotation";
1462                         return ERR;
1463                 }
1464                 arg += 1; arglen -= 2;
1465         default:
1466                 string_ncopy_do(opt, optsize, arg, strlen(arg));
1467                 return OK;
1468         }
1471 /* Wants: name = value */
1472 static int
1473 option_set_command(int argc, const char *argv[])
1475         if (argc != 3) {
1476                 config_msg = "Wrong number of arguments given to set command";
1477                 return ERR;
1478         }
1480         if (strcmp(argv[1], "=")) {
1481                 config_msg = "No value assigned";
1482                 return ERR;
1483         }
1485         if (!strcmp(argv[0], "show-author"))
1486                 return parse_bool(&opt_author, argv[2]);
1488         if (!strcmp(argv[0], "show-date"))
1489                 return parse_bool(&opt_date, argv[2]);
1491         if (!strcmp(argv[0], "show-rev-graph"))
1492                 return parse_bool(&opt_rev_graph, argv[2]);
1494         if (!strcmp(argv[0], "show-refs"))
1495                 return parse_bool(&opt_show_refs, argv[2]);
1497         if (!strcmp(argv[0], "show-line-numbers"))
1498                 return parse_bool(&opt_line_number, argv[2]);
1500         if (!strcmp(argv[0], "line-graphics"))
1501                 return parse_bool(&opt_line_graphics, argv[2]);
1503         if (!strcmp(argv[0], "line-number-interval"))
1504                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1506         if (!strcmp(argv[0], "author-width"))
1507                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1509         if (!strcmp(argv[0], "tab-size"))
1510                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1512         if (!strcmp(argv[0], "commit-encoding"))
1513                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1515         config_msg = "Unknown variable name";
1516         return ERR;
1519 /* Wants: mode request key */
1520 static int
1521 option_bind_command(int argc, const char *argv[])
1523         enum request request;
1524         int keymap;
1525         int key;
1527         if (argc < 3) {
1528                 config_msg = "Wrong number of arguments given to bind command";
1529                 return ERR;
1530         }
1532         if (set_keymap(&keymap, argv[0]) == ERR) {
1533                 config_msg = "Unknown key map";
1534                 return ERR;
1535         }
1537         key = get_key_value(argv[1]);
1538         if (key == ERR) {
1539                 config_msg = "Unknown key";
1540                 return ERR;
1541         }
1543         request = get_request(argv[2]);
1544         if (request == REQ_NONE) {
1545                 static struct enum_map obsolete[] = {
1546                         ENUM_MAP("cherry-pick",         REQ_NONE),
1547                         ENUM_MAP("screen-resize",       REQ_NONE),
1548                         ENUM_MAP("tree-parent",         REQ_PARENT),
1549                 };
1550                 int alias;
1552                 if (map_enum(&alias, obsolete, argv[2])) {
1553                         if (alias != REQ_NONE)
1554                                 add_keybinding(keymap, alias, key);
1555                         config_msg = "Obsolete request name";
1556                         return ERR;
1557                 }
1558         }
1559         if (request == REQ_NONE && *argv[2]++ == '!')
1560                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1561         if (request == REQ_NONE) {
1562                 config_msg = "Unknown request name";
1563                 return ERR;
1564         }
1566         add_keybinding(keymap, request, key);
1568         return OK;
1571 static int
1572 set_option(const char *opt, char *value)
1574         const char *argv[SIZEOF_ARG];
1575         int argc = 0;
1577         if (!argv_from_string(argv, &argc, value)) {
1578                 config_msg = "Too many option arguments";
1579                 return ERR;
1580         }
1582         if (!strcmp(opt, "color"))
1583                 return option_color_command(argc, argv);
1585         if (!strcmp(opt, "set"))
1586                 return option_set_command(argc, argv);
1588         if (!strcmp(opt, "bind"))
1589                 return option_bind_command(argc, argv);
1591         config_msg = "Unknown option command";
1592         return ERR;
1595 static int
1596 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1598         int status = OK;
1600         config_lineno++;
1601         config_msg = "Internal error";
1603         /* Check for comment markers, since read_properties() will
1604          * only ensure opt and value are split at first " \t". */
1605         optlen = strcspn(opt, "#");
1606         if (optlen == 0)
1607                 return OK;
1609         if (opt[optlen] != 0) {
1610                 config_msg = "No option value";
1611                 status = ERR;
1613         }  else {
1614                 /* Look for comment endings in the value. */
1615                 size_t len = strcspn(value, "#");
1617                 if (len < valuelen) {
1618                         valuelen = len;
1619                         value[valuelen] = 0;
1620                 }
1622                 status = set_option(opt, value);
1623         }
1625         if (status == ERR) {
1626                 warn("Error on line %d, near '%.*s': %s",
1627                      config_lineno, (int) optlen, opt, config_msg);
1628                 config_errors = TRUE;
1629         }
1631         /* Always keep going if errors are encountered. */
1632         return OK;
1635 static void
1636 load_option_file(const char *path)
1638         struct io io = {};
1640         /* It's OK that the file doesn't exist. */
1641         if (!io_open(&io, path))
1642                 return;
1644         config_lineno = 0;
1645         config_errors = FALSE;
1647         if (io_load(&io, " \t", read_option) == ERR ||
1648             config_errors == TRUE)
1649                 warn("Errors while loading %s.", path);
1652 static int
1653 load_options(void)
1655         const char *home = getenv("HOME");
1656         const char *tigrc_user = getenv("TIGRC_USER");
1657         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1658         char buf[SIZEOF_STR];
1660         add_builtin_run_requests();
1662         if (!tigrc_system) {
1663                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1664                         return ERR;
1665                 tigrc_system = buf;
1666         }
1667         load_option_file(tigrc_system);
1669         if (!tigrc_user) {
1670                 if (!home || !string_format(buf, "%s/.tigrc", home))
1671                         return ERR;
1672                 tigrc_user = buf;
1673         }
1674         load_option_file(tigrc_user);
1676         return OK;
1680 /*
1681  * The viewer
1682  */
1684 struct view;
1685 struct view_ops;
1687 /* The display array of active views and the index of the current view. */
1688 static struct view *display[2];
1689 static unsigned int current_view;
1691 #define foreach_displayed_view(view, i) \
1692         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1694 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1696 /* Current head and commit ID */
1697 static char ref_blob[SIZEOF_REF]        = "";
1698 static char ref_commit[SIZEOF_REF]      = "HEAD";
1699 static char ref_head[SIZEOF_REF]        = "HEAD";
1701 struct view {
1702         const char *name;       /* View name */
1703         const char *cmd_env;    /* Command line set via environment */
1704         const char *id;         /* Points to either of ref_{head,commit,blob} */
1706         struct view_ops *ops;   /* View operations */
1708         enum keymap keymap;     /* What keymap does this view have */
1709         bool git_dir;           /* Whether the view requires a git directory. */
1711         char ref[SIZEOF_REF];   /* Hovered commit reference */
1712         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1714         int height, width;      /* The width and height of the main window */
1715         WINDOW *win;            /* The main window */
1716         WINDOW *title;          /* The title window living below the main window */
1718         /* Navigation */
1719         unsigned long offset;   /* Offset of the window top */
1720         unsigned long yoffset;  /* Offset from the window side. */
1721         unsigned long lineno;   /* Current line number */
1722         unsigned long p_offset; /* Previous offset of the window top */
1723         unsigned long p_yoffset;/* Previous offset from the window side */
1724         unsigned long p_lineno; /* Previous current line number */
1725         bool p_restore;         /* Should the previous position be restored. */
1727         /* Searching */
1728         char grep[SIZEOF_STR];  /* Search string */
1729         regex_t *regex;         /* Pre-compiled regexp */
1731         /* If non-NULL, points to the view that opened this view. If this view
1732          * is closed tig will switch back to the parent view. */
1733         struct view *parent;
1735         /* Buffering */
1736         size_t lines;           /* Total number of lines */
1737         struct line *line;      /* Line index */
1738         size_t line_alloc;      /* Total number of allocated lines */
1739         unsigned int digits;    /* Number of digits in the lines member. */
1741         /* Drawing */
1742         struct line *curline;   /* Line currently being drawn. */
1743         enum line_type curtype; /* Attribute currently used for drawing. */
1744         unsigned long col;      /* Column when drawing. */
1745         bool has_scrolled;      /* View was scrolled. */
1746         bool can_hscroll;       /* View can be scrolled horizontally. */
1748         /* Loading */
1749         struct io io;
1750         struct io *pipe;
1751         time_t start_time;
1752         time_t update_secs;
1753 };
1755 struct view_ops {
1756         /* What type of content being displayed. Used in the title bar. */
1757         const char *type;
1758         /* Default command arguments. */
1759         const char **argv;
1760         /* Open and reads in all view content. */
1761         bool (*open)(struct view *view);
1762         /* Read one line; updates view->line. */
1763         bool (*read)(struct view *view, char *data);
1764         /* Draw one line; @lineno must be < view->height. */
1765         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1766         /* Depending on view handle a special requests. */
1767         enum request (*request)(struct view *view, enum request request, struct line *line);
1768         /* Search for regexp in a line. */
1769         bool (*grep)(struct view *view, struct line *line);
1770         /* Select line */
1771         void (*select)(struct view *view, struct line *line);
1772 };
1774 static struct view_ops blame_ops;
1775 static struct view_ops blob_ops;
1776 static struct view_ops diff_ops;
1777 static struct view_ops help_ops;
1778 static struct view_ops log_ops;
1779 static struct view_ops main_ops;
1780 static struct view_ops pager_ops;
1781 static struct view_ops stage_ops;
1782 static struct view_ops status_ops;
1783 static struct view_ops tree_ops;
1785 #define VIEW_STR(name, env, ref, ops, map, git) \
1786         { name, #env, ref, ops, map, git }
1788 #define VIEW_(id, name, ops, git, ref) \
1789         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1792 static struct view views[] = {
1793         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1794         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1795         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1796         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1797         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1798         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1799         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1800         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1801         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1802         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1803 };
1805 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1806 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1808 #define foreach_view(view, i) \
1809         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1811 #define view_is_displayed(view) \
1812         (view == display[0] || view == display[1])
1815 enum line_graphic {
1816         LINE_GRAPHIC_VLINE
1817 };
1819 static int line_graphics[] = {
1820         /* LINE_GRAPHIC_VLINE: */ '|'
1821 };
1823 static inline void
1824 set_view_attr(struct view *view, enum line_type type)
1826         if (!view->curline->selected && view->curtype != type) {
1827                 wattrset(view->win, get_line_attr(type));
1828                 wchgat(view->win, -1, 0, type, NULL);
1829                 view->curtype = type;
1830         }
1833 static int
1834 draw_chars(struct view *view, enum line_type type, const char *string,
1835            int max_len, bool use_tilde)
1837         int len = 0;
1838         int col = 0;
1839         int trimmed = FALSE;
1840         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1842         if (max_len <= 0)
1843                 return 0;
1845         if (opt_utf8) {
1846                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1847         } else {
1848                 col = len = strlen(string);
1849                 if (len > max_len) {
1850                         if (use_tilde) {
1851                                 max_len -= 1;
1852                         }
1853                         col = len = max_len;
1854                         trimmed = TRUE;
1855                 }
1856         }
1858         set_view_attr(view, type);
1859         if (len > 0)
1860                 waddnstr(view->win, string, len);
1861         if (trimmed && use_tilde) {
1862                 set_view_attr(view, LINE_DELIMITER);
1863                 waddch(view->win, '~');
1864                 col++;
1865         }
1867         if (view->col + col >= view->width + view->yoffset)
1868                 view->can_hscroll = TRUE;
1870         return col;
1873 static int
1874 draw_space(struct view *view, enum line_type type, int max, int spaces)
1876         static char space[] = "                    ";
1877         int col = 0;
1879         spaces = MIN(max, spaces);
1881         while (spaces > 0) {
1882                 int len = MIN(spaces, sizeof(space) - 1);
1884                 col += draw_chars(view, type, space, spaces, FALSE);
1885                 spaces -= len;
1886         }
1888         return col;
1891 static bool
1892 draw_lineno(struct view *view, unsigned int lineno)
1894         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1895         char number[10];
1896         int digits3 = view->digits < 3 ? 3 : view->digits;
1897         int max_number = MIN(digits3, STRING_SIZE(number));
1898         int max = view->width - view->col;
1899         int col;
1901         if (max < max_number)
1902                 max_number = max;
1904         lineno += view->offset + 1;
1905         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1906                 static char fmt[] = "%1ld";
1908                 if (view->digits <= 9)
1909                         fmt[1] = '0' + digits3;
1911                 if (!string_format(number, fmt, lineno))
1912                         number[0] = 0;
1913                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1914         } else {
1915                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1916         }
1918         if (col < max && skip <= col) {
1919                 set_view_attr(view, LINE_DEFAULT);
1920                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1921         }
1922         col++;
1924         view->col += col;
1925         if (col < max && skip <= col)
1926                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1927         view->col++;
1929         return view->width + view->yoffset <= view->col;
1932 static bool
1933 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1935         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1936         return view->width - view->col <= 0;
1939 static bool
1940 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1942         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1943         int max = view->width - view->col;
1944         int i;
1946         if (max < size)
1947                 size = max;
1949         set_view_attr(view, type);
1950         /* Using waddch() instead of waddnstr() ensures that
1951          * they'll be rendered correctly for the cursor line. */
1952         for (i = skip; i < size; i++)
1953                 waddch(view->win, graphic[i]);
1955         view->col += size;
1956         if (size < max && skip <= size)
1957                 waddch(view->win, ' ');
1958         view->col++;
1960         return view->width - view->col <= 0;
1963 static bool
1964 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1966         int max = MIN(view->width - view->col, len);
1967         int col;
1969         if (text)
1970                 col = draw_chars(view, type, text, max - 1, trim);
1971         else
1972                 col = draw_space(view, type, max - 1, max - 1);
1974         view->col += col;
1975         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1976         return view->width + view->yoffset <= view->col;
1979 static bool
1980 draw_date(struct view *view, struct tm *time)
1982         char buf[DATE_COLS];
1983         char *date;
1984         int timelen = 0;
1986         if (time)
1987                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1988         date = timelen ? buf : NULL;
1990         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1993 static bool
1994 draw_author(struct view *view, const char *author)
1996         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1998         if (!trim) {
1999                 static char initials[10];
2000                 size_t pos;
2002 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2004                 memset(initials, 0, sizeof(initials));
2005                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2006                         while (is_initial_sep(*author))
2007                                 author++;
2008                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2009                         while (*author && !is_initial_sep(author[1]))
2010                                 author++;
2011                 }
2013                 author = initials;
2014         }
2016         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2019 static bool
2020 draw_mode(struct view *view, mode_t mode)
2022         static const char dir_mode[]    = "drwxr-xr-x";
2023         static const char link_mode[]   = "lrwxrwxrwx";
2024         static const char exe_mode[]    = "-rwxr-xr-x";
2025         static const char file_mode[]   = "-rw-r--r--";
2026         const char *str;
2028         if (S_ISDIR(mode))
2029                 str = dir_mode;
2030         else if (S_ISLNK(mode))
2031                 str = link_mode;
2032         else if (mode & S_IXUSR)
2033                 str = exe_mode;
2034         else
2035                 str = file_mode;
2037         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2040 static bool
2041 draw_view_line(struct view *view, unsigned int lineno)
2043         struct line *line;
2044         bool selected = (view->offset + lineno == view->lineno);
2046         assert(view_is_displayed(view));
2048         if (view->offset + lineno >= view->lines)
2049                 return FALSE;
2051         line = &view->line[view->offset + lineno];
2053         wmove(view->win, lineno, 0);
2054         if (line->cleareol)
2055                 wclrtoeol(view->win);
2056         view->col = 0;
2057         view->curline = line;
2058         view->curtype = LINE_NONE;
2059         line->selected = FALSE;
2060         line->dirty = line->cleareol = 0;
2062         if (selected) {
2063                 set_view_attr(view, LINE_CURSOR);
2064                 line->selected = TRUE;
2065                 view->ops->select(view, line);
2066         }
2068         return view->ops->draw(view, line, lineno);
2071 static void
2072 redraw_view_dirty(struct view *view)
2074         bool dirty = FALSE;
2075         int lineno;
2077         for (lineno = 0; lineno < view->height; lineno++) {
2078                 if (view->offset + lineno >= view->lines)
2079                         break;
2080                 if (!view->line[view->offset + lineno].dirty)
2081                         continue;
2082                 dirty = TRUE;
2083                 if (!draw_view_line(view, lineno))
2084                         break;
2085         }
2087         if (!dirty)
2088                 return;
2089         wnoutrefresh(view->win);
2092 static void
2093 redraw_view_from(struct view *view, int lineno)
2095         assert(0 <= lineno && lineno < view->height);
2097         if (lineno == 0)
2098                 view->can_hscroll = FALSE;
2100         for (; lineno < view->height; lineno++) {
2101                 if (!draw_view_line(view, lineno))
2102                         break;
2103         }
2105         wnoutrefresh(view->win);
2108 static void
2109 redraw_view(struct view *view)
2111         werase(view->win);
2112         redraw_view_from(view, 0);
2116 static void
2117 update_view_title(struct view *view)
2119         char buf[SIZEOF_STR];
2120         char state[SIZEOF_STR];
2121         size_t bufpos = 0, statelen = 0;
2123         assert(view_is_displayed(view));
2125         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2126                 unsigned int view_lines = view->offset + view->height;
2127                 unsigned int lines = view->lines
2128                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2129                                    : 0;
2131                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2132                                    view->ops->type,
2133                                    view->lineno + 1,
2134                                    view->lines,
2135                                    lines);
2137         }
2139         if (view->pipe) {
2140                 time_t secs = time(NULL) - view->start_time;
2142                 /* Three git seconds are a long time ... */
2143                 if (secs > 2)
2144                         string_format_from(state, &statelen, " loading %lds", secs);
2145         }
2147         string_format_from(buf, &bufpos, "[%s]", view->name);
2148         if (*view->ref && bufpos < view->width) {
2149                 size_t refsize = strlen(view->ref);
2150                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2152                 if (minsize < view->width)
2153                         refsize = view->width - minsize + 7;
2154                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2155         }
2157         if (statelen && bufpos < view->width) {
2158                 string_format_from(buf, &bufpos, "%s", state);
2159         }
2161         if (view == display[current_view])
2162                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2163         else
2164                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2166         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2167         wclrtoeol(view->title);
2168         wnoutrefresh(view->title);
2171 static void
2172 resize_display(void)
2174         int offset, i;
2175         struct view *base = display[0];
2176         struct view *view = display[1] ? display[1] : display[0];
2178         /* Setup window dimensions */
2180         getmaxyx(stdscr, base->height, base->width);
2182         /* Make room for the status window. */
2183         base->height -= 1;
2185         if (view != base) {
2186                 /* Horizontal split. */
2187                 view->width   = base->width;
2188                 view->height  = SCALE_SPLIT_VIEW(base->height);
2189                 base->height -= view->height;
2191                 /* Make room for the title bar. */
2192                 view->height -= 1;
2193         }
2195         /* Make room for the title bar. */
2196         base->height -= 1;
2198         offset = 0;
2200         foreach_displayed_view (view, i) {
2201                 if (!view->win) {
2202                         view->win = newwin(view->height, 0, offset, 0);
2203                         if (!view->win)
2204                                 die("Failed to create %s view", view->name);
2206                         scrollok(view->win, FALSE);
2208                         view->title = newwin(1, 0, offset + view->height, 0);
2209                         if (!view->title)
2210                                 die("Failed to create title window");
2212                 } else {
2213                         wresize(view->win, view->height, view->width);
2214                         mvwin(view->win,   offset, 0);
2215                         mvwin(view->title, offset + view->height, 0);
2216                 }
2218                 offset += view->height + 1;
2219         }
2222 static void
2223 redraw_display(bool clear)
2225         struct view *view;
2226         int i;
2228         foreach_displayed_view (view, i) {
2229                 if (clear)
2230                         wclear(view->win);
2231                 redraw_view(view);
2232                 update_view_title(view);
2233         }
2236 static void
2237 toggle_view_option(bool *option, const char *help)
2239         *option = !*option;
2240         redraw_display(FALSE);
2241         report("%sabling %s", *option ? "En" : "Dis", help);
2244 /*
2245  * Navigation
2246  */
2248 /* Scrolling backend */
2249 static void
2250 do_scroll_view(struct view *view, int lines)
2252         bool redraw_current_line = FALSE;
2254         /* The rendering expects the new offset. */
2255         view->offset += lines;
2257         assert(0 <= view->offset && view->offset < view->lines);
2258         assert(lines);
2260         /* Move current line into the view. */
2261         if (view->lineno < view->offset) {
2262                 view->lineno = view->offset;
2263                 redraw_current_line = TRUE;
2264         } else if (view->lineno >= view->offset + view->height) {
2265                 view->lineno = view->offset + view->height - 1;
2266                 redraw_current_line = TRUE;
2267         }
2269         assert(view->offset <= view->lineno && view->lineno < view->lines);
2271         /* Redraw the whole screen if scrolling is pointless. */
2272         if (view->height < ABS(lines)) {
2273                 redraw_view(view);
2275         } else {
2276                 int line = lines > 0 ? view->height - lines : 0;
2277                 int end = line + ABS(lines);
2279                 scrollok(view->win, TRUE);
2280                 wscrl(view->win, lines);
2281                 scrollok(view->win, FALSE);
2283                 while (line < end && draw_view_line(view, line))
2284                         line++;
2286                 if (redraw_current_line)
2287                         draw_view_line(view, view->lineno - view->offset);
2288                 wnoutrefresh(view->win);
2289         }
2291         view->has_scrolled = TRUE;
2292         report("");
2295 /* Scroll frontend */
2296 static void
2297 scroll_view(struct view *view, enum request request)
2299         int lines = 1;
2301         assert(view_is_displayed(view));
2303         switch (request) {
2304         case REQ_SCROLL_LEFT:
2305                 if (view->yoffset == 0) {
2306                         report("Cannot scroll beyond the first column");
2307                         return;
2308                 }
2309                 if (view->yoffset <= SCROLL_INTERVAL)
2310                         view->yoffset = 0;
2311                 else
2312                         view->yoffset -= SCROLL_INTERVAL;
2313                 redraw_view_from(view, 0);
2314                 report("");
2315                 return;
2316         case REQ_SCROLL_RIGHT:
2317                 if (!view->can_hscroll) {
2318                         report("Cannot scroll beyond the last column");
2319                         return;
2320                 }
2321                 view->yoffset += SCROLL_INTERVAL;
2322                 redraw_view(view);
2323                 report("");
2324                 return;
2325         case REQ_SCROLL_PAGE_DOWN:
2326                 lines = view->height;
2327         case REQ_SCROLL_LINE_DOWN:
2328                 if (view->offset + lines > view->lines)
2329                         lines = view->lines - view->offset;
2331                 if (lines == 0 || view->offset + view->height >= view->lines) {
2332                         report("Cannot scroll beyond the last line");
2333                         return;
2334                 }
2335                 break;
2337         case REQ_SCROLL_PAGE_UP:
2338                 lines = view->height;
2339         case REQ_SCROLL_LINE_UP:
2340                 if (lines > view->offset)
2341                         lines = view->offset;
2343                 if (lines == 0) {
2344                         report("Cannot scroll beyond the first line");
2345                         return;
2346                 }
2348                 lines = -lines;
2349                 break;
2351         default:
2352                 die("request %d not handled in switch", request);
2353         }
2355         do_scroll_view(view, lines);
2358 /* Cursor moving */
2359 static void
2360 move_view(struct view *view, enum request request)
2362         int scroll_steps = 0;
2363         int steps;
2365         switch (request) {
2366         case REQ_MOVE_FIRST_LINE:
2367                 steps = -view->lineno;
2368                 break;
2370         case REQ_MOVE_LAST_LINE:
2371                 steps = view->lines - view->lineno - 1;
2372                 break;
2374         case REQ_MOVE_PAGE_UP:
2375                 steps = view->height > view->lineno
2376                       ? -view->lineno : -view->height;
2377                 break;
2379         case REQ_MOVE_PAGE_DOWN:
2380                 steps = view->lineno + view->height >= view->lines
2381                       ? view->lines - view->lineno - 1 : view->height;
2382                 break;
2384         case REQ_MOVE_UP:
2385                 steps = -1;
2386                 break;
2388         case REQ_MOVE_DOWN:
2389                 steps = 1;
2390                 break;
2392         default:
2393                 die("request %d not handled in switch", request);
2394         }
2396         if (steps <= 0 && view->lineno == 0) {
2397                 report("Cannot move beyond the first line");
2398                 return;
2400         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2401                 report("Cannot move beyond the last line");
2402                 return;
2403         }
2405         /* Move the current line */
2406         view->lineno += steps;
2407         assert(0 <= view->lineno && view->lineno < view->lines);
2409         /* Check whether the view needs to be scrolled */
2410         if (view->lineno < view->offset ||
2411             view->lineno >= view->offset + view->height) {
2412                 scroll_steps = steps;
2413                 if (steps < 0 && -steps > view->offset) {
2414                         scroll_steps = -view->offset;
2416                 } else if (steps > 0) {
2417                         if (view->lineno == view->lines - 1 &&
2418                             view->lines > view->height) {
2419                                 scroll_steps = view->lines - view->offset - 1;
2420                                 if (scroll_steps >= view->height)
2421                                         scroll_steps -= view->height - 1;
2422                         }
2423                 }
2424         }
2426         if (!view_is_displayed(view)) {
2427                 view->offset += scroll_steps;
2428                 assert(0 <= view->offset && view->offset < view->lines);
2429                 view->ops->select(view, &view->line[view->lineno]);
2430                 return;
2431         }
2433         /* Repaint the old "current" line if we be scrolling */
2434         if (ABS(steps) < view->height)
2435                 draw_view_line(view, view->lineno - steps - view->offset);
2437         if (scroll_steps) {
2438                 do_scroll_view(view, scroll_steps);
2439                 return;
2440         }
2442         /* Draw the current line */
2443         draw_view_line(view, view->lineno - view->offset);
2445         wnoutrefresh(view->win);
2446         report("");
2450 /*
2451  * Searching
2452  */
2454 static void search_view(struct view *view, enum request request);
2456 static void
2457 select_view_line(struct view *view, unsigned long lineno)
2459         if (lineno - view->offset >= view->height) {
2460                 view->offset = lineno;
2461                 view->lineno = lineno;
2462                 if (view_is_displayed(view))
2463                         redraw_view(view);
2465         } else {
2466                 unsigned long old_lineno = view->lineno - view->offset;
2468                 view->lineno = lineno;
2469                 if (view_is_displayed(view)) {
2470                         draw_view_line(view, old_lineno);
2471                         draw_view_line(view, view->lineno - view->offset);
2472                         wnoutrefresh(view->win);
2473                 } else {
2474                         view->ops->select(view, &view->line[view->lineno]);
2475                 }
2476         }
2479 static void
2480 find_next(struct view *view, enum request request)
2482         unsigned long lineno = view->lineno;
2483         int direction;
2485         if (!*view->grep) {
2486                 if (!*opt_search)
2487                         report("No previous search");
2488                 else
2489                         search_view(view, request);
2490                 return;
2491         }
2493         switch (request) {
2494         case REQ_SEARCH:
2495         case REQ_FIND_NEXT:
2496                 direction = 1;
2497                 break;
2499         case REQ_SEARCH_BACK:
2500         case REQ_FIND_PREV:
2501                 direction = -1;
2502                 break;
2504         default:
2505                 return;
2506         }
2508         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2509                 lineno += direction;
2511         /* Note, lineno is unsigned long so will wrap around in which case it
2512          * will become bigger than view->lines. */
2513         for (; lineno < view->lines; lineno += direction) {
2514                 if (view->ops->grep(view, &view->line[lineno])) {
2515                         select_view_line(view, lineno);
2516                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2517                         return;
2518                 }
2519         }
2521         report("No match found for '%s'", view->grep);
2524 static void
2525 search_view(struct view *view, enum request request)
2527         int regex_err;
2529         if (view->regex) {
2530                 regfree(view->regex);
2531                 *view->grep = 0;
2532         } else {
2533                 view->regex = calloc(1, sizeof(*view->regex));
2534                 if (!view->regex)
2535                         return;
2536         }
2538         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2539         if (regex_err != 0) {
2540                 char buf[SIZEOF_STR] = "unknown error";
2542                 regerror(regex_err, view->regex, buf, sizeof(buf));
2543                 report("Search failed: %s", buf);
2544                 return;
2545         }
2547         string_copy(view->grep, opt_search);
2549         find_next(view, request);
2552 /*
2553  * Incremental updating
2554  */
2556 static void
2557 reset_view(struct view *view)
2559         int i;
2561         for (i = 0; i < view->lines; i++)
2562                 free(view->line[i].data);
2563         free(view->line);
2565         view->p_offset = view->offset;
2566         view->p_yoffset = view->yoffset;
2567         view->p_lineno = view->lineno;
2569         view->line = NULL;
2570         view->offset = 0;
2571         view->yoffset = 0;
2572         view->lines  = 0;
2573         view->lineno = 0;
2574         view->line_alloc = 0;
2575         view->vid[0] = 0;
2576         view->update_secs = 0;
2579 static void
2580 free_argv(const char *argv[])
2582         int argc;
2584         for (argc = 0; argv[argc]; argc++)
2585                 free((void *) argv[argc]);
2588 static bool
2589 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2591         char buf[SIZEOF_STR];
2592         int argc;
2593         bool noreplace = flags == FORMAT_NONE;
2595         free_argv(dst_argv);
2597         for (argc = 0; src_argv[argc]; argc++) {
2598                 const char *arg = src_argv[argc];
2599                 size_t bufpos = 0;
2601                 while (arg) {
2602                         char *next = strstr(arg, "%(");
2603                         int len = next - arg;
2604                         const char *value;
2606                         if (!next || noreplace) {
2607                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2608                                         noreplace = TRUE;
2609                                 len = strlen(arg);
2610                                 value = "";
2612                         } else if (!prefixcmp(next, "%(directory)")) {
2613                                 value = opt_path;
2615                         } else if (!prefixcmp(next, "%(file)")) {
2616                                 value = opt_file;
2618                         } else if (!prefixcmp(next, "%(ref)")) {
2619                                 value = *opt_ref ? opt_ref : "HEAD";
2621                         } else if (!prefixcmp(next, "%(head)")) {
2622                                 value = ref_head;
2624                         } else if (!prefixcmp(next, "%(commit)")) {
2625                                 value = ref_commit;
2627                         } else if (!prefixcmp(next, "%(blob)")) {
2628                                 value = ref_blob;
2630                         } else {
2631                                 report("Unknown replacement: `%s`", next);
2632                                 return FALSE;
2633                         }
2635                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2636                                 return FALSE;
2638                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2639                 }
2641                 dst_argv[argc] = strdup(buf);
2642                 if (!dst_argv[argc])
2643                         break;
2644         }
2646         dst_argv[argc] = NULL;
2648         return src_argv[argc] == NULL;
2651 static bool
2652 restore_view_position(struct view *view)
2654         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2655                 return FALSE;
2657         /* Changing the view position cancels the restoring. */
2658         /* FIXME: Changing back to the first line is not detected. */
2659         if (view->offset != 0 || view->lineno != 0) {
2660                 view->p_restore = FALSE;
2661                 return FALSE;
2662         }
2664         if (view->p_lineno >= view->lines) {
2665                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2666                 if (view->p_offset >= view->p_lineno) {
2667                         unsigned long half = view->height / 2;
2669                         if (view->p_lineno > half)
2670                                 view->p_offset = view->p_lineno - half;
2671                         else
2672                                 view->p_offset = 0;
2673                 }
2674         }
2676         if (view_is_displayed(view) &&
2677             view->offset != view->p_offset &&
2678             view->lineno != view->p_lineno)
2679                 werase(view->win);
2681         view->offset = view->p_offset;
2682         view->yoffset = view->p_yoffset;
2683         view->lineno = view->p_lineno;
2684         view->p_restore = FALSE;
2686         return TRUE;
2689 static void
2690 end_update(struct view *view, bool force)
2692         if (!view->pipe)
2693                 return;
2694         while (!view->ops->read(view, NULL))
2695                 if (!force)
2696                         return;
2697         set_nonblocking_input(FALSE);
2698         if (force)
2699                 kill_io(view->pipe);
2700         done_io(view->pipe);
2701         view->pipe = NULL;
2704 static void
2705 setup_update(struct view *view, const char *vid)
2707         set_nonblocking_input(TRUE);
2708         reset_view(view);
2709         string_copy_rev(view->vid, vid);
2710         view->pipe = &view->io;
2711         view->start_time = time(NULL);
2714 static bool
2715 prepare_update(struct view *view, const char *argv[], const char *dir,
2716                enum format_flags flags)
2718         if (view->pipe)
2719                 end_update(view, TRUE);
2720         return init_io_rd(&view->io, argv, dir, flags);
2723 static bool
2724 prepare_update_file(struct view *view, const char *name)
2726         if (view->pipe)
2727                 end_update(view, TRUE);
2728         return io_open(&view->io, name);
2731 static bool
2732 begin_update(struct view *view, bool refresh)
2734         if (view->pipe)
2735                 end_update(view, TRUE);
2737         if (refresh) {
2738                 if (!start_io(&view->io))
2739                         return FALSE;
2741         } else {
2742                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2743                         opt_path[0] = 0;
2745                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2746                         return FALSE;
2748                 /* Put the current ref_* value to the view title ref
2749                  * member. This is needed by the blob view. Most other
2750                  * views sets it automatically after loading because the
2751                  * first line is a commit line. */
2752                 string_copy_rev(view->ref, view->id);
2753         }
2755         setup_update(view, view->id);
2757         return TRUE;
2760 #define ITEM_CHUNK_SIZE 256
2761 static void *
2762 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2764         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2765         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2767         if (mem == NULL || num_chunks != num_chunks_new) {
2768                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2769                 mem = realloc(mem, *size * item_size);
2770         }
2772         return mem;
2775 static struct line *
2776 realloc_lines(struct view *view, size_t line_size)
2778         size_t alloc = view->line_alloc;
2779         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2780                                          sizeof(*view->line));
2782         if (!tmp)
2783                 return NULL;
2785         view->line = tmp;
2786         view->line_alloc = alloc;
2787         return view->line;
2790 static bool
2791 update_view(struct view *view)
2793         char out_buffer[BUFSIZ * 2];
2794         char *line;
2795         /* Clear the view and redraw everything since the tree sorting
2796          * might have rearranged things. */
2797         bool redraw = view->lines == 0;
2798         bool can_read = TRUE;
2800         if (!view->pipe)
2801                 return TRUE;
2803         if (!io_can_read(view->pipe)) {
2804                 if (view->lines == 0) {
2805                         time_t secs = time(NULL) - view->start_time;
2807                         if (secs > 1 && secs > view->update_secs) {
2808                                 if (view->update_secs == 0)
2809                                         redraw_view(view);
2810                                 update_view_title(view);
2811                                 view->update_secs = secs;
2812                         }
2813                 }
2814                 return TRUE;
2815         }
2817         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2818                 if (opt_iconv != ICONV_NONE) {
2819                         ICONV_CONST char *inbuf = line;
2820                         size_t inlen = strlen(line) + 1;
2822                         char *outbuf = out_buffer;
2823                         size_t outlen = sizeof(out_buffer);
2825                         size_t ret;
2827                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2828                         if (ret != (size_t) -1)
2829                                 line = out_buffer;
2830                 }
2832                 if (!view->ops->read(view, line)) {
2833                         report("Allocation failure");
2834                         end_update(view, TRUE);
2835                         return FALSE;
2836                 }
2837         }
2839         {
2840                 unsigned long lines = view->lines;
2841                 int digits;
2843                 for (digits = 0; lines; digits++)
2844                         lines /= 10;
2846                 /* Keep the displayed view in sync with line number scaling. */
2847                 if (digits != view->digits) {
2848                         view->digits = digits;
2849                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2850                                 redraw = TRUE;
2851                 }
2852         }
2854         if (io_error(view->pipe)) {
2855                 report("Failed to read: %s", io_strerror(view->pipe));
2856                 end_update(view, TRUE);
2858         } else if (io_eof(view->pipe)) {
2859                 report("");
2860                 end_update(view, FALSE);
2861         }
2863         if (restore_view_position(view))
2864                 redraw = TRUE;
2866         if (!view_is_displayed(view))
2867                 return TRUE;
2869         if (redraw)
2870                 redraw_view_from(view, 0);
2871         else
2872                 redraw_view_dirty(view);
2874         /* Update the title _after_ the redraw so that if the redraw picks up a
2875          * commit reference in view->ref it'll be available here. */
2876         update_view_title(view);
2877         return TRUE;
2880 static struct line *
2881 add_line_data(struct view *view, void *data, enum line_type type)
2883         struct line *line;
2885         if (!realloc_lines(view, view->lines + 1))
2886                 return NULL;
2888         line = &view->line[view->lines++];
2889         memset(line, 0, sizeof(*line));
2890         line->type = type;
2891         line->data = data;
2892         line->dirty = 1;
2894         return line;
2897 static struct line *
2898 add_line_text(struct view *view, const char *text, enum line_type type)
2900         char *data = text ? strdup(text) : NULL;
2902         return data ? add_line_data(view, data, type) : NULL;
2905 static struct line *
2906 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2908         char buf[SIZEOF_STR];
2909         va_list args;
2911         va_start(args, fmt);
2912         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2913                 buf[0] = 0;
2914         va_end(args);
2916         return buf[0] ? add_line_text(view, buf, type) : NULL;
2919 /*
2920  * View opening
2921  */
2923 enum open_flags {
2924         OPEN_DEFAULT = 0,       /* Use default view switching. */
2925         OPEN_SPLIT = 1,         /* Split current view. */
2926         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2927         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2928         OPEN_PREPARED = 32,     /* Open already prepared command. */
2929 };
2931 static void
2932 open_view(struct view *prev, enum request request, enum open_flags flags)
2934         bool split = !!(flags & OPEN_SPLIT);
2935         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2936         bool nomaximize = !!(flags & OPEN_REFRESH);
2937         struct view *view = VIEW(request);
2938         int nviews = displayed_views();
2939         struct view *base_view = display[0];
2941         if (view == prev && nviews == 1 && !reload) {
2942                 report("Already in %s view", view->name);
2943                 return;
2944         }
2946         if (view->git_dir && !opt_git_dir[0]) {
2947                 report("The %s view is disabled in pager view", view->name);
2948                 return;
2949         }
2951         if (split) {
2952                 display[1] = view;
2953                 current_view = 1;
2954         } else if (!nomaximize) {
2955                 /* Maximize the current view. */
2956                 memset(display, 0, sizeof(display));
2957                 current_view = 0;
2958                 display[current_view] = view;
2959         }
2961         /* Resize the view when switching between split- and full-screen,
2962          * or when switching between two different full-screen views. */
2963         if (nviews != displayed_views() ||
2964             (nviews == 1 && base_view != display[0]))
2965                 resize_display();
2967         if (view->ops->open) {
2968                 if (view->pipe)
2969                         end_update(view, TRUE);
2970                 if (!view->ops->open(view)) {
2971                         report("Failed to load %s view", view->name);
2972                         return;
2973                 }
2974                 restore_view_position(view);
2976         } else if ((reload || strcmp(view->vid, view->id)) &&
2977                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2978                 report("Failed to load %s view", view->name);
2979                 return;
2980         }
2982         if (split && prev->lineno - prev->offset >= prev->height) {
2983                 /* Take the title line into account. */
2984                 int lines = prev->lineno - prev->offset - prev->height + 1;
2986                 /* Scroll the view that was split if the current line is
2987                  * outside the new limited view. */
2988                 do_scroll_view(prev, lines);
2989         }
2991         if (prev && view != prev) {
2992                 if (split) {
2993                         /* "Blur" the previous view. */
2994                         update_view_title(prev);
2995                 }
2997                 view->parent = prev;
2998         }
3000         if (view->pipe && view->lines == 0) {
3001                 /* Clear the old view and let the incremental updating refill
3002                  * the screen. */
3003                 werase(view->win);
3004                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3005                 report("");
3006         } else if (view_is_displayed(view)) {
3007                 redraw_view(view);
3008                 report("");
3009         }
3012 static void
3013 open_external_viewer(const char *argv[], const char *dir)
3015         def_prog_mode();           /* save current tty modes */
3016         endwin();                  /* restore original tty modes */
3017         run_io_fg(argv, dir);
3018         fprintf(stderr, "Press Enter to continue");
3019         getc(opt_tty);
3020         reset_prog_mode();
3021         redraw_display(TRUE);
3024 static void
3025 open_mergetool(const char *file)
3027         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3029         open_external_viewer(mergetool_argv, opt_cdup);
3032 static void
3033 open_editor(bool from_root, const char *file)
3035         const char *editor_argv[] = { "vi", file, NULL };
3036         const char *editor;
3038         editor = getenv("GIT_EDITOR");
3039         if (!editor && *opt_editor)
3040                 editor = opt_editor;
3041         if (!editor)
3042                 editor = getenv("VISUAL");
3043         if (!editor)
3044                 editor = getenv("EDITOR");
3045         if (!editor)
3046                 editor = "vi";
3048         editor_argv[0] = editor;
3049         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3052 static void
3053 open_run_request(enum request request)
3055         struct run_request *req = get_run_request(request);
3056         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3058         if (!req) {
3059                 report("Unknown run request");
3060                 return;
3061         }
3063         if (format_argv(argv, req->argv, FORMAT_ALL))
3064                 open_external_viewer(argv, NULL);
3065         free_argv(argv);
3068 /*
3069  * User request switch noodle
3070  */
3072 static int
3073 view_driver(struct view *view, enum request request)
3075         int i;
3077         if (request == REQ_NONE) {
3078                 doupdate();
3079                 return TRUE;
3080         }
3082         if (request > REQ_NONE) {
3083                 open_run_request(request);
3084                 /* FIXME: When all views can refresh always do this. */
3085                 if (view == VIEW(REQ_VIEW_STATUS) ||
3086                     view == VIEW(REQ_VIEW_MAIN) ||
3087                     view == VIEW(REQ_VIEW_LOG) ||
3088                     view == VIEW(REQ_VIEW_STAGE))
3089                         request = REQ_REFRESH;
3090                 else
3091                         return TRUE;
3092         }
3094         if (view && view->lines) {
3095                 request = view->ops->request(view, request, &view->line[view->lineno]);
3096                 if (request == REQ_NONE)
3097                         return TRUE;
3098         }
3100         switch (request) {
3101         case REQ_MOVE_UP:
3102         case REQ_MOVE_DOWN:
3103         case REQ_MOVE_PAGE_UP:
3104         case REQ_MOVE_PAGE_DOWN:
3105         case REQ_MOVE_FIRST_LINE:
3106         case REQ_MOVE_LAST_LINE:
3107                 move_view(view, request);
3108                 break;
3110         case REQ_SCROLL_LEFT:
3111         case REQ_SCROLL_RIGHT:
3112         case REQ_SCROLL_LINE_DOWN:
3113         case REQ_SCROLL_LINE_UP:
3114         case REQ_SCROLL_PAGE_DOWN:
3115         case REQ_SCROLL_PAGE_UP:
3116                 scroll_view(view, request);
3117                 break;
3119         case REQ_VIEW_BLAME:
3120                 if (!opt_file[0]) {
3121                         report("No file chosen, press %s to open tree view",
3122                                get_key(REQ_VIEW_TREE));
3123                         break;
3124                 }
3125                 open_view(view, request, OPEN_DEFAULT);
3126                 break;
3128         case REQ_VIEW_BLOB:
3129                 if (!ref_blob[0]) {
3130                         report("No file chosen, press %s to open tree view",
3131                                get_key(REQ_VIEW_TREE));
3132                         break;
3133                 }
3134                 open_view(view, request, OPEN_DEFAULT);
3135                 break;
3137         case REQ_VIEW_PAGER:
3138                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3139                         report("No pager content, press %s to run command from prompt",
3140                                get_key(REQ_PROMPT));
3141                         break;
3142                 }
3143                 open_view(view, request, OPEN_DEFAULT);
3144                 break;
3146         case REQ_VIEW_STAGE:
3147                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3148                         report("No stage content, press %s to open the status view and choose file",
3149                                get_key(REQ_VIEW_STATUS));
3150                         break;
3151                 }
3152                 open_view(view, request, OPEN_DEFAULT);
3153                 break;
3155         case REQ_VIEW_STATUS:
3156                 if (opt_is_inside_work_tree == FALSE) {
3157                         report("The status view requires a working tree");
3158                         break;
3159                 }
3160                 open_view(view, request, OPEN_DEFAULT);
3161                 break;
3163         case REQ_VIEW_MAIN:
3164         case REQ_VIEW_DIFF:
3165         case REQ_VIEW_LOG:
3166         case REQ_VIEW_TREE:
3167         case REQ_VIEW_HELP:
3168                 open_view(view, request, OPEN_DEFAULT);
3169                 break;
3171         case REQ_NEXT:
3172         case REQ_PREVIOUS:
3173                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3175                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3176                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3177                    (view == VIEW(REQ_VIEW_DIFF) &&
3178                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3179                    (view == VIEW(REQ_VIEW_STAGE) &&
3180                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3181                    (view == VIEW(REQ_VIEW_BLOB) &&
3182                      view->parent == VIEW(REQ_VIEW_TREE))) {
3183                         int line;
3185                         view = view->parent;
3186                         line = view->lineno;
3187                         move_view(view, request);
3188                         if (view_is_displayed(view))
3189                                 update_view_title(view);
3190                         if (line != view->lineno)
3191                                 view->ops->request(view, REQ_ENTER,
3192                                                    &view->line[view->lineno]);
3194                 } else {
3195                         move_view(view, request);
3196                 }
3197                 break;
3199         case REQ_VIEW_NEXT:
3200         {
3201                 int nviews = displayed_views();
3202                 int next_view = (current_view + 1) % nviews;
3204                 if (next_view == current_view) {
3205                         report("Only one view is displayed");
3206                         break;
3207                 }
3209                 current_view = next_view;
3210                 /* Blur out the title of the previous view. */
3211                 update_view_title(view);
3212                 report("");
3213                 break;
3214         }
3215         case REQ_REFRESH:
3216                 report("Refreshing is not yet supported for the %s view", view->name);
3217                 break;
3219         case REQ_MAXIMIZE:
3220                 if (displayed_views() == 2)
3221                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3222                 break;
3224         case REQ_TOGGLE_LINENO:
3225                 toggle_view_option(&opt_line_number, "line numbers");
3226                 break;
3228         case REQ_TOGGLE_DATE:
3229                 toggle_view_option(&opt_date, "date display");
3230                 break;
3232         case REQ_TOGGLE_AUTHOR:
3233                 toggle_view_option(&opt_author, "author display");
3234                 break;
3236         case REQ_TOGGLE_REV_GRAPH:
3237                 toggle_view_option(&opt_rev_graph, "revision graph display");
3238                 break;
3240         case REQ_TOGGLE_REFS:
3241                 toggle_view_option(&opt_show_refs, "reference display");
3242                 break;
3244         case REQ_SEARCH:
3245         case REQ_SEARCH_BACK:
3246                 search_view(view, request);
3247                 break;
3249         case REQ_FIND_NEXT:
3250         case REQ_FIND_PREV:
3251                 find_next(view, request);
3252                 break;
3254         case REQ_STOP_LOADING:
3255                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3256                         view = &views[i];
3257                         if (view->pipe)
3258                                 report("Stopped loading the %s view", view->name),
3259                         end_update(view, TRUE);
3260                 }
3261                 break;
3263         case REQ_SHOW_VERSION:
3264                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3265                 return TRUE;
3267         case REQ_SCREEN_REDRAW:
3268                 redraw_display(TRUE);
3269                 break;
3271         case REQ_EDIT:
3272                 report("Nothing to edit");
3273                 break;
3275         case REQ_ENTER:
3276                 report("Nothing to enter");
3277                 break;
3279         case REQ_VIEW_CLOSE:
3280                 /* XXX: Mark closed views by letting view->parent point to the
3281                  * view itself. Parents to closed view should never be
3282                  * followed. */
3283                 if (view->parent &&
3284                     view->parent->parent != view->parent) {
3285                         memset(display, 0, sizeof(display));
3286                         current_view = 0;
3287                         display[current_view] = view->parent;
3288                         view->parent = view;
3289                         resize_display();
3290                         redraw_display(FALSE);
3291                         report("");
3292                         break;
3293                 }
3294                 /* Fall-through */
3295         case REQ_QUIT:
3296                 return FALSE;
3298         default:
3299                 report("Unknown key, press 'h' for help");
3300                 return TRUE;
3301         }
3303         return TRUE;
3307 /*
3308  * View backend utilities
3309  */
3311 static void
3312 parse_timezone(time_t *time, const char *zone)
3314         long tz;
3316         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3317         tz += ('0' - zone[2]) * 60 * 60;
3318         tz += ('0' - zone[3]) * 60;
3319         tz += ('0' - zone[4]);
3321         if (zone[0] == '-')
3322                 tz = -tz;
3324         *time -= tz;
3327 /* Parse author lines where the name may be empty:
3328  *      author  <email@address.tld> 1138474660 +0100
3329  */
3330 static void
3331 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3333         char *nameend = strchr(ident, '<');
3334         char *emailend = strchr(ident, '>');
3336         if (nameend && emailend)
3337                 *nameend = *emailend = 0;
3338         ident = chomp_string(ident);
3339         if (!*ident) {
3340                 if (nameend)
3341                         ident = chomp_string(nameend + 1);
3342                 if (!*ident)
3343                         ident = "Unknown";
3344         }
3346         string_ncopy_do(author, authorsize, ident, strlen(ident));
3348         /* Parse epoch and timezone */
3349         if (emailend && emailend[1] == ' ') {
3350                 char *secs = emailend + 2;
3351                 char *zone = strchr(secs, ' ');
3352                 time_t time = (time_t) atol(secs);
3354                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3355                         parse_timezone(&time, zone + 1);
3357                 gmtime_r(&time, tm);
3358         }
3361 static enum input_status
3362 select_commit_parent_handler(void *data, char *buf, int c)
3364         size_t parents = *(size_t *) data;
3365         int parent = 0;
3367         if (!isdigit(c))
3368                 return INPUT_SKIP;
3370         if (*buf)
3371                 parent = atoi(buf) * 10;
3372         parent += c - '0';
3374         if (parent > parents)
3375                 return INPUT_SKIP;
3376         return INPUT_OK;
3379 static bool
3380 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3382         char buf[SIZEOF_STR * 4];
3383         const char *revlist_argv[] = {
3384                 "git", "rev-list", "-1", "--parents", id, NULL
3385         };
3386         int parents;
3388         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3389             !*chomp_string(buf) ||
3390             (parents = (strlen(buf) / 40) - 1) < 0) {
3391                 report("Failed to get parent information");
3392                 return FALSE;
3394         } else if (parents == 0) {
3395                 report("The selected commit has no parents");
3396                 return FALSE;
3397         }
3399         if (parents > 1) {
3400                 char prompt[SIZEOF_STR];
3401                 char *result;
3403                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3404                         return FALSE;
3405                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3406                 if (!result)
3407                         return FALSE;
3408                 parents = atoi(result);
3409         }
3411         string_copy_rev(rev, &buf[41 * parents]);
3412         return TRUE;
3415 /*
3416  * Pager backend
3417  */
3419 static bool
3420 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3422         char text[SIZEOF_STR];
3424         if (opt_line_number && draw_lineno(view, lineno))
3425                 return TRUE;
3427         string_expand(text, sizeof(text), line->data, opt_tab_size);
3428         draw_text(view, line->type, text, TRUE);
3429         return TRUE;
3432 static bool
3433 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3435         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3436         char refbuf[SIZEOF_STR];
3437         char *ref = NULL;
3439         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3440                 ref = chomp_string(refbuf);
3442         if (!ref || !*ref)
3443                 return TRUE;
3445         /* This is the only fatal call, since it can "corrupt" the buffer. */
3446         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3447                 return FALSE;
3449         return TRUE;
3452 static void
3453 add_pager_refs(struct view *view, struct line *line)
3455         char buf[SIZEOF_STR];
3456         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3457         struct ref **refs;
3458         size_t bufpos = 0, refpos = 0;
3459         const char *sep = "Refs: ";
3460         bool is_tag = FALSE;
3462         assert(line->type == LINE_COMMIT);
3464         refs = get_refs(commit_id);
3465         if (!refs) {
3466                 if (view == VIEW(REQ_VIEW_DIFF))
3467                         goto try_add_describe_ref;
3468                 return;
3469         }
3471         do {
3472                 struct ref *ref = refs[refpos];
3473                 const char *fmt = ref->tag    ? "%s[%s]" :
3474                                   ref->remote ? "%s<%s>" : "%s%s";
3476                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3477                         return;
3478                 sep = ", ";
3479                 if (ref->tag)
3480                         is_tag = TRUE;
3481         } while (refs[refpos++]->next);
3483         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3484 try_add_describe_ref:
3485                 /* Add <tag>-g<commit_id> "fake" reference. */
3486                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3487                         return;
3488         }
3490         if (bufpos == 0)
3491                 return;
3493         add_line_text(view, buf, LINE_PP_REFS);
3496 static bool
3497 pager_read(struct view *view, char *data)
3499         struct line *line;
3501         if (!data)
3502                 return TRUE;
3504         line = add_line_text(view, data, get_line_type(data));
3505         if (!line)
3506                 return FALSE;
3508         if (line->type == LINE_COMMIT &&
3509             (view == VIEW(REQ_VIEW_DIFF) ||
3510              view == VIEW(REQ_VIEW_LOG)))
3511                 add_pager_refs(view, line);
3513         return TRUE;
3516 static enum request
3517 pager_request(struct view *view, enum request request, struct line *line)
3519         int split = 0;
3521         if (request != REQ_ENTER)
3522                 return request;
3524         if (line->type == LINE_COMMIT &&
3525            (view == VIEW(REQ_VIEW_LOG) ||
3526             view == VIEW(REQ_VIEW_PAGER))) {
3527                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3528                 split = 1;
3529         }
3531         /* Always scroll the view even if it was split. That way
3532          * you can use Enter to scroll through the log view and
3533          * split open each commit diff. */
3534         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3536         /* FIXME: A minor workaround. Scrolling the view will call report("")
3537          * but if we are scrolling a non-current view this won't properly
3538          * update the view title. */
3539         if (split)
3540                 update_view_title(view);
3542         return REQ_NONE;
3545 static bool
3546 pager_grep(struct view *view, struct line *line)
3548         regmatch_t pmatch;
3549         char *text = line->data;
3551         if (!*text)
3552                 return FALSE;
3554         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3555                 return FALSE;
3557         return TRUE;
3560 static void
3561 pager_select(struct view *view, struct line *line)
3563         if (line->type == LINE_COMMIT) {
3564                 char *text = (char *)line->data + STRING_SIZE("commit ");
3566                 if (view != VIEW(REQ_VIEW_PAGER))
3567                         string_copy_rev(view->ref, text);
3568                 string_copy_rev(ref_commit, text);
3569         }
3572 static struct view_ops pager_ops = {
3573         "line",
3574         NULL,
3575         NULL,
3576         pager_read,
3577         pager_draw,
3578         pager_request,
3579         pager_grep,
3580         pager_select,
3581 };
3583 static const char *log_argv[SIZEOF_ARG] = {
3584         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3585 };
3587 static enum request
3588 log_request(struct view *view, enum request request, struct line *line)
3590         switch (request) {
3591         case REQ_REFRESH:
3592                 load_refs();
3593                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3594                 return REQ_NONE;
3595         default:
3596                 return pager_request(view, request, line);
3597         }
3600 static struct view_ops log_ops = {
3601         "line",
3602         log_argv,
3603         NULL,
3604         pager_read,
3605         pager_draw,
3606         log_request,
3607         pager_grep,
3608         pager_select,
3609 };
3611 static const char *diff_argv[SIZEOF_ARG] = {
3612         "git", "show", "--pretty=fuller", "--no-color", "--root",
3613                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3614 };
3616 static struct view_ops diff_ops = {
3617         "line",
3618         diff_argv,
3619         NULL,
3620         pager_read,
3621         pager_draw,
3622         pager_request,
3623         pager_grep,
3624         pager_select,
3625 };
3627 /*
3628  * Help backend
3629  */
3631 static bool
3632 help_open(struct view *view)
3634         char buf[SIZEOF_STR];
3635         size_t bufpos;
3636         int i;
3638         if (view->lines > 0)
3639                 return TRUE;
3641         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3643         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3644                 const char *key;
3646                 if (req_info[i].request == REQ_NONE)
3647                         continue;
3649                 if (!req_info[i].request) {
3650                         add_line_text(view, "", LINE_DEFAULT);
3651                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3652                         continue;
3653                 }
3655                 key = get_key(req_info[i].request);
3656                 if (!*key)
3657                         key = "(no key defined)";
3659                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3660                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3661                         if (buf[bufpos] == '_')
3662                                 buf[bufpos] = '-';
3663                 }
3665                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3666                                 key, buf, req_info[i].help);
3667         }
3669         if (run_requests) {
3670                 add_line_text(view, "", LINE_DEFAULT);
3671                 add_line_text(view, "External commands:", LINE_DEFAULT);
3672         }
3674         for (i = 0; i < run_requests; i++) {
3675                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3676                 const char *key;
3677                 int argc;
3679                 if (!req)
3680                         continue;
3682                 key = get_key_name(req->key);
3683                 if (!*key)
3684                         key = "(no key defined)";
3686                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3687                         if (!string_format_from(buf, &bufpos, "%s%s",
3688                                                 argc ? " " : "", req->argv[argc]))
3689                                 return REQ_NONE;
3691                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3692                                 keymap_table[req->keymap].name, key, buf);
3693         }
3695         return TRUE;
3698 static struct view_ops help_ops = {
3699         "line",
3700         NULL,
3701         help_open,
3702         NULL,
3703         pager_draw,
3704         pager_request,
3705         pager_grep,
3706         pager_select,
3707 };
3710 /*
3711  * Tree backend
3712  */
3714 struct tree_stack_entry {
3715         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3716         unsigned long lineno;           /* Line number to restore */
3717         char *name;                     /* Position of name in opt_path */
3718 };
3720 /* The top of the path stack. */
3721 static struct tree_stack_entry *tree_stack = NULL;
3722 unsigned long tree_lineno = 0;
3724 static void
3725 pop_tree_stack_entry(void)
3727         struct tree_stack_entry *entry = tree_stack;
3729         tree_lineno = entry->lineno;
3730         entry->name[0] = 0;
3731         tree_stack = entry->prev;
3732         free(entry);
3735 static void
3736 push_tree_stack_entry(const char *name, unsigned long lineno)
3738         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3739         size_t pathlen = strlen(opt_path);
3741         if (!entry)
3742                 return;
3744         entry->prev = tree_stack;
3745         entry->name = opt_path + pathlen;
3746         tree_stack = entry;
3748         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3749                 pop_tree_stack_entry();
3750                 return;
3751         }
3753         /* Move the current line to the first tree entry. */
3754         tree_lineno = 1;
3755         entry->lineno = lineno;
3758 /* Parse output from git-ls-tree(1):
3759  *
3760  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3761  */
3763 #define SIZEOF_TREE_ATTR \
3764         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3766 #define SIZEOF_TREE_MODE \
3767         STRING_SIZE("100644 ")
3769 #define TREE_ID_OFFSET \
3770         STRING_SIZE("100644 blob ")
3772 struct tree_entry {
3773         char id[SIZEOF_REV];
3774         mode_t mode;
3775         struct tm time;                 /* Date from the author ident. */
3776         char author[75];                /* Author of the commit. */
3777         char name[1];
3778 };
3780 static const char *
3781 tree_path(struct line *line)
3783         return ((struct tree_entry *) line->data)->name;
3787 static int
3788 tree_compare_entry(struct line *line1, struct line *line2)
3790         if (line1->type != line2->type)
3791                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3792         return strcmp(tree_path(line1), tree_path(line2));
3795 static struct line *
3796 tree_entry(struct view *view, enum line_type type, const char *path,
3797            const char *mode, const char *id)
3799         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3800         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3802         if (!entry || !line) {
3803                 free(entry);
3804                 return NULL;
3805         }
3807         strncpy(entry->name, path, strlen(path));
3808         if (mode)
3809                 entry->mode = strtoul(mode, NULL, 8);
3810         if (id)
3811                 string_copy_rev(entry->id, id);
3813         return line;
3816 static bool
3817 tree_read_date(struct view *view, char *text, bool *read_date)
3819         static char author_name[SIZEOF_STR];
3820         static struct tm author_time;
3822         if (!text && *read_date) {
3823                 *read_date = FALSE;
3824                 return TRUE;
3826         } else if (!text) {
3827                 char *path = *opt_path ? opt_path : ".";
3828                 /* Find next entry to process */
3829                 const char *log_file[] = {
3830                         "git", "log", "--no-color", "--pretty=raw",
3831                                 "--cc", "--raw", view->id, "--", path, NULL
3832                 };
3833                 struct io io = {};
3835                 if (!view->lines) {
3836                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3837                         report("Tree is empty");
3838                         return TRUE;
3839                 }
3841                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3842                         report("Failed to load tree data");
3843                         return TRUE;
3844                 }
3846                 done_io(view->pipe);
3847                 view->io = io;
3848                 *read_date = TRUE;
3849                 return FALSE;
3851         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3852                 parse_author_line(text + STRING_SIZE("author "),
3853                                   author_name, sizeof(author_name), &author_time);
3855         } else if (*text == ':') {
3856                 char *pos;
3857                 size_t annotated = 1;
3858                 size_t i;
3860                 pos = strchr(text, '\t');
3861                 if (!pos)
3862                         return TRUE;
3863                 text = pos + 1;
3864                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3865                         text += strlen(opt_prefix);
3866                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3867                         text += strlen(opt_path);
3868                 pos = strchr(text, '/');
3869                 if (pos)
3870                         *pos = 0;
3872                 for (i = 1; i < view->lines; i++) {
3873                         struct line *line = &view->line[i];
3874                         struct tree_entry *entry = line->data;
3876                         annotated += !!*entry->author;
3877                         if (*entry->author || strcmp(entry->name, text))
3878                                 continue;
3880                         string_copy(entry->author, author_name);
3881                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3882                         line->dirty = 1;
3883                         break;
3884                 }
3886                 if (annotated == view->lines)
3887                         kill_io(view->pipe);
3888         }
3889         return TRUE;
3892 static bool
3893 tree_read(struct view *view, char *text)
3895         static bool read_date = FALSE;
3896         struct tree_entry *data;
3897         struct line *entry, *line;
3898         enum line_type type;
3899         size_t textlen = text ? strlen(text) : 0;
3900         char *path = text + SIZEOF_TREE_ATTR;
3902         if (read_date || !text)
3903                 return tree_read_date(view, text, &read_date);
3905         if (textlen <= SIZEOF_TREE_ATTR)
3906                 return FALSE;
3907         if (view->lines == 0 &&
3908             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3909                 return FALSE;
3911         /* Strip the path part ... */
3912         if (*opt_path) {
3913                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3914                 size_t striplen = strlen(opt_path);
3916                 if (pathlen > striplen)
3917                         memmove(path, path + striplen,
3918                                 pathlen - striplen + 1);
3920                 /* Insert "link" to parent directory. */
3921                 if (view->lines == 1 &&
3922                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3923                         return FALSE;
3924         }
3926         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3927         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3928         if (!entry)
3929                 return FALSE;
3930         data = entry->data;
3932         /* Skip "Directory ..." and ".." line. */
3933         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3934                 if (tree_compare_entry(line, entry) <= 0)
3935                         continue;
3937                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3939                 line->data = data;
3940                 line->type = type;
3941                 for (; line <= entry; line++)
3942                         line->dirty = line->cleareol = 1;
3943                 return TRUE;
3944         }
3946         if (tree_lineno > view->lineno) {
3947                 view->lineno = tree_lineno;
3948                 tree_lineno = 0;
3949         }
3951         return TRUE;
3954 static bool
3955 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3957         struct tree_entry *entry = line->data;
3959         if (line->type == LINE_TREE_HEAD) {
3960                 if (draw_text(view, line->type, "Directory path /", TRUE))
3961                         return TRUE;
3962         } else {
3963                 if (draw_mode(view, entry->mode))
3964                         return TRUE;
3966                 if (opt_author && draw_author(view, entry->author))
3967                         return TRUE;
3969                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3970                         return TRUE;
3971         }
3972         if (draw_text(view, line->type, entry->name, TRUE))
3973                 return TRUE;
3974         return TRUE;
3977 static void
3978 open_blob_editor()
3980         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3981         int fd = mkstemp(file);
3983         if (fd == -1)
3984                 report("Failed to create temporary file");
3985         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3986                 report("Failed to save blob data to file");
3987         else
3988                 open_editor(FALSE, file);
3989         if (fd != -1)
3990                 unlink(file);
3993 static enum request
3994 tree_request(struct view *view, enum request request, struct line *line)
3996         enum open_flags flags;
3998         switch (request) {
3999         case REQ_VIEW_BLAME:
4000                 if (line->type != LINE_TREE_FILE) {
4001                         report("Blame only supported for files");
4002                         return REQ_NONE;
4003                 }
4005                 string_copy(opt_ref, view->vid);
4006                 return request;
4008         case REQ_EDIT:
4009                 if (line->type != LINE_TREE_FILE) {
4010                         report("Edit only supported for files");
4011                 } else if (!is_head_commit(view->vid)) {
4012                         open_blob_editor();
4013                 } else {
4014                         open_editor(TRUE, opt_file);
4015                 }
4016                 return REQ_NONE;
4018         case REQ_PARENT:
4019                 if (!*opt_path) {
4020                         /* quit view if at top of tree */
4021                         return REQ_VIEW_CLOSE;
4022                 }
4023                 /* fake 'cd  ..' */
4024                 line = &view->line[1];
4025                 break;
4027         case REQ_ENTER:
4028                 break;
4030         default:
4031                 return request;
4032         }
4034         /* Cleanup the stack if the tree view is at a different tree. */
4035         while (!*opt_path && tree_stack)
4036                 pop_tree_stack_entry();
4038         switch (line->type) {
4039         case LINE_TREE_DIR:
4040                 /* Depending on whether it is a subdirectory or parent link
4041                  * mangle the path buffer. */
4042                 if (line == &view->line[1] && *opt_path) {
4043                         pop_tree_stack_entry();
4045                 } else {
4046                         const char *basename = tree_path(line);
4048                         push_tree_stack_entry(basename, view->lineno);
4049                 }
4051                 /* Trees and subtrees share the same ID, so they are not not
4052                  * unique like blobs. */
4053                 flags = OPEN_RELOAD;
4054                 request = REQ_VIEW_TREE;
4055                 break;
4057         case LINE_TREE_FILE:
4058                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4059                 request = REQ_VIEW_BLOB;
4060                 break;
4062         default:
4063                 return REQ_NONE;
4064         }
4066         open_view(view, request, flags);
4067         if (request == REQ_VIEW_TREE)
4068                 view->lineno = tree_lineno;
4070         return REQ_NONE;
4073 static void
4074 tree_select(struct view *view, struct line *line)
4076         struct tree_entry *entry = line->data;
4078         if (line->type == LINE_TREE_FILE) {
4079                 string_copy_rev(ref_blob, entry->id);
4080                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4082         } else if (line->type != LINE_TREE_DIR) {
4083                 return;
4084         }
4086         string_copy_rev(view->ref, entry->id);
4089 static const char *tree_argv[SIZEOF_ARG] = {
4090         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4091 };
4093 static struct view_ops tree_ops = {
4094         "file",
4095         tree_argv,
4096         NULL,
4097         tree_read,
4098         tree_draw,
4099         tree_request,
4100         pager_grep,
4101         tree_select,
4102 };
4104 static bool
4105 blob_read(struct view *view, char *line)
4107         if (!line)
4108                 return TRUE;
4109         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4112 static enum request
4113 blob_request(struct view *view, enum request request, struct line *line)
4115         switch (request) {
4116         case REQ_EDIT:
4117                 open_blob_editor();
4118                 return REQ_NONE;
4119         default:
4120                 return pager_request(view, request, line);
4121         }
4124 static const char *blob_argv[SIZEOF_ARG] = {
4125         "git", "cat-file", "blob", "%(blob)", NULL
4126 };
4128 static struct view_ops blob_ops = {
4129         "line",
4130         blob_argv,
4131         NULL,
4132         blob_read,
4133         pager_draw,
4134         blob_request,
4135         pager_grep,
4136         pager_select,
4137 };
4139 /*
4140  * Blame backend
4141  *
4142  * Loading the blame view is a two phase job:
4143  *
4144  *  1. File content is read either using opt_file from the
4145  *     filesystem or using git-cat-file.
4146  *  2. Then blame information is incrementally added by
4147  *     reading output from git-blame.
4148  */
4150 static const char *blame_head_argv[] = {
4151         "git", "blame", "--incremental", "--", "%(file)", NULL
4152 };
4154 static const char *blame_ref_argv[] = {
4155         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4156 };
4158 static const char *blame_cat_file_argv[] = {
4159         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4160 };
4162 struct blame_commit {
4163         char id[SIZEOF_REV];            /* SHA1 ID. */
4164         char title[128];                /* First line of the commit message. */
4165         char author[75];                /* Author of the commit. */
4166         struct tm time;                 /* Date from the author ident. */
4167         char filename[128];             /* Name of file. */
4168         bool has_previous;              /* Was a "previous" line detected. */
4169 };
4171 struct blame {
4172         struct blame_commit *commit;
4173         char text[1];
4174 };
4176 static bool
4177 blame_open(struct view *view)
4179         if (*opt_ref || !io_open(&view->io, opt_file)) {
4180                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4181                         return FALSE;
4182         }
4184         setup_update(view, opt_file);
4185         string_format(view->ref, "%s ...", opt_file);
4187         return TRUE;
4190 static struct blame_commit *
4191 get_blame_commit(struct view *view, const char *id)
4193         size_t i;
4195         for (i = 0; i < view->lines; i++) {
4196                 struct blame *blame = view->line[i].data;
4198                 if (!blame->commit)
4199                         continue;
4201                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4202                         return blame->commit;
4203         }
4205         {
4206                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4208                 if (commit)
4209                         string_ncopy(commit->id, id, SIZEOF_REV);
4210                 return commit;
4211         }
4214 static bool
4215 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4217         const char *pos = *posref;
4219         *posref = NULL;
4220         pos = strchr(pos + 1, ' ');
4221         if (!pos || !isdigit(pos[1]))
4222                 return FALSE;
4223         *number = atoi(pos + 1);
4224         if (*number < min || *number > max)
4225                 return FALSE;
4227         *posref = pos;
4228         return TRUE;
4231 static struct blame_commit *
4232 parse_blame_commit(struct view *view, const char *text, int *blamed)
4234         struct blame_commit *commit;
4235         struct blame *blame;
4236         const char *pos = text + SIZEOF_REV - 1;
4237         size_t lineno;
4238         size_t group;
4240         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4241                 return NULL;
4243         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4244             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4245                 return NULL;
4247         commit = get_blame_commit(view, text);
4248         if (!commit)
4249                 return NULL;
4251         *blamed += group;
4252         while (group--) {
4253                 struct line *line = &view->line[lineno + group - 1];
4255                 blame = line->data;
4256                 blame->commit = commit;
4257                 line->dirty = 1;
4258         }
4260         return commit;
4263 static bool
4264 blame_read_file(struct view *view, const char *line, bool *read_file)
4266         if (!line) {
4267                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4268                 struct io io = {};
4270                 if (view->lines == 0 && !view->parent)
4271                         die("No blame exist for %s", view->vid);
4273                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4274                         report("Failed to load blame data");
4275                         return TRUE;
4276                 }
4278                 done_io(view->pipe);
4279                 view->io = io;
4280                 *read_file = FALSE;
4281                 return FALSE;
4283         } else {
4284                 size_t linelen = string_expand_length(line, opt_tab_size);
4285                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4287                 if (!blame)
4288                         return FALSE;
4290                 blame->commit = NULL;
4291                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4292                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4293         }
4296 static bool
4297 match_blame_header(const char *name, char **line)
4299         size_t namelen = strlen(name);
4300         bool matched = !strncmp(name, *line, namelen);
4302         if (matched)
4303                 *line += namelen;
4305         return matched;
4308 static bool
4309 blame_read(struct view *view, char *line)
4311         static struct blame_commit *commit = NULL;
4312         static int blamed = 0;
4313         static time_t author_time;
4314         static bool read_file = TRUE;
4316         if (read_file)
4317                 return blame_read_file(view, line, &read_file);
4319         if (!line) {
4320                 /* Reset all! */
4321                 commit = NULL;
4322                 blamed = 0;
4323                 read_file = TRUE;
4324                 string_format(view->ref, "%s", view->vid);
4325                 if (view_is_displayed(view)) {
4326                         update_view_title(view);
4327                         redraw_view_from(view, 0);
4328                 }
4329                 return TRUE;
4330         }
4332         if (!commit) {
4333                 commit = parse_blame_commit(view, line, &blamed);
4334                 string_format(view->ref, "%s %2d%%", view->vid,
4335                               view->lines ? blamed * 100 / view->lines : 0);
4337         } else if (match_blame_header("author ", &line)) {
4338                 string_ncopy(commit->author, line, strlen(line));
4340         } else if (match_blame_header("author-time ", &line)) {
4341                 author_time = (time_t) atol(line);
4343         } else if (match_blame_header("author-tz ", &line)) {
4344                 parse_timezone(&author_time, line);
4345                 gmtime_r(&author_time, &commit->time);
4347         } else if (match_blame_header("summary ", &line)) {
4348                 string_ncopy(commit->title, line, strlen(line));
4350         } else if (match_blame_header("previous ", &line)) {
4351                 commit->has_previous = TRUE;
4353         } else if (match_blame_header("filename ", &line)) {
4354                 string_ncopy(commit->filename, line, strlen(line));
4355                 commit = NULL;
4356         }
4358         return TRUE;
4361 static bool
4362 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4364         struct blame *blame = line->data;
4365         struct tm *time = NULL;
4366         const char *id = NULL, *author = NULL;
4368         if (blame->commit && *blame->commit->filename) {
4369                 id = blame->commit->id;
4370                 author = blame->commit->author;
4371                 time = &blame->commit->time;
4372         }
4374         if (opt_date && draw_date(view, time))
4375                 return TRUE;
4377         if (opt_author && draw_author(view, author))
4378                 return TRUE;
4380         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4381                 return TRUE;
4383         if (draw_lineno(view, lineno))
4384                 return TRUE;
4386         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4387         return TRUE;
4390 static bool
4391 check_blame_commit(struct blame *blame)
4393         if (!blame->commit)
4394                 report("Commit data not loaded yet");
4395         else if (!strcmp(blame->commit->id, NULL_ID))
4396                 report("No commit exist for the selected line");
4397         else
4398                 return TRUE;
4399         return FALSE;
4402 static enum request
4403 blame_request(struct view *view, enum request request, struct line *line)
4405         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4406         struct blame *blame = line->data;
4408         switch (request) {
4409         case REQ_VIEW_BLAME:
4410                 if (check_blame_commit(blame)) {
4411                         string_copy(opt_ref, blame->commit->id);
4412                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4413                 }
4414                 break;
4416         case REQ_PARENT:
4417                 if (check_blame_commit(blame) &&
4418                     select_commit_parent(blame->commit->id, opt_ref))
4419                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4420                 break;
4422         case REQ_ENTER:
4423                 if (!blame->commit) {
4424                         report("No commit loaded yet");
4425                         break;
4426                 }
4428                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4429                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4430                         break;
4432                 if (!strcmp(blame->commit->id, NULL_ID)) {
4433                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4434                         const char *diff_index_argv[] = {
4435                                 "git", "diff-index", "--root", "--patch-with-stat",
4436                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4437                         };
4439                         if (!blame->commit->has_previous) {
4440                                 diff_index_argv[1] = "diff";
4441                                 diff_index_argv[2] = "--no-color";
4442                                 diff_index_argv[6] = "--";
4443                                 diff_index_argv[7] = "/dev/null";
4444                         }
4446                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4447                                 report("Failed to allocate diff command");
4448                                 break;
4449                         }
4450                         flags |= OPEN_PREPARED;
4451                 }
4453                 open_view(view, REQ_VIEW_DIFF, flags);
4454                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4455                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4456                 break;
4458         default:
4459                 return request;
4460         }
4462         return REQ_NONE;
4465 static bool
4466 blame_grep(struct view *view, struct line *line)
4468         struct blame *blame = line->data;
4469         struct blame_commit *commit = blame->commit;
4470         regmatch_t pmatch;
4472 #define MATCH(text, on)                                                 \
4473         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4475         if (commit) {
4476                 char buf[DATE_COLS + 1];
4478                 if (MATCH(commit->title, 1) ||
4479                     MATCH(commit->author, opt_author) ||
4480                     MATCH(commit->id, opt_date))
4481                         return TRUE;
4483                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4484                     MATCH(buf, 1))
4485                         return TRUE;
4486         }
4488         return MATCH(blame->text, 1);
4490 #undef MATCH
4493 static void
4494 blame_select(struct view *view, struct line *line)
4496         struct blame *blame = line->data;
4497         struct blame_commit *commit = blame->commit;
4499         if (!commit)
4500                 return;
4502         if (!strcmp(commit->id, NULL_ID))
4503                 string_ncopy(ref_commit, "HEAD", 4);
4504         else
4505                 string_copy_rev(ref_commit, commit->id);
4508 static struct view_ops blame_ops = {
4509         "line",
4510         NULL,
4511         blame_open,
4512         blame_read,
4513         blame_draw,
4514         blame_request,
4515         blame_grep,
4516         blame_select,
4517 };
4519 /*
4520  * Status backend
4521  */
4523 struct status {
4524         char status;
4525         struct {
4526                 mode_t mode;
4527                 char rev[SIZEOF_REV];
4528                 char name[SIZEOF_STR];
4529         } old;
4530         struct {
4531                 mode_t mode;
4532                 char rev[SIZEOF_REV];
4533                 char name[SIZEOF_STR];
4534         } new;
4535 };
4537 static char status_onbranch[SIZEOF_STR];
4538 static struct status stage_status;
4539 static enum line_type stage_line_type;
4540 static size_t stage_chunks;
4541 static int *stage_chunk;
4543 /* This should work even for the "On branch" line. */
4544 static inline bool
4545 status_has_none(struct view *view, struct line *line)
4547         return line < view->line + view->lines && !line[1].data;
4550 /* Get fields from the diff line:
4551  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4552  */
4553 static inline bool
4554 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4556         const char *old_mode = buf +  1;
4557         const char *new_mode = buf +  8;
4558         const char *old_rev  = buf + 15;
4559         const char *new_rev  = buf + 56;
4560         const char *status   = buf + 97;
4562         if (bufsize < 98 ||
4563             old_mode[-1] != ':' ||
4564             new_mode[-1] != ' ' ||
4565             old_rev[-1]  != ' ' ||
4566             new_rev[-1]  != ' ' ||
4567             status[-1]   != ' ')
4568                 return FALSE;
4570         file->status = *status;
4572         string_copy_rev(file->old.rev, old_rev);
4573         string_copy_rev(file->new.rev, new_rev);
4575         file->old.mode = strtoul(old_mode, NULL, 8);
4576         file->new.mode = strtoul(new_mode, NULL, 8);
4578         file->old.name[0] = file->new.name[0] = 0;
4580         return TRUE;
4583 static bool
4584 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4586         struct status *unmerged = NULL;
4587         char *buf;
4588         struct io io = {};
4590         if (!run_io(&io, argv, NULL, IO_RD))
4591                 return FALSE;
4593         add_line_data(view, NULL, type);
4595         while ((buf = io_get(&io, 0, TRUE))) {
4596                 struct status *file = unmerged;
4598                 if (!file) {
4599                         file = calloc(1, sizeof(*file));
4600                         if (!file || !add_line_data(view, file, type))
4601                                 goto error_out;
4602                 }
4604                 /* Parse diff info part. */
4605                 if (status) {
4606                         file->status = status;
4607                         if (status == 'A')
4608                                 string_copy(file->old.rev, NULL_ID);
4610                 } else if (!file->status || file == unmerged) {
4611                         if (!status_get_diff(file, buf, strlen(buf)))
4612                                 goto error_out;
4614                         buf = io_get(&io, 0, TRUE);
4615                         if (!buf)
4616                                 break;
4618                         /* Collapse all modified entries that follow an
4619                          * associated unmerged entry. */
4620                         if (unmerged == file) {
4621                                 unmerged->status = 'U';
4622                                 unmerged = NULL;
4623                         } else if (file->status == 'U') {
4624                                 unmerged = file;
4625                         }
4626                 }
4628                 /* Grab the old name for rename/copy. */
4629                 if (!*file->old.name &&
4630                     (file->status == 'R' || file->status == 'C')) {
4631                         string_ncopy(file->old.name, buf, strlen(buf));
4633                         buf = io_get(&io, 0, TRUE);
4634                         if (!buf)
4635                                 break;
4636                 }
4638                 /* git-ls-files just delivers a NUL separated list of
4639                  * file names similar to the second half of the
4640                  * git-diff-* output. */
4641                 string_ncopy(file->new.name, buf, strlen(buf));
4642                 if (!*file->old.name)
4643                         string_copy(file->old.name, file->new.name);
4644                 file = NULL;
4645         }
4647         if (io_error(&io)) {
4648 error_out:
4649                 done_io(&io);
4650                 return FALSE;
4651         }
4653         if (!view->line[view->lines - 1].data)
4654                 add_line_data(view, NULL, LINE_STAT_NONE);
4656         done_io(&io);
4657         return TRUE;
4660 /* Don't show unmerged entries in the staged section. */
4661 static const char *status_diff_index_argv[] = {
4662         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4663                              "--cached", "-M", "HEAD", NULL
4664 };
4666 static const char *status_diff_files_argv[] = {
4667         "git", "diff-files", "-z", NULL
4668 };
4670 static const char *status_list_other_argv[] = {
4671         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4672 };
4674 static const char *status_list_no_head_argv[] = {
4675         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4676 };
4678 static const char *update_index_argv[] = {
4679         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4680 };
4682 /* Restore the previous line number to stay in the context or select a
4683  * line with something that can be updated. */
4684 static void
4685 status_restore(struct view *view)
4687         if (view->p_lineno >= view->lines)
4688                 view->p_lineno = view->lines - 1;
4689         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4690                 view->p_lineno++;
4691         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4692                 view->p_lineno--;
4694         /* If the above fails, always skip the "On branch" line. */
4695         if (view->p_lineno < view->lines)
4696                 view->lineno = view->p_lineno;
4697         else
4698                 view->lineno = 1;
4700         if (view->lineno < view->offset)
4701                 view->offset = view->lineno;
4702         else if (view->offset + view->height <= view->lineno)
4703                 view->offset = view->lineno - view->height + 1;
4705         view->p_restore = FALSE;
4708 /* First parse staged info using git-diff-index(1), then parse unstaged
4709  * info using git-diff-files(1), and finally untracked files using
4710  * git-ls-files(1). */
4711 static bool
4712 status_open(struct view *view)
4714         reset_view(view);
4716         add_line_data(view, NULL, LINE_STAT_HEAD);
4717         if (is_initial_commit())
4718                 string_copy(status_onbranch, "Initial commit");
4719         else if (!*opt_head)
4720                 string_copy(status_onbranch, "Not currently on any branch");
4721         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4722                 return FALSE;
4724         run_io_bg(update_index_argv);
4726         if (is_initial_commit()) {
4727                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4728                         return FALSE;
4729         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4730                 return FALSE;
4731         }
4733         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4734             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4735                 return FALSE;
4737         /* Restore the exact position or use the specialized restore
4738          * mode? */
4739         if (!view->p_restore)
4740                 status_restore(view);
4741         return TRUE;
4744 static bool
4745 status_draw(struct view *view, struct line *line, unsigned int lineno)
4747         struct status *status = line->data;
4748         enum line_type type;
4749         const char *text;
4751         if (!status) {
4752                 switch (line->type) {
4753                 case LINE_STAT_STAGED:
4754                         type = LINE_STAT_SECTION;
4755                         text = "Changes to be committed:";
4756                         break;
4758                 case LINE_STAT_UNSTAGED:
4759                         type = LINE_STAT_SECTION;
4760                         text = "Changed but not updated:";
4761                         break;
4763                 case LINE_STAT_UNTRACKED:
4764                         type = LINE_STAT_SECTION;
4765                         text = "Untracked files:";
4766                         break;
4768                 case LINE_STAT_NONE:
4769                         type = LINE_DEFAULT;
4770                         text = "  (no files)";
4771                         break;
4773                 case LINE_STAT_HEAD:
4774                         type = LINE_STAT_HEAD;
4775                         text = status_onbranch;
4776                         break;
4778                 default:
4779                         return FALSE;
4780                 }
4781         } else {
4782                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4784                 buf[0] = status->status;
4785                 if (draw_text(view, line->type, buf, TRUE))
4786                         return TRUE;
4787                 type = LINE_DEFAULT;
4788                 text = status->new.name;
4789         }
4791         draw_text(view, type, text, TRUE);
4792         return TRUE;
4795 static enum request
4796 status_enter(struct view *view, struct line *line)
4798         struct status *status = line->data;
4799         const char *oldpath = status ? status->old.name : NULL;
4800         /* Diffs for unmerged entries are empty when passing the new
4801          * path, so leave it empty. */
4802         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4803         const char *info;
4804         enum open_flags split;
4805         struct view *stage = VIEW(REQ_VIEW_STAGE);
4807         if (line->type == LINE_STAT_NONE ||
4808             (!status && line[1].type == LINE_STAT_NONE)) {
4809                 report("No file to diff");
4810                 return REQ_NONE;
4811         }
4813         switch (line->type) {
4814         case LINE_STAT_STAGED:
4815                 if (is_initial_commit()) {
4816                         const char *no_head_diff_argv[] = {
4817                                 "git", "diff", "--no-color", "--patch-with-stat",
4818                                         "--", "/dev/null", newpath, NULL
4819                         };
4821                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4822                                 return REQ_QUIT;
4823                 } else {
4824                         const char *index_show_argv[] = {
4825                                 "git", "diff-index", "--root", "--patch-with-stat",
4826                                         "-C", "-M", "--cached", "HEAD", "--",
4827                                         oldpath, newpath, NULL
4828                         };
4830                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4831                                 return REQ_QUIT;
4832                 }
4834                 if (status)
4835                         info = "Staged changes to %s";
4836                 else
4837                         info = "Staged changes";
4838                 break;
4840         case LINE_STAT_UNSTAGED:
4841         {
4842                 const char *files_show_argv[] = {
4843                         "git", "diff-files", "--root", "--patch-with-stat",
4844                                 "-C", "-M", "--", oldpath, newpath, NULL
4845                 };
4847                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4848                         return REQ_QUIT;
4849                 if (status)
4850                         info = "Unstaged changes to %s";
4851                 else
4852                         info = "Unstaged changes";
4853                 break;
4854         }
4855         case LINE_STAT_UNTRACKED:
4856                 if (!newpath) {
4857                         report("No file to show");
4858                         return REQ_NONE;
4859                 }
4861                 if (!suffixcmp(status->new.name, -1, "/")) {
4862                         report("Cannot display a directory");
4863                         return REQ_NONE;
4864                 }
4866                 if (!prepare_update_file(stage, newpath))
4867                         return REQ_QUIT;
4868                 info = "Untracked file %s";
4869                 break;
4871         case LINE_STAT_HEAD:
4872                 return REQ_NONE;
4874         default:
4875                 die("line type %d not handled in switch", line->type);
4876         }
4878         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4879         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4880         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4881                 if (status) {
4882                         stage_status = *status;
4883                 } else {
4884                         memset(&stage_status, 0, sizeof(stage_status));
4885                 }
4887                 stage_line_type = line->type;
4888                 stage_chunks = 0;
4889                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4890         }
4892         return REQ_NONE;
4895 static bool
4896 status_exists(struct status *status, enum line_type type)
4898         struct view *view = VIEW(REQ_VIEW_STATUS);
4899         unsigned long lineno;
4901         for (lineno = 0; lineno < view->lines; lineno++) {
4902                 struct line *line = &view->line[lineno];
4903                 struct status *pos = line->data;
4905                 if (line->type != type)
4906                         continue;
4907                 if (!pos && (!status || !status->status) && line[1].data) {
4908                         select_view_line(view, lineno);
4909                         return TRUE;
4910                 }
4911                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4912                         select_view_line(view, lineno);
4913                         return TRUE;
4914                 }
4915         }
4917         return FALSE;
4921 static bool
4922 status_update_prepare(struct io *io, enum line_type type)
4924         const char *staged_argv[] = {
4925                 "git", "update-index", "-z", "--index-info", NULL
4926         };
4927         const char *others_argv[] = {
4928                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4929         };
4931         switch (type) {
4932         case LINE_STAT_STAGED:
4933                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4935         case LINE_STAT_UNSTAGED:
4936                 return run_io(io, others_argv, opt_cdup, IO_WR);
4938         case LINE_STAT_UNTRACKED:
4939                 return run_io(io, others_argv, NULL, IO_WR);
4941         default:
4942                 die("line type %d not handled in switch", type);
4943                 return FALSE;
4944         }
4947 static bool
4948 status_update_write(struct io *io, struct status *status, enum line_type type)
4950         char buf[SIZEOF_STR];
4951         size_t bufsize = 0;
4953         switch (type) {
4954         case LINE_STAT_STAGED:
4955                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4956                                         status->old.mode,
4957                                         status->old.rev,
4958                                         status->old.name, 0))
4959                         return FALSE;
4960                 break;
4962         case LINE_STAT_UNSTAGED:
4963         case LINE_STAT_UNTRACKED:
4964                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4965                         return FALSE;
4966                 break;
4968         default:
4969                 die("line type %d not handled in switch", type);
4970         }
4972         return io_write(io, buf, bufsize);
4975 static bool
4976 status_update_file(struct status *status, enum line_type type)
4978         struct io io = {};
4979         bool result;
4981         if (!status_update_prepare(&io, type))
4982                 return FALSE;
4984         result = status_update_write(&io, status, type);
4985         done_io(&io);
4986         return result;
4989 static bool
4990 status_update_files(struct view *view, struct line *line)
4992         struct io io = {};
4993         bool result = TRUE;
4994         struct line *pos = view->line + view->lines;
4995         int files = 0;
4996         int file, done;
4998         if (!status_update_prepare(&io, line->type))
4999                 return FALSE;
5001         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5002                 files++;
5004         for (file = 0, done = 0; result && file < files; line++, file++) {
5005                 int almost_done = file * 100 / files;
5007                 if (almost_done > done) {
5008                         done = almost_done;
5009                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5010                                       file, files, done);
5011                         update_view_title(view);
5012                 }
5013                 result = status_update_write(&io, line->data, line->type);
5014         }
5016         done_io(&io);
5017         return result;
5020 static bool
5021 status_update(struct view *view)
5023         struct line *line = &view->line[view->lineno];
5025         assert(view->lines);
5027         if (!line->data) {
5028                 /* This should work even for the "On branch" line. */
5029                 if (line < view->line + view->lines && !line[1].data) {
5030                         report("Nothing to update");
5031                         return FALSE;
5032                 }
5034                 if (!status_update_files(view, line + 1)) {
5035                         report("Failed to update file status");
5036                         return FALSE;
5037                 }
5039         } else if (!status_update_file(line->data, line->type)) {
5040                 report("Failed to update file status");
5041                 return FALSE;
5042         }
5044         return TRUE;
5047 static bool
5048 status_revert(struct status *status, enum line_type type, bool has_none)
5050         if (!status || type != LINE_STAT_UNSTAGED) {
5051                 if (type == LINE_STAT_STAGED) {
5052                         report("Cannot revert changes to staged files");
5053                 } else if (type == LINE_STAT_UNTRACKED) {
5054                         report("Cannot revert changes to untracked files");
5055                 } else if (has_none) {
5056                         report("Nothing to revert");
5057                 } else {
5058                         report("Cannot revert changes to multiple files");
5059                 }
5060                 return FALSE;
5062         } else {
5063                 char mode[10] = "100644";
5064                 const char *reset_argv[] = {
5065                         "git", "update-index", "--cacheinfo", mode,
5066                                 status->old.rev, status->old.name, NULL
5067                 };
5068                 const char *checkout_argv[] = {
5069                         "git", "checkout", "--", status->old.name, NULL
5070                 };
5072                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5073                         return FALSE;
5074                 string_format(mode, "%o", status->old.mode);
5075                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5076                         run_io_fg(checkout_argv, opt_cdup);
5077         }
5080 static enum request
5081 status_request(struct view *view, enum request request, struct line *line)
5083         struct status *status = line->data;
5085         switch (request) {
5086         case REQ_STATUS_UPDATE:
5087                 if (!status_update(view))
5088                         return REQ_NONE;
5089                 break;
5091         case REQ_STATUS_REVERT:
5092                 if (!status_revert(status, line->type, status_has_none(view, line)))
5093                         return REQ_NONE;
5094                 break;
5096         case REQ_STATUS_MERGE:
5097                 if (!status || status->status != 'U') {
5098                         report("Merging only possible for files with unmerged status ('U').");
5099                         return REQ_NONE;
5100                 }
5101                 open_mergetool(status->new.name);
5102                 break;
5104         case REQ_EDIT:
5105                 if (!status)
5106                         return request;
5107                 if (status->status == 'D') {
5108                         report("File has been deleted.");
5109                         return REQ_NONE;
5110                 }
5112                 open_editor(status->status != '?', status->new.name);
5113                 break;
5115         case REQ_VIEW_BLAME:
5116                 if (status) {
5117                         string_copy(opt_file, status->new.name);
5118                         opt_ref[0] = 0;
5119                 }
5120                 return request;
5122         case REQ_ENTER:
5123                 /* After returning the status view has been split to
5124                  * show the stage view. No further reloading is
5125                  * necessary. */
5126                 status_enter(view, line);
5127                 return REQ_NONE;
5129         case REQ_REFRESH:
5130                 /* Simply reload the view. */
5131                 break;
5133         default:
5134                 return request;
5135         }
5137         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5139         return REQ_NONE;
5142 static void
5143 status_select(struct view *view, struct line *line)
5145         struct status *status = line->data;
5146         char file[SIZEOF_STR] = "all files";
5147         const char *text;
5148         const char *key;
5150         if (status && !string_format(file, "'%s'", status->new.name))
5151                 return;
5153         if (!status && line[1].type == LINE_STAT_NONE)
5154                 line++;
5156         switch (line->type) {
5157         case LINE_STAT_STAGED:
5158                 text = "Press %s to unstage %s for commit";
5159                 break;
5161         case LINE_STAT_UNSTAGED:
5162                 text = "Press %s to stage %s for commit";
5163                 break;
5165         case LINE_STAT_UNTRACKED:
5166                 text = "Press %s to stage %s for addition";
5167                 break;
5169         case LINE_STAT_HEAD:
5170         case LINE_STAT_NONE:
5171                 text = "Nothing to update";
5172                 break;
5174         default:
5175                 die("line type %d not handled in switch", line->type);
5176         }
5178         if (status && status->status == 'U') {
5179                 text = "Press %s to resolve conflict in %s";
5180                 key = get_key(REQ_STATUS_MERGE);
5182         } else {
5183                 key = get_key(REQ_STATUS_UPDATE);
5184         }
5186         string_format(view->ref, text, key, file);
5189 static bool
5190 status_grep(struct view *view, struct line *line)
5192         struct status *status = line->data;
5193         enum { S_STATUS, S_NAME, S_END } state;
5194         char buf[2] = "?";
5195         regmatch_t pmatch;
5197         if (!status)
5198                 return FALSE;
5200         for (state = S_STATUS; state < S_END; state++) {
5201                 const char *text;
5203                 switch (state) {
5204                 case S_NAME:    text = status->new.name;        break;
5205                 case S_STATUS:
5206                         buf[0] = status->status;
5207                         text = buf;
5208                         break;
5210                 default:
5211                         return FALSE;
5212                 }
5214                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5215                         return TRUE;
5216         }
5218         return FALSE;
5221 static struct view_ops status_ops = {
5222         "file",
5223         NULL,
5224         status_open,
5225         NULL,
5226         status_draw,
5227         status_request,
5228         status_grep,
5229         status_select,
5230 };
5233 static bool
5234 stage_diff_write(struct io *io, struct line *line, struct line *end)
5236         while (line < end) {
5237                 if (!io_write(io, line->data, strlen(line->data)) ||
5238                     !io_write(io, "\n", 1))
5239                         return FALSE;
5240                 line++;
5241                 if (line->type == LINE_DIFF_CHUNK ||
5242                     line->type == LINE_DIFF_HEADER)
5243                         break;
5244         }
5246         return TRUE;
5249 static struct line *
5250 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5252         for (; view->line < line; line--)
5253                 if (line->type == type)
5254                         return line;
5256         return NULL;
5259 static bool
5260 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5262         const char *apply_argv[SIZEOF_ARG] = {
5263                 "git", "apply", "--whitespace=nowarn", NULL
5264         };
5265         struct line *diff_hdr;
5266         struct io io = {};
5267         int argc = 3;
5269         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5270         if (!diff_hdr)
5271                 return FALSE;
5273         if (!revert)
5274                 apply_argv[argc++] = "--cached";
5275         if (revert || stage_line_type == LINE_STAT_STAGED)
5276                 apply_argv[argc++] = "-R";
5277         apply_argv[argc++] = "-";
5278         apply_argv[argc++] = NULL;
5279         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5280                 return FALSE;
5282         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5283             !stage_diff_write(&io, chunk, view->line + view->lines))
5284                 chunk = NULL;
5286         done_io(&io);
5287         run_io_bg(update_index_argv);
5289         return chunk ? TRUE : FALSE;
5292 static bool
5293 stage_update(struct view *view, struct line *line)
5295         struct line *chunk = NULL;
5297         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5298                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5300         if (chunk) {
5301                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5302                         report("Failed to apply chunk");
5303                         return FALSE;
5304                 }
5306         } else if (!stage_status.status) {
5307                 view = VIEW(REQ_VIEW_STATUS);
5309                 for (line = view->line; line < view->line + view->lines; line++)
5310                         if (line->type == stage_line_type)
5311                                 break;
5313                 if (!status_update_files(view, line + 1)) {
5314                         report("Failed to update files");
5315                         return FALSE;
5316                 }
5318         } else if (!status_update_file(&stage_status, stage_line_type)) {
5319                 report("Failed to update file");
5320                 return FALSE;
5321         }
5323         return TRUE;
5326 static bool
5327 stage_revert(struct view *view, struct line *line)
5329         struct line *chunk = NULL;
5331         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5332                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5334         if (chunk) {
5335                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5336                         return FALSE;
5338                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5339                         report("Failed to revert chunk");
5340                         return FALSE;
5341                 }
5342                 return TRUE;
5344         } else {
5345                 return status_revert(stage_status.status ? &stage_status : NULL,
5346                                      stage_line_type, FALSE);
5347         }
5351 static void
5352 stage_next(struct view *view, struct line *line)
5354         int i;
5356         if (!stage_chunks) {
5357                 static size_t alloc = 0;
5358                 int *tmp;
5360                 for (line = view->line; line < view->line + view->lines; line++) {
5361                         if (line->type != LINE_DIFF_CHUNK)
5362                                 continue;
5364                         tmp = realloc_items(stage_chunk, &alloc,
5365                                             stage_chunks, sizeof(*tmp));
5366                         if (!tmp) {
5367                                 report("Allocation failure");
5368                                 return;
5369                         }
5371                         stage_chunk = tmp;
5372                         stage_chunk[stage_chunks++] = line - view->line;
5373                 }
5374         }
5376         for (i = 0; i < stage_chunks; i++) {
5377                 if (stage_chunk[i] > view->lineno) {
5378                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5379                         report("Chunk %d of %d", i + 1, stage_chunks);
5380                         return;
5381                 }
5382         }
5384         report("No next chunk found");
5387 static enum request
5388 stage_request(struct view *view, enum request request, struct line *line)
5390         switch (request) {
5391         case REQ_STATUS_UPDATE:
5392                 if (!stage_update(view, line))
5393                         return REQ_NONE;
5394                 break;
5396         case REQ_STATUS_REVERT:
5397                 if (!stage_revert(view, line))
5398                         return REQ_NONE;
5399                 break;
5401         case REQ_STAGE_NEXT:
5402                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5403                         report("File is untracked; press %s to add",
5404                                get_key(REQ_STATUS_UPDATE));
5405                         return REQ_NONE;
5406                 }
5407                 stage_next(view, line);
5408                 return REQ_NONE;
5410         case REQ_EDIT:
5411                 if (!stage_status.new.name[0])
5412                         return request;
5413                 if (stage_status.status == 'D') {
5414                         report("File has been deleted.");
5415                         return REQ_NONE;
5416                 }
5418                 open_editor(stage_status.status != '?', stage_status.new.name);
5419                 break;
5421         case REQ_REFRESH:
5422                 /* Reload everything ... */
5423                 break;
5425         case REQ_VIEW_BLAME:
5426                 if (stage_status.new.name[0]) {
5427                         string_copy(opt_file, stage_status.new.name);
5428                         opt_ref[0] = 0;
5429                 }
5430                 return request;
5432         case REQ_ENTER:
5433                 return pager_request(view, request, line);
5435         default:
5436                 return request;
5437         }
5439         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5440         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5442         /* Check whether the staged entry still exists, and close the
5443          * stage view if it doesn't. */
5444         if (!status_exists(&stage_status, stage_line_type)) {
5445                 status_restore(VIEW(REQ_VIEW_STATUS));
5446                 return REQ_VIEW_CLOSE;
5447         }
5449         if (stage_line_type == LINE_STAT_UNTRACKED) {
5450                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5451                         report("Cannot display a directory");
5452                         return REQ_NONE;
5453                 }
5455                 if (!prepare_update_file(view, stage_status.new.name)) {
5456                         report("Failed to open file: %s", strerror(errno));
5457                         return REQ_NONE;
5458                 }
5459         }
5460         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5462         return REQ_NONE;
5465 static struct view_ops stage_ops = {
5466         "line",
5467         NULL,
5468         NULL,
5469         pager_read,
5470         pager_draw,
5471         stage_request,
5472         pager_grep,
5473         pager_select,
5474 };
5477 /*
5478  * Revision graph
5479  */
5481 struct commit {
5482         char id[SIZEOF_REV];            /* SHA1 ID. */
5483         char title[128];                /* First line of the commit message. */
5484         char author[75];                /* Author of the commit. */
5485         struct tm time;                 /* Date from the author ident. */
5486         struct ref **refs;              /* Repository references. */
5487         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5488         size_t graph_size;              /* The width of the graph array. */
5489         bool has_parents;               /* Rewritten --parents seen. */
5490 };
5492 /* Size of rev graph with no  "padding" columns */
5493 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5495 struct rev_graph {
5496         struct rev_graph *prev, *next, *parents;
5497         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5498         size_t size;
5499         struct commit *commit;
5500         size_t pos;
5501         unsigned int boundary:1;
5502 };
5504 /* Parents of the commit being visualized. */
5505 static struct rev_graph graph_parents[4];
5507 /* The current stack of revisions on the graph. */
5508 static struct rev_graph graph_stacks[4] = {
5509         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5510         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5511         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5512         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5513 };
5515 static inline bool
5516 graph_parent_is_merge(struct rev_graph *graph)
5518         return graph->parents->size > 1;
5521 static inline void
5522 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5524         struct commit *commit = graph->commit;
5526         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5527                 commit->graph[commit->graph_size++] = symbol;
5530 static void
5531 clear_rev_graph(struct rev_graph *graph)
5533         graph->boundary = 0;
5534         graph->size = graph->pos = 0;
5535         graph->commit = NULL;
5536         memset(graph->parents, 0, sizeof(*graph->parents));
5539 static void
5540 done_rev_graph(struct rev_graph *graph)
5542         if (graph_parent_is_merge(graph) &&
5543             graph->pos < graph->size - 1 &&
5544             graph->next->size == graph->size + graph->parents->size - 1) {
5545                 size_t i = graph->pos + graph->parents->size - 1;
5547                 graph->commit->graph_size = i * 2;
5548                 while (i < graph->next->size - 1) {
5549                         append_to_rev_graph(graph, ' ');
5550                         append_to_rev_graph(graph, '\\');
5551                         i++;
5552                 }
5553         }
5555         clear_rev_graph(graph);
5558 static void
5559 push_rev_graph(struct rev_graph *graph, const char *parent)
5561         int i;
5563         /* "Collapse" duplicate parents lines.
5564          *
5565          * FIXME: This needs to also update update the drawn graph but
5566          * for now it just serves as a method for pruning graph lines. */
5567         for (i = 0; i < graph->size; i++)
5568                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5569                         return;
5571         if (graph->size < SIZEOF_REVITEMS) {
5572                 string_copy_rev(graph->rev[graph->size++], parent);
5573         }
5576 static chtype
5577 get_rev_graph_symbol(struct rev_graph *graph)
5579         chtype symbol;
5581         if (graph->boundary)
5582                 symbol = REVGRAPH_BOUND;
5583         else if (graph->parents->size == 0)
5584                 symbol = REVGRAPH_INIT;
5585         else if (graph_parent_is_merge(graph))
5586                 symbol = REVGRAPH_MERGE;
5587         else if (graph->pos >= graph->size)
5588                 symbol = REVGRAPH_BRANCH;
5589         else
5590                 symbol = REVGRAPH_COMMIT;
5592         return symbol;
5595 static void
5596 draw_rev_graph(struct rev_graph *graph)
5598         struct rev_filler {
5599                 chtype separator, line;
5600         };
5601         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5602         static struct rev_filler fillers[] = {
5603                 { ' ',  '|' },
5604                 { '`',  '.' },
5605                 { '\'', ' ' },
5606                 { '/',  ' ' },
5607         };
5608         chtype symbol = get_rev_graph_symbol(graph);
5609         struct rev_filler *filler;
5610         size_t i;
5612         if (opt_line_graphics)
5613                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5615         filler = &fillers[DEFAULT];
5617         for (i = 0; i < graph->pos; i++) {
5618                 append_to_rev_graph(graph, filler->line);
5619                 if (graph_parent_is_merge(graph->prev) &&
5620                     graph->prev->pos == i)
5621                         filler = &fillers[RSHARP];
5623                 append_to_rev_graph(graph, filler->separator);
5624         }
5626         /* Place the symbol for this revision. */
5627         append_to_rev_graph(graph, symbol);
5629         if (graph->prev->size > graph->size)
5630                 filler = &fillers[RDIAG];
5631         else
5632                 filler = &fillers[DEFAULT];
5634         i++;
5636         for (; i < graph->size; i++) {
5637                 append_to_rev_graph(graph, filler->separator);
5638                 append_to_rev_graph(graph, filler->line);
5639                 if (graph_parent_is_merge(graph->prev) &&
5640                     i < graph->prev->pos + graph->parents->size)
5641                         filler = &fillers[RSHARP];
5642                 if (graph->prev->size > graph->size)
5643                         filler = &fillers[LDIAG];
5644         }
5646         if (graph->prev->size > graph->size) {
5647                 append_to_rev_graph(graph, filler->separator);
5648                 if (filler->line != ' ')
5649                         append_to_rev_graph(graph, filler->line);
5650         }
5653 /* Prepare the next rev graph */
5654 static void
5655 prepare_rev_graph(struct rev_graph *graph)
5657         size_t i;
5659         /* First, traverse all lines of revisions up to the active one. */
5660         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5661                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5662                         break;
5664                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5665         }
5667         /* Interleave the new revision parent(s). */
5668         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5669                 push_rev_graph(graph->next, graph->parents->rev[i]);
5671         /* Lastly, put any remaining revisions. */
5672         for (i = graph->pos + 1; i < graph->size; i++)
5673                 push_rev_graph(graph->next, graph->rev[i]);
5676 static void
5677 update_rev_graph(struct view *view, struct rev_graph *graph)
5679         /* If this is the finalizing update ... */
5680         if (graph->commit)
5681                 prepare_rev_graph(graph);
5683         /* Graph visualization needs a one rev look-ahead,
5684          * so the first update doesn't visualize anything. */
5685         if (!graph->prev->commit)
5686                 return;
5688         if (view->lines > 2)
5689                 view->line[view->lines - 3].dirty = 1;
5690         if (view->lines > 1)
5691                 view->line[view->lines - 2].dirty = 1;
5692         draw_rev_graph(graph->prev);
5693         done_rev_graph(graph->prev->prev);
5697 /*
5698  * Main view backend
5699  */
5701 static const char *main_argv[SIZEOF_ARG] = {
5702         "git", "log", "--no-color", "--pretty=raw", "--parents",
5703                       "--topo-order", "%(head)", NULL
5704 };
5706 static bool
5707 main_draw(struct view *view, struct line *line, unsigned int lineno)
5709         struct commit *commit = line->data;
5711         if (!*commit->author)
5712                 return FALSE;
5714         if (opt_date && draw_date(view, &commit->time))
5715                 return TRUE;
5717         if (opt_author && draw_author(view, commit->author))
5718                 return TRUE;
5720         if (opt_rev_graph && commit->graph_size &&
5721             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5722                 return TRUE;
5724         if (opt_show_refs && commit->refs) {
5725                 size_t i = 0;
5727                 do {
5728                         enum line_type type;
5730                         if (commit->refs[i]->head)
5731                                 type = LINE_MAIN_HEAD;
5732                         else if (commit->refs[i]->ltag)
5733                                 type = LINE_MAIN_LOCAL_TAG;
5734                         else if (commit->refs[i]->tag)
5735                                 type = LINE_MAIN_TAG;
5736                         else if (commit->refs[i]->tracked)
5737                                 type = LINE_MAIN_TRACKED;
5738                         else if (commit->refs[i]->remote)
5739                                 type = LINE_MAIN_REMOTE;
5740                         else
5741                                 type = LINE_MAIN_REF;
5743                         if (draw_text(view, type, "[", TRUE) ||
5744                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5745                             draw_text(view, type, "]", TRUE))
5746                                 return TRUE;
5748                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5749                                 return TRUE;
5750                 } while (commit->refs[i++]->next);
5751         }
5753         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5754         return TRUE;
5757 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5758 static bool
5759 main_read(struct view *view, char *line)
5761         static struct rev_graph *graph = graph_stacks;
5762         enum line_type type;
5763         struct commit *commit;
5765         if (!line) {
5766                 int i;
5768                 if (!view->lines && !view->parent)
5769                         die("No revisions match the given arguments.");
5770                 if (view->lines > 0) {
5771                         commit = view->line[view->lines - 1].data;
5772                         view->line[view->lines - 1].dirty = 1;
5773                         if (!*commit->author) {
5774                                 view->lines--;
5775                                 free(commit);
5776                                 graph->commit = NULL;
5777                         }
5778                 }
5779                 update_rev_graph(view, graph);
5781                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5782                         clear_rev_graph(&graph_stacks[i]);
5783                 return TRUE;
5784         }
5786         type = get_line_type(line);
5787         if (type == LINE_COMMIT) {
5788                 commit = calloc(1, sizeof(struct commit));
5789                 if (!commit)
5790                         return FALSE;
5792                 line += STRING_SIZE("commit ");
5793                 if (*line == '-') {
5794                         graph->boundary = 1;
5795                         line++;
5796                 }
5798                 string_copy_rev(commit->id, line);
5799                 commit->refs = get_refs(commit->id);
5800                 graph->commit = commit;
5801                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5803                 while ((line = strchr(line, ' '))) {
5804                         line++;
5805                         push_rev_graph(graph->parents, line);
5806                         commit->has_parents = TRUE;
5807                 }
5808                 return TRUE;
5809         }
5811         if (!view->lines)
5812                 return TRUE;
5813         commit = view->line[view->lines - 1].data;
5815         switch (type) {
5816         case LINE_PARENT:
5817                 if (commit->has_parents)
5818                         break;
5819                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5820                 break;
5822         case LINE_AUTHOR:
5823                 parse_author_line(line + STRING_SIZE("author "),
5824                                   commit->author, sizeof(commit->author),
5825                                   &commit->time);
5826                 update_rev_graph(view, graph);
5827                 graph = graph->next;
5828                 break;
5830         default:
5831                 /* Fill in the commit title if it has not already been set. */
5832                 if (commit->title[0])
5833                         break;
5835                 /* Require titles to start with a non-space character at the
5836                  * offset used by git log. */
5837                 if (strncmp(line, "    ", 4))
5838                         break;
5839                 line += 4;
5840                 /* Well, if the title starts with a whitespace character,
5841                  * try to be forgiving.  Otherwise we end up with no title. */
5842                 while (isspace(*line))
5843                         line++;
5844                 if (*line == '\0')
5845                         break;
5846                 /* FIXME: More graceful handling of titles; append "..." to
5847                  * shortened titles, etc. */
5849                 string_expand(commit->title, sizeof(commit->title), line, 1);
5850                 view->line[view->lines - 1].dirty = 1;
5851         }
5853         return TRUE;
5856 static enum request
5857 main_request(struct view *view, enum request request, struct line *line)
5859         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5861         switch (request) {
5862         case REQ_ENTER:
5863                 open_view(view, REQ_VIEW_DIFF, flags);
5864                 break;
5865         case REQ_REFRESH:
5866                 load_refs();
5867                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5868                 break;
5869         default:
5870                 return request;
5871         }
5873         return REQ_NONE;
5876 static bool
5877 grep_refs(struct ref **refs, regex_t *regex)
5879         regmatch_t pmatch;
5880         size_t i = 0;
5882         if (!refs)
5883                 return FALSE;
5884         do {
5885                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5886                         return TRUE;
5887         } while (refs[i++]->next);
5889         return FALSE;
5892 static bool
5893 main_grep(struct view *view, struct line *line)
5895         struct commit *commit = line->data;
5896         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5897         char buf[DATE_COLS + 1];
5898         regmatch_t pmatch;
5900         for (state = S_TITLE; state < S_END; state++) {
5901                 char *text;
5903                 switch (state) {
5904                 case S_TITLE:   text = commit->title;   break;
5905                 case S_AUTHOR:
5906                         if (!opt_author)
5907                                 continue;
5908                         text = commit->author;
5909                         break;
5910                 case S_DATE:
5911                         if (!opt_date)
5912                                 continue;
5913                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5914                                 continue;
5915                         text = buf;
5916                         break;
5917                 case S_REFS:
5918                         if (!opt_show_refs)
5919                                 continue;
5920                         if (grep_refs(commit->refs, view->regex) == TRUE)
5921                                 return TRUE;
5922                         continue;
5923                 default:
5924                         return FALSE;
5925                 }
5927                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5928                         return TRUE;
5929         }
5931         return FALSE;
5934 static void
5935 main_select(struct view *view, struct line *line)
5937         struct commit *commit = line->data;
5939         string_copy_rev(view->ref, commit->id);
5940         string_copy_rev(ref_commit, view->ref);
5943 static struct view_ops main_ops = {
5944         "commit",
5945         main_argv,
5946         NULL,
5947         main_read,
5948         main_draw,
5949         main_request,
5950         main_grep,
5951         main_select,
5952 };
5955 /*
5956  * Unicode / UTF-8 handling
5957  *
5958  * NOTE: Much of the following code for dealing with Unicode is derived from
5959  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5960  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
5961  */
5963 static inline int
5964 unicode_width(unsigned long c)
5966         if (c >= 0x1100 &&
5967            (c <= 0x115f                         /* Hangul Jamo */
5968             || c == 0x2329
5969             || c == 0x232a
5970             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5971                                                 /* CJK ... Yi */
5972             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5973             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5974             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5975             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5976             || (c >= 0xffe0  && c <= 0xffe6)
5977             || (c >= 0x20000 && c <= 0x2fffd)
5978             || (c >= 0x30000 && c <= 0x3fffd)))
5979                 return 2;
5981         if (c == '\t')
5982                 return opt_tab_size;
5984         return 1;
5987 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5988  * Illegal bytes are set one. */
5989 static const unsigned char utf8_bytes[256] = {
5990         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,
5991         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,
5992         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,
5993         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,
5994         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,
5995         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,
5996         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,
5997         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,
5998 };
6000 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6001 static inline unsigned long
6002 utf8_to_unicode(const char *string, size_t length)
6004         unsigned long unicode;
6006         switch (length) {
6007         case 1:
6008                 unicode  =   string[0];
6009                 break;
6010         case 2:
6011                 unicode  =  (string[0] & 0x1f) << 6;
6012                 unicode +=  (string[1] & 0x3f);
6013                 break;
6014         case 3:
6015                 unicode  =  (string[0] & 0x0f) << 12;
6016                 unicode += ((string[1] & 0x3f) << 6);
6017                 unicode +=  (string[2] & 0x3f);
6018                 break;
6019         case 4:
6020                 unicode  =  (string[0] & 0x0f) << 18;
6021                 unicode += ((string[1] & 0x3f) << 12);
6022                 unicode += ((string[2] & 0x3f) << 6);
6023                 unicode +=  (string[3] & 0x3f);
6024                 break;
6025         case 5:
6026                 unicode  =  (string[0] & 0x0f) << 24;
6027                 unicode += ((string[1] & 0x3f) << 18);
6028                 unicode += ((string[2] & 0x3f) << 12);
6029                 unicode += ((string[3] & 0x3f) << 6);
6030                 unicode +=  (string[4] & 0x3f);
6031                 break;
6032         case 6:
6033                 unicode  =  (string[0] & 0x01) << 30;
6034                 unicode += ((string[1] & 0x3f) << 24);
6035                 unicode += ((string[2] & 0x3f) << 18);
6036                 unicode += ((string[3] & 0x3f) << 12);
6037                 unicode += ((string[4] & 0x3f) << 6);
6038                 unicode +=  (string[5] & 0x3f);
6039                 break;
6040         default:
6041                 die("Invalid Unicode length");
6042         }
6044         /* Invalid characters could return the special 0xfffd value but NUL
6045          * should be just as good. */
6046         return unicode > 0xffff ? 0 : unicode;
6049 /* Calculates how much of string can be shown within the given maximum width
6050  * and sets trimmed parameter to non-zero value if all of string could not be
6051  * shown. If the reserve flag is TRUE, it will reserve at least one
6052  * trailing character, which can be useful when drawing a delimiter.
6053  *
6054  * Returns the number of bytes to output from string to satisfy max_width. */
6055 static size_t
6056 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6058         const char *string = *start;
6059         const char *end = strchr(string, '\0');
6060         unsigned char last_bytes = 0;
6061         size_t last_ucwidth = 0;
6063         *width = 0;
6064         *trimmed = 0;
6066         while (string < end) {
6067                 int c = *(unsigned char *) string;
6068                 unsigned char bytes = utf8_bytes[c];
6069                 size_t ucwidth;
6070                 unsigned long unicode;
6072                 if (string + bytes > end)
6073                         break;
6075                 /* Change representation to figure out whether
6076                  * it is a single- or double-width character. */
6078                 unicode = utf8_to_unicode(string, bytes);
6079                 /* FIXME: Graceful handling of invalid Unicode character. */
6080                 if (!unicode)
6081                         break;
6083                 ucwidth = unicode_width(unicode);
6084                 if (skip > 0) {
6085                         skip -= ucwidth <= skip ? ucwidth : skip;
6086                         *start += bytes;
6087                 }
6088                 *width  += ucwidth;
6089                 if (*width > max_width) {
6090                         *trimmed = 1;
6091                         *width -= ucwidth;
6092                         if (reserve && *width == max_width) {
6093                                 string -= last_bytes;
6094                                 *width -= last_ucwidth;
6095                         }
6096                         break;
6097                 }
6099                 string  += bytes;
6100                 last_bytes = ucwidth ? bytes : 0;
6101                 last_ucwidth = ucwidth;
6102         }
6104         return string - *start;
6108 /*
6109  * Status management
6110  */
6112 /* Whether or not the curses interface has been initialized. */
6113 static bool cursed = FALSE;
6115 /* Terminal hacks and workarounds. */
6116 static bool use_scroll_redrawwin;
6117 static bool use_scroll_status_wclear;
6119 /* The status window is used for polling keystrokes. */
6120 static WINDOW *status_win;
6122 /* Reading from the prompt? */
6123 static bool input_mode = FALSE;
6125 static bool status_empty = FALSE;
6127 /* Update status and title window. */
6128 static void
6129 report(const char *msg, ...)
6131         struct view *view = display[current_view];
6133         if (input_mode)
6134                 return;
6136         if (!view) {
6137                 char buf[SIZEOF_STR];
6138                 va_list args;
6140                 va_start(args, msg);
6141                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6142                         buf[sizeof(buf) - 1] = 0;
6143                         buf[sizeof(buf) - 2] = '.';
6144                         buf[sizeof(buf) - 3] = '.';
6145                         buf[sizeof(buf) - 4] = '.';
6146                 }
6147                 va_end(args);
6148                 die("%s", buf);
6149         }
6151         if (!status_empty || *msg) {
6152                 va_list args;
6154                 va_start(args, msg);
6156                 wmove(status_win, 0, 0);
6157                 if (view->has_scrolled && use_scroll_status_wclear)
6158                         wclear(status_win);
6159                 if (*msg) {
6160                         vwprintw(status_win, msg, args);
6161                         status_empty = FALSE;
6162                 } else {
6163                         status_empty = TRUE;
6164                 }
6165                 wclrtoeol(status_win);
6166                 wnoutrefresh(status_win);
6168                 va_end(args);
6169         }
6171         update_view_title(view);
6174 /* Controls when nodelay should be in effect when polling user input. */
6175 static void
6176 set_nonblocking_input(bool loading)
6178         static unsigned int loading_views;
6180         if ((loading == FALSE && loading_views-- == 1) ||
6181             (loading == TRUE  && loading_views++ == 0))
6182                 nodelay(status_win, loading);
6185 static void
6186 init_display(void)
6188         const char *term;
6189         int x, y;
6191         /* Initialize the curses library */
6192         if (isatty(STDIN_FILENO)) {
6193                 cursed = !!initscr();
6194                 opt_tty = stdin;
6195         } else {
6196                 /* Leave stdin and stdout alone when acting as a pager. */
6197                 opt_tty = fopen("/dev/tty", "r+");
6198                 if (!opt_tty)
6199                         die("Failed to open /dev/tty");
6200                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6201         }
6203         if (!cursed)
6204                 die("Failed to initialize curses");
6206         nonl();         /* Disable conversion and detect newlines from input. */
6207         cbreak();       /* Take input chars one at a time, no wait for \n */
6208         noecho();       /* Don't echo input */
6209         leaveok(stdscr, FALSE);
6211         if (has_colors())
6212                 init_colors();
6214         getmaxyx(stdscr, y, x);
6215         status_win = newwin(1, 0, y - 1, 0);
6216         if (!status_win)
6217                 die("Failed to create status window");
6219         /* Enable keyboard mapping */
6220         keypad(status_win, TRUE);
6221         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6223         TABSIZE = opt_tab_size;
6224         if (opt_line_graphics) {
6225                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6226         }
6228         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6229         if (term && !strcmp(term, "gnome-terminal")) {
6230                 /* In the gnome-terminal-emulator, the message from
6231                  * scrolling up one line when impossible followed by
6232                  * scrolling down one line causes corruption of the
6233                  * status line. This is fixed by calling wclear. */
6234                 use_scroll_status_wclear = TRUE;
6235                 use_scroll_redrawwin = FALSE;
6237         } else if (term && !strcmp(term, "xrvt-xpm")) {
6238                 /* No problems with full optimizations in xrvt-(unicode)
6239                  * and aterm. */
6240                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6242         } else {
6243                 /* When scrolling in (u)xterm the last line in the
6244                  * scrolling direction will update slowly. */
6245                 use_scroll_redrawwin = TRUE;
6246                 use_scroll_status_wclear = FALSE;
6247         }
6250 static int
6251 get_input(int prompt_position)
6253         struct view *view;
6254         int i, key, cursor_y, cursor_x;
6256         if (prompt_position)
6257                 input_mode = TRUE;
6259         while (TRUE) {
6260                 foreach_view (view, i) {
6261                         update_view(view);
6262                         if (view_is_displayed(view) && view->has_scrolled &&
6263                             use_scroll_redrawwin)
6264                                 redrawwin(view->win);
6265                         view->has_scrolled = FALSE;
6266                 }
6268                 /* Update the cursor position. */
6269                 if (prompt_position) {
6270                         getbegyx(status_win, cursor_y, cursor_x);
6271                         cursor_x = prompt_position;
6272                 } else {
6273                         view = display[current_view];
6274                         getbegyx(view->win, cursor_y, cursor_x);
6275                         cursor_x = view->width - 1;
6276                         cursor_y += view->lineno - view->offset;
6277                 }
6278                 setsyx(cursor_y, cursor_x);
6280                 /* Refresh, accept single keystroke of input */
6281                 doupdate();
6282                 key = wgetch(status_win);
6284                 /* wgetch() with nodelay() enabled returns ERR when
6285                  * there's no input. */
6286                 if (key == ERR) {
6288                 } else if (key == KEY_RESIZE) {
6289                         int height, width;
6291                         getmaxyx(stdscr, height, width);
6293                         wresize(status_win, 1, width);
6294                         mvwin(status_win, height - 1, 0);
6295                         wnoutrefresh(status_win);
6296                         resize_display();
6297                         redraw_display(TRUE);
6299                 } else {
6300                         input_mode = FALSE;
6301                         return key;
6302                 }
6303         }
6306 static char *
6307 prompt_input(const char *prompt, input_handler handler, void *data)
6309         enum input_status status = INPUT_OK;
6310         static char buf[SIZEOF_STR];
6311         size_t pos = 0;
6313         buf[pos] = 0;
6315         while (status == INPUT_OK || status == INPUT_SKIP) {
6316                 int key;
6318                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6319                 wclrtoeol(status_win);
6321                 key = get_input(pos + 1);
6322                 switch (key) {
6323                 case KEY_RETURN:
6324                 case KEY_ENTER:
6325                 case '\n':
6326                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6327                         break;
6329                 case KEY_BACKSPACE:
6330                         if (pos > 0)
6331                                 buf[--pos] = 0;
6332                         else
6333                                 status = INPUT_CANCEL;
6334                         break;
6336                 case KEY_ESC:
6337                         status = INPUT_CANCEL;
6338                         break;
6340                 default:
6341                         if (pos >= sizeof(buf)) {
6342                                 report("Input string too long");
6343                                 return NULL;
6344                         }
6346                         status = handler(data, buf, key);
6347                         if (status == INPUT_OK)
6348                                 buf[pos++] = (char) key;
6349                 }
6350         }
6352         /* Clear the status window */
6353         status_empty = FALSE;
6354         report("");
6356         if (status == INPUT_CANCEL)
6357                 return NULL;
6359         buf[pos++] = 0;
6361         return buf;
6364 static enum input_status
6365 prompt_yesno_handler(void *data, char *buf, int c)
6367         if (c == 'y' || c == 'Y')
6368                 return INPUT_STOP;
6369         if (c == 'n' || c == 'N')
6370                 return INPUT_CANCEL;
6371         return INPUT_SKIP;
6374 static bool
6375 prompt_yesno(const char *prompt)
6377         char prompt2[SIZEOF_STR];
6379         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6380                 return FALSE;
6382         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6385 static enum input_status
6386 read_prompt_handler(void *data, char *buf, int c)
6388         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6391 static char *
6392 read_prompt(const char *prompt)
6394         return prompt_input(prompt, read_prompt_handler, NULL);
6397 /*
6398  * Repository properties
6399  */
6401 static struct ref *refs = NULL;
6402 static size_t refs_alloc = 0;
6403 static size_t refs_size = 0;
6405 /* Id <-> ref store */
6406 static struct ref ***id_refs = NULL;
6407 static size_t id_refs_alloc = 0;
6408 static size_t id_refs_size = 0;
6410 static int
6411 compare_refs(const void *ref1_, const void *ref2_)
6413         const struct ref *ref1 = *(const struct ref **)ref1_;
6414         const struct ref *ref2 = *(const struct ref **)ref2_;
6416         if (ref1->tag != ref2->tag)
6417                 return ref2->tag - ref1->tag;
6418         if (ref1->ltag != ref2->ltag)
6419                 return ref2->ltag - ref2->ltag;
6420         if (ref1->head != ref2->head)
6421                 return ref2->head - ref1->head;
6422         if (ref1->tracked != ref2->tracked)
6423                 return ref2->tracked - ref1->tracked;
6424         if (ref1->remote != ref2->remote)
6425                 return ref2->remote - ref1->remote;
6426         return strcmp(ref1->name, ref2->name);
6429 static struct ref **
6430 get_refs(const char *id)
6432         struct ref ***tmp_id_refs;
6433         struct ref **ref_list = NULL;
6434         size_t ref_list_alloc = 0;
6435         size_t ref_list_size = 0;
6436         size_t i;
6438         for (i = 0; i < id_refs_size; i++)
6439                 if (!strcmp(id, id_refs[i][0]->id))
6440                         return id_refs[i];
6442         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6443                                     sizeof(*id_refs));
6444         if (!tmp_id_refs)
6445                 return NULL;
6447         id_refs = tmp_id_refs;
6449         for (i = 0; i < refs_size; i++) {
6450                 struct ref **tmp;
6452                 if (strcmp(id, refs[i].id))
6453                         continue;
6455                 tmp = realloc_items(ref_list, &ref_list_alloc,
6456                                     ref_list_size + 1, sizeof(*ref_list));
6457                 if (!tmp) {
6458                         if (ref_list)
6459                                 free(ref_list);
6460                         return NULL;
6461                 }
6463                 ref_list = tmp;
6464                 ref_list[ref_list_size] = &refs[i];
6465                 /* XXX: The properties of the commit chains ensures that we can
6466                  * safely modify the shared ref. The repo references will
6467                  * always be similar for the same id. */
6468                 ref_list[ref_list_size]->next = 1;
6470                 ref_list_size++;
6471         }
6473         if (ref_list) {
6474                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6475                 ref_list[ref_list_size - 1]->next = 0;
6476                 id_refs[id_refs_size++] = ref_list;
6477         }
6479         return ref_list;
6482 static int
6483 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6485         struct ref *ref;
6486         bool tag = FALSE;
6487         bool ltag = FALSE;
6488         bool remote = FALSE;
6489         bool tracked = FALSE;
6490         bool check_replace = FALSE;
6491         bool head = FALSE;
6493         if (!prefixcmp(name, "refs/tags/")) {
6494                 if (!suffixcmp(name, namelen, "^{}")) {
6495                         namelen -= 3;
6496                         name[namelen] = 0;
6497                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6498                                 check_replace = TRUE;
6499                 } else {
6500                         ltag = TRUE;
6501                 }
6503                 tag = TRUE;
6504                 namelen -= STRING_SIZE("refs/tags/");
6505                 name    += STRING_SIZE("refs/tags/");
6507         } else if (!prefixcmp(name, "refs/remotes/")) {
6508                 remote = TRUE;
6509                 namelen -= STRING_SIZE("refs/remotes/");
6510                 name    += STRING_SIZE("refs/remotes/");
6511                 tracked  = !strcmp(opt_remote, name);
6513         } else if (!prefixcmp(name, "refs/heads/")) {
6514                 namelen -= STRING_SIZE("refs/heads/");
6515                 name    += STRING_SIZE("refs/heads/");
6516                 head     = !strncmp(opt_head, name, namelen);
6518         } else if (!strcmp(name, "HEAD")) {
6519                 string_ncopy(opt_head_rev, id, idlen);
6520                 return OK;
6521         }
6523         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6524                 /* it's an annotated tag, replace the previous SHA1 with the
6525                  * resolved commit id; relies on the fact git-ls-remote lists
6526                  * the commit id of an annotated tag right before the commit id
6527                  * it points to. */
6528                 refs[refs_size - 1].ltag = ltag;
6529                 string_copy_rev(refs[refs_size - 1].id, id);
6531                 return OK;
6532         }
6533         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6534         if (!refs)
6535                 return ERR;
6537         ref = &refs[refs_size++];
6538         ref->name = malloc(namelen + 1);
6539         if (!ref->name)
6540                 return ERR;
6542         strncpy(ref->name, name, namelen);
6543         ref->name[namelen] = 0;
6544         ref->head = head;
6545         ref->tag = tag;
6546         ref->ltag = ltag;
6547         ref->remote = remote;
6548         ref->tracked = tracked;
6549         string_copy_rev(ref->id, id);
6551         return OK;
6554 static int
6555 load_refs(void)
6557         static const char *ls_remote_argv[SIZEOF_ARG] = {
6558                 "git", "ls-remote", ".", NULL
6559         };
6560         static bool init = FALSE;
6562         if (!init) {
6563                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6564                 init = TRUE;
6565         }
6567         if (!*opt_git_dir)
6568                 return OK;
6570         while (refs_size > 0)
6571                 free(refs[--refs_size].name);
6572         while (id_refs_size > 0)
6573                 free(id_refs[--id_refs_size]);
6575         return run_io_load(ls_remote_argv, "\t", read_ref);
6578 static void
6579 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6581         const char *argv[SIZEOF_ARG] = { name, "=" };
6582         int argc = 1 + (cmd == option_set_command);
6583         int error = ERR;
6585         if (!argv_from_string(argv, &argc, value))
6586                 config_msg = "Too many option arguments";
6587         else
6588                 error = cmd(argc, argv);
6590         if (error == ERR)
6591                 warn("Option 'tig.%s': %s", name, config_msg);
6594 static int
6595 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6597         if (!strcmp(name, "i18n.commitencoding"))
6598                 string_ncopy(opt_encoding, value, valuelen);
6600         if (!strcmp(name, "core.editor"))
6601                 string_ncopy(opt_editor, value, valuelen);
6603         if (!prefixcmp(name, "tig.color."))
6604                 set_repo_config_option(name + 10, value, option_color_command);
6606         else if (!prefixcmp(name, "tig.bind."))
6607                 set_repo_config_option(name + 9, value, option_bind_command);
6609         else if (!prefixcmp(name, "tig."))
6610                 set_repo_config_option(name + 4, value, option_set_command);
6612         /* branch.<head>.remote */
6613         if (*opt_head &&
6614             !strncmp(name, "branch.", 7) &&
6615             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6616             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6617                 string_ncopy(opt_remote, value, valuelen);
6619         if (*opt_head && *opt_remote &&
6620             !strncmp(name, "branch.", 7) &&
6621             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6622             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6623                 size_t from = strlen(opt_remote);
6625                 if (!prefixcmp(value, "refs/heads/")) {
6626                         value += STRING_SIZE("refs/heads/");
6627                         valuelen -= STRING_SIZE("refs/heads/");
6628                 }
6630                 if (!string_format_from(opt_remote, &from, "/%s", value))
6631                         opt_remote[0] = 0;
6632         }
6634         return OK;
6637 static int
6638 load_git_config(void)
6640         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6642         return run_io_load(config_list_argv, "=", read_repo_config_option);
6645 static int
6646 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6648         if (!opt_git_dir[0]) {
6649                 string_ncopy(opt_git_dir, name, namelen);
6651         } else if (opt_is_inside_work_tree == -1) {
6652                 /* This can be 3 different values depending on the
6653                  * version of git being used. If git-rev-parse does not
6654                  * understand --is-inside-work-tree it will simply echo
6655                  * the option else either "true" or "false" is printed.
6656                  * Default to true for the unknown case. */
6657                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6659         } else if (*name == '.') {
6660                 string_ncopy(opt_cdup, name, namelen);
6662         } else {
6663                 string_ncopy(opt_prefix, name, namelen);
6664         }
6666         return OK;
6669 static int
6670 load_repo_info(void)
6672         const char *head_argv[] = {
6673                 "git", "symbolic-ref", "HEAD", NULL
6674         };
6675         const char *rev_parse_argv[] = {
6676                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6677                         "--show-cdup", "--show-prefix", NULL
6678         };
6680         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6681                 chomp_string(opt_head);
6682                 if (!prefixcmp(opt_head, "refs/heads/")) {
6683                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6685                         memmove(opt_head, offset, strlen(offset) + 1);
6686                 }
6687         }
6689         return run_io_load(rev_parse_argv, "=", read_repo_info);
6693 /*
6694  * Main
6695  */
6697 static const char usage[] =
6698 "tig " TIG_VERSION " (" __DATE__ ")\n"
6699 "\n"
6700 "Usage: tig        [options] [revs] [--] [paths]\n"
6701 "   or: tig show   [options] [revs] [--] [paths]\n"
6702 "   or: tig blame  [rev] path\n"
6703 "   or: tig status\n"
6704 "   or: tig <      [git command output]\n"
6705 "\n"
6706 "Options:\n"
6707 "  -v, --version   Show version and exit\n"
6708 "  -h, --help      Show help message and exit";
6710 static void __NORETURN
6711 quit(int sig)
6713         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6714         if (cursed)
6715                 endwin();
6716         exit(0);
6719 static void __NORETURN
6720 die(const char *err, ...)
6722         va_list args;
6724         endwin();
6726         va_start(args, err);
6727         fputs("tig: ", stderr);
6728         vfprintf(stderr, err, args);
6729         fputs("\n", stderr);
6730         va_end(args);
6732         exit(1);
6735 static void
6736 warn(const char *msg, ...)
6738         va_list args;
6740         va_start(args, msg);
6741         fputs("tig warning: ", stderr);
6742         vfprintf(stderr, msg, args);
6743         fputs("\n", stderr);
6744         va_end(args);
6747 static enum request
6748 parse_options(int argc, const char *argv[])
6750         enum request request = REQ_VIEW_MAIN;
6751         const char *subcommand;
6752         bool seen_dashdash = FALSE;
6753         /* XXX: This is vulnerable to the user overriding options
6754          * required for the main view parser. */
6755         const char *custom_argv[SIZEOF_ARG] = {
6756                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6757                         "--topo-order", NULL
6758         };
6759         int i, j = 6;
6761         if (!isatty(STDIN_FILENO)) {
6762                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6763                 return REQ_VIEW_PAGER;
6764         }
6766         if (argc <= 1)
6767                 return REQ_NONE;
6769         subcommand = argv[1];
6770         if (!strcmp(subcommand, "status")) {
6771                 if (argc > 2)
6772                         warn("ignoring arguments after `%s'", subcommand);
6773                 return REQ_VIEW_STATUS;
6775         } else if (!strcmp(subcommand, "blame")) {
6776                 if (argc <= 2 || argc > 4)
6777                         die("invalid number of options to blame\n\n%s", usage);
6779                 i = 2;
6780                 if (argc == 4) {
6781                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6782                         i++;
6783                 }
6785                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6786                 return REQ_VIEW_BLAME;
6788         } else if (!strcmp(subcommand, "show")) {
6789                 request = REQ_VIEW_DIFF;
6791         } else {
6792                 subcommand = NULL;
6793         }
6795         if (subcommand) {
6796                 custom_argv[1] = subcommand;
6797                 j = 2;
6798         }
6800         for (i = 1 + !!subcommand; i < argc; i++) {
6801                 const char *opt = argv[i];
6803                 if (seen_dashdash || !strcmp(opt, "--")) {
6804                         seen_dashdash = TRUE;
6806                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6807                         printf("tig version %s\n", TIG_VERSION);
6808                         quit(0);
6810                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6811                         printf("%s\n", usage);
6812                         quit(0);
6813                 }
6815                 custom_argv[j++] = opt;
6816                 if (j >= ARRAY_SIZE(custom_argv))
6817                         die("command too long");
6818         }
6820         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6821                 die("Failed to format arguments"); 
6823         return request;
6826 int
6827 main(int argc, const char *argv[])
6829         enum request request = parse_options(argc, argv);
6830         struct view *view;
6831         size_t i;
6833         signal(SIGINT, quit);
6835         if (setlocale(LC_ALL, "")) {
6836                 char *codeset = nl_langinfo(CODESET);
6838                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6839         }
6841         if (load_repo_info() == ERR)
6842                 die("Failed to load repo info.");
6844         if (load_options() == ERR)
6845                 die("Failed to load user config.");
6847         if (load_git_config() == ERR)
6848                 die("Failed to load repo config.");
6850         /* Require a git repository unless when running in pager mode. */
6851         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6852                 die("Not a git repository");
6854         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6855                 opt_utf8 = FALSE;
6857         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6858                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6859                 if (opt_iconv == ICONV_NONE)
6860                         die("Failed to initialize character set conversion");
6861         }
6863         if (load_refs() == ERR)
6864                 die("Failed to load refs.");
6866         foreach_view (view, i)
6867                 argv_from_env(view->ops->argv, view->cmd_env);
6869         init_display();
6871         if (request != REQ_NONE)
6872                 open_view(NULL, request, OPEN_PREPARED);
6873         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6875         while (view_driver(display[current_view], request)) {
6876                 int key = get_input(0);
6878                 view = display[current_view];
6879                 request = get_keybinding(view->keymap, key);
6881                 /* Some low-level request handling. This keeps access to
6882                  * status_win restricted. */
6883                 switch (request) {
6884                 case REQ_PROMPT:
6885                 {
6886                         char *cmd = read_prompt(":");
6888                         if (cmd) {
6889                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6890                                 const char *argv[SIZEOF_ARG] = { "git" };
6891                                 int argc = 1;
6893                                 /* When running random commands, initially show the
6894                                  * command in the title. However, it maybe later be
6895                                  * overwritten if a commit line is selected. */
6896                                 string_ncopy(next->ref, cmd, strlen(cmd));
6898                                 if (!argv_from_string(argv, &argc, cmd)) {
6899                                         report("Too many arguments");
6900                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6901                                         report("Failed to format command");
6902                                 } else {
6903                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6904                                 }
6905                         }
6907                         request = REQ_NONE;
6908                         break;
6909                 }
6910                 case REQ_SEARCH:
6911                 case REQ_SEARCH_BACK:
6912                 {
6913                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6914                         char *search = read_prompt(prompt);
6916                         if (search)
6917                                 string_ncopy(opt_search, search, strlen(search));
6918                         else if (*opt_search)
6919                                 request = request == REQ_SEARCH ?
6920                                         REQ_FIND_NEXT :
6921                                         REQ_FIND_PREV;
6922                         else
6923                                 request = REQ_NONE;
6924                         break;
6925                 }
6926                 default:
6927                         break;
6928                 }
6929         }
6931         quit(0);
6933         return 0;