Code

Minor cleanup of the tree view code; fix enter on the first line
[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_(VIEW_NEXT,         "Move focus to next view"), \
670         REQ_(REFRESH,           "Reload and refresh"), \
671         REQ_(MAXIMIZE,          "Maximize the current view"), \
672         REQ_(VIEW_CLOSE,        "Close the current view"), \
673         REQ_(QUIT,              "Close all views and quit"), \
674         \
675         REQ_GROUP("View specific requests") \
676         REQ_(STATUS_UPDATE,     "Update file status"), \
677         REQ_(STATUS_REVERT,     "Revert file changes"), \
678         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
679         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
680         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
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_TREE_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                 const char *obsolete[] = { "cherry-pick", "screen-resize" };
1555                 size_t namelen = strlen(argv[2]);
1556                 int i;
1558                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1559                         if (namelen == strlen(obsolete[i]) &&
1560                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1561                                 config_msg = "Obsolete request name";
1562                                 return ERR;
1563                         }
1564                 }
1565         }
1566         if (request == REQ_NONE && *argv[2]++ == '!')
1567                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1568         if (request == REQ_NONE) {
1569                 config_msg = "Unknown request name";
1570                 return ERR;
1571         }
1573         add_keybinding(keymap, request, key);
1575         return OK;
1578 static int
1579 set_option(const char *opt, char *value)
1581         const char *argv[SIZEOF_ARG];
1582         int argc = 0;
1584         if (!argv_from_string(argv, &argc, value)) {
1585                 config_msg = "Too many option arguments";
1586                 return ERR;
1587         }
1589         if (!strcmp(opt, "color"))
1590                 return option_color_command(argc, argv);
1592         if (!strcmp(opt, "set"))
1593                 return option_set_command(argc, argv);
1595         if (!strcmp(opt, "bind"))
1596                 return option_bind_command(argc, argv);
1598         config_msg = "Unknown option command";
1599         return ERR;
1602 static int
1603 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1605         int status = OK;
1607         config_lineno++;
1608         config_msg = "Internal error";
1610         /* Check for comment markers, since read_properties() will
1611          * only ensure opt and value are split at first " \t". */
1612         optlen = strcspn(opt, "#");
1613         if (optlen == 0)
1614                 return OK;
1616         if (opt[optlen] != 0) {
1617                 config_msg = "No option value";
1618                 status = ERR;
1620         }  else {
1621                 /* Look for comment endings in the value. */
1622                 size_t len = strcspn(value, "#");
1624                 if (len < valuelen) {
1625                         valuelen = len;
1626                         value[valuelen] = 0;
1627                 }
1629                 status = set_option(opt, value);
1630         }
1632         if (status == ERR) {
1633                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1634                         config_lineno, (int) optlen, opt, config_msg);
1635                 config_errors = TRUE;
1636         }
1638         /* Always keep going if errors are encountered. */
1639         return OK;
1642 static void
1643 load_option_file(const char *path)
1645         struct io io = {};
1647         /* It's ok that the file doesn't exist. */
1648         if (!io_open(&io, path))
1649                 return;
1651         config_lineno = 0;
1652         config_errors = FALSE;
1654         if (read_properties(&io, " \t", read_option) == ERR ||
1655             config_errors == TRUE)
1656                 fprintf(stderr, "Errors while loading %s.\n", path);
1659 static int
1660 load_options(void)
1662         const char *home = getenv("HOME");
1663         const char *tigrc_user = getenv("TIGRC_USER");
1664         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1665         char buf[SIZEOF_STR];
1667         add_builtin_run_requests();
1669         if (!tigrc_system) {
1670                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1671                         return ERR;
1672                 tigrc_system = buf;
1673         }
1674         load_option_file(tigrc_system);
1676         if (!tigrc_user) {
1677                 if (!home || !string_format(buf, "%s/.tigrc", home))
1678                         return ERR;
1679                 tigrc_user = buf;
1680         }
1681         load_option_file(tigrc_user);
1683         return OK;
1687 /*
1688  * The viewer
1689  */
1691 struct view;
1692 struct view_ops;
1694 /* The display array of active views and the index of the current view. */
1695 static struct view *display[2];
1696 static unsigned int current_view;
1698 /* Reading from the prompt? */
1699 static bool input_mode = FALSE;
1701 #define foreach_displayed_view(view, i) \
1702         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1704 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1706 /* Current head and commit ID */
1707 static char ref_blob[SIZEOF_REF]        = "";
1708 static char ref_commit[SIZEOF_REF]      = "HEAD";
1709 static char ref_head[SIZEOF_REF]        = "HEAD";
1711 struct view {
1712         const char *name;       /* View name */
1713         const char *cmd_env;    /* Command line set via environment */
1714         const char *id;         /* Points to either of ref_{head,commit,blob} */
1716         struct view_ops *ops;   /* View operations */
1718         enum keymap keymap;     /* What keymap does this view have */
1719         bool git_dir;           /* Whether the view requires a git directory. */
1721         char ref[SIZEOF_REF];   /* Hovered commit reference */
1722         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1724         int height, width;      /* The width and height of the main window */
1725         WINDOW *win;            /* The main window */
1726         WINDOW *title;          /* The title window living below the main window */
1728         /* Navigation */
1729         unsigned long offset;   /* Offset of the window top */
1730         unsigned long lineno;   /* Current line number */
1731         unsigned long p_offset; /* Previous offset of the window top */
1732         unsigned long p_lineno; /* Previous current line number */
1733         bool p_restore;         /* Should the previous position be restored. */
1735         /* Searching */
1736         char grep[SIZEOF_STR];  /* Search string */
1737         regex_t *regex;         /* Pre-compiled regex */
1739         /* If non-NULL, points to the view that opened this view. If this view
1740          * is closed tig will switch back to the parent view. */
1741         struct view *parent;
1743         /* Buffering */
1744         size_t lines;           /* Total number of lines */
1745         struct line *line;      /* Line index */
1746         size_t line_alloc;      /* Total number of allocated lines */
1747         unsigned int digits;    /* Number of digits in the lines member. */
1749         /* Drawing */
1750         struct line *curline;   /* Line currently being drawn. */
1751         enum line_type curtype; /* Attribute currently used for drawing. */
1752         unsigned long col;      /* Column when drawing. */
1754         /* Loading */
1755         struct io io;
1756         struct io *pipe;
1757         time_t start_time;
1758         time_t update_secs;
1759 };
1761 struct view_ops {
1762         /* What type of content being displayed. Used in the title bar. */
1763         const char *type;
1764         /* Default command arguments. */
1765         const char **argv;
1766         /* Open and reads in all view content. */
1767         bool (*open)(struct view *view);
1768         /* Read one line; updates view->line. */
1769         bool (*read)(struct view *view, char *data);
1770         /* Draw one line; @lineno must be < view->height. */
1771         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1772         /* Depending on view handle a special requests. */
1773         enum request (*request)(struct view *view, enum request request, struct line *line);
1774         /* Search for regex in a line. */
1775         bool (*grep)(struct view *view, struct line *line);
1776         /* Select line */
1777         void (*select)(struct view *view, struct line *line);
1778 };
1780 static struct view_ops blame_ops;
1781 static struct view_ops blob_ops;
1782 static struct view_ops diff_ops;
1783 static struct view_ops help_ops;
1784 static struct view_ops log_ops;
1785 static struct view_ops main_ops;
1786 static struct view_ops pager_ops;
1787 static struct view_ops stage_ops;
1788 static struct view_ops status_ops;
1789 static struct view_ops tree_ops;
1791 #define VIEW_STR(name, env, ref, ops, map, git) \
1792         { name, #env, ref, ops, map, git }
1794 #define VIEW_(id, name, ops, git, ref) \
1795         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1798 static struct view views[] = {
1799         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1800         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1801         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1802         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1803         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1804         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1805         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1806         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1807         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1808         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1809 };
1811 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1812 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1814 #define foreach_view(view, i) \
1815         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1817 #define view_is_displayed(view) \
1818         (view == display[0] || view == display[1])
1821 enum line_graphic {
1822         LINE_GRAPHIC_VLINE
1823 };
1825 static int line_graphics[] = {
1826         /* LINE_GRAPHIC_VLINE: */ '|'
1827 };
1829 static inline void
1830 set_view_attr(struct view *view, enum line_type type)
1832         if (!view->curline->selected && view->curtype != type) {
1833                 wattrset(view->win, get_line_attr(type));
1834                 wchgat(view->win, -1, 0, type, NULL);
1835                 view->curtype = type;
1836         }
1839 static int
1840 draw_chars(struct view *view, enum line_type type, const char *string,
1841            int max_len, bool use_tilde)
1843         int len = 0;
1844         int col = 0;
1845         int trimmed = FALSE;
1847         if (max_len <= 0)
1848                 return 0;
1850         if (opt_utf8) {
1851                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1852         } else {
1853                 col = len = strlen(string);
1854                 if (len > max_len) {
1855                         if (use_tilde) {
1856                                 max_len -= 1;
1857                         }
1858                         col = len = max_len;
1859                         trimmed = TRUE;
1860                 }
1861         }
1863         set_view_attr(view, type);
1864         waddnstr(view->win, string, len);
1865         if (trimmed && use_tilde) {
1866                 set_view_attr(view, LINE_DELIMITER);
1867                 waddch(view->win, '~');
1868                 col++;
1869         }
1871         return col;
1874 static int
1875 draw_space(struct view *view, enum line_type type, int max, int spaces)
1877         static char space[] = "                    ";
1878         int col = 0;
1880         spaces = MIN(max, spaces);
1882         while (spaces > 0) {
1883                 int len = MIN(spaces, sizeof(space) - 1);
1885                 col += draw_chars(view, type, space, spaces, FALSE);
1886                 spaces -= len;
1887         }
1889         return col;
1892 static bool
1893 draw_lineno(struct view *view, unsigned int lineno)
1895         char number[10];
1896         int digits3 = view->digits < 3 ? 3 : view->digits;
1897         int max_number = MIN(digits3, STRING_SIZE(number));
1898         int max = view->width - view->col;
1899         int col;
1901         if (max < max_number)
1902                 max_number = max;
1904         lineno += view->offset + 1;
1905         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1906                 static char fmt[] = "%1ld";
1908                 if (view->digits <= 9)
1909                         fmt[1] = '0' + digits3;
1911                 if (!string_format(number, fmt, lineno))
1912                         number[0] = 0;
1913                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1914         } else {
1915                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1916         }
1918         if (col < max) {
1919                 set_view_attr(view, LINE_DEFAULT);
1920                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1921                 col++;
1922         }
1924         if (col < max)
1925                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1926         view->col += col;
1928         return view->width - view->col <= 0;
1931 static bool
1932 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1934         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1935         return view->width - view->col <= 0;
1938 static bool
1939 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1941         int max = view->width - view->col;
1942         int i;
1944         if (max < size)
1945                 size = max;
1947         set_view_attr(view, type);
1948         /* Using waddch() instead of waddnstr() ensures that
1949          * they'll be rendered correctly for the cursor line. */
1950         for (i = 0; i < size; i++)
1951                 waddch(view->win, graphic[i]);
1953         view->col += size;
1954         if (size < max) {
1955                 waddch(view->win, ' ');
1956                 view->col++;
1957         }
1959         return view->width - view->col <= 0;
1962 static bool
1963 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1965         int max = MIN(view->width - view->col, len);
1966         int col;
1968         if (text)
1969                 col = draw_chars(view, type, text, max - 1, trim);
1970         else
1971                 col = draw_space(view, type, max - 1, max - 1);
1973         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1974         return view->width - view->col <= 0;
1977 static bool
1978 draw_date(struct view *view, struct tm *time)
1980         char buf[DATE_COLS];
1981         char *date;
1982         int timelen = 0;
1984         if (time)
1985                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1986         date = timelen ? buf : NULL;
1988         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1991 static bool
1992 draw_view_line(struct view *view, unsigned int lineno)
1994         struct line *line;
1995         bool selected = (view->offset + lineno == view->lineno);
1996         bool draw_ok;
1998         assert(view_is_displayed(view));
2000         if (view->offset + lineno >= view->lines)
2001                 return FALSE;
2003         line = &view->line[view->offset + lineno];
2005         wmove(view->win, lineno, 0);
2006         if (line->cleareol)
2007                 wclrtoeol(view->win);
2008         view->col = 0;
2009         view->curline = line;
2010         view->curtype = LINE_NONE;
2011         line->selected = FALSE;
2012         line->dirty = line->cleareol = 0;
2014         if (selected) {
2015                 set_view_attr(view, LINE_CURSOR);
2016                 line->selected = TRUE;
2017                 view->ops->select(view, line);
2018         }
2020         scrollok(view->win, FALSE);
2021         draw_ok = view->ops->draw(view, line, lineno);
2022         scrollok(view->win, TRUE);
2024         return draw_ok;
2027 static void
2028 redraw_view_dirty(struct view *view)
2030         bool dirty = FALSE;
2031         int lineno;
2033         for (lineno = 0; lineno < view->height; lineno++) {
2034                 if (view->offset + lineno >= view->lines)
2035                         break;
2036                 if (!view->line[view->offset + lineno].dirty)
2037                         continue;
2038                 dirty = TRUE;
2039                 if (!draw_view_line(view, lineno))
2040                         break;
2041         }
2043         if (!dirty)
2044                 return;
2045         redrawwin(view->win);
2046         if (input_mode)
2047                 wnoutrefresh(view->win);
2048         else
2049                 wrefresh(view->win);
2052 static void
2053 redraw_view_from(struct view *view, int lineno)
2055         assert(0 <= lineno && lineno < view->height);
2057         for (; lineno < view->height; lineno++) {
2058                 if (!draw_view_line(view, lineno))
2059                         break;
2060         }
2062         redrawwin(view->win);
2063         if (input_mode)
2064                 wnoutrefresh(view->win);
2065         else
2066                 wrefresh(view->win);
2069 static void
2070 redraw_view(struct view *view)
2072         werase(view->win);
2073         redraw_view_from(view, 0);
2077 static void
2078 update_view_title(struct view *view)
2080         char buf[SIZEOF_STR];
2081         char state[SIZEOF_STR];
2082         size_t bufpos = 0, statelen = 0;
2084         assert(view_is_displayed(view));
2086         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2087                 unsigned int view_lines = view->offset + view->height;
2088                 unsigned int lines = view->lines
2089                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2090                                    : 0;
2092                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2093                                    view->ops->type,
2094                                    view->lineno + 1,
2095                                    view->lines,
2096                                    lines);
2098         }
2100         if (view->pipe) {
2101                 time_t secs = time(NULL) - view->start_time;
2103                 /* Three git seconds are a long time ... */
2104                 if (secs > 2)
2105                         string_format_from(state, &statelen, " loading %lds", secs);
2106         }
2108         string_format_from(buf, &bufpos, "[%s]", view->name);
2109         if (*view->ref && bufpos < view->width) {
2110                 size_t refsize = strlen(view->ref);
2111                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2113                 if (minsize < view->width)
2114                         refsize = view->width - minsize + 7;
2115                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2116         }
2118         if (statelen && bufpos < view->width) {
2119                 string_format_from(buf, &bufpos, "%s", state);
2120         }
2122         if (view == display[current_view])
2123                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2124         else
2125                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2127         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2128         wclrtoeol(view->title);
2129         wmove(view->title, 0, view->width - 1);
2131         if (input_mode)
2132                 wnoutrefresh(view->title);
2133         else
2134                 wrefresh(view->title);
2137 static void
2138 resize_display(void)
2140         int offset, i;
2141         struct view *base = display[0];
2142         struct view *view = display[1] ? display[1] : display[0];
2144         /* Setup window dimensions */
2146         getmaxyx(stdscr, base->height, base->width);
2148         /* Make room for the status window. */
2149         base->height -= 1;
2151         if (view != base) {
2152                 /* Horizontal split. */
2153                 view->width   = base->width;
2154                 view->height  = SCALE_SPLIT_VIEW(base->height);
2155                 base->height -= view->height;
2157                 /* Make room for the title bar. */
2158                 view->height -= 1;
2159         }
2161         /* Make room for the title bar. */
2162         base->height -= 1;
2164         offset = 0;
2166         foreach_displayed_view (view, i) {
2167                 if (!view->win) {
2168                         view->win = newwin(view->height, 0, offset, 0);
2169                         if (!view->win)
2170                                 die("Failed to create %s view", view->name);
2172                         scrollok(view->win, TRUE);
2174                         view->title = newwin(1, 0, offset + view->height, 0);
2175                         if (!view->title)
2176                                 die("Failed to create title window");
2178                 } else {
2179                         wresize(view->win, view->height, view->width);
2180                         mvwin(view->win,   offset, 0);
2181                         mvwin(view->title, offset + view->height, 0);
2182                 }
2184                 offset += view->height + 1;
2185         }
2188 static void
2189 redraw_display(bool clear)
2191         struct view *view;
2192         int i;
2194         foreach_displayed_view (view, i) {
2195                 if (clear)
2196                         wclear(view->win);
2197                 redraw_view(view);
2198                 update_view_title(view);
2199         }
2202 static void
2203 update_display_cursor(struct view *view)
2205         /* Move the cursor to the right-most column of the cursor line.
2206          *
2207          * XXX: This could turn out to be a bit expensive, but it ensures that
2208          * the cursor does not jump around. */
2209         if (view->lines) {
2210                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2211                 wrefresh(view->win);
2212         }
2215 static void
2216 toggle_view_option(bool *option, const char *help)
2218         *option = !*option;
2219         redraw_display(FALSE);
2220         report("%sabling %s", *option ? "En" : "Dis", help);
2223 /*
2224  * Navigation
2225  */
2227 /* Scrolling backend */
2228 static void
2229 do_scroll_view(struct view *view, int lines)
2231         bool redraw_current_line = FALSE;
2233         /* The rendering expects the new offset. */
2234         view->offset += lines;
2236         assert(0 <= view->offset && view->offset < view->lines);
2237         assert(lines);
2239         /* Move current line into the view. */
2240         if (view->lineno < view->offset) {
2241                 view->lineno = view->offset;
2242                 redraw_current_line = TRUE;
2243         } else if (view->lineno >= view->offset + view->height) {
2244                 view->lineno = view->offset + view->height - 1;
2245                 redraw_current_line = TRUE;
2246         }
2248         assert(view->offset <= view->lineno && view->lineno < view->lines);
2250         /* Redraw the whole screen if scrolling is pointless. */
2251         if (view->height < ABS(lines)) {
2252                 redraw_view(view);
2254         } else {
2255                 int line = lines > 0 ? view->height - lines : 0;
2256                 int end = line + ABS(lines);
2258                 wscrl(view->win, lines);
2260                 for (; line < end; line++) {
2261                         if (!draw_view_line(view, line))
2262                                 break;
2263                 }
2265                 if (redraw_current_line)
2266                         draw_view_line(view, view->lineno - view->offset);
2267         }
2269         redrawwin(view->win);
2270         wrefresh(view->win);
2271         report("");
2274 /* Scroll frontend */
2275 static void
2276 scroll_view(struct view *view, enum request request)
2278         int lines = 1;
2280         assert(view_is_displayed(view));
2282         switch (request) {
2283         case REQ_SCROLL_PAGE_DOWN:
2284                 lines = view->height;
2285         case REQ_SCROLL_LINE_DOWN:
2286                 if (view->offset + lines > view->lines)
2287                         lines = view->lines - view->offset;
2289                 if (lines == 0 || view->offset + view->height >= view->lines) {
2290                         report("Cannot scroll beyond the last line");
2291                         return;
2292                 }
2293                 break;
2295         case REQ_SCROLL_PAGE_UP:
2296                 lines = view->height;
2297         case REQ_SCROLL_LINE_UP:
2298                 if (lines > view->offset)
2299                         lines = view->offset;
2301                 if (lines == 0) {
2302                         report("Cannot scroll beyond the first line");
2303                         return;
2304                 }
2306                 lines = -lines;
2307                 break;
2309         default:
2310                 die("request %d not handled in switch", request);
2311         }
2313         do_scroll_view(view, lines);
2316 /* Cursor moving */
2317 static void
2318 move_view(struct view *view, enum request request)
2320         int scroll_steps = 0;
2321         int steps;
2323         switch (request) {
2324         case REQ_MOVE_FIRST_LINE:
2325                 steps = -view->lineno;
2326                 break;
2328         case REQ_MOVE_LAST_LINE:
2329                 steps = view->lines - view->lineno - 1;
2330                 break;
2332         case REQ_MOVE_PAGE_UP:
2333                 steps = view->height > view->lineno
2334                       ? -view->lineno : -view->height;
2335                 break;
2337         case REQ_MOVE_PAGE_DOWN:
2338                 steps = view->lineno + view->height >= view->lines
2339                       ? view->lines - view->lineno - 1 : view->height;
2340                 break;
2342         case REQ_MOVE_UP:
2343                 steps = -1;
2344                 break;
2346         case REQ_MOVE_DOWN:
2347                 steps = 1;
2348                 break;
2350         default:
2351                 die("request %d not handled in switch", request);
2352         }
2354         if (steps <= 0 && view->lineno == 0) {
2355                 report("Cannot move beyond the first line");
2356                 return;
2358         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2359                 report("Cannot move beyond the last line");
2360                 return;
2361         }
2363         /* Move the current line */
2364         view->lineno += steps;
2365         assert(0 <= view->lineno && view->lineno < view->lines);
2367         /* Check whether the view needs to be scrolled */
2368         if (view->lineno < view->offset ||
2369             view->lineno >= view->offset + view->height) {
2370                 scroll_steps = steps;
2371                 if (steps < 0 && -steps > view->offset) {
2372                         scroll_steps = -view->offset;
2374                 } else if (steps > 0) {
2375                         if (view->lineno == view->lines - 1 &&
2376                             view->lines > view->height) {
2377                                 scroll_steps = view->lines - view->offset - 1;
2378                                 if (scroll_steps >= view->height)
2379                                         scroll_steps -= view->height - 1;
2380                         }
2381                 }
2382         }
2384         if (!view_is_displayed(view)) {
2385                 view->offset += scroll_steps;
2386                 assert(0 <= view->offset && view->offset < view->lines);
2387                 view->ops->select(view, &view->line[view->lineno]);
2388                 return;
2389         }
2391         /* Repaint the old "current" line if we be scrolling */
2392         if (ABS(steps) < view->height)
2393                 draw_view_line(view, view->lineno - steps - view->offset);
2395         if (scroll_steps) {
2396                 do_scroll_view(view, scroll_steps);
2397                 return;
2398         }
2400         /* Draw the current line */
2401         draw_view_line(view, view->lineno - view->offset);
2403         redrawwin(view->win);
2404         wrefresh(view->win);
2405         report("");
2409 /*
2410  * Searching
2411  */
2413 static void search_view(struct view *view, enum request request);
2415 static void
2416 select_view_line(struct view *view, unsigned long lineno)
2418         if (lineno - view->offset >= view->height) {
2419                 view->offset = lineno;
2420                 view->lineno = lineno;
2421                 if (view_is_displayed(view))
2422                         redraw_view(view);
2424         } else {
2425                 unsigned long old_lineno = view->lineno - view->offset;
2427                 view->lineno = lineno;
2428                 if (view_is_displayed(view)) {
2429                         draw_view_line(view, old_lineno);
2430                         draw_view_line(view, view->lineno - view->offset);
2431                         redrawwin(view->win);
2432                         wrefresh(view->win);
2433                 } else {
2434                         view->ops->select(view, &view->line[view->lineno]);
2435                 }
2436         }
2439 static void
2440 find_next(struct view *view, enum request request)
2442         unsigned long lineno = view->lineno;
2443         int direction;
2445         if (!*view->grep) {
2446                 if (!*opt_search)
2447                         report("No previous search");
2448                 else
2449                         search_view(view, request);
2450                 return;
2451         }
2453         switch (request) {
2454         case REQ_SEARCH:
2455         case REQ_FIND_NEXT:
2456                 direction = 1;
2457                 break;
2459         case REQ_SEARCH_BACK:
2460         case REQ_FIND_PREV:
2461                 direction = -1;
2462                 break;
2464         default:
2465                 return;
2466         }
2468         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2469                 lineno += direction;
2471         /* Note, lineno is unsigned long so will wrap around in which case it
2472          * will become bigger than view->lines. */
2473         for (; lineno < view->lines; lineno += direction) {
2474                 if (view->ops->grep(view, &view->line[lineno])) {
2475                         select_view_line(view, lineno);
2476                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2477                         return;
2478                 }
2479         }
2481         report("No match found for '%s'", view->grep);
2484 static void
2485 search_view(struct view *view, enum request request)
2487         int regex_err;
2489         if (view->regex) {
2490                 regfree(view->regex);
2491                 *view->grep = 0;
2492         } else {
2493                 view->regex = calloc(1, sizeof(*view->regex));
2494                 if (!view->regex)
2495                         return;
2496         }
2498         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2499         if (regex_err != 0) {
2500                 char buf[SIZEOF_STR] = "unknown error";
2502                 regerror(regex_err, view->regex, buf, sizeof(buf));
2503                 report("Search failed: %s", buf);
2504                 return;
2505         }
2507         string_copy(view->grep, opt_search);
2509         find_next(view, request);
2512 /*
2513  * Incremental updating
2514  */
2516 static void
2517 reset_view(struct view *view)
2519         int i;
2521         for (i = 0; i < view->lines; i++)
2522                 free(view->line[i].data);
2523         free(view->line);
2525         view->p_offset = view->offset;
2526         view->p_lineno = view->lineno;
2528         view->line = NULL;
2529         view->offset = 0;
2530         view->lines  = 0;
2531         view->lineno = 0;
2532         view->line_alloc = 0;
2533         view->vid[0] = 0;
2534         view->update_secs = 0;
2537 static void
2538 free_argv(const char *argv[])
2540         int argc;
2542         for (argc = 0; argv[argc]; argc++)
2543                 free((void *) argv[argc]);
2546 static bool
2547 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2549         char buf[SIZEOF_STR];
2550         int argc;
2551         bool noreplace = flags == FORMAT_NONE;
2553         free_argv(dst_argv);
2555         for (argc = 0; src_argv[argc]; argc++) {
2556                 const char *arg = src_argv[argc];
2557                 size_t bufpos = 0;
2559                 while (arg) {
2560                         char *next = strstr(arg, "%(");
2561                         int len = next - arg;
2562                         const char *value;
2564                         if (!next || noreplace) {
2565                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2566                                         noreplace = TRUE;
2567                                 len = strlen(arg);
2568                                 value = "";
2570                         } else if (!prefixcmp(next, "%(directory)")) {
2571                                 value = opt_path;
2573                         } else if (!prefixcmp(next, "%(file)")) {
2574                                 value = opt_file;
2576                         } else if (!prefixcmp(next, "%(ref)")) {
2577                                 value = *opt_ref ? opt_ref : "HEAD";
2579                         } else if (!prefixcmp(next, "%(head)")) {
2580                                 value = ref_head;
2582                         } else if (!prefixcmp(next, "%(commit)")) {
2583                                 value = ref_commit;
2585                         } else if (!prefixcmp(next, "%(blob)")) {
2586                                 value = ref_blob;
2588                         } else {
2589                                 report("Unknown replacement: `%s`", next);
2590                                 return FALSE;
2591                         }
2593                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2594                                 return FALSE;
2596                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2597                 }
2599                 dst_argv[argc] = strdup(buf);
2600                 if (!dst_argv[argc])
2601                         break;
2602         }
2604         dst_argv[argc] = NULL;
2606         return src_argv[argc] == NULL;
2609 static bool
2610 restore_view_position(struct view *view)
2612         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2613                 return FALSE;
2615         /* Changing the view position cancels the restoring. */
2616         /* FIXME: Changing back to the first line is not detected. */
2617         if (view->offset != 0 || view->lineno != 0) {
2618                 view->p_restore = FALSE;
2619                 return FALSE;
2620         }
2622         if (view->p_lineno >= view->lines) {
2623                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2624                 if (view->p_offset >= view->p_lineno) {
2625                         unsigned long half = view->height / 2;
2627                         if (view->p_lineno > half)
2628                                 view->p_offset = view->p_lineno - half;
2629                         else
2630                                 view->p_offset = 0;
2631                 }
2632         }
2634         if (view_is_displayed(view) &&
2635             view->offset != view->p_offset &&
2636             view->lineno != view->p_lineno)
2637                 werase(view->win);
2639         view->offset = view->p_offset;
2640         view->lineno = view->p_lineno;
2641         view->p_restore = FALSE;
2643         return TRUE;
2646 static void
2647 end_update(struct view *view, bool force)
2649         if (!view->pipe)
2650                 return;
2651         while (!view->ops->read(view, NULL))
2652                 if (!force)
2653                         return;
2654         set_nonblocking_input(FALSE);
2655         if (force)
2656                 kill_io(view->pipe);
2657         done_io(view->pipe);
2658         view->pipe = NULL;
2661 static void
2662 setup_update(struct view *view, const char *vid)
2664         set_nonblocking_input(TRUE);
2665         reset_view(view);
2666         string_copy_rev(view->vid, vid);
2667         view->pipe = &view->io;
2668         view->start_time = time(NULL);
2671 static bool
2672 prepare_update(struct view *view, const char *argv[], const char *dir,
2673                enum format_flags flags)
2675         if (view->pipe)
2676                 end_update(view, TRUE);
2677         return init_io_rd(&view->io, argv, dir, flags);
2680 static bool
2681 prepare_update_file(struct view *view, const char *name)
2683         if (view->pipe)
2684                 end_update(view, TRUE);
2685         return io_open(&view->io, name);
2688 static bool
2689 begin_update(struct view *view, bool refresh)
2691         if (view->pipe)
2692                 end_update(view, TRUE);
2694         if (refresh) {
2695                 if (!start_io(&view->io))
2696                         return FALSE;
2698         } else {
2699                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2700                         opt_path[0] = 0;
2702                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2703                         return FALSE;
2705                 /* Put the current ref_* value to the view title ref
2706                  * member. This is needed by the blob view. Most other
2707                  * views sets it automatically after loading because the
2708                  * first line is a commit line. */
2709                 string_copy_rev(view->ref, view->id);
2710         }
2712         setup_update(view, view->id);
2714         return TRUE;
2717 #define ITEM_CHUNK_SIZE 256
2718 static void *
2719 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2721         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2722         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2724         if (mem == NULL || num_chunks != num_chunks_new) {
2725                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2726                 mem = realloc(mem, *size * item_size);
2727         }
2729         return mem;
2732 static struct line *
2733 realloc_lines(struct view *view, size_t line_size)
2735         size_t alloc = view->line_alloc;
2736         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2737                                          sizeof(*view->line));
2739         if (!tmp)
2740                 return NULL;
2742         view->line = tmp;
2743         view->line_alloc = alloc;
2744         return view->line;
2747 static bool
2748 update_view(struct view *view)
2750         char out_buffer[BUFSIZ * 2];
2751         char *line;
2752         /* Clear the view and redraw everything since the tree sorting
2753          * might have rearranged things. */
2754         bool redraw = view->lines == 0;
2755         bool can_read = TRUE;
2757         if (!view->pipe)
2758                 return TRUE;
2760         if (!io_can_read(view->pipe)) {
2761                 if (view->lines == 0) {
2762                         time_t secs = time(NULL) - view->start_time;
2764                         if (secs > view->update_secs) {
2765                                 if (view->update_secs == 0)
2766                                         redraw_view(view);
2767                                 update_view_title(view);
2768                                 view->update_secs = secs;
2769                         }
2770                 }
2771                 return TRUE;
2772         }
2774         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2775                 if (opt_iconv != ICONV_NONE) {
2776                         ICONV_CONST char *inbuf = line;
2777                         size_t inlen = strlen(line) + 1;
2779                         char *outbuf = out_buffer;
2780                         size_t outlen = sizeof(out_buffer);
2782                         size_t ret;
2784                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2785                         if (ret != (size_t) -1)
2786                                 line = out_buffer;
2787                 }
2789                 if (!view->ops->read(view, line)) {
2790                         report("Allocation failure");
2791                         end_update(view, TRUE);
2792                         return FALSE;
2793                 }
2794         }
2796         {
2797                 unsigned long lines = view->lines;
2798                 int digits;
2800                 for (digits = 0; lines; digits++)
2801                         lines /= 10;
2803                 /* Keep the displayed view in sync with line number scaling. */
2804                 if (digits != view->digits) {
2805                         view->digits = digits;
2806                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2807                                 redraw = TRUE;
2808                 }
2809         }
2811         if (io_error(view->pipe)) {
2812                 report("Failed to read: %s", io_strerror(view->pipe));
2813                 end_update(view, TRUE);
2815         } else if (io_eof(view->pipe)) {
2816                 report("");
2817                 end_update(view, FALSE);
2818         }
2820         if (restore_view_position(view))
2821                 redraw = TRUE;
2823         if (!view_is_displayed(view))
2824                 return TRUE;
2826         if (redraw)
2827                 redraw_view_from(view, 0);
2828         else
2829                 redraw_view_dirty(view);
2831         /* Update the title _after_ the redraw so that if the redraw picks up a
2832          * commit reference in view->ref it'll be available here. */
2833         update_view_title(view);
2834         return TRUE;
2837 static struct line *
2838 add_line_data(struct view *view, void *data, enum line_type type)
2840         struct line *line;
2842         if (!realloc_lines(view, view->lines + 1))
2843                 return NULL;
2845         line = &view->line[view->lines++];
2846         memset(line, 0, sizeof(*line));
2847         line->type = type;
2848         line->data = data;
2849         line->dirty = 1;
2851         return line;
2854 static struct line *
2855 add_line_text(struct view *view, const char *text, enum line_type type)
2857         char *data = text ? strdup(text) : NULL;
2859         return data ? add_line_data(view, data, type) : NULL;
2862 static struct line *
2863 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2865         char buf[SIZEOF_STR];
2866         va_list args;
2868         va_start(args, fmt);
2869         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2870                 buf[0] = 0;
2871         va_end(args);
2873         return buf[0] ? add_line_text(view, buf, type) : NULL;
2876 /*
2877  * View opening
2878  */
2880 enum open_flags {
2881         OPEN_DEFAULT = 0,       /* Use default view switching. */
2882         OPEN_SPLIT = 1,         /* Split current view. */
2883         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2884         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2885         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2886         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2887         OPEN_PREPARED = 32,     /* Open already prepared command. */
2888 };
2890 static void
2891 open_view(struct view *prev, enum request request, enum open_flags flags)
2893         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2894         bool split = !!(flags & OPEN_SPLIT);
2895         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2896         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2897         struct view *view = VIEW(request);
2898         int nviews = displayed_views();
2899         struct view *base_view = display[0];
2901         if (view == prev && nviews == 1 && !reload) {
2902                 report("Already in %s view", view->name);
2903                 return;
2904         }
2906         if (view->git_dir && !opt_git_dir[0]) {
2907                 report("The %s view is disabled in pager view", view->name);
2908                 return;
2909         }
2911         if (split) {
2912                 display[1] = view;
2913                 if (!backgrounded)
2914                         current_view = 1;
2915         } else if (!nomaximize) {
2916                 /* Maximize the current view. */
2917                 memset(display, 0, sizeof(display));
2918                 current_view = 0;
2919                 display[current_view] = view;
2920         }
2922         /* Resize the view when switching between split- and full-screen,
2923          * or when switching between two different full-screen views. */
2924         if (nviews != displayed_views() ||
2925             (nviews == 1 && base_view != display[0]))
2926                 resize_display();
2928         if (view->ops->open) {
2929                 if (view->pipe)
2930                         end_update(view, TRUE);
2931                 if (!view->ops->open(view)) {
2932                         report("Failed to load %s view", view->name);
2933                         return;
2934                 }
2935                 restore_view_position(view);
2937         } else if ((reload || strcmp(view->vid, view->id)) &&
2938                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2939                 report("Failed to load %s view", view->name);
2940                 return;
2941         }
2943         if (split && prev->lineno - prev->offset >= prev->height) {
2944                 /* Take the title line into account. */
2945                 int lines = prev->lineno - prev->offset - prev->height + 1;
2947                 /* Scroll the view that was split if the current line is
2948                  * outside the new limited view. */
2949                 do_scroll_view(prev, lines);
2950         }
2952         if (prev && view != prev) {
2953                 if (split && !backgrounded) {
2954                         /* "Blur" the previous view. */
2955                         update_view_title(prev);
2956                 }
2958                 view->parent = prev;
2959         }
2961         if (view->pipe && view->lines == 0) {
2962                 /* Clear the old view and let the incremental updating refill
2963                  * the screen. */
2964                 werase(view->win);
2965                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2966                 report("");
2967         } else if (view_is_displayed(view)) {
2968                 redraw_view(view);
2969                 report("");
2970         }
2972         /* If the view is backgrounded the above calls to report()
2973          * won't redraw the view title. */
2974         if (backgrounded)
2975                 update_view_title(view);
2978 static void
2979 open_external_viewer(const char *argv[], const char *dir)
2981         def_prog_mode();           /* save current tty modes */
2982         endwin();                  /* restore original tty modes */
2983         run_io_fg(argv, dir);
2984         fprintf(stderr, "Press Enter to continue");
2985         getc(opt_tty);
2986         reset_prog_mode();
2987         redraw_display(TRUE);
2990 static void
2991 open_mergetool(const char *file)
2993         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2995         open_external_viewer(mergetool_argv, opt_cdup);
2998 static void
2999 open_editor(bool from_root, const char *file)
3001         const char *editor_argv[] = { "vi", file, NULL };
3002         const char *editor;
3004         editor = getenv("GIT_EDITOR");
3005         if (!editor && *opt_editor)
3006                 editor = opt_editor;
3007         if (!editor)
3008                 editor = getenv("VISUAL");
3009         if (!editor)
3010                 editor = getenv("EDITOR");
3011         if (!editor)
3012                 editor = "vi";
3014         editor_argv[0] = editor;
3015         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3018 static void
3019 open_run_request(enum request request)
3021         struct run_request *req = get_run_request(request);
3022         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3024         if (!req) {
3025                 report("Unknown run request");
3026                 return;
3027         }
3029         if (format_argv(argv, req->argv, FORMAT_ALL))
3030                 open_external_viewer(argv, NULL);
3031         free_argv(argv);
3034 /*
3035  * User request switch noodle
3036  */
3038 static int
3039 view_driver(struct view *view, enum request request)
3041         int i;
3043         if (request == REQ_NONE) {
3044                 doupdate();
3045                 return TRUE;
3046         }
3048         if (request > REQ_NONE) {
3049                 open_run_request(request);
3050                 /* FIXME: When all views can refresh always do this. */
3051                 if (view == VIEW(REQ_VIEW_STATUS) ||
3052                     view == VIEW(REQ_VIEW_MAIN) ||
3053                     view == VIEW(REQ_VIEW_LOG) ||
3054                     view == VIEW(REQ_VIEW_STAGE))
3055                         request = REQ_REFRESH;
3056                 else
3057                         return TRUE;
3058         }
3060         if (view && view->lines) {
3061                 request = view->ops->request(view, request, &view->line[view->lineno]);
3062                 if (request == REQ_NONE)
3063                         return TRUE;
3064         }
3066         switch (request) {
3067         case REQ_MOVE_UP:
3068         case REQ_MOVE_DOWN:
3069         case REQ_MOVE_PAGE_UP:
3070         case REQ_MOVE_PAGE_DOWN:
3071         case REQ_MOVE_FIRST_LINE:
3072         case REQ_MOVE_LAST_LINE:
3073                 move_view(view, request);
3074                 break;
3076         case REQ_SCROLL_LINE_DOWN:
3077         case REQ_SCROLL_LINE_UP:
3078         case REQ_SCROLL_PAGE_DOWN:
3079         case REQ_SCROLL_PAGE_UP:
3080                 scroll_view(view, request);
3081                 break;
3083         case REQ_VIEW_BLAME:
3084                 if (!opt_file[0]) {
3085                         report("No file chosen, press %s to open tree view",
3086                                get_key(REQ_VIEW_TREE));
3087                         break;
3088                 }
3089                 open_view(view, request, OPEN_DEFAULT);
3090                 break;
3092         case REQ_VIEW_BLOB:
3093                 if (!ref_blob[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_PAGER:
3102                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3103                         report("No pager content, press %s to run command from prompt",
3104                                get_key(REQ_PROMPT));
3105                         break;
3106                 }
3107                 open_view(view, request, OPEN_DEFAULT);
3108                 break;
3110         case REQ_VIEW_STAGE:
3111                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3112                         report("No stage content, press %s to open the status view and choose file",
3113                                get_key(REQ_VIEW_STATUS));
3114                         break;
3115                 }
3116                 open_view(view, request, OPEN_DEFAULT);
3117                 break;
3119         case REQ_VIEW_STATUS:
3120                 if (opt_is_inside_work_tree == FALSE) {
3121                         report("The status view requires a working tree");
3122                         break;
3123                 }
3124                 open_view(view, request, OPEN_DEFAULT);
3125                 break;
3127         case REQ_VIEW_MAIN:
3128         case REQ_VIEW_DIFF:
3129         case REQ_VIEW_LOG:
3130         case REQ_VIEW_TREE:
3131         case REQ_VIEW_HELP:
3132                 open_view(view, request, OPEN_DEFAULT);
3133                 break;
3135         case REQ_NEXT:
3136         case REQ_PREVIOUS:
3137                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3139                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3140                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3141                    (view == VIEW(REQ_VIEW_DIFF) &&
3142                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3143                    (view == VIEW(REQ_VIEW_STAGE) &&
3144                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3145                    (view == VIEW(REQ_VIEW_BLOB) &&
3146                      view->parent == VIEW(REQ_VIEW_TREE))) {
3147                         int line;
3149                         view = view->parent;
3150                         line = view->lineno;
3151                         move_view(view, request);
3152                         if (view_is_displayed(view))
3153                                 update_view_title(view);
3154                         if (line != view->lineno)
3155                                 view->ops->request(view, REQ_ENTER,
3156                                                    &view->line[view->lineno]);
3158                 } else {
3159                         move_view(view, request);
3160                 }
3161                 break;
3163         case REQ_VIEW_NEXT:
3164         {
3165                 int nviews = displayed_views();
3166                 int next_view = (current_view + 1) % nviews;
3168                 if (next_view == current_view) {
3169                         report("Only one view is displayed");
3170                         break;
3171                 }
3173                 current_view = next_view;
3174                 /* Blur out the title of the previous view. */
3175                 update_view_title(view);
3176                 report("");
3177                 break;
3178         }
3179         case REQ_REFRESH:
3180                 report("Refreshing is not yet supported for the %s view", view->name);
3181                 break;
3183         case REQ_MAXIMIZE:
3184                 if (displayed_views() == 2)
3185                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3186                 break;
3188         case REQ_TOGGLE_LINENO:
3189                 toggle_view_option(&opt_line_number, "line numbers");
3190                 break;
3192         case REQ_TOGGLE_DATE:
3193                 toggle_view_option(&opt_date, "date display");
3194                 break;
3196         case REQ_TOGGLE_AUTHOR:
3197                 toggle_view_option(&opt_author, "author display");
3198                 break;
3200         case REQ_TOGGLE_REV_GRAPH:
3201                 toggle_view_option(&opt_rev_graph, "revision graph display");
3202                 break;
3204         case REQ_TOGGLE_REFS:
3205                 toggle_view_option(&opt_show_refs, "reference display");
3206                 break;
3208         case REQ_SEARCH:
3209         case REQ_SEARCH_BACK:
3210                 search_view(view, request);
3211                 break;
3213         case REQ_FIND_NEXT:
3214         case REQ_FIND_PREV:
3215                 find_next(view, request);
3216                 break;
3218         case REQ_STOP_LOADING:
3219                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3220                         view = &views[i];
3221                         if (view->pipe)
3222                                 report("Stopped loading the %s view", view->name),
3223                         end_update(view, TRUE);
3224                 }
3225                 break;
3227         case REQ_SHOW_VERSION:
3228                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3229                 return TRUE;
3231         case REQ_SCREEN_REDRAW:
3232                 redraw_display(TRUE);
3233                 break;
3235         case REQ_EDIT:
3236                 report("Nothing to edit");
3237                 break;
3239         case REQ_ENTER:
3240                 report("Nothing to enter");
3241                 break;
3243         case REQ_VIEW_CLOSE:
3244                 /* XXX: Mark closed views by letting view->parent point to the
3245                  * view itself. Parents to closed view should never be
3246                  * followed. */
3247                 if (view->parent &&
3248                     view->parent->parent != view->parent) {
3249                         memset(display, 0, sizeof(display));
3250                         current_view = 0;
3251                         display[current_view] = view->parent;
3252                         view->parent = view;
3253                         resize_display();
3254                         redraw_display(FALSE);
3255                         report("");
3256                         break;
3257                 }
3258                 /* Fall-through */
3259         case REQ_QUIT:
3260                 return FALSE;
3262         default:
3263                 report("Unknown key, press 'h' for help");
3264                 return TRUE;
3265         }
3267         return TRUE;
3271 /*
3272  * View backend utilities
3273  */
3275 /* Parse author lines where the name may be empty:
3276  *      author  <email@address.tld> 1138474660 +0100
3277  */
3278 static void
3279 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3281         char *nameend = strchr(ident, '<');
3282         char *emailend = strchr(ident, '>');
3284         if (nameend && emailend)
3285                 *nameend = *emailend = 0;
3286         ident = chomp_string(ident);
3287         if (!*ident) {
3288                 if (nameend)
3289                         ident = chomp_string(nameend + 1);
3290                 if (!*ident)
3291                         ident = "Unknown";
3292         }
3294         string_ncopy_do(author, authorsize, ident, strlen(ident));
3296         /* Parse epoch and timezone */
3297         if (emailend && emailend[1] == ' ') {
3298                 char *secs = emailend + 2;
3299                 char *zone = strchr(secs, ' ');
3300                 time_t time = (time_t) atol(secs);
3302                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3303                         long tz;
3305                         zone++;
3306                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3307                         tz += ('0' - zone[2]) * 60 * 60;
3308                         tz += ('0' - zone[3]) * 60;
3309                         tz += ('0' - zone[4]) * 60;
3311                         if (zone[0] == '-')
3312                                 tz = -tz;
3314                         time -= tz;
3315                 }
3317                 gmtime_r(&time, tm);
3318         }
3321 /*
3322  * Pager backend
3323  */
3325 static bool
3326 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3328         char *text = line->data;
3330         if (opt_line_number && draw_lineno(view, lineno))
3331                 return TRUE;
3333         draw_text(view, line->type, text, TRUE);
3334         return TRUE;
3337 static bool
3338 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3340         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3341         char refbuf[SIZEOF_STR];
3342         char *ref = NULL;
3344         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3345                 ref = chomp_string(refbuf);
3347         if (!ref || !*ref)
3348                 return TRUE;
3350         /* This is the only fatal call, since it can "corrupt" the buffer. */
3351         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3352                 return FALSE;
3354         return TRUE;
3357 static void
3358 add_pager_refs(struct view *view, struct line *line)
3360         char buf[SIZEOF_STR];
3361         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3362         struct ref **refs;
3363         size_t bufpos = 0, refpos = 0;
3364         const char *sep = "Refs: ";
3365         bool is_tag = FALSE;
3367         assert(line->type == LINE_COMMIT);
3369         refs = get_refs(commit_id);
3370         if (!refs) {
3371                 if (view == VIEW(REQ_VIEW_DIFF))
3372                         goto try_add_describe_ref;
3373                 return;
3374         }
3376         do {
3377                 struct ref *ref = refs[refpos];
3378                 const char *fmt = ref->tag    ? "%s[%s]" :
3379                                   ref->remote ? "%s<%s>" : "%s%s";
3381                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3382                         return;
3383                 sep = ", ";
3384                 if (ref->tag)
3385                         is_tag = TRUE;
3386         } while (refs[refpos++]->next);
3388         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3389 try_add_describe_ref:
3390                 /* Add <tag>-g<commit_id> "fake" reference. */
3391                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3392                         return;
3393         }
3395         if (bufpos == 0)
3396                 return;
3398         add_line_text(view, buf, LINE_PP_REFS);
3401 static bool
3402 pager_read(struct view *view, char *data)
3404         struct line *line;
3406         if (!data)
3407                 return TRUE;
3409         line = add_line_text(view, data, get_line_type(data));
3410         if (!line)
3411                 return FALSE;
3413         if (line->type == LINE_COMMIT &&
3414             (view == VIEW(REQ_VIEW_DIFF) ||
3415              view == VIEW(REQ_VIEW_LOG)))
3416                 add_pager_refs(view, line);
3418         return TRUE;
3421 static enum request
3422 pager_request(struct view *view, enum request request, struct line *line)
3424         int split = 0;
3426         if (request != REQ_ENTER)
3427                 return request;
3429         if (line->type == LINE_COMMIT &&
3430            (view == VIEW(REQ_VIEW_LOG) ||
3431             view == VIEW(REQ_VIEW_PAGER))) {
3432                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3433                 split = 1;
3434         }
3436         /* Always scroll the view even if it was split. That way
3437          * you can use Enter to scroll through the log view and
3438          * split open each commit diff. */
3439         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3441         /* FIXME: A minor workaround. Scrolling the view will call report("")
3442          * but if we are scrolling a non-current view this won't properly
3443          * update the view title. */
3444         if (split)
3445                 update_view_title(view);
3447         return REQ_NONE;
3450 static bool
3451 pager_grep(struct view *view, struct line *line)
3453         regmatch_t pmatch;
3454         char *text = line->data;
3456         if (!*text)
3457                 return FALSE;
3459         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3460                 return FALSE;
3462         return TRUE;
3465 static void
3466 pager_select(struct view *view, struct line *line)
3468         if (line->type == LINE_COMMIT) {
3469                 char *text = (char *)line->data + STRING_SIZE("commit ");
3471                 if (view != VIEW(REQ_VIEW_PAGER))
3472                         string_copy_rev(view->ref, text);
3473                 string_copy_rev(ref_commit, text);
3474         }
3477 static struct view_ops pager_ops = {
3478         "line",
3479         NULL,
3480         NULL,
3481         pager_read,
3482         pager_draw,
3483         pager_request,
3484         pager_grep,
3485         pager_select,
3486 };
3488 static const char *log_argv[SIZEOF_ARG] = {
3489         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3490 };
3492 static enum request
3493 log_request(struct view *view, enum request request, struct line *line)
3495         switch (request) {
3496         case REQ_REFRESH:
3497                 load_refs();
3498                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3499                 return REQ_NONE;
3500         default:
3501                 return pager_request(view, request, line);
3502         }
3505 static struct view_ops log_ops = {
3506         "line",
3507         log_argv,
3508         NULL,
3509         pager_read,
3510         pager_draw,
3511         log_request,
3512         pager_grep,
3513         pager_select,
3514 };
3516 static const char *diff_argv[SIZEOF_ARG] = {
3517         "git", "show", "--pretty=fuller", "--no-color", "--root",
3518                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3519 };
3521 static struct view_ops diff_ops = {
3522         "line",
3523         diff_argv,
3524         NULL,
3525         pager_read,
3526         pager_draw,
3527         pager_request,
3528         pager_grep,
3529         pager_select,
3530 };
3532 /*
3533  * Help backend
3534  */
3536 static bool
3537 help_open(struct view *view)
3539         int lines = ARRAY_SIZE(req_info) + 2;
3540         int i;
3542         if (view->lines > 0)
3543                 return TRUE;
3545         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3546                 if (!req_info[i].request)
3547                         lines++;
3549         lines += run_requests + 1;
3551         view->line = calloc(lines, sizeof(*view->line));
3552         if (!view->line)
3553                 return FALSE;
3555         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3557         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3558                 const char *key;
3560                 if (req_info[i].request == REQ_NONE)
3561                         continue;
3563                 if (!req_info[i].request) {
3564                         add_line_text(view, "", LINE_DEFAULT);
3565                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3566                         continue;
3567                 }
3569                 key = get_key(req_info[i].request);
3570                 if (!*key)
3571                         key = "(no key defined)";
3573                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3574                                 key, req_info[i].help);
3575         }
3577         if (run_requests) {
3578                 add_line_text(view, "", LINE_DEFAULT);
3579                 add_line_text(view, "External commands:", LINE_DEFAULT);
3580         }
3582         for (i = 0; i < run_requests; i++) {
3583                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3584                 const char *key;
3585                 char cmd[SIZEOF_STR];
3586                 size_t bufpos;
3587                 int argc;
3589                 if (!req)
3590                         continue;
3592                 key = get_key_name(req->key);
3593                 if (!*key)
3594                         key = "(no key defined)";
3596                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3597                         if (!string_format_from(cmd, &bufpos, "%s%s",
3598                                                 argc ? " " : "", req->argv[argc]))
3599                                 return REQ_NONE;
3601                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3602                                 keymap_table[req->keymap].name, key, cmd);
3603         }
3605         return TRUE;
3608 static struct view_ops help_ops = {
3609         "line",
3610         NULL,
3611         help_open,
3612         NULL,
3613         pager_draw,
3614         pager_request,
3615         pager_grep,
3616         pager_select,
3617 };
3620 /*
3621  * Tree backend
3622  */
3624 struct tree_stack_entry {
3625         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3626         unsigned long lineno;           /* Line number to restore */
3627         char *name;                     /* Position of name in opt_path */
3628 };
3630 /* The top of the path stack. */
3631 static struct tree_stack_entry *tree_stack = NULL;
3632 unsigned long tree_lineno = 0;
3634 static void
3635 pop_tree_stack_entry(void)
3637         struct tree_stack_entry *entry = tree_stack;
3639         tree_lineno = entry->lineno;
3640         entry->name[0] = 0;
3641         tree_stack = entry->prev;
3642         free(entry);
3645 static void
3646 push_tree_stack_entry(const char *name, unsigned long lineno)
3648         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3649         size_t pathlen = strlen(opt_path);
3651         if (!entry)
3652                 return;
3654         entry->prev = tree_stack;
3655         entry->name = opt_path + pathlen;
3656         tree_stack = entry;
3658         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3659                 pop_tree_stack_entry();
3660                 return;
3661         }
3663         /* Move the current line to the first tree entry. */
3664         tree_lineno = 1;
3665         entry->lineno = lineno;
3668 /* Parse output from git-ls-tree(1):
3669  *
3670  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3671  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3672  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3673  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3674  */
3676 #define SIZEOF_TREE_ATTR \
3677         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3679 #define SIZEOF_TREE_MODE \
3680         STRING_SIZE("100644 ")
3682 #define TREE_ID_OFFSET \
3683         STRING_SIZE("100644 blob ")
3685 struct tree_entry {
3686         char id[SIZEOF_REV];
3687         mode_t mode;
3688         struct tm time;                 /* Date from the author ident. */
3689         char author[75];                /* Author of the commit. */
3690         char name[1];
3691 };
3693 static const char *
3694 tree_path(struct line *line)
3696         return ((struct tree_entry *) line->data)->name;
3700 static int
3701 tree_compare_entry(struct line *line1, struct line *line2)
3703         if (line1->type != line2->type)
3704                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3705         return strcmp(tree_path(line1), tree_path(line2));
3708 static struct line *
3709 tree_entry(struct view *view, enum line_type type, const char *path,
3710            const char *mode, const char *id)
3712         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3713         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3715         if (!entry || !line) {
3716                 free(entry);
3717                 return NULL;
3718         }
3720         strncpy(entry->name, path, strlen(path));
3721         if (mode)
3722                 entry->mode = strtoul(mode, NULL, 8);
3723         if (id)
3724                 string_copy_rev(entry->id, id);
3726         return line;
3729 static bool
3730 tree_read_date(struct view *view, char *text, bool *read_date)
3732         static char author_name[SIZEOF_STR];
3733         static struct tm author_time;
3735         if (!text && *read_date) {
3736                 *read_date = FALSE;
3737                 return TRUE;
3739         } else if (!text) {
3740                 char *path = *opt_path ? opt_path : ".";
3741                 /* Find next entry to process */
3742                 const char *log_file[] = {
3743                         "git", "log", "--no-color", "--pretty=raw",
3744                                 "--cc", "--raw", view->id, "--", path, NULL
3745                 };
3746                 struct io io = {};
3748                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3749                         report("Failed to load tree data");
3750                         return TRUE;
3751                 }
3753                 done_io(view->pipe);
3754                 view->io = io;
3755                 *read_date = TRUE;
3756                 return FALSE;
3758         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3759                 parse_author_line(text + STRING_SIZE("author "),
3760                                   author_name, sizeof(author_name), &author_time);
3762         } else if (*text == ':') {
3763                 char *pos;
3764                 size_t annotated = 1;
3765                 size_t i;
3767                 pos = strchr(text, '\t');
3768                 if (!pos)
3769                         return TRUE;
3770                 text = pos + 1;
3771                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3772                         text += strlen(opt_prefix);
3773                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3774                         text += strlen(opt_path);
3775                 pos = strchr(text, '/');
3776                 if (pos)
3777                         *pos = 0;
3779                 for (i = 1; i < view->lines; i++) {
3780                         struct line *line = &view->line[i];
3781                         struct tree_entry *entry = line->data;
3783                         annotated += !!*entry->author;
3784                         if (*entry->author || strcmp(entry->name, text))
3785                                 continue;
3787                         string_copy(entry->author, author_name);
3788                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3789                         line->dirty = 1;
3790                         break;
3791                 }
3793                 if (annotated == view->lines)
3794                         kill_io(view->pipe);
3795         }
3796         return TRUE;
3799 static bool
3800 tree_read(struct view *view, char *text)
3802         static bool read_date = FALSE;
3803         struct tree_entry *data;
3804         struct line *entry, *line;
3805         enum line_type type;
3806         size_t textlen = text ? strlen(text) : 0;
3807         char *path = text + SIZEOF_TREE_ATTR;
3809         if (read_date || !text)
3810                 return tree_read_date(view, text, &read_date);
3812         if (textlen <= SIZEOF_TREE_ATTR)
3813                 return FALSE;
3814         if (view->lines == 0 &&
3815             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3816                 return FALSE;
3818         /* Strip the path part ... */
3819         if (*opt_path) {
3820                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3821                 size_t striplen = strlen(opt_path);
3823                 if (pathlen > striplen)
3824                         memmove(path, path + striplen,
3825                                 pathlen - striplen + 1);
3827                 /* Insert "link" to parent directory. */
3828                 if (view->lines == 1 &&
3829                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3830                         return FALSE;
3831         }
3833         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3834         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3835         if (!entry)
3836                 return FALSE;
3837         data = entry->data;
3839         /* Skip "Directory ..." and ".." line. */
3840         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3841                 if (tree_compare_entry(line, entry) <= 0)
3842                         continue;
3844                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3846                 line->data = data;
3847                 line->type = type;
3848                 for (; line <= entry; line++)
3849                         line->dirty = line->cleareol = 1;
3850                 return TRUE;
3851         }
3853         if (tree_lineno > view->lineno) {
3854                 view->lineno = tree_lineno;
3855                 tree_lineno = 0;
3856         }
3858         return TRUE;
3861 static bool
3862 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3864         struct tree_entry *entry = line->data;
3866         if (line->type == LINE_TREE_PARENT) {
3867                 if (draw_text(view, line->type, "Directory path /", TRUE))
3868                         return TRUE;
3869         } else {
3870                 char mode[11] = "-r--r--r--";
3872                 if (S_ISDIR(entry->mode)) {
3873                         mode[3] = mode[6] = mode[9] = 'x';
3874                         mode[0] = 'd';
3875                 }
3876                 if (S_ISLNK(entry->mode))
3877                         mode[0] = 'l';
3878                 if (entry->mode & S_IWUSR)
3879                         mode[2] = 'w';
3880                 if (entry->mode & S_IXUSR)
3881                         mode[3] = 'x';
3882                 if (entry->mode & S_IXGRP)
3883                         mode[6] = 'x';
3884                 if (entry->mode & S_IXOTH)
3885                         mode[9] = 'x';
3886                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3887                         return TRUE;
3889                 if (opt_author &&
3890                     draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3891                         return TRUE;
3893                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3894                         return TRUE;
3895         }
3896         if (draw_text(view, line->type, entry->name, TRUE))
3897                 return TRUE;
3898         return TRUE;
3901 static void
3902 open_blob_editor()
3904         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3905         int fd = mkstemp(file);
3907         if (fd == -1)
3908                 report("Failed to create temporary file");
3909         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3910                 report("Failed to save blob data to file");
3911         else
3912                 open_editor(FALSE, file);
3913         if (fd != -1)
3914                 unlink(file);
3917 static enum request
3918 tree_request(struct view *view, enum request request, struct line *line)
3920         enum open_flags flags;
3922         switch (request) {
3923         case REQ_VIEW_BLAME:
3924                 if (line->type != LINE_TREE_FILE) {
3925                         report("Blame only supported for files");
3926                         return REQ_NONE;
3927                 }
3929                 string_copy(opt_ref, view->vid);
3930                 return request;
3932         case REQ_EDIT:
3933                 if (line->type != LINE_TREE_FILE) {
3934                         report("Edit only supported for files");
3935                 } else if (!is_head_commit(view->vid)) {
3936                         open_blob_editor();
3937                 } else {
3938                         open_editor(TRUE, opt_file);
3939                 }
3940                 return REQ_NONE;
3942         case REQ_TREE_PARENT:
3943                 if (!*opt_path) {
3944                         /* quit view if at top of tree */
3945                         return REQ_VIEW_CLOSE;
3946                 }
3947                 /* fake 'cd  ..' */
3948                 line = &view->line[1];
3949                 break;
3951         case REQ_ENTER:
3952                 break;
3954         default:
3955                 return request;
3956         }
3958         /* Cleanup the stack if the tree view is at a different tree. */
3959         while (!*opt_path && tree_stack)
3960                 pop_tree_stack_entry();
3962         switch (line->type) {
3963         case LINE_TREE_DIR:
3964                 /* Depending on whether it is a subdir or parent (updir?) link
3965                  * mangle the path buffer. */
3966                 if (line == &view->line[1] && *opt_path) {
3967                         pop_tree_stack_entry();
3969                 } else {
3970                         const char *basename = tree_path(line);
3972                         push_tree_stack_entry(basename, view->lineno);
3973                 }
3975                 /* Trees and subtrees share the same ID, so they are not not
3976                  * unique like blobs. */
3977                 flags = OPEN_RELOAD;
3978                 request = REQ_VIEW_TREE;
3979                 break;
3981         case LINE_TREE_FILE:
3982                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3983                 request = REQ_VIEW_BLOB;
3984                 break;
3986         default:
3987                 return REQ_NONE;
3988         }
3990         open_view(view, request, flags);
3991         if (request == REQ_VIEW_TREE)
3992                 view->lineno = tree_lineno;
3994         return REQ_NONE;
3997 static void
3998 tree_select(struct view *view, struct line *line)
4000         struct tree_entry *entry = line->data;
4002         if (line->type == LINE_TREE_FILE) {
4003                 string_copy_rev(ref_blob, entry->id);
4004                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4006         } else if (line->type != LINE_TREE_DIR) {
4007                 return;
4008         }
4010         string_copy_rev(view->ref, entry->id);
4013 static const char *tree_argv[SIZEOF_ARG] = {
4014         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4015 };
4017 static struct view_ops tree_ops = {
4018         "file",
4019         tree_argv,
4020         NULL,
4021         tree_read,
4022         tree_draw,
4023         tree_request,
4024         pager_grep,
4025         tree_select,
4026 };
4028 static bool
4029 blob_read(struct view *view, char *line)
4031         if (!line)
4032                 return TRUE;
4033         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4036 static enum request
4037 blob_request(struct view *view, enum request request, struct line *line)
4039         switch (request) {
4040         case REQ_EDIT:
4041                 open_blob_editor();
4042                 return REQ_NONE;
4043         default:
4044                 return pager_request(view, request, line);
4045         }
4048 static const char *blob_argv[SIZEOF_ARG] = {
4049         "git", "cat-file", "blob", "%(blob)", NULL
4050 };
4052 static struct view_ops blob_ops = {
4053         "line",
4054         blob_argv,
4055         NULL,
4056         blob_read,
4057         pager_draw,
4058         blob_request,
4059         pager_grep,
4060         pager_select,
4061 };
4063 /*
4064  * Blame backend
4065  *
4066  * Loading the blame view is a two phase job:
4067  *
4068  *  1. File content is read either using opt_file from the
4069  *     filesystem or using git-cat-file.
4070  *  2. Then blame information is incrementally added by
4071  *     reading output from git-blame.
4072  */
4074 static const char *blame_head_argv[] = {
4075         "git", "blame", "--incremental", "--", "%(file)", NULL
4076 };
4078 static const char *blame_ref_argv[] = {
4079         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4080 };
4082 static const char *blame_cat_file_argv[] = {
4083         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4084 };
4086 struct blame_commit {
4087         char id[SIZEOF_REV];            /* SHA1 ID. */
4088         char title[128];                /* First line of the commit message. */
4089         char author[75];                /* Author of the commit. */
4090         struct tm time;                 /* Date from the author ident. */
4091         char filename[128];             /* Name of file. */
4092         bool has_previous;              /* Was a "previous" line detected. */
4093 };
4095 struct blame {
4096         struct blame_commit *commit;
4097         char text[1];
4098 };
4100 static bool
4101 blame_open(struct view *view)
4103         if (*opt_ref || !io_open(&view->io, opt_file)) {
4104                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4105                         return FALSE;
4106         }
4108         setup_update(view, opt_file);
4109         string_format(view->ref, "%s ...", opt_file);
4111         return TRUE;
4114 static struct blame_commit *
4115 get_blame_commit(struct view *view, const char *id)
4117         size_t i;
4119         for (i = 0; i < view->lines; i++) {
4120                 struct blame *blame = view->line[i].data;
4122                 if (!blame->commit)
4123                         continue;
4125                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4126                         return blame->commit;
4127         }
4129         {
4130                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4132                 if (commit)
4133                         string_ncopy(commit->id, id, SIZEOF_REV);
4134                 return commit;
4135         }
4138 static bool
4139 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4141         const char *pos = *posref;
4143         *posref = NULL;
4144         pos = strchr(pos + 1, ' ');
4145         if (!pos || !isdigit(pos[1]))
4146                 return FALSE;
4147         *number = atoi(pos + 1);
4148         if (*number < min || *number > max)
4149                 return FALSE;
4151         *posref = pos;
4152         return TRUE;
4155 static struct blame_commit *
4156 parse_blame_commit(struct view *view, const char *text, int *blamed)
4158         struct blame_commit *commit;
4159         struct blame *blame;
4160         const char *pos = text + SIZEOF_REV - 1;
4161         size_t lineno;
4162         size_t group;
4164         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4165                 return NULL;
4167         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4168             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4169                 return NULL;
4171         commit = get_blame_commit(view, text);
4172         if (!commit)
4173                 return NULL;
4175         *blamed += group;
4176         while (group--) {
4177                 struct line *line = &view->line[lineno + group - 1];
4179                 blame = line->data;
4180                 blame->commit = commit;
4181                 line->dirty = 1;
4182         }
4184         return commit;
4187 static bool
4188 blame_read_file(struct view *view, const char *line, bool *read_file)
4190         if (!line) {
4191                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4192                 struct io io = {};
4194                 if (view->lines == 0 && !view->parent)
4195                         die("No blame exist for %s", view->vid);
4197                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4198                         report("Failed to load blame data");
4199                         return TRUE;
4200                 }
4202                 done_io(view->pipe);
4203                 view->io = io;
4204                 *read_file = FALSE;
4205                 return FALSE;
4207         } else {
4208                 size_t linelen = strlen(line);
4209                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4211                 blame->commit = NULL;
4212                 strncpy(blame->text, line, linelen);
4213                 blame->text[linelen] = 0;
4214                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4215         }
4218 static bool
4219 match_blame_header(const char *name, char **line)
4221         size_t namelen = strlen(name);
4222         bool matched = !strncmp(name, *line, namelen);
4224         if (matched)
4225                 *line += namelen;
4227         return matched;
4230 static bool
4231 blame_read(struct view *view, char *line)
4233         static struct blame_commit *commit = NULL;
4234         static int blamed = 0;
4235         static time_t author_time;
4236         static bool read_file = TRUE;
4238         if (read_file)
4239                 return blame_read_file(view, line, &read_file);
4241         if (!line) {
4242                 /* Reset all! */
4243                 commit = NULL;
4244                 blamed = 0;
4245                 read_file = TRUE;
4246                 string_format(view->ref, "%s", view->vid);
4247                 if (view_is_displayed(view)) {
4248                         update_view_title(view);
4249                         redraw_view_from(view, 0);
4250                 }
4251                 return TRUE;
4252         }
4254         if (!commit) {
4255                 commit = parse_blame_commit(view, line, &blamed);
4256                 string_format(view->ref, "%s %2d%%", view->vid,
4257                               view->lines ? blamed * 100 / view->lines : 0);
4259         } else if (match_blame_header("author ", &line)) {
4260                 string_ncopy(commit->author, line, strlen(line));
4262         } else if (match_blame_header("author-time ", &line)) {
4263                 author_time = (time_t) atol(line);
4265         } else if (match_blame_header("author-tz ", &line)) {
4266                 long tz;
4268                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4269                 tz += ('0' - line[2]) * 60 * 60;
4270                 tz += ('0' - line[3]) * 60;
4271                 tz += ('0' - line[4]) * 60;
4273                 if (line[0] == '-')
4274                         tz = -tz;
4276                 author_time -= tz;
4277                 gmtime_r(&author_time, &commit->time);
4279         } else if (match_blame_header("summary ", &line)) {
4280                 string_ncopy(commit->title, line, strlen(line));
4282         } else if (match_blame_header("previous ", &line)) {
4283                 commit->has_previous = TRUE;
4285         } else if (match_blame_header("filename ", &line)) {
4286                 string_ncopy(commit->filename, line, strlen(line));
4287                 commit = NULL;
4288         }
4290         return TRUE;
4293 static bool
4294 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4296         struct blame *blame = line->data;
4297         struct tm *time = NULL;
4298         const char *id = NULL, *author = NULL;
4300         if (blame->commit && *blame->commit->filename) {
4301                 id = blame->commit->id;
4302                 author = blame->commit->author;
4303                 time = &blame->commit->time;
4304         }
4306         if (opt_date && draw_date(view, time))
4307                 return TRUE;
4309         if (opt_author &&
4310             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4311                 return TRUE;
4313         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4314                 return TRUE;
4316         if (draw_lineno(view, lineno))
4317                 return TRUE;
4319         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4320         return TRUE;
4323 static bool
4324 check_blame_commit(struct blame *blame)
4326         if (!blame->commit)
4327                 report("Commit data not loaded yet");
4328         else if (!strcmp(blame->commit->id, NULL_ID))
4329                 report("No commit exist for the selected line");
4330         else
4331                 return TRUE;
4332         return FALSE;
4335 static enum request
4336 blame_request(struct view *view, enum request request, struct line *line)
4338         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4339         struct blame *blame = line->data;
4341         switch (request) {
4342         case REQ_VIEW_BLAME:
4343                 if (check_blame_commit(blame)) {
4344                         string_copy(opt_ref, blame->commit->id);
4345                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4346                 }
4347                 break;
4349         case REQ_ENTER:
4350                 if (!blame->commit) {
4351                         report("No commit loaded yet");
4352                         break;
4353                 }
4355                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4356                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4357                         break;
4359                 if (!strcmp(blame->commit->id, NULL_ID)) {
4360                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4361                         const char *diff_index_argv[] = {
4362                                 "git", "diff-index", "--root", "--patch-with-stat",
4363                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4364                         };
4366                         if (!blame->commit->has_previous) {
4367                                 diff_index_argv[1] = "diff";
4368                                 diff_index_argv[2] = "--no-color";
4369                                 diff_index_argv[6] = "--";
4370                                 diff_index_argv[7] = "/dev/null";
4371                         }
4373                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4374                                 report("Failed to allocate diff command");
4375                                 break;
4376                         }
4377                         flags |= OPEN_PREPARED;
4378                 }
4380                 open_view(view, REQ_VIEW_DIFF, flags);
4381                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4382                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4383                 break;
4385         default:
4386                 return request;
4387         }
4389         return REQ_NONE;
4392 static bool
4393 blame_grep(struct view *view, struct line *line)
4395         struct blame *blame = line->data;
4396         struct blame_commit *commit = blame->commit;
4397         regmatch_t pmatch;
4399 #define MATCH(text, on)                                                 \
4400         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4402         if (commit) {
4403                 char buf[DATE_COLS + 1];
4405                 if (MATCH(commit->title, 1) ||
4406                     MATCH(commit->author, opt_author) ||
4407                     MATCH(commit->id, opt_date))
4408                         return TRUE;
4410                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4411                     MATCH(buf, 1))
4412                         return TRUE;
4413         }
4415         return MATCH(blame->text, 1);
4417 #undef MATCH
4420 static void
4421 blame_select(struct view *view, struct line *line)
4423         struct blame *blame = line->data;
4424         struct blame_commit *commit = blame->commit;
4426         if (!commit)
4427                 return;
4429         if (!strcmp(commit->id, NULL_ID))
4430                 string_ncopy(ref_commit, "HEAD", 4);
4431         else
4432                 string_copy_rev(ref_commit, commit->id);
4435 static struct view_ops blame_ops = {
4436         "line",
4437         NULL,
4438         blame_open,
4439         blame_read,
4440         blame_draw,
4441         blame_request,
4442         blame_grep,
4443         blame_select,
4444 };
4446 /*
4447  * Status backend
4448  */
4450 struct status {
4451         char status;
4452         struct {
4453                 mode_t mode;
4454                 char rev[SIZEOF_REV];
4455                 char name[SIZEOF_STR];
4456         } old;
4457         struct {
4458                 mode_t mode;
4459                 char rev[SIZEOF_REV];
4460                 char name[SIZEOF_STR];
4461         } new;
4462 };
4464 static char status_onbranch[SIZEOF_STR];
4465 static struct status stage_status;
4466 static enum line_type stage_line_type;
4467 static size_t stage_chunks;
4468 static int *stage_chunk;
4470 /* This should work even for the "On branch" line. */
4471 static inline bool
4472 status_has_none(struct view *view, struct line *line)
4474         return line < view->line + view->lines && !line[1].data;
4477 /* Get fields from the diff line:
4478  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4479  */
4480 static inline bool
4481 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4483         const char *old_mode = buf +  1;
4484         const char *new_mode = buf +  8;
4485         const char *old_rev  = buf + 15;
4486         const char *new_rev  = buf + 56;
4487         const char *status   = buf + 97;
4489         if (bufsize < 98 ||
4490             old_mode[-1] != ':' ||
4491             new_mode[-1] != ' ' ||
4492             old_rev[-1]  != ' ' ||
4493             new_rev[-1]  != ' ' ||
4494             status[-1]   != ' ')
4495                 return FALSE;
4497         file->status = *status;
4499         string_copy_rev(file->old.rev, old_rev);
4500         string_copy_rev(file->new.rev, new_rev);
4502         file->old.mode = strtoul(old_mode, NULL, 8);
4503         file->new.mode = strtoul(new_mode, NULL, 8);
4505         file->old.name[0] = file->new.name[0] = 0;
4507         return TRUE;
4510 static bool
4511 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4513         struct status *file = NULL;
4514         struct status *unmerged = NULL;
4515         char *buf;
4516         struct io io = {};
4518         if (!run_io(&io, argv, NULL, IO_RD))
4519                 return FALSE;
4521         add_line_data(view, NULL, type);
4523         while ((buf = io_get(&io, 0, TRUE))) {
4524                 if (!file) {
4525                         file = calloc(1, sizeof(*file));
4526                         if (!file || !add_line_data(view, file, type))
4527                                 goto error_out;
4528                 }
4530                 /* Parse diff info part. */
4531                 if (status) {
4532                         file->status = status;
4533                         if (status == 'A')
4534                                 string_copy(file->old.rev, NULL_ID);
4536                 } else if (!file->status) {
4537                         if (!status_get_diff(file, buf, strlen(buf)))
4538                                 goto error_out;
4540                         buf = io_get(&io, 0, TRUE);
4541                         if (!buf)
4542                                 break;
4544                         /* Collapse all 'M'odified entries that follow a
4545                          * associated 'U'nmerged entry. */
4546                         if (file->status == 'U') {
4547                                 unmerged = file;
4549                         } else if (unmerged) {
4550                                 int collapse = !strcmp(buf, unmerged->new.name);
4552                                 unmerged = NULL;
4553                                 if (collapse) {
4554                                         free(file);
4555                                         file = NULL;
4556                                         view->lines--;
4557                                         continue;
4558                                 }
4559                         }
4560                 }
4562                 /* Grab the old name for rename/copy. */
4563                 if (!*file->old.name &&
4564                     (file->status == 'R' || file->status == 'C')) {
4565                         string_ncopy(file->old.name, buf, strlen(buf));
4567                         buf = io_get(&io, 0, TRUE);
4568                         if (!buf)
4569                                 break;
4570                 }
4572                 /* git-ls-files just delivers a NUL separated list of
4573                  * file names similar to the second half of the
4574                  * git-diff-* output. */
4575                 string_ncopy(file->new.name, buf, strlen(buf));
4576                 if (!*file->old.name)
4577                         string_copy(file->old.name, file->new.name);
4578                 file = NULL;
4579         }
4581         if (io_error(&io)) {
4582 error_out:
4583                 done_io(&io);
4584                 return FALSE;
4585         }
4587         if (!view->line[view->lines - 1].data)
4588                 add_line_data(view, NULL, LINE_STAT_NONE);
4590         done_io(&io);
4591         return TRUE;
4594 /* Don't show unmerged entries in the staged section. */
4595 static const char *status_diff_index_argv[] = {
4596         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4597                              "--cached", "-M", "HEAD", NULL
4598 };
4600 static const char *status_diff_files_argv[] = {
4601         "git", "diff-files", "-z", NULL
4602 };
4604 static const char *status_list_other_argv[] = {
4605         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4606 };
4608 static const char *status_list_no_head_argv[] = {
4609         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4610 };
4612 static const char *update_index_argv[] = {
4613         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4614 };
4616 /* Restore the previous line number to stay in the context or select a
4617  * line with something that can be updated. */
4618 static void
4619 status_restore(struct view *view)
4621         if (view->p_lineno >= view->lines)
4622                 view->p_lineno = view->lines - 1;
4623         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4624                 view->p_lineno++;
4625         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4626                 view->p_lineno--;
4628         /* If the above fails, always skip the "On branch" line. */
4629         if (view->p_lineno < view->lines)
4630                 view->lineno = view->p_lineno;
4631         else
4632                 view->lineno = 1;
4634         if (view->lineno < view->offset)
4635                 view->offset = view->lineno;
4636         else if (view->offset + view->height <= view->lineno)
4637                 view->offset = view->lineno - view->height + 1;
4639         view->p_restore = FALSE;
4642 /* First parse staged info using git-diff-index(1), then parse unstaged
4643  * info using git-diff-files(1), and finally untracked files using
4644  * git-ls-files(1). */
4645 static bool
4646 status_open(struct view *view)
4648         reset_view(view);
4650         add_line_data(view, NULL, LINE_STAT_HEAD);
4651         if (is_initial_commit())
4652                 string_copy(status_onbranch, "Initial commit");
4653         else if (!*opt_head)
4654                 string_copy(status_onbranch, "Not currently on any branch");
4655         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4656                 return FALSE;
4658         run_io_bg(update_index_argv);
4660         if (is_initial_commit()) {
4661                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4662                         return FALSE;
4663         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4664                 return FALSE;
4665         }
4667         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4668             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4669                 return FALSE;
4671         /* Restore the exact position or use the specialized restore
4672          * mode? */
4673         if (!view->p_restore)
4674                 status_restore(view);
4675         return TRUE;
4678 static bool
4679 status_draw(struct view *view, struct line *line, unsigned int lineno)
4681         struct status *status = line->data;
4682         enum line_type type;
4683         const char *text;
4685         if (!status) {
4686                 switch (line->type) {
4687                 case LINE_STAT_STAGED:
4688                         type = LINE_STAT_SECTION;
4689                         text = "Changes to be committed:";
4690                         break;
4692                 case LINE_STAT_UNSTAGED:
4693                         type = LINE_STAT_SECTION;
4694                         text = "Changed but not updated:";
4695                         break;
4697                 case LINE_STAT_UNTRACKED:
4698                         type = LINE_STAT_SECTION;
4699                         text = "Untracked files:";
4700                         break;
4702                 case LINE_STAT_NONE:
4703                         type = LINE_DEFAULT;
4704                         text = "    (no files)";
4705                         break;
4707                 case LINE_STAT_HEAD:
4708                         type = LINE_STAT_HEAD;
4709                         text = status_onbranch;
4710                         break;
4712                 default:
4713                         return FALSE;
4714                 }
4715         } else {
4716                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4718                 buf[0] = status->status;
4719                 if (draw_text(view, line->type, buf, TRUE))
4720                         return TRUE;
4721                 type = LINE_DEFAULT;
4722                 text = status->new.name;
4723         }
4725         draw_text(view, type, text, TRUE);
4726         return TRUE;
4729 static enum request
4730 status_enter(struct view *view, struct line *line)
4732         struct status *status = line->data;
4733         const char *oldpath = status ? status->old.name : NULL;
4734         /* Diffs for unmerged entries are empty when passing the new
4735          * path, so leave it empty. */
4736         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4737         const char *info;
4738         enum open_flags split;
4739         struct view *stage = VIEW(REQ_VIEW_STAGE);
4741         if (line->type == LINE_STAT_NONE ||
4742             (!status && line[1].type == LINE_STAT_NONE)) {
4743                 report("No file to diff");
4744                 return REQ_NONE;
4745         }
4747         switch (line->type) {
4748         case LINE_STAT_STAGED:
4749                 if (is_initial_commit()) {
4750                         const char *no_head_diff_argv[] = {
4751                                 "git", "diff", "--no-color", "--patch-with-stat",
4752                                         "--", "/dev/null", newpath, NULL
4753                         };
4755                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4756                                 return REQ_QUIT;
4757                 } else {
4758                         const char *index_show_argv[] = {
4759                                 "git", "diff-index", "--root", "--patch-with-stat",
4760                                         "-C", "-M", "--cached", "HEAD", "--",
4761                                         oldpath, newpath, NULL
4762                         };
4764                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4765                                 return REQ_QUIT;
4766                 }
4768                 if (status)
4769                         info = "Staged changes to %s";
4770                 else
4771                         info = "Staged changes";
4772                 break;
4774         case LINE_STAT_UNSTAGED:
4775         {
4776                 const char *files_show_argv[] = {
4777                         "git", "diff-files", "--root", "--patch-with-stat",
4778                                 "-C", "-M", "--", oldpath, newpath, NULL
4779                 };
4781                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4782                         return REQ_QUIT;
4783                 if (status)
4784                         info = "Unstaged changes to %s";
4785                 else
4786                         info = "Unstaged changes";
4787                 break;
4788         }
4789         case LINE_STAT_UNTRACKED:
4790                 if (!newpath) {
4791                         report("No file to show");
4792                         return REQ_NONE;
4793                 }
4795                 if (!suffixcmp(status->new.name, -1, "/")) {
4796                         report("Cannot display a directory");
4797                         return REQ_NONE;
4798                 }
4800                 if (!prepare_update_file(stage, newpath))
4801                         return REQ_QUIT;
4802                 info = "Untracked file %s";
4803                 break;
4805         case LINE_STAT_HEAD:
4806                 return REQ_NONE;
4808         default:
4809                 die("line type %d not handled in switch", line->type);
4810         }
4812         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4813         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4814         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4815                 if (status) {
4816                         stage_status = *status;
4817                 } else {
4818                         memset(&stage_status, 0, sizeof(stage_status));
4819                 }
4821                 stage_line_type = line->type;
4822                 stage_chunks = 0;
4823                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4824         }
4826         return REQ_NONE;
4829 static bool
4830 status_exists(struct status *status, enum line_type type)
4832         struct view *view = VIEW(REQ_VIEW_STATUS);
4833         unsigned long lineno;
4835         for (lineno = 0; lineno < view->lines; lineno++) {
4836                 struct line *line = &view->line[lineno];
4837                 struct status *pos = line->data;
4839                 if (line->type != type)
4840                         continue;
4841                 if (!pos && (!status || !status->status) && line[1].data) {
4842                         select_view_line(view, lineno);
4843                         return TRUE;
4844                 }
4845                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4846                         select_view_line(view, lineno);
4847                         return TRUE;
4848                 }
4849         }
4851         return FALSE;
4855 static bool
4856 status_update_prepare(struct io *io, enum line_type type)
4858         const char *staged_argv[] = {
4859                 "git", "update-index", "-z", "--index-info", NULL
4860         };
4861         const char *others_argv[] = {
4862                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4863         };
4865         switch (type) {
4866         case LINE_STAT_STAGED:
4867                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4869         case LINE_STAT_UNSTAGED:
4870                 return run_io(io, others_argv, opt_cdup, IO_WR);
4872         case LINE_STAT_UNTRACKED:
4873                 return run_io(io, others_argv, NULL, IO_WR);
4875         default:
4876                 die("line type %d not handled in switch", type);
4877                 return FALSE;
4878         }
4881 static bool
4882 status_update_write(struct io *io, struct status *status, enum line_type type)
4884         char buf[SIZEOF_STR];
4885         size_t bufsize = 0;
4887         switch (type) {
4888         case LINE_STAT_STAGED:
4889                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4890                                         status->old.mode,
4891                                         status->old.rev,
4892                                         status->old.name, 0))
4893                         return FALSE;
4894                 break;
4896         case LINE_STAT_UNSTAGED:
4897         case LINE_STAT_UNTRACKED:
4898                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4899                         return FALSE;
4900                 break;
4902         default:
4903                 die("line type %d not handled in switch", type);
4904         }
4906         return io_write(io, buf, bufsize);
4909 static bool
4910 status_update_file(struct status *status, enum line_type type)
4912         struct io io = {};
4913         bool result;
4915         if (!status_update_prepare(&io, type))
4916                 return FALSE;
4918         result = status_update_write(&io, status, type);
4919         done_io(&io);
4920         return result;
4923 static bool
4924 status_update_files(struct view *view, struct line *line)
4926         struct io io = {};
4927         bool result = TRUE;
4928         struct line *pos = view->line + view->lines;
4929         int files = 0;
4930         int file, done;
4932         if (!status_update_prepare(&io, line->type))
4933                 return FALSE;
4935         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4936                 files++;
4938         for (file = 0, done = 0; result && file < files; line++, file++) {
4939                 int almost_done = file * 100 / files;
4941                 if (almost_done > done) {
4942                         done = almost_done;
4943                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4944                                       file, files, done);
4945                         update_view_title(view);
4946                 }
4947                 result = status_update_write(&io, line->data, line->type);
4948         }
4950         done_io(&io);
4951         return result;
4954 static bool
4955 status_update(struct view *view)
4957         struct line *line = &view->line[view->lineno];
4959         assert(view->lines);
4961         if (!line->data) {
4962                 /* This should work even for the "On branch" line. */
4963                 if (line < view->line + view->lines && !line[1].data) {
4964                         report("Nothing to update");
4965                         return FALSE;
4966                 }
4968                 if (!status_update_files(view, line + 1)) {
4969                         report("Failed to update file status");
4970                         return FALSE;
4971                 }
4973         } else if (!status_update_file(line->data, line->type)) {
4974                 report("Failed to update file status");
4975                 return FALSE;
4976         }
4978         return TRUE;
4981 static bool
4982 status_revert(struct status *status, enum line_type type, bool has_none)
4984         if (!status || type != LINE_STAT_UNSTAGED) {
4985                 if (type == LINE_STAT_STAGED) {
4986                         report("Cannot revert changes to staged files");
4987                 } else if (type == LINE_STAT_UNTRACKED) {
4988                         report("Cannot revert changes to untracked files");
4989                 } else if (has_none) {
4990                         report("Nothing to revert");
4991                 } else {
4992                         report("Cannot revert changes to multiple files");
4993                 }
4994                 return FALSE;
4996         } else {
4997                 const char *checkout_argv[] = {
4998                         "git", "checkout", "--", status->old.name, NULL
4999                 };
5001                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5002                         return FALSE;
5003                 return run_io_fg(checkout_argv, opt_cdup);
5004         }
5007 static enum request
5008 status_request(struct view *view, enum request request, struct line *line)
5010         struct status *status = line->data;
5012         switch (request) {
5013         case REQ_STATUS_UPDATE:
5014                 if (!status_update(view))
5015                         return REQ_NONE;
5016                 break;
5018         case REQ_STATUS_REVERT:
5019                 if (!status_revert(status, line->type, status_has_none(view, line)))
5020                         return REQ_NONE;
5021                 break;
5023         case REQ_STATUS_MERGE:
5024                 if (!status || status->status != 'U') {
5025                         report("Merging only possible for files with unmerged status ('U').");
5026                         return REQ_NONE;
5027                 }
5028                 open_mergetool(status->new.name);
5029                 break;
5031         case REQ_EDIT:
5032                 if (!status)
5033                         return request;
5034                 if (status->status == 'D') {
5035                         report("File has been deleted.");
5036                         return REQ_NONE;
5037                 }
5039                 open_editor(status->status != '?', status->new.name);
5040                 break;
5042         case REQ_VIEW_BLAME:
5043                 if (status) {
5044                         string_copy(opt_file, status->new.name);
5045                         opt_ref[0] = 0;
5046                 }
5047                 return request;
5049         case REQ_ENTER:
5050                 /* After returning the status view has been split to
5051                  * show the stage view. No further reloading is
5052                  * necessary. */
5053                 status_enter(view, line);
5054                 return REQ_NONE;
5056         case REQ_REFRESH:
5057                 /* Simply reload the view. */
5058                 break;
5060         default:
5061                 return request;
5062         }
5064         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5066         return REQ_NONE;
5069 static void
5070 status_select(struct view *view, struct line *line)
5072         struct status *status = line->data;
5073         char file[SIZEOF_STR] = "all files";
5074         const char *text;
5075         const char *key;
5077         if (status && !string_format(file, "'%s'", status->new.name))
5078                 return;
5080         if (!status && line[1].type == LINE_STAT_NONE)
5081                 line++;
5083         switch (line->type) {
5084         case LINE_STAT_STAGED:
5085                 text = "Press %s to unstage %s for commit";
5086                 break;
5088         case LINE_STAT_UNSTAGED:
5089                 text = "Press %s to stage %s for commit";
5090                 break;
5092         case LINE_STAT_UNTRACKED:
5093                 text = "Press %s to stage %s for addition";
5094                 break;
5096         case LINE_STAT_HEAD:
5097         case LINE_STAT_NONE:
5098                 text = "Nothing to update";
5099                 break;
5101         default:
5102                 die("line type %d not handled in switch", line->type);
5103         }
5105         if (status && status->status == 'U') {
5106                 text = "Press %s to resolve conflict in %s";
5107                 key = get_key(REQ_STATUS_MERGE);
5109         } else {
5110                 key = get_key(REQ_STATUS_UPDATE);
5111         }
5113         string_format(view->ref, text, key, file);
5116 static bool
5117 status_grep(struct view *view, struct line *line)
5119         struct status *status = line->data;
5120         enum { S_STATUS, S_NAME, S_END } state;
5121         char buf[2] = "?";
5122         regmatch_t pmatch;
5124         if (!status)
5125                 return FALSE;
5127         for (state = S_STATUS; state < S_END; state++) {
5128                 const char *text;
5130                 switch (state) {
5131                 case S_NAME:    text = status->new.name;        break;
5132                 case S_STATUS:
5133                         buf[0] = status->status;
5134                         text = buf;
5135                         break;
5137                 default:
5138                         return FALSE;
5139                 }
5141                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5142                         return TRUE;
5143         }
5145         return FALSE;
5148 static struct view_ops status_ops = {
5149         "file",
5150         NULL,
5151         status_open,
5152         NULL,
5153         status_draw,
5154         status_request,
5155         status_grep,
5156         status_select,
5157 };
5160 static bool
5161 stage_diff_write(struct io *io, struct line *line, struct line *end)
5163         while (line < end) {
5164                 if (!io_write(io, line->data, strlen(line->data)) ||
5165                     !io_write(io, "\n", 1))
5166                         return FALSE;
5167                 line++;
5168                 if (line->type == LINE_DIFF_CHUNK ||
5169                     line->type == LINE_DIFF_HEADER)
5170                         break;
5171         }
5173         return TRUE;
5176 static struct line *
5177 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5179         for (; view->line < line; line--)
5180                 if (line->type == type)
5181                         return line;
5183         return NULL;
5186 static bool
5187 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5189         const char *apply_argv[SIZEOF_ARG] = {
5190                 "git", "apply", "--whitespace=nowarn", NULL
5191         };
5192         struct line *diff_hdr;
5193         struct io io = {};
5194         int argc = 3;
5196         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5197         if (!diff_hdr)
5198                 return FALSE;
5200         if (!revert)
5201                 apply_argv[argc++] = "--cached";
5202         if (revert || stage_line_type == LINE_STAT_STAGED)
5203                 apply_argv[argc++] = "-R";
5204         apply_argv[argc++] = "-";
5205         apply_argv[argc++] = NULL;
5206         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5207                 return FALSE;
5209         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5210             !stage_diff_write(&io, chunk, view->line + view->lines))
5211                 chunk = NULL;
5213         done_io(&io);
5214         run_io_bg(update_index_argv);
5216         return chunk ? TRUE : FALSE;
5219 static bool
5220 stage_update(struct view *view, struct line *line)
5222         struct line *chunk = NULL;
5224         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5225                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5227         if (chunk) {
5228                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5229                         report("Failed to apply chunk");
5230                         return FALSE;
5231                 }
5233         } else if (!stage_status.status) {
5234                 view = VIEW(REQ_VIEW_STATUS);
5236                 for (line = view->line; line < view->line + view->lines; line++)
5237                         if (line->type == stage_line_type)
5238                                 break;
5240                 if (!status_update_files(view, line + 1)) {
5241                         report("Failed to update files");
5242                         return FALSE;
5243                 }
5245         } else if (!status_update_file(&stage_status, stage_line_type)) {
5246                 report("Failed to update file");
5247                 return FALSE;
5248         }
5250         return TRUE;
5253 static bool
5254 stage_revert(struct view *view, struct line *line)
5256         struct line *chunk = NULL;
5258         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5259                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5261         if (chunk) {
5262                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5263                         return FALSE;
5265                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5266                         report("Failed to revert chunk");
5267                         return FALSE;
5268                 }
5269                 return TRUE;
5271         } else {
5272                 return status_revert(stage_status.status ? &stage_status : NULL,
5273                                      stage_line_type, FALSE);
5274         }
5278 static void
5279 stage_next(struct view *view, struct line *line)
5281         int i;
5283         if (!stage_chunks) {
5284                 static size_t alloc = 0;
5285                 int *tmp;
5287                 for (line = view->line; line < view->line + view->lines; line++) {
5288                         if (line->type != LINE_DIFF_CHUNK)
5289                                 continue;
5291                         tmp = realloc_items(stage_chunk, &alloc,
5292                                             stage_chunks, sizeof(*tmp));
5293                         if (!tmp) {
5294                                 report("Allocation failure");
5295                                 return;
5296                         }
5298                         stage_chunk = tmp;
5299                         stage_chunk[stage_chunks++] = line - view->line;
5300                 }
5301         }
5303         for (i = 0; i < stage_chunks; i++) {
5304                 if (stage_chunk[i] > view->lineno) {
5305                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5306                         report("Chunk %d of %d", i + 1, stage_chunks);
5307                         return;
5308                 }
5309         }
5311         report("No next chunk found");
5314 static enum request
5315 stage_request(struct view *view, enum request request, struct line *line)
5317         switch (request) {
5318         case REQ_STATUS_UPDATE:
5319                 if (!stage_update(view, line))
5320                         return REQ_NONE;
5321                 break;
5323         case REQ_STATUS_REVERT:
5324                 if (!stage_revert(view, line))
5325                         return REQ_NONE;
5326                 break;
5328         case REQ_STAGE_NEXT:
5329                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5330                         report("File is untracked; press %s to add",
5331                                get_key(REQ_STATUS_UPDATE));
5332                         return REQ_NONE;
5333                 }
5334                 stage_next(view, line);
5335                 return REQ_NONE;
5337         case REQ_EDIT:
5338                 if (!stage_status.new.name[0])
5339                         return request;
5340                 if (stage_status.status == 'D') {
5341                         report("File has been deleted.");
5342                         return REQ_NONE;
5343                 }
5345                 open_editor(stage_status.status != '?', stage_status.new.name);
5346                 break;
5348         case REQ_REFRESH:
5349                 /* Reload everything ... */
5350                 break;
5352         case REQ_VIEW_BLAME:
5353                 if (stage_status.new.name[0]) {
5354                         string_copy(opt_file, stage_status.new.name);
5355                         opt_ref[0] = 0;
5356                 }
5357                 return request;
5359         case REQ_ENTER:
5360                 return pager_request(view, request, line);
5362         default:
5363                 return request;
5364         }
5366         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5367         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5369         /* Check whether the staged entry still exists, and close the
5370          * stage view if it doesn't. */
5371         if (!status_exists(&stage_status, stage_line_type)) {
5372                 status_restore(VIEW(REQ_VIEW_STATUS));
5373                 return REQ_VIEW_CLOSE;
5374         }
5376         if (stage_line_type == LINE_STAT_UNTRACKED) {
5377                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5378                         report("Cannot display a directory");
5379                         return REQ_NONE;
5380                 }
5382                 if (!prepare_update_file(view, stage_status.new.name)) {
5383                         report("Failed to open file: %s", strerror(errno));
5384                         return REQ_NONE;
5385                 }
5386         }
5387         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5389         return REQ_NONE;
5392 static struct view_ops stage_ops = {
5393         "line",
5394         NULL,
5395         NULL,
5396         pager_read,
5397         pager_draw,
5398         stage_request,
5399         pager_grep,
5400         pager_select,
5401 };
5404 /*
5405  * Revision graph
5406  */
5408 struct commit {
5409         char id[SIZEOF_REV];            /* SHA1 ID. */
5410         char title[128];                /* First line of the commit message. */
5411         char author[75];                /* Author of the commit. */
5412         struct tm time;                 /* Date from the author ident. */
5413         struct ref **refs;              /* Repository references. */
5414         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5415         size_t graph_size;              /* The width of the graph array. */
5416         bool has_parents;               /* Rewritten --parents seen. */
5417 };
5419 /* Size of rev graph with no  "padding" columns */
5420 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5422 struct rev_graph {
5423         struct rev_graph *prev, *next, *parents;
5424         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5425         size_t size;
5426         struct commit *commit;
5427         size_t pos;
5428         unsigned int boundary:1;
5429 };
5431 /* Parents of the commit being visualized. */
5432 static struct rev_graph graph_parents[4];
5434 /* The current stack of revisions on the graph. */
5435 static struct rev_graph graph_stacks[4] = {
5436         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5437         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5438         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5439         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5440 };
5442 static inline bool
5443 graph_parent_is_merge(struct rev_graph *graph)
5445         return graph->parents->size > 1;
5448 static inline void
5449 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5451         struct commit *commit = graph->commit;
5453         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5454                 commit->graph[commit->graph_size++] = symbol;
5457 static void
5458 clear_rev_graph(struct rev_graph *graph)
5460         graph->boundary = 0;
5461         graph->size = graph->pos = 0;
5462         graph->commit = NULL;
5463         memset(graph->parents, 0, sizeof(*graph->parents));
5466 static void
5467 done_rev_graph(struct rev_graph *graph)
5469         if (graph_parent_is_merge(graph) &&
5470             graph->pos < graph->size - 1 &&
5471             graph->next->size == graph->size + graph->parents->size - 1) {
5472                 size_t i = graph->pos + graph->parents->size - 1;
5474                 graph->commit->graph_size = i * 2;
5475                 while (i < graph->next->size - 1) {
5476                         append_to_rev_graph(graph, ' ');
5477                         append_to_rev_graph(graph, '\\');
5478                         i++;
5479                 }
5480         }
5482         clear_rev_graph(graph);
5485 static void
5486 push_rev_graph(struct rev_graph *graph, const char *parent)
5488         int i;
5490         /* "Collapse" duplicate parents lines.
5491          *
5492          * FIXME: This needs to also update update the drawn graph but
5493          * for now it just serves as a method for pruning graph lines. */
5494         for (i = 0; i < graph->size; i++)
5495                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5496                         return;
5498         if (graph->size < SIZEOF_REVITEMS) {
5499                 string_copy_rev(graph->rev[graph->size++], parent);
5500         }
5503 static chtype
5504 get_rev_graph_symbol(struct rev_graph *graph)
5506         chtype symbol;
5508         if (graph->boundary)
5509                 symbol = REVGRAPH_BOUND;
5510         else if (graph->parents->size == 0)
5511                 symbol = REVGRAPH_INIT;
5512         else if (graph_parent_is_merge(graph))
5513                 symbol = REVGRAPH_MERGE;
5514         else if (graph->pos >= graph->size)
5515                 symbol = REVGRAPH_BRANCH;
5516         else
5517                 symbol = REVGRAPH_COMMIT;
5519         return symbol;
5522 static void
5523 draw_rev_graph(struct rev_graph *graph)
5525         struct rev_filler {
5526                 chtype separator, line;
5527         };
5528         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5529         static struct rev_filler fillers[] = {
5530                 { ' ',  '|' },
5531                 { '`',  '.' },
5532                 { '\'', ' ' },
5533                 { '/',  ' ' },
5534         };
5535         chtype symbol = get_rev_graph_symbol(graph);
5536         struct rev_filler *filler;
5537         size_t i;
5539         if (opt_line_graphics)
5540                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5542         filler = &fillers[DEFAULT];
5544         for (i = 0; i < graph->pos; i++) {
5545                 append_to_rev_graph(graph, filler->line);
5546                 if (graph_parent_is_merge(graph->prev) &&
5547                     graph->prev->pos == i)
5548                         filler = &fillers[RSHARP];
5550                 append_to_rev_graph(graph, filler->separator);
5551         }
5553         /* Place the symbol for this revision. */
5554         append_to_rev_graph(graph, symbol);
5556         if (graph->prev->size > graph->size)
5557                 filler = &fillers[RDIAG];
5558         else
5559                 filler = &fillers[DEFAULT];
5561         i++;
5563         for (; i < graph->size; i++) {
5564                 append_to_rev_graph(graph, filler->separator);
5565                 append_to_rev_graph(graph, filler->line);
5566                 if (graph_parent_is_merge(graph->prev) &&
5567                     i < graph->prev->pos + graph->parents->size)
5568                         filler = &fillers[RSHARP];
5569                 if (graph->prev->size > graph->size)
5570                         filler = &fillers[LDIAG];
5571         }
5573         if (graph->prev->size > graph->size) {
5574                 append_to_rev_graph(graph, filler->separator);
5575                 if (filler->line != ' ')
5576                         append_to_rev_graph(graph, filler->line);
5577         }
5580 /* Prepare the next rev graph */
5581 static void
5582 prepare_rev_graph(struct rev_graph *graph)
5584         size_t i;
5586         /* First, traverse all lines of revisions up to the active one. */
5587         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5588                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5589                         break;
5591                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5592         }
5594         /* Interleave the new revision parent(s). */
5595         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5596                 push_rev_graph(graph->next, graph->parents->rev[i]);
5598         /* Lastly, put any remaining revisions. */
5599         for (i = graph->pos + 1; i < graph->size; i++)
5600                 push_rev_graph(graph->next, graph->rev[i]);
5603 static void
5604 update_rev_graph(struct view *view, struct rev_graph *graph)
5606         /* If this is the finalizing update ... */
5607         if (graph->commit)
5608                 prepare_rev_graph(graph);
5610         /* Graph visualization needs a one rev look-ahead,
5611          * so the first update doesn't visualize anything. */
5612         if (!graph->prev->commit)
5613                 return;
5615         if (view->lines > 2)
5616                 view->line[view->lines - 3].dirty = 1;
5617         if (view->lines > 1)
5618                 view->line[view->lines - 2].dirty = 1;
5619         draw_rev_graph(graph->prev);
5620         done_rev_graph(graph->prev->prev);
5624 /*
5625  * Main view backend
5626  */
5628 static const char *main_argv[SIZEOF_ARG] = {
5629         "git", "log", "--no-color", "--pretty=raw", "--parents",
5630                       "--topo-order", "%(head)", NULL
5631 };
5633 static bool
5634 main_draw(struct view *view, struct line *line, unsigned int lineno)
5636         struct commit *commit = line->data;
5638         if (!*commit->author)
5639                 return FALSE;
5641         if (opt_date && draw_date(view, &commit->time))
5642                 return TRUE;
5644         if (opt_author &&
5645             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5646                 return TRUE;
5648         if (opt_rev_graph && commit->graph_size &&
5649             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5650                 return TRUE;
5652         if (opt_show_refs && commit->refs) {
5653                 size_t i = 0;
5655                 do {
5656                         enum line_type type;
5658                         if (commit->refs[i]->head)
5659                                 type = LINE_MAIN_HEAD;
5660                         else if (commit->refs[i]->ltag)
5661                                 type = LINE_MAIN_LOCAL_TAG;
5662                         else if (commit->refs[i]->tag)
5663                                 type = LINE_MAIN_TAG;
5664                         else if (commit->refs[i]->tracked)
5665                                 type = LINE_MAIN_TRACKED;
5666                         else if (commit->refs[i]->remote)
5667                                 type = LINE_MAIN_REMOTE;
5668                         else
5669                                 type = LINE_MAIN_REF;
5671                         if (draw_text(view, type, "[", TRUE) ||
5672                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5673                             draw_text(view, type, "]", TRUE))
5674                                 return TRUE;
5676                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5677                                 return TRUE;
5678                 } while (commit->refs[i++]->next);
5679         }
5681         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5682         return TRUE;
5685 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5686 static bool
5687 main_read(struct view *view, char *line)
5689         static struct rev_graph *graph = graph_stacks;
5690         enum line_type type;
5691         struct commit *commit;
5693         if (!line) {
5694                 int i;
5696                 if (!view->lines && !view->parent)
5697                         die("No revisions match the given arguments.");
5698                 if (view->lines > 0) {
5699                         commit = view->line[view->lines - 1].data;
5700                         view->line[view->lines - 1].dirty = 1;
5701                         if (!*commit->author) {
5702                                 view->lines--;
5703                                 free(commit);
5704                                 graph->commit = NULL;
5705                         }
5706                 }
5707                 update_rev_graph(view, graph);
5709                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5710                         clear_rev_graph(&graph_stacks[i]);
5711                 return TRUE;
5712         }
5714         type = get_line_type(line);
5715         if (type == LINE_COMMIT) {
5716                 commit = calloc(1, sizeof(struct commit));
5717                 if (!commit)
5718                         return FALSE;
5720                 line += STRING_SIZE("commit ");
5721                 if (*line == '-') {
5722                         graph->boundary = 1;
5723                         line++;
5724                 }
5726                 string_copy_rev(commit->id, line);
5727                 commit->refs = get_refs(commit->id);
5728                 graph->commit = commit;
5729                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5731                 while ((line = strchr(line, ' '))) {
5732                         line++;
5733                         push_rev_graph(graph->parents, line);
5734                         commit->has_parents = TRUE;
5735                 }
5736                 return TRUE;
5737         }
5739         if (!view->lines)
5740                 return TRUE;
5741         commit = view->line[view->lines - 1].data;
5743         switch (type) {
5744         case LINE_PARENT:
5745                 if (commit->has_parents)
5746                         break;
5747                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5748                 break;
5750         case LINE_AUTHOR:
5751                 parse_author_line(line + STRING_SIZE("author "),
5752                                   commit->author, sizeof(commit->author),
5753                                   &commit->time);
5754                 update_rev_graph(view, graph);
5755                 graph = graph->next;
5756                 break;
5758         default:
5759                 /* Fill in the commit title if it has not already been set. */
5760                 if (commit->title[0])
5761                         break;
5763                 /* Require titles to start with a non-space character at the
5764                  * offset used by git log. */
5765                 if (strncmp(line, "    ", 4))
5766                         break;
5767                 line += 4;
5768                 /* Well, if the title starts with a whitespace character,
5769                  * try to be forgiving.  Otherwise we end up with no title. */
5770                 while (isspace(*line))
5771                         line++;
5772                 if (*line == '\0')
5773                         break;
5774                 /* FIXME: More graceful handling of titles; append "..." to
5775                  * shortened titles, etc. */
5777                 string_ncopy(commit->title, line, strlen(line));
5778                 view->line[view->lines - 1].dirty = 1;
5779         }
5781         return TRUE;
5784 static enum request
5785 main_request(struct view *view, enum request request, struct line *line)
5787         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5789         switch (request) {
5790         case REQ_ENTER:
5791                 open_view(view, REQ_VIEW_DIFF, flags);
5792                 break;
5793         case REQ_REFRESH:
5794                 load_refs();
5795                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5796                 break;
5797         default:
5798                 return request;
5799         }
5801         return REQ_NONE;
5804 static bool
5805 grep_refs(struct ref **refs, regex_t *regex)
5807         regmatch_t pmatch;
5808         size_t i = 0;
5810         if (!refs)
5811                 return FALSE;
5812         do {
5813                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5814                         return TRUE;
5815         } while (refs[i++]->next);
5817         return FALSE;
5820 static bool
5821 main_grep(struct view *view, struct line *line)
5823         struct commit *commit = line->data;
5824         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5825         char buf[DATE_COLS + 1];
5826         regmatch_t pmatch;
5828         for (state = S_TITLE; state < S_END; state++) {
5829                 char *text;
5831                 switch (state) {
5832                 case S_TITLE:   text = commit->title;   break;
5833                 case S_AUTHOR:
5834                         if (!opt_author)
5835                                 continue;
5836                         text = commit->author;
5837                         break;
5838                 case S_DATE:
5839                         if (!opt_date)
5840                                 continue;
5841                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5842                                 continue;
5843                         text = buf;
5844                         break;
5845                 case S_REFS:
5846                         if (!opt_show_refs)
5847                                 continue;
5848                         if (grep_refs(commit->refs, view->regex) == TRUE)
5849                                 return TRUE;
5850                         continue;
5851                 default:
5852                         return FALSE;
5853                 }
5855                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5856                         return TRUE;
5857         }
5859         return FALSE;
5862 static void
5863 main_select(struct view *view, struct line *line)
5865         struct commit *commit = line->data;
5867         string_copy_rev(view->ref, commit->id);
5868         string_copy_rev(ref_commit, view->ref);
5871 static struct view_ops main_ops = {
5872         "commit",
5873         main_argv,
5874         NULL,
5875         main_read,
5876         main_draw,
5877         main_request,
5878         main_grep,
5879         main_select,
5880 };
5883 /*
5884  * Unicode / UTF-8 handling
5885  *
5886  * NOTE: Much of the following code for dealing with unicode is derived from
5887  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5888  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5889  */
5891 /* I've (over)annotated a lot of code snippets because I am not entirely
5892  * confident that the approach taken by this small UTF-8 interface is correct.
5893  * --jonas */
5895 static inline int
5896 unicode_width(unsigned long c)
5898         if (c >= 0x1100 &&
5899            (c <= 0x115f                         /* Hangul Jamo */
5900             || c == 0x2329
5901             || c == 0x232a
5902             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5903                                                 /* CJK ... Yi */
5904             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5905             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5906             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5907             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5908             || (c >= 0xffe0  && c <= 0xffe6)
5909             || (c >= 0x20000 && c <= 0x2fffd)
5910             || (c >= 0x30000 && c <= 0x3fffd)))
5911                 return 2;
5913         if (c == '\t')
5914                 return opt_tab_size;
5916         return 1;
5919 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5920  * Illegal bytes are set one. */
5921 static const unsigned char utf8_bytes[256] = {
5922         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,
5923         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,
5924         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,
5925         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,
5926         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,
5927         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,
5928         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,
5929         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,
5930 };
5932 /* Decode UTF-8 multi-byte representation into a unicode character. */
5933 static inline unsigned long
5934 utf8_to_unicode(const char *string, size_t length)
5936         unsigned long unicode;
5938         switch (length) {
5939         case 1:
5940                 unicode  =   string[0];
5941                 break;
5942         case 2:
5943                 unicode  =  (string[0] & 0x1f) << 6;
5944                 unicode +=  (string[1] & 0x3f);
5945                 break;
5946         case 3:
5947                 unicode  =  (string[0] & 0x0f) << 12;
5948                 unicode += ((string[1] & 0x3f) << 6);
5949                 unicode +=  (string[2] & 0x3f);
5950                 break;
5951         case 4:
5952                 unicode  =  (string[0] & 0x0f) << 18;
5953                 unicode += ((string[1] & 0x3f) << 12);
5954                 unicode += ((string[2] & 0x3f) << 6);
5955                 unicode +=  (string[3] & 0x3f);
5956                 break;
5957         case 5:
5958                 unicode  =  (string[0] & 0x0f) << 24;
5959                 unicode += ((string[1] & 0x3f) << 18);
5960                 unicode += ((string[2] & 0x3f) << 12);
5961                 unicode += ((string[3] & 0x3f) << 6);
5962                 unicode +=  (string[4] & 0x3f);
5963                 break;
5964         case 6:
5965                 unicode  =  (string[0] & 0x01) << 30;
5966                 unicode += ((string[1] & 0x3f) << 24);
5967                 unicode += ((string[2] & 0x3f) << 18);
5968                 unicode += ((string[3] & 0x3f) << 12);
5969                 unicode += ((string[4] & 0x3f) << 6);
5970                 unicode +=  (string[5] & 0x3f);
5971                 break;
5972         default:
5973                 die("Invalid unicode length");
5974         }
5976         /* Invalid characters could return the special 0xfffd value but NUL
5977          * should be just as good. */
5978         return unicode > 0xffff ? 0 : unicode;
5981 /* Calculates how much of string can be shown within the given maximum width
5982  * and sets trimmed parameter to non-zero value if all of string could not be
5983  * shown. If the reserve flag is TRUE, it will reserve at least one
5984  * trailing character, which can be useful when drawing a delimiter.
5985  *
5986  * Returns the number of bytes to output from string to satisfy max_width. */
5987 static size_t
5988 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5990         const char *start = string;
5991         const char *end = strchr(string, '\0');
5992         unsigned char last_bytes = 0;
5993         size_t last_ucwidth = 0;
5995         *width = 0;
5996         *trimmed = 0;
5998         while (string < end) {
5999                 int c = *(unsigned char *) string;
6000                 unsigned char bytes = utf8_bytes[c];
6001                 size_t ucwidth;
6002                 unsigned long unicode;
6004                 if (string + bytes > end)
6005                         break;
6007                 /* Change representation to figure out whether
6008                  * it is a single- or double-width character. */
6010                 unicode = utf8_to_unicode(string, bytes);
6011                 /* FIXME: Graceful handling of invalid unicode character. */
6012                 if (!unicode)
6013                         break;
6015                 ucwidth = unicode_width(unicode);
6016                 *width  += ucwidth;
6017                 if (*width > max_width) {
6018                         *trimmed = 1;
6019                         *width -= ucwidth;
6020                         if (reserve && *width == max_width) {
6021                                 string -= last_bytes;
6022                                 *width -= last_ucwidth;
6023                         }
6024                         break;
6025                 }
6027                 string  += bytes;
6028                 last_bytes = bytes;
6029                 last_ucwidth = ucwidth;
6030         }
6032         return string - start;
6036 /*
6037  * Status management
6038  */
6040 /* Whether or not the curses interface has been initialized. */
6041 static bool cursed = FALSE;
6043 /* The status window is used for polling keystrokes. */
6044 static WINDOW *status_win;
6046 static bool status_empty = TRUE;
6048 /* Update status and title window. */
6049 static void
6050 report(const char *msg, ...)
6052         struct view *view = display[current_view];
6054         if (input_mode)
6055                 return;
6057         if (!view) {
6058                 char buf[SIZEOF_STR];
6059                 va_list args;
6061                 va_start(args, msg);
6062                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6063                         buf[sizeof(buf) - 1] = 0;
6064                         buf[sizeof(buf) - 2] = '.';
6065                         buf[sizeof(buf) - 3] = '.';
6066                         buf[sizeof(buf) - 4] = '.';
6067                 }
6068                 va_end(args);
6069                 die("%s", buf);
6070         }
6072         if (!status_empty || *msg) {
6073                 va_list args;
6075                 va_start(args, msg);
6077                 wmove(status_win, 0, 0);
6078                 if (*msg) {
6079                         vwprintw(status_win, msg, args);
6080                         status_empty = FALSE;
6081                 } else {
6082                         status_empty = TRUE;
6083                 }
6084                 wclrtoeol(status_win);
6085                 wrefresh(status_win);
6087                 va_end(args);
6088         }
6090         update_view_title(view);
6091         update_display_cursor(view);
6094 /* Controls when nodelay should be in effect when polling user input. */
6095 static void
6096 set_nonblocking_input(bool loading)
6098         static unsigned int loading_views;
6100         if ((loading == FALSE && loading_views-- == 1) ||
6101             (loading == TRUE  && loading_views++ == 0))
6102                 nodelay(status_win, loading);
6105 static void
6106 init_display(void)
6108         int x, y;
6110         /* Initialize the curses library */
6111         if (isatty(STDIN_FILENO)) {
6112                 cursed = !!initscr();
6113                 opt_tty = stdin;
6114         } else {
6115                 /* Leave stdin and stdout alone when acting as a pager. */
6116                 opt_tty = fopen("/dev/tty", "r+");
6117                 if (!opt_tty)
6118                         die("Failed to open /dev/tty");
6119                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6120         }
6122         if (!cursed)
6123                 die("Failed to initialize curses");
6125         nonl();         /* Tell curses not to do NL->CR/NL on output */
6126         cbreak();       /* Take input chars one at a time, no wait for \n */
6127         noecho();       /* Don't echo input */
6128         leaveok(stdscr, TRUE);
6130         if (has_colors())
6131                 init_colors();
6133         getmaxyx(stdscr, y, x);
6134         status_win = newwin(1, 0, y - 1, 0);
6135         if (!status_win)
6136                 die("Failed to create status window");
6138         /* Enable keyboard mapping */
6139         keypad(status_win, TRUE);
6140         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6142         TABSIZE = opt_tab_size;
6143         if (opt_line_graphics) {
6144                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6145         }
6148 static int
6149 get_input(bool prompting)
6151         struct view *view;
6152         int i, key;
6154         if (prompting)
6155                 input_mode = TRUE;
6157         while (true) {
6158                 foreach_view (view, i)
6159                         update_view(view);
6161                 /* Refresh, accept single keystroke of input */
6162                 key = wgetch(status_win);
6164                 /* wgetch() with nodelay() enabled returns ERR when
6165                  * there's no input. */
6166                 if (key == ERR) {
6167                         doupdate();
6169                 } else if (key == KEY_RESIZE) {
6170                         int height, width;
6172                         getmaxyx(stdscr, height, width);
6174                         /* Resize the status view and let the view driver take
6175                          * care of resizing the displayed views. */
6176                         resize_display();
6177                         redraw_display(TRUE);
6178                         wresize(status_win, 1, width);
6179                         mvwin(status_win, height - 1, 0);
6180                         wrefresh(status_win);
6182                 } else {
6183                         input_mode = FALSE;
6184                         return key;
6185                 }
6186         }
6189 static bool
6190 prompt_yesno(const char *prompt)
6192         enum { WAIT, STOP, CANCEL  } status = WAIT;
6193         bool answer = FALSE;
6195         while (status == WAIT) {
6196                 int key;
6198                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
6199                 wclrtoeol(status_win);
6201                 key = get_input(TRUE);
6202                 switch (key) {
6203                 case 'y':
6204                 case 'Y':
6205                         answer = TRUE;
6206                         status = STOP;
6207                         break;
6209                 case KEY_ESC:
6210                 case KEY_RETURN:
6211                 case KEY_ENTER:
6212                 case KEY_BACKSPACE:
6213                 case 'n':
6214                 case 'N':
6215                 case '\n':
6216                 default:
6217                         answer = FALSE;
6218                         status = CANCEL;
6219                 }
6220         }
6222         /* Clear the status window */
6223         status_empty = FALSE;
6224         report("");
6226         return answer;
6229 static char *
6230 read_prompt(const char *prompt)
6232         enum { READING, STOP, CANCEL } status = READING;
6233         static char buf[SIZEOF_STR];
6234         int pos = 0;
6236         while (status == READING) {
6237                 int key;
6239                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6240                 wclrtoeol(status_win);
6242                 key = get_input(TRUE);
6243                 switch (key) {
6244                 case KEY_RETURN:
6245                 case KEY_ENTER:
6246                 case '\n':
6247                         status = pos ? STOP : CANCEL;
6248                         break;
6250                 case KEY_BACKSPACE:
6251                         if (pos > 0)
6252                                 pos--;
6253                         else
6254                                 status = CANCEL;
6255                         break;
6257                 case KEY_ESC:
6258                         status = CANCEL;
6259                         break;
6261                 default:
6262                         if (pos >= sizeof(buf)) {
6263                                 report("Input string too long");
6264                                 return NULL;
6265                         }
6267                         if (isprint(key))
6268                                 buf[pos++] = (char) key;
6269                 }
6270         }
6272         /* Clear the status window */
6273         status_empty = FALSE;
6274         report("");
6276         if (status == CANCEL)
6277                 return NULL;
6279         buf[pos++] = 0;
6281         return buf;
6284 /*
6285  * Repository properties
6286  */
6288 static int
6289 git_properties(const char **argv, const char *separators,
6290                int (*read_property)(char *, size_t, char *, size_t))
6292         struct io io = {};
6294         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6295                 return read_properties(&io, separators, read_property);
6296         return ERR;
6299 static struct ref *refs = NULL;
6300 static size_t refs_alloc = 0;
6301 static size_t refs_size = 0;
6303 /* Id <-> ref store */
6304 static struct ref ***id_refs = NULL;
6305 static size_t id_refs_alloc = 0;
6306 static size_t id_refs_size = 0;
6308 static int
6309 compare_refs(const void *ref1_, const void *ref2_)
6311         const struct ref *ref1 = *(const struct ref **)ref1_;
6312         const struct ref *ref2 = *(const struct ref **)ref2_;
6314         if (ref1->tag != ref2->tag)
6315                 return ref2->tag - ref1->tag;
6316         if (ref1->ltag != ref2->ltag)
6317                 return ref2->ltag - ref2->ltag;
6318         if (ref1->head != ref2->head)
6319                 return ref2->head - ref1->head;
6320         if (ref1->tracked != ref2->tracked)
6321                 return ref2->tracked - ref1->tracked;
6322         if (ref1->remote != ref2->remote)
6323                 return ref2->remote - ref1->remote;
6324         return strcmp(ref1->name, ref2->name);
6327 static struct ref **
6328 get_refs(const char *id)
6330         struct ref ***tmp_id_refs;
6331         struct ref **ref_list = NULL;
6332         size_t ref_list_alloc = 0;
6333         size_t ref_list_size = 0;
6334         size_t i;
6336         for (i = 0; i < id_refs_size; i++)
6337                 if (!strcmp(id, id_refs[i][0]->id))
6338                         return id_refs[i];
6340         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6341                                     sizeof(*id_refs));
6342         if (!tmp_id_refs)
6343                 return NULL;
6345         id_refs = tmp_id_refs;
6347         for (i = 0; i < refs_size; i++) {
6348                 struct ref **tmp;
6350                 if (strcmp(id, refs[i].id))
6351                         continue;
6353                 tmp = realloc_items(ref_list, &ref_list_alloc,
6354                                     ref_list_size + 1, sizeof(*ref_list));
6355                 if (!tmp) {
6356                         if (ref_list)
6357                                 free(ref_list);
6358                         return NULL;
6359                 }
6361                 ref_list = tmp;
6362                 ref_list[ref_list_size] = &refs[i];
6363                 /* XXX: The properties of the commit chains ensures that we can
6364                  * safely modify the shared ref. The repo references will
6365                  * always be similar for the same id. */
6366                 ref_list[ref_list_size]->next = 1;
6368                 ref_list_size++;
6369         }
6371         if (ref_list) {
6372                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6373                 ref_list[ref_list_size - 1]->next = 0;
6374                 id_refs[id_refs_size++] = ref_list;
6375         }
6377         return ref_list;
6380 static int
6381 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6383         struct ref *ref;
6384         bool tag = FALSE;
6385         bool ltag = FALSE;
6386         bool remote = FALSE;
6387         bool tracked = FALSE;
6388         bool check_replace = FALSE;
6389         bool head = FALSE;
6391         if (!prefixcmp(name, "refs/tags/")) {
6392                 if (!suffixcmp(name, namelen, "^{}")) {
6393                         namelen -= 3;
6394                         name[namelen] = 0;
6395                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6396                                 check_replace = TRUE;
6397                 } else {
6398                         ltag = TRUE;
6399                 }
6401                 tag = TRUE;
6402                 namelen -= STRING_SIZE("refs/tags/");
6403                 name    += STRING_SIZE("refs/tags/");
6405         } else if (!prefixcmp(name, "refs/remotes/")) {
6406                 remote = TRUE;
6407                 namelen -= STRING_SIZE("refs/remotes/");
6408                 name    += STRING_SIZE("refs/remotes/");
6409                 tracked  = !strcmp(opt_remote, name);
6411         } else if (!prefixcmp(name, "refs/heads/")) {
6412                 namelen -= STRING_SIZE("refs/heads/");
6413                 name    += STRING_SIZE("refs/heads/");
6414                 head     = !strncmp(opt_head, name, namelen);
6416         } else if (!strcmp(name, "HEAD")) {
6417                 string_ncopy(opt_head_rev, id, idlen);
6418                 return OK;
6419         }
6421         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6422                 /* it's an annotated tag, replace the previous sha1 with the
6423                  * resolved commit id; relies on the fact git-ls-remote lists
6424                  * the commit id of an annotated tag right before the commit id
6425                  * it points to. */
6426                 refs[refs_size - 1].ltag = ltag;
6427                 string_copy_rev(refs[refs_size - 1].id, id);
6429                 return OK;
6430         }
6431         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6432         if (!refs)
6433                 return ERR;
6435         ref = &refs[refs_size++];
6436         ref->name = malloc(namelen + 1);
6437         if (!ref->name)
6438                 return ERR;
6440         strncpy(ref->name, name, namelen);
6441         ref->name[namelen] = 0;
6442         ref->head = head;
6443         ref->tag = tag;
6444         ref->ltag = ltag;
6445         ref->remote = remote;
6446         ref->tracked = tracked;
6447         string_copy_rev(ref->id, id);
6449         return OK;
6452 static int
6453 load_refs(void)
6455         static const char *ls_remote_argv[SIZEOF_ARG] = {
6456                 "git", "ls-remote", ".", NULL
6457         };
6458         static bool init = FALSE;
6460         if (!init) {
6461                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6462                 init = TRUE;
6463         }
6465         if (!*opt_git_dir)
6466                 return OK;
6468         while (refs_size > 0)
6469                 free(refs[--refs_size].name);
6470         while (id_refs_size > 0)
6471                 free(id_refs[--id_refs_size]);
6473         return git_properties(ls_remote_argv, "\t", read_ref);
6476 static int
6477 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6479         if (!strcmp(name, "i18n.commitencoding"))
6480                 string_ncopy(opt_encoding, value, valuelen);
6482         if (!strcmp(name, "core.editor"))
6483                 string_ncopy(opt_editor, value, valuelen);
6485         /* branch.<head>.remote */
6486         if (*opt_head &&
6487             !strncmp(name, "branch.", 7) &&
6488             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6489             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6490                 string_ncopy(opt_remote, value, valuelen);
6492         if (*opt_head && *opt_remote &&
6493             !strncmp(name, "branch.", 7) &&
6494             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6495             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6496                 size_t from = strlen(opt_remote);
6498                 if (!prefixcmp(value, "refs/heads/")) {
6499                         value += STRING_SIZE("refs/heads/");
6500                         valuelen -= STRING_SIZE("refs/heads/");
6501                 }
6503                 if (!string_format_from(opt_remote, &from, "/%s", value))
6504                         opt_remote[0] = 0;
6505         }
6507         return OK;
6510 static int
6511 load_git_config(void)
6513         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6515         return git_properties(config_list_argv, "=", read_repo_config_option);
6518 static int
6519 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6521         if (!opt_git_dir[0]) {
6522                 string_ncopy(opt_git_dir, name, namelen);
6524         } else if (opt_is_inside_work_tree == -1) {
6525                 /* This can be 3 different values depending on the
6526                  * version of git being used. If git-rev-parse does not
6527                  * understand --is-inside-work-tree it will simply echo
6528                  * the option else either "true" or "false" is printed.
6529                  * Default to true for the unknown case. */
6530                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6532         } else if (*name == '.') {
6533                 string_ncopy(opt_cdup, name, namelen);
6535         } else {
6536                 string_ncopy(opt_prefix, name, namelen);
6537         }
6539         return OK;
6542 static int
6543 load_repo_info(void)
6545         const char *head_argv[] = {
6546                 "git", "symbolic-ref", "HEAD", NULL
6547         };
6548         const char *rev_parse_argv[] = {
6549                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6550                         "--show-cdup", "--show-prefix", NULL
6551         };
6553         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6554                 chomp_string(opt_head);
6555                 if (!prefixcmp(opt_head, "refs/heads/")) {
6556                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6558                         memmove(opt_head, offset, strlen(offset) + 1);
6559                 }
6560         }
6562         return git_properties(rev_parse_argv, "=", read_repo_info);
6565 static int
6566 read_properties(struct io *io, const char *separators,
6567                 int (*read_property)(char *, size_t, char *, size_t))
6569         char *name;
6570         int state = OK;
6572         if (!start_io(io))
6573                 return ERR;
6575         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6576                 char *value;
6577                 size_t namelen;
6578                 size_t valuelen;
6580                 name = chomp_string(name);
6581                 namelen = strcspn(name, separators);
6583                 if (name[namelen]) {
6584                         name[namelen] = 0;
6585                         value = chomp_string(name + namelen + 1);
6586                         valuelen = strlen(value);
6588                 } else {
6589                         value = "";
6590                         valuelen = 0;
6591                 }
6593                 state = read_property(name, namelen, value, valuelen);
6594         }
6596         if (state != ERR && io_error(io))
6597                 state = ERR;
6598         done_io(io);
6600         return state;
6604 /*
6605  * Main
6606  */
6608 static void __NORETURN
6609 quit(int sig)
6611         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6612         if (cursed)
6613                 endwin();
6614         exit(0);
6617 static void __NORETURN
6618 die(const char *err, ...)
6620         va_list args;
6622         endwin();
6624         va_start(args, err);
6625         fputs("tig: ", stderr);
6626         vfprintf(stderr, err, args);
6627         fputs("\n", stderr);
6628         va_end(args);
6630         exit(1);
6633 static void
6634 warn(const char *msg, ...)
6636         va_list args;
6638         va_start(args, msg);
6639         fputs("tig warning: ", stderr);
6640         vfprintf(stderr, msg, args);
6641         fputs("\n", stderr);
6642         va_end(args);
6645 int
6646 main(int argc, const char *argv[])
6648         const char **run_argv = NULL;
6649         struct view *view;
6650         enum request request;
6651         size_t i;
6653         signal(SIGINT, quit);
6655         if (setlocale(LC_ALL, "")) {
6656                 char *codeset = nl_langinfo(CODESET);
6658                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6659         }
6661         if (load_repo_info() == ERR)
6662                 die("Failed to load repo info.");
6664         if (load_options() == ERR)
6665                 die("Failed to load user config.");
6667         if (load_git_config() == ERR)
6668                 die("Failed to load repo config.");
6670         request = parse_options(argc, argv, &run_argv);
6671         if (request == REQ_NONE)
6672                 return 0;
6674         /* Require a git repository unless when running in pager mode. */
6675         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6676                 die("Not a git repository");
6678         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6679                 opt_utf8 = FALSE;
6681         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6682                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6683                 if (opt_iconv == ICONV_NONE)
6684                         die("Failed to initialize character set conversion");
6685         }
6687         if (load_refs() == ERR)
6688                 die("Failed to load refs.");
6690         foreach_view (view, i)
6691                 argv_from_env(view->ops->argv, view->cmd_env);
6693         init_display();
6695         if (request == REQ_VIEW_PAGER || run_argv) {
6696                 if (request == REQ_VIEW_PAGER)
6697                         io_open(&VIEW(request)->io, "");
6698                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6699                         die("Failed to format arguments");
6700                 open_view(NULL, request, OPEN_PREPARED);
6701                 request = REQ_NONE;
6702         }
6704         while (view_driver(display[current_view], request)) {
6705                 int key = get_input(FALSE);
6707                 view = display[current_view];
6708                 request = get_keybinding(view->keymap, key);
6710                 /* Some low-level request handling. This keeps access to
6711                  * status_win restricted. */
6712                 switch (request) {
6713                 case REQ_PROMPT:
6714                 {
6715                         char *cmd = read_prompt(":");
6717                         if (cmd) {
6718                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6719                                 const char *argv[SIZEOF_ARG] = { "git" };
6720                                 int argc = 1;
6722                                 /* When running random commands, initially show the
6723                                  * command in the title. However, it maybe later be
6724                                  * overwritten if a commit line is selected. */
6725                                 string_ncopy(next->ref, cmd, strlen(cmd));
6727                                 if (!argv_from_string(argv, &argc, cmd)) {
6728                                         report("Too many arguments");
6729                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6730                                         report("Failed to format command");
6731                                 } else {
6732                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6733                                 }
6734                         }
6736                         request = REQ_NONE;
6737                         break;
6738                 }
6739                 case REQ_SEARCH:
6740                 case REQ_SEARCH_BACK:
6741                 {
6742                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6743                         char *search = read_prompt(prompt);
6745                         if (search)
6746                                 string_ncopy(opt_search, search, strlen(search));
6747                         else
6748                                 request = REQ_NONE;
6749                         break;
6750                 }
6751                 default:
6752                         break;
6753                 }
6754         }
6756         quit(0);
6758         return 0;