Code

Tune the view clearing to wait until 2 seconds has passed
[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 size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
173 enum input_status {
174         INPUT_OK,
175         INPUT_SKIP,
176         INPUT_STOP,
177         INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216         int namelen;
218         while (isspace(*name))
219                 name++;
221         namelen = strlen(name) - 1;
222         while (namelen > 0 && isspace(name[namelen]))
223                 name[namelen--] = 0;
225         return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231         va_list args;
232         size_t pos = bufpos ? *bufpos : 0;
234         va_start(args, fmt);
235         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236         va_end(args);
238         if (bufpos)
239                 *bufpos = pos;
241         return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245         string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248         string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253         size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257         /* Diff-Header == DIFF_HEADER */
258         for (i = 0; i < len; i++) {
259                 if (toupper(str1[i]) == toupper(str2[i]))
260                         continue;
262                 if (string_enum_sep(str1[i]) &&
263                     string_enum_sep(str2[i]))
264                         continue;
266                 return str1[i] - str2[i];
267         }
269         return 0;
272 #define prefixcmp(str1, str2) \
273         strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278         size_t len = slen >= 0 ? slen : strlen(str);
279         size_t suffixlen = strlen(suffix);
281         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288         int valuelen;
290         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291                 bool advance = cmd[valuelen] != 0;
293                 cmd[valuelen] = 0;
294                 argv[(*argc)++] = chomp_string(cmd);
295                 cmd = chomp_string(cmd + valuelen + advance);
296         }
298         if (*argc < SIZEOF_ARG)
299                 argv[*argc] = NULL;
300         return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306         char *env = argv ? getenv(name) : NULL;
307         int argc = 0;
309         if (env && *env)
310                 env = strdup(env);
311         if (env && !argv_from_string(argv, &argc, env))
312                 die("Too many arguments in the `%s` environment variable", name);
316 /*
317  * Executing external commands.
318  */
320 enum io_type {
321         IO_FD,                  /* File descriptor based IO. */
322         IO_BG,                  /* Execute command in the background. */
323         IO_FG,                  /* Execute command with same std{in,out,err}. */
324         IO_RD,                  /* Read only fork+exec IO. */
325         IO_WR,                  /* Write only fork+exec IO. */
326         IO_AP,                  /* Append fork+exec output to file. */
327 };
329 struct io {
330         enum io_type type;      /* The requested type of pipe. */
331         const char *dir;        /* Directory from which to execute. */
332         pid_t pid;              /* Pipe for reading or writing. */
333         int pipe;               /* Pipe end for reading or writing. */
334         int error;              /* Error status. */
335         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
336         char *buf;              /* Read buffer. */
337         size_t bufalloc;        /* Allocated buffer size. */
338         size_t bufsize;         /* Buffer content size. */
339         char *bufpos;           /* Current buffer position. */
340         unsigned int eof:1;     /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
346         io->pipe = -1;
347         io->pid = 0;
348         io->buf = io->bufpos = NULL;
349         io->bufalloc = io->bufsize = 0;
350         io->error = 0;
351         io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357         reset_io(io);
358         io->type = type;
359         io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364                 enum format_flags flags)
366         init_io(io, dir, IO_RD);
367         return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373         init_io(io, NULL, IO_FD);
374         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375         return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387         pid_t pid = io->pid;
389         if (io->pipe != -1)
390                 close(io->pipe);
391         free(io->buf);
392         reset_io(io);
394         while (pid > 0) {
395                 int status;
396                 pid_t waiting = waitpid(pid, &status, 0);
398                 if (waiting < 0) {
399                         if (errno == EINTR)
400                                 continue;
401                         report("waitpid failed (%s)", strerror(errno));
402                         return FALSE;
403                 }
405                 return waiting == pid &&
406                        !WIFSIGNALED(status) &&
407                        WIFEXITED(status) &&
408                        !WEXITSTATUS(status);
409         }
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         int pipefds[2] = { -1, -1 };
419         if (io->type == IO_FD)
420                 return TRUE;
422         if ((io->type == IO_RD || io->type == IO_WR) &&
423             pipe(pipefds) < 0)
424                 return FALSE;
425         else if (io->type == IO_AP)
426                 pipefds[1] = io->pipe;
428         if ((io->pid = fork())) {
429                 if (pipefds[!(io->type == IO_WR)] != -1)
430                         close(pipefds[!(io->type == IO_WR)]);
431                 if (io->pid != -1) {
432                         io->pipe = pipefds[!!(io->type == IO_WR)];
433                         return TRUE;
434                 }
436         } else {
437                 if (io->type != IO_FG) {
438                         int devnull = open("/dev/null", O_RDWR);
439                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
440                         int writefd = (io->type == IO_RD || io->type == IO_AP)
441                                                         ? pipefds[1] : devnull;
443                         dup2(readfd,  STDIN_FILENO);
444                         dup2(writefd, STDOUT_FILENO);
445                         dup2(devnull, STDERR_FILENO);
447                         close(devnull);
448                         if (pipefds[0] != -1)
449                                 close(pipefds[0]);
450                         if (pipefds[1] != -1)
451                                 close(pipefds[1]);
452                 }
454                 if (io->dir && *io->dir && chdir(io->dir) == -1)
455                         die("Failed to change directory: %s", strerror(errno));
457                 execvp(io->argv[0], (char *const*) io->argv);
458                 die("Failed to execute program: %s", strerror(errno));
459         }
461         if (pipefds[!!(io->type == IO_WR)] != -1)
462                 close(pipefds[!!(io->type == IO_WR)]);
463         return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469         init_io(io, dir, type);
470         if (!format_argv(io->argv, argv, FORMAT_NONE))
471                 return FALSE;
472         return start_io(io);
475 static int
476 run_io_do(struct io *io)
478         return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484         struct io io = {};
486         init_io(&io, NULL, IO_BG);
487         if (!format_argv(io.argv, argv, FORMAT_NONE))
488                 return FALSE;
489         return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495         struct io io = {};
497         init_io(&io, dir, IO_FG);
498         if (!format_argv(io.argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506         struct io io = {};
508         init_io(&io, NULL, IO_AP);
509         io.pipe = fd;
510         if (format_argv(io.argv, argv, flags))
511                 return run_io_do(&io);
512         close(fd);
513         return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519         return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525         return io->eof;
528 static int
529 io_error(struct io *io)
531         return io->error;
534 static bool
535 io_strerror(struct io *io)
537         return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543         struct timeval tv = { 0, 500 };
544         fd_set fds;
546         FD_ZERO(&fds);
547         FD_SET(io->pipe, &fds);
549         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555         do {
556                 ssize_t readsize = read(io->pipe, buf, bufsize);
558                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559                         continue;
560                 else if (readsize == -1)
561                         io->error = errno;
562                 else if (readsize == 0)
563                         io->eof = 1;
564                 return readsize;
565         } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571         char *eol;
572         ssize_t readsize;
574         if (!io->buf) {
575                 io->buf = io->bufpos = malloc(BUFSIZ);
576                 if (!io->buf)
577                         return NULL;
578                 io->bufalloc = BUFSIZ;
579                 io->bufsize = 0;
580         }
582         while (TRUE) {
583                 if (io->bufsize > 0) {
584                         eol = memchr(io->bufpos, c, io->bufsize);
585                         if (eol) {
586                                 char *line = io->bufpos;
588                                 *eol = 0;
589                                 io->bufpos = eol + 1;
590                                 io->bufsize -= io->bufpos - line;
591                                 return line;
592                         }
593                 }
595                 if (io_eof(io)) {
596                         if (io->bufsize) {
597                                 io->bufpos[io->bufsize] = 0;
598                                 io->bufsize = 0;
599                                 return io->bufpos;
600                         }
601                         return NULL;
602                 }
604                 if (!can_read)
605                         return NULL;
607                 if (io->bufsize > 0 && io->bufpos > io->buf)
608                         memmove(io->buf, io->bufpos, io->bufsize);
610                 io->bufpos = io->buf;
611                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612                 if (io_error(io))
613                         return NULL;
614                 io->bufsize += readsize;
615         }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621         size_t written = 0;
623         while (!io_error(io) && written < bufsize) {
624                 ssize_t size;
626                 size = write(io->pipe, buf + written, bufsize - written);
627                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628                         continue;
629                 else if (size == -1)
630                         io->error = errno;
631                 else
632                         written += size;
633         }
635         return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641         struct io io = {};
642         bool error;
644         if (!run_io_rd(&io, argv, FORMAT_NONE))
645                 return FALSE;
647         io.buf = io.bufpos = buf;
648         io.bufalloc = bufsize;
649         error = !io_get(&io, '\n', TRUE) && io_error(&io);
650         io.buf = NULL;
652         return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
657 /*
658  * User requests
659  */
661 #define REQ_INFO \
662         /* XXX: Keep the view request first and in sync with views[]. */ \
663         REQ_GROUP("View switching") \
664         REQ_(VIEW_MAIN,         "Show main view"), \
665         REQ_(VIEW_DIFF,         "Show diff view"), \
666         REQ_(VIEW_LOG,          "Show log view"), \
667         REQ_(VIEW_TREE,         "Show tree view"), \
668         REQ_(VIEW_BLOB,         "Show blob view"), \
669         REQ_(VIEW_BLAME,        "Show blame view"), \
670         REQ_(VIEW_HELP,         "Show help page"), \
671         REQ_(VIEW_PAGER,        "Show pager view"), \
672         REQ_(VIEW_STATUS,       "Show status view"), \
673         REQ_(VIEW_STAGE,        "Show stage view"), \
674         \
675         REQ_GROUP("View manipulation") \
676         REQ_(ENTER,             "Enter current line and scroll"), \
677         REQ_(NEXT,              "Move to next"), \
678         REQ_(PREVIOUS,          "Move to previous"), \
679         REQ_(PARENT,            "Move to parent"), \
680         REQ_(VIEW_NEXT,         "Move focus to next view"), \
681         REQ_(REFRESH,           "Reload and refresh"), \
682         REQ_(MAXIMIZE,          "Maximize the current view"), \
683         REQ_(VIEW_CLOSE,        "Close the current view"), \
684         REQ_(QUIT,              "Close all views and quit"), \
685         \
686         REQ_GROUP("View specific requests") \
687         REQ_(STATUS_UPDATE,     "Update file status"), \
688         REQ_(STATUS_REVERT,     "Revert file changes"), \
689         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
690         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
691         \
692         REQ_GROUP("Cursor navigation") \
693         REQ_(MOVE_UP,           "Move cursor one line up"), \
694         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
695         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
696         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
697         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
698         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
699         \
700         REQ_GROUP("Scrolling") \
701         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
702         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
703         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
704         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
705         \
706         REQ_GROUP("Searching") \
707         REQ_(SEARCH,            "Search the view"), \
708         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
709         REQ_(FIND_NEXT,         "Find next search match"), \
710         REQ_(FIND_PREV,         "Find previous search match"), \
711         \
712         REQ_GROUP("Option manipulation") \
713         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
714         REQ_(TOGGLE_DATE,       "Toggle date display"), \
715         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
716         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
717         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
718         \
719         REQ_GROUP("Misc") \
720         REQ_(PROMPT,            "Bring up the prompt"), \
721         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
722         REQ_(SHOW_VERSION,      "Show version information"), \
723         REQ_(STOP_LOADING,      "Stop all loading views"), \
724         REQ_(EDIT,              "Open in editor"), \
725         REQ_(NONE,              "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733         /* Offset all requests to avoid conflicts with ncurses getch values. */
734         REQ_OFFSET = KEY_MAX + 1,
735         REQ_INFO
737 #undef  REQ_GROUP
738 #undef  REQ_
739 };
741 struct request_info {
742         enum request request;
743         const char *name;
744         int namelen;
745         const char *help;
746 };
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751         REQ_INFO
752 #undef  REQ_GROUP
753 #undef  REQ_
754 };
756 static enum request
757 get_request(const char *name)
759         int namelen = strlen(name);
760         int i;
762         for (i = 0; i < ARRAY_SIZE(req_info); i++)
763                 if (req_info[i].namelen == namelen &&
764                     !string_enum_compare(req_info[i].name, name, namelen))
765                         return req_info[i].request;
767         return REQ_NONE;
771 /*
772  * Options
773  */
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig        [options] [revs] [--] [paths]\n"
779 "   or: tig show   [options] [revs] [--] [paths]\n"
780 "   or: tig blame  [rev] path\n"
781 "   or: tig status\n"
782 "   or: tig <      [git command output]\n"
783 "\n"
784 "Options:\n"
785 "  -v, --version   Show version and exit\n"
786 "  -h, --help      Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date                    = TRUE;
790 static bool opt_author                  = TRUE;
791 static bool opt_line_number             = FALSE;
792 static bool opt_line_graphics           = TRUE;
793 static bool opt_rev_graph               = FALSE;
794 static bool opt_show_refs               = TRUE;
795 static int opt_num_interval             = NUMBER_INTERVAL;
796 static int opt_tab_size                 = TAB_SIZE;
797 static int opt_author_cols              = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR]        = "";
799 static char opt_file[SIZEOF_STR]        = "";
800 static char opt_ref[SIZEOF_REF]         = "";
801 static char opt_head[SIZEOF_REF]        = "";
802 static char opt_head_rev[SIZEOF_REV]    = "";
803 static char opt_remote[SIZEOF_REF]      = "";
804 static char opt_encoding[20]            = "UTF-8";
805 static bool opt_utf8                    = TRUE;
806 static char opt_codeset[20]             = "UTF-8";
807 static iconv_t opt_iconv                = ICONV_NONE;
808 static char opt_search[SIZEOF_STR]      = "";
809 static char opt_cdup[SIZEOF_STR]        = "";
810 static char opt_prefix[SIZEOF_STR]      = "";
811 static char opt_git_dir[SIZEOF_STR]     = "";
812 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR]      = "";
814 static FILE *opt_tty                    = NULL;
816 #define is_initial_commit()     (!*opt_head_rev)
817 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822         enum request request = REQ_VIEW_MAIN;
823         const char *subcommand;
824         bool seen_dashdash = FALSE;
825         /* XXX: This is vulnerable to the user overriding options
826          * required for the main view parser. */
827         static const char *custom_argv[SIZEOF_ARG] = {
828                 "git", "log", "--no-color", "--pretty=raw", "--parents",
829                         "--topo-order", NULL
830         };
831         int i, j = 6;
833         if (!isatty(STDIN_FILENO))
834                 return REQ_VIEW_PAGER;
836         if (argc <= 1)
837                 return REQ_VIEW_MAIN;
839         subcommand = argv[1];
840         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841                 if (!strcmp(subcommand, "-S"))
842                         warn("`-S' has been deprecated; use `tig status' instead");
843                 if (argc > 2)
844                         warn("ignoring arguments after `%s'", subcommand);
845                 return REQ_VIEW_STATUS;
847         } else if (!strcmp(subcommand, "blame")) {
848                 if (argc <= 2 || argc > 4)
849                         die("invalid number of options to blame\n\n%s", usage);
851                 i = 2;
852                 if (argc == 4) {
853                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854                         i++;
855                 }
857                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858                 return REQ_VIEW_BLAME;
860         } else if (!strcmp(subcommand, "show")) {
861                 request = REQ_VIEW_DIFF;
863         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865                 warn("`tig %s' has been deprecated", subcommand);
867         } else {
868                 subcommand = NULL;
869         }
871         if (subcommand) {
872                 custom_argv[1] = subcommand;
873                 j = 2;
874         }
876         for (i = 1 + !!subcommand; i < argc; i++) {
877                 const char *opt = argv[i];
879                 if (seen_dashdash || !strcmp(opt, "--")) {
880                         seen_dashdash = TRUE;
882                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883                         printf("tig version %s\n", TIG_VERSION);
884                         return REQ_NONE;
886                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887                         printf("%s\n", usage);
888                         return REQ_NONE;
889                 }
891                 custom_argv[j++] = opt;
892                 if (j >= ARRAY_SIZE(custom_argv))
893                         die("command too long");
894         }
896         custom_argv[j] = NULL;
897         *run_argv = custom_argv;
899         return request;
903 /*
904  * Line-oriented content detection.
905  */
907 #define LINE_INFO \
908 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
910 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
911 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
913 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
921 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
922 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
925 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
927 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
929 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
932 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
933 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
935 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
936 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
937 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
938 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
939 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
940 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
941 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
942 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
943 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
944 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
945 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
946 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
950 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
951 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
952 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
954 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
955 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
956 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
957 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
958 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
959 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
960 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
962 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
963 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967         LINE_##type
968         LINE_INFO,
969         LINE_NONE
970 #undef  LINE
971 };
973 struct line_info {
974         const char *name;       /* Option name. */
975         int namelen;            /* Size of option name. */
976         const char *line;       /* The start of line to match. */
977         int linelen;            /* Size of string to match. */
978         int fg, bg, attr;       /* Color and text attributes for the lines. */
979 };
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984         LINE_INFO
985 #undef  LINE
986 };
988 static enum line_type
989 get_line_type(const char *line)
991         int linelen = strlen(line);
992         enum line_type type;
994         for (type = 0; type < ARRAY_SIZE(line_info); type++)
995                 /* Case insensitive search matches Signed-off-by lines better. */
996                 if (linelen >= line_info[type].linelen &&
997                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998                         return type;
1000         return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006         assert(type < ARRAY_SIZE(line_info));
1007         return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013         size_t namelen = strlen(name);
1014         enum line_type type;
1016         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017                 if (namelen == line_info[type].namelen &&
1018                     !string_enum_compare(line_info[type].name, name, namelen))
1019                         return &line_info[type];
1021         return NULL;
1024 static void
1025 init_colors(void)
1027         int default_bg = line_info[LINE_DEFAULT].bg;
1028         int default_fg = line_info[LINE_DEFAULT].fg;
1029         enum line_type type;
1031         start_color();
1033         if (assume_default_colors(default_fg, default_bg) == ERR) {
1034                 default_bg = COLOR_BLACK;
1035                 default_fg = COLOR_WHITE;
1036         }
1038         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039                 struct line_info *info = &line_info[type];
1040                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043                 init_pair(type, fg, bg);
1044         }
1047 struct line {
1048         enum line_type type;
1050         /* State flags */
1051         unsigned int selected:1;
1052         unsigned int dirty:1;
1053         unsigned int cleareol:1;
1055         void *data;             /* User data */
1056 };
1059 /*
1060  * Keys
1061  */
1063 struct keybinding {
1064         int alias;
1065         enum request request;
1066 };
1068 static struct keybinding default_keybindings[] = {
1069         /* View switching */
1070         { 'm',          REQ_VIEW_MAIN },
1071         { 'd',          REQ_VIEW_DIFF },
1072         { 'l',          REQ_VIEW_LOG },
1073         { 't',          REQ_VIEW_TREE },
1074         { 'f',          REQ_VIEW_BLOB },
1075         { 'B',          REQ_VIEW_BLAME },
1076         { 'p',          REQ_VIEW_PAGER },
1077         { 'h',          REQ_VIEW_HELP },
1078         { 'S',          REQ_VIEW_STATUS },
1079         { 'c',          REQ_VIEW_STAGE },
1081         /* View manipulation */
1082         { 'q',          REQ_VIEW_CLOSE },
1083         { KEY_TAB,      REQ_VIEW_NEXT },
1084         { KEY_RETURN,   REQ_ENTER },
1085         { KEY_UP,       REQ_PREVIOUS },
1086         { KEY_DOWN,     REQ_NEXT },
1087         { 'R',          REQ_REFRESH },
1088         { KEY_F(5),     REQ_REFRESH },
1089         { 'O',          REQ_MAXIMIZE },
1091         /* Cursor navigation */
1092         { 'k',          REQ_MOVE_UP },
1093         { 'j',          REQ_MOVE_DOWN },
1094         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1095         { KEY_END,      REQ_MOVE_LAST_LINE },
1096         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1097         { ' ',          REQ_MOVE_PAGE_DOWN },
1098         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1099         { 'b',          REQ_MOVE_PAGE_UP },
1100         { '-',          REQ_MOVE_PAGE_UP },
1102         /* Scrolling */
1103         { KEY_IC,       REQ_SCROLL_LINE_UP },
1104         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1105         { 'w',          REQ_SCROLL_PAGE_UP },
1106         { 's',          REQ_SCROLL_PAGE_DOWN },
1108         /* Searching */
1109         { '/',          REQ_SEARCH },
1110         { '?',          REQ_SEARCH_BACK },
1111         { 'n',          REQ_FIND_NEXT },
1112         { 'N',          REQ_FIND_PREV },
1114         /* Misc */
1115         { 'Q',          REQ_QUIT },
1116         { 'z',          REQ_STOP_LOADING },
1117         { 'v',          REQ_SHOW_VERSION },
1118         { 'r',          REQ_SCREEN_REDRAW },
1119         { '.',          REQ_TOGGLE_LINENO },
1120         { 'D',          REQ_TOGGLE_DATE },
1121         { 'A',          REQ_TOGGLE_AUTHOR },
1122         { 'g',          REQ_TOGGLE_REV_GRAPH },
1123         { 'F',          REQ_TOGGLE_REFS },
1124         { ':',          REQ_PROMPT },
1125         { 'u',          REQ_STATUS_UPDATE },
1126         { '!',          REQ_STATUS_REVERT },
1127         { 'M',          REQ_STATUS_MERGE },
1128         { '@',          REQ_STAGE_NEXT },
1129         { ',',          REQ_PARENT },
1130         { 'e',          REQ_EDIT },
1131 };
1133 #define KEYMAP_INFO \
1134         KEYMAP_(GENERIC), \
1135         KEYMAP_(MAIN), \
1136         KEYMAP_(DIFF), \
1137         KEYMAP_(LOG), \
1138         KEYMAP_(TREE), \
1139         KEYMAP_(BLOB), \
1140         KEYMAP_(BLAME), \
1141         KEYMAP_(PAGER), \
1142         KEYMAP_(HELP), \
1143         KEYMAP_(STATUS), \
1144         KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148         KEYMAP_INFO
1149 #undef  KEYMAP_
1150 };
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154         KEYMAP_INFO
1155 #undef  KEYMAP_
1156 };
1158 #define set_keymap(map, name) \
1159         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162         struct keybinding *data;
1163         size_t size;
1164 };
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171         struct keybinding_table *table = &keybindings[keymap];
1173         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174         if (!table->data)
1175                 die("Failed to allocate keybinding");
1176         table->data[table->size].alias = key;
1177         table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181  * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185         size_t i;
1187         for (i = 0; i < keybindings[keymap].size; i++)
1188                 if (keybindings[keymap].data[i].alias == key)
1189                         return keybindings[keymap].data[i].request;
1191         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193                         return keybindings[KEYMAP_GENERIC].data[i].request;
1195         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196                 if (default_keybindings[i].alias == key)
1197                         return default_keybindings[i].request;
1199         return (enum request) key;
1203 struct key {
1204         const char *name;
1205         int value;
1206 };
1208 static struct key key_table[] = {
1209         { "Enter",      KEY_RETURN },
1210         { "Space",      ' ' },
1211         { "Backspace",  KEY_BACKSPACE },
1212         { "Tab",        KEY_TAB },
1213         { "Escape",     KEY_ESC },
1214         { "Left",       KEY_LEFT },
1215         { "Right",      KEY_RIGHT },
1216         { "Up",         KEY_UP },
1217         { "Down",       KEY_DOWN },
1218         { "Insert",     KEY_IC },
1219         { "Delete",     KEY_DC },
1220         { "Hash",       '#' },
1221         { "Home",       KEY_HOME },
1222         { "End",        KEY_END },
1223         { "PageUp",     KEY_PPAGE },
1224         { "PageDown",   KEY_NPAGE },
1225         { "F1",         KEY_F(1) },
1226         { "F2",         KEY_F(2) },
1227         { "F3",         KEY_F(3) },
1228         { "F4",         KEY_F(4) },
1229         { "F5",         KEY_F(5) },
1230         { "F6",         KEY_F(6) },
1231         { "F7",         KEY_F(7) },
1232         { "F8",         KEY_F(8) },
1233         { "F9",         KEY_F(9) },
1234         { "F10",        KEY_F(10) },
1235         { "F11",        KEY_F(11) },
1236         { "F12",        KEY_F(12) },
1237 };
1239 static int
1240 get_key_value(const char *name)
1242         int i;
1244         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245                 if (!strcasecmp(key_table[i].name, name))
1246                         return key_table[i].value;
1248         if (strlen(name) == 1 && isprint(*name))
1249                 return (int) *name;
1251         return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257         static char key_char[] = "'X'";
1258         const char *seq = NULL;
1259         int key;
1261         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262                 if (key_table[key].value == key_value)
1263                         seq = key_table[key].name;
1265         if (seq == NULL &&
1266             key_value < 127 &&
1267             isprint(key_value)) {
1268                 key_char[1] = (char) key_value;
1269                 seq = key_char;
1270         }
1272         return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278         static char buf[BUFSIZ];
1279         size_t pos = 0;
1280         char *sep = "";
1281         int i;
1283         buf[pos] = 0;
1285         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286                 struct keybinding *keybinding = &default_keybindings[i];
1288                 if (keybinding->request != request)
1289                         continue;
1291                 if (!string_format_from(buf, &pos, "%s%s", sep,
1292                                         get_key_name(keybinding->alias)))
1293                         return "Too many keybindings!";
1294                 sep = ", ";
1295         }
1297         return buf;
1300 struct run_request {
1301         enum keymap keymap;
1302         int key;
1303         const char *argv[SIZEOF_ARG];
1304 };
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312         struct run_request *req;
1314         if (argc >= ARRAY_SIZE(req->argv) - 1)
1315                 return REQ_NONE;
1317         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318         if (!req)
1319                 return REQ_NONE;
1321         run_request = req;
1322         req = &run_request[run_requests];
1323         req->keymap = keymap;
1324         req->key = key;
1325         req->argv[0] = NULL;
1327         if (!format_argv(req->argv, argv, FORMAT_NONE))
1328                 return REQ_NONE;
1330         return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336         if (request <= REQ_NONE)
1337                 return NULL;
1338         return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345         const char *gc[] = { "git", "gc", NULL };
1346         struct {
1347                 enum keymap keymap;
1348                 int key;
1349                 int argc;
1350                 const char **argv;
1351         } reqs[] = {
1352                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354         };
1355         int i;
1357         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358                 enum request req;
1360                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361                 if (req != REQ_NONE)
1362                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1363         }
1366 /*
1367  * User config file handling.
1368  */
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372         COLOR_MAP(DEFAULT),
1373         COLOR_MAP(BLACK),
1374         COLOR_MAP(BLUE),
1375         COLOR_MAP(CYAN),
1376         COLOR_MAP(GREEN),
1377         COLOR_MAP(MAGENTA),
1378         COLOR_MAP(RED),
1379         COLOR_MAP(WHITE),
1380         COLOR_MAP(YELLOW),
1381 };
1383 #define set_color(color, name) \
1384         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388         ATTR_MAP(NORMAL),
1389         ATTR_MAP(BLINK),
1390         ATTR_MAP(BOLD),
1391         ATTR_MAP(DIM),
1392         ATTR_MAP(REVERSE),
1393         ATTR_MAP(STANDOUT),
1394         ATTR_MAP(UNDERLINE),
1395 };
1397 #define set_attribute(attr, name) \
1398         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int   config_lineno;
1401 static bool  config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408         struct line_info *info;
1410         if (argc != 3 && argc != 4) {
1411                 config_msg = "Wrong number of arguments given to color command";
1412                 return ERR;
1413         }
1415         info = get_line_info(argv[0]);
1416         if (!info) {
1417                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418                         info = get_line_info("delimiter");
1420                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421                         info = get_line_info("date");
1423                 } else {
1424                         config_msg = "Unknown color name";
1425                         return ERR;
1426                 }
1427         }
1429         if (set_color(&info->fg, argv[1]) == ERR ||
1430             set_color(&info->bg, argv[2]) == ERR) {
1431                 config_msg = "Unknown color";
1432                 return ERR;
1433         }
1435         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436                 config_msg = "Unknown attribute";
1437                 return ERR;
1438         }
1440         return OK;
1443 static bool parse_bool(const char *s)
1445         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446                 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452         int value = atoi(s);
1454         return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461         if (argc != 3) {
1462                 config_msg = "Wrong number of arguments given to set command";
1463                 return ERR;
1464         }
1466         if (strcmp(argv[1], "=")) {
1467                 config_msg = "No value assigned";
1468                 return ERR;
1469         }
1471         if (!strcmp(argv[0], "show-author")) {
1472                 opt_author = parse_bool(argv[2]);
1473                 return OK;
1474         }
1476         if (!strcmp(argv[0], "show-date")) {
1477                 opt_date = parse_bool(argv[2]);
1478                 return OK;
1479         }
1481         if (!strcmp(argv[0], "show-rev-graph")) {
1482                 opt_rev_graph = parse_bool(argv[2]);
1483                 return OK;
1484         }
1486         if (!strcmp(argv[0], "show-refs")) {
1487                 opt_show_refs = parse_bool(argv[2]);
1488                 return OK;
1489         }
1491         if (!strcmp(argv[0], "show-line-numbers")) {
1492                 opt_line_number = parse_bool(argv[2]);
1493                 return OK;
1494         }
1496         if (!strcmp(argv[0], "line-graphics")) {
1497                 opt_line_graphics = parse_bool(argv[2]);
1498                 return OK;
1499         }
1501         if (!strcmp(argv[0], "line-number-interval")) {
1502                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503                 return OK;
1504         }
1506         if (!strcmp(argv[0], "author-width")) {
1507                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508                 return OK;
1509         }
1511         if (!strcmp(argv[0], "tab-size")) {
1512                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513                 return OK;
1514         }
1516         if (!strcmp(argv[0], "commit-encoding")) {
1517                 const char *arg = argv[2];
1518                 int arglen = strlen(arg);
1520                 switch (arg[0]) {
1521                 case '"':
1522                 case '\'':
1523                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524                                 config_msg = "Unmatched quotation";
1525                                 return ERR;
1526                         }
1527                         arg += 1; arglen -= 2;
1528                 default:
1529                         string_ncopy(opt_encoding, arg, strlen(arg));
1530                         return OK;
1531                 }
1532         }
1534         config_msg = "Unknown variable name";
1535         return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542         enum request request;
1543         int keymap;
1544         int key;
1546         if (argc < 3) {
1547                 config_msg = "Wrong number of arguments given to bind command";
1548                 return ERR;
1549         }
1551         if (set_keymap(&keymap, argv[0]) == ERR) {
1552                 config_msg = "Unknown key map";
1553                 return ERR;
1554         }
1556         key = get_key_value(argv[1]);
1557         if (key == ERR) {
1558                 config_msg = "Unknown key";
1559                 return ERR;
1560         }
1562         request = get_request(argv[2]);
1563         if (request == REQ_NONE) {
1564                 struct {
1565                         const char *name;
1566                         enum request request;
1567                 } obsolete[] = {
1568                         { "cherry-pick",        REQ_NONE },
1569                         { "screen-resize",      REQ_NONE },
1570                         { "tree-parent",        REQ_PARENT },
1571                 };
1572                 size_t namelen = strlen(argv[2]);
1573                 int i;
1575                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576                         if (namelen != strlen(obsolete[i].name) ||
1577                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1578                                 continue;
1579                         if (obsolete[i].request != REQ_NONE)
1580                                 add_keybinding(keymap, obsolete[i].request, key);
1581                         config_msg = "Obsolete request name";
1582                         return ERR;
1583                 }
1584         }
1585         if (request == REQ_NONE && *argv[2]++ == '!')
1586                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587         if (request == REQ_NONE) {
1588                 config_msg = "Unknown request name";
1589                 return ERR;
1590         }
1592         add_keybinding(keymap, request, key);
1594         return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600         const char *argv[SIZEOF_ARG];
1601         int argc = 0;
1603         if (!argv_from_string(argv, &argc, value)) {
1604                 config_msg = "Too many option arguments";
1605                 return ERR;
1606         }
1608         if (!strcmp(opt, "color"))
1609                 return option_color_command(argc, argv);
1611         if (!strcmp(opt, "set"))
1612                 return option_set_command(argc, argv);
1614         if (!strcmp(opt, "bind"))
1615                 return option_bind_command(argc, argv);
1617         config_msg = "Unknown option command";
1618         return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624         int status = OK;
1626         config_lineno++;
1627         config_msg = "Internal error";
1629         /* Check for comment markers, since read_properties() will
1630          * only ensure opt and value are split at first " \t". */
1631         optlen = strcspn(opt, "#");
1632         if (optlen == 0)
1633                 return OK;
1635         if (opt[optlen] != 0) {
1636                 config_msg = "No option value";
1637                 status = ERR;
1639         }  else {
1640                 /* Look for comment endings in the value. */
1641                 size_t len = strcspn(value, "#");
1643                 if (len < valuelen) {
1644                         valuelen = len;
1645                         value[valuelen] = 0;
1646                 }
1648                 status = set_option(opt, value);
1649         }
1651         if (status == ERR) {
1652                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653                         config_lineno, (int) optlen, opt, config_msg);
1654                 config_errors = TRUE;
1655         }
1657         /* Always keep going if errors are encountered. */
1658         return OK;
1661 static void
1662 load_option_file(const char *path)
1664         struct io io = {};
1666         /* It's ok that the file doesn't exist. */
1667         if (!io_open(&io, path))
1668                 return;
1670         config_lineno = 0;
1671         config_errors = FALSE;
1673         if (read_properties(&io, " \t", read_option) == ERR ||
1674             config_errors == TRUE)
1675                 fprintf(stderr, "Errors while loading %s.\n", path);
1678 static int
1679 load_options(void)
1681         const char *home = getenv("HOME");
1682         const char *tigrc_user = getenv("TIGRC_USER");
1683         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684         char buf[SIZEOF_STR];
1686         add_builtin_run_requests();
1688         if (!tigrc_system) {
1689                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690                         return ERR;
1691                 tigrc_system = buf;
1692         }
1693         load_option_file(tigrc_system);
1695         if (!tigrc_user) {
1696                 if (!home || !string_format(buf, "%s/.tigrc", home))
1697                         return ERR;
1698                 tigrc_user = buf;
1699         }
1700         load_option_file(tigrc_user);
1702         return OK;
1706 /*
1707  * The viewer
1708  */
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 #define foreach_displayed_view(view, i) \
1718         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1720 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1722 /* Current head and commit ID */
1723 static char ref_blob[SIZEOF_REF]        = "";
1724 static char ref_commit[SIZEOF_REF]      = "HEAD";
1725 static char ref_head[SIZEOF_REF]        = "HEAD";
1727 struct view {
1728         const char *name;       /* View name */
1729         const char *cmd_env;    /* Command line set via environment */
1730         const char *id;         /* Points to either of ref_{head,commit,blob} */
1732         struct view_ops *ops;   /* View operations */
1734         enum keymap keymap;     /* What keymap does this view have */
1735         bool git_dir;           /* Whether the view requires a git directory. */
1737         char ref[SIZEOF_REF];   /* Hovered commit reference */
1738         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1740         int height, width;      /* The width and height of the main window */
1741         WINDOW *win;            /* The main window */
1742         WINDOW *title;          /* The title window living below the main window */
1744         /* Navigation */
1745         unsigned long offset;   /* Offset of the window top */
1746         unsigned long lineno;   /* Current line number */
1747         unsigned long p_offset; /* Previous offset of the window top */
1748         unsigned long p_lineno; /* Previous current line number */
1749         bool p_restore;         /* Should the previous position be restored. */
1751         /* Searching */
1752         char grep[SIZEOF_STR];  /* Search string */
1753         regex_t *regex;         /* Pre-compiled regex */
1755         /* If non-NULL, points to the view that opened this view. If this view
1756          * is closed tig will switch back to the parent view. */
1757         struct view *parent;
1759         /* Buffering */
1760         size_t lines;           /* Total number of lines */
1761         struct line *line;      /* Line index */
1762         size_t line_alloc;      /* Total number of allocated lines */
1763         unsigned int digits;    /* Number of digits in the lines member. */
1765         /* Drawing */
1766         struct line *curline;   /* Line currently being drawn. */
1767         enum line_type curtype; /* Attribute currently used for drawing. */
1768         unsigned long col;      /* Column when drawing. */
1770         /* Loading */
1771         struct io io;
1772         struct io *pipe;
1773         time_t start_time;
1774         time_t update_secs;
1775 };
1777 struct view_ops {
1778         /* What type of content being displayed. Used in the title bar. */
1779         const char *type;
1780         /* Default command arguments. */
1781         const char **argv;
1782         /* Open and reads in all view content. */
1783         bool (*open)(struct view *view);
1784         /* Read one line; updates view->line. */
1785         bool (*read)(struct view *view, char *data);
1786         /* Draw one line; @lineno must be < view->height. */
1787         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1788         /* Depending on view handle a special requests. */
1789         enum request (*request)(struct view *view, enum request request, struct line *line);
1790         /* Search for regex in a line. */
1791         bool (*grep)(struct view *view, struct line *line);
1792         /* Select line */
1793         void (*select)(struct view *view, struct line *line);
1794 };
1796 static struct view_ops blame_ops;
1797 static struct view_ops blob_ops;
1798 static struct view_ops diff_ops;
1799 static struct view_ops help_ops;
1800 static struct view_ops log_ops;
1801 static struct view_ops main_ops;
1802 static struct view_ops pager_ops;
1803 static struct view_ops stage_ops;
1804 static struct view_ops status_ops;
1805 static struct view_ops tree_ops;
1807 #define VIEW_STR(name, env, ref, ops, map, git) \
1808         { name, #env, ref, ops, map, git }
1810 #define VIEW_(id, name, ops, git, ref) \
1811         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1814 static struct view views[] = {
1815         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1816         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1817         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1818         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1819         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1820         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1821         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1822         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1823         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1824         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1825 };
1827 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1828 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1830 #define foreach_view(view, i) \
1831         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1833 #define view_is_displayed(view) \
1834         (view == display[0] || view == display[1])
1837 enum line_graphic {
1838         LINE_GRAPHIC_VLINE
1839 };
1841 static int line_graphics[] = {
1842         /* LINE_GRAPHIC_VLINE: */ '|'
1843 };
1845 static inline void
1846 set_view_attr(struct view *view, enum line_type type)
1848         if (!view->curline->selected && view->curtype != type) {
1849                 wattrset(view->win, get_line_attr(type));
1850                 wchgat(view->win, -1, 0, type, NULL);
1851                 view->curtype = type;
1852         }
1855 static int
1856 draw_chars(struct view *view, enum line_type type, const char *string,
1857            int max_len, bool use_tilde)
1859         int len = 0;
1860         int col = 0;
1861         int trimmed = FALSE;
1863         if (max_len <= 0)
1864                 return 0;
1866         if (opt_utf8) {
1867                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1868         } else {
1869                 col = len = strlen(string);
1870                 if (len > max_len) {
1871                         if (use_tilde) {
1872                                 max_len -= 1;
1873                         }
1874                         col = len = max_len;
1875                         trimmed = TRUE;
1876                 }
1877         }
1879         set_view_attr(view, type);
1880         waddnstr(view->win, string, len);
1881         if (trimmed && use_tilde) {
1882                 set_view_attr(view, LINE_DELIMITER);
1883                 waddch(view->win, '~');
1884                 col++;
1885         }
1887         return col;
1890 static int
1891 draw_space(struct view *view, enum line_type type, int max, int spaces)
1893         static char space[] = "                    ";
1894         int col = 0;
1896         spaces = MIN(max, spaces);
1898         while (spaces > 0) {
1899                 int len = MIN(spaces, sizeof(space) - 1);
1901                 col += draw_chars(view, type, space, spaces, FALSE);
1902                 spaces -= len;
1903         }
1905         return col;
1908 static bool
1909 draw_lineno(struct view *view, unsigned int lineno)
1911         char number[10];
1912         int digits3 = view->digits < 3 ? 3 : view->digits;
1913         int max_number = MIN(digits3, STRING_SIZE(number));
1914         int max = view->width - view->col;
1915         int col;
1917         if (max < max_number)
1918                 max_number = max;
1920         lineno += view->offset + 1;
1921         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1922                 static char fmt[] = "%1ld";
1924                 if (view->digits <= 9)
1925                         fmt[1] = '0' + digits3;
1927                 if (!string_format(number, fmt, lineno))
1928                         number[0] = 0;
1929                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1930         } else {
1931                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1932         }
1934         if (col < max) {
1935                 set_view_attr(view, LINE_DEFAULT);
1936                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1937                 col++;
1938         }
1940         if (col < max)
1941                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1942         view->col += col;
1944         return view->width - view->col <= 0;
1947 static bool
1948 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1950         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1951         return view->width - view->col <= 0;
1954 static bool
1955 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1957         int max = view->width - view->col;
1958         int i;
1960         if (max < size)
1961                 size = max;
1963         set_view_attr(view, type);
1964         /* Using waddch() instead of waddnstr() ensures that
1965          * they'll be rendered correctly for the cursor line. */
1966         for (i = 0; i < size; i++)
1967                 waddch(view->win, graphic[i]);
1969         view->col += size;
1970         if (size < max) {
1971                 waddch(view->win, ' ');
1972                 view->col++;
1973         }
1975         return view->width - view->col <= 0;
1978 static bool
1979 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1981         int max = MIN(view->width - view->col, len);
1982         int col;
1984         if (text)
1985                 col = draw_chars(view, type, text, max - 1, trim);
1986         else
1987                 col = draw_space(view, type, max - 1, max - 1);
1989         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1990         return view->width - view->col <= 0;
1993 static bool
1994 draw_date(struct view *view, struct tm *time)
1996         char buf[DATE_COLS];
1997         char *date;
1998         int timelen = 0;
2000         if (time)
2001                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2002         date = timelen ? buf : NULL;
2004         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2007 static bool
2008 draw_author(struct view *view, const char *author)
2010         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2012         if (!trim) {
2013                 static char initials[10];
2014                 size_t pos;
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018                 memset(initials, 0, sizeof(initials));
2019                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020                         while (is_initial_sep(*author))
2021                                 author++;
2022                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023                         while (*author && !is_initial_sep(author[1]))
2024                                 author++;
2025                 }
2027                 author = initials;
2028         }
2030         return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2033 static bool
2034 draw_view_line(struct view *view, unsigned int lineno)
2036         struct line *line;
2037         bool selected = (view->offset + lineno == view->lineno);
2039         assert(view_is_displayed(view));
2041         if (view->offset + lineno >= view->lines)
2042                 return FALSE;
2044         line = &view->line[view->offset + lineno];
2046         wmove(view->win, lineno, 0);
2047         if (line->cleareol)
2048                 wclrtoeol(view->win);
2049         view->col = 0;
2050         view->curline = line;
2051         view->curtype = LINE_NONE;
2052         line->selected = FALSE;
2053         line->dirty = line->cleareol = 0;
2055         if (selected) {
2056                 set_view_attr(view, LINE_CURSOR);
2057                 line->selected = TRUE;
2058                 view->ops->select(view, line);
2059         }
2061         return view->ops->draw(view, line, lineno);
2064 static void
2065 redraw_view_dirty(struct view *view)
2067         bool dirty = FALSE;
2068         int lineno;
2070         for (lineno = 0; lineno < view->height; lineno++) {
2071                 if (view->offset + lineno >= view->lines)
2072                         break;
2073                 if (!view->line[view->offset + lineno].dirty)
2074                         continue;
2075                 dirty = TRUE;
2076                 if (!draw_view_line(view, lineno))
2077                         break;
2078         }
2080         if (!dirty)
2081                 return;
2082         wnoutrefresh(view->win);
2085 static void
2086 redraw_view_from(struct view *view, int lineno)
2088         assert(0 <= lineno && lineno < view->height);
2090         for (; lineno < view->height; lineno++) {
2091                 if (!draw_view_line(view, lineno))
2092                         break;
2093         }
2095         wnoutrefresh(view->win);
2098 static void
2099 redraw_view(struct view *view)
2101         werase(view->win);
2102         redraw_view_from(view, 0);
2106 static void
2107 update_view_title(struct view *view)
2109         char buf[SIZEOF_STR];
2110         char state[SIZEOF_STR];
2111         size_t bufpos = 0, statelen = 0;
2113         assert(view_is_displayed(view));
2115         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2116                 unsigned int view_lines = view->offset + view->height;
2117                 unsigned int lines = view->lines
2118                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2119                                    : 0;
2121                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2122                                    view->ops->type,
2123                                    view->lineno + 1,
2124                                    view->lines,
2125                                    lines);
2127         }
2129         if (view->pipe) {
2130                 time_t secs = time(NULL) - view->start_time;
2132                 /* Three git seconds are a long time ... */
2133                 if (secs > 2)
2134                         string_format_from(state, &statelen, " loading %lds", secs);
2135         }
2137         string_format_from(buf, &bufpos, "[%s]", view->name);
2138         if (*view->ref && bufpos < view->width) {
2139                 size_t refsize = strlen(view->ref);
2140                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2142                 if (minsize < view->width)
2143                         refsize = view->width - minsize + 7;
2144                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2145         }
2147         if (statelen && bufpos < view->width) {
2148                 string_format_from(buf, &bufpos, "%s", state);
2149         }
2151         if (view == display[current_view])
2152                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2153         else
2154                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2156         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2157         wclrtoeol(view->title);
2158         wnoutrefresh(view->title);
2161 static void
2162 resize_display(void)
2164         int offset, i;
2165         struct view *base = display[0];
2166         struct view *view = display[1] ? display[1] : display[0];
2168         /* Setup window dimensions */
2170         getmaxyx(stdscr, base->height, base->width);
2172         /* Make room for the status window. */
2173         base->height -= 1;
2175         if (view != base) {
2176                 /* Horizontal split. */
2177                 view->width   = base->width;
2178                 view->height  = SCALE_SPLIT_VIEW(base->height);
2179                 base->height -= view->height;
2181                 /* Make room for the title bar. */
2182                 view->height -= 1;
2183         }
2185         /* Make room for the title bar. */
2186         base->height -= 1;
2188         offset = 0;
2190         foreach_displayed_view (view, i) {
2191                 if (!view->win) {
2192                         view->win = newwin(view->height, 0, offset, 0);
2193                         if (!view->win)
2194                                 die("Failed to create %s view", view->name);
2196                         scrollok(view->win, FALSE);
2198                         view->title = newwin(1, 0, offset + view->height, 0);
2199                         if (!view->title)
2200                                 die("Failed to create title window");
2202                 } else {
2203                         wresize(view->win, view->height, view->width);
2204                         mvwin(view->win,   offset, 0);
2205                         mvwin(view->title, offset + view->height, 0);
2206                 }
2208                 offset += view->height + 1;
2209         }
2212 static void
2213 redraw_display(bool clear)
2215         struct view *view;
2216         int i;
2218         foreach_displayed_view (view, i) {
2219                 if (clear)
2220                         wclear(view->win);
2221                 redraw_view(view);
2222                 update_view_title(view);
2223         }
2226 static void
2227 toggle_view_option(bool *option, const char *help)
2229         *option = !*option;
2230         redraw_display(FALSE);
2231         report("%sabling %s", *option ? "En" : "Dis", help);
2234 /*
2235  * Navigation
2236  */
2238 /* Scrolling backend */
2239 static void
2240 do_scroll_view(struct view *view, int lines)
2242         bool redraw_current_line = FALSE;
2244         /* The rendering expects the new offset. */
2245         view->offset += lines;
2247         assert(0 <= view->offset && view->offset < view->lines);
2248         assert(lines);
2250         /* Move current line into the view. */
2251         if (view->lineno < view->offset) {
2252                 view->lineno = view->offset;
2253                 redraw_current_line = TRUE;
2254         } else if (view->lineno >= view->offset + view->height) {
2255                 view->lineno = view->offset + view->height - 1;
2256                 redraw_current_line = TRUE;
2257         }
2259         assert(view->offset <= view->lineno && view->lineno < view->lines);
2261         /* Redraw the whole screen if scrolling is pointless. */
2262         if (view->height < ABS(lines)) {
2263                 redraw_view(view);
2265         } else {
2266                 int line = lines > 0 ? view->height - lines : 0;
2267                 int end = line + ABS(lines);
2269                 scrollok(view->win, TRUE);
2270                 wscrl(view->win, lines);
2271                 scrollok(view->win, FALSE);
2273                 while (line < end && draw_view_line(view, line))
2274                         line++;
2276                 if (redraw_current_line)
2277                         draw_view_line(view, view->lineno - view->offset);
2278                 wnoutrefresh(view->win);
2279         }
2281         report("");
2284 /* Scroll frontend */
2285 static void
2286 scroll_view(struct view *view, enum request request)
2288         int lines = 1;
2290         assert(view_is_displayed(view));
2292         switch (request) {
2293         case REQ_SCROLL_PAGE_DOWN:
2294                 lines = view->height;
2295         case REQ_SCROLL_LINE_DOWN:
2296                 if (view->offset + lines > view->lines)
2297                         lines = view->lines - view->offset;
2299                 if (lines == 0 || view->offset + view->height >= view->lines) {
2300                         report("Cannot scroll beyond the last line");
2301                         return;
2302                 }
2303                 break;
2305         case REQ_SCROLL_PAGE_UP:
2306                 lines = view->height;
2307         case REQ_SCROLL_LINE_UP:
2308                 if (lines > view->offset)
2309                         lines = view->offset;
2311                 if (lines == 0) {
2312                         report("Cannot scroll beyond the first line");
2313                         return;
2314                 }
2316                 lines = -lines;
2317                 break;
2319         default:
2320                 die("request %d not handled in switch", request);
2321         }
2323         do_scroll_view(view, lines);
2326 /* Cursor moving */
2327 static void
2328 move_view(struct view *view, enum request request)
2330         int scroll_steps = 0;
2331         int steps;
2333         switch (request) {
2334         case REQ_MOVE_FIRST_LINE:
2335                 steps = -view->lineno;
2336                 break;
2338         case REQ_MOVE_LAST_LINE:
2339                 steps = view->lines - view->lineno - 1;
2340                 break;
2342         case REQ_MOVE_PAGE_UP:
2343                 steps = view->height > view->lineno
2344                       ? -view->lineno : -view->height;
2345                 break;
2347         case REQ_MOVE_PAGE_DOWN:
2348                 steps = view->lineno + view->height >= view->lines
2349                       ? view->lines - view->lineno - 1 : view->height;
2350                 break;
2352         case REQ_MOVE_UP:
2353                 steps = -1;
2354                 break;
2356         case REQ_MOVE_DOWN:
2357                 steps = 1;
2358                 break;
2360         default:
2361                 die("request %d not handled in switch", request);
2362         }
2364         if (steps <= 0 && view->lineno == 0) {
2365                 report("Cannot move beyond the first line");
2366                 return;
2368         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2369                 report("Cannot move beyond the last line");
2370                 return;
2371         }
2373         /* Move the current line */
2374         view->lineno += steps;
2375         assert(0 <= view->lineno && view->lineno < view->lines);
2377         /* Check whether the view needs to be scrolled */
2378         if (view->lineno < view->offset ||
2379             view->lineno >= view->offset + view->height) {
2380                 scroll_steps = steps;
2381                 if (steps < 0 && -steps > view->offset) {
2382                         scroll_steps = -view->offset;
2384                 } else if (steps > 0) {
2385                         if (view->lineno == view->lines - 1 &&
2386                             view->lines > view->height) {
2387                                 scroll_steps = view->lines - view->offset - 1;
2388                                 if (scroll_steps >= view->height)
2389                                         scroll_steps -= view->height - 1;
2390                         }
2391                 }
2392         }
2394         if (!view_is_displayed(view)) {
2395                 view->offset += scroll_steps;
2396                 assert(0 <= view->offset && view->offset < view->lines);
2397                 view->ops->select(view, &view->line[view->lineno]);
2398                 return;
2399         }
2401         /* Repaint the old "current" line if we be scrolling */
2402         if (ABS(steps) < view->height)
2403                 draw_view_line(view, view->lineno - steps - view->offset);
2405         if (scroll_steps) {
2406                 do_scroll_view(view, scroll_steps);
2407                 return;
2408         }
2410         /* Draw the current line */
2411         draw_view_line(view, view->lineno - view->offset);
2413         wnoutrefresh(view->win);
2414         report("");
2418 /*
2419  * Searching
2420  */
2422 static void search_view(struct view *view, enum request request);
2424 static void
2425 select_view_line(struct view *view, unsigned long lineno)
2427         if (lineno - view->offset >= view->height) {
2428                 view->offset = lineno;
2429                 view->lineno = lineno;
2430                 if (view_is_displayed(view))
2431                         redraw_view(view);
2433         } else {
2434                 unsigned long old_lineno = view->lineno - view->offset;
2436                 view->lineno = lineno;
2437                 if (view_is_displayed(view)) {
2438                         draw_view_line(view, old_lineno);
2439                         draw_view_line(view, view->lineno - view->offset);
2440                         wnoutrefresh(view->win);
2441                 } else {
2442                         view->ops->select(view, &view->line[view->lineno]);
2443                 }
2444         }
2447 static void
2448 find_next(struct view *view, enum request request)
2450         unsigned long lineno = view->lineno;
2451         int direction;
2453         if (!*view->grep) {
2454                 if (!*opt_search)
2455                         report("No previous search");
2456                 else
2457                         search_view(view, request);
2458                 return;
2459         }
2461         switch (request) {
2462         case REQ_SEARCH:
2463         case REQ_FIND_NEXT:
2464                 direction = 1;
2465                 break;
2467         case REQ_SEARCH_BACK:
2468         case REQ_FIND_PREV:
2469                 direction = -1;
2470                 break;
2472         default:
2473                 return;
2474         }
2476         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2477                 lineno += direction;
2479         /* Note, lineno is unsigned long so will wrap around in which case it
2480          * will become bigger than view->lines. */
2481         for (; lineno < view->lines; lineno += direction) {
2482                 if (view->ops->grep(view, &view->line[lineno])) {
2483                         select_view_line(view, lineno);
2484                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2485                         return;
2486                 }
2487         }
2489         report("No match found for '%s'", view->grep);
2492 static void
2493 search_view(struct view *view, enum request request)
2495         int regex_err;
2497         if (view->regex) {
2498                 regfree(view->regex);
2499                 *view->grep = 0;
2500         } else {
2501                 view->regex = calloc(1, sizeof(*view->regex));
2502                 if (!view->regex)
2503                         return;
2504         }
2506         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2507         if (regex_err != 0) {
2508                 char buf[SIZEOF_STR] = "unknown error";
2510                 regerror(regex_err, view->regex, buf, sizeof(buf));
2511                 report("Search failed: %s", buf);
2512                 return;
2513         }
2515         string_copy(view->grep, opt_search);
2517         find_next(view, request);
2520 /*
2521  * Incremental updating
2522  */
2524 static void
2525 reset_view(struct view *view)
2527         int i;
2529         for (i = 0; i < view->lines; i++)
2530                 free(view->line[i].data);
2531         free(view->line);
2533         view->p_offset = view->offset;
2534         view->p_lineno = view->lineno;
2536         view->line = NULL;
2537         view->offset = 0;
2538         view->lines  = 0;
2539         view->lineno = 0;
2540         view->line_alloc = 0;
2541         view->vid[0] = 0;
2542         view->update_secs = 0;
2545 static void
2546 free_argv(const char *argv[])
2548         int argc;
2550         for (argc = 0; argv[argc]; argc++)
2551                 free((void *) argv[argc]);
2554 static bool
2555 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2557         char buf[SIZEOF_STR];
2558         int argc;
2559         bool noreplace = flags == FORMAT_NONE;
2561         free_argv(dst_argv);
2563         for (argc = 0; src_argv[argc]; argc++) {
2564                 const char *arg = src_argv[argc];
2565                 size_t bufpos = 0;
2567                 while (arg) {
2568                         char *next = strstr(arg, "%(");
2569                         int len = next - arg;
2570                         const char *value;
2572                         if (!next || noreplace) {
2573                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2574                                         noreplace = TRUE;
2575                                 len = strlen(arg);
2576                                 value = "";
2578                         } else if (!prefixcmp(next, "%(directory)")) {
2579                                 value = opt_path;
2581                         } else if (!prefixcmp(next, "%(file)")) {
2582                                 value = opt_file;
2584                         } else if (!prefixcmp(next, "%(ref)")) {
2585                                 value = *opt_ref ? opt_ref : "HEAD";
2587                         } else if (!prefixcmp(next, "%(head)")) {
2588                                 value = ref_head;
2590                         } else if (!prefixcmp(next, "%(commit)")) {
2591                                 value = ref_commit;
2593                         } else if (!prefixcmp(next, "%(blob)")) {
2594                                 value = ref_blob;
2596                         } else {
2597                                 report("Unknown replacement: `%s`", next);
2598                                 return FALSE;
2599                         }
2601                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2602                                 return FALSE;
2604                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2605                 }
2607                 dst_argv[argc] = strdup(buf);
2608                 if (!dst_argv[argc])
2609                         break;
2610         }
2612         dst_argv[argc] = NULL;
2614         return src_argv[argc] == NULL;
2617 static bool
2618 restore_view_position(struct view *view)
2620         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2621                 return FALSE;
2623         /* Changing the view position cancels the restoring. */
2624         /* FIXME: Changing back to the first line is not detected. */
2625         if (view->offset != 0 || view->lineno != 0) {
2626                 view->p_restore = FALSE;
2627                 return FALSE;
2628         }
2630         if (view->p_lineno >= view->lines) {
2631                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2632                 if (view->p_offset >= view->p_lineno) {
2633                         unsigned long half = view->height / 2;
2635                         if (view->p_lineno > half)
2636                                 view->p_offset = view->p_lineno - half;
2637                         else
2638                                 view->p_offset = 0;
2639                 }
2640         }
2642         if (view_is_displayed(view) &&
2643             view->offset != view->p_offset &&
2644             view->lineno != view->p_lineno)
2645                 werase(view->win);
2647         view->offset = view->p_offset;
2648         view->lineno = view->p_lineno;
2649         view->p_restore = FALSE;
2651         return TRUE;
2654 static void
2655 end_update(struct view *view, bool force)
2657         if (!view->pipe)
2658                 return;
2659         while (!view->ops->read(view, NULL))
2660                 if (!force)
2661                         return;
2662         set_nonblocking_input(FALSE);
2663         if (force)
2664                 kill_io(view->pipe);
2665         done_io(view->pipe);
2666         view->pipe = NULL;
2669 static void
2670 setup_update(struct view *view, const char *vid)
2672         set_nonblocking_input(TRUE);
2673         reset_view(view);
2674         string_copy_rev(view->vid, vid);
2675         view->pipe = &view->io;
2676         view->start_time = time(NULL);
2679 static bool
2680 prepare_update(struct view *view, const char *argv[], const char *dir,
2681                enum format_flags flags)
2683         if (view->pipe)
2684                 end_update(view, TRUE);
2685         return init_io_rd(&view->io, argv, dir, flags);
2688 static bool
2689 prepare_update_file(struct view *view, const char *name)
2691         if (view->pipe)
2692                 end_update(view, TRUE);
2693         return io_open(&view->io, name);
2696 static bool
2697 begin_update(struct view *view, bool refresh)
2699         if (view->pipe)
2700                 end_update(view, TRUE);
2702         if (refresh) {
2703                 if (!start_io(&view->io))
2704                         return FALSE;
2706         } else {
2707                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2708                         opt_path[0] = 0;
2710                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2711                         return FALSE;
2713                 /* Put the current ref_* value to the view title ref
2714                  * member. This is needed by the blob view. Most other
2715                  * views sets it automatically after loading because the
2716                  * first line is a commit line. */
2717                 string_copy_rev(view->ref, view->id);
2718         }
2720         setup_update(view, view->id);
2722         return TRUE;
2725 #define ITEM_CHUNK_SIZE 256
2726 static void *
2727 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2729         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2730         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2732         if (mem == NULL || num_chunks != num_chunks_new) {
2733                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2734                 mem = realloc(mem, *size * item_size);
2735         }
2737         return mem;
2740 static struct line *
2741 realloc_lines(struct view *view, size_t line_size)
2743         size_t alloc = view->line_alloc;
2744         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2745                                          sizeof(*view->line));
2747         if (!tmp)
2748                 return NULL;
2750         view->line = tmp;
2751         view->line_alloc = alloc;
2752         return view->line;
2755 static bool
2756 update_view(struct view *view)
2758         char out_buffer[BUFSIZ * 2];
2759         char *line;
2760         /* Clear the view and redraw everything since the tree sorting
2761          * might have rearranged things. */
2762         bool redraw = view->lines == 0;
2763         bool can_read = TRUE;
2765         if (!view->pipe)
2766                 return TRUE;
2768         if (!io_can_read(view->pipe)) {
2769                 if (view->lines == 0) {
2770                         time_t secs = time(NULL) - view->start_time;
2772                         if (secs > 1 && secs > view->update_secs) {
2773                                 if (view->update_secs == 0)
2774                                         redraw_view(view);
2775                                 update_view_title(view);
2776                                 view->update_secs = secs;
2777                         }
2778                 }
2779                 return TRUE;
2780         }
2782         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2783                 if (opt_iconv != ICONV_NONE) {
2784                         ICONV_CONST char *inbuf = line;
2785                         size_t inlen = strlen(line) + 1;
2787                         char *outbuf = out_buffer;
2788                         size_t outlen = sizeof(out_buffer);
2790                         size_t ret;
2792                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2793                         if (ret != (size_t) -1)
2794                                 line = out_buffer;
2795                 }
2797                 if (!view->ops->read(view, line)) {
2798                         report("Allocation failure");
2799                         end_update(view, TRUE);
2800                         return FALSE;
2801                 }
2802         }
2804         {
2805                 unsigned long lines = view->lines;
2806                 int digits;
2808                 for (digits = 0; lines; digits++)
2809                         lines /= 10;
2811                 /* Keep the displayed view in sync with line number scaling. */
2812                 if (digits != view->digits) {
2813                         view->digits = digits;
2814                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2815                                 redraw = TRUE;
2816                 }
2817         }
2819         if (io_error(view->pipe)) {
2820                 report("Failed to read: %s", io_strerror(view->pipe));
2821                 end_update(view, TRUE);
2823         } else if (io_eof(view->pipe)) {
2824                 report("");
2825                 end_update(view, FALSE);
2826         }
2828         if (restore_view_position(view))
2829                 redraw = TRUE;
2831         if (!view_is_displayed(view))
2832                 return TRUE;
2834         if (redraw)
2835                 redraw_view_from(view, 0);
2836         else
2837                 redraw_view_dirty(view);
2839         /* Update the title _after_ the redraw so that if the redraw picks up a
2840          * commit reference in view->ref it'll be available here. */
2841         update_view_title(view);
2842         return TRUE;
2845 static struct line *
2846 add_line_data(struct view *view, void *data, enum line_type type)
2848         struct line *line;
2850         if (!realloc_lines(view, view->lines + 1))
2851                 return NULL;
2853         line = &view->line[view->lines++];
2854         memset(line, 0, sizeof(*line));
2855         line->type = type;
2856         line->data = data;
2857         line->dirty = 1;
2859         return line;
2862 static struct line *
2863 add_line_text(struct view *view, const char *text, enum line_type type)
2865         char *data = text ? strdup(text) : NULL;
2867         return data ? add_line_data(view, data, type) : NULL;
2870 static struct line *
2871 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2873         char buf[SIZEOF_STR];
2874         va_list args;
2876         va_start(args, fmt);
2877         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2878                 buf[0] = 0;
2879         va_end(args);
2881         return buf[0] ? add_line_text(view, buf, type) : NULL;
2884 /*
2885  * View opening
2886  */
2888 enum open_flags {
2889         OPEN_DEFAULT = 0,       /* Use default view switching. */
2890         OPEN_SPLIT = 1,         /* Split current view. */
2891         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2892         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2893         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2894         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2895         OPEN_PREPARED = 32,     /* Open already prepared command. */
2896 };
2898 static void
2899 open_view(struct view *prev, enum request request, enum open_flags flags)
2901         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2902         bool split = !!(flags & OPEN_SPLIT);
2903         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2904         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2905         struct view *view = VIEW(request);
2906         int nviews = displayed_views();
2907         struct view *base_view = display[0];
2909         if (view == prev && nviews == 1 && !reload) {
2910                 report("Already in %s view", view->name);
2911                 return;
2912         }
2914         if (view->git_dir && !opt_git_dir[0]) {
2915                 report("The %s view is disabled in pager view", view->name);
2916                 return;
2917         }
2919         if (split) {
2920                 display[1] = view;
2921                 if (!backgrounded)
2922                         current_view = 1;
2923         } else if (!nomaximize) {
2924                 /* Maximize the current view. */
2925                 memset(display, 0, sizeof(display));
2926                 current_view = 0;
2927                 display[current_view] = view;
2928         }
2930         /* Resize the view when switching between split- and full-screen,
2931          * or when switching between two different full-screen views. */
2932         if (nviews != displayed_views() ||
2933             (nviews == 1 && base_view != display[0]))
2934                 resize_display();
2936         if (view->ops->open) {
2937                 if (view->pipe)
2938                         end_update(view, TRUE);
2939                 if (!view->ops->open(view)) {
2940                         report("Failed to load %s view", view->name);
2941                         return;
2942                 }
2943                 restore_view_position(view);
2945         } else if ((reload || strcmp(view->vid, view->id)) &&
2946                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2947                 report("Failed to load %s view", view->name);
2948                 return;
2949         }
2951         if (split && prev->lineno - prev->offset >= prev->height) {
2952                 /* Take the title line into account. */
2953                 int lines = prev->lineno - prev->offset - prev->height + 1;
2955                 /* Scroll the view that was split if the current line is
2956                  * outside the new limited view. */
2957                 do_scroll_view(prev, lines);
2958         }
2960         if (prev && view != prev) {
2961                 if (split && !backgrounded) {
2962                         /* "Blur" the previous view. */
2963                         update_view_title(prev);
2964                 }
2966                 view->parent = prev;
2967         }
2969         if (view->pipe && view->lines == 0) {
2970                 /* Clear the old view and let the incremental updating refill
2971                  * the screen. */
2972                 werase(view->win);
2973                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2974                 report("");
2975         } else if (view_is_displayed(view)) {
2976                 redraw_view(view);
2977                 report("");
2978         }
2980         /* If the view is backgrounded the above calls to report()
2981          * won't redraw the view title. */
2982         if (backgrounded)
2983                 update_view_title(view);
2986 static void
2987 open_external_viewer(const char *argv[], const char *dir)
2989         def_prog_mode();           /* save current tty modes */
2990         endwin();                  /* restore original tty modes */
2991         run_io_fg(argv, dir);
2992         fprintf(stderr, "Press Enter to continue");
2993         getc(opt_tty);
2994         reset_prog_mode();
2995         redraw_display(TRUE);
2998 static void
2999 open_mergetool(const char *file)
3001         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3003         open_external_viewer(mergetool_argv, opt_cdup);
3006 static void
3007 open_editor(bool from_root, const char *file)
3009         const char *editor_argv[] = { "vi", file, NULL };
3010         const char *editor;
3012         editor = getenv("GIT_EDITOR");
3013         if (!editor && *opt_editor)
3014                 editor = opt_editor;
3015         if (!editor)
3016                 editor = getenv("VISUAL");
3017         if (!editor)
3018                 editor = getenv("EDITOR");
3019         if (!editor)
3020                 editor = "vi";
3022         editor_argv[0] = editor;
3023         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3026 static void
3027 open_run_request(enum request request)
3029         struct run_request *req = get_run_request(request);
3030         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3032         if (!req) {
3033                 report("Unknown run request");
3034                 return;
3035         }
3037         if (format_argv(argv, req->argv, FORMAT_ALL))
3038                 open_external_viewer(argv, NULL);
3039         free_argv(argv);
3042 /*
3043  * User request switch noodle
3044  */
3046 static int
3047 view_driver(struct view *view, enum request request)
3049         int i;
3051         if (request == REQ_NONE) {
3052                 doupdate();
3053                 return TRUE;
3054         }
3056         if (request > REQ_NONE) {
3057                 open_run_request(request);
3058                 /* FIXME: When all views can refresh always do this. */
3059                 if (view == VIEW(REQ_VIEW_STATUS) ||
3060                     view == VIEW(REQ_VIEW_MAIN) ||
3061                     view == VIEW(REQ_VIEW_LOG) ||
3062                     view == VIEW(REQ_VIEW_STAGE))
3063                         request = REQ_REFRESH;
3064                 else
3065                         return TRUE;
3066         }
3068         if (view && view->lines) {
3069                 request = view->ops->request(view, request, &view->line[view->lineno]);
3070                 if (request == REQ_NONE)
3071                         return TRUE;
3072         }
3074         switch (request) {
3075         case REQ_MOVE_UP:
3076         case REQ_MOVE_DOWN:
3077         case REQ_MOVE_PAGE_UP:
3078         case REQ_MOVE_PAGE_DOWN:
3079         case REQ_MOVE_FIRST_LINE:
3080         case REQ_MOVE_LAST_LINE:
3081                 move_view(view, request);
3082                 break;
3084         case REQ_SCROLL_LINE_DOWN:
3085         case REQ_SCROLL_LINE_UP:
3086         case REQ_SCROLL_PAGE_DOWN:
3087         case REQ_SCROLL_PAGE_UP:
3088                 scroll_view(view, request);
3089                 break;
3091         case REQ_VIEW_BLAME:
3092                 if (!opt_file[0]) {
3093                         report("No file chosen, press %s to open tree view",
3094                                get_key(REQ_VIEW_TREE));
3095                         break;
3096                 }
3097                 open_view(view, request, OPEN_DEFAULT);
3098                 break;
3100         case REQ_VIEW_BLOB:
3101                 if (!ref_blob[0]) {
3102                         report("No file chosen, press %s to open tree view",
3103                                get_key(REQ_VIEW_TREE));
3104                         break;
3105                 }
3106                 open_view(view, request, OPEN_DEFAULT);
3107                 break;
3109         case REQ_VIEW_PAGER:
3110                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3111                         report("No pager content, press %s to run command from prompt",
3112                                get_key(REQ_PROMPT));
3113                         break;
3114                 }
3115                 open_view(view, request, OPEN_DEFAULT);
3116                 break;
3118         case REQ_VIEW_STAGE:
3119                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3120                         report("No stage content, press %s to open the status view and choose file",
3121                                get_key(REQ_VIEW_STATUS));
3122                         break;
3123                 }
3124                 open_view(view, request, OPEN_DEFAULT);
3125                 break;
3127         case REQ_VIEW_STATUS:
3128                 if (opt_is_inside_work_tree == FALSE) {
3129                         report("The status view requires a working tree");
3130                         break;
3131                 }
3132                 open_view(view, request, OPEN_DEFAULT);
3133                 break;
3135         case REQ_VIEW_MAIN:
3136         case REQ_VIEW_DIFF:
3137         case REQ_VIEW_LOG:
3138         case REQ_VIEW_TREE:
3139         case REQ_VIEW_HELP:
3140                 open_view(view, request, OPEN_DEFAULT);
3141                 break;
3143         case REQ_NEXT:
3144         case REQ_PREVIOUS:
3145                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3147                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3148                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3149                    (view == VIEW(REQ_VIEW_DIFF) &&
3150                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3151                    (view == VIEW(REQ_VIEW_STAGE) &&
3152                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3153                    (view == VIEW(REQ_VIEW_BLOB) &&
3154                      view->parent == VIEW(REQ_VIEW_TREE))) {
3155                         int line;
3157                         view = view->parent;
3158                         line = view->lineno;
3159                         move_view(view, request);
3160                         if (view_is_displayed(view))
3161                                 update_view_title(view);
3162                         if (line != view->lineno)
3163                                 view->ops->request(view, REQ_ENTER,
3164                                                    &view->line[view->lineno]);
3166                 } else {
3167                         move_view(view, request);
3168                 }
3169                 break;
3171         case REQ_VIEW_NEXT:
3172         {
3173                 int nviews = displayed_views();
3174                 int next_view = (current_view + 1) % nviews;
3176                 if (next_view == current_view) {
3177                         report("Only one view is displayed");
3178                         break;
3179                 }
3181                 current_view = next_view;
3182                 /* Blur out the title of the previous view. */
3183                 update_view_title(view);
3184                 report("");
3185                 break;
3186         }
3187         case REQ_REFRESH:
3188                 report("Refreshing is not yet supported for the %s view", view->name);
3189                 break;
3191         case REQ_MAXIMIZE:
3192                 if (displayed_views() == 2)
3193                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3194                 break;
3196         case REQ_TOGGLE_LINENO:
3197                 toggle_view_option(&opt_line_number, "line numbers");
3198                 break;
3200         case REQ_TOGGLE_DATE:
3201                 toggle_view_option(&opt_date, "date display");
3202                 break;
3204         case REQ_TOGGLE_AUTHOR:
3205                 toggle_view_option(&opt_author, "author display");
3206                 break;
3208         case REQ_TOGGLE_REV_GRAPH:
3209                 toggle_view_option(&opt_rev_graph, "revision graph display");
3210                 break;
3212         case REQ_TOGGLE_REFS:
3213                 toggle_view_option(&opt_show_refs, "reference display");
3214                 break;
3216         case REQ_SEARCH:
3217         case REQ_SEARCH_BACK:
3218                 search_view(view, request);
3219                 break;
3221         case REQ_FIND_NEXT:
3222         case REQ_FIND_PREV:
3223                 find_next(view, request);
3224                 break;
3226         case REQ_STOP_LOADING:
3227                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3228                         view = &views[i];
3229                         if (view->pipe)
3230                                 report("Stopped loading the %s view", view->name),
3231                         end_update(view, TRUE);
3232                 }
3233                 break;
3235         case REQ_SHOW_VERSION:
3236                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3237                 return TRUE;
3239         case REQ_SCREEN_REDRAW:
3240                 redraw_display(TRUE);
3241                 break;
3243         case REQ_EDIT:
3244                 report("Nothing to edit");
3245                 break;
3247         case REQ_ENTER:
3248                 report("Nothing to enter");
3249                 break;
3251         case REQ_VIEW_CLOSE:
3252                 /* XXX: Mark closed views by letting view->parent point to the
3253                  * view itself. Parents to closed view should never be
3254                  * followed. */
3255                 if (view->parent &&
3256                     view->parent->parent != view->parent) {
3257                         memset(display, 0, sizeof(display));
3258                         current_view = 0;
3259                         display[current_view] = view->parent;
3260                         view->parent = view;
3261                         resize_display();
3262                         redraw_display(FALSE);
3263                         report("");
3264                         break;
3265                 }
3266                 /* Fall-through */
3267         case REQ_QUIT:
3268                 return FALSE;
3270         default:
3271                 report("Unknown key, press 'h' for help");
3272                 return TRUE;
3273         }
3275         return TRUE;
3279 /*
3280  * View backend utilities
3281  */
3283 /* Parse author lines where the name may be empty:
3284  *      author  <email@address.tld> 1138474660 +0100
3285  */
3286 static void
3287 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3289         char *nameend = strchr(ident, '<');
3290         char *emailend = strchr(ident, '>');
3292         if (nameend && emailend)
3293                 *nameend = *emailend = 0;
3294         ident = chomp_string(ident);
3295         if (!*ident) {
3296                 if (nameend)
3297                         ident = chomp_string(nameend + 1);
3298                 if (!*ident)
3299                         ident = "Unknown";
3300         }
3302         string_ncopy_do(author, authorsize, ident, strlen(ident));
3304         /* Parse epoch and timezone */
3305         if (emailend && emailend[1] == ' ') {
3306                 char *secs = emailend + 2;
3307                 char *zone = strchr(secs, ' ');
3308                 time_t time = (time_t) atol(secs);
3310                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3311                         long tz;
3313                         zone++;
3314                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3315                         tz += ('0' - zone[2]) * 60 * 60;
3316                         tz += ('0' - zone[3]) * 60;
3317                         tz += ('0' - zone[4]) * 60;
3319                         if (zone[0] == '-')
3320                                 tz = -tz;
3322                         time -= tz;
3323                 }
3325                 gmtime_r(&time, tm);
3326         }
3329 static enum input_status
3330 select_commit_parent_handler(void *data, char *buf, int c)
3332         size_t parents = *(size_t *) data;
3333         int parent = 0;
3335         if (!isdigit(c))
3336                 return INPUT_SKIP;
3338         if (*buf)
3339                 parent = atoi(buf) * 10;
3340         parent += c - '0';
3342         if (parent > parents)
3343                 return INPUT_SKIP;
3344         return INPUT_OK;
3347 static bool
3348 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3350         char buf[SIZEOF_STR * 4];
3351         const char *revlist_argv[] = {
3352                 "git", "rev-list", "-1", "--parents", id, NULL
3353         };
3354         int parents;
3356         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3357             !*chomp_string(buf) ||
3358             (parents = (strlen(buf) / 40) - 1) < 0) {
3359                 report("Failed to get parent information");
3360                 return FALSE;
3362         } else if (parents == 0) {
3363                 report("The selected commit has no parents");
3364                 return FALSE;
3365         }
3367         if (parents > 1) {
3368                 char prompt[SIZEOF_STR];
3369                 char *result;
3371                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3372                         return FALSE;
3373                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3374                 if (!result)
3375                         return FALSE;
3376                 parents = atoi(result);
3377         }
3379         string_copy_rev(rev, &buf[41 * parents]);
3380         return TRUE;
3383 /*
3384  * Pager backend
3385  */
3387 static bool
3388 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3390         char *text = line->data;
3392         if (opt_line_number && draw_lineno(view, lineno))
3393                 return TRUE;
3395         draw_text(view, line->type, text, TRUE);
3396         return TRUE;
3399 static bool
3400 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3402         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3403         char refbuf[SIZEOF_STR];
3404         char *ref = NULL;
3406         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3407                 ref = chomp_string(refbuf);
3409         if (!ref || !*ref)
3410                 return TRUE;
3412         /* This is the only fatal call, since it can "corrupt" the buffer. */
3413         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3414                 return FALSE;
3416         return TRUE;
3419 static void
3420 add_pager_refs(struct view *view, struct line *line)
3422         char buf[SIZEOF_STR];
3423         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3424         struct ref **refs;
3425         size_t bufpos = 0, refpos = 0;
3426         const char *sep = "Refs: ";
3427         bool is_tag = FALSE;
3429         assert(line->type == LINE_COMMIT);
3431         refs = get_refs(commit_id);
3432         if (!refs) {
3433                 if (view == VIEW(REQ_VIEW_DIFF))
3434                         goto try_add_describe_ref;
3435                 return;
3436         }
3438         do {
3439                 struct ref *ref = refs[refpos];
3440                 const char *fmt = ref->tag    ? "%s[%s]" :
3441                                   ref->remote ? "%s<%s>" : "%s%s";
3443                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3444                         return;
3445                 sep = ", ";
3446                 if (ref->tag)
3447                         is_tag = TRUE;
3448         } while (refs[refpos++]->next);
3450         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3451 try_add_describe_ref:
3452                 /* Add <tag>-g<commit_id> "fake" reference. */
3453                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3454                         return;
3455         }
3457         if (bufpos == 0)
3458                 return;
3460         add_line_text(view, buf, LINE_PP_REFS);
3463 static bool
3464 pager_read(struct view *view, char *data)
3466         struct line *line;
3468         if (!data)
3469                 return TRUE;
3471         line = add_line_text(view, data, get_line_type(data));
3472         if (!line)
3473                 return FALSE;
3475         if (line->type == LINE_COMMIT &&
3476             (view == VIEW(REQ_VIEW_DIFF) ||
3477              view == VIEW(REQ_VIEW_LOG)))
3478                 add_pager_refs(view, line);
3480         return TRUE;
3483 static enum request
3484 pager_request(struct view *view, enum request request, struct line *line)
3486         int split = 0;
3488         if (request != REQ_ENTER)
3489                 return request;
3491         if (line->type == LINE_COMMIT &&
3492            (view == VIEW(REQ_VIEW_LOG) ||
3493             view == VIEW(REQ_VIEW_PAGER))) {
3494                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3495                 split = 1;
3496         }
3498         /* Always scroll the view even if it was split. That way
3499          * you can use Enter to scroll through the log view and
3500          * split open each commit diff. */
3501         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3503         /* FIXME: A minor workaround. Scrolling the view will call report("")
3504          * but if we are scrolling a non-current view this won't properly
3505          * update the view title. */
3506         if (split)
3507                 update_view_title(view);
3509         return REQ_NONE;
3512 static bool
3513 pager_grep(struct view *view, struct line *line)
3515         regmatch_t pmatch;
3516         char *text = line->data;
3518         if (!*text)
3519                 return FALSE;
3521         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3522                 return FALSE;
3524         return TRUE;
3527 static void
3528 pager_select(struct view *view, struct line *line)
3530         if (line->type == LINE_COMMIT) {
3531                 char *text = (char *)line->data + STRING_SIZE("commit ");
3533                 if (view != VIEW(REQ_VIEW_PAGER))
3534                         string_copy_rev(view->ref, text);
3535                 string_copy_rev(ref_commit, text);
3536         }
3539 static struct view_ops pager_ops = {
3540         "line",
3541         NULL,
3542         NULL,
3543         pager_read,
3544         pager_draw,
3545         pager_request,
3546         pager_grep,
3547         pager_select,
3548 };
3550 static const char *log_argv[SIZEOF_ARG] = {
3551         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3552 };
3554 static enum request
3555 log_request(struct view *view, enum request request, struct line *line)
3557         switch (request) {
3558         case REQ_REFRESH:
3559                 load_refs();
3560                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3561                 return REQ_NONE;
3562         default:
3563                 return pager_request(view, request, line);
3564         }
3567 static struct view_ops log_ops = {
3568         "line",
3569         log_argv,
3570         NULL,
3571         pager_read,
3572         pager_draw,
3573         log_request,
3574         pager_grep,
3575         pager_select,
3576 };
3578 static const char *diff_argv[SIZEOF_ARG] = {
3579         "git", "show", "--pretty=fuller", "--no-color", "--root",
3580                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3581 };
3583 static struct view_ops diff_ops = {
3584         "line",
3585         diff_argv,
3586         NULL,
3587         pager_read,
3588         pager_draw,
3589         pager_request,
3590         pager_grep,
3591         pager_select,
3592 };
3594 /*
3595  * Help backend
3596  */
3598 static bool
3599 help_open(struct view *view)
3601         char buf[SIZEOF_STR];
3602         size_t bufpos;
3603         int i;
3605         if (view->lines > 0)
3606                 return TRUE;
3608         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3610         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3611                 const char *key;
3613                 if (req_info[i].request == REQ_NONE)
3614                         continue;
3616                 if (!req_info[i].request) {
3617                         add_line_text(view, "", LINE_DEFAULT);
3618                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3619                         continue;
3620                 }
3622                 key = get_key(req_info[i].request);
3623                 if (!*key)
3624                         key = "(no key defined)";
3626                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3627                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3628                         if (buf[bufpos] == '_')
3629                                 buf[bufpos] = '-';
3630                 }
3632                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3633                                 key, buf, req_info[i].help);
3634         }
3636         if (run_requests) {
3637                 add_line_text(view, "", LINE_DEFAULT);
3638                 add_line_text(view, "External commands:", LINE_DEFAULT);
3639         }
3641         for (i = 0; i < run_requests; i++) {
3642                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3643                 const char *key;
3644                 int argc;
3646                 if (!req)
3647                         continue;
3649                 key = get_key_name(req->key);
3650                 if (!*key)
3651                         key = "(no key defined)";
3653                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3654                         if (!string_format_from(buf, &bufpos, "%s%s",
3655                                                 argc ? " " : "", req->argv[argc]))
3656                                 return REQ_NONE;
3658                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3659                                 keymap_table[req->keymap].name, key, buf);
3660         }
3662         return TRUE;
3665 static struct view_ops help_ops = {
3666         "line",
3667         NULL,
3668         help_open,
3669         NULL,
3670         pager_draw,
3671         pager_request,
3672         pager_grep,
3673         pager_select,
3674 };
3677 /*
3678  * Tree backend
3679  */
3681 struct tree_stack_entry {
3682         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3683         unsigned long lineno;           /* Line number to restore */
3684         char *name;                     /* Position of name in opt_path */
3685 };
3687 /* The top of the path stack. */
3688 static struct tree_stack_entry *tree_stack = NULL;
3689 unsigned long tree_lineno = 0;
3691 static void
3692 pop_tree_stack_entry(void)
3694         struct tree_stack_entry *entry = tree_stack;
3696         tree_lineno = entry->lineno;
3697         entry->name[0] = 0;
3698         tree_stack = entry->prev;
3699         free(entry);
3702 static void
3703 push_tree_stack_entry(const char *name, unsigned long lineno)
3705         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3706         size_t pathlen = strlen(opt_path);
3708         if (!entry)
3709                 return;
3711         entry->prev = tree_stack;
3712         entry->name = opt_path + pathlen;
3713         tree_stack = entry;
3715         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3716                 pop_tree_stack_entry();
3717                 return;
3718         }
3720         /* Move the current line to the first tree entry. */
3721         tree_lineno = 1;
3722         entry->lineno = lineno;
3725 /* Parse output from git-ls-tree(1):
3726  *
3727  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3728  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3729  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3730  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3731  */
3733 #define SIZEOF_TREE_ATTR \
3734         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3736 #define SIZEOF_TREE_MODE \
3737         STRING_SIZE("100644 ")
3739 #define TREE_ID_OFFSET \
3740         STRING_SIZE("100644 blob ")
3742 struct tree_entry {
3743         char id[SIZEOF_REV];
3744         mode_t mode;
3745         struct tm time;                 /* Date from the author ident. */
3746         char author[75];                /* Author of the commit. */
3747         char name[1];
3748 };
3750 static const char *
3751 tree_path(struct line *line)
3753         return ((struct tree_entry *) line->data)->name;
3757 static int
3758 tree_compare_entry(struct line *line1, struct line *line2)
3760         if (line1->type != line2->type)
3761                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3762         return strcmp(tree_path(line1), tree_path(line2));
3765 static struct line *
3766 tree_entry(struct view *view, enum line_type type, const char *path,
3767            const char *mode, const char *id)
3769         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3770         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3772         if (!entry || !line) {
3773                 free(entry);
3774                 return NULL;
3775         }
3777         strncpy(entry->name, path, strlen(path));
3778         if (mode)
3779                 entry->mode = strtoul(mode, NULL, 8);
3780         if (id)
3781                 string_copy_rev(entry->id, id);
3783         return line;
3786 static bool
3787 tree_read_date(struct view *view, char *text, bool *read_date)
3789         static char author_name[SIZEOF_STR];
3790         static struct tm author_time;
3792         if (!text && *read_date) {
3793                 *read_date = FALSE;
3794                 return TRUE;
3796         } else if (!text) {
3797                 char *path = *opt_path ? opt_path : ".";
3798                 /* Find next entry to process */
3799                 const char *log_file[] = {
3800                         "git", "log", "--no-color", "--pretty=raw",
3801                                 "--cc", "--raw", view->id, "--", path, NULL
3802                 };
3803                 struct io io = {};
3805                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3806                         report("Failed to load tree data");
3807                         return TRUE;
3808                 }
3810                 done_io(view->pipe);
3811                 view->io = io;
3812                 *read_date = TRUE;
3813                 return FALSE;
3815         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3816                 parse_author_line(text + STRING_SIZE("author "),
3817                                   author_name, sizeof(author_name), &author_time);
3819         } else if (*text == ':') {
3820                 char *pos;
3821                 size_t annotated = 1;
3822                 size_t i;
3824                 pos = strchr(text, '\t');
3825                 if (!pos)
3826                         return TRUE;
3827                 text = pos + 1;
3828                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3829                         text += strlen(opt_prefix);
3830                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3831                         text += strlen(opt_path);
3832                 pos = strchr(text, '/');
3833                 if (pos)
3834                         *pos = 0;
3836                 for (i = 1; i < view->lines; i++) {
3837                         struct line *line = &view->line[i];
3838                         struct tree_entry *entry = line->data;
3840                         annotated += !!*entry->author;
3841                         if (*entry->author || strcmp(entry->name, text))
3842                                 continue;
3844                         string_copy(entry->author, author_name);
3845                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3846                         line->dirty = 1;
3847                         break;
3848                 }
3850                 if (annotated == view->lines)
3851                         kill_io(view->pipe);
3852         }
3853         return TRUE;
3856 static bool
3857 tree_read(struct view *view, char *text)
3859         static bool read_date = FALSE;
3860         struct tree_entry *data;
3861         struct line *entry, *line;
3862         enum line_type type;
3863         size_t textlen = text ? strlen(text) : 0;
3864         char *path = text + SIZEOF_TREE_ATTR;
3866         if (read_date || !text)
3867                 return tree_read_date(view, text, &read_date);
3869         if (textlen <= SIZEOF_TREE_ATTR)
3870                 return FALSE;
3871         if (view->lines == 0 &&
3872             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3873                 return FALSE;
3875         /* Strip the path part ... */
3876         if (*opt_path) {
3877                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3878                 size_t striplen = strlen(opt_path);
3880                 if (pathlen > striplen)
3881                         memmove(path, path + striplen,
3882                                 pathlen - striplen + 1);
3884                 /* Insert "link" to parent directory. */
3885                 if (view->lines == 1 &&
3886                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3887                         return FALSE;
3888         }
3890         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3891         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3892         if (!entry)
3893                 return FALSE;
3894         data = entry->data;
3896         /* Skip "Directory ..." and ".." line. */
3897         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3898                 if (tree_compare_entry(line, entry) <= 0)
3899                         continue;
3901                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3903                 line->data = data;
3904                 line->type = type;
3905                 for (; line <= entry; line++)
3906                         line->dirty = line->cleareol = 1;
3907                 return TRUE;
3908         }
3910         if (tree_lineno > view->lineno) {
3911                 view->lineno = tree_lineno;
3912                 tree_lineno = 0;
3913         }
3915         return TRUE;
3918 static bool
3919 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3921         struct tree_entry *entry = line->data;
3923         if (line->type == LINE_TREE_PARENT) {
3924                 if (draw_text(view, line->type, "Directory path /", TRUE))
3925                         return TRUE;
3926         } else {
3927                 char mode[11] = "-r--r--r--";
3929                 if (S_ISDIR(entry->mode)) {
3930                         mode[3] = mode[6] = mode[9] = 'x';
3931                         mode[0] = 'd';
3932                 }
3933                 if (S_ISLNK(entry->mode))
3934                         mode[0] = 'l';
3935                 if (entry->mode & S_IWUSR)
3936                         mode[2] = 'w';
3937                 if (entry->mode & S_IXUSR)
3938                         mode[3] = 'x';
3939                 if (entry->mode & S_IXGRP)
3940                         mode[6] = 'x';
3941                 if (entry->mode & S_IXOTH)
3942                         mode[9] = 'x';
3943                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3944                         return TRUE;
3946                 if (opt_author && draw_author(view, entry->author))
3947                         return TRUE;
3949                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3950                         return TRUE;
3951         }
3952         if (draw_text(view, line->type, entry->name, TRUE))
3953                 return TRUE;
3954         return TRUE;
3957 static void
3958 open_blob_editor()
3960         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3961         int fd = mkstemp(file);
3963         if (fd == -1)
3964                 report("Failed to create temporary file");
3965         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3966                 report("Failed to save blob data to file");
3967         else
3968                 open_editor(FALSE, file);
3969         if (fd != -1)
3970                 unlink(file);
3973 static enum request
3974 tree_request(struct view *view, enum request request, struct line *line)
3976         enum open_flags flags;
3978         switch (request) {
3979         case REQ_VIEW_BLAME:
3980                 if (line->type != LINE_TREE_FILE) {
3981                         report("Blame only supported for files");
3982                         return REQ_NONE;
3983                 }
3985                 string_copy(opt_ref, view->vid);
3986                 return request;
3988         case REQ_EDIT:
3989                 if (line->type != LINE_TREE_FILE) {
3990                         report("Edit only supported for files");
3991                 } else if (!is_head_commit(view->vid)) {
3992                         open_blob_editor();
3993                 } else {
3994                         open_editor(TRUE, opt_file);
3995                 }
3996                 return REQ_NONE;
3998         case REQ_PARENT:
3999                 if (!*opt_path) {
4000                         /* quit view if at top of tree */
4001                         return REQ_VIEW_CLOSE;
4002                 }
4003                 /* fake 'cd  ..' */
4004                 line = &view->line[1];
4005                 break;
4007         case REQ_ENTER:
4008                 break;
4010         default:
4011                 return request;
4012         }
4014         /* Cleanup the stack if the tree view is at a different tree. */
4015         while (!*opt_path && tree_stack)
4016                 pop_tree_stack_entry();
4018         switch (line->type) {
4019         case LINE_TREE_DIR:
4020                 /* Depending on whether it is a subdir or parent (updir?) link
4021                  * mangle the path buffer. */
4022                 if (line == &view->line[1] && *opt_path) {
4023                         pop_tree_stack_entry();
4025                 } else {
4026                         const char *basename = tree_path(line);
4028                         push_tree_stack_entry(basename, view->lineno);
4029                 }
4031                 /* Trees and subtrees share the same ID, so they are not not
4032                  * unique like blobs. */
4033                 flags = OPEN_RELOAD;
4034                 request = REQ_VIEW_TREE;
4035                 break;
4037         case LINE_TREE_FILE:
4038                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4039                 request = REQ_VIEW_BLOB;
4040                 break;
4042         default:
4043                 return REQ_NONE;
4044         }
4046         open_view(view, request, flags);
4047         if (request == REQ_VIEW_TREE)
4048                 view->lineno = tree_lineno;
4050         return REQ_NONE;
4053 static void
4054 tree_select(struct view *view, struct line *line)
4056         struct tree_entry *entry = line->data;
4058         if (line->type == LINE_TREE_FILE) {
4059                 string_copy_rev(ref_blob, entry->id);
4060                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4062         } else if (line->type != LINE_TREE_DIR) {
4063                 return;
4064         }
4066         string_copy_rev(view->ref, entry->id);
4069 static const char *tree_argv[SIZEOF_ARG] = {
4070         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4071 };
4073 static struct view_ops tree_ops = {
4074         "file",
4075         tree_argv,
4076         NULL,
4077         tree_read,
4078         tree_draw,
4079         tree_request,
4080         pager_grep,
4081         tree_select,
4082 };
4084 static bool
4085 blob_read(struct view *view, char *line)
4087         if (!line)
4088                 return TRUE;
4089         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4092 static enum request
4093 blob_request(struct view *view, enum request request, struct line *line)
4095         switch (request) {
4096         case REQ_EDIT:
4097                 open_blob_editor();
4098                 return REQ_NONE;
4099         default:
4100                 return pager_request(view, request, line);
4101         }
4104 static const char *blob_argv[SIZEOF_ARG] = {
4105         "git", "cat-file", "blob", "%(blob)", NULL
4106 };
4108 static struct view_ops blob_ops = {
4109         "line",
4110         blob_argv,
4111         NULL,
4112         blob_read,
4113         pager_draw,
4114         blob_request,
4115         pager_grep,
4116         pager_select,
4117 };
4119 /*
4120  * Blame backend
4121  *
4122  * Loading the blame view is a two phase job:
4123  *
4124  *  1. File content is read either using opt_file from the
4125  *     filesystem or using git-cat-file.
4126  *  2. Then blame information is incrementally added by
4127  *     reading output from git-blame.
4128  */
4130 static const char *blame_head_argv[] = {
4131         "git", "blame", "--incremental", "--", "%(file)", NULL
4132 };
4134 static const char *blame_ref_argv[] = {
4135         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4136 };
4138 static const char *blame_cat_file_argv[] = {
4139         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4140 };
4142 struct blame_commit {
4143         char id[SIZEOF_REV];            /* SHA1 ID. */
4144         char title[128];                /* First line of the commit message. */
4145         char author[75];                /* Author of the commit. */
4146         struct tm time;                 /* Date from the author ident. */
4147         char filename[128];             /* Name of file. */
4148         bool has_previous;              /* Was a "previous" line detected. */
4149 };
4151 struct blame {
4152         struct blame_commit *commit;
4153         char text[1];
4154 };
4156 static bool
4157 blame_open(struct view *view)
4159         if (*opt_ref || !io_open(&view->io, opt_file)) {
4160                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4161                         return FALSE;
4162         }
4164         setup_update(view, opt_file);
4165         string_format(view->ref, "%s ...", opt_file);
4167         return TRUE;
4170 static struct blame_commit *
4171 get_blame_commit(struct view *view, const char *id)
4173         size_t i;
4175         for (i = 0; i < view->lines; i++) {
4176                 struct blame *blame = view->line[i].data;
4178                 if (!blame->commit)
4179                         continue;
4181                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4182                         return blame->commit;
4183         }
4185         {
4186                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4188                 if (commit)
4189                         string_ncopy(commit->id, id, SIZEOF_REV);
4190                 return commit;
4191         }
4194 static bool
4195 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4197         const char *pos = *posref;
4199         *posref = NULL;
4200         pos = strchr(pos + 1, ' ');
4201         if (!pos || !isdigit(pos[1]))
4202                 return FALSE;
4203         *number = atoi(pos + 1);
4204         if (*number < min || *number > max)
4205                 return FALSE;
4207         *posref = pos;
4208         return TRUE;
4211 static struct blame_commit *
4212 parse_blame_commit(struct view *view, const char *text, int *blamed)
4214         struct blame_commit *commit;
4215         struct blame *blame;
4216         const char *pos = text + SIZEOF_REV - 1;
4217         size_t lineno;
4218         size_t group;
4220         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4221                 return NULL;
4223         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4224             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4225                 return NULL;
4227         commit = get_blame_commit(view, text);
4228         if (!commit)
4229                 return NULL;
4231         *blamed += group;
4232         while (group--) {
4233                 struct line *line = &view->line[lineno + group - 1];
4235                 blame = line->data;
4236                 blame->commit = commit;
4237                 line->dirty = 1;
4238         }
4240         return commit;
4243 static bool
4244 blame_read_file(struct view *view, const char *line, bool *read_file)
4246         if (!line) {
4247                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4248                 struct io io = {};
4250                 if (view->lines == 0 && !view->parent)
4251                         die("No blame exist for %s", view->vid);
4253                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4254                         report("Failed to load blame data");
4255                         return TRUE;
4256                 }
4258                 done_io(view->pipe);
4259                 view->io = io;
4260                 *read_file = FALSE;
4261                 return FALSE;
4263         } else {
4264                 size_t linelen = strlen(line);
4265                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4267                 blame->commit = NULL;
4268                 strncpy(blame->text, line, linelen);
4269                 blame->text[linelen] = 0;
4270                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4271         }
4274 static bool
4275 match_blame_header(const char *name, char **line)
4277         size_t namelen = strlen(name);
4278         bool matched = !strncmp(name, *line, namelen);
4280         if (matched)
4281                 *line += namelen;
4283         return matched;
4286 static bool
4287 blame_read(struct view *view, char *line)
4289         static struct blame_commit *commit = NULL;
4290         static int blamed = 0;
4291         static time_t author_time;
4292         static bool read_file = TRUE;
4294         if (read_file)
4295                 return blame_read_file(view, line, &read_file);
4297         if (!line) {
4298                 /* Reset all! */
4299                 commit = NULL;
4300                 blamed = 0;
4301                 read_file = TRUE;
4302                 string_format(view->ref, "%s", view->vid);
4303                 if (view_is_displayed(view)) {
4304                         update_view_title(view);
4305                         redraw_view_from(view, 0);
4306                 }
4307                 return TRUE;
4308         }
4310         if (!commit) {
4311                 commit = parse_blame_commit(view, line, &blamed);
4312                 string_format(view->ref, "%s %2d%%", view->vid,
4313                               view->lines ? blamed * 100 / view->lines : 0);
4315         } else if (match_blame_header("author ", &line)) {
4316                 string_ncopy(commit->author, line, strlen(line));
4318         } else if (match_blame_header("author-time ", &line)) {
4319                 author_time = (time_t) atol(line);
4321         } else if (match_blame_header("author-tz ", &line)) {
4322                 long tz;
4324                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4325                 tz += ('0' - line[2]) * 60 * 60;
4326                 tz += ('0' - line[3]) * 60;
4327                 tz += ('0' - line[4]) * 60;
4329                 if (line[0] == '-')
4330                         tz = -tz;
4332                 author_time -= tz;
4333                 gmtime_r(&author_time, &commit->time);
4335         } else if (match_blame_header("summary ", &line)) {
4336                 string_ncopy(commit->title, line, strlen(line));
4338         } else if (match_blame_header("previous ", &line)) {
4339                 commit->has_previous = TRUE;
4341         } else if (match_blame_header("filename ", &line)) {
4342                 string_ncopy(commit->filename, line, strlen(line));
4343                 commit = NULL;
4344         }
4346         return TRUE;
4349 static bool
4350 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4352         struct blame *blame = line->data;
4353         struct tm *time = NULL;
4354         const char *id = NULL, *author = NULL;
4356         if (blame->commit && *blame->commit->filename) {
4357                 id = blame->commit->id;
4358                 author = blame->commit->author;
4359                 time = &blame->commit->time;
4360         }
4362         if (opt_date && draw_date(view, time))
4363                 return TRUE;
4365         if (opt_author && draw_author(view, author))
4366                 return TRUE;
4368         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4369                 return TRUE;
4371         if (draw_lineno(view, lineno))
4372                 return TRUE;
4374         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4375         return TRUE;
4378 static bool
4379 check_blame_commit(struct blame *blame)
4381         if (!blame->commit)
4382                 report("Commit data not loaded yet");
4383         else if (!strcmp(blame->commit->id, NULL_ID))
4384                 report("No commit exist for the selected line");
4385         else
4386                 return TRUE;
4387         return FALSE;
4390 static enum request
4391 blame_request(struct view *view, enum request request, struct line *line)
4393         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4394         struct blame *blame = line->data;
4396         switch (request) {
4397         case REQ_VIEW_BLAME:
4398                 if (check_blame_commit(blame)) {
4399                         string_copy(opt_ref, blame->commit->id);
4400                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4401                 }
4402                 break;
4404         case REQ_PARENT:
4405                 if (check_blame_commit(blame) &&
4406                     select_commit_parent(blame->commit->id, opt_ref))
4407                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4408                 break;
4410         case REQ_ENTER:
4411                 if (!blame->commit) {
4412                         report("No commit loaded yet");
4413                         break;
4414                 }
4416                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4417                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4418                         break;
4420                 if (!strcmp(blame->commit->id, NULL_ID)) {
4421                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4422                         const char *diff_index_argv[] = {
4423                                 "git", "diff-index", "--root", "--patch-with-stat",
4424                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4425                         };
4427                         if (!blame->commit->has_previous) {
4428                                 diff_index_argv[1] = "diff";
4429                                 diff_index_argv[2] = "--no-color";
4430                                 diff_index_argv[6] = "--";
4431                                 diff_index_argv[7] = "/dev/null";
4432                         }
4434                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4435                                 report("Failed to allocate diff command");
4436                                 break;
4437                         }
4438                         flags |= OPEN_PREPARED;
4439                 }
4441                 open_view(view, REQ_VIEW_DIFF, flags);
4442                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4443                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4444                 break;
4446         default:
4447                 return request;
4448         }
4450         return REQ_NONE;
4453 static bool
4454 blame_grep(struct view *view, struct line *line)
4456         struct blame *blame = line->data;
4457         struct blame_commit *commit = blame->commit;
4458         regmatch_t pmatch;
4460 #define MATCH(text, on)                                                 \
4461         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4463         if (commit) {
4464                 char buf[DATE_COLS + 1];
4466                 if (MATCH(commit->title, 1) ||
4467                     MATCH(commit->author, opt_author) ||
4468                     MATCH(commit->id, opt_date))
4469                         return TRUE;
4471                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4472                     MATCH(buf, 1))
4473                         return TRUE;
4474         }
4476         return MATCH(blame->text, 1);
4478 #undef MATCH
4481 static void
4482 blame_select(struct view *view, struct line *line)
4484         struct blame *blame = line->data;
4485         struct blame_commit *commit = blame->commit;
4487         if (!commit)
4488                 return;
4490         if (!strcmp(commit->id, NULL_ID))
4491                 string_ncopy(ref_commit, "HEAD", 4);
4492         else
4493                 string_copy_rev(ref_commit, commit->id);
4496 static struct view_ops blame_ops = {
4497         "line",
4498         NULL,
4499         blame_open,
4500         blame_read,
4501         blame_draw,
4502         blame_request,
4503         blame_grep,
4504         blame_select,
4505 };
4507 /*
4508  * Status backend
4509  */
4511 struct status {
4512         char status;
4513         struct {
4514                 mode_t mode;
4515                 char rev[SIZEOF_REV];
4516                 char name[SIZEOF_STR];
4517         } old;
4518         struct {
4519                 mode_t mode;
4520                 char rev[SIZEOF_REV];
4521                 char name[SIZEOF_STR];
4522         } new;
4523 };
4525 static char status_onbranch[SIZEOF_STR];
4526 static struct status stage_status;
4527 static enum line_type stage_line_type;
4528 static size_t stage_chunks;
4529 static int *stage_chunk;
4531 /* This should work even for the "On branch" line. */
4532 static inline bool
4533 status_has_none(struct view *view, struct line *line)
4535         return line < view->line + view->lines && !line[1].data;
4538 /* Get fields from the diff line:
4539  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4540  */
4541 static inline bool
4542 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4544         const char *old_mode = buf +  1;
4545         const char *new_mode = buf +  8;
4546         const char *old_rev  = buf + 15;
4547         const char *new_rev  = buf + 56;
4548         const char *status   = buf + 97;
4550         if (bufsize < 98 ||
4551             old_mode[-1] != ':' ||
4552             new_mode[-1] != ' ' ||
4553             old_rev[-1]  != ' ' ||
4554             new_rev[-1]  != ' ' ||
4555             status[-1]   != ' ')
4556                 return FALSE;
4558         file->status = *status;
4560         string_copy_rev(file->old.rev, old_rev);
4561         string_copy_rev(file->new.rev, new_rev);
4563         file->old.mode = strtoul(old_mode, NULL, 8);
4564         file->new.mode = strtoul(new_mode, NULL, 8);
4566         file->old.name[0] = file->new.name[0] = 0;
4568         return TRUE;
4571 static bool
4572 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4574         struct status *file = NULL;
4575         struct status *unmerged = NULL;
4576         char *buf;
4577         struct io io = {};
4579         if (!run_io(&io, argv, NULL, IO_RD))
4580                 return FALSE;
4582         add_line_data(view, NULL, type);
4584         while ((buf = io_get(&io, 0, TRUE))) {
4585                 if (!file) {
4586                         file = calloc(1, sizeof(*file));
4587                         if (!file || !add_line_data(view, file, type))
4588                                 goto error_out;
4589                 }
4591                 /* Parse diff info part. */
4592                 if (status) {
4593                         file->status = status;
4594                         if (status == 'A')
4595                                 string_copy(file->old.rev, NULL_ID);
4597                 } else if (!file->status) {
4598                         if (!status_get_diff(file, buf, strlen(buf)))
4599                                 goto error_out;
4601                         buf = io_get(&io, 0, TRUE);
4602                         if (!buf)
4603                                 break;
4605                         /* Collapse all 'M'odified entries that follow a
4606                          * associated 'U'nmerged entry. */
4607                         if (file->status == 'U') {
4608                                 unmerged = file;
4610                         } else if (unmerged) {
4611                                 int collapse = !strcmp(buf, unmerged->new.name);
4613                                 unmerged = NULL;
4614                                 if (collapse) {
4615                                         free(file);
4616                                         file = NULL;
4617                                         view->lines--;
4618                                         continue;
4619                                 }
4620                         }
4621                 }
4623                 /* Grab the old name for rename/copy. */
4624                 if (!*file->old.name &&
4625                     (file->status == 'R' || file->status == 'C')) {
4626                         string_ncopy(file->old.name, buf, strlen(buf));
4628                         buf = io_get(&io, 0, TRUE);
4629                         if (!buf)
4630                                 break;
4631                 }
4633                 /* git-ls-files just delivers a NUL separated list of
4634                  * file names similar to the second half of the
4635                  * git-diff-* output. */
4636                 string_ncopy(file->new.name, buf, strlen(buf));
4637                 if (!*file->old.name)
4638                         string_copy(file->old.name, file->new.name);
4639                 file = NULL;
4640         }
4642         if (io_error(&io)) {
4643 error_out:
4644                 done_io(&io);
4645                 return FALSE;
4646         }
4648         if (!view->line[view->lines - 1].data)
4649                 add_line_data(view, NULL, LINE_STAT_NONE);
4651         done_io(&io);
4652         return TRUE;
4655 /* Don't show unmerged entries in the staged section. */
4656 static const char *status_diff_index_argv[] = {
4657         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4658                              "--cached", "-M", "HEAD", NULL
4659 };
4661 static const char *status_diff_files_argv[] = {
4662         "git", "diff-files", "-z", NULL
4663 };
4665 static const char *status_list_other_argv[] = {
4666         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4667 };
4669 static const char *status_list_no_head_argv[] = {
4670         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4671 };
4673 static const char *update_index_argv[] = {
4674         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4675 };
4677 /* Restore the previous line number to stay in the context or select a
4678  * line with something that can be updated. */
4679 static void
4680 status_restore(struct view *view)
4682         if (view->p_lineno >= view->lines)
4683                 view->p_lineno = view->lines - 1;
4684         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4685                 view->p_lineno++;
4686         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4687                 view->p_lineno--;
4689         /* If the above fails, always skip the "On branch" line. */
4690         if (view->p_lineno < view->lines)
4691                 view->lineno = view->p_lineno;
4692         else
4693                 view->lineno = 1;
4695         if (view->lineno < view->offset)
4696                 view->offset = view->lineno;
4697         else if (view->offset + view->height <= view->lineno)
4698                 view->offset = view->lineno - view->height + 1;
4700         view->p_restore = FALSE;
4703 /* First parse staged info using git-diff-index(1), then parse unstaged
4704  * info using git-diff-files(1), and finally untracked files using
4705  * git-ls-files(1). */
4706 static bool
4707 status_open(struct view *view)
4709         reset_view(view);
4711         add_line_data(view, NULL, LINE_STAT_HEAD);
4712         if (is_initial_commit())
4713                 string_copy(status_onbranch, "Initial commit");
4714         else if (!*opt_head)
4715                 string_copy(status_onbranch, "Not currently on any branch");
4716         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4717                 return FALSE;
4719         run_io_bg(update_index_argv);
4721         if (is_initial_commit()) {
4722                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4723                         return FALSE;
4724         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4725                 return FALSE;
4726         }
4728         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4729             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4730                 return FALSE;
4732         /* Restore the exact position or use the specialized restore
4733          * mode? */
4734         if (!view->p_restore)
4735                 status_restore(view);
4736         return TRUE;
4739 static bool
4740 status_draw(struct view *view, struct line *line, unsigned int lineno)
4742         struct status *status = line->data;
4743         enum line_type type;
4744         const char *text;
4746         if (!status) {
4747                 switch (line->type) {
4748                 case LINE_STAT_STAGED:
4749                         type = LINE_STAT_SECTION;
4750                         text = "Changes to be committed:";
4751                         break;
4753                 case LINE_STAT_UNSTAGED:
4754                         type = LINE_STAT_SECTION;
4755                         text = "Changed but not updated:";
4756                         break;
4758                 case LINE_STAT_UNTRACKED:
4759                         type = LINE_STAT_SECTION;
4760                         text = "Untracked files:";
4761                         break;
4763                 case LINE_STAT_NONE:
4764                         type = LINE_DEFAULT;
4765                         text = "    (no files)";
4766                         break;
4768                 case LINE_STAT_HEAD:
4769                         type = LINE_STAT_HEAD;
4770                         text = status_onbranch;
4771                         break;
4773                 default:
4774                         return FALSE;
4775                 }
4776         } else {
4777                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4779                 buf[0] = status->status;
4780                 if (draw_text(view, line->type, buf, TRUE))
4781                         return TRUE;
4782                 type = LINE_DEFAULT;
4783                 text = status->new.name;
4784         }
4786         draw_text(view, type, text, TRUE);
4787         return TRUE;
4790 static enum request
4791 status_enter(struct view *view, struct line *line)
4793         struct status *status = line->data;
4794         const char *oldpath = status ? status->old.name : NULL;
4795         /* Diffs for unmerged entries are empty when passing the new
4796          * path, so leave it empty. */
4797         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4798         const char *info;
4799         enum open_flags split;
4800         struct view *stage = VIEW(REQ_VIEW_STAGE);
4802         if (line->type == LINE_STAT_NONE ||
4803             (!status && line[1].type == LINE_STAT_NONE)) {
4804                 report("No file to diff");
4805                 return REQ_NONE;
4806         }
4808         switch (line->type) {
4809         case LINE_STAT_STAGED:
4810                 if (is_initial_commit()) {
4811                         const char *no_head_diff_argv[] = {
4812                                 "git", "diff", "--no-color", "--patch-with-stat",
4813                                         "--", "/dev/null", newpath, NULL
4814                         };
4816                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4817                                 return REQ_QUIT;
4818                 } else {
4819                         const char *index_show_argv[] = {
4820                                 "git", "diff-index", "--root", "--patch-with-stat",
4821                                         "-C", "-M", "--cached", "HEAD", "--",
4822                                         oldpath, newpath, NULL
4823                         };
4825                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4826                                 return REQ_QUIT;
4827                 }
4829                 if (status)
4830                         info = "Staged changes to %s";
4831                 else
4832                         info = "Staged changes";
4833                 break;
4835         case LINE_STAT_UNSTAGED:
4836         {
4837                 const char *files_show_argv[] = {
4838                         "git", "diff-files", "--root", "--patch-with-stat",
4839                                 "-C", "-M", "--", oldpath, newpath, NULL
4840                 };
4842                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4843                         return REQ_QUIT;
4844                 if (status)
4845                         info = "Unstaged changes to %s";
4846                 else
4847                         info = "Unstaged changes";
4848                 break;
4849         }
4850         case LINE_STAT_UNTRACKED:
4851                 if (!newpath) {
4852                         report("No file to show");
4853                         return REQ_NONE;
4854                 }
4856                 if (!suffixcmp(status->new.name, -1, "/")) {
4857                         report("Cannot display a directory");
4858                         return REQ_NONE;
4859                 }
4861                 if (!prepare_update_file(stage, newpath))
4862                         return REQ_QUIT;
4863                 info = "Untracked file %s";
4864                 break;
4866         case LINE_STAT_HEAD:
4867                 return REQ_NONE;
4869         default:
4870                 die("line type %d not handled in switch", line->type);
4871         }
4873         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4874         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4875         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4876                 if (status) {
4877                         stage_status = *status;
4878                 } else {
4879                         memset(&stage_status, 0, sizeof(stage_status));
4880                 }
4882                 stage_line_type = line->type;
4883                 stage_chunks = 0;
4884                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4885         }
4887         return REQ_NONE;
4890 static bool
4891 status_exists(struct status *status, enum line_type type)
4893         struct view *view = VIEW(REQ_VIEW_STATUS);
4894         unsigned long lineno;
4896         for (lineno = 0; lineno < view->lines; lineno++) {
4897                 struct line *line = &view->line[lineno];
4898                 struct status *pos = line->data;
4900                 if (line->type != type)
4901                         continue;
4902                 if (!pos && (!status || !status->status) && line[1].data) {
4903                         select_view_line(view, lineno);
4904                         return TRUE;
4905                 }
4906                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4907                         select_view_line(view, lineno);
4908                         return TRUE;
4909                 }
4910         }
4912         return FALSE;
4916 static bool
4917 status_update_prepare(struct io *io, enum line_type type)
4919         const char *staged_argv[] = {
4920                 "git", "update-index", "-z", "--index-info", NULL
4921         };
4922         const char *others_argv[] = {
4923                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4924         };
4926         switch (type) {
4927         case LINE_STAT_STAGED:
4928                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4930         case LINE_STAT_UNSTAGED:
4931                 return run_io(io, others_argv, opt_cdup, IO_WR);
4933         case LINE_STAT_UNTRACKED:
4934                 return run_io(io, others_argv, NULL, IO_WR);
4936         default:
4937                 die("line type %d not handled in switch", type);
4938                 return FALSE;
4939         }
4942 static bool
4943 status_update_write(struct io *io, struct status *status, enum line_type type)
4945         char buf[SIZEOF_STR];
4946         size_t bufsize = 0;
4948         switch (type) {
4949         case LINE_STAT_STAGED:
4950                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4951                                         status->old.mode,
4952                                         status->old.rev,
4953                                         status->old.name, 0))
4954                         return FALSE;
4955                 break;
4957         case LINE_STAT_UNSTAGED:
4958         case LINE_STAT_UNTRACKED:
4959                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4960                         return FALSE;
4961                 break;
4963         default:
4964                 die("line type %d not handled in switch", type);
4965         }
4967         return io_write(io, buf, bufsize);
4970 static bool
4971 status_update_file(struct status *status, enum line_type type)
4973         struct io io = {};
4974         bool result;
4976         if (!status_update_prepare(&io, type))
4977                 return FALSE;
4979         result = status_update_write(&io, status, type);
4980         done_io(&io);
4981         return result;
4984 static bool
4985 status_update_files(struct view *view, struct line *line)
4987         struct io io = {};
4988         bool result = TRUE;
4989         struct line *pos = view->line + view->lines;
4990         int files = 0;
4991         int file, done;
4993         if (!status_update_prepare(&io, line->type))
4994                 return FALSE;
4996         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4997                 files++;
4999         for (file = 0, done = 0; result && file < files; line++, file++) {
5000                 int almost_done = file * 100 / files;
5002                 if (almost_done > done) {
5003                         done = almost_done;
5004                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5005                                       file, files, done);
5006                         update_view_title(view);
5007                 }
5008                 result = status_update_write(&io, line->data, line->type);
5009         }
5011         done_io(&io);
5012         return result;
5015 static bool
5016 status_update(struct view *view)
5018         struct line *line = &view->line[view->lineno];
5020         assert(view->lines);
5022         if (!line->data) {
5023                 /* This should work even for the "On branch" line. */
5024                 if (line < view->line + view->lines && !line[1].data) {
5025                         report("Nothing to update");
5026                         return FALSE;
5027                 }
5029                 if (!status_update_files(view, line + 1)) {
5030                         report("Failed to update file status");
5031                         return FALSE;
5032                 }
5034         } else if (!status_update_file(line->data, line->type)) {
5035                 report("Failed to update file status");
5036                 return FALSE;
5037         }
5039         return TRUE;
5042 static bool
5043 status_revert(struct status *status, enum line_type type, bool has_none)
5045         if (!status || type != LINE_STAT_UNSTAGED) {
5046                 if (type == LINE_STAT_STAGED) {
5047                         report("Cannot revert changes to staged files");
5048                 } else if (type == LINE_STAT_UNTRACKED) {
5049                         report("Cannot revert changes to untracked files");
5050                 } else if (has_none) {
5051                         report("Nothing to revert");
5052                 } else {
5053                         report("Cannot revert changes to multiple files");
5054                 }
5055                 return FALSE;
5057         } else {
5058                 const char *checkout_argv[] = {
5059                         "git", "checkout", "--", status->old.name, NULL
5060                 };
5062                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5063                         return FALSE;
5064                 return run_io_fg(checkout_argv, opt_cdup);
5065         }
5068 static enum request
5069 status_request(struct view *view, enum request request, struct line *line)
5071         struct status *status = line->data;
5073         switch (request) {
5074         case REQ_STATUS_UPDATE:
5075                 if (!status_update(view))
5076                         return REQ_NONE;
5077                 break;
5079         case REQ_STATUS_REVERT:
5080                 if (!status_revert(status, line->type, status_has_none(view, line)))
5081                         return REQ_NONE;
5082                 break;
5084         case REQ_STATUS_MERGE:
5085                 if (!status || status->status != 'U') {
5086                         report("Merging only possible for files with unmerged status ('U').");
5087                         return REQ_NONE;
5088                 }
5089                 open_mergetool(status->new.name);
5090                 break;
5092         case REQ_EDIT:
5093                 if (!status)
5094                         return request;
5095                 if (status->status == 'D') {
5096                         report("File has been deleted.");
5097                         return REQ_NONE;
5098                 }
5100                 open_editor(status->status != '?', status->new.name);
5101                 break;
5103         case REQ_VIEW_BLAME:
5104                 if (status) {
5105                         string_copy(opt_file, status->new.name);
5106                         opt_ref[0] = 0;
5107                 }
5108                 return request;
5110         case REQ_ENTER:
5111                 /* After returning the status view has been split to
5112                  * show the stage view. No further reloading is
5113                  * necessary. */
5114                 status_enter(view, line);
5115                 return REQ_NONE;
5117         case REQ_REFRESH:
5118                 /* Simply reload the view. */
5119                 break;
5121         default:
5122                 return request;
5123         }
5125         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5127         return REQ_NONE;
5130 static void
5131 status_select(struct view *view, struct line *line)
5133         struct status *status = line->data;
5134         char file[SIZEOF_STR] = "all files";
5135         const char *text;
5136         const char *key;
5138         if (status && !string_format(file, "'%s'", status->new.name))
5139                 return;
5141         if (!status && line[1].type == LINE_STAT_NONE)
5142                 line++;
5144         switch (line->type) {
5145         case LINE_STAT_STAGED:
5146                 text = "Press %s to unstage %s for commit";
5147                 break;
5149         case LINE_STAT_UNSTAGED:
5150                 text = "Press %s to stage %s for commit";
5151                 break;
5153         case LINE_STAT_UNTRACKED:
5154                 text = "Press %s to stage %s for addition";
5155                 break;
5157         case LINE_STAT_HEAD:
5158         case LINE_STAT_NONE:
5159                 text = "Nothing to update";
5160                 break;
5162         default:
5163                 die("line type %d not handled in switch", line->type);
5164         }
5166         if (status && status->status == 'U') {
5167                 text = "Press %s to resolve conflict in %s";
5168                 key = get_key(REQ_STATUS_MERGE);
5170         } else {
5171                 key = get_key(REQ_STATUS_UPDATE);
5172         }
5174         string_format(view->ref, text, key, file);
5177 static bool
5178 status_grep(struct view *view, struct line *line)
5180         struct status *status = line->data;
5181         enum { S_STATUS, S_NAME, S_END } state;
5182         char buf[2] = "?";
5183         regmatch_t pmatch;
5185         if (!status)
5186                 return FALSE;
5188         for (state = S_STATUS; state < S_END; state++) {
5189                 const char *text;
5191                 switch (state) {
5192                 case S_NAME:    text = status->new.name;        break;
5193                 case S_STATUS:
5194                         buf[0] = status->status;
5195                         text = buf;
5196                         break;
5198                 default:
5199                         return FALSE;
5200                 }
5202                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5203                         return TRUE;
5204         }
5206         return FALSE;
5209 static struct view_ops status_ops = {
5210         "file",
5211         NULL,
5212         status_open,
5213         NULL,
5214         status_draw,
5215         status_request,
5216         status_grep,
5217         status_select,
5218 };
5221 static bool
5222 stage_diff_write(struct io *io, struct line *line, struct line *end)
5224         while (line < end) {
5225                 if (!io_write(io, line->data, strlen(line->data)) ||
5226                     !io_write(io, "\n", 1))
5227                         return FALSE;
5228                 line++;
5229                 if (line->type == LINE_DIFF_CHUNK ||
5230                     line->type == LINE_DIFF_HEADER)
5231                         break;
5232         }
5234         return TRUE;
5237 static struct line *
5238 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5240         for (; view->line < line; line--)
5241                 if (line->type == type)
5242                         return line;
5244         return NULL;
5247 static bool
5248 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5250         const char *apply_argv[SIZEOF_ARG] = {
5251                 "git", "apply", "--whitespace=nowarn", NULL
5252         };
5253         struct line *diff_hdr;
5254         struct io io = {};
5255         int argc = 3;
5257         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5258         if (!diff_hdr)
5259                 return FALSE;
5261         if (!revert)
5262                 apply_argv[argc++] = "--cached";
5263         if (revert || stage_line_type == LINE_STAT_STAGED)
5264                 apply_argv[argc++] = "-R";
5265         apply_argv[argc++] = "-";
5266         apply_argv[argc++] = NULL;
5267         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5268                 return FALSE;
5270         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5271             !stage_diff_write(&io, chunk, view->line + view->lines))
5272                 chunk = NULL;
5274         done_io(&io);
5275         run_io_bg(update_index_argv);
5277         return chunk ? TRUE : FALSE;
5280 static bool
5281 stage_update(struct view *view, struct line *line)
5283         struct line *chunk = NULL;
5285         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5286                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5288         if (chunk) {
5289                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5290                         report("Failed to apply chunk");
5291                         return FALSE;
5292                 }
5294         } else if (!stage_status.status) {
5295                 view = VIEW(REQ_VIEW_STATUS);
5297                 for (line = view->line; line < view->line + view->lines; line++)
5298                         if (line->type == stage_line_type)
5299                                 break;
5301                 if (!status_update_files(view, line + 1)) {
5302                         report("Failed to update files");
5303                         return FALSE;
5304                 }
5306         } else if (!status_update_file(&stage_status, stage_line_type)) {
5307                 report("Failed to update file");
5308                 return FALSE;
5309         }
5311         return TRUE;
5314 static bool
5315 stage_revert(struct view *view, struct line *line)
5317         struct line *chunk = NULL;
5319         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5320                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5322         if (chunk) {
5323                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5324                         return FALSE;
5326                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5327                         report("Failed to revert chunk");
5328                         return FALSE;
5329                 }
5330                 return TRUE;
5332         } else {
5333                 return status_revert(stage_status.status ? &stage_status : NULL,
5334                                      stage_line_type, FALSE);
5335         }
5339 static void
5340 stage_next(struct view *view, struct line *line)
5342         int i;
5344         if (!stage_chunks) {
5345                 static size_t alloc = 0;
5346                 int *tmp;
5348                 for (line = view->line; line < view->line + view->lines; line++) {
5349                         if (line->type != LINE_DIFF_CHUNK)
5350                                 continue;
5352                         tmp = realloc_items(stage_chunk, &alloc,
5353                                             stage_chunks, sizeof(*tmp));
5354                         if (!tmp) {
5355                                 report("Allocation failure");
5356                                 return;
5357                         }
5359                         stage_chunk = tmp;
5360                         stage_chunk[stage_chunks++] = line - view->line;
5361                 }
5362         }
5364         for (i = 0; i < stage_chunks; i++) {
5365                 if (stage_chunk[i] > view->lineno) {
5366                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5367                         report("Chunk %d of %d", i + 1, stage_chunks);
5368                         return;
5369                 }
5370         }
5372         report("No next chunk found");
5375 static enum request
5376 stage_request(struct view *view, enum request request, struct line *line)
5378         switch (request) {
5379         case REQ_STATUS_UPDATE:
5380                 if (!stage_update(view, line))
5381                         return REQ_NONE;
5382                 break;
5384         case REQ_STATUS_REVERT:
5385                 if (!stage_revert(view, line))
5386                         return REQ_NONE;
5387                 break;
5389         case REQ_STAGE_NEXT:
5390                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5391                         report("File is untracked; press %s to add",
5392                                get_key(REQ_STATUS_UPDATE));
5393                         return REQ_NONE;
5394                 }
5395                 stage_next(view, line);
5396                 return REQ_NONE;
5398         case REQ_EDIT:
5399                 if (!stage_status.new.name[0])
5400                         return request;
5401                 if (stage_status.status == 'D') {
5402                         report("File has been deleted.");
5403                         return REQ_NONE;
5404                 }
5406                 open_editor(stage_status.status != '?', stage_status.new.name);
5407                 break;
5409         case REQ_REFRESH:
5410                 /* Reload everything ... */
5411                 break;
5413         case REQ_VIEW_BLAME:
5414                 if (stage_status.new.name[0]) {
5415                         string_copy(opt_file, stage_status.new.name);
5416                         opt_ref[0] = 0;
5417                 }
5418                 return request;
5420         case REQ_ENTER:
5421                 return pager_request(view, request, line);
5423         default:
5424                 return request;
5425         }
5427         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5428         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5430         /* Check whether the staged entry still exists, and close the
5431          * stage view if it doesn't. */
5432         if (!status_exists(&stage_status, stage_line_type)) {
5433                 status_restore(VIEW(REQ_VIEW_STATUS));
5434                 return REQ_VIEW_CLOSE;
5435         }
5437         if (stage_line_type == LINE_STAT_UNTRACKED) {
5438                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5439                         report("Cannot display a directory");
5440                         return REQ_NONE;
5441                 }
5443                 if (!prepare_update_file(view, stage_status.new.name)) {
5444                         report("Failed to open file: %s", strerror(errno));
5445                         return REQ_NONE;
5446                 }
5447         }
5448         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5450         return REQ_NONE;
5453 static struct view_ops stage_ops = {
5454         "line",
5455         NULL,
5456         NULL,
5457         pager_read,
5458         pager_draw,
5459         stage_request,
5460         pager_grep,
5461         pager_select,
5462 };
5465 /*
5466  * Revision graph
5467  */
5469 struct commit {
5470         char id[SIZEOF_REV];            /* SHA1 ID. */
5471         char title[128];                /* First line of the commit message. */
5472         char author[75];                /* Author of the commit. */
5473         struct tm time;                 /* Date from the author ident. */
5474         struct ref **refs;              /* Repository references. */
5475         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5476         size_t graph_size;              /* The width of the graph array. */
5477         bool has_parents;               /* Rewritten --parents seen. */
5478 };
5480 /* Size of rev graph with no  "padding" columns */
5481 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5483 struct rev_graph {
5484         struct rev_graph *prev, *next, *parents;
5485         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5486         size_t size;
5487         struct commit *commit;
5488         size_t pos;
5489         unsigned int boundary:1;
5490 };
5492 /* Parents of the commit being visualized. */
5493 static struct rev_graph graph_parents[4];
5495 /* The current stack of revisions on the graph. */
5496 static struct rev_graph graph_stacks[4] = {
5497         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5498         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5499         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5500         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5501 };
5503 static inline bool
5504 graph_parent_is_merge(struct rev_graph *graph)
5506         return graph->parents->size > 1;
5509 static inline void
5510 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5512         struct commit *commit = graph->commit;
5514         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5515                 commit->graph[commit->graph_size++] = symbol;
5518 static void
5519 clear_rev_graph(struct rev_graph *graph)
5521         graph->boundary = 0;
5522         graph->size = graph->pos = 0;
5523         graph->commit = NULL;
5524         memset(graph->parents, 0, sizeof(*graph->parents));
5527 static void
5528 done_rev_graph(struct rev_graph *graph)
5530         if (graph_parent_is_merge(graph) &&
5531             graph->pos < graph->size - 1 &&
5532             graph->next->size == graph->size + graph->parents->size - 1) {
5533                 size_t i = graph->pos + graph->parents->size - 1;
5535                 graph->commit->graph_size = i * 2;
5536                 while (i < graph->next->size - 1) {
5537                         append_to_rev_graph(graph, ' ');
5538                         append_to_rev_graph(graph, '\\');
5539                         i++;
5540                 }
5541         }
5543         clear_rev_graph(graph);
5546 static void
5547 push_rev_graph(struct rev_graph *graph, const char *parent)
5549         int i;
5551         /* "Collapse" duplicate parents lines.
5552          *
5553          * FIXME: This needs to also update update the drawn graph but
5554          * for now it just serves as a method for pruning graph lines. */
5555         for (i = 0; i < graph->size; i++)
5556                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5557                         return;
5559         if (graph->size < SIZEOF_REVITEMS) {
5560                 string_copy_rev(graph->rev[graph->size++], parent);
5561         }
5564 static chtype
5565 get_rev_graph_symbol(struct rev_graph *graph)
5567         chtype symbol;
5569         if (graph->boundary)
5570                 symbol = REVGRAPH_BOUND;
5571         else if (graph->parents->size == 0)
5572                 symbol = REVGRAPH_INIT;
5573         else if (graph_parent_is_merge(graph))
5574                 symbol = REVGRAPH_MERGE;
5575         else if (graph->pos >= graph->size)
5576                 symbol = REVGRAPH_BRANCH;
5577         else
5578                 symbol = REVGRAPH_COMMIT;
5580         return symbol;
5583 static void
5584 draw_rev_graph(struct rev_graph *graph)
5586         struct rev_filler {
5587                 chtype separator, line;
5588         };
5589         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5590         static struct rev_filler fillers[] = {
5591                 { ' ',  '|' },
5592                 { '`',  '.' },
5593                 { '\'', ' ' },
5594                 { '/',  ' ' },
5595         };
5596         chtype symbol = get_rev_graph_symbol(graph);
5597         struct rev_filler *filler;
5598         size_t i;
5600         if (opt_line_graphics)
5601                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5603         filler = &fillers[DEFAULT];
5605         for (i = 0; i < graph->pos; i++) {
5606                 append_to_rev_graph(graph, filler->line);
5607                 if (graph_parent_is_merge(graph->prev) &&
5608                     graph->prev->pos == i)
5609                         filler = &fillers[RSHARP];
5611                 append_to_rev_graph(graph, filler->separator);
5612         }
5614         /* Place the symbol for this revision. */
5615         append_to_rev_graph(graph, symbol);
5617         if (graph->prev->size > graph->size)
5618                 filler = &fillers[RDIAG];
5619         else
5620                 filler = &fillers[DEFAULT];
5622         i++;
5624         for (; i < graph->size; i++) {
5625                 append_to_rev_graph(graph, filler->separator);
5626                 append_to_rev_graph(graph, filler->line);
5627                 if (graph_parent_is_merge(graph->prev) &&
5628                     i < graph->prev->pos + graph->parents->size)
5629                         filler = &fillers[RSHARP];
5630                 if (graph->prev->size > graph->size)
5631                         filler = &fillers[LDIAG];
5632         }
5634         if (graph->prev->size > graph->size) {
5635                 append_to_rev_graph(graph, filler->separator);
5636                 if (filler->line != ' ')
5637                         append_to_rev_graph(graph, filler->line);
5638         }
5641 /* Prepare the next rev graph */
5642 static void
5643 prepare_rev_graph(struct rev_graph *graph)
5645         size_t i;
5647         /* First, traverse all lines of revisions up to the active one. */
5648         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5649                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5650                         break;
5652                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5653         }
5655         /* Interleave the new revision parent(s). */
5656         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5657                 push_rev_graph(graph->next, graph->parents->rev[i]);
5659         /* Lastly, put any remaining revisions. */
5660         for (i = graph->pos + 1; i < graph->size; i++)
5661                 push_rev_graph(graph->next, graph->rev[i]);
5664 static void
5665 update_rev_graph(struct view *view, struct rev_graph *graph)
5667         /* If this is the finalizing update ... */
5668         if (graph->commit)
5669                 prepare_rev_graph(graph);
5671         /* Graph visualization needs a one rev look-ahead,
5672          * so the first update doesn't visualize anything. */
5673         if (!graph->prev->commit)
5674                 return;
5676         if (view->lines > 2)
5677                 view->line[view->lines - 3].dirty = 1;
5678         if (view->lines > 1)
5679                 view->line[view->lines - 2].dirty = 1;
5680         draw_rev_graph(graph->prev);
5681         done_rev_graph(graph->prev->prev);
5685 /*
5686  * Main view backend
5687  */
5689 static const char *main_argv[SIZEOF_ARG] = {
5690         "git", "log", "--no-color", "--pretty=raw", "--parents",
5691                       "--topo-order", "%(head)", NULL
5692 };
5694 static bool
5695 main_draw(struct view *view, struct line *line, unsigned int lineno)
5697         struct commit *commit = line->data;
5699         if (!*commit->author)
5700                 return FALSE;
5702         if (opt_date && draw_date(view, &commit->time))
5703                 return TRUE;
5705         if (opt_author && draw_author(view, commit->author))
5706                 return TRUE;
5708         if (opt_rev_graph && commit->graph_size &&
5709             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5710                 return TRUE;
5712         if (opt_show_refs && commit->refs) {
5713                 size_t i = 0;
5715                 do {
5716                         enum line_type type;
5718                         if (commit->refs[i]->head)
5719                                 type = LINE_MAIN_HEAD;
5720                         else if (commit->refs[i]->ltag)
5721                                 type = LINE_MAIN_LOCAL_TAG;
5722                         else if (commit->refs[i]->tag)
5723                                 type = LINE_MAIN_TAG;
5724                         else if (commit->refs[i]->tracked)
5725                                 type = LINE_MAIN_TRACKED;
5726                         else if (commit->refs[i]->remote)
5727                                 type = LINE_MAIN_REMOTE;
5728                         else
5729                                 type = LINE_MAIN_REF;
5731                         if (draw_text(view, type, "[", TRUE) ||
5732                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5733                             draw_text(view, type, "]", TRUE))
5734                                 return TRUE;
5736                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5737                                 return TRUE;
5738                 } while (commit->refs[i++]->next);
5739         }
5741         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5742         return TRUE;
5745 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5746 static bool
5747 main_read(struct view *view, char *line)
5749         static struct rev_graph *graph = graph_stacks;
5750         enum line_type type;
5751         struct commit *commit;
5753         if (!line) {
5754                 int i;
5756                 if (!view->lines && !view->parent)
5757                         die("No revisions match the given arguments.");
5758                 if (view->lines > 0) {
5759                         commit = view->line[view->lines - 1].data;
5760                         view->line[view->lines - 1].dirty = 1;
5761                         if (!*commit->author) {
5762                                 view->lines--;
5763                                 free(commit);
5764                                 graph->commit = NULL;
5765                         }
5766                 }
5767                 update_rev_graph(view, graph);
5769                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5770                         clear_rev_graph(&graph_stacks[i]);
5771                 return TRUE;
5772         }
5774         type = get_line_type(line);
5775         if (type == LINE_COMMIT) {
5776                 commit = calloc(1, sizeof(struct commit));
5777                 if (!commit)
5778                         return FALSE;
5780                 line += STRING_SIZE("commit ");
5781                 if (*line == '-') {
5782                         graph->boundary = 1;
5783                         line++;
5784                 }
5786                 string_copy_rev(commit->id, line);
5787                 commit->refs = get_refs(commit->id);
5788                 graph->commit = commit;
5789                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5791                 while ((line = strchr(line, ' '))) {
5792                         line++;
5793                         push_rev_graph(graph->parents, line);
5794                         commit->has_parents = TRUE;
5795                 }
5796                 return TRUE;
5797         }
5799         if (!view->lines)
5800                 return TRUE;
5801         commit = view->line[view->lines - 1].data;
5803         switch (type) {
5804         case LINE_PARENT:
5805                 if (commit->has_parents)
5806                         break;
5807                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5808                 break;
5810         case LINE_AUTHOR:
5811                 parse_author_line(line + STRING_SIZE("author "),
5812                                   commit->author, sizeof(commit->author),
5813                                   &commit->time);
5814                 update_rev_graph(view, graph);
5815                 graph = graph->next;
5816                 break;
5818         default:
5819                 /* Fill in the commit title if it has not already been set. */
5820                 if (commit->title[0])
5821                         break;
5823                 /* Require titles to start with a non-space character at the
5824                  * offset used by git log. */
5825                 if (strncmp(line, "    ", 4))
5826                         break;
5827                 line += 4;
5828                 /* Well, if the title starts with a whitespace character,
5829                  * try to be forgiving.  Otherwise we end up with no title. */
5830                 while (isspace(*line))
5831                         line++;
5832                 if (*line == '\0')
5833                         break;
5834                 /* FIXME: More graceful handling of titles; append "..." to
5835                  * shortened titles, etc. */
5837                 string_ncopy(commit->title, line, strlen(line));
5838                 view->line[view->lines - 1].dirty = 1;
5839         }
5841         return TRUE;
5844 static enum request
5845 main_request(struct view *view, enum request request, struct line *line)
5847         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5849         switch (request) {
5850         case REQ_ENTER:
5851                 open_view(view, REQ_VIEW_DIFF, flags);
5852                 break;
5853         case REQ_REFRESH:
5854                 load_refs();
5855                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5856                 break;
5857         default:
5858                 return request;
5859         }
5861         return REQ_NONE;
5864 static bool
5865 grep_refs(struct ref **refs, regex_t *regex)
5867         regmatch_t pmatch;
5868         size_t i = 0;
5870         if (!refs)
5871                 return FALSE;
5872         do {
5873                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5874                         return TRUE;
5875         } while (refs[i++]->next);
5877         return FALSE;
5880 static bool
5881 main_grep(struct view *view, struct line *line)
5883         struct commit *commit = line->data;
5884         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5885         char buf[DATE_COLS + 1];
5886         regmatch_t pmatch;
5888         for (state = S_TITLE; state < S_END; state++) {
5889                 char *text;
5891                 switch (state) {
5892                 case S_TITLE:   text = commit->title;   break;
5893                 case S_AUTHOR:
5894                         if (!opt_author)
5895                                 continue;
5896                         text = commit->author;
5897                         break;
5898                 case S_DATE:
5899                         if (!opt_date)
5900                                 continue;
5901                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5902                                 continue;
5903                         text = buf;
5904                         break;
5905                 case S_REFS:
5906                         if (!opt_show_refs)
5907                                 continue;
5908                         if (grep_refs(commit->refs, view->regex) == TRUE)
5909                                 return TRUE;
5910                         continue;
5911                 default:
5912                         return FALSE;
5913                 }
5915                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5916                         return TRUE;
5917         }
5919         return FALSE;
5922 static void
5923 main_select(struct view *view, struct line *line)
5925         struct commit *commit = line->data;
5927         string_copy_rev(view->ref, commit->id);
5928         string_copy_rev(ref_commit, view->ref);
5931 static struct view_ops main_ops = {
5932         "commit",
5933         main_argv,
5934         NULL,
5935         main_read,
5936         main_draw,
5937         main_request,
5938         main_grep,
5939         main_select,
5940 };
5943 /*
5944  * Unicode / UTF-8 handling
5945  *
5946  * NOTE: Much of the following code for dealing with unicode is derived from
5947  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5948  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5949  */
5951 /* I've (over)annotated a lot of code snippets because I am not entirely
5952  * confident that the approach taken by this small UTF-8 interface is correct.
5953  * --jonas */
5955 static inline int
5956 unicode_width(unsigned long c)
5958         if (c >= 0x1100 &&
5959            (c <= 0x115f                         /* Hangul Jamo */
5960             || c == 0x2329
5961             || c == 0x232a
5962             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5963                                                 /* CJK ... Yi */
5964             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5965             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5966             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5967             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5968             || (c >= 0xffe0  && c <= 0xffe6)
5969             || (c >= 0x20000 && c <= 0x2fffd)
5970             || (c >= 0x30000 && c <= 0x3fffd)))
5971                 return 2;
5973         if (c == '\t')
5974                 return opt_tab_size;
5976         return 1;
5979 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5980  * Illegal bytes are set one. */
5981 static const unsigned char utf8_bytes[256] = {
5982         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,
5983         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,
5984         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,
5985         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,
5986         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,
5987         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,
5988         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,
5989         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,
5990 };
5992 /* Decode UTF-8 multi-byte representation into a unicode character. */
5993 static inline unsigned long
5994 utf8_to_unicode(const char *string, size_t length)
5996         unsigned long unicode;
5998         switch (length) {
5999         case 1:
6000                 unicode  =   string[0];
6001                 break;
6002         case 2:
6003                 unicode  =  (string[0] & 0x1f) << 6;
6004                 unicode +=  (string[1] & 0x3f);
6005                 break;
6006         case 3:
6007                 unicode  =  (string[0] & 0x0f) << 12;
6008                 unicode += ((string[1] & 0x3f) << 6);
6009                 unicode +=  (string[2] & 0x3f);
6010                 break;
6011         case 4:
6012                 unicode  =  (string[0] & 0x0f) << 18;
6013                 unicode += ((string[1] & 0x3f) << 12);
6014                 unicode += ((string[2] & 0x3f) << 6);
6015                 unicode +=  (string[3] & 0x3f);
6016                 break;
6017         case 5:
6018                 unicode  =  (string[0] & 0x0f) << 24;
6019                 unicode += ((string[1] & 0x3f) << 18);
6020                 unicode += ((string[2] & 0x3f) << 12);
6021                 unicode += ((string[3] & 0x3f) << 6);
6022                 unicode +=  (string[4] & 0x3f);
6023                 break;
6024         case 6:
6025                 unicode  =  (string[0] & 0x01) << 30;
6026                 unicode += ((string[1] & 0x3f) << 24);
6027                 unicode += ((string[2] & 0x3f) << 18);
6028                 unicode += ((string[3] & 0x3f) << 12);
6029                 unicode += ((string[4] & 0x3f) << 6);
6030                 unicode +=  (string[5] & 0x3f);
6031                 break;
6032         default:
6033                 die("Invalid unicode length");
6034         }
6036         /* Invalid characters could return the special 0xfffd value but NUL
6037          * should be just as good. */
6038         return unicode > 0xffff ? 0 : unicode;
6041 /* Calculates how much of string can be shown within the given maximum width
6042  * and sets trimmed parameter to non-zero value if all of string could not be
6043  * shown. If the reserve flag is TRUE, it will reserve at least one
6044  * trailing character, which can be useful when drawing a delimiter.
6045  *
6046  * Returns the number of bytes to output from string to satisfy max_width. */
6047 static size_t
6048 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6050         const char *start = string;
6051         const char *end = strchr(string, '\0');
6052         unsigned char last_bytes = 0;
6053         size_t last_ucwidth = 0;
6055         *width = 0;
6056         *trimmed = 0;
6058         while (string < end) {
6059                 int c = *(unsigned char *) string;
6060                 unsigned char bytes = utf8_bytes[c];
6061                 size_t ucwidth;
6062                 unsigned long unicode;
6064                 if (string + bytes > end)
6065                         break;
6067                 /* Change representation to figure out whether
6068                  * it is a single- or double-width character. */
6070                 unicode = utf8_to_unicode(string, bytes);
6071                 /* FIXME: Graceful handling of invalid unicode character. */
6072                 if (!unicode)
6073                         break;
6075                 ucwidth = unicode_width(unicode);
6076                 *width  += ucwidth;
6077                 if (*width > max_width) {
6078                         *trimmed = 1;
6079                         *width -= ucwidth;
6080                         if (reserve && *width == max_width) {
6081                                 string -= last_bytes;
6082                                 *width -= last_ucwidth;
6083                         }
6084                         break;
6085                 }
6087                 string  += bytes;
6088                 last_bytes = bytes;
6089                 last_ucwidth = ucwidth;
6090         }
6092         return string - start;
6096 /*
6097  * Status management
6098  */
6100 /* Whether or not the curses interface has been initialized. */
6101 static bool cursed = FALSE;
6103 /* The status window is used for polling keystrokes. */
6104 static WINDOW *status_win;
6106 /* Reading from the prompt? */
6107 static bool input_mode = FALSE;
6109 static bool status_empty = FALSE;
6111 /* Update status and title window. */
6112 static void
6113 report(const char *msg, ...)
6115         struct view *view = display[current_view];
6117         if (input_mode)
6118                 return;
6120         if (!view) {
6121                 char buf[SIZEOF_STR];
6122                 va_list args;
6124                 va_start(args, msg);
6125                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6126                         buf[sizeof(buf) - 1] = 0;
6127                         buf[sizeof(buf) - 2] = '.';
6128                         buf[sizeof(buf) - 3] = '.';
6129                         buf[sizeof(buf) - 4] = '.';
6130                 }
6131                 va_end(args);
6132                 die("%s", buf);
6133         }
6135         if (!status_empty || *msg) {
6136                 va_list args;
6138                 va_start(args, msg);
6140                 wmove(status_win, 0, 0);
6141                 if (*msg) {
6142                         vwprintw(status_win, msg, args);
6143                         status_empty = FALSE;
6144                 } else {
6145                         status_empty = TRUE;
6146                 }
6147                 wclrtoeol(status_win);
6148                 wnoutrefresh(status_win);
6150                 va_end(args);
6151         }
6153         update_view_title(view);
6156 /* Controls when nodelay should be in effect when polling user input. */
6157 static void
6158 set_nonblocking_input(bool loading)
6160         static unsigned int loading_views;
6162         if ((loading == FALSE && loading_views-- == 1) ||
6163             (loading == TRUE  && loading_views++ == 0))
6164                 nodelay(status_win, loading);
6167 static void
6168 init_display(void)
6170         int x, y;
6172         /* Initialize the curses library */
6173         if (isatty(STDIN_FILENO)) {
6174                 cursed = !!initscr();
6175                 opt_tty = stdin;
6176         } else {
6177                 /* Leave stdin and stdout alone when acting as a pager. */
6178                 opt_tty = fopen("/dev/tty", "r+");
6179                 if (!opt_tty)
6180                         die("Failed to open /dev/tty");
6181                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6182         }
6184         if (!cursed)
6185                 die("Failed to initialize curses");
6187         nonl();         /* Tell curses not to do NL->CR/NL on output */
6188         cbreak();       /* Take input chars one at a time, no wait for \n */
6189         noecho();       /* Don't echo input */
6190         leaveok(stdscr, FALSE);
6192         if (has_colors())
6193                 init_colors();
6195         getmaxyx(stdscr, y, x);
6196         status_win = newwin(1, 0, y - 1, 0);
6197         if (!status_win)
6198                 die("Failed to create status window");
6200         /* Enable keyboard mapping */
6201         keypad(status_win, TRUE);
6202         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6204         TABSIZE = opt_tab_size;
6205         if (opt_line_graphics) {
6206                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6207         }
6210 static int
6211 get_input(int prompt_position)
6213         struct view *view;
6214         int i, key, cursor_y, cursor_x;
6216         if (prompt_position)
6217                 input_mode = TRUE;
6219         while (TRUE) {
6220                 foreach_view (view, i)
6221                         update_view(view);
6223                 /* Update the cursor position. */
6224                 if (prompt_position) {
6225                         getbegyx(status_win, cursor_y, cursor_x);
6226                         cursor_x = prompt_position;
6227                 } else {
6228                         view = display[current_view];
6229                         getbegyx(view->win, cursor_y, cursor_x);
6230                         cursor_x = view->width - 1;
6231                         cursor_y += view->lineno - view->offset;
6232                 }
6233                 setsyx(cursor_y, cursor_x);
6235                 /* Refresh, accept single keystroke of input */
6236                 doupdate();
6237                 key = wgetch(status_win);
6239                 /* wgetch() with nodelay() enabled returns ERR when
6240                  * there's no input. */
6241                 if (key == ERR) {
6243                 } else if (key == KEY_RESIZE) {
6244                         int height, width;
6246                         getmaxyx(stdscr, height, width);
6248                         wresize(status_win, 1, width);
6249                         mvwin(status_win, height - 1, 0);
6250                         wnoutrefresh(status_win);
6251                         resize_display();
6252                         redraw_display(TRUE);
6254                 } else {
6255                         input_mode = FALSE;
6256                         return key;
6257                 }
6258         }
6261 static char *
6262 prompt_input(const char *prompt, input_handler handler, void *data)
6264         enum input_status status = INPUT_OK;
6265         static char buf[SIZEOF_STR];
6266         size_t pos = 0;
6268         buf[pos] = 0;
6270         while (status == INPUT_OK || status == INPUT_SKIP) {
6271                 int key;
6273                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6274                 wclrtoeol(status_win);
6276                 key = get_input(pos + 1);
6277                 switch (key) {
6278                 case KEY_RETURN:
6279                 case KEY_ENTER:
6280                 case '\n':
6281                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6282                         break;
6284                 case KEY_BACKSPACE:
6285                         if (pos > 0)
6286                                 buf[--pos] = 0;
6287                         else
6288                                 status = INPUT_CANCEL;
6289                         break;
6291                 case KEY_ESC:
6292                         status = INPUT_CANCEL;
6293                         break;
6295                 default:
6296                         if (pos >= sizeof(buf)) {
6297                                 report("Input string too long");
6298                                 return NULL;
6299                         }
6301                         status = handler(data, buf, key);
6302                         if (status == INPUT_OK)
6303                                 buf[pos++] = (char) key;
6304                 }
6305         }
6307         /* Clear the status window */
6308         status_empty = FALSE;
6309         report("");
6311         if (status == INPUT_CANCEL)
6312                 return NULL;
6314         buf[pos++] = 0;
6316         return buf;
6319 static enum input_status
6320 prompt_yesno_handler(void *data, char *buf, int c)
6322         if (c == 'y' || c == 'Y')
6323                 return INPUT_STOP;
6324         if (c == 'n' || c == 'N')
6325                 return INPUT_CANCEL;
6326         return INPUT_SKIP;
6329 static bool
6330 prompt_yesno(const char *prompt)
6332         char prompt2[SIZEOF_STR];
6334         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6335                 return FALSE;
6337         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6340 static enum input_status
6341 read_prompt_handler(void *data, char *buf, int c)
6343         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6346 static char *
6347 read_prompt(const char *prompt)
6349         return prompt_input(prompt, read_prompt_handler, NULL);
6352 /*
6353  * Repository properties
6354  */
6356 static int
6357 git_properties(const char **argv, const char *separators,
6358                int (*read_property)(char *, size_t, char *, size_t))
6360         struct io io = {};
6362         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6363                 return read_properties(&io, separators, read_property);
6364         return ERR;
6367 static struct ref *refs = NULL;
6368 static size_t refs_alloc = 0;
6369 static size_t refs_size = 0;
6371 /* Id <-> ref store */
6372 static struct ref ***id_refs = NULL;
6373 static size_t id_refs_alloc = 0;
6374 static size_t id_refs_size = 0;
6376 static int
6377 compare_refs(const void *ref1_, const void *ref2_)
6379         const struct ref *ref1 = *(const struct ref **)ref1_;
6380         const struct ref *ref2 = *(const struct ref **)ref2_;
6382         if (ref1->tag != ref2->tag)
6383                 return ref2->tag - ref1->tag;
6384         if (ref1->ltag != ref2->ltag)
6385                 return ref2->ltag - ref2->ltag;
6386         if (ref1->head != ref2->head)
6387                 return ref2->head - ref1->head;
6388         if (ref1->tracked != ref2->tracked)
6389                 return ref2->tracked - ref1->tracked;
6390         if (ref1->remote != ref2->remote)
6391                 return ref2->remote - ref1->remote;
6392         return strcmp(ref1->name, ref2->name);
6395 static struct ref **
6396 get_refs(const char *id)
6398         struct ref ***tmp_id_refs;
6399         struct ref **ref_list = NULL;
6400         size_t ref_list_alloc = 0;
6401         size_t ref_list_size = 0;
6402         size_t i;
6404         for (i = 0; i < id_refs_size; i++)
6405                 if (!strcmp(id, id_refs[i][0]->id))
6406                         return id_refs[i];
6408         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6409                                     sizeof(*id_refs));
6410         if (!tmp_id_refs)
6411                 return NULL;
6413         id_refs = tmp_id_refs;
6415         for (i = 0; i < refs_size; i++) {
6416                 struct ref **tmp;
6418                 if (strcmp(id, refs[i].id))
6419                         continue;
6421                 tmp = realloc_items(ref_list, &ref_list_alloc,
6422                                     ref_list_size + 1, sizeof(*ref_list));
6423                 if (!tmp) {
6424                         if (ref_list)
6425                                 free(ref_list);
6426                         return NULL;
6427                 }
6429                 ref_list = tmp;
6430                 ref_list[ref_list_size] = &refs[i];
6431                 /* XXX: The properties of the commit chains ensures that we can
6432                  * safely modify the shared ref. The repo references will
6433                  * always be similar for the same id. */
6434                 ref_list[ref_list_size]->next = 1;
6436                 ref_list_size++;
6437         }
6439         if (ref_list) {
6440                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6441                 ref_list[ref_list_size - 1]->next = 0;
6442                 id_refs[id_refs_size++] = ref_list;
6443         }
6445         return ref_list;
6448 static int
6449 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6451         struct ref *ref;
6452         bool tag = FALSE;
6453         bool ltag = FALSE;
6454         bool remote = FALSE;
6455         bool tracked = FALSE;
6456         bool check_replace = FALSE;
6457         bool head = FALSE;
6459         if (!prefixcmp(name, "refs/tags/")) {
6460                 if (!suffixcmp(name, namelen, "^{}")) {
6461                         namelen -= 3;
6462                         name[namelen] = 0;
6463                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6464                                 check_replace = TRUE;
6465                 } else {
6466                         ltag = TRUE;
6467                 }
6469                 tag = TRUE;
6470                 namelen -= STRING_SIZE("refs/tags/");
6471                 name    += STRING_SIZE("refs/tags/");
6473         } else if (!prefixcmp(name, "refs/remotes/")) {
6474                 remote = TRUE;
6475                 namelen -= STRING_SIZE("refs/remotes/");
6476                 name    += STRING_SIZE("refs/remotes/");
6477                 tracked  = !strcmp(opt_remote, name);
6479         } else if (!prefixcmp(name, "refs/heads/")) {
6480                 namelen -= STRING_SIZE("refs/heads/");
6481                 name    += STRING_SIZE("refs/heads/");
6482                 head     = !strncmp(opt_head, name, namelen);
6484         } else if (!strcmp(name, "HEAD")) {
6485                 string_ncopy(opt_head_rev, id, idlen);
6486                 return OK;
6487         }
6489         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6490                 /* it's an annotated tag, replace the previous sha1 with the
6491                  * resolved commit id; relies on the fact git-ls-remote lists
6492                  * the commit id of an annotated tag right before the commit id
6493                  * it points to. */
6494                 refs[refs_size - 1].ltag = ltag;
6495                 string_copy_rev(refs[refs_size - 1].id, id);
6497                 return OK;
6498         }
6499         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6500         if (!refs)
6501                 return ERR;
6503         ref = &refs[refs_size++];
6504         ref->name = malloc(namelen + 1);
6505         if (!ref->name)
6506                 return ERR;
6508         strncpy(ref->name, name, namelen);
6509         ref->name[namelen] = 0;
6510         ref->head = head;
6511         ref->tag = tag;
6512         ref->ltag = ltag;
6513         ref->remote = remote;
6514         ref->tracked = tracked;
6515         string_copy_rev(ref->id, id);
6517         return OK;
6520 static int
6521 load_refs(void)
6523         static const char *ls_remote_argv[SIZEOF_ARG] = {
6524                 "git", "ls-remote", ".", NULL
6525         };
6526         static bool init = FALSE;
6528         if (!init) {
6529                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6530                 init = TRUE;
6531         }
6533         if (!*opt_git_dir)
6534                 return OK;
6536         while (refs_size > 0)
6537                 free(refs[--refs_size].name);
6538         while (id_refs_size > 0)
6539                 free(id_refs[--id_refs_size]);
6541         return git_properties(ls_remote_argv, "\t", read_ref);
6544 static int
6545 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6547         if (!strcmp(name, "i18n.commitencoding"))
6548                 string_ncopy(opt_encoding, value, valuelen);
6550         if (!strcmp(name, "core.editor"))
6551                 string_ncopy(opt_editor, value, valuelen);
6553         /* branch.<head>.remote */
6554         if (*opt_head &&
6555             !strncmp(name, "branch.", 7) &&
6556             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6557             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6558                 string_ncopy(opt_remote, value, valuelen);
6560         if (*opt_head && *opt_remote &&
6561             !strncmp(name, "branch.", 7) &&
6562             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6563             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6564                 size_t from = strlen(opt_remote);
6566                 if (!prefixcmp(value, "refs/heads/")) {
6567                         value += STRING_SIZE("refs/heads/");
6568                         valuelen -= STRING_SIZE("refs/heads/");
6569                 }
6571                 if (!string_format_from(opt_remote, &from, "/%s", value))
6572                         opt_remote[0] = 0;
6573         }
6575         return OK;
6578 static int
6579 load_git_config(void)
6581         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6583         return git_properties(config_list_argv, "=", read_repo_config_option);
6586 static int
6587 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6589         if (!opt_git_dir[0]) {
6590                 string_ncopy(opt_git_dir, name, namelen);
6592         } else if (opt_is_inside_work_tree == -1) {
6593                 /* This can be 3 different values depending on the
6594                  * version of git being used. If git-rev-parse does not
6595                  * understand --is-inside-work-tree it will simply echo
6596                  * the option else either "true" or "false" is printed.
6597                  * Default to true for the unknown case. */
6598                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6600         } else if (*name == '.') {
6601                 string_ncopy(opt_cdup, name, namelen);
6603         } else {
6604                 string_ncopy(opt_prefix, name, namelen);
6605         }
6607         return OK;
6610 static int
6611 load_repo_info(void)
6613         const char *head_argv[] = {
6614                 "git", "symbolic-ref", "HEAD", NULL
6615         };
6616         const char *rev_parse_argv[] = {
6617                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6618                         "--show-cdup", "--show-prefix", NULL
6619         };
6621         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6622                 chomp_string(opt_head);
6623                 if (!prefixcmp(opt_head, "refs/heads/")) {
6624                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6626                         memmove(opt_head, offset, strlen(offset) + 1);
6627                 }
6628         }
6630         return git_properties(rev_parse_argv, "=", read_repo_info);
6633 static int
6634 read_properties(struct io *io, const char *separators,
6635                 int (*read_property)(char *, size_t, char *, size_t))
6637         char *name;
6638         int state = OK;
6640         if (!start_io(io))
6641                 return ERR;
6643         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6644                 char *value;
6645                 size_t namelen;
6646                 size_t valuelen;
6648                 name = chomp_string(name);
6649                 namelen = strcspn(name, separators);
6651                 if (name[namelen]) {
6652                         name[namelen] = 0;
6653                         value = chomp_string(name + namelen + 1);
6654                         valuelen = strlen(value);
6656                 } else {
6657                         value = "";
6658                         valuelen = 0;
6659                 }
6661                 state = read_property(name, namelen, value, valuelen);
6662         }
6664         if (state != ERR && io_error(io))
6665                 state = ERR;
6666         done_io(io);
6668         return state;
6672 /*
6673  * Main
6674  */
6676 static void __NORETURN
6677 quit(int sig)
6679         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6680         if (cursed)
6681                 endwin();
6682         exit(0);
6685 static void __NORETURN
6686 die(const char *err, ...)
6688         va_list args;
6690         endwin();
6692         va_start(args, err);
6693         fputs("tig: ", stderr);
6694         vfprintf(stderr, err, args);
6695         fputs("\n", stderr);
6696         va_end(args);
6698         exit(1);
6701 static void
6702 warn(const char *msg, ...)
6704         va_list args;
6706         va_start(args, msg);
6707         fputs("tig warning: ", stderr);
6708         vfprintf(stderr, msg, args);
6709         fputs("\n", stderr);
6710         va_end(args);
6713 int
6714 main(int argc, const char *argv[])
6716         const char **run_argv = NULL;
6717         struct view *view;
6718         enum request request;
6719         size_t i;
6721         signal(SIGINT, quit);
6723         if (setlocale(LC_ALL, "")) {
6724                 char *codeset = nl_langinfo(CODESET);
6726                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6727         }
6729         if (load_repo_info() == ERR)
6730                 die("Failed to load repo info.");
6732         if (load_options() == ERR)
6733                 die("Failed to load user config.");
6735         if (load_git_config() == ERR)
6736                 die("Failed to load repo config.");
6738         request = parse_options(argc, argv, &run_argv);
6739         if (request == REQ_NONE)
6740                 return 0;
6742         /* Require a git repository unless when running in pager mode. */
6743         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6744                 die("Not a git repository");
6746         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6747                 opt_utf8 = FALSE;
6749         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6750                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6751                 if (opt_iconv == ICONV_NONE)
6752                         die("Failed to initialize character set conversion");
6753         }
6755         if (load_refs() == ERR)
6756                 die("Failed to load refs.");
6758         foreach_view (view, i)
6759                 argv_from_env(view->ops->argv, view->cmd_env);
6761         init_display();
6763         if (request == REQ_VIEW_PAGER || run_argv) {
6764                 if (request == REQ_VIEW_PAGER)
6765                         io_open(&VIEW(request)->io, "");
6766                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6767                         die("Failed to format arguments");
6768                 open_view(NULL, request, OPEN_PREPARED);
6769                 request = REQ_NONE;
6770         }
6772         while (view_driver(display[current_view], request)) {
6773                 int key = get_input(0);
6775                 view = display[current_view];
6776                 request = get_keybinding(view->keymap, key);
6778                 /* Some low-level request handling. This keeps access to
6779                  * status_win restricted. */
6780                 switch (request) {
6781                 case REQ_PROMPT:
6782                 {
6783                         char *cmd = read_prompt(":");
6785                         if (cmd) {
6786                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6787                                 const char *argv[SIZEOF_ARG] = { "git" };
6788                                 int argc = 1;
6790                                 /* When running random commands, initially show the
6791                                  * command in the title. However, it maybe later be
6792                                  * overwritten if a commit line is selected. */
6793                                 string_ncopy(next->ref, cmd, strlen(cmd));
6795                                 if (!argv_from_string(argv, &argc, cmd)) {
6796                                         report("Too many arguments");
6797                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6798                                         report("Failed to format command");
6799                                 } else {
6800                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6801                                 }
6802                         }
6804                         request = REQ_NONE;
6805                         break;
6806                 }
6807                 case REQ_SEARCH:
6808                 case REQ_SEARCH_BACK:
6809                 {
6810                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6811                         char *search = read_prompt(prompt);
6813                         if (search)
6814                                 string_ncopy(opt_search, search, strlen(search));
6815                         else
6816                                 request = REQ_NONE;
6817                         break;
6818                 }
6819                 default:
6820                         break;
6821                 }
6822         }
6824         quit(0);
6826         return 0;