Code

f72932144b4d893db73720d3946da34d15b97ee7
[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 bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
76 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
78 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x)  (sizeof(x) - 1)
81 #define SIZEOF_STR      1024    /* Default string size. */
82 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG      32      /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT   'I'
89 #define REVGRAPH_MERGE  'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND  '^'
94 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT   (-1)
99 #define ICONV_NONE      ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST     /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
106 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS     20
109 #define ID_COLS         8
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152         const char *name;
153         int namelen;
154         int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159                  int *value, const char *name, int namelen)
162         int i;
164         for (i = 0; i < map_size; i++)
165                 if (namelen == map[i].namelen &&
166                     !strncasecmp(name, map[i].name, namelen)) {
167                         *value = map[i].value;
168                         return OK;
169                 }
171         return ERR;
175 /*
176  * String helpers
177  */
179 static inline void
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
182         if (srclen > dstlen - 1)
183                 srclen = dstlen - 1;
185         strncpy(dst, src, srclen);
186         dst[srclen] = 0;
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195         string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 static char *
204 chomp_string(char *name)
206         int namelen;
208         while (isspace(*name))
209                 name++;
211         namelen = strlen(name) - 1;
212         while (namelen > 0 && isspace(name[namelen]))
213                 name[namelen--] = 0;
215         return name;
218 static bool
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221         va_list args;
222         size_t pos = bufpos ? *bufpos : 0;
224         va_start(args, fmt);
225         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
226         va_end(args);
228         if (bufpos)
229                 *bufpos = pos;
231         return pos >= bufsize ? FALSE : TRUE;
234 #define string_format(buf, fmt, args...) \
235         string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238         string_nformat(buf, sizeof(buf), from, fmt, args)
240 static int
241 string_enum_compare(const char *str1, const char *str2, int len)
243         size_t i;
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247         /* Diff-Header == DIFF_HEADER */
248         for (i = 0; i < len; i++) {
249                 if (toupper(str1[i]) == toupper(str2[i]))
250                         continue;
252                 if (string_enum_sep(str1[i]) &&
253                     string_enum_sep(str2[i]))
254                         continue;
256                 return str1[i] - str2[i];
257         }
259         return 0;
262 #define prefixcmp(str1, str2) \
263         strncmp(str1, str2, STRING_SIZE(str2))
265 static inline int
266 suffixcmp(const char *str, int slen, const char *suffix)
268         size_t len = slen >= 0 ? slen : strlen(str);
269         size_t suffixlen = strlen(suffix);
271         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
275 static bool
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
278         int valuelen;
280         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281                 bool advance = cmd[valuelen] != 0;
283                 cmd[valuelen] = 0;
284                 argv[(*argc)++] = chomp_string(cmd);
285                 cmd += valuelen + advance;
286         }
288         if (*argc < SIZEOF_ARG)
289                 argv[*argc] = NULL;
290         return *argc < SIZEOF_ARG;
293 static void
294 argv_from_env(const char **argv, const char *name)
296         char *env = argv ? getenv(name) : NULL;
297         int argc = 0;
299         if (env && *env)
300                 env = strdup(env);
301         if (env && !argv_from_string(argv, &argc, env))
302                 die("Too many arguments in the `%s` environment variable", name);
306 /*
307  * Executing external commands.
308  */
310 enum io_type {
311         IO_FD,                  /* File descriptor based IO. */
312         IO_BG,                  /* Execute command in the background. */
313         IO_FG,                  /* Execute command with same std{in,out,err}. */
314         IO_RD,                  /* Read only fork+exec IO. */
315         IO_WR,                  /* Write only fork+exec IO. */
316         IO_AP,                  /* Append fork+exec output to file. */
317 };
319 struct io {
320         enum io_type type;      /* The requested type of pipe. */
321         const char *dir;        /* Directory from which to execute. */
322         pid_t pid;              /* Pipe for reading or writing. */
323         int pipe;               /* Pipe end for reading or writing. */
324         int error;              /* Error status. */
325         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
326         char *buf;              /* Read buffer. */
327         size_t bufalloc;        /* Allocated buffer size. */
328         size_t bufsize;         /* Buffer content size. */
329         char *bufpos;           /* Current buffer position. */
330         unsigned int eof:1;     /* Has end of file been reached. */
331 };
333 static void
334 reset_io(struct io *io)
336         io->pipe = -1;
337         io->pid = 0;
338         io->buf = io->bufpos = NULL;
339         io->bufalloc = io->bufsize = 0;
340         io->error = 0;
341         io->eof = 0;
344 static void
345 init_io(struct io *io, const char *dir, enum io_type type)
347         reset_io(io);
348         io->type = type;
349         io->dir = dir;
352 static bool
353 init_io_rd(struct io *io, const char *argv[], const char *dir,
354                 enum format_flags flags)
356         init_io(io, dir, IO_RD);
357         return format_argv(io->argv, argv, flags);
360 static bool
361 io_open(struct io *io, const char *name)
363         init_io(io, NULL, IO_FD);
364         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
365         return io->pipe != -1;
368 static bool
369 kill_io(struct io *io)
371         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
374 static bool
375 done_io(struct io *io)
377         pid_t pid = io->pid;
379         if (io->pipe != -1)
380                 close(io->pipe);
381         free(io->buf);
382         reset_io(io);
384         while (pid > 0) {
385                 int status;
386                 pid_t waiting = waitpid(pid, &status, 0);
388                 if (waiting < 0) {
389                         if (errno == EINTR)
390                                 continue;
391                         report("waitpid failed (%s)", strerror(errno));
392                         return FALSE;
393                 }
395                 return waiting == pid &&
396                        !WIFSIGNALED(status) &&
397                        WIFEXITED(status) &&
398                        !WEXITSTATUS(status);
399         }
401         return TRUE;
404 static bool
405 start_io(struct io *io)
407         int pipefds[2] = { -1, -1 };
409         if (io->type == IO_FD)
410                 return TRUE;
412         if ((io->type == IO_RD || io->type == IO_WR) &&
413             pipe(pipefds) < 0)
414                 return FALSE;
415         else if (io->type == IO_AP)
416                 pipefds[1] = io->pipe;
418         if ((io->pid = fork())) {
419                 if (pipefds[!(io->type == IO_WR)] != -1)
420                         close(pipefds[!(io->type == IO_WR)]);
421                 if (io->pid != -1) {
422                         io->pipe = pipefds[!!(io->type == IO_WR)];
423                         return TRUE;
424                 }
426         } else {
427                 if (io->type != IO_FG) {
428                         int devnull = open("/dev/null", O_RDWR);
429                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
430                         int writefd = (io->type == IO_RD || io->type == IO_AP)
431                                                         ? pipefds[1] : devnull;
433                         dup2(readfd,  STDIN_FILENO);
434                         dup2(writefd, STDOUT_FILENO);
435                         dup2(devnull, STDERR_FILENO);
437                         close(devnull);
438                         if (pipefds[0] != -1)
439                                 close(pipefds[0]);
440                         if (pipefds[1] != -1)
441                                 close(pipefds[1]);
442                 }
444                 if (io->dir && *io->dir && chdir(io->dir) == -1)
445                         die("Failed to change directory: %s", strerror(errno));
447                 execvp(io->argv[0], (char *const*) io->argv);
448                 die("Failed to execute program: %s", strerror(errno));
449         }
451         if (pipefds[!!(io->type == IO_WR)] != -1)
452                 close(pipefds[!!(io->type == IO_WR)]);
453         return FALSE;
456 static bool
457 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
459         init_io(io, dir, type);
460         if (!format_argv(io->argv, argv, FORMAT_NONE))
461                 return FALSE;
462         return start_io(io);
465 static int
466 run_io_do(struct io *io)
468         return start_io(io) && done_io(io);
471 static int
472 run_io_bg(const char **argv)
474         struct io io = {};
476         init_io(&io, NULL, IO_BG);
477         if (!format_argv(io.argv, argv, FORMAT_NONE))
478                 return FALSE;
479         return run_io_do(&io);
482 static bool
483 run_io_fg(const char **argv, const char *dir)
485         struct io io = {};
487         init_io(&io, dir, IO_FG);
488         if (!format_argv(io.argv, argv, FORMAT_NONE))
489                 return FALSE;
490         return run_io_do(&io);
493 static bool
494 run_io_append(const char **argv, enum format_flags flags, int fd)
496         struct io io = {};
498         init_io(&io, NULL, IO_AP);
499         io.pipe = fd;
500         if (format_argv(io.argv, argv, flags))
501                 return run_io_do(&io);
502         close(fd);
503         return FALSE;
506 static bool
507 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
509         return init_io_rd(io, argv, NULL, flags) && start_io(io);
512 static bool
513 io_eof(struct io *io)
515         return io->eof;
518 static int
519 io_error(struct io *io)
521         return io->error;
524 static bool
525 io_strerror(struct io *io)
527         return strerror(io->error);
530 static bool
531 io_can_read(struct io *io)
533         struct timeval tv = { 0, 500 };
534         fd_set fds;
536         FD_ZERO(&fds);
537         FD_SET(io->pipe, &fds);
539         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
542 static ssize_t
543 io_read(struct io *io, void *buf, size_t bufsize)
545         do {
546                 ssize_t readsize = read(io->pipe, buf, bufsize);
548                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
549                         continue;
550                 else if (readsize == -1)
551                         io->error = errno;
552                 else if (readsize == 0)
553                         io->eof = 1;
554                 return readsize;
555         } while (1);
558 static char *
559 io_get(struct io *io, int c, bool can_read)
561         char *eol;
562         ssize_t readsize;
564         if (!io->buf) {
565                 io->buf = io->bufpos = malloc(BUFSIZ);
566                 if (!io->buf)
567                         return NULL;
568                 io->bufalloc = BUFSIZ;
569                 io->bufsize = 0;
570         }
572         while (TRUE) {
573                 if (io->bufsize > 0) {
574                         eol = memchr(io->bufpos, c, io->bufsize);
575                         if (eol) {
576                                 char *line = io->bufpos;
578                                 *eol = 0;
579                                 io->bufpos = eol + 1;
580                                 io->bufsize -= io->bufpos - line;
581                                 return line;
582                         }
583                 }
585                 if (io_eof(io)) {
586                         if (io->bufsize) {
587                                 io->bufpos[io->bufsize] = 0;
588                                 io->bufsize = 0;
589                                 return io->bufpos;
590                         }
591                         return NULL;
592                 }
594                 if (!can_read)
595                         return NULL;
597                 if (io->bufsize > 0 && io->bufpos > io->buf)
598                         memmove(io->buf, io->bufpos, io->bufsize);
600                 io->bufpos = io->buf;
601                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
602                 if (io_error(io))
603                         return NULL;
604                 io->bufsize += readsize;
605         }
608 static bool
609 io_write(struct io *io, const void *buf, size_t bufsize)
611         size_t written = 0;
613         while (!io_error(io) && written < bufsize) {
614                 ssize_t size;
616                 size = write(io->pipe, buf + written, bufsize - written);
617                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
618                         continue;
619                 else if (size == -1)
620                         io->error = errno;
621                 else
622                         written += size;
623         }
625         return written == bufsize;
628 static bool
629 run_io_buf(const char **argv, char buf[], size_t bufsize)
631         struct io io = {};
632         bool error;
634         if (!run_io_rd(&io, argv, FORMAT_NONE))
635                 return FALSE;
637         io.buf = io.bufpos = buf;
638         io.bufalloc = bufsize;
639         error = !io_get(&io, '\n', TRUE) && io_error(&io);
640         io.buf = NULL;
642         return done_io(&io) || error;
645 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
647 /*
648  * User requests
649  */
651 #define REQ_INFO \
652         /* XXX: Keep the view request first and in sync with views[]. */ \
653         REQ_GROUP("View switching") \
654         REQ_(VIEW_MAIN,         "Show main view"), \
655         REQ_(VIEW_DIFF,         "Show diff view"), \
656         REQ_(VIEW_LOG,          "Show log view"), \
657         REQ_(VIEW_TREE,         "Show tree view"), \
658         REQ_(VIEW_BLOB,         "Show blob view"), \
659         REQ_(VIEW_BLAME,        "Show blame view"), \
660         REQ_(VIEW_HELP,         "Show help page"), \
661         REQ_(VIEW_PAGER,        "Show pager view"), \
662         REQ_(VIEW_STATUS,       "Show status view"), \
663         REQ_(VIEW_STAGE,        "Show stage view"), \
664         \
665         REQ_GROUP("View manipulation") \
666         REQ_(ENTER,             "Enter current line and scroll"), \
667         REQ_(NEXT,              "Move to next"), \
668         REQ_(PREVIOUS,          "Move to previous"), \
669         REQ_(PARENT,            "Move to parent"), \
670         REQ_(VIEW_NEXT,         "Move focus to next view"), \
671         REQ_(REFRESH,           "Reload and refresh"), \
672         REQ_(MAXIMIZE,          "Maximize the current view"), \
673         REQ_(VIEW_CLOSE,        "Close the current view"), \
674         REQ_(QUIT,              "Close all views and quit"), \
675         \
676         REQ_GROUP("View specific requests") \
677         REQ_(STATUS_UPDATE,     "Update file status"), \
678         REQ_(STATUS_REVERT,     "Revert file changes"), \
679         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
680         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
681         \
682         REQ_GROUP("Cursor navigation") \
683         REQ_(MOVE_UP,           "Move cursor one line up"), \
684         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
685         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
686         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
687         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
688         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
689         \
690         REQ_GROUP("Scrolling") \
691         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
692         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
693         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
694         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
695         \
696         REQ_GROUP("Searching") \
697         REQ_(SEARCH,            "Search the view"), \
698         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
699         REQ_(FIND_NEXT,         "Find next search match"), \
700         REQ_(FIND_PREV,         "Find previous search match"), \
701         \
702         REQ_GROUP("Option manipulation") \
703         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
704         REQ_(TOGGLE_DATE,       "Toggle date display"), \
705         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
706         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
707         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
708         \
709         REQ_GROUP("Misc") \
710         REQ_(PROMPT,            "Bring up the prompt"), \
711         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
712         REQ_(SHOW_VERSION,      "Show version information"), \
713         REQ_(STOP_LOADING,      "Stop all loading views"), \
714         REQ_(EDIT,              "Open in editor"), \
715         REQ_(NONE,              "Do nothing")
718 /* User action requests. */
719 enum request {
720 #define REQ_GROUP(help)
721 #define REQ_(req, help) REQ_##req
723         /* Offset all requests to avoid conflicts with ncurses getch values. */
724         REQ_OFFSET = KEY_MAX + 1,
725         REQ_INFO
727 #undef  REQ_GROUP
728 #undef  REQ_
729 };
731 struct request_info {
732         enum request request;
733         const char *name;
734         int namelen;
735         const char *help;
736 };
738 static struct request_info req_info[] = {
739 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
740 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
741         REQ_INFO
742 #undef  REQ_GROUP
743 #undef  REQ_
744 };
746 static enum request
747 get_request(const char *name)
749         int namelen = strlen(name);
750         int i;
752         for (i = 0; i < ARRAY_SIZE(req_info); i++)
753                 if (req_info[i].namelen == namelen &&
754                     !string_enum_compare(req_info[i].name, name, namelen))
755                         return req_info[i].request;
757         return REQ_NONE;
761 /*
762  * Options
763  */
765 static const char usage[] =
766 "tig " TIG_VERSION " (" __DATE__ ")\n"
767 "\n"
768 "Usage: tig        [options] [revs] [--] [paths]\n"
769 "   or: tig show   [options] [revs] [--] [paths]\n"
770 "   or: tig blame  [rev] path\n"
771 "   or: tig status\n"
772 "   or: tig <      [git command output]\n"
773 "\n"
774 "Options:\n"
775 "  -v, --version   Show version and exit\n"
776 "  -h, --help      Show help message and exit";
778 /* Option and state variables. */
779 static bool opt_date                    = TRUE;
780 static bool opt_author                  = TRUE;
781 static bool opt_line_number             = FALSE;
782 static bool opt_line_graphics           = TRUE;
783 static bool opt_rev_graph               = FALSE;
784 static bool opt_show_refs               = TRUE;
785 static int opt_num_interval             = NUMBER_INTERVAL;
786 static int opt_tab_size                 = TAB_SIZE;
787 static int opt_author_cols              = AUTHOR_COLS-1;
788 static char opt_path[SIZEOF_STR]        = "";
789 static char opt_file[SIZEOF_STR]        = "";
790 static char opt_ref[SIZEOF_REF]         = "";
791 static char opt_head[SIZEOF_REF]        = "";
792 static char opt_head_rev[SIZEOF_REV]    = "";
793 static char opt_remote[SIZEOF_REF]      = "";
794 static char opt_encoding[20]            = "UTF-8";
795 static bool opt_utf8                    = TRUE;
796 static char opt_codeset[20]             = "UTF-8";
797 static iconv_t opt_iconv                = ICONV_NONE;
798 static char opt_search[SIZEOF_STR]      = "";
799 static char opt_cdup[SIZEOF_STR]        = "";
800 static char opt_prefix[SIZEOF_STR]      = "";
801 static char opt_git_dir[SIZEOF_STR]     = "";
802 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
803 static char opt_editor[SIZEOF_STR]      = "";
804 static FILE *opt_tty                    = NULL;
806 #define is_initial_commit()     (!*opt_head_rev)
807 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
809 static enum request
810 parse_options(int argc, const char *argv[], const char ***run_argv)
812         enum request request = REQ_VIEW_MAIN;
813         const char *subcommand;
814         bool seen_dashdash = FALSE;
815         /* XXX: This is vulnerable to the user overriding options
816          * required for the main view parser. */
817         static const char *custom_argv[SIZEOF_ARG] = {
818                 "git", "log", "--no-color", "--pretty=raw", "--parents",
819                         "--topo-order", NULL
820         };
821         int i, j = 6;
823         if (!isatty(STDIN_FILENO))
824                 return REQ_VIEW_PAGER;
826         if (argc <= 1)
827                 return REQ_VIEW_MAIN;
829         subcommand = argv[1];
830         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
831                 if (!strcmp(subcommand, "-S"))
832                         warn("`-S' has been deprecated; use `tig status' instead");
833                 if (argc > 2)
834                         warn("ignoring arguments after `%s'", subcommand);
835                 return REQ_VIEW_STATUS;
837         } else if (!strcmp(subcommand, "blame")) {
838                 if (argc <= 2 || argc > 4)
839                         die("invalid number of options to blame\n\n%s", usage);
841                 i = 2;
842                 if (argc == 4) {
843                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
844                         i++;
845                 }
847                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
848                 return REQ_VIEW_BLAME;
850         } else if (!strcmp(subcommand, "show")) {
851                 request = REQ_VIEW_DIFF;
853         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
854                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
855                 warn("`tig %s' has been deprecated", subcommand);
857         } else {
858                 subcommand = NULL;
859         }
861         if (subcommand) {
862                 custom_argv[1] = subcommand;
863                 j = 2;
864         }
866         for (i = 1 + !!subcommand; i < argc; i++) {
867                 const char *opt = argv[i];
869                 if (seen_dashdash || !strcmp(opt, "--")) {
870                         seen_dashdash = TRUE;
872                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
873                         printf("tig version %s\n", TIG_VERSION);
874                         return REQ_NONE;
876                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
877                         printf("%s\n", usage);
878                         return REQ_NONE;
879                 }
881                 custom_argv[j++] = opt;
882                 if (j >= ARRAY_SIZE(custom_argv))
883                         die("command too long");
884         }
886         custom_argv[j] = NULL;
887         *run_argv = custom_argv;
889         return request;
893 /*
894  * Line-oriented content detection.
895  */
897 #define LINE_INFO \
898 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
899 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
900 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
901 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
902 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
903 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
909 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
910 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
911 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
912 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
913 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
914 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
915 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
916 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
917 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
918 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
919 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
920 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
921 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
922 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
927 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
928 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
929 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
930 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
932 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
933 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
934 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
935 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
936 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
937 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
938 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
939 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
940 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
941 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
942 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
944 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
945 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
946 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
947 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
948 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
949 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
950 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
951 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
952 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
955 enum line_type {
956 #define LINE(type, line, fg, bg, attr) \
957         LINE_##type
958         LINE_INFO,
959         LINE_NONE
960 #undef  LINE
961 };
963 struct line_info {
964         const char *name;       /* Option name. */
965         int namelen;            /* Size of option name. */
966         const char *line;       /* The start of line to match. */
967         int linelen;            /* Size of string to match. */
968         int fg, bg, attr;       /* Color and text attributes for the lines. */
969 };
971 static struct line_info line_info[] = {
972 #define LINE(type, line, fg, bg, attr) \
973         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
974         LINE_INFO
975 #undef  LINE
976 };
978 static enum line_type
979 get_line_type(const char *line)
981         int linelen = strlen(line);
982         enum line_type type;
984         for (type = 0; type < ARRAY_SIZE(line_info); type++)
985                 /* Case insensitive search matches Signed-off-by lines better. */
986                 if (linelen >= line_info[type].linelen &&
987                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
988                         return type;
990         return LINE_DEFAULT;
993 static inline int
994 get_line_attr(enum line_type type)
996         assert(type < ARRAY_SIZE(line_info));
997         return COLOR_PAIR(type) | line_info[type].attr;
1000 static struct line_info *
1001 get_line_info(const char *name)
1003         size_t namelen = strlen(name);
1004         enum line_type type;
1006         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1007                 if (namelen == line_info[type].namelen &&
1008                     !string_enum_compare(line_info[type].name, name, namelen))
1009                         return &line_info[type];
1011         return NULL;
1014 static void
1015 init_colors(void)
1017         int default_bg = line_info[LINE_DEFAULT].bg;
1018         int default_fg = line_info[LINE_DEFAULT].fg;
1019         enum line_type type;
1021         start_color();
1023         if (assume_default_colors(default_fg, default_bg) == ERR) {
1024                 default_bg = COLOR_BLACK;
1025                 default_fg = COLOR_WHITE;
1026         }
1028         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1029                 struct line_info *info = &line_info[type];
1030                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1031                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1033                 init_pair(type, fg, bg);
1034         }
1037 struct line {
1038         enum line_type type;
1040         /* State flags */
1041         unsigned int selected:1;
1042         unsigned int dirty:1;
1043         unsigned int cleareol:1;
1045         void *data;             /* User data */
1046 };
1049 /*
1050  * Keys
1051  */
1053 struct keybinding {
1054         int alias;
1055         enum request request;
1056 };
1058 static struct keybinding default_keybindings[] = {
1059         /* View switching */
1060         { 'm',          REQ_VIEW_MAIN },
1061         { 'd',          REQ_VIEW_DIFF },
1062         { 'l',          REQ_VIEW_LOG },
1063         { 't',          REQ_VIEW_TREE },
1064         { 'f',          REQ_VIEW_BLOB },
1065         { 'B',          REQ_VIEW_BLAME },
1066         { 'p',          REQ_VIEW_PAGER },
1067         { 'h',          REQ_VIEW_HELP },
1068         { 'S',          REQ_VIEW_STATUS },
1069         { 'c',          REQ_VIEW_STAGE },
1071         /* View manipulation */
1072         { 'q',          REQ_VIEW_CLOSE },
1073         { KEY_TAB,      REQ_VIEW_NEXT },
1074         { KEY_RETURN,   REQ_ENTER },
1075         { KEY_UP,       REQ_PREVIOUS },
1076         { KEY_DOWN,     REQ_NEXT },
1077         { 'R',          REQ_REFRESH },
1078         { KEY_F(5),     REQ_REFRESH },
1079         { 'O',          REQ_MAXIMIZE },
1081         /* Cursor navigation */
1082         { 'k',          REQ_MOVE_UP },
1083         { 'j',          REQ_MOVE_DOWN },
1084         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1085         { KEY_END,      REQ_MOVE_LAST_LINE },
1086         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1087         { ' ',          REQ_MOVE_PAGE_DOWN },
1088         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1089         { 'b',          REQ_MOVE_PAGE_UP },
1090         { '-',          REQ_MOVE_PAGE_UP },
1092         /* Scrolling */
1093         { KEY_IC,       REQ_SCROLL_LINE_UP },
1094         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1095         { 'w',          REQ_SCROLL_PAGE_UP },
1096         { 's',          REQ_SCROLL_PAGE_DOWN },
1098         /* Searching */
1099         { '/',          REQ_SEARCH },
1100         { '?',          REQ_SEARCH_BACK },
1101         { 'n',          REQ_FIND_NEXT },
1102         { 'N',          REQ_FIND_PREV },
1104         /* Misc */
1105         { 'Q',          REQ_QUIT },
1106         { 'z',          REQ_STOP_LOADING },
1107         { 'v',          REQ_SHOW_VERSION },
1108         { 'r',          REQ_SCREEN_REDRAW },
1109         { '.',          REQ_TOGGLE_LINENO },
1110         { 'D',          REQ_TOGGLE_DATE },
1111         { 'A',          REQ_TOGGLE_AUTHOR },
1112         { 'g',          REQ_TOGGLE_REV_GRAPH },
1113         { 'F',          REQ_TOGGLE_REFS },
1114         { ':',          REQ_PROMPT },
1115         { 'u',          REQ_STATUS_UPDATE },
1116         { '!',          REQ_STATUS_REVERT },
1117         { 'M',          REQ_STATUS_MERGE },
1118         { '@',          REQ_STAGE_NEXT },
1119         { ',',          REQ_PARENT },
1120         { 'e',          REQ_EDIT },
1121 };
1123 #define KEYMAP_INFO \
1124         KEYMAP_(GENERIC), \
1125         KEYMAP_(MAIN), \
1126         KEYMAP_(DIFF), \
1127         KEYMAP_(LOG), \
1128         KEYMAP_(TREE), \
1129         KEYMAP_(BLOB), \
1130         KEYMAP_(BLAME), \
1131         KEYMAP_(PAGER), \
1132         KEYMAP_(HELP), \
1133         KEYMAP_(STATUS), \
1134         KEYMAP_(STAGE)
1136 enum keymap {
1137 #define KEYMAP_(name) KEYMAP_##name
1138         KEYMAP_INFO
1139 #undef  KEYMAP_
1140 };
1142 static struct int_map keymap_table[] = {
1143 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1144         KEYMAP_INFO
1145 #undef  KEYMAP_
1146 };
1148 #define set_keymap(map, name) \
1149         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1151 struct keybinding_table {
1152         struct keybinding *data;
1153         size_t size;
1154 };
1156 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1158 static void
1159 add_keybinding(enum keymap keymap, enum request request, int key)
1161         struct keybinding_table *table = &keybindings[keymap];
1163         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1164         if (!table->data)
1165                 die("Failed to allocate keybinding");
1166         table->data[table->size].alias = key;
1167         table->data[table->size++].request = request;
1170 /* Looks for a key binding first in the given map, then in the generic map, and
1171  * lastly in the default keybindings. */
1172 static enum request
1173 get_keybinding(enum keymap keymap, int key)
1175         size_t i;
1177         for (i = 0; i < keybindings[keymap].size; i++)
1178                 if (keybindings[keymap].data[i].alias == key)
1179                         return keybindings[keymap].data[i].request;
1181         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1182                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1183                         return keybindings[KEYMAP_GENERIC].data[i].request;
1185         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1186                 if (default_keybindings[i].alias == key)
1187                         return default_keybindings[i].request;
1189         return (enum request) key;
1193 struct key {
1194         const char *name;
1195         int value;
1196 };
1198 static struct key key_table[] = {
1199         { "Enter",      KEY_RETURN },
1200         { "Space",      ' ' },
1201         { "Backspace",  KEY_BACKSPACE },
1202         { "Tab",        KEY_TAB },
1203         { "Escape",     KEY_ESC },
1204         { "Left",       KEY_LEFT },
1205         { "Right",      KEY_RIGHT },
1206         { "Up",         KEY_UP },
1207         { "Down",       KEY_DOWN },
1208         { "Insert",     KEY_IC },
1209         { "Delete",     KEY_DC },
1210         { "Hash",       '#' },
1211         { "Home",       KEY_HOME },
1212         { "End",        KEY_END },
1213         { "PageUp",     KEY_PPAGE },
1214         { "PageDown",   KEY_NPAGE },
1215         { "F1",         KEY_F(1) },
1216         { "F2",         KEY_F(2) },
1217         { "F3",         KEY_F(3) },
1218         { "F4",         KEY_F(4) },
1219         { "F5",         KEY_F(5) },
1220         { "F6",         KEY_F(6) },
1221         { "F7",         KEY_F(7) },
1222         { "F8",         KEY_F(8) },
1223         { "F9",         KEY_F(9) },
1224         { "F10",        KEY_F(10) },
1225         { "F11",        KEY_F(11) },
1226         { "F12",        KEY_F(12) },
1227 };
1229 static int
1230 get_key_value(const char *name)
1232         int i;
1234         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1235                 if (!strcasecmp(key_table[i].name, name))
1236                         return key_table[i].value;
1238         if (strlen(name) == 1 && isprint(*name))
1239                 return (int) *name;
1241         return ERR;
1244 static const char *
1245 get_key_name(int key_value)
1247         static char key_char[] = "'X'";
1248         const char *seq = NULL;
1249         int key;
1251         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1252                 if (key_table[key].value == key_value)
1253                         seq = key_table[key].name;
1255         if (seq == NULL &&
1256             key_value < 127 &&
1257             isprint(key_value)) {
1258                 key_char[1] = (char) key_value;
1259                 seq = key_char;
1260         }
1262         return seq ? seq : "(no key)";
1265 static const char *
1266 get_key(enum request request)
1268         static char buf[BUFSIZ];
1269         size_t pos = 0;
1270         char *sep = "";
1271         int i;
1273         buf[pos] = 0;
1275         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1276                 struct keybinding *keybinding = &default_keybindings[i];
1278                 if (keybinding->request != request)
1279                         continue;
1281                 if (!string_format_from(buf, &pos, "%s%s", sep,
1282                                         get_key_name(keybinding->alias)))
1283                         return "Too many keybindings!";
1284                 sep = ", ";
1285         }
1287         return buf;
1290 struct run_request {
1291         enum keymap keymap;
1292         int key;
1293         const char *argv[SIZEOF_ARG];
1294 };
1296 static struct run_request *run_request;
1297 static size_t run_requests;
1299 static enum request
1300 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1302         struct run_request *req;
1304         if (argc >= ARRAY_SIZE(req->argv) - 1)
1305                 return REQ_NONE;
1307         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1308         if (!req)
1309                 return REQ_NONE;
1311         run_request = req;
1312         req = &run_request[run_requests];
1313         req->keymap = keymap;
1314         req->key = key;
1315         req->argv[0] = NULL;
1317         if (!format_argv(req->argv, argv, FORMAT_NONE))
1318                 return REQ_NONE;
1320         return REQ_NONE + ++run_requests;
1323 static struct run_request *
1324 get_run_request(enum request request)
1326         if (request <= REQ_NONE)
1327                 return NULL;
1328         return &run_request[request - REQ_NONE - 1];
1331 static void
1332 add_builtin_run_requests(void)
1334         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1335         const char *gc[] = { "git", "gc", NULL };
1336         struct {
1337                 enum keymap keymap;
1338                 int key;
1339                 int argc;
1340                 const char **argv;
1341         } reqs[] = {
1342                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1343                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1344         };
1345         int i;
1347         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1348                 enum request req;
1350                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1351                 if (req != REQ_NONE)
1352                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1353         }
1356 /*
1357  * User config file handling.
1358  */
1360 static struct int_map color_map[] = {
1361 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1362         COLOR_MAP(DEFAULT),
1363         COLOR_MAP(BLACK),
1364         COLOR_MAP(BLUE),
1365         COLOR_MAP(CYAN),
1366         COLOR_MAP(GREEN),
1367         COLOR_MAP(MAGENTA),
1368         COLOR_MAP(RED),
1369         COLOR_MAP(WHITE),
1370         COLOR_MAP(YELLOW),
1371 };
1373 #define set_color(color, name) \
1374         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1376 static struct int_map attr_map[] = {
1377 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1378         ATTR_MAP(NORMAL),
1379         ATTR_MAP(BLINK),
1380         ATTR_MAP(BOLD),
1381         ATTR_MAP(DIM),
1382         ATTR_MAP(REVERSE),
1383         ATTR_MAP(STANDOUT),
1384         ATTR_MAP(UNDERLINE),
1385 };
1387 #define set_attribute(attr, name) \
1388         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1390 static int   config_lineno;
1391 static bool  config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attr] */
1395 static int
1396 option_color_command(int argc, const char *argv[])
1398         struct line_info *info;
1400         if (argc != 3 && argc != 4) {
1401                 config_msg = "Wrong number of arguments given to color command";
1402                 return ERR;
1403         }
1405         info = get_line_info(argv[0]);
1406         if (!info) {
1407                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408                         info = get_line_info("delimiter");
1410                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411                         info = get_line_info("date");
1413                 } else {
1414                         config_msg = "Unknown color name";
1415                         return ERR;
1416                 }
1417         }
1419         if (set_color(&info->fg, argv[1]) == ERR ||
1420             set_color(&info->bg, argv[2]) == ERR) {
1421                 config_msg = "Unknown color";
1422                 return ERR;
1423         }
1425         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1426                 config_msg = "Unknown attribute";
1427                 return ERR;
1428         }
1430         return OK;
1433 static bool parse_bool(const char *s)
1435         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1436                 !strcmp(s, "yes")) ? TRUE : FALSE;
1439 static int
1440 parse_int(const char *s, int default_value, int min, int max)
1442         int value = atoi(s);
1444         return (value < min || value > max) ? default_value : value;
1447 /* Wants: name = value */
1448 static int
1449 option_set_command(int argc, const char *argv[])
1451         if (argc != 3) {
1452                 config_msg = "Wrong number of arguments given to set command";
1453                 return ERR;
1454         }
1456         if (strcmp(argv[1], "=")) {
1457                 config_msg = "No value assigned";
1458                 return ERR;
1459         }
1461         if (!strcmp(argv[0], "show-author")) {
1462                 opt_author = parse_bool(argv[2]);
1463                 return OK;
1464         }
1466         if (!strcmp(argv[0], "show-date")) {
1467                 opt_date = parse_bool(argv[2]);
1468                 return OK;
1469         }
1471         if (!strcmp(argv[0], "show-rev-graph")) {
1472                 opt_rev_graph = parse_bool(argv[2]);
1473                 return OK;
1474         }
1476         if (!strcmp(argv[0], "show-refs")) {
1477                 opt_show_refs = parse_bool(argv[2]);
1478                 return OK;
1479         }
1481         if (!strcmp(argv[0], "show-line-numbers")) {
1482                 opt_line_number = parse_bool(argv[2]);
1483                 return OK;
1484         }
1486         if (!strcmp(argv[0], "line-graphics")) {
1487                 opt_line_graphics = parse_bool(argv[2]);
1488                 return OK;
1489         }
1491         if (!strcmp(argv[0], "line-number-interval")) {
1492                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1493                 return OK;
1494         }
1496         if (!strcmp(argv[0], "author-width")) {
1497                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1498                 return OK;
1499         }
1501         if (!strcmp(argv[0], "tab-size")) {
1502                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1503                 return OK;
1504         }
1506         if (!strcmp(argv[0], "commit-encoding")) {
1507                 const char *arg = argv[2];
1508                 int arglen = strlen(arg);
1510                 switch (arg[0]) {
1511                 case '"':
1512                 case '\'':
1513                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1514                                 config_msg = "Unmatched quotation";
1515                                 return ERR;
1516                         }
1517                         arg += 1; arglen -= 2;
1518                 default:
1519                         string_ncopy(opt_encoding, arg, strlen(arg));
1520                         return OK;
1521                 }
1522         }
1524         config_msg = "Unknown variable name";
1525         return ERR;
1528 /* Wants: mode request key */
1529 static int
1530 option_bind_command(int argc, const char *argv[])
1532         enum request request;
1533         int keymap;
1534         int key;
1536         if (argc < 3) {
1537                 config_msg = "Wrong number of arguments given to bind command";
1538                 return ERR;
1539         }
1541         if (set_keymap(&keymap, argv[0]) == ERR) {
1542                 config_msg = "Unknown key map";
1543                 return ERR;
1544         }
1546         key = get_key_value(argv[1]);
1547         if (key == ERR) {
1548                 config_msg = "Unknown key";
1549                 return ERR;
1550         }
1552         request = get_request(argv[2]);
1553         if (request == REQ_NONE) {
1554                 struct {
1555                         const char *name;
1556                         enum request request;
1557                 } obsolete[] = {
1558                         { "cherry-pick",        REQ_NONE },
1559                         { "screen-resize",      REQ_NONE },
1560                         { "tree-parent",        REQ_PARENT },
1561                 };
1562                 size_t namelen = strlen(argv[2]);
1563                 int i;
1565                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1566                         if (namelen != strlen(obsolete[i].name) ||
1567                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1568                                 continue;
1569                         if (obsolete[i].request != REQ_NONE)
1570                                 add_keybinding(keymap, obsolete[i].request, key);
1571                         config_msg = "Obsolete request name";
1572                         return ERR;
1573                 }
1574         }
1575         if (request == REQ_NONE && *argv[2]++ == '!')
1576                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1577         if (request == REQ_NONE) {
1578                 config_msg = "Unknown request name";
1579                 return ERR;
1580         }
1582         add_keybinding(keymap, request, key);
1584         return OK;
1587 static int
1588 set_option(const char *opt, char *value)
1590         const char *argv[SIZEOF_ARG];
1591         int argc = 0;
1593         if (!argv_from_string(argv, &argc, value)) {
1594                 config_msg = "Too many option arguments";
1595                 return ERR;
1596         }
1598         if (!strcmp(opt, "color"))
1599                 return option_color_command(argc, argv);
1601         if (!strcmp(opt, "set"))
1602                 return option_set_command(argc, argv);
1604         if (!strcmp(opt, "bind"))
1605                 return option_bind_command(argc, argv);
1607         config_msg = "Unknown option command";
1608         return ERR;
1611 static int
1612 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1614         int status = OK;
1616         config_lineno++;
1617         config_msg = "Internal error";
1619         /* Check for comment markers, since read_properties() will
1620          * only ensure opt and value are split at first " \t". */
1621         optlen = strcspn(opt, "#");
1622         if (optlen == 0)
1623                 return OK;
1625         if (opt[optlen] != 0) {
1626                 config_msg = "No option value";
1627                 status = ERR;
1629         }  else {
1630                 /* Look for comment endings in the value. */
1631                 size_t len = strcspn(value, "#");
1633                 if (len < valuelen) {
1634                         valuelen = len;
1635                         value[valuelen] = 0;
1636                 }
1638                 status = set_option(opt, value);
1639         }
1641         if (status == ERR) {
1642                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1643                         config_lineno, (int) optlen, opt, config_msg);
1644                 config_errors = TRUE;
1645         }
1647         /* Always keep going if errors are encountered. */
1648         return OK;
1651 static void
1652 load_option_file(const char *path)
1654         struct io io = {};
1656         /* It's ok that the file doesn't exist. */
1657         if (!io_open(&io, path))
1658                 return;
1660         config_lineno = 0;
1661         config_errors = FALSE;
1663         if (read_properties(&io, " \t", read_option) == ERR ||
1664             config_errors == TRUE)
1665                 fprintf(stderr, "Errors while loading %s.\n", path);
1668 static int
1669 load_options(void)
1671         const char *home = getenv("HOME");
1672         const char *tigrc_user = getenv("TIGRC_USER");
1673         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1674         char buf[SIZEOF_STR];
1676         add_builtin_run_requests();
1678         if (!tigrc_system) {
1679                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1680                         return ERR;
1681                 tigrc_system = buf;
1682         }
1683         load_option_file(tigrc_system);
1685         if (!tigrc_user) {
1686                 if (!home || !string_format(buf, "%s/.tigrc", home))
1687                         return ERR;
1688                 tigrc_user = buf;
1689         }
1690         load_option_file(tigrc_user);
1692         return OK;
1696 /*
1697  * The viewer
1698  */
1700 struct view;
1701 struct view_ops;
1703 /* The display array of active views and the index of the current view. */
1704 static struct view *display[2];
1705 static unsigned int current_view;
1707 /* Reading from the prompt? */
1708 static bool input_mode = FALSE;
1710 #define foreach_displayed_view(view, i) \
1711         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1713 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1715 /* Current head and commit ID */
1716 static char ref_blob[SIZEOF_REF]        = "";
1717 static char ref_commit[SIZEOF_REF]      = "HEAD";
1718 static char ref_head[SIZEOF_REF]        = "HEAD";
1720 struct view {
1721         const char *name;       /* View name */
1722         const char *cmd_env;    /* Command line set via environment */
1723         const char *id;         /* Points to either of ref_{head,commit,blob} */
1725         struct view_ops *ops;   /* View operations */
1727         enum keymap keymap;     /* What keymap does this view have */
1728         bool git_dir;           /* Whether the view requires a git directory. */
1730         char ref[SIZEOF_REF];   /* Hovered commit reference */
1731         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1733         int height, width;      /* The width and height of the main window */
1734         WINDOW *win;            /* The main window */
1735         WINDOW *title;          /* The title window living below the main window */
1737         /* Navigation */
1738         unsigned long offset;   /* Offset of the window top */
1739         unsigned long lineno;   /* Current line number */
1740         unsigned long p_offset; /* Previous offset of the window top */
1741         unsigned long p_lineno; /* Previous current line number */
1742         bool p_restore;         /* Should the previous position be restored. */
1744         /* Searching */
1745         char grep[SIZEOF_STR];  /* Search string */
1746         regex_t *regex;         /* Pre-compiled regex */
1748         /* If non-NULL, points to the view that opened this view. If this view
1749          * is closed tig will switch back to the parent view. */
1750         struct view *parent;
1752         /* Buffering */
1753         size_t lines;           /* Total number of lines */
1754         struct line *line;      /* Line index */
1755         size_t line_alloc;      /* Total number of allocated lines */
1756         unsigned int digits;    /* Number of digits in the lines member. */
1758         /* Drawing */
1759         struct line *curline;   /* Line currently being drawn. */
1760         enum line_type curtype; /* Attribute currently used for drawing. */
1761         unsigned long col;      /* Column when drawing. */
1763         /* Loading */
1764         struct io io;
1765         struct io *pipe;
1766         time_t start_time;
1767         time_t update_secs;
1768 };
1770 struct view_ops {
1771         /* What type of content being displayed. Used in the title bar. */
1772         const char *type;
1773         /* Default command arguments. */
1774         const char **argv;
1775         /* Open and reads in all view content. */
1776         bool (*open)(struct view *view);
1777         /* Read one line; updates view->line. */
1778         bool (*read)(struct view *view, char *data);
1779         /* Draw one line; @lineno must be < view->height. */
1780         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1781         /* Depending on view handle a special requests. */
1782         enum request (*request)(struct view *view, enum request request, struct line *line);
1783         /* Search for regex in a line. */
1784         bool (*grep)(struct view *view, struct line *line);
1785         /* Select line */
1786         void (*select)(struct view *view, struct line *line);
1787 };
1789 static struct view_ops blame_ops;
1790 static struct view_ops blob_ops;
1791 static struct view_ops diff_ops;
1792 static struct view_ops help_ops;
1793 static struct view_ops log_ops;
1794 static struct view_ops main_ops;
1795 static struct view_ops pager_ops;
1796 static struct view_ops stage_ops;
1797 static struct view_ops status_ops;
1798 static struct view_ops tree_ops;
1800 #define VIEW_STR(name, env, ref, ops, map, git) \
1801         { name, #env, ref, ops, map, git }
1803 #define VIEW_(id, name, ops, git, ref) \
1804         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1807 static struct view views[] = {
1808         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1809         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1810         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1811         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1812         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1813         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1814         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1815         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1816         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1817         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1818 };
1820 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1821 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1823 #define foreach_view(view, i) \
1824         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1826 #define view_is_displayed(view) \
1827         (view == display[0] || view == display[1])
1830 enum line_graphic {
1831         LINE_GRAPHIC_VLINE
1832 };
1834 static int line_graphics[] = {
1835         /* LINE_GRAPHIC_VLINE: */ '|'
1836 };
1838 static inline void
1839 set_view_attr(struct view *view, enum line_type type)
1841         if (!view->curline->selected && view->curtype != type) {
1842                 wattrset(view->win, get_line_attr(type));
1843                 wchgat(view->win, -1, 0, type, NULL);
1844                 view->curtype = type;
1845         }
1848 static int
1849 draw_chars(struct view *view, enum line_type type, const char *string,
1850            int max_len, bool use_tilde)
1852         int len = 0;
1853         int col = 0;
1854         int trimmed = FALSE;
1856         if (max_len <= 0)
1857                 return 0;
1859         if (opt_utf8) {
1860                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1861         } else {
1862                 col = len = strlen(string);
1863                 if (len > max_len) {
1864                         if (use_tilde) {
1865                                 max_len -= 1;
1866                         }
1867                         col = len = max_len;
1868                         trimmed = TRUE;
1869                 }
1870         }
1872         set_view_attr(view, type);
1873         waddnstr(view->win, string, len);
1874         if (trimmed && use_tilde) {
1875                 set_view_attr(view, LINE_DELIMITER);
1876                 waddch(view->win, '~');
1877                 col++;
1878         }
1880         return col;
1883 static int
1884 draw_space(struct view *view, enum line_type type, int max, int spaces)
1886         static char space[] = "                    ";
1887         int col = 0;
1889         spaces = MIN(max, spaces);
1891         while (spaces > 0) {
1892                 int len = MIN(spaces, sizeof(space) - 1);
1894                 col += draw_chars(view, type, space, spaces, FALSE);
1895                 spaces -= len;
1896         }
1898         return col;
1901 static bool
1902 draw_lineno(struct view *view, unsigned int lineno)
1904         char number[10];
1905         int digits3 = view->digits < 3 ? 3 : view->digits;
1906         int max_number = MIN(digits3, STRING_SIZE(number));
1907         int max = view->width - view->col;
1908         int col;
1910         if (max < max_number)
1911                 max_number = max;
1913         lineno += view->offset + 1;
1914         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1915                 static char fmt[] = "%1ld";
1917                 if (view->digits <= 9)
1918                         fmt[1] = '0' + digits3;
1920                 if (!string_format(number, fmt, lineno))
1921                         number[0] = 0;
1922                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1923         } else {
1924                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1925         }
1927         if (col < max) {
1928                 set_view_attr(view, LINE_DEFAULT);
1929                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1930                 col++;
1931         }
1933         if (col < max)
1934                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1935         view->col += col;
1937         return view->width - view->col <= 0;
1940 static bool
1941 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1943         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1944         return view->width - view->col <= 0;
1947 static bool
1948 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1950         int max = view->width - view->col;
1951         int i;
1953         if (max < size)
1954                 size = max;
1956         set_view_attr(view, type);
1957         /* Using waddch() instead of waddnstr() ensures that
1958          * they'll be rendered correctly for the cursor line. */
1959         for (i = 0; i < size; i++)
1960                 waddch(view->win, graphic[i]);
1962         view->col += size;
1963         if (size < max) {
1964                 waddch(view->win, ' ');
1965                 view->col++;
1966         }
1968         return view->width - view->col <= 0;
1971 static bool
1972 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1974         int max = MIN(view->width - view->col, len);
1975         int col;
1977         if (text)
1978                 col = draw_chars(view, type, text, max - 1, trim);
1979         else
1980                 col = draw_space(view, type, max - 1, max - 1);
1982         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1983         return view->width - view->col <= 0;
1986 static bool
1987 draw_date(struct view *view, struct tm *time)
1989         char buf[DATE_COLS];
1990         char *date;
1991         int timelen = 0;
1993         if (time)
1994                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1995         date = timelen ? buf : NULL;
1997         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2000 static bool
2001 draw_view_line(struct view *view, unsigned int lineno)
2003         struct line *line;
2004         bool selected = (view->offset + lineno == view->lineno);
2005         bool draw_ok;
2007         assert(view_is_displayed(view));
2009         if (view->offset + lineno >= view->lines)
2010                 return FALSE;
2012         line = &view->line[view->offset + lineno];
2014         wmove(view->win, lineno, 0);
2015         if (line->cleareol)
2016                 wclrtoeol(view->win);
2017         view->col = 0;
2018         view->curline = line;
2019         view->curtype = LINE_NONE;
2020         line->selected = FALSE;
2021         line->dirty = line->cleareol = 0;
2023         if (selected) {
2024                 set_view_attr(view, LINE_CURSOR);
2025                 line->selected = TRUE;
2026                 view->ops->select(view, line);
2027         }
2029         scrollok(view->win, FALSE);
2030         draw_ok = view->ops->draw(view, line, lineno);
2031         scrollok(view->win, TRUE);
2033         return draw_ok;
2036 static void
2037 redraw_view_dirty(struct view *view)
2039         bool dirty = FALSE;
2040         int lineno;
2042         for (lineno = 0; lineno < view->height; lineno++) {
2043                 if (view->offset + lineno >= view->lines)
2044                         break;
2045                 if (!view->line[view->offset + lineno].dirty)
2046                         continue;
2047                 dirty = TRUE;
2048                 if (!draw_view_line(view, lineno))
2049                         break;
2050         }
2052         if (!dirty)
2053                 return;
2054         redrawwin(view->win);
2055         if (input_mode)
2056                 wnoutrefresh(view->win);
2057         else
2058                 wrefresh(view->win);
2061 static void
2062 redraw_view_from(struct view *view, int lineno)
2064         assert(0 <= lineno && lineno < view->height);
2066         for (; lineno < view->height; lineno++) {
2067                 if (!draw_view_line(view, lineno))
2068                         break;
2069         }
2071         redrawwin(view->win);
2072         if (input_mode)
2073                 wnoutrefresh(view->win);
2074         else
2075                 wrefresh(view->win);
2078 static void
2079 redraw_view(struct view *view)
2081         werase(view->win);
2082         redraw_view_from(view, 0);
2086 static void
2087 update_view_title(struct view *view)
2089         char buf[SIZEOF_STR];
2090         char state[SIZEOF_STR];
2091         size_t bufpos = 0, statelen = 0;
2093         assert(view_is_displayed(view));
2095         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2096                 unsigned int view_lines = view->offset + view->height;
2097                 unsigned int lines = view->lines
2098                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2099                                    : 0;
2101                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2102                                    view->ops->type,
2103                                    view->lineno + 1,
2104                                    view->lines,
2105                                    lines);
2107         }
2109         if (view->pipe) {
2110                 time_t secs = time(NULL) - view->start_time;
2112                 /* Three git seconds are a long time ... */
2113                 if (secs > 2)
2114                         string_format_from(state, &statelen, " loading %lds", secs);
2115         }
2117         string_format_from(buf, &bufpos, "[%s]", view->name);
2118         if (*view->ref && bufpos < view->width) {
2119                 size_t refsize = strlen(view->ref);
2120                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2122                 if (minsize < view->width)
2123                         refsize = view->width - minsize + 7;
2124                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2125         }
2127         if (statelen && bufpos < view->width) {
2128                 string_format_from(buf, &bufpos, "%s", state);
2129         }
2131         if (view == display[current_view])
2132                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2133         else
2134                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2136         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2137         wclrtoeol(view->title);
2138         wmove(view->title, 0, view->width - 1);
2140         if (input_mode)
2141                 wnoutrefresh(view->title);
2142         else
2143                 wrefresh(view->title);
2146 static void
2147 resize_display(void)
2149         int offset, i;
2150         struct view *base = display[0];
2151         struct view *view = display[1] ? display[1] : display[0];
2153         /* Setup window dimensions */
2155         getmaxyx(stdscr, base->height, base->width);
2157         /* Make room for the status window. */
2158         base->height -= 1;
2160         if (view != base) {
2161                 /* Horizontal split. */
2162                 view->width   = base->width;
2163                 view->height  = SCALE_SPLIT_VIEW(base->height);
2164                 base->height -= view->height;
2166                 /* Make room for the title bar. */
2167                 view->height -= 1;
2168         }
2170         /* Make room for the title bar. */
2171         base->height -= 1;
2173         offset = 0;
2175         foreach_displayed_view (view, i) {
2176                 if (!view->win) {
2177                         view->win = newwin(view->height, 0, offset, 0);
2178                         if (!view->win)
2179                                 die("Failed to create %s view", view->name);
2181                         scrollok(view->win, TRUE);
2183                         view->title = newwin(1, 0, offset + view->height, 0);
2184                         if (!view->title)
2185                                 die("Failed to create title window");
2187                 } else {
2188                         wresize(view->win, view->height, view->width);
2189                         mvwin(view->win,   offset, 0);
2190                         mvwin(view->title, offset + view->height, 0);
2191                 }
2193                 offset += view->height + 1;
2194         }
2197 static void
2198 redraw_display(bool clear)
2200         struct view *view;
2201         int i;
2203         foreach_displayed_view (view, i) {
2204                 if (clear)
2205                         wclear(view->win);
2206                 redraw_view(view);
2207                 update_view_title(view);
2208         }
2211 static void
2212 update_display_cursor(struct view *view)
2214         /* Move the cursor to the right-most column of the cursor line.
2215          *
2216          * XXX: This could turn out to be a bit expensive, but it ensures that
2217          * the cursor does not jump around. */
2218         if (view->lines) {
2219                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2220                 wrefresh(view->win);
2221         }
2224 static void
2225 toggle_view_option(bool *option, const char *help)
2227         *option = !*option;
2228         redraw_display(FALSE);
2229         report("%sabling %s", *option ? "En" : "Dis", help);
2232 /*
2233  * Navigation
2234  */
2236 /* Scrolling backend */
2237 static void
2238 do_scroll_view(struct view *view, int lines)
2240         bool redraw_current_line = FALSE;
2242         /* The rendering expects the new offset. */
2243         view->offset += lines;
2245         assert(0 <= view->offset && view->offset < view->lines);
2246         assert(lines);
2248         /* Move current line into the view. */
2249         if (view->lineno < view->offset) {
2250                 view->lineno = view->offset;
2251                 redraw_current_line = TRUE;
2252         } else if (view->lineno >= view->offset + view->height) {
2253                 view->lineno = view->offset + view->height - 1;
2254                 redraw_current_line = TRUE;
2255         }
2257         assert(view->offset <= view->lineno && view->lineno < view->lines);
2259         /* Redraw the whole screen if scrolling is pointless. */
2260         if (view->height < ABS(lines)) {
2261                 redraw_view(view);
2263         } else {
2264                 int line = lines > 0 ? view->height - lines : 0;
2265                 int end = line + ABS(lines);
2267                 wscrl(view->win, lines);
2269                 for (; line < end; line++) {
2270                         if (!draw_view_line(view, line))
2271                                 break;
2272                 }
2274                 if (redraw_current_line)
2275                         draw_view_line(view, view->lineno - view->offset);
2276         }
2278         redrawwin(view->win);
2279         wrefresh(view->win);
2280         report("");
2283 /* Scroll frontend */
2284 static void
2285 scroll_view(struct view *view, enum request request)
2287         int lines = 1;
2289         assert(view_is_displayed(view));
2291         switch (request) {
2292         case REQ_SCROLL_PAGE_DOWN:
2293                 lines = view->height;
2294         case REQ_SCROLL_LINE_DOWN:
2295                 if (view->offset + lines > view->lines)
2296                         lines = view->lines - view->offset;
2298                 if (lines == 0 || view->offset + view->height >= view->lines) {
2299                         report("Cannot scroll beyond the last line");
2300                         return;
2301                 }
2302                 break;
2304         case REQ_SCROLL_PAGE_UP:
2305                 lines = view->height;
2306         case REQ_SCROLL_LINE_UP:
2307                 if (lines > view->offset)
2308                         lines = view->offset;
2310                 if (lines == 0) {
2311                         report("Cannot scroll beyond the first line");
2312                         return;
2313                 }
2315                 lines = -lines;
2316                 break;
2318         default:
2319                 die("request %d not handled in switch", request);
2320         }
2322         do_scroll_view(view, lines);
2325 /* Cursor moving */
2326 static void
2327 move_view(struct view *view, enum request request)
2329         int scroll_steps = 0;
2330         int steps;
2332         switch (request) {
2333         case REQ_MOVE_FIRST_LINE:
2334                 steps = -view->lineno;
2335                 break;
2337         case REQ_MOVE_LAST_LINE:
2338                 steps = view->lines - view->lineno - 1;
2339                 break;
2341         case REQ_MOVE_PAGE_UP:
2342                 steps = view->height > view->lineno
2343                       ? -view->lineno : -view->height;
2344                 break;
2346         case REQ_MOVE_PAGE_DOWN:
2347                 steps = view->lineno + view->height >= view->lines
2348                       ? view->lines - view->lineno - 1 : view->height;
2349                 break;
2351         case REQ_MOVE_UP:
2352                 steps = -1;
2353                 break;
2355         case REQ_MOVE_DOWN:
2356                 steps = 1;
2357                 break;
2359         default:
2360                 die("request %d not handled in switch", request);
2361         }
2363         if (steps <= 0 && view->lineno == 0) {
2364                 report("Cannot move beyond the first line");
2365                 return;
2367         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2368                 report("Cannot move beyond the last line");
2369                 return;
2370         }
2372         /* Move the current line */
2373         view->lineno += steps;
2374         assert(0 <= view->lineno && view->lineno < view->lines);
2376         /* Check whether the view needs to be scrolled */
2377         if (view->lineno < view->offset ||
2378             view->lineno >= view->offset + view->height) {
2379                 scroll_steps = steps;
2380                 if (steps < 0 && -steps > view->offset) {
2381                         scroll_steps = -view->offset;
2383                 } else if (steps > 0) {
2384                         if (view->lineno == view->lines - 1 &&
2385                             view->lines > view->height) {
2386                                 scroll_steps = view->lines - view->offset - 1;
2387                                 if (scroll_steps >= view->height)
2388                                         scroll_steps -= view->height - 1;
2389                         }
2390                 }
2391         }
2393         if (!view_is_displayed(view)) {
2394                 view->offset += scroll_steps;
2395                 assert(0 <= view->offset && view->offset < view->lines);
2396                 view->ops->select(view, &view->line[view->lineno]);
2397                 return;
2398         }
2400         /* Repaint the old "current" line if we be scrolling */
2401         if (ABS(steps) < view->height)
2402                 draw_view_line(view, view->lineno - steps - view->offset);
2404         if (scroll_steps) {
2405                 do_scroll_view(view, scroll_steps);
2406                 return;
2407         }
2409         /* Draw the current line */
2410         draw_view_line(view, view->lineno - view->offset);
2412         redrawwin(view->win);
2413         wrefresh(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                         redrawwin(view->win);
2441                         wrefresh(view->win);
2442                 } else {
2443                         view->ops->select(view, &view->line[view->lineno]);
2444                 }
2445         }
2448 static void
2449 find_next(struct view *view, enum request request)
2451         unsigned long lineno = view->lineno;
2452         int direction;
2454         if (!*view->grep) {
2455                 if (!*opt_search)
2456                         report("No previous search");
2457                 else
2458                         search_view(view, request);
2459                 return;
2460         }
2462         switch (request) {
2463         case REQ_SEARCH:
2464         case REQ_FIND_NEXT:
2465                 direction = 1;
2466                 break;
2468         case REQ_SEARCH_BACK:
2469         case REQ_FIND_PREV:
2470                 direction = -1;
2471                 break;
2473         default:
2474                 return;
2475         }
2477         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2478                 lineno += direction;
2480         /* Note, lineno is unsigned long so will wrap around in which case it
2481          * will become bigger than view->lines. */
2482         for (; lineno < view->lines; lineno += direction) {
2483                 if (view->ops->grep(view, &view->line[lineno])) {
2484                         select_view_line(view, lineno);
2485                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2486                         return;
2487                 }
2488         }
2490         report("No match found for '%s'", view->grep);
2493 static void
2494 search_view(struct view *view, enum request request)
2496         int regex_err;
2498         if (view->regex) {
2499                 regfree(view->regex);
2500                 *view->grep = 0;
2501         } else {
2502                 view->regex = calloc(1, sizeof(*view->regex));
2503                 if (!view->regex)
2504                         return;
2505         }
2507         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2508         if (regex_err != 0) {
2509                 char buf[SIZEOF_STR] = "unknown error";
2511                 regerror(regex_err, view->regex, buf, sizeof(buf));
2512                 report("Search failed: %s", buf);
2513                 return;
2514         }
2516         string_copy(view->grep, opt_search);
2518         find_next(view, request);
2521 /*
2522  * Incremental updating
2523  */
2525 static void
2526 reset_view(struct view *view)
2528         int i;
2530         for (i = 0; i < view->lines; i++)
2531                 free(view->line[i].data);
2532         free(view->line);
2534         view->p_offset = view->offset;
2535         view->p_lineno = view->lineno;
2537         view->line = NULL;
2538         view->offset = 0;
2539         view->lines  = 0;
2540         view->lineno = 0;
2541         view->line_alloc = 0;
2542         view->vid[0] = 0;
2543         view->update_secs = 0;
2546 static void
2547 free_argv(const char *argv[])
2549         int argc;
2551         for (argc = 0; argv[argc]; argc++)
2552                 free((void *) argv[argc]);
2555 static bool
2556 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2558         char buf[SIZEOF_STR];
2559         int argc;
2560         bool noreplace = flags == FORMAT_NONE;
2562         free_argv(dst_argv);
2564         for (argc = 0; src_argv[argc]; argc++) {
2565                 const char *arg = src_argv[argc];
2566                 size_t bufpos = 0;
2568                 while (arg) {
2569                         char *next = strstr(arg, "%(");
2570                         int len = next - arg;
2571                         const char *value;
2573                         if (!next || noreplace) {
2574                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2575                                         noreplace = TRUE;
2576                                 len = strlen(arg);
2577                                 value = "";
2579                         } else if (!prefixcmp(next, "%(directory)")) {
2580                                 value = opt_path;
2582                         } else if (!prefixcmp(next, "%(file)")) {
2583                                 value = opt_file;
2585                         } else if (!prefixcmp(next, "%(ref)")) {
2586                                 value = *opt_ref ? opt_ref : "HEAD";
2588                         } else if (!prefixcmp(next, "%(head)")) {
2589                                 value = ref_head;
2591                         } else if (!prefixcmp(next, "%(commit)")) {
2592                                 value = ref_commit;
2594                         } else if (!prefixcmp(next, "%(blob)")) {
2595                                 value = ref_blob;
2597                         } else {
2598                                 report("Unknown replacement: `%s`", next);
2599                                 return FALSE;
2600                         }
2602                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2603                                 return FALSE;
2605                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2606                 }
2608                 dst_argv[argc] = strdup(buf);
2609                 if (!dst_argv[argc])
2610                         break;
2611         }
2613         dst_argv[argc] = NULL;
2615         return src_argv[argc] == NULL;
2618 static bool
2619 restore_view_position(struct view *view)
2621         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2622                 return FALSE;
2624         /* Changing the view position cancels the restoring. */
2625         /* FIXME: Changing back to the first line is not detected. */
2626         if (view->offset != 0 || view->lineno != 0) {
2627                 view->p_restore = FALSE;
2628                 return FALSE;
2629         }
2631         if (view->p_lineno >= view->lines) {
2632                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2633                 if (view->p_offset >= view->p_lineno) {
2634                         unsigned long half = view->height / 2;
2636                         if (view->p_lineno > half)
2637                                 view->p_offset = view->p_lineno - half;
2638                         else
2639                                 view->p_offset = 0;
2640                 }
2641         }
2643         if (view_is_displayed(view) &&
2644             view->offset != view->p_offset &&
2645             view->lineno != view->p_lineno)
2646                 werase(view->win);
2648         view->offset = view->p_offset;
2649         view->lineno = view->p_lineno;
2650         view->p_restore = FALSE;
2652         return TRUE;
2655 static void
2656 end_update(struct view *view, bool force)
2658         if (!view->pipe)
2659                 return;
2660         while (!view->ops->read(view, NULL))
2661                 if (!force)
2662                         return;
2663         set_nonblocking_input(FALSE);
2664         if (force)
2665                 kill_io(view->pipe);
2666         done_io(view->pipe);
2667         view->pipe = NULL;
2670 static void
2671 setup_update(struct view *view, const char *vid)
2673         set_nonblocking_input(TRUE);
2674         reset_view(view);
2675         string_copy_rev(view->vid, vid);
2676         view->pipe = &view->io;
2677         view->start_time = time(NULL);
2680 static bool
2681 prepare_update(struct view *view, const char *argv[], const char *dir,
2682                enum format_flags flags)
2684         if (view->pipe)
2685                 end_update(view, TRUE);
2686         return init_io_rd(&view->io, argv, dir, flags);
2689 static bool
2690 prepare_update_file(struct view *view, const char *name)
2692         if (view->pipe)
2693                 end_update(view, TRUE);
2694         return io_open(&view->io, name);
2697 static bool
2698 begin_update(struct view *view, bool refresh)
2700         if (view->pipe)
2701                 end_update(view, TRUE);
2703         if (refresh) {
2704                 if (!start_io(&view->io))
2705                         return FALSE;
2707         } else {
2708                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2709                         opt_path[0] = 0;
2711                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2712                         return FALSE;
2714                 /* Put the current ref_* value to the view title ref
2715                  * member. This is needed by the blob view. Most other
2716                  * views sets it automatically after loading because the
2717                  * first line is a commit line. */
2718                 string_copy_rev(view->ref, view->id);
2719         }
2721         setup_update(view, view->id);
2723         return TRUE;
2726 #define ITEM_CHUNK_SIZE 256
2727 static void *
2728 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2730         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2731         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2733         if (mem == NULL || num_chunks != num_chunks_new) {
2734                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2735                 mem = realloc(mem, *size * item_size);
2736         }
2738         return mem;
2741 static struct line *
2742 realloc_lines(struct view *view, size_t line_size)
2744         size_t alloc = view->line_alloc;
2745         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2746                                          sizeof(*view->line));
2748         if (!tmp)
2749                 return NULL;
2751         view->line = tmp;
2752         view->line_alloc = alloc;
2753         return view->line;
2756 static bool
2757 update_view(struct view *view)
2759         char out_buffer[BUFSIZ * 2];
2760         char *line;
2761         /* Clear the view and redraw everything since the tree sorting
2762          * might have rearranged things. */
2763         bool redraw = view->lines == 0;
2764         bool can_read = TRUE;
2766         if (!view->pipe)
2767                 return TRUE;
2769         if (!io_can_read(view->pipe)) {
2770                 if (view->lines == 0) {
2771                         time_t secs = time(NULL) - view->start_time;
2773                         if (secs > view->update_secs) {
2774                                 if (view->update_secs == 0)
2775                                         redraw_view(view);
2776                                 update_view_title(view);
2777                                 view->update_secs = secs;
2778                         }
2779                 }
2780                 return TRUE;
2781         }
2783         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2784                 if (opt_iconv != ICONV_NONE) {
2785                         ICONV_CONST char *inbuf = line;
2786                         size_t inlen = strlen(line) + 1;
2788                         char *outbuf = out_buffer;
2789                         size_t outlen = sizeof(out_buffer);
2791                         size_t ret;
2793                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2794                         if (ret != (size_t) -1)
2795                                 line = out_buffer;
2796                 }
2798                 if (!view->ops->read(view, line)) {
2799                         report("Allocation failure");
2800                         end_update(view, TRUE);
2801                         return FALSE;
2802                 }
2803         }
2805         {
2806                 unsigned long lines = view->lines;
2807                 int digits;
2809                 for (digits = 0; lines; digits++)
2810                         lines /= 10;
2812                 /* Keep the displayed view in sync with line number scaling. */
2813                 if (digits != view->digits) {
2814                         view->digits = digits;
2815                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2816                                 redraw = TRUE;
2817                 }
2818         }
2820         if (io_error(view->pipe)) {
2821                 report("Failed to read: %s", io_strerror(view->pipe));
2822                 end_update(view, TRUE);
2824         } else if (io_eof(view->pipe)) {
2825                 report("");
2826                 end_update(view, FALSE);
2827         }
2829         if (restore_view_position(view))
2830                 redraw = TRUE;
2832         if (!view_is_displayed(view))
2833                 return TRUE;
2835         if (redraw)
2836                 redraw_view_from(view, 0);
2837         else
2838                 redraw_view_dirty(view);
2840         /* Update the title _after_ the redraw so that if the redraw picks up a
2841          * commit reference in view->ref it'll be available here. */
2842         update_view_title(view);
2843         return TRUE;
2846 static struct line *
2847 add_line_data(struct view *view, void *data, enum line_type type)
2849         struct line *line;
2851         if (!realloc_lines(view, view->lines + 1))
2852                 return NULL;
2854         line = &view->line[view->lines++];
2855         memset(line, 0, sizeof(*line));
2856         line->type = type;
2857         line->data = data;
2858         line->dirty = 1;
2860         return line;
2863 static struct line *
2864 add_line_text(struct view *view, const char *text, enum line_type type)
2866         char *data = text ? strdup(text) : NULL;
2868         return data ? add_line_data(view, data, type) : NULL;
2871 static struct line *
2872 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2874         char buf[SIZEOF_STR];
2875         va_list args;
2877         va_start(args, fmt);
2878         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2879                 buf[0] = 0;
2880         va_end(args);
2882         return buf[0] ? add_line_text(view, buf, type) : NULL;
2885 /*
2886  * View opening
2887  */
2889 enum open_flags {
2890         OPEN_DEFAULT = 0,       /* Use default view switching. */
2891         OPEN_SPLIT = 1,         /* Split current view. */
2892         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2893         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2894         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2895         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2896         OPEN_PREPARED = 32,     /* Open already prepared command. */
2897 };
2899 static void
2900 open_view(struct view *prev, enum request request, enum open_flags flags)
2902         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2903         bool split = !!(flags & OPEN_SPLIT);
2904         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2905         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2906         struct view *view = VIEW(request);
2907         int nviews = displayed_views();
2908         struct view *base_view = display[0];
2910         if (view == prev && nviews == 1 && !reload) {
2911                 report("Already in %s view", view->name);
2912                 return;
2913         }
2915         if (view->git_dir && !opt_git_dir[0]) {
2916                 report("The %s view is disabled in pager view", view->name);
2917                 return;
2918         }
2920         if (split) {
2921                 display[1] = view;
2922                 if (!backgrounded)
2923                         current_view = 1;
2924         } else if (!nomaximize) {
2925                 /* Maximize the current view. */
2926                 memset(display, 0, sizeof(display));
2927                 current_view = 0;
2928                 display[current_view] = view;
2929         }
2931         /* Resize the view when switching between split- and full-screen,
2932          * or when switching between two different full-screen views. */
2933         if (nviews != displayed_views() ||
2934             (nviews == 1 && base_view != display[0]))
2935                 resize_display();
2937         if (view->ops->open) {
2938                 if (view->pipe)
2939                         end_update(view, TRUE);
2940                 if (!view->ops->open(view)) {
2941                         report("Failed to load %s view", view->name);
2942                         return;
2943                 }
2944                 restore_view_position(view);
2946         } else if ((reload || strcmp(view->vid, view->id)) &&
2947                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2948                 report("Failed to load %s view", view->name);
2949                 return;
2950         }
2952         if (split && prev->lineno - prev->offset >= prev->height) {
2953                 /* Take the title line into account. */
2954                 int lines = prev->lineno - prev->offset - prev->height + 1;
2956                 /* Scroll the view that was split if the current line is
2957                  * outside the new limited view. */
2958                 do_scroll_view(prev, lines);
2959         }
2961         if (prev && view != prev) {
2962                 if (split && !backgrounded) {
2963                         /* "Blur" the previous view. */
2964                         update_view_title(prev);
2965                 }
2967                 view->parent = prev;
2968         }
2970         if (view->pipe && view->lines == 0) {
2971                 /* Clear the old view and let the incremental updating refill
2972                  * the screen. */
2973                 werase(view->win);
2974                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2975                 report("");
2976         } else if (view_is_displayed(view)) {
2977                 redraw_view(view);
2978                 report("");
2979         }
2981         /* If the view is backgrounded the above calls to report()
2982          * won't redraw the view title. */
2983         if (backgrounded)
2984                 update_view_title(view);
2987 static void
2988 open_external_viewer(const char *argv[], const char *dir)
2990         def_prog_mode();           /* save current tty modes */
2991         endwin();                  /* restore original tty modes */
2992         run_io_fg(argv, dir);
2993         fprintf(stderr, "Press Enter to continue");
2994         getc(opt_tty);
2995         reset_prog_mode();
2996         redraw_display(TRUE);
2999 static void
3000 open_mergetool(const char *file)
3002         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3004         open_external_viewer(mergetool_argv, opt_cdup);
3007 static void
3008 open_editor(bool from_root, const char *file)
3010         const char *editor_argv[] = { "vi", file, NULL };
3011         const char *editor;
3013         editor = getenv("GIT_EDITOR");
3014         if (!editor && *opt_editor)
3015                 editor = opt_editor;
3016         if (!editor)
3017                 editor = getenv("VISUAL");
3018         if (!editor)
3019                 editor = getenv("EDITOR");
3020         if (!editor)
3021                 editor = "vi";
3023         editor_argv[0] = editor;
3024         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3027 static void
3028 open_run_request(enum request request)
3030         struct run_request *req = get_run_request(request);
3031         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3033         if (!req) {
3034                 report("Unknown run request");
3035                 return;
3036         }
3038         if (format_argv(argv, req->argv, FORMAT_ALL))
3039                 open_external_viewer(argv, NULL);
3040         free_argv(argv);
3043 /*
3044  * User request switch noodle
3045  */
3047 static int
3048 view_driver(struct view *view, enum request request)
3050         int i;
3052         if (request == REQ_NONE) {
3053                 doupdate();
3054                 return TRUE;
3055         }
3057         if (request > REQ_NONE) {
3058                 open_run_request(request);
3059                 /* FIXME: When all views can refresh always do this. */
3060                 if (view == VIEW(REQ_VIEW_STATUS) ||
3061                     view == VIEW(REQ_VIEW_MAIN) ||
3062                     view == VIEW(REQ_VIEW_LOG) ||
3063                     view == VIEW(REQ_VIEW_STAGE))
3064                         request = REQ_REFRESH;
3065                 else
3066                         return TRUE;
3067         }
3069         if (view && view->lines) {
3070                 request = view->ops->request(view, request, &view->line[view->lineno]);
3071                 if (request == REQ_NONE)
3072                         return TRUE;
3073         }
3075         switch (request) {
3076         case REQ_MOVE_UP:
3077         case REQ_MOVE_DOWN:
3078         case REQ_MOVE_PAGE_UP:
3079         case REQ_MOVE_PAGE_DOWN:
3080         case REQ_MOVE_FIRST_LINE:
3081         case REQ_MOVE_LAST_LINE:
3082                 move_view(view, request);
3083                 break;
3085         case REQ_SCROLL_LINE_DOWN:
3086         case REQ_SCROLL_LINE_UP:
3087         case REQ_SCROLL_PAGE_DOWN:
3088         case REQ_SCROLL_PAGE_UP:
3089                 scroll_view(view, request);
3090                 break;
3092         case REQ_VIEW_BLAME:
3093                 if (!opt_file[0]) {
3094                         report("No file chosen, press %s to open tree view",
3095                                get_key(REQ_VIEW_TREE));
3096                         break;
3097                 }
3098                 open_view(view, request, OPEN_DEFAULT);
3099                 break;
3101         case REQ_VIEW_BLOB:
3102                 if (!ref_blob[0]) {
3103                         report("No file chosen, press %s to open tree view",
3104                                get_key(REQ_VIEW_TREE));
3105                         break;
3106                 }
3107                 open_view(view, request, OPEN_DEFAULT);
3108                 break;
3110         case REQ_VIEW_PAGER:
3111                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3112                         report("No pager content, press %s to run command from prompt",
3113                                get_key(REQ_PROMPT));
3114                         break;
3115                 }
3116                 open_view(view, request, OPEN_DEFAULT);
3117                 break;
3119         case REQ_VIEW_STAGE:
3120                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3121                         report("No stage content, press %s to open the status view and choose file",
3122                                get_key(REQ_VIEW_STATUS));
3123                         break;
3124                 }
3125                 open_view(view, request, OPEN_DEFAULT);
3126                 break;
3128         case REQ_VIEW_STATUS:
3129                 if (opt_is_inside_work_tree == FALSE) {
3130                         report("The status view requires a working tree");
3131                         break;
3132                 }
3133                 open_view(view, request, OPEN_DEFAULT);
3134                 break;
3136         case REQ_VIEW_MAIN:
3137         case REQ_VIEW_DIFF:
3138         case REQ_VIEW_LOG:
3139         case REQ_VIEW_TREE:
3140         case REQ_VIEW_HELP:
3141                 open_view(view, request, OPEN_DEFAULT);
3142                 break;
3144         case REQ_NEXT:
3145         case REQ_PREVIOUS:
3146                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3148                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3149                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3150                    (view == VIEW(REQ_VIEW_DIFF) &&
3151                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3152                    (view == VIEW(REQ_VIEW_STAGE) &&
3153                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3154                    (view == VIEW(REQ_VIEW_BLOB) &&
3155                      view->parent == VIEW(REQ_VIEW_TREE))) {
3156                         int line;
3158                         view = view->parent;
3159                         line = view->lineno;
3160                         move_view(view, request);
3161                         if (view_is_displayed(view))
3162                                 update_view_title(view);
3163                         if (line != view->lineno)
3164                                 view->ops->request(view, REQ_ENTER,
3165                                                    &view->line[view->lineno]);
3167                 } else {
3168                         move_view(view, request);
3169                 }
3170                 break;
3172         case REQ_VIEW_NEXT:
3173         {
3174                 int nviews = displayed_views();
3175                 int next_view = (current_view + 1) % nviews;
3177                 if (next_view == current_view) {
3178                         report("Only one view is displayed");
3179                         break;
3180                 }
3182                 current_view = next_view;
3183                 /* Blur out the title of the previous view. */
3184                 update_view_title(view);
3185                 report("");
3186                 break;
3187         }
3188         case REQ_REFRESH:
3189                 report("Refreshing is not yet supported for the %s view", view->name);
3190                 break;
3192         case REQ_MAXIMIZE:
3193                 if (displayed_views() == 2)
3194                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3195                 break;
3197         case REQ_TOGGLE_LINENO:
3198                 toggle_view_option(&opt_line_number, "line numbers");
3199                 break;
3201         case REQ_TOGGLE_DATE:
3202                 toggle_view_option(&opt_date, "date display");
3203                 break;
3205         case REQ_TOGGLE_AUTHOR:
3206                 toggle_view_option(&opt_author, "author display");
3207                 break;
3209         case REQ_TOGGLE_REV_GRAPH:
3210                 toggle_view_option(&opt_rev_graph, "revision graph display");
3211                 break;
3213         case REQ_TOGGLE_REFS:
3214                 toggle_view_option(&opt_show_refs, "reference display");
3215                 break;
3217         case REQ_SEARCH:
3218         case REQ_SEARCH_BACK:
3219                 search_view(view, request);
3220                 break;
3222         case REQ_FIND_NEXT:
3223         case REQ_FIND_PREV:
3224                 find_next(view, request);
3225                 break;
3227         case REQ_STOP_LOADING:
3228                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3229                         view = &views[i];
3230                         if (view->pipe)
3231                                 report("Stopped loading the %s view", view->name),
3232                         end_update(view, TRUE);
3233                 }
3234                 break;
3236         case REQ_SHOW_VERSION:
3237                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3238                 return TRUE;
3240         case REQ_SCREEN_REDRAW:
3241                 redraw_display(TRUE);
3242                 break;
3244         case REQ_EDIT:
3245                 report("Nothing to edit");
3246                 break;
3248         case REQ_ENTER:
3249                 report("Nothing to enter");
3250                 break;
3252         case REQ_VIEW_CLOSE:
3253                 /* XXX: Mark closed views by letting view->parent point to the
3254                  * view itself. Parents to closed view should never be
3255                  * followed. */
3256                 if (view->parent &&
3257                     view->parent->parent != view->parent) {
3258                         memset(display, 0, sizeof(display));
3259                         current_view = 0;
3260                         display[current_view] = view->parent;
3261                         view->parent = view;
3262                         resize_display();
3263                         redraw_display(FALSE);
3264                         report("");
3265                         break;
3266                 }
3267                 /* Fall-through */
3268         case REQ_QUIT:
3269                 return FALSE;
3271         default:
3272                 report("Unknown key, press 'h' for help");
3273                 return TRUE;
3274         }
3276         return TRUE;
3280 /*
3281  * View backend utilities
3282  */
3284 /* Parse author lines where the name may be empty:
3285  *      author  <email@address.tld> 1138474660 +0100
3286  */
3287 static void
3288 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3290         char *nameend = strchr(ident, '<');
3291         char *emailend = strchr(ident, '>');
3293         if (nameend && emailend)
3294                 *nameend = *emailend = 0;
3295         ident = chomp_string(ident);
3296         if (!*ident) {
3297                 if (nameend)
3298                         ident = chomp_string(nameend + 1);
3299                 if (!*ident)
3300                         ident = "Unknown";
3301         }
3303         string_ncopy_do(author, authorsize, ident, strlen(ident));
3305         /* Parse epoch and timezone */
3306         if (emailend && emailend[1] == ' ') {
3307                 char *secs = emailend + 2;
3308                 char *zone = strchr(secs, ' ');
3309                 time_t time = (time_t) atol(secs);
3311                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3312                         long tz;
3314                         zone++;
3315                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3316                         tz += ('0' - zone[2]) * 60 * 60;
3317                         tz += ('0' - zone[3]) * 60;
3318                         tz += ('0' - zone[4]) * 60;
3320                         if (zone[0] == '-')
3321                                 tz = -tz;
3323                         time -= tz;
3324                 }
3326                 gmtime_r(&time, tm);
3327         }
3330 /*
3331  * Pager backend
3332  */
3334 static bool
3335 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3337         char *text = line->data;
3339         if (opt_line_number && draw_lineno(view, lineno))
3340                 return TRUE;
3342         draw_text(view, line->type, text, TRUE);
3343         return TRUE;
3346 static bool
3347 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3349         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3350         char refbuf[SIZEOF_STR];
3351         char *ref = NULL;
3353         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3354                 ref = chomp_string(refbuf);
3356         if (!ref || !*ref)
3357                 return TRUE;
3359         /* This is the only fatal call, since it can "corrupt" the buffer. */
3360         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3361                 return FALSE;
3363         return TRUE;
3366 static void
3367 add_pager_refs(struct view *view, struct line *line)
3369         char buf[SIZEOF_STR];
3370         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3371         struct ref **refs;
3372         size_t bufpos = 0, refpos = 0;
3373         const char *sep = "Refs: ";
3374         bool is_tag = FALSE;
3376         assert(line->type == LINE_COMMIT);
3378         refs = get_refs(commit_id);
3379         if (!refs) {
3380                 if (view == VIEW(REQ_VIEW_DIFF))
3381                         goto try_add_describe_ref;
3382                 return;
3383         }
3385         do {
3386                 struct ref *ref = refs[refpos];
3387                 const char *fmt = ref->tag    ? "%s[%s]" :
3388                                   ref->remote ? "%s<%s>" : "%s%s";
3390                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3391                         return;
3392                 sep = ", ";
3393                 if (ref->tag)
3394                         is_tag = TRUE;
3395         } while (refs[refpos++]->next);
3397         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3398 try_add_describe_ref:
3399                 /* Add <tag>-g<commit_id> "fake" reference. */
3400                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3401                         return;
3402         }
3404         if (bufpos == 0)
3405                 return;
3407         add_line_text(view, buf, LINE_PP_REFS);
3410 static bool
3411 pager_read(struct view *view, char *data)
3413         struct line *line;
3415         if (!data)
3416                 return TRUE;
3418         line = add_line_text(view, data, get_line_type(data));
3419         if (!line)
3420                 return FALSE;
3422         if (line->type == LINE_COMMIT &&
3423             (view == VIEW(REQ_VIEW_DIFF) ||
3424              view == VIEW(REQ_VIEW_LOG)))
3425                 add_pager_refs(view, line);
3427         return TRUE;
3430 static enum request
3431 pager_request(struct view *view, enum request request, struct line *line)
3433         int split = 0;
3435         if (request != REQ_ENTER)
3436                 return request;
3438         if (line->type == LINE_COMMIT &&
3439            (view == VIEW(REQ_VIEW_LOG) ||
3440             view == VIEW(REQ_VIEW_PAGER))) {
3441                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3442                 split = 1;
3443         }
3445         /* Always scroll the view even if it was split. That way
3446          * you can use Enter to scroll through the log view and
3447          * split open each commit diff. */
3448         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3450         /* FIXME: A minor workaround. Scrolling the view will call report("")
3451          * but if we are scrolling a non-current view this won't properly
3452          * update the view title. */
3453         if (split)
3454                 update_view_title(view);
3456         return REQ_NONE;
3459 static bool
3460 pager_grep(struct view *view, struct line *line)
3462         regmatch_t pmatch;
3463         char *text = line->data;
3465         if (!*text)
3466                 return FALSE;
3468         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3469                 return FALSE;
3471         return TRUE;
3474 static void
3475 pager_select(struct view *view, struct line *line)
3477         if (line->type == LINE_COMMIT) {
3478                 char *text = (char *)line->data + STRING_SIZE("commit ");
3480                 if (view != VIEW(REQ_VIEW_PAGER))
3481                         string_copy_rev(view->ref, text);
3482                 string_copy_rev(ref_commit, text);
3483         }
3486 static struct view_ops pager_ops = {
3487         "line",
3488         NULL,
3489         NULL,
3490         pager_read,
3491         pager_draw,
3492         pager_request,
3493         pager_grep,
3494         pager_select,
3495 };
3497 static const char *log_argv[SIZEOF_ARG] = {
3498         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3499 };
3501 static enum request
3502 log_request(struct view *view, enum request request, struct line *line)
3504         switch (request) {
3505         case REQ_REFRESH:
3506                 load_refs();
3507                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3508                 return REQ_NONE;
3509         default:
3510                 return pager_request(view, request, line);
3511         }
3514 static struct view_ops log_ops = {
3515         "line",
3516         log_argv,
3517         NULL,
3518         pager_read,
3519         pager_draw,
3520         log_request,
3521         pager_grep,
3522         pager_select,
3523 };
3525 static const char *diff_argv[SIZEOF_ARG] = {
3526         "git", "show", "--pretty=fuller", "--no-color", "--root",
3527                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3528 };
3530 static struct view_ops diff_ops = {
3531         "line",
3532         diff_argv,
3533         NULL,
3534         pager_read,
3535         pager_draw,
3536         pager_request,
3537         pager_grep,
3538         pager_select,
3539 };
3541 /*
3542  * Help backend
3543  */
3545 static bool
3546 help_open(struct view *view)
3548         int lines = ARRAY_SIZE(req_info) + 2;
3549         int i;
3551         if (view->lines > 0)
3552                 return TRUE;
3554         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3555                 if (!req_info[i].request)
3556                         lines++;
3558         lines += run_requests + 1;
3560         view->line = calloc(lines, sizeof(*view->line));
3561         if (!view->line)
3562                 return FALSE;
3564         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3566         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3567                 const char *key;
3569                 if (req_info[i].request == REQ_NONE)
3570                         continue;
3572                 if (!req_info[i].request) {
3573                         add_line_text(view, "", LINE_DEFAULT);
3574                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3575                         continue;
3576                 }
3578                 key = get_key(req_info[i].request);
3579                 if (!*key)
3580                         key = "(no key defined)";
3582                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3583                                 key, req_info[i].help);
3584         }
3586         if (run_requests) {
3587                 add_line_text(view, "", LINE_DEFAULT);
3588                 add_line_text(view, "External commands:", LINE_DEFAULT);
3589         }
3591         for (i = 0; i < run_requests; i++) {
3592                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3593                 const char *key;
3594                 char cmd[SIZEOF_STR];
3595                 size_t bufpos;
3596                 int argc;
3598                 if (!req)
3599                         continue;
3601                 key = get_key_name(req->key);
3602                 if (!*key)
3603                         key = "(no key defined)";
3605                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3606                         if (!string_format_from(cmd, &bufpos, "%s%s",
3607                                                 argc ? " " : "", req->argv[argc]))
3608                                 return REQ_NONE;
3610                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3611                                 keymap_table[req->keymap].name, key, cmd);
3612         }
3614         return TRUE;
3617 static struct view_ops help_ops = {
3618         "line",
3619         NULL,
3620         help_open,
3621         NULL,
3622         pager_draw,
3623         pager_request,
3624         pager_grep,
3625         pager_select,
3626 };
3629 /*
3630  * Tree backend
3631  */
3633 struct tree_stack_entry {
3634         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3635         unsigned long lineno;           /* Line number to restore */
3636         char *name;                     /* Position of name in opt_path */
3637 };
3639 /* The top of the path stack. */
3640 static struct tree_stack_entry *tree_stack = NULL;
3641 unsigned long tree_lineno = 0;
3643 static void
3644 pop_tree_stack_entry(void)
3646         struct tree_stack_entry *entry = tree_stack;
3648         tree_lineno = entry->lineno;
3649         entry->name[0] = 0;
3650         tree_stack = entry->prev;
3651         free(entry);
3654 static void
3655 push_tree_stack_entry(const char *name, unsigned long lineno)
3657         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3658         size_t pathlen = strlen(opt_path);
3660         if (!entry)
3661                 return;
3663         entry->prev = tree_stack;
3664         entry->name = opt_path + pathlen;
3665         tree_stack = entry;
3667         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3668                 pop_tree_stack_entry();
3669                 return;
3670         }
3672         /* Move the current line to the first tree entry. */
3673         tree_lineno = 1;
3674         entry->lineno = lineno;
3677 /* Parse output from git-ls-tree(1):
3678  *
3679  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3680  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3681  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3682  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3683  */
3685 #define SIZEOF_TREE_ATTR \
3686         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3688 #define SIZEOF_TREE_MODE \
3689         STRING_SIZE("100644 ")
3691 #define TREE_ID_OFFSET \
3692         STRING_SIZE("100644 blob ")
3694 struct tree_entry {
3695         char id[SIZEOF_REV];
3696         mode_t mode;
3697         struct tm time;                 /* Date from the author ident. */
3698         char author[75];                /* Author of the commit. */
3699         char name[1];
3700 };
3702 static const char *
3703 tree_path(struct line *line)
3705         return ((struct tree_entry *) line->data)->name;
3709 static int
3710 tree_compare_entry(struct line *line1, struct line *line2)
3712         if (line1->type != line2->type)
3713                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3714         return strcmp(tree_path(line1), tree_path(line2));
3717 static struct line *
3718 tree_entry(struct view *view, enum line_type type, const char *path,
3719            const char *mode, const char *id)
3721         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3722         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3724         if (!entry || !line) {
3725                 free(entry);
3726                 return NULL;
3727         }
3729         strncpy(entry->name, path, strlen(path));
3730         if (mode)
3731                 entry->mode = strtoul(mode, NULL, 8);
3732         if (id)
3733                 string_copy_rev(entry->id, id);
3735         return line;
3738 static bool
3739 tree_read_date(struct view *view, char *text, bool *read_date)
3741         static char author_name[SIZEOF_STR];
3742         static struct tm author_time;
3744         if (!text && *read_date) {
3745                 *read_date = FALSE;
3746                 return TRUE;
3748         } else if (!text) {
3749                 char *path = *opt_path ? opt_path : ".";
3750                 /* Find next entry to process */
3751                 const char *log_file[] = {
3752                         "git", "log", "--no-color", "--pretty=raw",
3753                                 "--cc", "--raw", view->id, "--", path, NULL
3754                 };
3755                 struct io io = {};
3757                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3758                         report("Failed to load tree data");
3759                         return TRUE;
3760                 }
3762                 done_io(view->pipe);
3763                 view->io = io;
3764                 *read_date = TRUE;
3765                 return FALSE;
3767         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3768                 parse_author_line(text + STRING_SIZE("author "),
3769                                   author_name, sizeof(author_name), &author_time);
3771         } else if (*text == ':') {
3772                 char *pos;
3773                 size_t annotated = 1;
3774                 size_t i;
3776                 pos = strchr(text, '\t');
3777                 if (!pos)
3778                         return TRUE;
3779                 text = pos + 1;
3780                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3781                         text += strlen(opt_prefix);
3782                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3783                         text += strlen(opt_path);
3784                 pos = strchr(text, '/');
3785                 if (pos)
3786                         *pos = 0;
3788                 for (i = 1; i < view->lines; i++) {
3789                         struct line *line = &view->line[i];
3790                         struct tree_entry *entry = line->data;
3792                         annotated += !!*entry->author;
3793                         if (*entry->author || strcmp(entry->name, text))
3794                                 continue;
3796                         string_copy(entry->author, author_name);
3797                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3798                         line->dirty = 1;
3799                         break;
3800                 }
3802                 if (annotated == view->lines)
3803                         kill_io(view->pipe);
3804         }
3805         return TRUE;
3808 static bool
3809 tree_read(struct view *view, char *text)
3811         static bool read_date = FALSE;
3812         struct tree_entry *data;
3813         struct line *entry, *line;
3814         enum line_type type;
3815         size_t textlen = text ? strlen(text) : 0;
3816         char *path = text + SIZEOF_TREE_ATTR;
3818         if (read_date || !text)
3819                 return tree_read_date(view, text, &read_date);
3821         if (textlen <= SIZEOF_TREE_ATTR)
3822                 return FALSE;
3823         if (view->lines == 0 &&
3824             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3825                 return FALSE;
3827         /* Strip the path part ... */
3828         if (*opt_path) {
3829                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3830                 size_t striplen = strlen(opt_path);
3832                 if (pathlen > striplen)
3833                         memmove(path, path + striplen,
3834                                 pathlen - striplen + 1);
3836                 /* Insert "link" to parent directory. */
3837                 if (view->lines == 1 &&
3838                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3839                         return FALSE;
3840         }
3842         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3843         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3844         if (!entry)
3845                 return FALSE;
3846         data = entry->data;
3848         /* Skip "Directory ..." and ".." line. */
3849         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3850                 if (tree_compare_entry(line, entry) <= 0)
3851                         continue;
3853                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3855                 line->data = data;
3856                 line->type = type;
3857                 for (; line <= entry; line++)
3858                         line->dirty = line->cleareol = 1;
3859                 return TRUE;
3860         }
3862         if (tree_lineno > view->lineno) {
3863                 view->lineno = tree_lineno;
3864                 tree_lineno = 0;
3865         }
3867         return TRUE;
3870 static bool
3871 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3873         struct tree_entry *entry = line->data;
3875         if (line->type == LINE_TREE_PARENT) {
3876                 if (draw_text(view, line->type, "Directory path /", TRUE))
3877                         return TRUE;
3878         } else {
3879                 char mode[11] = "-r--r--r--";
3881                 if (S_ISDIR(entry->mode)) {
3882                         mode[3] = mode[6] = mode[9] = 'x';
3883                         mode[0] = 'd';
3884                 }
3885                 if (S_ISLNK(entry->mode))
3886                         mode[0] = 'l';
3887                 if (entry->mode & S_IWUSR)
3888                         mode[2] = 'w';
3889                 if (entry->mode & S_IXUSR)
3890                         mode[3] = 'x';
3891                 if (entry->mode & S_IXGRP)
3892                         mode[6] = 'x';
3893                 if (entry->mode & S_IXOTH)
3894                         mode[9] = 'x';
3895                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3896                         return TRUE;
3898                 if (opt_author &&
3899                     draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3900                         return TRUE;
3902                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3903                         return TRUE;
3904         }
3905         if (draw_text(view, line->type, entry->name, TRUE))
3906                 return TRUE;
3907         return TRUE;
3910 static void
3911 open_blob_editor()
3913         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3914         int fd = mkstemp(file);
3916         if (fd == -1)
3917                 report("Failed to create temporary file");
3918         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3919                 report("Failed to save blob data to file");
3920         else
3921                 open_editor(FALSE, file);
3922         if (fd != -1)
3923                 unlink(file);
3926 static enum request
3927 tree_request(struct view *view, enum request request, struct line *line)
3929         enum open_flags flags;
3931         switch (request) {
3932         case REQ_VIEW_BLAME:
3933                 if (line->type != LINE_TREE_FILE) {
3934                         report("Blame only supported for files");
3935                         return REQ_NONE;
3936                 }
3938                 string_copy(opt_ref, view->vid);
3939                 return request;
3941         case REQ_EDIT:
3942                 if (line->type != LINE_TREE_FILE) {
3943                         report("Edit only supported for files");
3944                 } else if (!is_head_commit(view->vid)) {
3945                         open_blob_editor();
3946                 } else {
3947                         open_editor(TRUE, opt_file);
3948                 }
3949                 return REQ_NONE;
3951         case REQ_PARENT:
3952                 if (!*opt_path) {
3953                         /* quit view if at top of tree */
3954                         return REQ_VIEW_CLOSE;
3955                 }
3956                 /* fake 'cd  ..' */
3957                 line = &view->line[1];
3958                 break;
3960         case REQ_ENTER:
3961                 break;
3963         default:
3964                 return request;
3965         }
3967         /* Cleanup the stack if the tree view is at a different tree. */
3968         while (!*opt_path && tree_stack)
3969                 pop_tree_stack_entry();
3971         switch (line->type) {
3972         case LINE_TREE_DIR:
3973                 /* Depending on whether it is a subdir or parent (updir?) link
3974                  * mangle the path buffer. */
3975                 if (line == &view->line[1] && *opt_path) {
3976                         pop_tree_stack_entry();
3978                 } else {
3979                         const char *basename = tree_path(line);
3981                         push_tree_stack_entry(basename, view->lineno);
3982                 }
3984                 /* Trees and subtrees share the same ID, so they are not not
3985                  * unique like blobs. */
3986                 flags = OPEN_RELOAD;
3987                 request = REQ_VIEW_TREE;
3988                 break;
3990         case LINE_TREE_FILE:
3991                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3992                 request = REQ_VIEW_BLOB;
3993                 break;
3995         default:
3996                 return REQ_NONE;
3997         }
3999         open_view(view, request, flags);
4000         if (request == REQ_VIEW_TREE)
4001                 view->lineno = tree_lineno;
4003         return REQ_NONE;
4006 static void
4007 tree_select(struct view *view, struct line *line)
4009         struct tree_entry *entry = line->data;
4011         if (line->type == LINE_TREE_FILE) {
4012                 string_copy_rev(ref_blob, entry->id);
4013                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4015         } else if (line->type != LINE_TREE_DIR) {
4016                 return;
4017         }
4019         string_copy_rev(view->ref, entry->id);
4022 static const char *tree_argv[SIZEOF_ARG] = {
4023         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4024 };
4026 static struct view_ops tree_ops = {
4027         "file",
4028         tree_argv,
4029         NULL,
4030         tree_read,
4031         tree_draw,
4032         tree_request,
4033         pager_grep,
4034         tree_select,
4035 };
4037 static bool
4038 blob_read(struct view *view, char *line)
4040         if (!line)
4041                 return TRUE;
4042         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4045 static enum request
4046 blob_request(struct view *view, enum request request, struct line *line)
4048         switch (request) {
4049         case REQ_EDIT:
4050                 open_blob_editor();
4051                 return REQ_NONE;
4052         default:
4053                 return pager_request(view, request, line);
4054         }
4057 static const char *blob_argv[SIZEOF_ARG] = {
4058         "git", "cat-file", "blob", "%(blob)", NULL
4059 };
4061 static struct view_ops blob_ops = {
4062         "line",
4063         blob_argv,
4064         NULL,
4065         blob_read,
4066         pager_draw,
4067         blob_request,
4068         pager_grep,
4069         pager_select,
4070 };
4072 /*
4073  * Blame backend
4074  *
4075  * Loading the blame view is a two phase job:
4076  *
4077  *  1. File content is read either using opt_file from the
4078  *     filesystem or using git-cat-file.
4079  *  2. Then blame information is incrementally added by
4080  *     reading output from git-blame.
4081  */
4083 static const char *blame_head_argv[] = {
4084         "git", "blame", "--incremental", "--", "%(file)", NULL
4085 };
4087 static const char *blame_ref_argv[] = {
4088         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4089 };
4091 static const char *blame_cat_file_argv[] = {
4092         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4093 };
4095 struct blame_commit {
4096         char id[SIZEOF_REV];            /* SHA1 ID. */
4097         char title[128];                /* First line of the commit message. */
4098         char author[75];                /* Author of the commit. */
4099         struct tm time;                 /* Date from the author ident. */
4100         char filename[128];             /* Name of file. */
4101         bool has_previous;              /* Was a "previous" line detected. */
4102 };
4104 struct blame {
4105         struct blame_commit *commit;
4106         char text[1];
4107 };
4109 static bool
4110 blame_open(struct view *view)
4112         if (*opt_ref || !io_open(&view->io, opt_file)) {
4113                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4114                         return FALSE;
4115         }
4117         setup_update(view, opt_file);
4118         string_format(view->ref, "%s ...", opt_file);
4120         return TRUE;
4123 static struct blame_commit *
4124 get_blame_commit(struct view *view, const char *id)
4126         size_t i;
4128         for (i = 0; i < view->lines; i++) {
4129                 struct blame *blame = view->line[i].data;
4131                 if (!blame->commit)
4132                         continue;
4134                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4135                         return blame->commit;
4136         }
4138         {
4139                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4141                 if (commit)
4142                         string_ncopy(commit->id, id, SIZEOF_REV);
4143                 return commit;
4144         }
4147 static bool
4148 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4150         const char *pos = *posref;
4152         *posref = NULL;
4153         pos = strchr(pos + 1, ' ');
4154         if (!pos || !isdigit(pos[1]))
4155                 return FALSE;
4156         *number = atoi(pos + 1);
4157         if (*number < min || *number > max)
4158                 return FALSE;
4160         *posref = pos;
4161         return TRUE;
4164 static struct blame_commit *
4165 parse_blame_commit(struct view *view, const char *text, int *blamed)
4167         struct blame_commit *commit;
4168         struct blame *blame;
4169         const char *pos = text + SIZEOF_REV - 1;
4170         size_t lineno;
4171         size_t group;
4173         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4174                 return NULL;
4176         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4177             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4178                 return NULL;
4180         commit = get_blame_commit(view, text);
4181         if (!commit)
4182                 return NULL;
4184         *blamed += group;
4185         while (group--) {
4186                 struct line *line = &view->line[lineno + group - 1];
4188                 blame = line->data;
4189                 blame->commit = commit;
4190                 line->dirty = 1;
4191         }
4193         return commit;
4196 static bool
4197 blame_read_file(struct view *view, const char *line, bool *read_file)
4199         if (!line) {
4200                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4201                 struct io io = {};
4203                 if (view->lines == 0 && !view->parent)
4204                         die("No blame exist for %s", view->vid);
4206                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4207                         report("Failed to load blame data");
4208                         return TRUE;
4209                 }
4211                 done_io(view->pipe);
4212                 view->io = io;
4213                 *read_file = FALSE;
4214                 return FALSE;
4216         } else {
4217                 size_t linelen = strlen(line);
4218                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4220                 blame->commit = NULL;
4221                 strncpy(blame->text, line, linelen);
4222                 blame->text[linelen] = 0;
4223                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4224         }
4227 static bool
4228 match_blame_header(const char *name, char **line)
4230         size_t namelen = strlen(name);
4231         bool matched = !strncmp(name, *line, namelen);
4233         if (matched)
4234                 *line += namelen;
4236         return matched;
4239 static bool
4240 blame_read(struct view *view, char *line)
4242         static struct blame_commit *commit = NULL;
4243         static int blamed = 0;
4244         static time_t author_time;
4245         static bool read_file = TRUE;
4247         if (read_file)
4248                 return blame_read_file(view, line, &read_file);
4250         if (!line) {
4251                 /* Reset all! */
4252                 commit = NULL;
4253                 blamed = 0;
4254                 read_file = TRUE;
4255                 string_format(view->ref, "%s", view->vid);
4256                 if (view_is_displayed(view)) {
4257                         update_view_title(view);
4258                         redraw_view_from(view, 0);
4259                 }
4260                 return TRUE;
4261         }
4263         if (!commit) {
4264                 commit = parse_blame_commit(view, line, &blamed);
4265                 string_format(view->ref, "%s %2d%%", view->vid,
4266                               view->lines ? blamed * 100 / view->lines : 0);
4268         } else if (match_blame_header("author ", &line)) {
4269                 string_ncopy(commit->author, line, strlen(line));
4271         } else if (match_blame_header("author-time ", &line)) {
4272                 author_time = (time_t) atol(line);
4274         } else if (match_blame_header("author-tz ", &line)) {
4275                 long tz;
4277                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4278                 tz += ('0' - line[2]) * 60 * 60;
4279                 tz += ('0' - line[3]) * 60;
4280                 tz += ('0' - line[4]) * 60;
4282                 if (line[0] == '-')
4283                         tz = -tz;
4285                 author_time -= tz;
4286                 gmtime_r(&author_time, &commit->time);
4288         } else if (match_blame_header("summary ", &line)) {
4289                 string_ncopy(commit->title, line, strlen(line));
4291         } else if (match_blame_header("previous ", &line)) {
4292                 commit->has_previous = TRUE;
4294         } else if (match_blame_header("filename ", &line)) {
4295                 string_ncopy(commit->filename, line, strlen(line));
4296                 commit = NULL;
4297         }
4299         return TRUE;
4302 static bool
4303 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4305         struct blame *blame = line->data;
4306         struct tm *time = NULL;
4307         const char *id = NULL, *author = NULL;
4309         if (blame->commit && *blame->commit->filename) {
4310                 id = blame->commit->id;
4311                 author = blame->commit->author;
4312                 time = &blame->commit->time;
4313         }
4315         if (opt_date && draw_date(view, time))
4316                 return TRUE;
4318         if (opt_author &&
4319             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4320                 return TRUE;
4322         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4323                 return TRUE;
4325         if (draw_lineno(view, lineno))
4326                 return TRUE;
4328         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4329         return TRUE;
4332 static bool
4333 check_blame_commit(struct blame *blame)
4335         if (!blame->commit)
4336                 report("Commit data not loaded yet");
4337         else if (!strcmp(blame->commit->id, NULL_ID))
4338                 report("No commit exist for the selected line");
4339         else
4340                 return TRUE;
4341         return FALSE;
4344 static enum request
4345 blame_request(struct view *view, enum request request, struct line *line)
4347         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4348         struct blame *blame = line->data;
4350         switch (request) {
4351         case REQ_VIEW_BLAME:
4352                 if (check_blame_commit(blame)) {
4353                         string_copy(opt_ref, blame->commit->id);
4354                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4355                 }
4356                 break;
4358         case REQ_ENTER:
4359                 if (!blame->commit) {
4360                         report("No commit loaded yet");
4361                         break;
4362                 }
4364                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4365                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4366                         break;
4368                 if (!strcmp(blame->commit->id, NULL_ID)) {
4369                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4370                         const char *diff_index_argv[] = {
4371                                 "git", "diff-index", "--root", "--patch-with-stat",
4372                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4373                         };
4375                         if (!blame->commit->has_previous) {
4376                                 diff_index_argv[1] = "diff";
4377                                 diff_index_argv[2] = "--no-color";
4378                                 diff_index_argv[6] = "--";
4379                                 diff_index_argv[7] = "/dev/null";
4380                         }
4382                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4383                                 report("Failed to allocate diff command");
4384                                 break;
4385                         }
4386                         flags |= OPEN_PREPARED;
4387                 }
4389                 open_view(view, REQ_VIEW_DIFF, flags);
4390                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4391                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4392                 break;
4394         default:
4395                 return request;
4396         }
4398         return REQ_NONE;
4401 static bool
4402 blame_grep(struct view *view, struct line *line)
4404         struct blame *blame = line->data;
4405         struct blame_commit *commit = blame->commit;
4406         regmatch_t pmatch;
4408 #define MATCH(text, on)                                                 \
4409         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4411         if (commit) {
4412                 char buf[DATE_COLS + 1];
4414                 if (MATCH(commit->title, 1) ||
4415                     MATCH(commit->author, opt_author) ||
4416                     MATCH(commit->id, opt_date))
4417                         return TRUE;
4419                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4420                     MATCH(buf, 1))
4421                         return TRUE;
4422         }
4424         return MATCH(blame->text, 1);
4426 #undef MATCH
4429 static void
4430 blame_select(struct view *view, struct line *line)
4432         struct blame *blame = line->data;
4433         struct blame_commit *commit = blame->commit;
4435         if (!commit)
4436                 return;
4438         if (!strcmp(commit->id, NULL_ID))
4439                 string_ncopy(ref_commit, "HEAD", 4);
4440         else
4441                 string_copy_rev(ref_commit, commit->id);
4444 static struct view_ops blame_ops = {
4445         "line",
4446         NULL,
4447         blame_open,
4448         blame_read,
4449         blame_draw,
4450         blame_request,
4451         blame_grep,
4452         blame_select,
4453 };
4455 /*
4456  * Status backend
4457  */
4459 struct status {
4460         char status;
4461         struct {
4462                 mode_t mode;
4463                 char rev[SIZEOF_REV];
4464                 char name[SIZEOF_STR];
4465         } old;
4466         struct {
4467                 mode_t mode;
4468                 char rev[SIZEOF_REV];
4469                 char name[SIZEOF_STR];
4470         } new;
4471 };
4473 static char status_onbranch[SIZEOF_STR];
4474 static struct status stage_status;
4475 static enum line_type stage_line_type;
4476 static size_t stage_chunks;
4477 static int *stage_chunk;
4479 /* This should work even for the "On branch" line. */
4480 static inline bool
4481 status_has_none(struct view *view, struct line *line)
4483         return line < view->line + view->lines && !line[1].data;
4486 /* Get fields from the diff line:
4487  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4488  */
4489 static inline bool
4490 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4492         const char *old_mode = buf +  1;
4493         const char *new_mode = buf +  8;
4494         const char *old_rev  = buf + 15;
4495         const char *new_rev  = buf + 56;
4496         const char *status   = buf + 97;
4498         if (bufsize < 98 ||
4499             old_mode[-1] != ':' ||
4500             new_mode[-1] != ' ' ||
4501             old_rev[-1]  != ' ' ||
4502             new_rev[-1]  != ' ' ||
4503             status[-1]   != ' ')
4504                 return FALSE;
4506         file->status = *status;
4508         string_copy_rev(file->old.rev, old_rev);
4509         string_copy_rev(file->new.rev, new_rev);
4511         file->old.mode = strtoul(old_mode, NULL, 8);
4512         file->new.mode = strtoul(new_mode, NULL, 8);
4514         file->old.name[0] = file->new.name[0] = 0;
4516         return TRUE;
4519 static bool
4520 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4522         struct status *file = NULL;
4523         struct status *unmerged = NULL;
4524         char *buf;
4525         struct io io = {};
4527         if (!run_io(&io, argv, NULL, IO_RD))
4528                 return FALSE;
4530         add_line_data(view, NULL, type);
4532         while ((buf = io_get(&io, 0, TRUE))) {
4533                 if (!file) {
4534                         file = calloc(1, sizeof(*file));
4535                         if (!file || !add_line_data(view, file, type))
4536                                 goto error_out;
4537                 }
4539                 /* Parse diff info part. */
4540                 if (status) {
4541                         file->status = status;
4542                         if (status == 'A')
4543                                 string_copy(file->old.rev, NULL_ID);
4545                 } else if (!file->status) {
4546                         if (!status_get_diff(file, buf, strlen(buf)))
4547                                 goto error_out;
4549                         buf = io_get(&io, 0, TRUE);
4550                         if (!buf)
4551                                 break;
4553                         /* Collapse all 'M'odified entries that follow a
4554                          * associated 'U'nmerged entry. */
4555                         if (file->status == 'U') {
4556                                 unmerged = file;
4558                         } else if (unmerged) {
4559                                 int collapse = !strcmp(buf, unmerged->new.name);
4561                                 unmerged = NULL;
4562                                 if (collapse) {
4563                                         free(file);
4564                                         file = NULL;
4565                                         view->lines--;
4566                                         continue;
4567                                 }
4568                         }
4569                 }
4571                 /* Grab the old name for rename/copy. */
4572                 if (!*file->old.name &&
4573                     (file->status == 'R' || file->status == 'C')) {
4574                         string_ncopy(file->old.name, buf, strlen(buf));
4576                         buf = io_get(&io, 0, TRUE);
4577                         if (!buf)
4578                                 break;
4579                 }
4581                 /* git-ls-files just delivers a NUL separated list of
4582                  * file names similar to the second half of the
4583                  * git-diff-* output. */
4584                 string_ncopy(file->new.name, buf, strlen(buf));
4585                 if (!*file->old.name)
4586                         string_copy(file->old.name, file->new.name);
4587                 file = NULL;
4588         }
4590         if (io_error(&io)) {
4591 error_out:
4592                 done_io(&io);
4593                 return FALSE;
4594         }
4596         if (!view->line[view->lines - 1].data)
4597                 add_line_data(view, NULL, LINE_STAT_NONE);
4599         done_io(&io);
4600         return TRUE;
4603 /* Don't show unmerged entries in the staged section. */
4604 static const char *status_diff_index_argv[] = {
4605         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4606                              "--cached", "-M", "HEAD", NULL
4607 };
4609 static const char *status_diff_files_argv[] = {
4610         "git", "diff-files", "-z", NULL
4611 };
4613 static const char *status_list_other_argv[] = {
4614         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4615 };
4617 static const char *status_list_no_head_argv[] = {
4618         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4619 };
4621 static const char *update_index_argv[] = {
4622         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4623 };
4625 /* Restore the previous line number to stay in the context or select a
4626  * line with something that can be updated. */
4627 static void
4628 status_restore(struct view *view)
4630         if (view->p_lineno >= view->lines)
4631                 view->p_lineno = view->lines - 1;
4632         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4633                 view->p_lineno++;
4634         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4635                 view->p_lineno--;
4637         /* If the above fails, always skip the "On branch" line. */
4638         if (view->p_lineno < view->lines)
4639                 view->lineno = view->p_lineno;
4640         else
4641                 view->lineno = 1;
4643         if (view->lineno < view->offset)
4644                 view->offset = view->lineno;
4645         else if (view->offset + view->height <= view->lineno)
4646                 view->offset = view->lineno - view->height + 1;
4648         view->p_restore = FALSE;
4651 /* First parse staged info using git-diff-index(1), then parse unstaged
4652  * info using git-diff-files(1), and finally untracked files using
4653  * git-ls-files(1). */
4654 static bool
4655 status_open(struct view *view)
4657         reset_view(view);
4659         add_line_data(view, NULL, LINE_STAT_HEAD);
4660         if (is_initial_commit())
4661                 string_copy(status_onbranch, "Initial commit");
4662         else if (!*opt_head)
4663                 string_copy(status_onbranch, "Not currently on any branch");
4664         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4665                 return FALSE;
4667         run_io_bg(update_index_argv);
4669         if (is_initial_commit()) {
4670                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4671                         return FALSE;
4672         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4673                 return FALSE;
4674         }
4676         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4677             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4678                 return FALSE;
4680         /* Restore the exact position or use the specialized restore
4681          * mode? */
4682         if (!view->p_restore)
4683                 status_restore(view);
4684         return TRUE;
4687 static bool
4688 status_draw(struct view *view, struct line *line, unsigned int lineno)
4690         struct status *status = line->data;
4691         enum line_type type;
4692         const char *text;
4694         if (!status) {
4695                 switch (line->type) {
4696                 case LINE_STAT_STAGED:
4697                         type = LINE_STAT_SECTION;
4698                         text = "Changes to be committed:";
4699                         break;
4701                 case LINE_STAT_UNSTAGED:
4702                         type = LINE_STAT_SECTION;
4703                         text = "Changed but not updated:";
4704                         break;
4706                 case LINE_STAT_UNTRACKED:
4707                         type = LINE_STAT_SECTION;
4708                         text = "Untracked files:";
4709                         break;
4711                 case LINE_STAT_NONE:
4712                         type = LINE_DEFAULT;
4713                         text = "    (no files)";
4714                         break;
4716                 case LINE_STAT_HEAD:
4717                         type = LINE_STAT_HEAD;
4718                         text = status_onbranch;
4719                         break;
4721                 default:
4722                         return FALSE;
4723                 }
4724         } else {
4725                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4727                 buf[0] = status->status;
4728                 if (draw_text(view, line->type, buf, TRUE))
4729                         return TRUE;
4730                 type = LINE_DEFAULT;
4731                 text = status->new.name;
4732         }
4734         draw_text(view, type, text, TRUE);
4735         return TRUE;
4738 static enum request
4739 status_enter(struct view *view, struct line *line)
4741         struct status *status = line->data;
4742         const char *oldpath = status ? status->old.name : NULL;
4743         /* Diffs for unmerged entries are empty when passing the new
4744          * path, so leave it empty. */
4745         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4746         const char *info;
4747         enum open_flags split;
4748         struct view *stage = VIEW(REQ_VIEW_STAGE);
4750         if (line->type == LINE_STAT_NONE ||
4751             (!status && line[1].type == LINE_STAT_NONE)) {
4752                 report("No file to diff");
4753                 return REQ_NONE;
4754         }
4756         switch (line->type) {
4757         case LINE_STAT_STAGED:
4758                 if (is_initial_commit()) {
4759                         const char *no_head_diff_argv[] = {
4760                                 "git", "diff", "--no-color", "--patch-with-stat",
4761                                         "--", "/dev/null", newpath, NULL
4762                         };
4764                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4765                                 return REQ_QUIT;
4766                 } else {
4767                         const char *index_show_argv[] = {
4768                                 "git", "diff-index", "--root", "--patch-with-stat",
4769                                         "-C", "-M", "--cached", "HEAD", "--",
4770                                         oldpath, newpath, NULL
4771                         };
4773                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4774                                 return REQ_QUIT;
4775                 }
4777                 if (status)
4778                         info = "Staged changes to %s";
4779                 else
4780                         info = "Staged changes";
4781                 break;
4783         case LINE_STAT_UNSTAGED:
4784         {
4785                 const char *files_show_argv[] = {
4786                         "git", "diff-files", "--root", "--patch-with-stat",
4787                                 "-C", "-M", "--", oldpath, newpath, NULL
4788                 };
4790                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4791                         return REQ_QUIT;
4792                 if (status)
4793                         info = "Unstaged changes to %s";
4794                 else
4795                         info = "Unstaged changes";
4796                 break;
4797         }
4798         case LINE_STAT_UNTRACKED:
4799                 if (!newpath) {
4800                         report("No file to show");
4801                         return REQ_NONE;
4802                 }
4804                 if (!suffixcmp(status->new.name, -1, "/")) {
4805                         report("Cannot display a directory");
4806                         return REQ_NONE;
4807                 }
4809                 if (!prepare_update_file(stage, newpath))
4810                         return REQ_QUIT;
4811                 info = "Untracked file %s";
4812                 break;
4814         case LINE_STAT_HEAD:
4815                 return REQ_NONE;
4817         default:
4818                 die("line type %d not handled in switch", line->type);
4819         }
4821         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4822         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4823         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4824                 if (status) {
4825                         stage_status = *status;
4826                 } else {
4827                         memset(&stage_status, 0, sizeof(stage_status));
4828                 }
4830                 stage_line_type = line->type;
4831                 stage_chunks = 0;
4832                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4833         }
4835         return REQ_NONE;
4838 static bool
4839 status_exists(struct status *status, enum line_type type)
4841         struct view *view = VIEW(REQ_VIEW_STATUS);
4842         unsigned long lineno;
4844         for (lineno = 0; lineno < view->lines; lineno++) {
4845                 struct line *line = &view->line[lineno];
4846                 struct status *pos = line->data;
4848                 if (line->type != type)
4849                         continue;
4850                 if (!pos && (!status || !status->status) && line[1].data) {
4851                         select_view_line(view, lineno);
4852                         return TRUE;
4853                 }
4854                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4855                         select_view_line(view, lineno);
4856                         return TRUE;
4857                 }
4858         }
4860         return FALSE;
4864 static bool
4865 status_update_prepare(struct io *io, enum line_type type)
4867         const char *staged_argv[] = {
4868                 "git", "update-index", "-z", "--index-info", NULL
4869         };
4870         const char *others_argv[] = {
4871                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4872         };
4874         switch (type) {
4875         case LINE_STAT_STAGED:
4876                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4878         case LINE_STAT_UNSTAGED:
4879                 return run_io(io, others_argv, opt_cdup, IO_WR);
4881         case LINE_STAT_UNTRACKED:
4882                 return run_io(io, others_argv, NULL, IO_WR);
4884         default:
4885                 die("line type %d not handled in switch", type);
4886                 return FALSE;
4887         }
4890 static bool
4891 status_update_write(struct io *io, struct status *status, enum line_type type)
4893         char buf[SIZEOF_STR];
4894         size_t bufsize = 0;
4896         switch (type) {
4897         case LINE_STAT_STAGED:
4898                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4899                                         status->old.mode,
4900                                         status->old.rev,
4901                                         status->old.name, 0))
4902                         return FALSE;
4903                 break;
4905         case LINE_STAT_UNSTAGED:
4906         case LINE_STAT_UNTRACKED:
4907                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4908                         return FALSE;
4909                 break;
4911         default:
4912                 die("line type %d not handled in switch", type);
4913         }
4915         return io_write(io, buf, bufsize);
4918 static bool
4919 status_update_file(struct status *status, enum line_type type)
4921         struct io io = {};
4922         bool result;
4924         if (!status_update_prepare(&io, type))
4925                 return FALSE;
4927         result = status_update_write(&io, status, type);
4928         done_io(&io);
4929         return result;
4932 static bool
4933 status_update_files(struct view *view, struct line *line)
4935         struct io io = {};
4936         bool result = TRUE;
4937         struct line *pos = view->line + view->lines;
4938         int files = 0;
4939         int file, done;
4941         if (!status_update_prepare(&io, line->type))
4942                 return FALSE;
4944         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4945                 files++;
4947         for (file = 0, done = 0; result && file < files; line++, file++) {
4948                 int almost_done = file * 100 / files;
4950                 if (almost_done > done) {
4951                         done = almost_done;
4952                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4953                                       file, files, done);
4954                         update_view_title(view);
4955                 }
4956                 result = status_update_write(&io, line->data, line->type);
4957         }
4959         done_io(&io);
4960         return result;
4963 static bool
4964 status_update(struct view *view)
4966         struct line *line = &view->line[view->lineno];
4968         assert(view->lines);
4970         if (!line->data) {
4971                 /* This should work even for the "On branch" line. */
4972                 if (line < view->line + view->lines && !line[1].data) {
4973                         report("Nothing to update");
4974                         return FALSE;
4975                 }
4977                 if (!status_update_files(view, line + 1)) {
4978                         report("Failed to update file status");
4979                         return FALSE;
4980                 }
4982         } else if (!status_update_file(line->data, line->type)) {
4983                 report("Failed to update file status");
4984                 return FALSE;
4985         }
4987         return TRUE;
4990 static bool
4991 status_revert(struct status *status, enum line_type type, bool has_none)
4993         if (!status || type != LINE_STAT_UNSTAGED) {
4994                 if (type == LINE_STAT_STAGED) {
4995                         report("Cannot revert changes to staged files");
4996                 } else if (type == LINE_STAT_UNTRACKED) {
4997                         report("Cannot revert changes to untracked files");
4998                 } else if (has_none) {
4999                         report("Nothing to revert");
5000                 } else {
5001                         report("Cannot revert changes to multiple files");
5002                 }
5003                 return FALSE;
5005         } else {
5006                 const char *checkout_argv[] = {
5007                         "git", "checkout", "--", status->old.name, NULL
5008                 };
5010                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5011                         return FALSE;
5012                 return run_io_fg(checkout_argv, opt_cdup);
5013         }
5016 static enum request
5017 status_request(struct view *view, enum request request, struct line *line)
5019         struct status *status = line->data;
5021         switch (request) {
5022         case REQ_STATUS_UPDATE:
5023                 if (!status_update(view))
5024                         return REQ_NONE;
5025                 break;
5027         case REQ_STATUS_REVERT:
5028                 if (!status_revert(status, line->type, status_has_none(view, line)))
5029                         return REQ_NONE;
5030                 break;
5032         case REQ_STATUS_MERGE:
5033                 if (!status || status->status != 'U') {
5034                         report("Merging only possible for files with unmerged status ('U').");
5035                         return REQ_NONE;
5036                 }
5037                 open_mergetool(status->new.name);
5038                 break;
5040         case REQ_EDIT:
5041                 if (!status)
5042                         return request;
5043                 if (status->status == 'D') {
5044                         report("File has been deleted.");
5045                         return REQ_NONE;
5046                 }
5048                 open_editor(status->status != '?', status->new.name);
5049                 break;
5051         case REQ_VIEW_BLAME:
5052                 if (status) {
5053                         string_copy(opt_file, status->new.name);
5054                         opt_ref[0] = 0;
5055                 }
5056                 return request;
5058         case REQ_ENTER:
5059                 /* After returning the status view has been split to
5060                  * show the stage view. No further reloading is
5061                  * necessary. */
5062                 status_enter(view, line);
5063                 return REQ_NONE;
5065         case REQ_REFRESH:
5066                 /* Simply reload the view. */
5067                 break;
5069         default:
5070                 return request;
5071         }
5073         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5075         return REQ_NONE;
5078 static void
5079 status_select(struct view *view, struct line *line)
5081         struct status *status = line->data;
5082         char file[SIZEOF_STR] = "all files";
5083         const char *text;
5084         const char *key;
5086         if (status && !string_format(file, "'%s'", status->new.name))
5087                 return;
5089         if (!status && line[1].type == LINE_STAT_NONE)
5090                 line++;
5092         switch (line->type) {
5093         case LINE_STAT_STAGED:
5094                 text = "Press %s to unstage %s for commit";
5095                 break;
5097         case LINE_STAT_UNSTAGED:
5098                 text = "Press %s to stage %s for commit";
5099                 break;
5101         case LINE_STAT_UNTRACKED:
5102                 text = "Press %s to stage %s for addition";
5103                 break;
5105         case LINE_STAT_HEAD:
5106         case LINE_STAT_NONE:
5107                 text = "Nothing to update";
5108                 break;
5110         default:
5111                 die("line type %d not handled in switch", line->type);
5112         }
5114         if (status && status->status == 'U') {
5115                 text = "Press %s to resolve conflict in %s";
5116                 key = get_key(REQ_STATUS_MERGE);
5118         } else {
5119                 key = get_key(REQ_STATUS_UPDATE);
5120         }
5122         string_format(view->ref, text, key, file);
5125 static bool
5126 status_grep(struct view *view, struct line *line)
5128         struct status *status = line->data;
5129         enum { S_STATUS, S_NAME, S_END } state;
5130         char buf[2] = "?";
5131         regmatch_t pmatch;
5133         if (!status)
5134                 return FALSE;
5136         for (state = S_STATUS; state < S_END; state++) {
5137                 const char *text;
5139                 switch (state) {
5140                 case S_NAME:    text = status->new.name;        break;
5141                 case S_STATUS:
5142                         buf[0] = status->status;
5143                         text = buf;
5144                         break;
5146                 default:
5147                         return FALSE;
5148                 }
5150                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5151                         return TRUE;
5152         }
5154         return FALSE;
5157 static struct view_ops status_ops = {
5158         "file",
5159         NULL,
5160         status_open,
5161         NULL,
5162         status_draw,
5163         status_request,
5164         status_grep,
5165         status_select,
5166 };
5169 static bool
5170 stage_diff_write(struct io *io, struct line *line, struct line *end)
5172         while (line < end) {
5173                 if (!io_write(io, line->data, strlen(line->data)) ||
5174                     !io_write(io, "\n", 1))
5175                         return FALSE;
5176                 line++;
5177                 if (line->type == LINE_DIFF_CHUNK ||
5178                     line->type == LINE_DIFF_HEADER)
5179                         break;
5180         }
5182         return TRUE;
5185 static struct line *
5186 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5188         for (; view->line < line; line--)
5189                 if (line->type == type)
5190                         return line;
5192         return NULL;
5195 static bool
5196 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5198         const char *apply_argv[SIZEOF_ARG] = {
5199                 "git", "apply", "--whitespace=nowarn", NULL
5200         };
5201         struct line *diff_hdr;
5202         struct io io = {};
5203         int argc = 3;
5205         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5206         if (!diff_hdr)
5207                 return FALSE;
5209         if (!revert)
5210                 apply_argv[argc++] = "--cached";
5211         if (revert || stage_line_type == LINE_STAT_STAGED)
5212                 apply_argv[argc++] = "-R";
5213         apply_argv[argc++] = "-";
5214         apply_argv[argc++] = NULL;
5215         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5216                 return FALSE;
5218         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5219             !stage_diff_write(&io, chunk, view->line + view->lines))
5220                 chunk = NULL;
5222         done_io(&io);
5223         run_io_bg(update_index_argv);
5225         return chunk ? TRUE : FALSE;
5228 static bool
5229 stage_update(struct view *view, struct line *line)
5231         struct line *chunk = NULL;
5233         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5234                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5236         if (chunk) {
5237                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5238                         report("Failed to apply chunk");
5239                         return FALSE;
5240                 }
5242         } else if (!stage_status.status) {
5243                 view = VIEW(REQ_VIEW_STATUS);
5245                 for (line = view->line; line < view->line + view->lines; line++)
5246                         if (line->type == stage_line_type)
5247                                 break;
5249                 if (!status_update_files(view, line + 1)) {
5250                         report("Failed to update files");
5251                         return FALSE;
5252                 }
5254         } else if (!status_update_file(&stage_status, stage_line_type)) {
5255                 report("Failed to update file");
5256                 return FALSE;
5257         }
5259         return TRUE;
5262 static bool
5263 stage_revert(struct view *view, struct line *line)
5265         struct line *chunk = NULL;
5267         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5268                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5270         if (chunk) {
5271                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5272                         return FALSE;
5274                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5275                         report("Failed to revert chunk");
5276                         return FALSE;
5277                 }
5278                 return TRUE;
5280         } else {
5281                 return status_revert(stage_status.status ? &stage_status : NULL,
5282                                      stage_line_type, FALSE);
5283         }
5287 static void
5288 stage_next(struct view *view, struct line *line)
5290         int i;
5292         if (!stage_chunks) {
5293                 static size_t alloc = 0;
5294                 int *tmp;
5296                 for (line = view->line; line < view->line + view->lines; line++) {
5297                         if (line->type != LINE_DIFF_CHUNK)
5298                                 continue;
5300                         tmp = realloc_items(stage_chunk, &alloc,
5301                                             stage_chunks, sizeof(*tmp));
5302                         if (!tmp) {
5303                                 report("Allocation failure");
5304                                 return;
5305                         }
5307                         stage_chunk = tmp;
5308                         stage_chunk[stage_chunks++] = line - view->line;
5309                 }
5310         }
5312         for (i = 0; i < stage_chunks; i++) {
5313                 if (stage_chunk[i] > view->lineno) {
5314                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5315                         report("Chunk %d of %d", i + 1, stage_chunks);
5316                         return;
5317                 }
5318         }
5320         report("No next chunk found");
5323 static enum request
5324 stage_request(struct view *view, enum request request, struct line *line)
5326         switch (request) {
5327         case REQ_STATUS_UPDATE:
5328                 if (!stage_update(view, line))
5329                         return REQ_NONE;
5330                 break;
5332         case REQ_STATUS_REVERT:
5333                 if (!stage_revert(view, line))
5334                         return REQ_NONE;
5335                 break;
5337         case REQ_STAGE_NEXT:
5338                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5339                         report("File is untracked; press %s to add",
5340                                get_key(REQ_STATUS_UPDATE));
5341                         return REQ_NONE;
5342                 }
5343                 stage_next(view, line);
5344                 return REQ_NONE;
5346         case REQ_EDIT:
5347                 if (!stage_status.new.name[0])
5348                         return request;
5349                 if (stage_status.status == 'D') {
5350                         report("File has been deleted.");
5351                         return REQ_NONE;
5352                 }
5354                 open_editor(stage_status.status != '?', stage_status.new.name);
5355                 break;
5357         case REQ_REFRESH:
5358                 /* Reload everything ... */
5359                 break;
5361         case REQ_VIEW_BLAME:
5362                 if (stage_status.new.name[0]) {
5363                         string_copy(opt_file, stage_status.new.name);
5364                         opt_ref[0] = 0;
5365                 }
5366                 return request;
5368         case REQ_ENTER:
5369                 return pager_request(view, request, line);
5371         default:
5372                 return request;
5373         }
5375         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5376         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5378         /* Check whether the staged entry still exists, and close the
5379          * stage view if it doesn't. */
5380         if (!status_exists(&stage_status, stage_line_type)) {
5381                 status_restore(VIEW(REQ_VIEW_STATUS));
5382                 return REQ_VIEW_CLOSE;
5383         }
5385         if (stage_line_type == LINE_STAT_UNTRACKED) {
5386                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5387                         report("Cannot display a directory");
5388                         return REQ_NONE;
5389                 }
5391                 if (!prepare_update_file(view, stage_status.new.name)) {
5392                         report("Failed to open file: %s", strerror(errno));
5393                         return REQ_NONE;
5394                 }
5395         }
5396         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5398         return REQ_NONE;
5401 static struct view_ops stage_ops = {
5402         "line",
5403         NULL,
5404         NULL,
5405         pager_read,
5406         pager_draw,
5407         stage_request,
5408         pager_grep,
5409         pager_select,
5410 };
5413 /*
5414  * Revision graph
5415  */
5417 struct commit {
5418         char id[SIZEOF_REV];            /* SHA1 ID. */
5419         char title[128];                /* First line of the commit message. */
5420         char author[75];                /* Author of the commit. */
5421         struct tm time;                 /* Date from the author ident. */
5422         struct ref **refs;              /* Repository references. */
5423         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5424         size_t graph_size;              /* The width of the graph array. */
5425         bool has_parents;               /* Rewritten --parents seen. */
5426 };
5428 /* Size of rev graph with no  "padding" columns */
5429 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5431 struct rev_graph {
5432         struct rev_graph *prev, *next, *parents;
5433         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5434         size_t size;
5435         struct commit *commit;
5436         size_t pos;
5437         unsigned int boundary:1;
5438 };
5440 /* Parents of the commit being visualized. */
5441 static struct rev_graph graph_parents[4];
5443 /* The current stack of revisions on the graph. */
5444 static struct rev_graph graph_stacks[4] = {
5445         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5446         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5447         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5448         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5449 };
5451 static inline bool
5452 graph_parent_is_merge(struct rev_graph *graph)
5454         return graph->parents->size > 1;
5457 static inline void
5458 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5460         struct commit *commit = graph->commit;
5462         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5463                 commit->graph[commit->graph_size++] = symbol;
5466 static void
5467 clear_rev_graph(struct rev_graph *graph)
5469         graph->boundary = 0;
5470         graph->size = graph->pos = 0;
5471         graph->commit = NULL;
5472         memset(graph->parents, 0, sizeof(*graph->parents));
5475 static void
5476 done_rev_graph(struct rev_graph *graph)
5478         if (graph_parent_is_merge(graph) &&
5479             graph->pos < graph->size - 1 &&
5480             graph->next->size == graph->size + graph->parents->size - 1) {
5481                 size_t i = graph->pos + graph->parents->size - 1;
5483                 graph->commit->graph_size = i * 2;
5484                 while (i < graph->next->size - 1) {
5485                         append_to_rev_graph(graph, ' ');
5486                         append_to_rev_graph(graph, '\\');
5487                         i++;
5488                 }
5489         }
5491         clear_rev_graph(graph);
5494 static void
5495 push_rev_graph(struct rev_graph *graph, const char *parent)
5497         int i;
5499         /* "Collapse" duplicate parents lines.
5500          *
5501          * FIXME: This needs to also update update the drawn graph but
5502          * for now it just serves as a method for pruning graph lines. */
5503         for (i = 0; i < graph->size; i++)
5504                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5505                         return;
5507         if (graph->size < SIZEOF_REVITEMS) {
5508                 string_copy_rev(graph->rev[graph->size++], parent);
5509         }
5512 static chtype
5513 get_rev_graph_symbol(struct rev_graph *graph)
5515         chtype symbol;
5517         if (graph->boundary)
5518                 symbol = REVGRAPH_BOUND;
5519         else if (graph->parents->size == 0)
5520                 symbol = REVGRAPH_INIT;
5521         else if (graph_parent_is_merge(graph))
5522                 symbol = REVGRAPH_MERGE;
5523         else if (graph->pos >= graph->size)
5524                 symbol = REVGRAPH_BRANCH;
5525         else
5526                 symbol = REVGRAPH_COMMIT;
5528         return symbol;
5531 static void
5532 draw_rev_graph(struct rev_graph *graph)
5534         struct rev_filler {
5535                 chtype separator, line;
5536         };
5537         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5538         static struct rev_filler fillers[] = {
5539                 { ' ',  '|' },
5540                 { '`',  '.' },
5541                 { '\'', ' ' },
5542                 { '/',  ' ' },
5543         };
5544         chtype symbol = get_rev_graph_symbol(graph);
5545         struct rev_filler *filler;
5546         size_t i;
5548         if (opt_line_graphics)
5549                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5551         filler = &fillers[DEFAULT];
5553         for (i = 0; i < graph->pos; i++) {
5554                 append_to_rev_graph(graph, filler->line);
5555                 if (graph_parent_is_merge(graph->prev) &&
5556                     graph->prev->pos == i)
5557                         filler = &fillers[RSHARP];
5559                 append_to_rev_graph(graph, filler->separator);
5560         }
5562         /* Place the symbol for this revision. */
5563         append_to_rev_graph(graph, symbol);
5565         if (graph->prev->size > graph->size)
5566                 filler = &fillers[RDIAG];
5567         else
5568                 filler = &fillers[DEFAULT];
5570         i++;
5572         for (; i < graph->size; i++) {
5573                 append_to_rev_graph(graph, filler->separator);
5574                 append_to_rev_graph(graph, filler->line);
5575                 if (graph_parent_is_merge(graph->prev) &&
5576                     i < graph->prev->pos + graph->parents->size)
5577                         filler = &fillers[RSHARP];
5578                 if (graph->prev->size > graph->size)
5579                         filler = &fillers[LDIAG];
5580         }
5582         if (graph->prev->size > graph->size) {
5583                 append_to_rev_graph(graph, filler->separator);
5584                 if (filler->line != ' ')
5585                         append_to_rev_graph(graph, filler->line);
5586         }
5589 /* Prepare the next rev graph */
5590 static void
5591 prepare_rev_graph(struct rev_graph *graph)
5593         size_t i;
5595         /* First, traverse all lines of revisions up to the active one. */
5596         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5597                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5598                         break;
5600                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5601         }
5603         /* Interleave the new revision parent(s). */
5604         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5605                 push_rev_graph(graph->next, graph->parents->rev[i]);
5607         /* Lastly, put any remaining revisions. */
5608         for (i = graph->pos + 1; i < graph->size; i++)
5609                 push_rev_graph(graph->next, graph->rev[i]);
5612 static void
5613 update_rev_graph(struct view *view, struct rev_graph *graph)
5615         /* If this is the finalizing update ... */
5616         if (graph->commit)
5617                 prepare_rev_graph(graph);
5619         /* Graph visualization needs a one rev look-ahead,
5620          * so the first update doesn't visualize anything. */
5621         if (!graph->prev->commit)
5622                 return;
5624         if (view->lines > 2)
5625                 view->line[view->lines - 3].dirty = 1;
5626         if (view->lines > 1)
5627                 view->line[view->lines - 2].dirty = 1;
5628         draw_rev_graph(graph->prev);
5629         done_rev_graph(graph->prev->prev);
5633 /*
5634  * Main view backend
5635  */
5637 static const char *main_argv[SIZEOF_ARG] = {
5638         "git", "log", "--no-color", "--pretty=raw", "--parents",
5639                       "--topo-order", "%(head)", NULL
5640 };
5642 static bool
5643 main_draw(struct view *view, struct line *line, unsigned int lineno)
5645         struct commit *commit = line->data;
5647         if (!*commit->author)
5648                 return FALSE;
5650         if (opt_date && draw_date(view, &commit->time))
5651                 return TRUE;
5653         if (opt_author &&
5654             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5655                 return TRUE;
5657         if (opt_rev_graph && commit->graph_size &&
5658             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5659                 return TRUE;
5661         if (opt_show_refs && commit->refs) {
5662                 size_t i = 0;
5664                 do {
5665                         enum line_type type;
5667                         if (commit->refs[i]->head)
5668                                 type = LINE_MAIN_HEAD;
5669                         else if (commit->refs[i]->ltag)
5670                                 type = LINE_MAIN_LOCAL_TAG;
5671                         else if (commit->refs[i]->tag)
5672                                 type = LINE_MAIN_TAG;
5673                         else if (commit->refs[i]->tracked)
5674                                 type = LINE_MAIN_TRACKED;
5675                         else if (commit->refs[i]->remote)
5676                                 type = LINE_MAIN_REMOTE;
5677                         else
5678                                 type = LINE_MAIN_REF;
5680                         if (draw_text(view, type, "[", TRUE) ||
5681                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5682                             draw_text(view, type, "]", TRUE))
5683                                 return TRUE;
5685                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5686                                 return TRUE;
5687                 } while (commit->refs[i++]->next);
5688         }
5690         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5691         return TRUE;
5694 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5695 static bool
5696 main_read(struct view *view, char *line)
5698         static struct rev_graph *graph = graph_stacks;
5699         enum line_type type;
5700         struct commit *commit;
5702         if (!line) {
5703                 int i;
5705                 if (!view->lines && !view->parent)
5706                         die("No revisions match the given arguments.");
5707                 if (view->lines > 0) {
5708                         commit = view->line[view->lines - 1].data;
5709                         view->line[view->lines - 1].dirty = 1;
5710                         if (!*commit->author) {
5711                                 view->lines--;
5712                                 free(commit);
5713                                 graph->commit = NULL;
5714                         }
5715                 }
5716                 update_rev_graph(view, graph);
5718                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5719                         clear_rev_graph(&graph_stacks[i]);
5720                 return TRUE;
5721         }
5723         type = get_line_type(line);
5724         if (type == LINE_COMMIT) {
5725                 commit = calloc(1, sizeof(struct commit));
5726                 if (!commit)
5727                         return FALSE;
5729                 line += STRING_SIZE("commit ");
5730                 if (*line == '-') {
5731                         graph->boundary = 1;
5732                         line++;
5733                 }
5735                 string_copy_rev(commit->id, line);
5736                 commit->refs = get_refs(commit->id);
5737                 graph->commit = commit;
5738                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5740                 while ((line = strchr(line, ' '))) {
5741                         line++;
5742                         push_rev_graph(graph->parents, line);
5743                         commit->has_parents = TRUE;
5744                 }
5745                 return TRUE;
5746         }
5748         if (!view->lines)
5749                 return TRUE;
5750         commit = view->line[view->lines - 1].data;
5752         switch (type) {
5753         case LINE_PARENT:
5754                 if (commit->has_parents)
5755                         break;
5756                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5757                 break;
5759         case LINE_AUTHOR:
5760                 parse_author_line(line + STRING_SIZE("author "),
5761                                   commit->author, sizeof(commit->author),
5762                                   &commit->time);
5763                 update_rev_graph(view, graph);
5764                 graph = graph->next;
5765                 break;
5767         default:
5768                 /* Fill in the commit title if it has not already been set. */
5769                 if (commit->title[0])
5770                         break;
5772                 /* Require titles to start with a non-space character at the
5773                  * offset used by git log. */
5774                 if (strncmp(line, "    ", 4))
5775                         break;
5776                 line += 4;
5777                 /* Well, if the title starts with a whitespace character,
5778                  * try to be forgiving.  Otherwise we end up with no title. */
5779                 while (isspace(*line))
5780                         line++;
5781                 if (*line == '\0')
5782                         break;
5783                 /* FIXME: More graceful handling of titles; append "..." to
5784                  * shortened titles, etc. */
5786                 string_ncopy(commit->title, line, strlen(line));
5787                 view->line[view->lines - 1].dirty = 1;
5788         }
5790         return TRUE;
5793 static enum request
5794 main_request(struct view *view, enum request request, struct line *line)
5796         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5798         switch (request) {
5799         case REQ_ENTER:
5800                 open_view(view, REQ_VIEW_DIFF, flags);
5801                 break;
5802         case REQ_REFRESH:
5803                 load_refs();
5804                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5805                 break;
5806         default:
5807                 return request;
5808         }
5810         return REQ_NONE;
5813 static bool
5814 grep_refs(struct ref **refs, regex_t *regex)
5816         regmatch_t pmatch;
5817         size_t i = 0;
5819         if (!refs)
5820                 return FALSE;
5821         do {
5822                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5823                         return TRUE;
5824         } while (refs[i++]->next);
5826         return FALSE;
5829 static bool
5830 main_grep(struct view *view, struct line *line)
5832         struct commit *commit = line->data;
5833         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5834         char buf[DATE_COLS + 1];
5835         regmatch_t pmatch;
5837         for (state = S_TITLE; state < S_END; state++) {
5838                 char *text;
5840                 switch (state) {
5841                 case S_TITLE:   text = commit->title;   break;
5842                 case S_AUTHOR:
5843                         if (!opt_author)
5844                                 continue;
5845                         text = commit->author;
5846                         break;
5847                 case S_DATE:
5848                         if (!opt_date)
5849                                 continue;
5850                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5851                                 continue;
5852                         text = buf;
5853                         break;
5854                 case S_REFS:
5855                         if (!opt_show_refs)
5856                                 continue;
5857                         if (grep_refs(commit->refs, view->regex) == TRUE)
5858                                 return TRUE;
5859                         continue;
5860                 default:
5861                         return FALSE;
5862                 }
5864                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5865                         return TRUE;
5866         }
5868         return FALSE;
5871 static void
5872 main_select(struct view *view, struct line *line)
5874         struct commit *commit = line->data;
5876         string_copy_rev(view->ref, commit->id);
5877         string_copy_rev(ref_commit, view->ref);
5880 static struct view_ops main_ops = {
5881         "commit",
5882         main_argv,
5883         NULL,
5884         main_read,
5885         main_draw,
5886         main_request,
5887         main_grep,
5888         main_select,
5889 };
5892 /*
5893  * Unicode / UTF-8 handling
5894  *
5895  * NOTE: Much of the following code for dealing with unicode is derived from
5896  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5897  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5898  */
5900 /* I've (over)annotated a lot of code snippets because I am not entirely
5901  * confident that the approach taken by this small UTF-8 interface is correct.
5902  * --jonas */
5904 static inline int
5905 unicode_width(unsigned long c)
5907         if (c >= 0x1100 &&
5908            (c <= 0x115f                         /* Hangul Jamo */
5909             || c == 0x2329
5910             || c == 0x232a
5911             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5912                                                 /* CJK ... Yi */
5913             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5914             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5915             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5916             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5917             || (c >= 0xffe0  && c <= 0xffe6)
5918             || (c >= 0x20000 && c <= 0x2fffd)
5919             || (c >= 0x30000 && c <= 0x3fffd)))
5920                 return 2;
5922         if (c == '\t')
5923                 return opt_tab_size;
5925         return 1;
5928 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5929  * Illegal bytes are set one. */
5930 static const unsigned char utf8_bytes[256] = {
5931         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,
5932         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,
5933         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,
5934         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,
5935         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,
5936         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,
5937         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,
5938         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,
5939 };
5941 /* Decode UTF-8 multi-byte representation into a unicode character. */
5942 static inline unsigned long
5943 utf8_to_unicode(const char *string, size_t length)
5945         unsigned long unicode;
5947         switch (length) {
5948         case 1:
5949                 unicode  =   string[0];
5950                 break;
5951         case 2:
5952                 unicode  =  (string[0] & 0x1f) << 6;
5953                 unicode +=  (string[1] & 0x3f);
5954                 break;
5955         case 3:
5956                 unicode  =  (string[0] & 0x0f) << 12;
5957                 unicode += ((string[1] & 0x3f) << 6);
5958                 unicode +=  (string[2] & 0x3f);
5959                 break;
5960         case 4:
5961                 unicode  =  (string[0] & 0x0f) << 18;
5962                 unicode += ((string[1] & 0x3f) << 12);
5963                 unicode += ((string[2] & 0x3f) << 6);
5964                 unicode +=  (string[3] & 0x3f);
5965                 break;
5966         case 5:
5967                 unicode  =  (string[0] & 0x0f) << 24;
5968                 unicode += ((string[1] & 0x3f) << 18);
5969                 unicode += ((string[2] & 0x3f) << 12);
5970                 unicode += ((string[3] & 0x3f) << 6);
5971                 unicode +=  (string[4] & 0x3f);
5972                 break;
5973         case 6:
5974                 unicode  =  (string[0] & 0x01) << 30;
5975                 unicode += ((string[1] & 0x3f) << 24);
5976                 unicode += ((string[2] & 0x3f) << 18);
5977                 unicode += ((string[3] & 0x3f) << 12);
5978                 unicode += ((string[4] & 0x3f) << 6);
5979                 unicode +=  (string[5] & 0x3f);
5980                 break;
5981         default:
5982                 die("Invalid unicode length");
5983         }
5985         /* Invalid characters could return the special 0xfffd value but NUL
5986          * should be just as good. */
5987         return unicode > 0xffff ? 0 : unicode;
5990 /* Calculates how much of string can be shown within the given maximum width
5991  * and sets trimmed parameter to non-zero value if all of string could not be
5992  * shown. If the reserve flag is TRUE, it will reserve at least one
5993  * trailing character, which can be useful when drawing a delimiter.
5994  *
5995  * Returns the number of bytes to output from string to satisfy max_width. */
5996 static size_t
5997 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5999         const char *start = string;
6000         const char *end = strchr(string, '\0');
6001         unsigned char last_bytes = 0;
6002         size_t last_ucwidth = 0;
6004         *width = 0;
6005         *trimmed = 0;
6007         while (string < end) {
6008                 int c = *(unsigned char *) string;
6009                 unsigned char bytes = utf8_bytes[c];
6010                 size_t ucwidth;
6011                 unsigned long unicode;
6013                 if (string + bytes > end)
6014                         break;
6016                 /* Change representation to figure out whether
6017                  * it is a single- or double-width character. */
6019                 unicode = utf8_to_unicode(string, bytes);
6020                 /* FIXME: Graceful handling of invalid unicode character. */
6021                 if (!unicode)
6022                         break;
6024                 ucwidth = unicode_width(unicode);
6025                 *width  += ucwidth;
6026                 if (*width > max_width) {
6027                         *trimmed = 1;
6028                         *width -= ucwidth;
6029                         if (reserve && *width == max_width) {
6030                                 string -= last_bytes;
6031                                 *width -= last_ucwidth;
6032                         }
6033                         break;
6034                 }
6036                 string  += bytes;
6037                 last_bytes = bytes;
6038                 last_ucwidth = ucwidth;
6039         }
6041         return string - start;
6045 /*
6046  * Status management
6047  */
6049 /* Whether or not the curses interface has been initialized. */
6050 static bool cursed = FALSE;
6052 /* The status window is used for polling keystrokes. */
6053 static WINDOW *status_win;
6055 static bool status_empty = TRUE;
6057 /* Update status and title window. */
6058 static void
6059 report(const char *msg, ...)
6061         struct view *view = display[current_view];
6063         if (input_mode)
6064                 return;
6066         if (!view) {
6067                 char buf[SIZEOF_STR];
6068                 va_list args;
6070                 va_start(args, msg);
6071                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6072                         buf[sizeof(buf) - 1] = 0;
6073                         buf[sizeof(buf) - 2] = '.';
6074                         buf[sizeof(buf) - 3] = '.';
6075                         buf[sizeof(buf) - 4] = '.';
6076                 }
6077                 va_end(args);
6078                 die("%s", buf);
6079         }
6081         if (!status_empty || *msg) {
6082                 va_list args;
6084                 va_start(args, msg);
6086                 wmove(status_win, 0, 0);
6087                 if (*msg) {
6088                         vwprintw(status_win, msg, args);
6089                         status_empty = FALSE;
6090                 } else {
6091                         status_empty = TRUE;
6092                 }
6093                 wclrtoeol(status_win);
6094                 wrefresh(status_win);
6096                 va_end(args);
6097         }
6099         update_view_title(view);
6100         update_display_cursor(view);
6103 /* Controls when nodelay should be in effect when polling user input. */
6104 static void
6105 set_nonblocking_input(bool loading)
6107         static unsigned int loading_views;
6109         if ((loading == FALSE && loading_views-- == 1) ||
6110             (loading == TRUE  && loading_views++ == 0))
6111                 nodelay(status_win, loading);
6114 static void
6115 init_display(void)
6117         int x, y;
6119         /* Initialize the curses library */
6120         if (isatty(STDIN_FILENO)) {
6121                 cursed = !!initscr();
6122                 opt_tty = stdin;
6123         } else {
6124                 /* Leave stdin and stdout alone when acting as a pager. */
6125                 opt_tty = fopen("/dev/tty", "r+");
6126                 if (!opt_tty)
6127                         die("Failed to open /dev/tty");
6128                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6129         }
6131         if (!cursed)
6132                 die("Failed to initialize curses");
6134         nonl();         /* Tell curses not to do NL->CR/NL on output */
6135         cbreak();       /* Take input chars one at a time, no wait for \n */
6136         noecho();       /* Don't echo input */
6137         leaveok(stdscr, TRUE);
6139         if (has_colors())
6140                 init_colors();
6142         getmaxyx(stdscr, y, x);
6143         status_win = newwin(1, 0, y - 1, 0);
6144         if (!status_win)
6145                 die("Failed to create status window");
6147         /* Enable keyboard mapping */
6148         keypad(status_win, TRUE);
6149         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6151         TABSIZE = opt_tab_size;
6152         if (opt_line_graphics) {
6153                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6154         }
6157 static int
6158 get_input(bool prompting)
6160         struct view *view;
6161         int i, key;
6163         if (prompting)
6164                 input_mode = TRUE;
6166         while (true) {
6167                 foreach_view (view, i)
6168                         update_view(view);
6170                 /* Refresh, accept single keystroke of input */
6171                 key = wgetch(status_win);
6173                 /* wgetch() with nodelay() enabled returns ERR when
6174                  * there's no input. */
6175                 if (key == ERR) {
6176                         doupdate();
6178                 } else if (key == KEY_RESIZE) {
6179                         int height, width;
6181                         getmaxyx(stdscr, height, width);
6183                         /* Resize the status view and let the view driver take
6184                          * care of resizing the displayed views. */
6185                         resize_display();
6186                         redraw_display(TRUE);
6187                         wresize(status_win, 1, width);
6188                         mvwin(status_win, height - 1, 0);
6189                         wrefresh(status_win);
6191                 } else {
6192                         input_mode = FALSE;
6193                         return key;
6194                 }
6195         }
6198 static bool
6199 prompt_yesno(const char *prompt)
6201         enum { WAIT, STOP, CANCEL  } status = WAIT;
6202         bool answer = FALSE;
6204         while (status == WAIT) {
6205                 int key;
6207                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
6208                 wclrtoeol(status_win);
6210                 key = get_input(TRUE);
6211                 switch (key) {
6212                 case 'y':
6213                 case 'Y':
6214                         answer = TRUE;
6215                         status = STOP;
6216                         break;
6218                 case KEY_ESC:
6219                 case KEY_RETURN:
6220                 case KEY_ENTER:
6221                 case KEY_BACKSPACE:
6222                 case 'n':
6223                 case 'N':
6224                 case '\n':
6225                 default:
6226                         answer = FALSE;
6227                         status = CANCEL;
6228                 }
6229         }
6231         /* Clear the status window */
6232         status_empty = FALSE;
6233         report("");
6235         return answer;
6238 static char *
6239 read_prompt(const char *prompt)
6241         enum { READING, STOP, CANCEL } status = READING;
6242         static char buf[SIZEOF_STR];
6243         int pos = 0;
6245         while (status == READING) {
6246                 int key;
6248                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6249                 wclrtoeol(status_win);
6251                 key = get_input(TRUE);
6252                 switch (key) {
6253                 case KEY_RETURN:
6254                 case KEY_ENTER:
6255                 case '\n':
6256                         status = pos ? STOP : CANCEL;
6257                         break;
6259                 case KEY_BACKSPACE:
6260                         if (pos > 0)
6261                                 pos--;
6262                         else
6263                                 status = CANCEL;
6264                         break;
6266                 case KEY_ESC:
6267                         status = CANCEL;
6268                         break;
6270                 default:
6271                         if (pos >= sizeof(buf)) {
6272                                 report("Input string too long");
6273                                 return NULL;
6274                         }
6276                         if (isprint(key))
6277                                 buf[pos++] = (char) key;
6278                 }
6279         }
6281         /* Clear the status window */
6282         status_empty = FALSE;
6283         report("");
6285         if (status == CANCEL)
6286                 return NULL;
6288         buf[pos++] = 0;
6290         return buf;
6293 /*
6294  * Repository properties
6295  */
6297 static int
6298 git_properties(const char **argv, const char *separators,
6299                int (*read_property)(char *, size_t, char *, size_t))
6301         struct io io = {};
6303         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6304                 return read_properties(&io, separators, read_property);
6305         return ERR;
6308 static struct ref *refs = NULL;
6309 static size_t refs_alloc = 0;
6310 static size_t refs_size = 0;
6312 /* Id <-> ref store */
6313 static struct ref ***id_refs = NULL;
6314 static size_t id_refs_alloc = 0;
6315 static size_t id_refs_size = 0;
6317 static int
6318 compare_refs(const void *ref1_, const void *ref2_)
6320         const struct ref *ref1 = *(const struct ref **)ref1_;
6321         const struct ref *ref2 = *(const struct ref **)ref2_;
6323         if (ref1->tag != ref2->tag)
6324                 return ref2->tag - ref1->tag;
6325         if (ref1->ltag != ref2->ltag)
6326                 return ref2->ltag - ref2->ltag;
6327         if (ref1->head != ref2->head)
6328                 return ref2->head - ref1->head;
6329         if (ref1->tracked != ref2->tracked)
6330                 return ref2->tracked - ref1->tracked;
6331         if (ref1->remote != ref2->remote)
6332                 return ref2->remote - ref1->remote;
6333         return strcmp(ref1->name, ref2->name);
6336 static struct ref **
6337 get_refs(const char *id)
6339         struct ref ***tmp_id_refs;
6340         struct ref **ref_list = NULL;
6341         size_t ref_list_alloc = 0;
6342         size_t ref_list_size = 0;
6343         size_t i;
6345         for (i = 0; i < id_refs_size; i++)
6346                 if (!strcmp(id, id_refs[i][0]->id))
6347                         return id_refs[i];
6349         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6350                                     sizeof(*id_refs));
6351         if (!tmp_id_refs)
6352                 return NULL;
6354         id_refs = tmp_id_refs;
6356         for (i = 0; i < refs_size; i++) {
6357                 struct ref **tmp;
6359                 if (strcmp(id, refs[i].id))
6360                         continue;
6362                 tmp = realloc_items(ref_list, &ref_list_alloc,
6363                                     ref_list_size + 1, sizeof(*ref_list));
6364                 if (!tmp) {
6365                         if (ref_list)
6366                                 free(ref_list);
6367                         return NULL;
6368                 }
6370                 ref_list = tmp;
6371                 ref_list[ref_list_size] = &refs[i];
6372                 /* XXX: The properties of the commit chains ensures that we can
6373                  * safely modify the shared ref. The repo references will
6374                  * always be similar for the same id. */
6375                 ref_list[ref_list_size]->next = 1;
6377                 ref_list_size++;
6378         }
6380         if (ref_list) {
6381                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6382                 ref_list[ref_list_size - 1]->next = 0;
6383                 id_refs[id_refs_size++] = ref_list;
6384         }
6386         return ref_list;
6389 static int
6390 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6392         struct ref *ref;
6393         bool tag = FALSE;
6394         bool ltag = FALSE;
6395         bool remote = FALSE;
6396         bool tracked = FALSE;
6397         bool check_replace = FALSE;
6398         bool head = FALSE;
6400         if (!prefixcmp(name, "refs/tags/")) {
6401                 if (!suffixcmp(name, namelen, "^{}")) {
6402                         namelen -= 3;
6403                         name[namelen] = 0;
6404                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6405                                 check_replace = TRUE;
6406                 } else {
6407                         ltag = TRUE;
6408                 }
6410                 tag = TRUE;
6411                 namelen -= STRING_SIZE("refs/tags/");
6412                 name    += STRING_SIZE("refs/tags/");
6414         } else if (!prefixcmp(name, "refs/remotes/")) {
6415                 remote = TRUE;
6416                 namelen -= STRING_SIZE("refs/remotes/");
6417                 name    += STRING_SIZE("refs/remotes/");
6418                 tracked  = !strcmp(opt_remote, name);
6420         } else if (!prefixcmp(name, "refs/heads/")) {
6421                 namelen -= STRING_SIZE("refs/heads/");
6422                 name    += STRING_SIZE("refs/heads/");
6423                 head     = !strncmp(opt_head, name, namelen);
6425         } else if (!strcmp(name, "HEAD")) {
6426                 string_ncopy(opt_head_rev, id, idlen);
6427                 return OK;
6428         }
6430         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6431                 /* it's an annotated tag, replace the previous sha1 with the
6432                  * resolved commit id; relies on the fact git-ls-remote lists
6433                  * the commit id of an annotated tag right before the commit id
6434                  * it points to. */
6435                 refs[refs_size - 1].ltag = ltag;
6436                 string_copy_rev(refs[refs_size - 1].id, id);
6438                 return OK;
6439         }
6440         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6441         if (!refs)
6442                 return ERR;
6444         ref = &refs[refs_size++];
6445         ref->name = malloc(namelen + 1);
6446         if (!ref->name)
6447                 return ERR;
6449         strncpy(ref->name, name, namelen);
6450         ref->name[namelen] = 0;
6451         ref->head = head;
6452         ref->tag = tag;
6453         ref->ltag = ltag;
6454         ref->remote = remote;
6455         ref->tracked = tracked;
6456         string_copy_rev(ref->id, id);
6458         return OK;
6461 static int
6462 load_refs(void)
6464         static const char *ls_remote_argv[SIZEOF_ARG] = {
6465                 "git", "ls-remote", ".", NULL
6466         };
6467         static bool init = FALSE;
6469         if (!init) {
6470                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6471                 init = TRUE;
6472         }
6474         if (!*opt_git_dir)
6475                 return OK;
6477         while (refs_size > 0)
6478                 free(refs[--refs_size].name);
6479         while (id_refs_size > 0)
6480                 free(id_refs[--id_refs_size]);
6482         return git_properties(ls_remote_argv, "\t", read_ref);
6485 static int
6486 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6488         if (!strcmp(name, "i18n.commitencoding"))
6489                 string_ncopy(opt_encoding, value, valuelen);
6491         if (!strcmp(name, "core.editor"))
6492                 string_ncopy(opt_editor, value, valuelen);
6494         /* branch.<head>.remote */
6495         if (*opt_head &&
6496             !strncmp(name, "branch.", 7) &&
6497             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6498             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6499                 string_ncopy(opt_remote, value, valuelen);
6501         if (*opt_head && *opt_remote &&
6502             !strncmp(name, "branch.", 7) &&
6503             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6504             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6505                 size_t from = strlen(opt_remote);
6507                 if (!prefixcmp(value, "refs/heads/")) {
6508                         value += STRING_SIZE("refs/heads/");
6509                         valuelen -= STRING_SIZE("refs/heads/");
6510                 }
6512                 if (!string_format_from(opt_remote, &from, "/%s", value))
6513                         opt_remote[0] = 0;
6514         }
6516         return OK;
6519 static int
6520 load_git_config(void)
6522         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6524         return git_properties(config_list_argv, "=", read_repo_config_option);
6527 static int
6528 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6530         if (!opt_git_dir[0]) {
6531                 string_ncopy(opt_git_dir, name, namelen);
6533         } else if (opt_is_inside_work_tree == -1) {
6534                 /* This can be 3 different values depending on the
6535                  * version of git being used. If git-rev-parse does not
6536                  * understand --is-inside-work-tree it will simply echo
6537                  * the option else either "true" or "false" is printed.
6538                  * Default to true for the unknown case. */
6539                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6541         } else if (*name == '.') {
6542                 string_ncopy(opt_cdup, name, namelen);
6544         } else {
6545                 string_ncopy(opt_prefix, name, namelen);
6546         }
6548         return OK;
6551 static int
6552 load_repo_info(void)
6554         const char *head_argv[] = {
6555                 "git", "symbolic-ref", "HEAD", NULL
6556         };
6557         const char *rev_parse_argv[] = {
6558                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6559                         "--show-cdup", "--show-prefix", NULL
6560         };
6562         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6563                 chomp_string(opt_head);
6564                 if (!prefixcmp(opt_head, "refs/heads/")) {
6565                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6567                         memmove(opt_head, offset, strlen(offset) + 1);
6568                 }
6569         }
6571         return git_properties(rev_parse_argv, "=", read_repo_info);
6574 static int
6575 read_properties(struct io *io, const char *separators,
6576                 int (*read_property)(char *, size_t, char *, size_t))
6578         char *name;
6579         int state = OK;
6581         if (!start_io(io))
6582                 return ERR;
6584         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6585                 char *value;
6586                 size_t namelen;
6587                 size_t valuelen;
6589                 name = chomp_string(name);
6590                 namelen = strcspn(name, separators);
6592                 if (name[namelen]) {
6593                         name[namelen] = 0;
6594                         value = chomp_string(name + namelen + 1);
6595                         valuelen = strlen(value);
6597                 } else {
6598                         value = "";
6599                         valuelen = 0;
6600                 }
6602                 state = read_property(name, namelen, value, valuelen);
6603         }
6605         if (state != ERR && io_error(io))
6606                 state = ERR;
6607         done_io(io);
6609         return state;
6613 /*
6614  * Main
6615  */
6617 static void __NORETURN
6618 quit(int sig)
6620         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6621         if (cursed)
6622                 endwin();
6623         exit(0);
6626 static void __NORETURN
6627 die(const char *err, ...)
6629         va_list args;
6631         endwin();
6633         va_start(args, err);
6634         fputs("tig: ", stderr);
6635         vfprintf(stderr, err, args);
6636         fputs("\n", stderr);
6637         va_end(args);
6639         exit(1);
6642 static void
6643 warn(const char *msg, ...)
6645         va_list args;
6647         va_start(args, msg);
6648         fputs("tig warning: ", stderr);
6649         vfprintf(stderr, msg, args);
6650         fputs("\n", stderr);
6651         va_end(args);
6654 int
6655 main(int argc, const char *argv[])
6657         const char **run_argv = NULL;
6658         struct view *view;
6659         enum request request;
6660         size_t i;
6662         signal(SIGINT, quit);
6664         if (setlocale(LC_ALL, "")) {
6665                 char *codeset = nl_langinfo(CODESET);
6667                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6668         }
6670         if (load_repo_info() == ERR)
6671                 die("Failed to load repo info.");
6673         if (load_options() == ERR)
6674                 die("Failed to load user config.");
6676         if (load_git_config() == ERR)
6677                 die("Failed to load repo config.");
6679         request = parse_options(argc, argv, &run_argv);
6680         if (request == REQ_NONE)
6681                 return 0;
6683         /* Require a git repository unless when running in pager mode. */
6684         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6685                 die("Not a git repository");
6687         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6688                 opt_utf8 = FALSE;
6690         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6691                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6692                 if (opt_iconv == ICONV_NONE)
6693                         die("Failed to initialize character set conversion");
6694         }
6696         if (load_refs() == ERR)
6697                 die("Failed to load refs.");
6699         foreach_view (view, i)
6700                 argv_from_env(view->ops->argv, view->cmd_env);
6702         init_display();
6704         if (request == REQ_VIEW_PAGER || run_argv) {
6705                 if (request == REQ_VIEW_PAGER)
6706                         io_open(&VIEW(request)->io, "");
6707                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6708                         die("Failed to format arguments");
6709                 open_view(NULL, request, OPEN_PREPARED);
6710                 request = REQ_NONE;
6711         }
6713         while (view_driver(display[current_view], request)) {
6714                 int key = get_input(FALSE);
6716                 view = display[current_view];
6717                 request = get_keybinding(view->keymap, key);
6719                 /* Some low-level request handling. This keeps access to
6720                  * status_win restricted. */
6721                 switch (request) {
6722                 case REQ_PROMPT:
6723                 {
6724                         char *cmd = read_prompt(":");
6726                         if (cmd) {
6727                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6728                                 const char *argv[SIZEOF_ARG] = { "git" };
6729                                 int argc = 1;
6731                                 /* When running random commands, initially show the
6732                                  * command in the title. However, it maybe later be
6733                                  * overwritten if a commit line is selected. */
6734                                 string_ncopy(next->ref, cmd, strlen(cmd));
6736                                 if (!argv_from_string(argv, &argc, cmd)) {
6737                                         report("Too many arguments");
6738                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6739                                         report("Failed to format command");
6740                                 } else {
6741                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6742                                 }
6743                         }
6745                         request = REQ_NONE;
6746                         break;
6747                 }
6748                 case REQ_SEARCH:
6749                 case REQ_SEARCH_BACK:
6750                 {
6751                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6752                         char *search = read_prompt(prompt);
6754                         if (search)
6755                                 string_ncopy(opt_search, search, strlen(search));
6756                         else
6757                                 request = REQ_NONE;
6758                         break;
6759                 }
6760                 default:
6761                         break;
6762                 }
6763         }
6765         quit(0);
6767         return 0;