Code

146bd7021d1dd78d457c197221e648856fadf194
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
173 enum input_status {
174         INPUT_OK,
175         INPUT_SKIP,
176         INPUT_STOP,
177         INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216         int namelen;
218         while (isspace(*name))
219                 name++;
221         namelen = strlen(name) - 1;
222         while (namelen > 0 && isspace(name[namelen]))
223                 name[namelen--] = 0;
225         return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231         va_list args;
232         size_t pos = bufpos ? *bufpos : 0;
234         va_start(args, fmt);
235         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236         va_end(args);
238         if (bufpos)
239                 *bufpos = pos;
241         return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245         string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248         string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253         size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257         /* Diff-Header == DIFF_HEADER */
258         for (i = 0; i < len; i++) {
259                 if (toupper(str1[i]) == toupper(str2[i]))
260                         continue;
262                 if (string_enum_sep(str1[i]) &&
263                     string_enum_sep(str2[i]))
264                         continue;
266                 return str1[i] - str2[i];
267         }
269         return 0;
272 #define prefixcmp(str1, str2) \
273         strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278         size_t len = slen >= 0 ? slen : strlen(str);
279         size_t suffixlen = strlen(suffix);
281         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288         int valuelen;
290         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291                 bool advance = cmd[valuelen] != 0;
293                 cmd[valuelen] = 0;
294                 argv[(*argc)++] = chomp_string(cmd);
295                 cmd += valuelen + advance;
296         }
298         if (*argc < SIZEOF_ARG)
299                 argv[*argc] = NULL;
300         return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306         char *env = argv ? getenv(name) : NULL;
307         int argc = 0;
309         if (env && *env)
310                 env = strdup(env);
311         if (env && !argv_from_string(argv, &argc, env))
312                 die("Too many arguments in the `%s` environment variable", name);
316 /*
317  * Executing external commands.
318  */
320 enum io_type {
321         IO_FD,                  /* File descriptor based IO. */
322         IO_BG,                  /* Execute command in the background. */
323         IO_FG,                  /* Execute command with same std{in,out,err}. */
324         IO_RD,                  /* Read only fork+exec IO. */
325         IO_WR,                  /* Write only fork+exec IO. */
326         IO_AP,                  /* Append fork+exec output to file. */
327 };
329 struct io {
330         enum io_type type;      /* The requested type of pipe. */
331         const char *dir;        /* Directory from which to execute. */
332         pid_t pid;              /* Pipe for reading or writing. */
333         int pipe;               /* Pipe end for reading or writing. */
334         int error;              /* Error status. */
335         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
336         char *buf;              /* Read buffer. */
337         size_t bufalloc;        /* Allocated buffer size. */
338         size_t bufsize;         /* Buffer content size. */
339         char *bufpos;           /* Current buffer position. */
340         unsigned int eof:1;     /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
346         io->pipe = -1;
347         io->pid = 0;
348         io->buf = io->bufpos = NULL;
349         io->bufalloc = io->bufsize = 0;
350         io->error = 0;
351         io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357         reset_io(io);
358         io->type = type;
359         io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364                 enum format_flags flags)
366         init_io(io, dir, IO_RD);
367         return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373         init_io(io, NULL, IO_FD);
374         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375         return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387         pid_t pid = io->pid;
389         if (io->pipe != -1)
390                 close(io->pipe);
391         free(io->buf);
392         reset_io(io);
394         while (pid > 0) {
395                 int status;
396                 pid_t waiting = waitpid(pid, &status, 0);
398                 if (waiting < 0) {
399                         if (errno == EINTR)
400                                 continue;
401                         report("waitpid failed (%s)", strerror(errno));
402                         return FALSE;
403                 }
405                 return waiting == pid &&
406                        !WIFSIGNALED(status) &&
407                        WIFEXITED(status) &&
408                        !WEXITSTATUS(status);
409         }
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         int pipefds[2] = { -1, -1 };
419         if (io->type == IO_FD)
420                 return TRUE;
422         if ((io->type == IO_RD || io->type == IO_WR) &&
423             pipe(pipefds) < 0)
424                 return FALSE;
425         else if (io->type == IO_AP)
426                 pipefds[1] = io->pipe;
428         if ((io->pid = fork())) {
429                 if (pipefds[!(io->type == IO_WR)] != -1)
430                         close(pipefds[!(io->type == IO_WR)]);
431                 if (io->pid != -1) {
432                         io->pipe = pipefds[!!(io->type == IO_WR)];
433                         return TRUE;
434                 }
436         } else {
437                 if (io->type != IO_FG) {
438                         int devnull = open("/dev/null", O_RDWR);
439                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
440                         int writefd = (io->type == IO_RD || io->type == IO_AP)
441                                                         ? pipefds[1] : devnull;
443                         dup2(readfd,  STDIN_FILENO);
444                         dup2(writefd, STDOUT_FILENO);
445                         dup2(devnull, STDERR_FILENO);
447                         close(devnull);
448                         if (pipefds[0] != -1)
449                                 close(pipefds[0]);
450                         if (pipefds[1] != -1)
451                                 close(pipefds[1]);
452                 }
454                 if (io->dir && *io->dir && chdir(io->dir) == -1)
455                         die("Failed to change directory: %s", strerror(errno));
457                 execvp(io->argv[0], (char *const*) io->argv);
458                 die("Failed to execute program: %s", strerror(errno));
459         }
461         if (pipefds[!!(io->type == IO_WR)] != -1)
462                 close(pipefds[!!(io->type == IO_WR)]);
463         return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469         init_io(io, dir, type);
470         if (!format_argv(io->argv, argv, FORMAT_NONE))
471                 return FALSE;
472         return start_io(io);
475 static int
476 run_io_do(struct io *io)
478         return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484         struct io io = {};
486         init_io(&io, NULL, IO_BG);
487         if (!format_argv(io.argv, argv, FORMAT_NONE))
488                 return FALSE;
489         return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495         struct io io = {};
497         init_io(&io, dir, IO_FG);
498         if (!format_argv(io.argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506         struct io io = {};
508         init_io(&io, NULL, IO_AP);
509         io.pipe = fd;
510         if (format_argv(io.argv, argv, flags))
511                 return run_io_do(&io);
512         close(fd);
513         return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519         return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525         return io->eof;
528 static int
529 io_error(struct io *io)
531         return io->error;
534 static bool
535 io_strerror(struct io *io)
537         return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543         struct timeval tv = { 0, 500 };
544         fd_set fds;
546         FD_ZERO(&fds);
547         FD_SET(io->pipe, &fds);
549         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555         do {
556                 ssize_t readsize = read(io->pipe, buf, bufsize);
558                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559                         continue;
560                 else if (readsize == -1)
561                         io->error = errno;
562                 else if (readsize == 0)
563                         io->eof = 1;
564                 return readsize;
565         } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571         char *eol;
572         ssize_t readsize;
574         if (!io->buf) {
575                 io->buf = io->bufpos = malloc(BUFSIZ);
576                 if (!io->buf)
577                         return NULL;
578                 io->bufalloc = BUFSIZ;
579                 io->bufsize = 0;
580         }
582         while (TRUE) {
583                 if (io->bufsize > 0) {
584                         eol = memchr(io->bufpos, c, io->bufsize);
585                         if (eol) {
586                                 char *line = io->bufpos;
588                                 *eol = 0;
589                                 io->bufpos = eol + 1;
590                                 io->bufsize -= io->bufpos - line;
591                                 return line;
592                         }
593                 }
595                 if (io_eof(io)) {
596                         if (io->bufsize) {
597                                 io->bufpos[io->bufsize] = 0;
598                                 io->bufsize = 0;
599                                 return io->bufpos;
600                         }
601                         return NULL;
602                 }
604                 if (!can_read)
605                         return NULL;
607                 if (io->bufsize > 0 && io->bufpos > io->buf)
608                         memmove(io->buf, io->bufpos, io->bufsize);
610                 io->bufpos = io->buf;
611                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612                 if (io_error(io))
613                         return NULL;
614                 io->bufsize += readsize;
615         }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621         size_t written = 0;
623         while (!io_error(io) && written < bufsize) {
624                 ssize_t size;
626                 size = write(io->pipe, buf + written, bufsize - written);
627                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628                         continue;
629                 else if (size == -1)
630                         io->error = errno;
631                 else
632                         written += size;
633         }
635         return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641         struct io io = {};
642         bool error;
644         if (!run_io_rd(&io, argv, FORMAT_NONE))
645                 return FALSE;
647         io.buf = io.bufpos = buf;
648         io.bufalloc = bufsize;
649         error = !io_get(&io, '\n', TRUE) && io_error(&io);
650         io.buf = NULL;
652         return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
657 /*
658  * User requests
659  */
661 #define REQ_INFO \
662         /* XXX: Keep the view request first and in sync with views[]. */ \
663         REQ_GROUP("View switching") \
664         REQ_(VIEW_MAIN,         "Show main view"), \
665         REQ_(VIEW_DIFF,         "Show diff view"), \
666         REQ_(VIEW_LOG,          "Show log view"), \
667         REQ_(VIEW_TREE,         "Show tree view"), \
668         REQ_(VIEW_BLOB,         "Show blob view"), \
669         REQ_(VIEW_BLAME,        "Show blame view"), \
670         REQ_(VIEW_HELP,         "Show help page"), \
671         REQ_(VIEW_PAGER,        "Show pager view"), \
672         REQ_(VIEW_STATUS,       "Show status view"), \
673         REQ_(VIEW_STAGE,        "Show stage view"), \
674         \
675         REQ_GROUP("View manipulation") \
676         REQ_(ENTER,             "Enter current line and scroll"), \
677         REQ_(NEXT,              "Move to next"), \
678         REQ_(PREVIOUS,          "Move to previous"), \
679         REQ_(PARENT,            "Move to parent"), \
680         REQ_(VIEW_NEXT,         "Move focus to next view"), \
681         REQ_(REFRESH,           "Reload and refresh"), \
682         REQ_(MAXIMIZE,          "Maximize the current view"), \
683         REQ_(VIEW_CLOSE,        "Close the current view"), \
684         REQ_(QUIT,              "Close all views and quit"), \
685         \
686         REQ_GROUP("View specific requests") \
687         REQ_(STATUS_UPDATE,     "Update file status"), \
688         REQ_(STATUS_REVERT,     "Revert file changes"), \
689         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
690         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
691         \
692         REQ_GROUP("Cursor navigation") \
693         REQ_(MOVE_UP,           "Move cursor one line up"), \
694         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
695         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
696         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
697         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
698         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
699         \
700         REQ_GROUP("Scrolling") \
701         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
702         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
703         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
704         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
705         \
706         REQ_GROUP("Searching") \
707         REQ_(SEARCH,            "Search the view"), \
708         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
709         REQ_(FIND_NEXT,         "Find next search match"), \
710         REQ_(FIND_PREV,         "Find previous search match"), \
711         \
712         REQ_GROUP("Option manipulation") \
713         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
714         REQ_(TOGGLE_DATE,       "Toggle date display"), \
715         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
716         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
717         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
718         \
719         REQ_GROUP("Misc") \
720         REQ_(PROMPT,            "Bring up the prompt"), \
721         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
722         REQ_(SHOW_VERSION,      "Show version information"), \
723         REQ_(STOP_LOADING,      "Stop all loading views"), \
724         REQ_(EDIT,              "Open in editor"), \
725         REQ_(NONE,              "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733         /* Offset all requests to avoid conflicts with ncurses getch values. */
734         REQ_OFFSET = KEY_MAX + 1,
735         REQ_INFO
737 #undef  REQ_GROUP
738 #undef  REQ_
739 };
741 struct request_info {
742         enum request request;
743         const char *name;
744         int namelen;
745         const char *help;
746 };
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751         REQ_INFO
752 #undef  REQ_GROUP
753 #undef  REQ_
754 };
756 static enum request
757 get_request(const char *name)
759         int namelen = strlen(name);
760         int i;
762         for (i = 0; i < ARRAY_SIZE(req_info); i++)
763                 if (req_info[i].namelen == namelen &&
764                     !string_enum_compare(req_info[i].name, name, namelen))
765                         return req_info[i].request;
767         return REQ_NONE;
771 /*
772  * Options
773  */
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig        [options] [revs] [--] [paths]\n"
779 "   or: tig show   [options] [revs] [--] [paths]\n"
780 "   or: tig blame  [rev] path\n"
781 "   or: tig status\n"
782 "   or: tig <      [git command output]\n"
783 "\n"
784 "Options:\n"
785 "  -v, --version   Show version and exit\n"
786 "  -h, --help      Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date                    = TRUE;
790 static bool opt_author                  = TRUE;
791 static bool opt_line_number             = FALSE;
792 static bool opt_line_graphics           = TRUE;
793 static bool opt_rev_graph               = FALSE;
794 static bool opt_show_refs               = TRUE;
795 static int opt_num_interval             = NUMBER_INTERVAL;
796 static int opt_tab_size                 = TAB_SIZE;
797 static int opt_author_cols              = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR]        = "";
799 static char opt_file[SIZEOF_STR]        = "";
800 static char opt_ref[SIZEOF_REF]         = "";
801 static char opt_head[SIZEOF_REF]        = "";
802 static char opt_head_rev[SIZEOF_REV]    = "";
803 static char opt_remote[SIZEOF_REF]      = "";
804 static char opt_encoding[20]            = "UTF-8";
805 static bool opt_utf8                    = TRUE;
806 static char opt_codeset[20]             = "UTF-8";
807 static iconv_t opt_iconv                = ICONV_NONE;
808 static char opt_search[SIZEOF_STR]      = "";
809 static char opt_cdup[SIZEOF_STR]        = "";
810 static char opt_prefix[SIZEOF_STR]      = "";
811 static char opt_git_dir[SIZEOF_STR]     = "";
812 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR]      = "";
814 static FILE *opt_tty                    = NULL;
816 #define is_initial_commit()     (!*opt_head_rev)
817 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822         enum request request = REQ_VIEW_MAIN;
823         const char *subcommand;
824         bool seen_dashdash = FALSE;
825         /* XXX: This is vulnerable to the user overriding options
826          * required for the main view parser. */
827         static const char *custom_argv[SIZEOF_ARG] = {
828                 "git", "log", "--no-color", "--pretty=raw", "--parents",
829                         "--topo-order", NULL
830         };
831         int i, j = 6;
833         if (!isatty(STDIN_FILENO))
834                 return REQ_VIEW_PAGER;
836         if (argc <= 1)
837                 return REQ_VIEW_MAIN;
839         subcommand = argv[1];
840         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841                 if (!strcmp(subcommand, "-S"))
842                         warn("`-S' has been deprecated; use `tig status' instead");
843                 if (argc > 2)
844                         warn("ignoring arguments after `%s'", subcommand);
845                 return REQ_VIEW_STATUS;
847         } else if (!strcmp(subcommand, "blame")) {
848                 if (argc <= 2 || argc > 4)
849                         die("invalid number of options to blame\n\n%s", usage);
851                 i = 2;
852                 if (argc == 4) {
853                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854                         i++;
855                 }
857                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858                 return REQ_VIEW_BLAME;
860         } else if (!strcmp(subcommand, "show")) {
861                 request = REQ_VIEW_DIFF;
863         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865                 warn("`tig %s' has been deprecated", subcommand);
867         } else {
868                 subcommand = NULL;
869         }
871         if (subcommand) {
872                 custom_argv[1] = subcommand;
873                 j = 2;
874         }
876         for (i = 1 + !!subcommand; i < argc; i++) {
877                 const char *opt = argv[i];
879                 if (seen_dashdash || !strcmp(opt, "--")) {
880                         seen_dashdash = TRUE;
882                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883                         printf("tig version %s\n", TIG_VERSION);
884                         return REQ_NONE;
886                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887                         printf("%s\n", usage);
888                         return REQ_NONE;
889                 }
891                 custom_argv[j++] = opt;
892                 if (j >= ARRAY_SIZE(custom_argv))
893                         die("command too long");
894         }
896         custom_argv[j] = NULL;
897         *run_argv = custom_argv;
899         return request;
903 /*
904  * Line-oriented content detection.
905  */
907 #define LINE_INFO \
908 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
910 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
911 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
913 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
921 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
922 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
925 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
927 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
929 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
932 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
933 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
935 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
936 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
937 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
938 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
939 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
940 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
941 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
942 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
943 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
944 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
945 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
946 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
950 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
951 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
952 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
954 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
955 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
956 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
957 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
958 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
959 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
960 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
962 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
963 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967         LINE_##type
968         LINE_INFO,
969         LINE_NONE
970 #undef  LINE
971 };
973 struct line_info {
974         const char *name;       /* Option name. */
975         int namelen;            /* Size of option name. */
976         const char *line;       /* The start of line to match. */
977         int linelen;            /* Size of string to match. */
978         int fg, bg, attr;       /* Color and text attributes for the lines. */
979 };
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984         LINE_INFO
985 #undef  LINE
986 };
988 static enum line_type
989 get_line_type(const char *line)
991         int linelen = strlen(line);
992         enum line_type type;
994         for (type = 0; type < ARRAY_SIZE(line_info); type++)
995                 /* Case insensitive search matches Signed-off-by lines better. */
996                 if (linelen >= line_info[type].linelen &&
997                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998                         return type;
1000         return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006         assert(type < ARRAY_SIZE(line_info));
1007         return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013         size_t namelen = strlen(name);
1014         enum line_type type;
1016         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017                 if (namelen == line_info[type].namelen &&
1018                     !string_enum_compare(line_info[type].name, name, namelen))
1019                         return &line_info[type];
1021         return NULL;
1024 static void
1025 init_colors(void)
1027         int default_bg = line_info[LINE_DEFAULT].bg;
1028         int default_fg = line_info[LINE_DEFAULT].fg;
1029         enum line_type type;
1031         start_color();
1033         if (assume_default_colors(default_fg, default_bg) == ERR) {
1034                 default_bg = COLOR_BLACK;
1035                 default_fg = COLOR_WHITE;
1036         }
1038         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039                 struct line_info *info = &line_info[type];
1040                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043                 init_pair(type, fg, bg);
1044         }
1047 struct line {
1048         enum line_type type;
1050         /* State flags */
1051         unsigned int selected:1;
1052         unsigned int dirty:1;
1053         unsigned int cleareol:1;
1055         void *data;             /* User data */
1056 };
1059 /*
1060  * Keys
1061  */
1063 struct keybinding {
1064         int alias;
1065         enum request request;
1066 };
1068 static struct keybinding default_keybindings[] = {
1069         /* View switching */
1070         { 'm',          REQ_VIEW_MAIN },
1071         { 'd',          REQ_VIEW_DIFF },
1072         { 'l',          REQ_VIEW_LOG },
1073         { 't',          REQ_VIEW_TREE },
1074         { 'f',          REQ_VIEW_BLOB },
1075         { 'B',          REQ_VIEW_BLAME },
1076         { 'p',          REQ_VIEW_PAGER },
1077         { 'h',          REQ_VIEW_HELP },
1078         { 'S',          REQ_VIEW_STATUS },
1079         { 'c',          REQ_VIEW_STAGE },
1081         /* View manipulation */
1082         { 'q',          REQ_VIEW_CLOSE },
1083         { KEY_TAB,      REQ_VIEW_NEXT },
1084         { KEY_RETURN,   REQ_ENTER },
1085         { KEY_UP,       REQ_PREVIOUS },
1086         { KEY_DOWN,     REQ_NEXT },
1087         { 'R',          REQ_REFRESH },
1088         { KEY_F(5),     REQ_REFRESH },
1089         { 'O',          REQ_MAXIMIZE },
1091         /* Cursor navigation */
1092         { 'k',          REQ_MOVE_UP },
1093         { 'j',          REQ_MOVE_DOWN },
1094         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1095         { KEY_END,      REQ_MOVE_LAST_LINE },
1096         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1097         { ' ',          REQ_MOVE_PAGE_DOWN },
1098         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1099         { 'b',          REQ_MOVE_PAGE_UP },
1100         { '-',          REQ_MOVE_PAGE_UP },
1102         /* Scrolling */
1103         { KEY_IC,       REQ_SCROLL_LINE_UP },
1104         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1105         { 'w',          REQ_SCROLL_PAGE_UP },
1106         { 's',          REQ_SCROLL_PAGE_DOWN },
1108         /* Searching */
1109         { '/',          REQ_SEARCH },
1110         { '?',          REQ_SEARCH_BACK },
1111         { 'n',          REQ_FIND_NEXT },
1112         { 'N',          REQ_FIND_PREV },
1114         /* Misc */
1115         { 'Q',          REQ_QUIT },
1116         { 'z',          REQ_STOP_LOADING },
1117         { 'v',          REQ_SHOW_VERSION },
1118         { 'r',          REQ_SCREEN_REDRAW },
1119         { '.',          REQ_TOGGLE_LINENO },
1120         { 'D',          REQ_TOGGLE_DATE },
1121         { 'A',          REQ_TOGGLE_AUTHOR },
1122         { 'g',          REQ_TOGGLE_REV_GRAPH },
1123         { 'F',          REQ_TOGGLE_REFS },
1124         { ':',          REQ_PROMPT },
1125         { 'u',          REQ_STATUS_UPDATE },
1126         { '!',          REQ_STATUS_REVERT },
1127         { 'M',          REQ_STATUS_MERGE },
1128         { '@',          REQ_STAGE_NEXT },
1129         { ',',          REQ_PARENT },
1130         { 'e',          REQ_EDIT },
1131 };
1133 #define KEYMAP_INFO \
1134         KEYMAP_(GENERIC), \
1135         KEYMAP_(MAIN), \
1136         KEYMAP_(DIFF), \
1137         KEYMAP_(LOG), \
1138         KEYMAP_(TREE), \
1139         KEYMAP_(BLOB), \
1140         KEYMAP_(BLAME), \
1141         KEYMAP_(PAGER), \
1142         KEYMAP_(HELP), \
1143         KEYMAP_(STATUS), \
1144         KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148         KEYMAP_INFO
1149 #undef  KEYMAP_
1150 };
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154         KEYMAP_INFO
1155 #undef  KEYMAP_
1156 };
1158 #define set_keymap(map, name) \
1159         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162         struct keybinding *data;
1163         size_t size;
1164 };
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171         struct keybinding_table *table = &keybindings[keymap];
1173         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174         if (!table->data)
1175                 die("Failed to allocate keybinding");
1176         table->data[table->size].alias = key;
1177         table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181  * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185         size_t i;
1187         for (i = 0; i < keybindings[keymap].size; i++)
1188                 if (keybindings[keymap].data[i].alias == key)
1189                         return keybindings[keymap].data[i].request;
1191         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193                         return keybindings[KEYMAP_GENERIC].data[i].request;
1195         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196                 if (default_keybindings[i].alias == key)
1197                         return default_keybindings[i].request;
1199         return (enum request) key;
1203 struct key {
1204         const char *name;
1205         int value;
1206 };
1208 static struct key key_table[] = {
1209         { "Enter",      KEY_RETURN },
1210         { "Space",      ' ' },
1211         { "Backspace",  KEY_BACKSPACE },
1212         { "Tab",        KEY_TAB },
1213         { "Escape",     KEY_ESC },
1214         { "Left",       KEY_LEFT },
1215         { "Right",      KEY_RIGHT },
1216         { "Up",         KEY_UP },
1217         { "Down",       KEY_DOWN },
1218         { "Insert",     KEY_IC },
1219         { "Delete",     KEY_DC },
1220         { "Hash",       '#' },
1221         { "Home",       KEY_HOME },
1222         { "End",        KEY_END },
1223         { "PageUp",     KEY_PPAGE },
1224         { "PageDown",   KEY_NPAGE },
1225         { "F1",         KEY_F(1) },
1226         { "F2",         KEY_F(2) },
1227         { "F3",         KEY_F(3) },
1228         { "F4",         KEY_F(4) },
1229         { "F5",         KEY_F(5) },
1230         { "F6",         KEY_F(6) },
1231         { "F7",         KEY_F(7) },
1232         { "F8",         KEY_F(8) },
1233         { "F9",         KEY_F(9) },
1234         { "F10",        KEY_F(10) },
1235         { "F11",        KEY_F(11) },
1236         { "F12",        KEY_F(12) },
1237 };
1239 static int
1240 get_key_value(const char *name)
1242         int i;
1244         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245                 if (!strcasecmp(key_table[i].name, name))
1246                         return key_table[i].value;
1248         if (strlen(name) == 1 && isprint(*name))
1249                 return (int) *name;
1251         return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257         static char key_char[] = "'X'";
1258         const char *seq = NULL;
1259         int key;
1261         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262                 if (key_table[key].value == key_value)
1263                         seq = key_table[key].name;
1265         if (seq == NULL &&
1266             key_value < 127 &&
1267             isprint(key_value)) {
1268                 key_char[1] = (char) key_value;
1269                 seq = key_char;
1270         }
1272         return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278         static char buf[BUFSIZ];
1279         size_t pos = 0;
1280         char *sep = "";
1281         int i;
1283         buf[pos] = 0;
1285         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286                 struct keybinding *keybinding = &default_keybindings[i];
1288                 if (keybinding->request != request)
1289                         continue;
1291                 if (!string_format_from(buf, &pos, "%s%s", sep,
1292                                         get_key_name(keybinding->alias)))
1293                         return "Too many keybindings!";
1294                 sep = ", ";
1295         }
1297         return buf;
1300 struct run_request {
1301         enum keymap keymap;
1302         int key;
1303         const char *argv[SIZEOF_ARG];
1304 };
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312         struct run_request *req;
1314         if (argc >= ARRAY_SIZE(req->argv) - 1)
1315                 return REQ_NONE;
1317         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318         if (!req)
1319                 return REQ_NONE;
1321         run_request = req;
1322         req = &run_request[run_requests];
1323         req->keymap = keymap;
1324         req->key = key;
1325         req->argv[0] = NULL;
1327         if (!format_argv(req->argv, argv, FORMAT_NONE))
1328                 return REQ_NONE;
1330         return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336         if (request <= REQ_NONE)
1337                 return NULL;
1338         return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345         const char *gc[] = { "git", "gc", NULL };
1346         struct {
1347                 enum keymap keymap;
1348                 int key;
1349                 int argc;
1350                 const char **argv;
1351         } reqs[] = {
1352                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354         };
1355         int i;
1357         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358                 enum request req;
1360                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361                 if (req != REQ_NONE)
1362                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1363         }
1366 /*
1367  * User config file handling.
1368  */
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372         COLOR_MAP(DEFAULT),
1373         COLOR_MAP(BLACK),
1374         COLOR_MAP(BLUE),
1375         COLOR_MAP(CYAN),
1376         COLOR_MAP(GREEN),
1377         COLOR_MAP(MAGENTA),
1378         COLOR_MAP(RED),
1379         COLOR_MAP(WHITE),
1380         COLOR_MAP(YELLOW),
1381 };
1383 #define set_color(color, name) \
1384         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388         ATTR_MAP(NORMAL),
1389         ATTR_MAP(BLINK),
1390         ATTR_MAP(BOLD),
1391         ATTR_MAP(DIM),
1392         ATTR_MAP(REVERSE),
1393         ATTR_MAP(STANDOUT),
1394         ATTR_MAP(UNDERLINE),
1395 };
1397 #define set_attribute(attr, name) \
1398         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int   config_lineno;
1401 static bool  config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408         struct line_info *info;
1410         if (argc != 3 && argc != 4) {
1411                 config_msg = "Wrong number of arguments given to color command";
1412                 return ERR;
1413         }
1415         info = get_line_info(argv[0]);
1416         if (!info) {
1417                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418                         info = get_line_info("delimiter");
1420                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421                         info = get_line_info("date");
1423                 } else {
1424                         config_msg = "Unknown color name";
1425                         return ERR;
1426                 }
1427         }
1429         if (set_color(&info->fg, argv[1]) == ERR ||
1430             set_color(&info->bg, argv[2]) == ERR) {
1431                 config_msg = "Unknown color";
1432                 return ERR;
1433         }
1435         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436                 config_msg = "Unknown attribute";
1437                 return ERR;
1438         }
1440         return OK;
1443 static bool parse_bool(const char *s)
1445         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446                 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452         int value = atoi(s);
1454         return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461         if (argc != 3) {
1462                 config_msg = "Wrong number of arguments given to set command";
1463                 return ERR;
1464         }
1466         if (strcmp(argv[1], "=")) {
1467                 config_msg = "No value assigned";
1468                 return ERR;
1469         }
1471         if (!strcmp(argv[0], "show-author")) {
1472                 opt_author = parse_bool(argv[2]);
1473                 return OK;
1474         }
1476         if (!strcmp(argv[0], "show-date")) {
1477                 opt_date = parse_bool(argv[2]);
1478                 return OK;
1479         }
1481         if (!strcmp(argv[0], "show-rev-graph")) {
1482                 opt_rev_graph = parse_bool(argv[2]);
1483                 return OK;
1484         }
1486         if (!strcmp(argv[0], "show-refs")) {
1487                 opt_show_refs = parse_bool(argv[2]);
1488                 return OK;
1489         }
1491         if (!strcmp(argv[0], "show-line-numbers")) {
1492                 opt_line_number = parse_bool(argv[2]);
1493                 return OK;
1494         }
1496         if (!strcmp(argv[0], "line-graphics")) {
1497                 opt_line_graphics = parse_bool(argv[2]);
1498                 return OK;
1499         }
1501         if (!strcmp(argv[0], "line-number-interval")) {
1502                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503                 return OK;
1504         }
1506         if (!strcmp(argv[0], "author-width")) {
1507                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508                 return OK;
1509         }
1511         if (!strcmp(argv[0], "tab-size")) {
1512                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513                 return OK;
1514         }
1516         if (!strcmp(argv[0], "commit-encoding")) {
1517                 const char *arg = argv[2];
1518                 int arglen = strlen(arg);
1520                 switch (arg[0]) {
1521                 case '"':
1522                 case '\'':
1523                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524                                 config_msg = "Unmatched quotation";
1525                                 return ERR;
1526                         }
1527                         arg += 1; arglen -= 2;
1528                 default:
1529                         string_ncopy(opt_encoding, arg, strlen(arg));
1530                         return OK;
1531                 }
1532         }
1534         config_msg = "Unknown variable name";
1535         return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542         enum request request;
1543         int keymap;
1544         int key;
1546         if (argc < 3) {
1547                 config_msg = "Wrong number of arguments given to bind command";
1548                 return ERR;
1549         }
1551         if (set_keymap(&keymap, argv[0]) == ERR) {
1552                 config_msg = "Unknown key map";
1553                 return ERR;
1554         }
1556         key = get_key_value(argv[1]);
1557         if (key == ERR) {
1558                 config_msg = "Unknown key";
1559                 return ERR;
1560         }
1562         request = get_request(argv[2]);
1563         if (request == REQ_NONE) {
1564                 struct {
1565                         const char *name;
1566                         enum request request;
1567                 } obsolete[] = {
1568                         { "cherry-pick",        REQ_NONE },
1569                         { "screen-resize",      REQ_NONE },
1570                         { "tree-parent",        REQ_PARENT },
1571                 };
1572                 size_t namelen = strlen(argv[2]);
1573                 int i;
1575                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576                         if (namelen != strlen(obsolete[i].name) ||
1577                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1578                                 continue;
1579                         if (obsolete[i].request != REQ_NONE)
1580                                 add_keybinding(keymap, obsolete[i].request, key);
1581                         config_msg = "Obsolete request name";
1582                         return ERR;
1583                 }
1584         }
1585         if (request == REQ_NONE && *argv[2]++ == '!')
1586                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587         if (request == REQ_NONE) {
1588                 config_msg = "Unknown request name";
1589                 return ERR;
1590         }
1592         add_keybinding(keymap, request, key);
1594         return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600         const char *argv[SIZEOF_ARG];
1601         int argc = 0;
1603         if (!argv_from_string(argv, &argc, value)) {
1604                 config_msg = "Too many option arguments";
1605                 return ERR;
1606         }
1608         if (!strcmp(opt, "color"))
1609                 return option_color_command(argc, argv);
1611         if (!strcmp(opt, "set"))
1612                 return option_set_command(argc, argv);
1614         if (!strcmp(opt, "bind"))
1615                 return option_bind_command(argc, argv);
1617         config_msg = "Unknown option command";
1618         return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624         int status = OK;
1626         config_lineno++;
1627         config_msg = "Internal error";
1629         /* Check for comment markers, since read_properties() will
1630          * only ensure opt and value are split at first " \t". */
1631         optlen = strcspn(opt, "#");
1632         if (optlen == 0)
1633                 return OK;
1635         if (opt[optlen] != 0) {
1636                 config_msg = "No option value";
1637                 status = ERR;
1639         }  else {
1640                 /* Look for comment endings in the value. */
1641                 size_t len = strcspn(value, "#");
1643                 if (len < valuelen) {
1644                         valuelen = len;
1645                         value[valuelen] = 0;
1646                 }
1648                 status = set_option(opt, value);
1649         }
1651         if (status == ERR) {
1652                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653                         config_lineno, (int) optlen, opt, config_msg);
1654                 config_errors = TRUE;
1655         }
1657         /* Always keep going if errors are encountered. */
1658         return OK;
1661 static void
1662 load_option_file(const char *path)
1664         struct io io = {};
1666         /* It's ok that the file doesn't exist. */
1667         if (!io_open(&io, path))
1668                 return;
1670         config_lineno = 0;
1671         config_errors = FALSE;
1673         if (read_properties(&io, " \t", read_option) == ERR ||
1674             config_errors == TRUE)
1675                 fprintf(stderr, "Errors while loading %s.\n", path);
1678 static int
1679 load_options(void)
1681         const char *home = getenv("HOME");
1682         const char *tigrc_user = getenv("TIGRC_USER");
1683         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684         char buf[SIZEOF_STR];
1686         add_builtin_run_requests();
1688         if (!tigrc_system) {
1689                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690                         return ERR;
1691                 tigrc_system = buf;
1692         }
1693         load_option_file(tigrc_system);
1695         if (!tigrc_user) {
1696                 if (!home || !string_format(buf, "%s/.tigrc", home))
1697                         return ERR;
1698                 tigrc_user = buf;
1699         }
1700         load_option_file(tigrc_user);
1702         return OK;
1706 /*
1707  * The viewer
1708  */
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 /* Reading from the prompt? */
1718 static bool input_mode = FALSE;
1720 #define foreach_displayed_view(view, i) \
1721         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1723 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1725 /* Current head and commit ID */
1726 static char ref_blob[SIZEOF_REF]        = "";
1727 static char ref_commit[SIZEOF_REF]      = "HEAD";
1728 static char ref_head[SIZEOF_REF]        = "HEAD";
1730 struct view {
1731         const char *name;       /* View name */
1732         const char *cmd_env;    /* Command line set via environment */
1733         const char *id;         /* Points to either of ref_{head,commit,blob} */
1735         struct view_ops *ops;   /* View operations */
1737         enum keymap keymap;     /* What keymap does this view have */
1738         bool git_dir;           /* Whether the view requires a git directory. */
1740         char ref[SIZEOF_REF];   /* Hovered commit reference */
1741         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1743         int height, width;      /* The width and height of the main window */
1744         WINDOW *win;            /* The main window */
1745         WINDOW *title;          /* The title window living below the main window */
1747         /* Navigation */
1748         unsigned long offset;   /* Offset of the window top */
1749         unsigned long lineno;   /* Current line number */
1750         unsigned long p_offset; /* Previous offset of the window top */
1751         unsigned long p_lineno; /* Previous current line number */
1752         bool p_restore;         /* Should the previous position be restored. */
1754         /* Searching */
1755         char grep[SIZEOF_STR];  /* Search string */
1756         regex_t *regex;         /* Pre-compiled regex */
1758         /* If non-NULL, points to the view that opened this view. If this view
1759          * is closed tig will switch back to the parent view. */
1760         struct view *parent;
1762         /* Buffering */
1763         size_t lines;           /* Total number of lines */
1764         struct line *line;      /* Line index */
1765         size_t line_alloc;      /* Total number of allocated lines */
1766         unsigned int digits;    /* Number of digits in the lines member. */
1768         /* Drawing */
1769         struct line *curline;   /* Line currently being drawn. */
1770         enum line_type curtype; /* Attribute currently used for drawing. */
1771         unsigned long col;      /* Column when drawing. */
1773         /* Loading */
1774         struct io io;
1775         struct io *pipe;
1776         time_t start_time;
1777         time_t update_secs;
1778 };
1780 struct view_ops {
1781         /* What type of content being displayed. Used in the title bar. */
1782         const char *type;
1783         /* Default command arguments. */
1784         const char **argv;
1785         /* Open and reads in all view content. */
1786         bool (*open)(struct view *view);
1787         /* Read one line; updates view->line. */
1788         bool (*read)(struct view *view, char *data);
1789         /* Draw one line; @lineno must be < view->height. */
1790         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1791         /* Depending on view handle a special requests. */
1792         enum request (*request)(struct view *view, enum request request, struct line *line);
1793         /* Search for regex in a line. */
1794         bool (*grep)(struct view *view, struct line *line);
1795         /* Select line */
1796         void (*select)(struct view *view, struct line *line);
1797 };
1799 static struct view_ops blame_ops;
1800 static struct view_ops blob_ops;
1801 static struct view_ops diff_ops;
1802 static struct view_ops help_ops;
1803 static struct view_ops log_ops;
1804 static struct view_ops main_ops;
1805 static struct view_ops pager_ops;
1806 static struct view_ops stage_ops;
1807 static struct view_ops status_ops;
1808 static struct view_ops tree_ops;
1810 #define VIEW_STR(name, env, ref, ops, map, git) \
1811         { name, #env, ref, ops, map, git }
1813 #define VIEW_(id, name, ops, git, ref) \
1814         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1817 static struct view views[] = {
1818         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1819         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1820         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1821         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1822         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1823         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1824         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1825         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1826         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1827         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1828 };
1830 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1831 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1833 #define foreach_view(view, i) \
1834         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1836 #define view_is_displayed(view) \
1837         (view == display[0] || view == display[1])
1840 enum line_graphic {
1841         LINE_GRAPHIC_VLINE
1842 };
1844 static int line_graphics[] = {
1845         /* LINE_GRAPHIC_VLINE: */ '|'
1846 };
1848 static inline void
1849 set_view_attr(struct view *view, enum line_type type)
1851         if (!view->curline->selected && view->curtype != type) {
1852                 wattrset(view->win, get_line_attr(type));
1853                 wchgat(view->win, -1, 0, type, NULL);
1854                 view->curtype = type;
1855         }
1858 static int
1859 draw_chars(struct view *view, enum line_type type, const char *string,
1860            int max_len, bool use_tilde)
1862         int len = 0;
1863         int col = 0;
1864         int trimmed = FALSE;
1866         if (max_len <= 0)
1867                 return 0;
1869         if (opt_utf8) {
1870                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1871         } else {
1872                 col = len = strlen(string);
1873                 if (len > max_len) {
1874                         if (use_tilde) {
1875                                 max_len -= 1;
1876                         }
1877                         col = len = max_len;
1878                         trimmed = TRUE;
1879                 }
1880         }
1882         set_view_attr(view, type);
1883         waddnstr(view->win, string, len);
1884         if (trimmed && use_tilde) {
1885                 set_view_attr(view, LINE_DELIMITER);
1886                 waddch(view->win, '~');
1887                 col++;
1888         }
1890         return col;
1893 static int
1894 draw_space(struct view *view, enum line_type type, int max, int spaces)
1896         static char space[] = "                    ";
1897         int col = 0;
1899         spaces = MIN(max, spaces);
1901         while (spaces > 0) {
1902                 int len = MIN(spaces, sizeof(space) - 1);
1904                 col += draw_chars(view, type, space, spaces, FALSE);
1905                 spaces -= len;
1906         }
1908         return col;
1911 static bool
1912 draw_lineno(struct view *view, unsigned int lineno)
1914         char number[10];
1915         int digits3 = view->digits < 3 ? 3 : view->digits;
1916         int max_number = MIN(digits3, STRING_SIZE(number));
1917         int max = view->width - view->col;
1918         int col;
1920         if (max < max_number)
1921                 max_number = max;
1923         lineno += view->offset + 1;
1924         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925                 static char fmt[] = "%1ld";
1927                 if (view->digits <= 9)
1928                         fmt[1] = '0' + digits3;
1930                 if (!string_format(number, fmt, lineno))
1931                         number[0] = 0;
1932                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933         } else {
1934                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935         }
1937         if (col < max) {
1938                 set_view_attr(view, LINE_DEFAULT);
1939                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940                 col++;
1941         }
1943         if (col < max)
1944                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1945         view->col += col;
1947         return view->width - view->col <= 0;
1950 static bool
1951 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1953         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1954         return view->width - view->col <= 0;
1957 static bool
1958 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1960         int max = view->width - view->col;
1961         int i;
1963         if (max < size)
1964                 size = max;
1966         set_view_attr(view, type);
1967         /* Using waddch() instead of waddnstr() ensures that
1968          * they'll be rendered correctly for the cursor line. */
1969         for (i = 0; i < size; i++)
1970                 waddch(view->win, graphic[i]);
1972         view->col += size;
1973         if (size < max) {
1974                 waddch(view->win, ' ');
1975                 view->col++;
1976         }
1978         return view->width - view->col <= 0;
1981 static bool
1982 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1984         int max = MIN(view->width - view->col, len);
1985         int col;
1987         if (text)
1988                 col = draw_chars(view, type, text, max - 1, trim);
1989         else
1990                 col = draw_space(view, type, max - 1, max - 1);
1992         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1993         return view->width - view->col <= 0;
1996 static bool
1997 draw_date(struct view *view, struct tm *time)
1999         char buf[DATE_COLS];
2000         char *date;
2001         int timelen = 0;
2003         if (time)
2004                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2005         date = timelen ? buf : NULL;
2007         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2010 static bool
2011 draw_view_line(struct view *view, unsigned int lineno)
2013         struct line *line;
2014         bool selected = (view->offset + lineno == view->lineno);
2016         assert(view_is_displayed(view));
2018         if (view->offset + lineno >= view->lines)
2019                 return FALSE;
2021         line = &view->line[view->offset + lineno];
2023         wmove(view->win, lineno, 0);
2024         if (line->cleareol)
2025                 wclrtoeol(view->win);
2026         view->col = 0;
2027         view->curline = line;
2028         view->curtype = LINE_NONE;
2029         line->selected = FALSE;
2030         line->dirty = line->cleareol = 0;
2032         if (selected) {
2033                 set_view_attr(view, LINE_CURSOR);
2034                 line->selected = TRUE;
2035                 view->ops->select(view, line);
2036         }
2038         return view->ops->draw(view, line, lineno);
2041 static void
2042 redraw_view_dirty(struct view *view)
2044         bool dirty = FALSE;
2045         int lineno;
2047         for (lineno = 0; lineno < view->height; lineno++) {
2048                 if (view->offset + lineno >= view->lines)
2049                         break;
2050                 if (!view->line[view->offset + lineno].dirty)
2051                         continue;
2052                 dirty = TRUE;
2053                 if (!draw_view_line(view, lineno))
2054                         break;
2055         }
2057         if (!dirty)
2058                 return;
2059         if (input_mode)
2060                 wnoutrefresh(view->win);
2061         else
2062                 wrefresh(view->win);
2065 static void
2066 redraw_view_from(struct view *view, int lineno)
2068         assert(0 <= lineno && lineno < view->height);
2070         for (; lineno < view->height; lineno++) {
2071                 if (!draw_view_line(view, lineno))
2072                         break;
2073         }
2075         if (input_mode)
2076                 wnoutrefresh(view->win);
2077         else
2078                 wrefresh(view->win);
2081 static void
2082 redraw_view(struct view *view)
2084         werase(view->win);
2085         redraw_view_from(view, 0);
2089 static void
2090 update_display_cursor(struct view *view)
2092         /* Move the cursor to the right-most column of the cursor line.
2093          *
2094          * XXX: This could turn out to be a bit expensive, but it ensures that
2095          * the cursor does not jump around. */
2096         if (view->lines) {
2097                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2098                 wrefresh(view->win);
2099         }
2102 static void
2103 update_view_title(struct view *view)
2105         char buf[SIZEOF_STR];
2106         char state[SIZEOF_STR];
2107         size_t bufpos = 0, statelen = 0;
2109         assert(view_is_displayed(view));
2111         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2112                 unsigned int view_lines = view->offset + view->height;
2113                 unsigned int lines = view->lines
2114                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2115                                    : 0;
2117                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2118                                    view->ops->type,
2119                                    view->lineno + 1,
2120                                    view->lines,
2121                                    lines);
2123         }
2125         if (view->pipe) {
2126                 time_t secs = time(NULL) - view->start_time;
2128                 /* Three git seconds are a long time ... */
2129                 if (secs > 2)
2130                         string_format_from(state, &statelen, " loading %lds", secs);
2131         }
2133         string_format_from(buf, &bufpos, "[%s]", view->name);
2134         if (*view->ref && bufpos < view->width) {
2135                 size_t refsize = strlen(view->ref);
2136                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2138                 if (minsize < view->width)
2139                         refsize = view->width - minsize + 7;
2140                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2141         }
2143         if (statelen && bufpos < view->width) {
2144                 string_format_from(buf, &bufpos, "%s", state);
2145         }
2147         if (view == display[current_view])
2148                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2149         else
2150                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2152         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2153         wclrtoeol(view->title);
2154         wmove(view->title, 0, view->width - 1);
2156         if (input_mode)
2157                 wnoutrefresh(view->title);
2158         else
2159                 wrefresh(view->title);
2162 static void
2163 resize_display(void)
2165         int offset, i;
2166         struct view *base = display[0];
2167         struct view *view = display[1] ? display[1] : display[0];
2169         /* Setup window dimensions */
2171         getmaxyx(stdscr, base->height, base->width);
2173         /* Make room for the status window. */
2174         base->height -= 1;
2176         if (view != base) {
2177                 /* Horizontal split. */
2178                 view->width   = base->width;
2179                 view->height  = SCALE_SPLIT_VIEW(base->height);
2180                 base->height -= view->height;
2182                 /* Make room for the title bar. */
2183                 view->height -= 1;
2184         }
2186         /* Make room for the title bar. */
2187         base->height -= 1;
2189         offset = 0;
2191         foreach_displayed_view (view, i) {
2192                 if (!view->win) {
2193                         view->win = newwin(view->height, 0, offset, 0);
2194                         if (!view->win)
2195                                 die("Failed to create %s view", view->name);
2197                         scrollok(view->win, FALSE);
2199                         view->title = newwin(1, 0, offset + view->height, 0);
2200                         if (!view->title)
2201                                 die("Failed to create title window");
2203                 } else {
2204                         wresize(view->win, view->height, view->width);
2205                         mvwin(view->win,   offset, 0);
2206                         mvwin(view->title, offset + view->height, 0);
2207                 }
2209                 offset += view->height + 1;
2210         }
2213 static void
2214 redraw_display(bool clear)
2216         struct view *view;
2217         int i;
2219         foreach_displayed_view (view, i) {
2220                 if (clear)
2221                         wclear(view->win);
2222                 redraw_view(view);
2223                 update_view_title(view);
2224         }
2226         if (display[current_view])
2227                 update_display_cursor(display[current_view]);
2230 static void
2231 toggle_view_option(bool *option, const char *help)
2233         *option = !*option;
2234         redraw_display(FALSE);
2235         report("%sabling %s", *option ? "En" : "Dis", help);
2238 /*
2239  * Navigation
2240  */
2242 /* Scrolling backend */
2243 static void
2244 do_scroll_view(struct view *view, int lines)
2246         bool redraw_current_line = FALSE;
2248         /* The rendering expects the new offset. */
2249         view->offset += lines;
2251         assert(0 <= view->offset && view->offset < view->lines);
2252         assert(lines);
2254         /* Move current line into the view. */
2255         if (view->lineno < view->offset) {
2256                 view->lineno = view->offset;
2257                 redraw_current_line = TRUE;
2258         } else if (view->lineno >= view->offset + view->height) {
2259                 view->lineno = view->offset + view->height - 1;
2260                 redraw_current_line = TRUE;
2261         }
2263         assert(view->offset <= view->lineno && view->lineno < view->lines);
2265         /* Redraw the whole screen if scrolling is pointless. */
2266         if (view->height < ABS(lines)) {
2267                 redraw_view(view);
2269         } else {
2270                 int line = lines > 0 ? view->height - lines : 0;
2271                 int end = line + ABS(lines);
2273                 scrollok(view->win, TRUE);
2274                 wscrl(view->win, lines);
2275                 scrollok(view->win, FALSE);
2277                 for (; line < end; line++) {
2278                         if (!draw_view_line(view, line))
2279                                 break;
2280                 }
2282                 if (redraw_current_line)
2283                         draw_view_line(view, view->lineno - view->offset);
2284         }
2286         wrefresh(view->win);
2287         report("");
2290 /* Scroll frontend */
2291 static void
2292 scroll_view(struct view *view, enum request request)
2294         int lines = 1;
2296         assert(view_is_displayed(view));
2298         switch (request) {
2299         case REQ_SCROLL_PAGE_DOWN:
2300                 lines = view->height;
2301         case REQ_SCROLL_LINE_DOWN:
2302                 if (view->offset + lines > view->lines)
2303                         lines = view->lines - view->offset;
2305                 if (lines == 0 || view->offset + view->height >= view->lines) {
2306                         report("Cannot scroll beyond the last line");
2307                         return;
2308                 }
2309                 break;
2311         case REQ_SCROLL_PAGE_UP:
2312                 lines = view->height;
2313         case REQ_SCROLL_LINE_UP:
2314                 if (lines > view->offset)
2315                         lines = view->offset;
2317                 if (lines == 0) {
2318                         report("Cannot scroll beyond the first line");
2319                         return;
2320                 }
2322                 lines = -lines;
2323                 break;
2325         default:
2326                 die("request %d not handled in switch", request);
2327         }
2329         do_scroll_view(view, lines);
2332 /* Cursor moving */
2333 static void
2334 move_view(struct view *view, enum request request)
2336         int scroll_steps = 0;
2337         int steps;
2339         switch (request) {
2340         case REQ_MOVE_FIRST_LINE:
2341                 steps = -view->lineno;
2342                 break;
2344         case REQ_MOVE_LAST_LINE:
2345                 steps = view->lines - view->lineno - 1;
2346                 break;
2348         case REQ_MOVE_PAGE_UP:
2349                 steps = view->height > view->lineno
2350                       ? -view->lineno : -view->height;
2351                 break;
2353         case REQ_MOVE_PAGE_DOWN:
2354                 steps = view->lineno + view->height >= view->lines
2355                       ? view->lines - view->lineno - 1 : view->height;
2356                 break;
2358         case REQ_MOVE_UP:
2359                 steps = -1;
2360                 break;
2362         case REQ_MOVE_DOWN:
2363                 steps = 1;
2364                 break;
2366         default:
2367                 die("request %d not handled in switch", request);
2368         }
2370         if (steps <= 0 && view->lineno == 0) {
2371                 report("Cannot move beyond the first line");
2372                 return;
2374         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2375                 report("Cannot move beyond the last line");
2376                 return;
2377         }
2379         /* Move the current line */
2380         view->lineno += steps;
2381         assert(0 <= view->lineno && view->lineno < view->lines);
2383         /* Check whether the view needs to be scrolled */
2384         if (view->lineno < view->offset ||
2385             view->lineno >= view->offset + view->height) {
2386                 scroll_steps = steps;
2387                 if (steps < 0 && -steps > view->offset) {
2388                         scroll_steps = -view->offset;
2390                 } else if (steps > 0) {
2391                         if (view->lineno == view->lines - 1 &&
2392                             view->lines > view->height) {
2393                                 scroll_steps = view->lines - view->offset - 1;
2394                                 if (scroll_steps >= view->height)
2395                                         scroll_steps -= view->height - 1;
2396                         }
2397                 }
2398         }
2400         if (!view_is_displayed(view)) {
2401                 view->offset += scroll_steps;
2402                 assert(0 <= view->offset && view->offset < view->lines);
2403                 view->ops->select(view, &view->line[view->lineno]);
2404                 return;
2405         }
2407         /* Repaint the old "current" line if we be scrolling */
2408         if (ABS(steps) < view->height)
2409                 draw_view_line(view, view->lineno - steps - view->offset);
2411         if (scroll_steps) {
2412                 do_scroll_view(view, scroll_steps);
2413                 return;
2414         }
2416         /* Draw the current line */
2417         draw_view_line(view, view->lineno - view->offset);
2419         wrefresh(view->win);
2420         report("");
2424 /*
2425  * Searching
2426  */
2428 static void search_view(struct view *view, enum request request);
2430 static void
2431 select_view_line(struct view *view, unsigned long lineno)
2433         if (lineno - view->offset >= view->height) {
2434                 view->offset = lineno;
2435                 view->lineno = lineno;
2436                 if (view_is_displayed(view))
2437                         redraw_view(view);
2439         } else {
2440                 unsigned long old_lineno = view->lineno - view->offset;
2442                 view->lineno = lineno;
2443                 if (view_is_displayed(view)) {
2444                         draw_view_line(view, old_lineno);
2445                         draw_view_line(view, view->lineno - view->offset);
2446                         wrefresh(view->win);
2447                 } else {
2448                         view->ops->select(view, &view->line[view->lineno]);
2449                 }
2450         }
2453 static void
2454 find_next(struct view *view, enum request request)
2456         unsigned long lineno = view->lineno;
2457         int direction;
2459         if (!*view->grep) {
2460                 if (!*opt_search)
2461                         report("No previous search");
2462                 else
2463                         search_view(view, request);
2464                 return;
2465         }
2467         switch (request) {
2468         case REQ_SEARCH:
2469         case REQ_FIND_NEXT:
2470                 direction = 1;
2471                 break;
2473         case REQ_SEARCH_BACK:
2474         case REQ_FIND_PREV:
2475                 direction = -1;
2476                 break;
2478         default:
2479                 return;
2480         }
2482         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2483                 lineno += direction;
2485         /* Note, lineno is unsigned long so will wrap around in which case it
2486          * will become bigger than view->lines. */
2487         for (; lineno < view->lines; lineno += direction) {
2488                 if (view->ops->grep(view, &view->line[lineno])) {
2489                         select_view_line(view, lineno);
2490                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2491                         return;
2492                 }
2493         }
2495         report("No match found for '%s'", view->grep);
2498 static void
2499 search_view(struct view *view, enum request request)
2501         int regex_err;
2503         if (view->regex) {
2504                 regfree(view->regex);
2505                 *view->grep = 0;
2506         } else {
2507                 view->regex = calloc(1, sizeof(*view->regex));
2508                 if (!view->regex)
2509                         return;
2510         }
2512         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2513         if (regex_err != 0) {
2514                 char buf[SIZEOF_STR] = "unknown error";
2516                 regerror(regex_err, view->regex, buf, sizeof(buf));
2517                 report("Search failed: %s", buf);
2518                 return;
2519         }
2521         string_copy(view->grep, opt_search);
2523         find_next(view, request);
2526 /*
2527  * Incremental updating
2528  */
2530 static void
2531 reset_view(struct view *view)
2533         int i;
2535         for (i = 0; i < view->lines; i++)
2536                 free(view->line[i].data);
2537         free(view->line);
2539         view->p_offset = view->offset;
2540         view->p_lineno = view->lineno;
2542         view->line = NULL;
2543         view->offset = 0;
2544         view->lines  = 0;
2545         view->lineno = 0;
2546         view->line_alloc = 0;
2547         view->vid[0] = 0;
2548         view->update_secs = 0;
2551 static void
2552 free_argv(const char *argv[])
2554         int argc;
2556         for (argc = 0; argv[argc]; argc++)
2557                 free((void *) argv[argc]);
2560 static bool
2561 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2563         char buf[SIZEOF_STR];
2564         int argc;
2565         bool noreplace = flags == FORMAT_NONE;
2567         free_argv(dst_argv);
2569         for (argc = 0; src_argv[argc]; argc++) {
2570                 const char *arg = src_argv[argc];
2571                 size_t bufpos = 0;
2573                 while (arg) {
2574                         char *next = strstr(arg, "%(");
2575                         int len = next - arg;
2576                         const char *value;
2578                         if (!next || noreplace) {
2579                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2580                                         noreplace = TRUE;
2581                                 len = strlen(arg);
2582                                 value = "";
2584                         } else if (!prefixcmp(next, "%(directory)")) {
2585                                 value = opt_path;
2587                         } else if (!prefixcmp(next, "%(file)")) {
2588                                 value = opt_file;
2590                         } else if (!prefixcmp(next, "%(ref)")) {
2591                                 value = *opt_ref ? opt_ref : "HEAD";
2593                         } else if (!prefixcmp(next, "%(head)")) {
2594                                 value = ref_head;
2596                         } else if (!prefixcmp(next, "%(commit)")) {
2597                                 value = ref_commit;
2599                         } else if (!prefixcmp(next, "%(blob)")) {
2600                                 value = ref_blob;
2602                         } else {
2603                                 report("Unknown replacement: `%s`", next);
2604                                 return FALSE;
2605                         }
2607                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2608                                 return FALSE;
2610                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2611                 }
2613                 dst_argv[argc] = strdup(buf);
2614                 if (!dst_argv[argc])
2615                         break;
2616         }
2618         dst_argv[argc] = NULL;
2620         return src_argv[argc] == NULL;
2623 static bool
2624 restore_view_position(struct view *view)
2626         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2627                 return FALSE;
2629         /* Changing the view position cancels the restoring. */
2630         /* FIXME: Changing back to the first line is not detected. */
2631         if (view->offset != 0 || view->lineno != 0) {
2632                 view->p_restore = FALSE;
2633                 return FALSE;
2634         }
2636         if (view->p_lineno >= view->lines) {
2637                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2638                 if (view->p_offset >= view->p_lineno) {
2639                         unsigned long half = view->height / 2;
2641                         if (view->p_lineno > half)
2642                                 view->p_offset = view->p_lineno - half;
2643                         else
2644                                 view->p_offset = 0;
2645                 }
2646         }
2648         if (view_is_displayed(view) &&
2649             view->offset != view->p_offset &&
2650             view->lineno != view->p_lineno)
2651                 werase(view->win);
2653         view->offset = view->p_offset;
2654         view->lineno = view->p_lineno;
2655         view->p_restore = FALSE;
2657         return TRUE;
2660 static void
2661 end_update(struct view *view, bool force)
2663         if (!view->pipe)
2664                 return;
2665         while (!view->ops->read(view, NULL))
2666                 if (!force)
2667                         return;
2668         set_nonblocking_input(FALSE);
2669         if (force)
2670                 kill_io(view->pipe);
2671         done_io(view->pipe);
2672         view->pipe = NULL;
2675 static void
2676 setup_update(struct view *view, const char *vid)
2678         set_nonblocking_input(TRUE);
2679         reset_view(view);
2680         string_copy_rev(view->vid, vid);
2681         view->pipe = &view->io;
2682         view->start_time = time(NULL);
2685 static bool
2686 prepare_update(struct view *view, const char *argv[], const char *dir,
2687                enum format_flags flags)
2689         if (view->pipe)
2690                 end_update(view, TRUE);
2691         return init_io_rd(&view->io, argv, dir, flags);
2694 static bool
2695 prepare_update_file(struct view *view, const char *name)
2697         if (view->pipe)
2698                 end_update(view, TRUE);
2699         return io_open(&view->io, name);
2702 static bool
2703 begin_update(struct view *view, bool refresh)
2705         if (view->pipe)
2706                 end_update(view, TRUE);
2708         if (refresh) {
2709                 if (!start_io(&view->io))
2710                         return FALSE;
2712         } else {
2713                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2714                         opt_path[0] = 0;
2716                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2717                         return FALSE;
2719                 /* Put the current ref_* value to the view title ref
2720                  * member. This is needed by the blob view. Most other
2721                  * views sets it automatically after loading because the
2722                  * first line is a commit line. */
2723                 string_copy_rev(view->ref, view->id);
2724         }
2726         setup_update(view, view->id);
2728         return TRUE;
2731 #define ITEM_CHUNK_SIZE 256
2732 static void *
2733 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2735         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2736         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2738         if (mem == NULL || num_chunks != num_chunks_new) {
2739                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2740                 mem = realloc(mem, *size * item_size);
2741         }
2743         return mem;
2746 static struct line *
2747 realloc_lines(struct view *view, size_t line_size)
2749         size_t alloc = view->line_alloc;
2750         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2751                                          sizeof(*view->line));
2753         if (!tmp)
2754                 return NULL;
2756         view->line = tmp;
2757         view->line_alloc = alloc;
2758         return view->line;
2761 static bool
2762 update_view(struct view *view)
2764         char out_buffer[BUFSIZ * 2];
2765         char *line;
2766         /* Clear the view and redraw everything since the tree sorting
2767          * might have rearranged things. */
2768         bool redraw = view->lines == 0;
2769         bool can_read = TRUE;
2771         if (!view->pipe)
2772                 return TRUE;
2774         if (!io_can_read(view->pipe)) {
2775                 if (view->lines == 0) {
2776                         time_t secs = time(NULL) - view->start_time;
2778                         if (secs > view->update_secs) {
2779                                 if (view->update_secs == 0)
2780                                         redraw_view(view);
2781                                 update_view_title(view);
2782                                 view->update_secs = secs;
2783                         }
2784                 }
2785                 return TRUE;
2786         }
2788         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2789                 if (opt_iconv != ICONV_NONE) {
2790                         ICONV_CONST char *inbuf = line;
2791                         size_t inlen = strlen(line) + 1;
2793                         char *outbuf = out_buffer;
2794                         size_t outlen = sizeof(out_buffer);
2796                         size_t ret;
2798                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2799                         if (ret != (size_t) -1)
2800                                 line = out_buffer;
2801                 }
2803                 if (!view->ops->read(view, line)) {
2804                         report("Allocation failure");
2805                         end_update(view, TRUE);
2806                         return FALSE;
2807                 }
2808         }
2810         {
2811                 unsigned long lines = view->lines;
2812                 int digits;
2814                 for (digits = 0; lines; digits++)
2815                         lines /= 10;
2817                 /* Keep the displayed view in sync with line number scaling. */
2818                 if (digits != view->digits) {
2819                         view->digits = digits;
2820                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2821                                 redraw = TRUE;
2822                 }
2823         }
2825         if (io_error(view->pipe)) {
2826                 report("Failed to read: %s", io_strerror(view->pipe));
2827                 end_update(view, TRUE);
2829         } else if (io_eof(view->pipe)) {
2830                 report("");
2831                 end_update(view, FALSE);
2832         }
2834         if (restore_view_position(view))
2835                 redraw = TRUE;
2837         if (!view_is_displayed(view))
2838                 return TRUE;
2840         if (redraw)
2841                 redraw_view_from(view, 0);
2842         else
2843                 redraw_view_dirty(view);
2845         /* Update the title _after_ the redraw so that if the redraw picks up a
2846          * commit reference in view->ref it'll be available here. */
2847         update_view_title(view);
2848         update_display_cursor(view);
2849         return TRUE;
2852 static struct line *
2853 add_line_data(struct view *view, void *data, enum line_type type)
2855         struct line *line;
2857         if (!realloc_lines(view, view->lines + 1))
2858                 return NULL;
2860         line = &view->line[view->lines++];
2861         memset(line, 0, sizeof(*line));
2862         line->type = type;
2863         line->data = data;
2864         line->dirty = 1;
2866         return line;
2869 static struct line *
2870 add_line_text(struct view *view, const char *text, enum line_type type)
2872         char *data = text ? strdup(text) : NULL;
2874         return data ? add_line_data(view, data, type) : NULL;
2877 static struct line *
2878 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2880         char buf[SIZEOF_STR];
2881         va_list args;
2883         va_start(args, fmt);
2884         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2885                 buf[0] = 0;
2886         va_end(args);
2888         return buf[0] ? add_line_text(view, buf, type) : NULL;
2891 /*
2892  * View opening
2893  */
2895 enum open_flags {
2896         OPEN_DEFAULT = 0,       /* Use default view switching. */
2897         OPEN_SPLIT = 1,         /* Split current view. */
2898         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2899         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2900         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2901         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2902         OPEN_PREPARED = 32,     /* Open already prepared command. */
2903 };
2905 static void
2906 open_view(struct view *prev, enum request request, enum open_flags flags)
2908         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2909         bool split = !!(flags & OPEN_SPLIT);
2910         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2911         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2912         struct view *view = VIEW(request);
2913         int nviews = displayed_views();
2914         struct view *base_view = display[0];
2916         if (view == prev && nviews == 1 && !reload) {
2917                 report("Already in %s view", view->name);
2918                 return;
2919         }
2921         if (view->git_dir && !opt_git_dir[0]) {
2922                 report("The %s view is disabled in pager view", view->name);
2923                 return;
2924         }
2926         if (split) {
2927                 display[1] = view;
2928                 if (!backgrounded)
2929                         current_view = 1;
2930         } else if (!nomaximize) {
2931                 /* Maximize the current view. */
2932                 memset(display, 0, sizeof(display));
2933                 current_view = 0;
2934                 display[current_view] = view;
2935         }
2937         /* Resize the view when switching between split- and full-screen,
2938          * or when switching between two different full-screen views. */
2939         if (nviews != displayed_views() ||
2940             (nviews == 1 && base_view != display[0]))
2941                 resize_display();
2943         if (view->ops->open) {
2944                 if (view->pipe)
2945                         end_update(view, TRUE);
2946                 if (!view->ops->open(view)) {
2947                         report("Failed to load %s view", view->name);
2948                         return;
2949                 }
2950                 restore_view_position(view);
2952         } else if ((reload || strcmp(view->vid, view->id)) &&
2953                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2954                 report("Failed to load %s view", view->name);
2955                 return;
2956         }
2958         if (split && prev->lineno - prev->offset >= prev->height) {
2959                 /* Take the title line into account. */
2960                 int lines = prev->lineno - prev->offset - prev->height + 1;
2962                 /* Scroll the view that was split if the current line is
2963                  * outside the new limited view. */
2964                 do_scroll_view(prev, lines);
2965         }
2967         if (prev && view != prev) {
2968                 if (split && !backgrounded) {
2969                         /* "Blur" the previous view. */
2970                         update_view_title(prev);
2971                 }
2973                 view->parent = prev;
2974         }
2976         if (view->pipe && view->lines == 0) {
2977                 /* Clear the old view and let the incremental updating refill
2978                  * the screen. */
2979                 werase(view->win);
2980                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2981                 report("");
2982         } else if (view_is_displayed(view)) {
2983                 redraw_view(view);
2984                 report("");
2985         }
2987         /* If the view is backgrounded the above calls to report()
2988          * won't redraw the view title. */
2989         if (backgrounded)
2990                 update_view_title(view);
2993 static void
2994 open_external_viewer(const char *argv[], const char *dir)
2996         def_prog_mode();           /* save current tty modes */
2997         endwin();                  /* restore original tty modes */
2998         run_io_fg(argv, dir);
2999         fprintf(stderr, "Press Enter to continue");
3000         getc(opt_tty);
3001         reset_prog_mode();
3002         redraw_display(TRUE);
3005 static void
3006 open_mergetool(const char *file)
3008         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3010         open_external_viewer(mergetool_argv, opt_cdup);
3013 static void
3014 open_editor(bool from_root, const char *file)
3016         const char *editor_argv[] = { "vi", file, NULL };
3017         const char *editor;
3019         editor = getenv("GIT_EDITOR");
3020         if (!editor && *opt_editor)
3021                 editor = opt_editor;
3022         if (!editor)
3023                 editor = getenv("VISUAL");
3024         if (!editor)
3025                 editor = getenv("EDITOR");
3026         if (!editor)
3027                 editor = "vi";
3029         editor_argv[0] = editor;
3030         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3033 static void
3034 open_run_request(enum request request)
3036         struct run_request *req = get_run_request(request);
3037         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3039         if (!req) {
3040                 report("Unknown run request");
3041                 return;
3042         }
3044         if (format_argv(argv, req->argv, FORMAT_ALL))
3045                 open_external_viewer(argv, NULL);
3046         free_argv(argv);
3049 /*
3050  * User request switch noodle
3051  */
3053 static int
3054 view_driver(struct view *view, enum request request)
3056         int i;
3058         if (request == REQ_NONE) {
3059                 doupdate();
3060                 return TRUE;
3061         }
3063         if (request > REQ_NONE) {
3064                 open_run_request(request);
3065                 /* FIXME: When all views can refresh always do this. */
3066                 if (view == VIEW(REQ_VIEW_STATUS) ||
3067                     view == VIEW(REQ_VIEW_MAIN) ||
3068                     view == VIEW(REQ_VIEW_LOG) ||
3069                     view == VIEW(REQ_VIEW_STAGE))
3070                         request = REQ_REFRESH;
3071                 else
3072                         return TRUE;
3073         }
3075         if (view && view->lines) {
3076                 request = view->ops->request(view, request, &view->line[view->lineno]);
3077                 if (request == REQ_NONE)
3078                         return TRUE;
3079         }
3081         switch (request) {
3082         case REQ_MOVE_UP:
3083         case REQ_MOVE_DOWN:
3084         case REQ_MOVE_PAGE_UP:
3085         case REQ_MOVE_PAGE_DOWN:
3086         case REQ_MOVE_FIRST_LINE:
3087         case REQ_MOVE_LAST_LINE:
3088                 move_view(view, request);
3089                 break;
3091         case REQ_SCROLL_LINE_DOWN:
3092         case REQ_SCROLL_LINE_UP:
3093         case REQ_SCROLL_PAGE_DOWN:
3094         case REQ_SCROLL_PAGE_UP:
3095                 scroll_view(view, request);
3096                 break;
3098         case REQ_VIEW_BLAME:
3099                 if (!opt_file[0]) {
3100                         report("No file chosen, press %s to open tree view",
3101                                get_key(REQ_VIEW_TREE));
3102                         break;
3103                 }
3104                 open_view(view, request, OPEN_DEFAULT);
3105                 break;
3107         case REQ_VIEW_BLOB:
3108                 if (!ref_blob[0]) {
3109                         report("No file chosen, press %s to open tree view",
3110                                get_key(REQ_VIEW_TREE));
3111                         break;
3112                 }
3113                 open_view(view, request, OPEN_DEFAULT);
3114                 break;
3116         case REQ_VIEW_PAGER:
3117                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3118                         report("No pager content, press %s to run command from prompt",
3119                                get_key(REQ_PROMPT));
3120                         break;
3121                 }
3122                 open_view(view, request, OPEN_DEFAULT);
3123                 break;
3125         case REQ_VIEW_STAGE:
3126                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3127                         report("No stage content, press %s to open the status view and choose file",
3128                                get_key(REQ_VIEW_STATUS));
3129                         break;
3130                 }
3131                 open_view(view, request, OPEN_DEFAULT);
3132                 break;
3134         case REQ_VIEW_STATUS:
3135                 if (opt_is_inside_work_tree == FALSE) {
3136                         report("The status view requires a working tree");
3137                         break;
3138                 }
3139                 open_view(view, request, OPEN_DEFAULT);
3140                 break;
3142         case REQ_VIEW_MAIN:
3143         case REQ_VIEW_DIFF:
3144         case REQ_VIEW_LOG:
3145         case REQ_VIEW_TREE:
3146         case REQ_VIEW_HELP:
3147                 open_view(view, request, OPEN_DEFAULT);
3148                 break;
3150         case REQ_NEXT:
3151         case REQ_PREVIOUS:
3152                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3154                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3155                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3156                    (view == VIEW(REQ_VIEW_DIFF) &&
3157                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3158                    (view == VIEW(REQ_VIEW_STAGE) &&
3159                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3160                    (view == VIEW(REQ_VIEW_BLOB) &&
3161                      view->parent == VIEW(REQ_VIEW_TREE))) {
3162                         int line;
3164                         view = view->parent;
3165                         line = view->lineno;
3166                         move_view(view, request);
3167                         if (view_is_displayed(view))
3168                                 update_view_title(view);
3169                         if (line != view->lineno)
3170                                 view->ops->request(view, REQ_ENTER,
3171                                                    &view->line[view->lineno]);
3173                 } else {
3174                         move_view(view, request);
3175                 }
3176                 break;
3178         case REQ_VIEW_NEXT:
3179         {
3180                 int nviews = displayed_views();
3181                 int next_view = (current_view + 1) % nviews;
3183                 if (next_view == current_view) {
3184                         report("Only one view is displayed");
3185                         break;
3186                 }
3188                 current_view = next_view;
3189                 /* Blur out the title of the previous view. */
3190                 update_view_title(view);
3191                 report("");
3192                 break;
3193         }
3194         case REQ_REFRESH:
3195                 report("Refreshing is not yet supported for the %s view", view->name);
3196                 break;
3198         case REQ_MAXIMIZE:
3199                 if (displayed_views() == 2)
3200                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3201                 break;
3203         case REQ_TOGGLE_LINENO:
3204                 toggle_view_option(&opt_line_number, "line numbers");
3205                 break;
3207         case REQ_TOGGLE_DATE:
3208                 toggle_view_option(&opt_date, "date display");
3209                 break;
3211         case REQ_TOGGLE_AUTHOR:
3212                 toggle_view_option(&opt_author, "author display");
3213                 break;
3215         case REQ_TOGGLE_REV_GRAPH:
3216                 toggle_view_option(&opt_rev_graph, "revision graph display");
3217                 break;
3219         case REQ_TOGGLE_REFS:
3220                 toggle_view_option(&opt_show_refs, "reference display");
3221                 break;
3223         case REQ_SEARCH:
3224         case REQ_SEARCH_BACK:
3225                 search_view(view, request);
3226                 break;
3228         case REQ_FIND_NEXT:
3229         case REQ_FIND_PREV:
3230                 find_next(view, request);
3231                 break;
3233         case REQ_STOP_LOADING:
3234                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3235                         view = &views[i];
3236                         if (view->pipe)
3237                                 report("Stopped loading the %s view", view->name),
3238                         end_update(view, TRUE);
3239                 }
3240                 break;
3242         case REQ_SHOW_VERSION:
3243                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3244                 return TRUE;
3246         case REQ_SCREEN_REDRAW:
3247                 redraw_display(TRUE);
3248                 break;
3250         case REQ_EDIT:
3251                 report("Nothing to edit");
3252                 break;
3254         case REQ_ENTER:
3255                 report("Nothing to enter");
3256                 break;
3258         case REQ_VIEW_CLOSE:
3259                 /* XXX: Mark closed views by letting view->parent point to the
3260                  * view itself. Parents to closed view should never be
3261                  * followed. */
3262                 if (view->parent &&
3263                     view->parent->parent != view->parent) {
3264                         memset(display, 0, sizeof(display));
3265                         current_view = 0;
3266                         display[current_view] = view->parent;
3267                         view->parent = view;
3268                         resize_display();
3269                         redraw_display(FALSE);
3270                         report("");
3271                         break;
3272                 }
3273                 /* Fall-through */
3274         case REQ_QUIT:
3275                 return FALSE;
3277         default:
3278                 report("Unknown key, press 'h' for help");
3279                 return TRUE;
3280         }
3282         return TRUE;
3286 /*
3287  * View backend utilities
3288  */
3290 /* Parse author lines where the name may be empty:
3291  *      author  <email@address.tld> 1138474660 +0100
3292  */
3293 static void
3294 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3296         char *nameend = strchr(ident, '<');
3297         char *emailend = strchr(ident, '>');
3299         if (nameend && emailend)
3300                 *nameend = *emailend = 0;
3301         ident = chomp_string(ident);
3302         if (!*ident) {
3303                 if (nameend)
3304                         ident = chomp_string(nameend + 1);
3305                 if (!*ident)
3306                         ident = "Unknown";
3307         }
3309         string_ncopy_do(author, authorsize, ident, strlen(ident));
3311         /* Parse epoch and timezone */
3312         if (emailend && emailend[1] == ' ') {
3313                 char *secs = emailend + 2;
3314                 char *zone = strchr(secs, ' ');
3315                 time_t time = (time_t) atol(secs);
3317                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3318                         long tz;
3320                         zone++;
3321                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3322                         tz += ('0' - zone[2]) * 60 * 60;
3323                         tz += ('0' - zone[3]) * 60;
3324                         tz += ('0' - zone[4]) * 60;
3326                         if (zone[0] == '-')
3327                                 tz = -tz;
3329                         time -= tz;
3330                 }
3332                 gmtime_r(&time, tm);
3333         }
3336 static enum input_status
3337 select_commit_parent_handler(void *data, char *buf, int c)
3339         size_t parents = *(size_t *) data;
3340         int parent = 0;
3342         if (!isdigit(c))
3343                 return INPUT_SKIP;
3345         if (*buf)
3346                 parent = atoi(buf) * 10;
3347         parent += c - '0';
3349         if (parent > parents)
3350                 return INPUT_SKIP;
3351         return INPUT_OK;
3354 static bool
3355 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3357         char buf[SIZEOF_STR * 4];
3358         const char *revlist_argv[] = {
3359                 "git", "rev-list", "-1", "--parents", id, NULL
3360         };
3361         int parents;
3363         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3364             !*chomp_string(buf) ||
3365             (parents = (strlen(buf) / 40) - 1) < 0) {
3366                 report("Failed to get parent information");
3367                 return FALSE;
3369         } else if (parents == 0) {
3370                 report("The selected commit has no parents");
3371                 return FALSE;
3372         }
3374         if (parents > 1) {
3375                 char prompt[SIZEOF_STR];
3376                 char *result;
3378                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3379                         return FALSE;
3380                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3381                 if (!result)
3382                         return FALSE;
3383                 parents = atoi(result);
3384         }
3386         string_copy_rev(rev, &buf[41 * parents]);
3387         return TRUE;
3390 /*
3391  * Pager backend
3392  */
3394 static bool
3395 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3397         char *text = line->data;
3399         if (opt_line_number && draw_lineno(view, lineno))
3400                 return TRUE;
3402         draw_text(view, line->type, text, TRUE);
3403         return TRUE;
3406 static bool
3407 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3409         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3410         char refbuf[SIZEOF_STR];
3411         char *ref = NULL;
3413         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3414                 ref = chomp_string(refbuf);
3416         if (!ref || !*ref)
3417                 return TRUE;
3419         /* This is the only fatal call, since it can "corrupt" the buffer. */
3420         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3421                 return FALSE;
3423         return TRUE;
3426 static void
3427 add_pager_refs(struct view *view, struct line *line)
3429         char buf[SIZEOF_STR];
3430         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3431         struct ref **refs;
3432         size_t bufpos = 0, refpos = 0;
3433         const char *sep = "Refs: ";
3434         bool is_tag = FALSE;
3436         assert(line->type == LINE_COMMIT);
3438         refs = get_refs(commit_id);
3439         if (!refs) {
3440                 if (view == VIEW(REQ_VIEW_DIFF))
3441                         goto try_add_describe_ref;
3442                 return;
3443         }
3445         do {
3446                 struct ref *ref = refs[refpos];
3447                 const char *fmt = ref->tag    ? "%s[%s]" :
3448                                   ref->remote ? "%s<%s>" : "%s%s";
3450                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3451                         return;
3452                 sep = ", ";
3453                 if (ref->tag)
3454                         is_tag = TRUE;
3455         } while (refs[refpos++]->next);
3457         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3458 try_add_describe_ref:
3459                 /* Add <tag>-g<commit_id> "fake" reference. */
3460                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3461                         return;
3462         }
3464         if (bufpos == 0)
3465                 return;
3467         add_line_text(view, buf, LINE_PP_REFS);
3470 static bool
3471 pager_read(struct view *view, char *data)
3473         struct line *line;
3475         if (!data)
3476                 return TRUE;
3478         line = add_line_text(view, data, get_line_type(data));
3479         if (!line)
3480                 return FALSE;
3482         if (line->type == LINE_COMMIT &&
3483             (view == VIEW(REQ_VIEW_DIFF) ||
3484              view == VIEW(REQ_VIEW_LOG)))
3485                 add_pager_refs(view, line);
3487         return TRUE;
3490 static enum request
3491 pager_request(struct view *view, enum request request, struct line *line)
3493         int split = 0;
3495         if (request != REQ_ENTER)
3496                 return request;
3498         if (line->type == LINE_COMMIT &&
3499            (view == VIEW(REQ_VIEW_LOG) ||
3500             view == VIEW(REQ_VIEW_PAGER))) {
3501                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3502                 split = 1;
3503         }
3505         /* Always scroll the view even if it was split. That way
3506          * you can use Enter to scroll through the log view and
3507          * split open each commit diff. */
3508         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3510         /* FIXME: A minor workaround. Scrolling the view will call report("")
3511          * but if we are scrolling a non-current view this won't properly
3512          * update the view title. */
3513         if (split)
3514                 update_view_title(view);
3516         return REQ_NONE;
3519 static bool
3520 pager_grep(struct view *view, struct line *line)
3522         regmatch_t pmatch;
3523         char *text = line->data;
3525         if (!*text)
3526                 return FALSE;
3528         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3529                 return FALSE;
3531         return TRUE;
3534 static void
3535 pager_select(struct view *view, struct line *line)
3537         if (line->type == LINE_COMMIT) {
3538                 char *text = (char *)line->data + STRING_SIZE("commit ");
3540                 if (view != VIEW(REQ_VIEW_PAGER))
3541                         string_copy_rev(view->ref, text);
3542                 string_copy_rev(ref_commit, text);
3543         }
3546 static struct view_ops pager_ops = {
3547         "line",
3548         NULL,
3549         NULL,
3550         pager_read,
3551         pager_draw,
3552         pager_request,
3553         pager_grep,
3554         pager_select,
3555 };
3557 static const char *log_argv[SIZEOF_ARG] = {
3558         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3559 };
3561 static enum request
3562 log_request(struct view *view, enum request request, struct line *line)
3564         switch (request) {
3565         case REQ_REFRESH:
3566                 load_refs();
3567                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3568                 return REQ_NONE;
3569         default:
3570                 return pager_request(view, request, line);
3571         }
3574 static struct view_ops log_ops = {
3575         "line",
3576         log_argv,
3577         NULL,
3578         pager_read,
3579         pager_draw,
3580         log_request,
3581         pager_grep,
3582         pager_select,
3583 };
3585 static const char *diff_argv[SIZEOF_ARG] = {
3586         "git", "show", "--pretty=fuller", "--no-color", "--root",
3587                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3588 };
3590 static struct view_ops diff_ops = {
3591         "line",
3592         diff_argv,
3593         NULL,
3594         pager_read,
3595         pager_draw,
3596         pager_request,
3597         pager_grep,
3598         pager_select,
3599 };
3601 /*
3602  * Help backend
3603  */
3605 static bool
3606 help_open(struct view *view)
3608         char buf[SIZEOF_STR];
3609         size_t bufpos;
3610         int i;
3612         if (view->lines > 0)
3613                 return TRUE;
3615         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3617         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3618                 const char *key;
3620                 if (req_info[i].request == REQ_NONE)
3621                         continue;
3623                 if (!req_info[i].request) {
3624                         add_line_text(view, "", LINE_DEFAULT);
3625                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3626                         continue;
3627                 }
3629                 key = get_key(req_info[i].request);
3630                 if (!*key)
3631                         key = "(no key defined)";
3633                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3634                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3635                         if (buf[bufpos] == '_')
3636                                 buf[bufpos] = '-';
3637                 }
3639                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3640                                 key, buf, req_info[i].help);
3641         }
3643         if (run_requests) {
3644                 add_line_text(view, "", LINE_DEFAULT);
3645                 add_line_text(view, "External commands:", LINE_DEFAULT);
3646         }
3648         for (i = 0; i < run_requests; i++) {
3649                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3650                 const char *key;
3651                 int argc;
3653                 if (!req)
3654                         continue;
3656                 key = get_key_name(req->key);
3657                 if (!*key)
3658                         key = "(no key defined)";
3660                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3661                         if (!string_format_from(buf, &bufpos, "%s%s",
3662                                                 argc ? " " : "", req->argv[argc]))
3663                                 return REQ_NONE;
3665                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3666                                 keymap_table[req->keymap].name, key, buf);
3667         }
3669         return TRUE;
3672 static struct view_ops help_ops = {
3673         "line",
3674         NULL,
3675         help_open,
3676         NULL,
3677         pager_draw,
3678         pager_request,
3679         pager_grep,
3680         pager_select,
3681 };
3684 /*
3685  * Tree backend
3686  */
3688 struct tree_stack_entry {
3689         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3690         unsigned long lineno;           /* Line number to restore */
3691         char *name;                     /* Position of name in opt_path */
3692 };
3694 /* The top of the path stack. */
3695 static struct tree_stack_entry *tree_stack = NULL;
3696 unsigned long tree_lineno = 0;
3698 static void
3699 pop_tree_stack_entry(void)
3701         struct tree_stack_entry *entry = tree_stack;
3703         tree_lineno = entry->lineno;
3704         entry->name[0] = 0;
3705         tree_stack = entry->prev;
3706         free(entry);
3709 static void
3710 push_tree_stack_entry(const char *name, unsigned long lineno)
3712         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3713         size_t pathlen = strlen(opt_path);
3715         if (!entry)
3716                 return;
3718         entry->prev = tree_stack;
3719         entry->name = opt_path + pathlen;
3720         tree_stack = entry;
3722         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3723                 pop_tree_stack_entry();
3724                 return;
3725         }
3727         /* Move the current line to the first tree entry. */
3728         tree_lineno = 1;
3729         entry->lineno = lineno;
3732 /* Parse output from git-ls-tree(1):
3733  *
3734  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3735  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3736  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3737  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3738  */
3740 #define SIZEOF_TREE_ATTR \
3741         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3743 #define SIZEOF_TREE_MODE \
3744         STRING_SIZE("100644 ")
3746 #define TREE_ID_OFFSET \
3747         STRING_SIZE("100644 blob ")
3749 struct tree_entry {
3750         char id[SIZEOF_REV];
3751         mode_t mode;
3752         struct tm time;                 /* Date from the author ident. */
3753         char author[75];                /* Author of the commit. */
3754         char name[1];
3755 };
3757 static const char *
3758 tree_path(struct line *line)
3760         return ((struct tree_entry *) line->data)->name;
3764 static int
3765 tree_compare_entry(struct line *line1, struct line *line2)
3767         if (line1->type != line2->type)
3768                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3769         return strcmp(tree_path(line1), tree_path(line2));
3772 static struct line *
3773 tree_entry(struct view *view, enum line_type type, const char *path,
3774            const char *mode, const char *id)
3776         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3777         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3779         if (!entry || !line) {
3780                 free(entry);
3781                 return NULL;
3782         }
3784         strncpy(entry->name, path, strlen(path));
3785         if (mode)
3786                 entry->mode = strtoul(mode, NULL, 8);
3787         if (id)
3788                 string_copy_rev(entry->id, id);
3790         return line;
3793 static bool
3794 tree_read_date(struct view *view, char *text, bool *read_date)
3796         static char author_name[SIZEOF_STR];
3797         static struct tm author_time;
3799         if (!text && *read_date) {
3800                 *read_date = FALSE;
3801                 return TRUE;
3803         } else if (!text) {
3804                 char *path = *opt_path ? opt_path : ".";
3805                 /* Find next entry to process */
3806                 const char *log_file[] = {
3807                         "git", "log", "--no-color", "--pretty=raw",
3808                                 "--cc", "--raw", view->id, "--", path, NULL
3809                 };
3810                 struct io io = {};
3812                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3813                         report("Failed to load tree data");
3814                         return TRUE;
3815                 }
3817                 done_io(view->pipe);
3818                 view->io = io;
3819                 *read_date = TRUE;
3820                 return FALSE;
3822         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3823                 parse_author_line(text + STRING_SIZE("author "),
3824                                   author_name, sizeof(author_name), &author_time);
3826         } else if (*text == ':') {
3827                 char *pos;
3828                 size_t annotated = 1;
3829                 size_t i;
3831                 pos = strchr(text, '\t');
3832                 if (!pos)
3833                         return TRUE;
3834                 text = pos + 1;
3835                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3836                         text += strlen(opt_prefix);
3837                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3838                         text += strlen(opt_path);
3839                 pos = strchr(text, '/');
3840                 if (pos)
3841                         *pos = 0;
3843                 for (i = 1; i < view->lines; i++) {
3844                         struct line *line = &view->line[i];
3845                         struct tree_entry *entry = line->data;
3847                         annotated += !!*entry->author;
3848                         if (*entry->author || strcmp(entry->name, text))
3849                                 continue;
3851                         string_copy(entry->author, author_name);
3852                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3853                         line->dirty = 1;
3854                         break;
3855                 }
3857                 if (annotated == view->lines)
3858                         kill_io(view->pipe);
3859         }
3860         return TRUE;
3863 static bool
3864 tree_read(struct view *view, char *text)
3866         static bool read_date = FALSE;
3867         struct tree_entry *data;
3868         struct line *entry, *line;
3869         enum line_type type;
3870         size_t textlen = text ? strlen(text) : 0;
3871         char *path = text + SIZEOF_TREE_ATTR;
3873         if (read_date || !text)
3874                 return tree_read_date(view, text, &read_date);
3876         if (textlen <= SIZEOF_TREE_ATTR)
3877                 return FALSE;
3878         if (view->lines == 0 &&
3879             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3880                 return FALSE;
3882         /* Strip the path part ... */
3883         if (*opt_path) {
3884                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3885                 size_t striplen = strlen(opt_path);
3887                 if (pathlen > striplen)
3888                         memmove(path, path + striplen,
3889                                 pathlen - striplen + 1);
3891                 /* Insert "link" to parent directory. */
3892                 if (view->lines == 1 &&
3893                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3894                         return FALSE;
3895         }
3897         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3898         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3899         if (!entry)
3900                 return FALSE;
3901         data = entry->data;
3903         /* Skip "Directory ..." and ".." line. */
3904         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3905                 if (tree_compare_entry(line, entry) <= 0)
3906                         continue;
3908                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3910                 line->data = data;
3911                 line->type = type;
3912                 for (; line <= entry; line++)
3913                         line->dirty = line->cleareol = 1;
3914                 return TRUE;
3915         }
3917         if (tree_lineno > view->lineno) {
3918                 view->lineno = tree_lineno;
3919                 tree_lineno = 0;
3920         }
3922         return TRUE;
3925 static bool
3926 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3928         struct tree_entry *entry = line->data;
3930         if (line->type == LINE_TREE_PARENT) {
3931                 if (draw_text(view, line->type, "Directory path /", TRUE))
3932                         return TRUE;
3933         } else {
3934                 char mode[11] = "-r--r--r--";
3936                 if (S_ISDIR(entry->mode)) {
3937                         mode[3] = mode[6] = mode[9] = 'x';
3938                         mode[0] = 'd';
3939                 }
3940                 if (S_ISLNK(entry->mode))
3941                         mode[0] = 'l';
3942                 if (entry->mode & S_IWUSR)
3943                         mode[2] = 'w';
3944                 if (entry->mode & S_IXUSR)
3945                         mode[3] = 'x';
3946                 if (entry->mode & S_IXGRP)
3947                         mode[6] = 'x';
3948                 if (entry->mode & S_IXOTH)
3949                         mode[9] = 'x';
3950                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3951                         return TRUE;
3953                 if (opt_author &&
3954                     draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3955                         return TRUE;
3957                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3958                         return TRUE;
3959         }
3960         if (draw_text(view, line->type, entry->name, TRUE))
3961                 return TRUE;
3962         return TRUE;
3965 static void
3966 open_blob_editor()
3968         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3969         int fd = mkstemp(file);
3971         if (fd == -1)
3972                 report("Failed to create temporary file");
3973         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3974                 report("Failed to save blob data to file");
3975         else
3976                 open_editor(FALSE, file);
3977         if (fd != -1)
3978                 unlink(file);
3981 static enum request
3982 tree_request(struct view *view, enum request request, struct line *line)
3984         enum open_flags flags;
3986         switch (request) {
3987         case REQ_VIEW_BLAME:
3988                 if (line->type != LINE_TREE_FILE) {
3989                         report("Blame only supported for files");
3990                         return REQ_NONE;
3991                 }
3993                 string_copy(opt_ref, view->vid);
3994                 return request;
3996         case REQ_EDIT:
3997                 if (line->type != LINE_TREE_FILE) {
3998                         report("Edit only supported for files");
3999                 } else if (!is_head_commit(view->vid)) {
4000                         open_blob_editor();
4001                 } else {
4002                         open_editor(TRUE, opt_file);
4003                 }
4004                 return REQ_NONE;
4006         case REQ_PARENT:
4007                 if (!*opt_path) {
4008                         /* quit view if at top of tree */
4009                         return REQ_VIEW_CLOSE;
4010                 }
4011                 /* fake 'cd  ..' */
4012                 line = &view->line[1];
4013                 break;
4015         case REQ_ENTER:
4016                 break;
4018         default:
4019                 return request;
4020         }
4022         /* Cleanup the stack if the tree view is at a different tree. */
4023         while (!*opt_path && tree_stack)
4024                 pop_tree_stack_entry();
4026         switch (line->type) {
4027         case LINE_TREE_DIR:
4028                 /* Depending on whether it is a subdir or parent (updir?) link
4029                  * mangle the path buffer. */
4030                 if (line == &view->line[1] && *opt_path) {
4031                         pop_tree_stack_entry();
4033                 } else {
4034                         const char *basename = tree_path(line);
4036                         push_tree_stack_entry(basename, view->lineno);
4037                 }
4039                 /* Trees and subtrees share the same ID, so they are not not
4040                  * unique like blobs. */
4041                 flags = OPEN_RELOAD;
4042                 request = REQ_VIEW_TREE;
4043                 break;
4045         case LINE_TREE_FILE:
4046                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4047                 request = REQ_VIEW_BLOB;
4048                 break;
4050         default:
4051                 return REQ_NONE;
4052         }
4054         open_view(view, request, flags);
4055         if (request == REQ_VIEW_TREE)
4056                 view->lineno = tree_lineno;
4058         return REQ_NONE;
4061 static void
4062 tree_select(struct view *view, struct line *line)
4064         struct tree_entry *entry = line->data;
4066         if (line->type == LINE_TREE_FILE) {
4067                 string_copy_rev(ref_blob, entry->id);
4068                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4070         } else if (line->type != LINE_TREE_DIR) {
4071                 return;
4072         }
4074         string_copy_rev(view->ref, entry->id);
4077 static const char *tree_argv[SIZEOF_ARG] = {
4078         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4079 };
4081 static struct view_ops tree_ops = {
4082         "file",
4083         tree_argv,
4084         NULL,
4085         tree_read,
4086         tree_draw,
4087         tree_request,
4088         pager_grep,
4089         tree_select,
4090 };
4092 static bool
4093 blob_read(struct view *view, char *line)
4095         if (!line)
4096                 return TRUE;
4097         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4100 static enum request
4101 blob_request(struct view *view, enum request request, struct line *line)
4103         switch (request) {
4104         case REQ_EDIT:
4105                 open_blob_editor();
4106                 return REQ_NONE;
4107         default:
4108                 return pager_request(view, request, line);
4109         }
4112 static const char *blob_argv[SIZEOF_ARG] = {
4113         "git", "cat-file", "blob", "%(blob)", NULL
4114 };
4116 static struct view_ops blob_ops = {
4117         "line",
4118         blob_argv,
4119         NULL,
4120         blob_read,
4121         pager_draw,
4122         blob_request,
4123         pager_grep,
4124         pager_select,
4125 };
4127 /*
4128  * Blame backend
4129  *
4130  * Loading the blame view is a two phase job:
4131  *
4132  *  1. File content is read either using opt_file from the
4133  *     filesystem or using git-cat-file.
4134  *  2. Then blame information is incrementally added by
4135  *     reading output from git-blame.
4136  */
4138 static const char *blame_head_argv[] = {
4139         "git", "blame", "--incremental", "--", "%(file)", NULL
4140 };
4142 static const char *blame_ref_argv[] = {
4143         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4144 };
4146 static const char *blame_cat_file_argv[] = {
4147         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4148 };
4150 struct blame_commit {
4151         char id[SIZEOF_REV];            /* SHA1 ID. */
4152         char title[128];                /* First line of the commit message. */
4153         char author[75];                /* Author of the commit. */
4154         struct tm time;                 /* Date from the author ident. */
4155         char filename[128];             /* Name of file. */
4156         bool has_previous;              /* Was a "previous" line detected. */
4157 };
4159 struct blame {
4160         struct blame_commit *commit;
4161         char text[1];
4162 };
4164 static bool
4165 blame_open(struct view *view)
4167         if (*opt_ref || !io_open(&view->io, opt_file)) {
4168                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4169                         return FALSE;
4170         }
4172         setup_update(view, opt_file);
4173         string_format(view->ref, "%s ...", opt_file);
4175         return TRUE;
4178 static struct blame_commit *
4179 get_blame_commit(struct view *view, const char *id)
4181         size_t i;
4183         for (i = 0; i < view->lines; i++) {
4184                 struct blame *blame = view->line[i].data;
4186                 if (!blame->commit)
4187                         continue;
4189                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4190                         return blame->commit;
4191         }
4193         {
4194                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4196                 if (commit)
4197                         string_ncopy(commit->id, id, SIZEOF_REV);
4198                 return commit;
4199         }
4202 static bool
4203 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4205         const char *pos = *posref;
4207         *posref = NULL;
4208         pos = strchr(pos + 1, ' ');
4209         if (!pos || !isdigit(pos[1]))
4210                 return FALSE;
4211         *number = atoi(pos + 1);
4212         if (*number < min || *number > max)
4213                 return FALSE;
4215         *posref = pos;
4216         return TRUE;
4219 static struct blame_commit *
4220 parse_blame_commit(struct view *view, const char *text, int *blamed)
4222         struct blame_commit *commit;
4223         struct blame *blame;
4224         const char *pos = text + SIZEOF_REV - 1;
4225         size_t lineno;
4226         size_t group;
4228         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4229                 return NULL;
4231         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4232             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4233                 return NULL;
4235         commit = get_blame_commit(view, text);
4236         if (!commit)
4237                 return NULL;
4239         *blamed += group;
4240         while (group--) {
4241                 struct line *line = &view->line[lineno + group - 1];
4243                 blame = line->data;
4244                 blame->commit = commit;
4245                 line->dirty = 1;
4246         }
4248         return commit;
4251 static bool
4252 blame_read_file(struct view *view, const char *line, bool *read_file)
4254         if (!line) {
4255                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4256                 struct io io = {};
4258                 if (view->lines == 0 && !view->parent)
4259                         die("No blame exist for %s", view->vid);
4261                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4262                         report("Failed to load blame data");
4263                         return TRUE;
4264                 }
4266                 done_io(view->pipe);
4267                 view->io = io;
4268                 *read_file = FALSE;
4269                 return FALSE;
4271         } else {
4272                 size_t linelen = strlen(line);
4273                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4275                 blame->commit = NULL;
4276                 strncpy(blame->text, line, linelen);
4277                 blame->text[linelen] = 0;
4278                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4279         }
4282 static bool
4283 match_blame_header(const char *name, char **line)
4285         size_t namelen = strlen(name);
4286         bool matched = !strncmp(name, *line, namelen);
4288         if (matched)
4289                 *line += namelen;
4291         return matched;
4294 static bool
4295 blame_read(struct view *view, char *line)
4297         static struct blame_commit *commit = NULL;
4298         static int blamed = 0;
4299         static time_t author_time;
4300         static bool read_file = TRUE;
4302         if (read_file)
4303                 return blame_read_file(view, line, &read_file);
4305         if (!line) {
4306                 /* Reset all! */
4307                 commit = NULL;
4308                 blamed = 0;
4309                 read_file = TRUE;
4310                 string_format(view->ref, "%s", view->vid);
4311                 if (view_is_displayed(view)) {
4312                         update_view_title(view);
4313                         redraw_view_from(view, 0);
4314                 }
4315                 return TRUE;
4316         }
4318         if (!commit) {
4319                 commit = parse_blame_commit(view, line, &blamed);
4320                 string_format(view->ref, "%s %2d%%", view->vid,
4321                               view->lines ? blamed * 100 / view->lines : 0);
4323         } else if (match_blame_header("author ", &line)) {
4324                 string_ncopy(commit->author, line, strlen(line));
4326         } else if (match_blame_header("author-time ", &line)) {
4327                 author_time = (time_t) atol(line);
4329         } else if (match_blame_header("author-tz ", &line)) {
4330                 long tz;
4332                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4333                 tz += ('0' - line[2]) * 60 * 60;
4334                 tz += ('0' - line[3]) * 60;
4335                 tz += ('0' - line[4]) * 60;
4337                 if (line[0] == '-')
4338                         tz = -tz;
4340                 author_time -= tz;
4341                 gmtime_r(&author_time, &commit->time);
4343         } else if (match_blame_header("summary ", &line)) {
4344                 string_ncopy(commit->title, line, strlen(line));
4346         } else if (match_blame_header("previous ", &line)) {
4347                 commit->has_previous = TRUE;
4349         } else if (match_blame_header("filename ", &line)) {
4350                 string_ncopy(commit->filename, line, strlen(line));
4351                 commit = NULL;
4352         }
4354         return TRUE;
4357 static bool
4358 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4360         struct blame *blame = line->data;
4361         struct tm *time = NULL;
4362         const char *id = NULL, *author = NULL;
4364         if (blame->commit && *blame->commit->filename) {
4365                 id = blame->commit->id;
4366                 author = blame->commit->author;
4367                 time = &blame->commit->time;
4368         }
4370         if (opt_date && draw_date(view, time))
4371                 return TRUE;
4373         if (opt_author &&
4374             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4375                 return TRUE;
4377         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4378                 return TRUE;
4380         if (draw_lineno(view, lineno))
4381                 return TRUE;
4383         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4384         return TRUE;
4387 static bool
4388 check_blame_commit(struct blame *blame)
4390         if (!blame->commit)
4391                 report("Commit data not loaded yet");
4392         else if (!strcmp(blame->commit->id, NULL_ID))
4393                 report("No commit exist for the selected line");
4394         else
4395                 return TRUE;
4396         return FALSE;
4399 static enum request
4400 blame_request(struct view *view, enum request request, struct line *line)
4402         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4403         struct blame *blame = line->data;
4405         switch (request) {
4406         case REQ_VIEW_BLAME:
4407                 if (check_blame_commit(blame)) {
4408                         string_copy(opt_ref, blame->commit->id);
4409                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4410                 }
4411                 break;
4413         case REQ_PARENT:
4414                 if (check_blame_commit(blame) &&
4415                     select_commit_parent(blame->commit->id, opt_ref))
4416                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4417                 break;
4419         case REQ_ENTER:
4420                 if (!blame->commit) {
4421                         report("No commit loaded yet");
4422                         break;
4423                 }
4425                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4426                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4427                         break;
4429                 if (!strcmp(blame->commit->id, NULL_ID)) {
4430                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4431                         const char *diff_index_argv[] = {
4432                                 "git", "diff-index", "--root", "--patch-with-stat",
4433                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4434                         };
4436                         if (!blame->commit->has_previous) {
4437                                 diff_index_argv[1] = "diff";
4438                                 diff_index_argv[2] = "--no-color";
4439                                 diff_index_argv[6] = "--";
4440                                 diff_index_argv[7] = "/dev/null";
4441                         }
4443                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4444                                 report("Failed to allocate diff command");
4445                                 break;
4446                         }
4447                         flags |= OPEN_PREPARED;
4448                 }
4450                 open_view(view, REQ_VIEW_DIFF, flags);
4451                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4452                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4453                 break;
4455         default:
4456                 return request;
4457         }
4459         return REQ_NONE;
4462 static bool
4463 blame_grep(struct view *view, struct line *line)
4465         struct blame *blame = line->data;
4466         struct blame_commit *commit = blame->commit;
4467         regmatch_t pmatch;
4469 #define MATCH(text, on)                                                 \
4470         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4472         if (commit) {
4473                 char buf[DATE_COLS + 1];
4475                 if (MATCH(commit->title, 1) ||
4476                     MATCH(commit->author, opt_author) ||
4477                     MATCH(commit->id, opt_date))
4478                         return TRUE;
4480                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4481                     MATCH(buf, 1))
4482                         return TRUE;
4483         }
4485         return MATCH(blame->text, 1);
4487 #undef MATCH
4490 static void
4491 blame_select(struct view *view, struct line *line)
4493         struct blame *blame = line->data;
4494         struct blame_commit *commit = blame->commit;
4496         if (!commit)
4497                 return;
4499         if (!strcmp(commit->id, NULL_ID))
4500                 string_ncopy(ref_commit, "HEAD", 4);
4501         else
4502                 string_copy_rev(ref_commit, commit->id);
4505 static struct view_ops blame_ops = {
4506         "line",
4507         NULL,
4508         blame_open,
4509         blame_read,
4510         blame_draw,
4511         blame_request,
4512         blame_grep,
4513         blame_select,
4514 };
4516 /*
4517  * Status backend
4518  */
4520 struct status {
4521         char status;
4522         struct {
4523                 mode_t mode;
4524                 char rev[SIZEOF_REV];
4525                 char name[SIZEOF_STR];
4526         } old;
4527         struct {
4528                 mode_t mode;
4529                 char rev[SIZEOF_REV];
4530                 char name[SIZEOF_STR];
4531         } new;
4532 };
4534 static char status_onbranch[SIZEOF_STR];
4535 static struct status stage_status;
4536 static enum line_type stage_line_type;
4537 static size_t stage_chunks;
4538 static int *stage_chunk;
4540 /* This should work even for the "On branch" line. */
4541 static inline bool
4542 status_has_none(struct view *view, struct line *line)
4544         return line < view->line + view->lines && !line[1].data;
4547 /* Get fields from the diff line:
4548  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4549  */
4550 static inline bool
4551 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4553         const char *old_mode = buf +  1;
4554         const char *new_mode = buf +  8;
4555         const char *old_rev  = buf + 15;
4556         const char *new_rev  = buf + 56;
4557         const char *status   = buf + 97;
4559         if (bufsize < 98 ||
4560             old_mode[-1] != ':' ||
4561             new_mode[-1] != ' ' ||
4562             old_rev[-1]  != ' ' ||
4563             new_rev[-1]  != ' ' ||
4564             status[-1]   != ' ')
4565                 return FALSE;
4567         file->status = *status;
4569         string_copy_rev(file->old.rev, old_rev);
4570         string_copy_rev(file->new.rev, new_rev);
4572         file->old.mode = strtoul(old_mode, NULL, 8);
4573         file->new.mode = strtoul(new_mode, NULL, 8);
4575         file->old.name[0] = file->new.name[0] = 0;
4577         return TRUE;
4580 static bool
4581 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4583         struct status *file = NULL;
4584         struct status *unmerged = NULL;
4585         char *buf;
4586         struct io io = {};
4588         if (!run_io(&io, argv, NULL, IO_RD))
4589                 return FALSE;
4591         add_line_data(view, NULL, type);
4593         while ((buf = io_get(&io, 0, TRUE))) {
4594                 if (!file) {
4595                         file = calloc(1, sizeof(*file));
4596                         if (!file || !add_line_data(view, file, type))
4597                                 goto error_out;
4598                 }
4600                 /* Parse diff info part. */
4601                 if (status) {
4602                         file->status = status;
4603                         if (status == 'A')
4604                                 string_copy(file->old.rev, NULL_ID);
4606                 } else if (!file->status) {
4607                         if (!status_get_diff(file, buf, strlen(buf)))
4608                                 goto error_out;
4610                         buf = io_get(&io, 0, TRUE);
4611                         if (!buf)
4612                                 break;
4614                         /* Collapse all 'M'odified entries that follow a
4615                          * associated 'U'nmerged entry. */
4616                         if (file->status == 'U') {
4617                                 unmerged = file;
4619                         } else if (unmerged) {
4620                                 int collapse = !strcmp(buf, unmerged->new.name);
4622                                 unmerged = NULL;
4623                                 if (collapse) {
4624                                         free(file);
4625                                         file = NULL;
4626                                         view->lines--;
4627                                         continue;
4628                                 }
4629                         }
4630                 }
4632                 /* Grab the old name for rename/copy. */
4633                 if (!*file->old.name &&
4634                     (file->status == 'R' || file->status == 'C')) {
4635                         string_ncopy(file->old.name, buf, strlen(buf));
4637                         buf = io_get(&io, 0, TRUE);
4638                         if (!buf)
4639                                 break;
4640                 }
4642                 /* git-ls-files just delivers a NUL separated list of
4643                  * file names similar to the second half of the
4644                  * git-diff-* output. */
4645                 string_ncopy(file->new.name, buf, strlen(buf));
4646                 if (!*file->old.name)
4647                         string_copy(file->old.name, file->new.name);
4648                 file = NULL;
4649         }
4651         if (io_error(&io)) {
4652 error_out:
4653                 done_io(&io);
4654                 return FALSE;
4655         }
4657         if (!view->line[view->lines - 1].data)
4658                 add_line_data(view, NULL, LINE_STAT_NONE);
4660         done_io(&io);
4661         return TRUE;
4664 /* Don't show unmerged entries in the staged section. */
4665 static const char *status_diff_index_argv[] = {
4666         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4667                              "--cached", "-M", "HEAD", NULL
4668 };
4670 static const char *status_diff_files_argv[] = {
4671         "git", "diff-files", "-z", NULL
4672 };
4674 static const char *status_list_other_argv[] = {
4675         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4676 };
4678 static const char *status_list_no_head_argv[] = {
4679         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4680 };
4682 static const char *update_index_argv[] = {
4683         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4684 };
4686 /* Restore the previous line number to stay in the context or select a
4687  * line with something that can be updated. */
4688 static void
4689 status_restore(struct view *view)
4691         if (view->p_lineno >= view->lines)
4692                 view->p_lineno = view->lines - 1;
4693         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4694                 view->p_lineno++;
4695         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4696                 view->p_lineno--;
4698         /* If the above fails, always skip the "On branch" line. */
4699         if (view->p_lineno < view->lines)
4700                 view->lineno = view->p_lineno;
4701         else
4702                 view->lineno = 1;
4704         if (view->lineno < view->offset)
4705                 view->offset = view->lineno;
4706         else if (view->offset + view->height <= view->lineno)
4707                 view->offset = view->lineno - view->height + 1;
4709         view->p_restore = FALSE;
4712 /* First parse staged info using git-diff-index(1), then parse unstaged
4713  * info using git-diff-files(1), and finally untracked files using
4714  * git-ls-files(1). */
4715 static bool
4716 status_open(struct view *view)
4718         reset_view(view);
4720         add_line_data(view, NULL, LINE_STAT_HEAD);
4721         if (is_initial_commit())
4722                 string_copy(status_onbranch, "Initial commit");
4723         else if (!*opt_head)
4724                 string_copy(status_onbranch, "Not currently on any branch");
4725         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4726                 return FALSE;
4728         run_io_bg(update_index_argv);
4730         if (is_initial_commit()) {
4731                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4732                         return FALSE;
4733         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4734                 return FALSE;
4735         }
4737         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4738             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4739                 return FALSE;
4741         /* Restore the exact position or use the specialized restore
4742          * mode? */
4743         if (!view->p_restore)
4744                 status_restore(view);
4745         return TRUE;
4748 static bool
4749 status_draw(struct view *view, struct line *line, unsigned int lineno)
4751         struct status *status = line->data;
4752         enum line_type type;
4753         const char *text;
4755         if (!status) {
4756                 switch (line->type) {
4757                 case LINE_STAT_STAGED:
4758                         type = LINE_STAT_SECTION;
4759                         text = "Changes to be committed:";
4760                         break;
4762                 case LINE_STAT_UNSTAGED:
4763                         type = LINE_STAT_SECTION;
4764                         text = "Changed but not updated:";
4765                         break;
4767                 case LINE_STAT_UNTRACKED:
4768                         type = LINE_STAT_SECTION;
4769                         text = "Untracked files:";
4770                         break;
4772                 case LINE_STAT_NONE:
4773                         type = LINE_DEFAULT;
4774                         text = "    (no files)";
4775                         break;
4777                 case LINE_STAT_HEAD:
4778                         type = LINE_STAT_HEAD;
4779                         text = status_onbranch;
4780                         break;
4782                 default:
4783                         return FALSE;
4784                 }
4785         } else {
4786                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4788                 buf[0] = status->status;
4789                 if (draw_text(view, line->type, buf, TRUE))
4790                         return TRUE;
4791                 type = LINE_DEFAULT;
4792                 text = status->new.name;
4793         }
4795         draw_text(view, type, text, TRUE);
4796         return TRUE;
4799 static enum request
4800 status_enter(struct view *view, struct line *line)
4802         struct status *status = line->data;
4803         const char *oldpath = status ? status->old.name : NULL;
4804         /* Diffs for unmerged entries are empty when passing the new
4805          * path, so leave it empty. */
4806         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4807         const char *info;
4808         enum open_flags split;
4809         struct view *stage = VIEW(REQ_VIEW_STAGE);
4811         if (line->type == LINE_STAT_NONE ||
4812             (!status && line[1].type == LINE_STAT_NONE)) {
4813                 report("No file to diff");
4814                 return REQ_NONE;
4815         }
4817         switch (line->type) {
4818         case LINE_STAT_STAGED:
4819                 if (is_initial_commit()) {
4820                         const char *no_head_diff_argv[] = {
4821                                 "git", "diff", "--no-color", "--patch-with-stat",
4822                                         "--", "/dev/null", newpath, NULL
4823                         };
4825                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4826                                 return REQ_QUIT;
4827                 } else {
4828                         const char *index_show_argv[] = {
4829                                 "git", "diff-index", "--root", "--patch-with-stat",
4830                                         "-C", "-M", "--cached", "HEAD", "--",
4831                                         oldpath, newpath, NULL
4832                         };
4834                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4835                                 return REQ_QUIT;
4836                 }
4838                 if (status)
4839                         info = "Staged changes to %s";
4840                 else
4841                         info = "Staged changes";
4842                 break;
4844         case LINE_STAT_UNSTAGED:
4845         {
4846                 const char *files_show_argv[] = {
4847                         "git", "diff-files", "--root", "--patch-with-stat",
4848                                 "-C", "-M", "--", oldpath, newpath, NULL
4849                 };
4851                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4852                         return REQ_QUIT;
4853                 if (status)
4854                         info = "Unstaged changes to %s";
4855                 else
4856                         info = "Unstaged changes";
4857                 break;
4858         }
4859         case LINE_STAT_UNTRACKED:
4860                 if (!newpath) {
4861                         report("No file to show");
4862                         return REQ_NONE;
4863                 }
4865                 if (!suffixcmp(status->new.name, -1, "/")) {
4866                         report("Cannot display a directory");
4867                         return REQ_NONE;
4868                 }
4870                 if (!prepare_update_file(stage, newpath))
4871                         return REQ_QUIT;
4872                 info = "Untracked file %s";
4873                 break;
4875         case LINE_STAT_HEAD:
4876                 return REQ_NONE;
4878         default:
4879                 die("line type %d not handled in switch", line->type);
4880         }
4882         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4883         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4884         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4885                 if (status) {
4886                         stage_status = *status;
4887                 } else {
4888                         memset(&stage_status, 0, sizeof(stage_status));
4889                 }
4891                 stage_line_type = line->type;
4892                 stage_chunks = 0;
4893                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4894         }
4896         return REQ_NONE;
4899 static bool
4900 status_exists(struct status *status, enum line_type type)
4902         struct view *view = VIEW(REQ_VIEW_STATUS);
4903         unsigned long lineno;
4905         for (lineno = 0; lineno < view->lines; lineno++) {
4906                 struct line *line = &view->line[lineno];
4907                 struct status *pos = line->data;
4909                 if (line->type != type)
4910                         continue;
4911                 if (!pos && (!status || !status->status) && line[1].data) {
4912                         select_view_line(view, lineno);
4913                         return TRUE;
4914                 }
4915                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4916                         select_view_line(view, lineno);
4917                         return TRUE;
4918                 }
4919         }
4921         return FALSE;
4925 static bool
4926 status_update_prepare(struct io *io, enum line_type type)
4928         const char *staged_argv[] = {
4929                 "git", "update-index", "-z", "--index-info", NULL
4930         };
4931         const char *others_argv[] = {
4932                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4933         };
4935         switch (type) {
4936         case LINE_STAT_STAGED:
4937                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4939         case LINE_STAT_UNSTAGED:
4940                 return run_io(io, others_argv, opt_cdup, IO_WR);
4942         case LINE_STAT_UNTRACKED:
4943                 return run_io(io, others_argv, NULL, IO_WR);
4945         default:
4946                 die("line type %d not handled in switch", type);
4947                 return FALSE;
4948         }
4951 static bool
4952 status_update_write(struct io *io, struct status *status, enum line_type type)
4954         char buf[SIZEOF_STR];
4955         size_t bufsize = 0;
4957         switch (type) {
4958         case LINE_STAT_STAGED:
4959                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4960                                         status->old.mode,
4961                                         status->old.rev,
4962                                         status->old.name, 0))
4963                         return FALSE;
4964                 break;
4966         case LINE_STAT_UNSTAGED:
4967         case LINE_STAT_UNTRACKED:
4968                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4969                         return FALSE;
4970                 break;
4972         default:
4973                 die("line type %d not handled in switch", type);
4974         }
4976         return io_write(io, buf, bufsize);
4979 static bool
4980 status_update_file(struct status *status, enum line_type type)
4982         struct io io = {};
4983         bool result;
4985         if (!status_update_prepare(&io, type))
4986                 return FALSE;
4988         result = status_update_write(&io, status, type);
4989         done_io(&io);
4990         return result;
4993 static bool
4994 status_update_files(struct view *view, struct line *line)
4996         struct io io = {};
4997         bool result = TRUE;
4998         struct line *pos = view->line + view->lines;
4999         int files = 0;
5000         int file, done;
5002         if (!status_update_prepare(&io, line->type))
5003                 return FALSE;
5005         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5006                 files++;
5008         for (file = 0, done = 0; result && file < files; line++, file++) {
5009                 int almost_done = file * 100 / files;
5011                 if (almost_done > done) {
5012                         done = almost_done;
5013                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5014                                       file, files, done);
5015                         update_view_title(view);
5016                 }
5017                 result = status_update_write(&io, line->data, line->type);
5018         }
5020         done_io(&io);
5021         return result;
5024 static bool
5025 status_update(struct view *view)
5027         struct line *line = &view->line[view->lineno];
5029         assert(view->lines);
5031         if (!line->data) {
5032                 /* This should work even for the "On branch" line. */
5033                 if (line < view->line + view->lines && !line[1].data) {
5034                         report("Nothing to update");
5035                         return FALSE;
5036                 }
5038                 if (!status_update_files(view, line + 1)) {
5039                         report("Failed to update file status");
5040                         return FALSE;
5041                 }
5043         } else if (!status_update_file(line->data, line->type)) {
5044                 report("Failed to update file status");
5045                 return FALSE;
5046         }
5048         return TRUE;
5051 static bool
5052 status_revert(struct status *status, enum line_type type, bool has_none)
5054         if (!status || type != LINE_STAT_UNSTAGED) {
5055                 if (type == LINE_STAT_STAGED) {
5056                         report("Cannot revert changes to staged files");
5057                 } else if (type == LINE_STAT_UNTRACKED) {
5058                         report("Cannot revert changes to untracked files");
5059                 } else if (has_none) {
5060                         report("Nothing to revert");
5061                 } else {
5062                         report("Cannot revert changes to multiple files");
5063                 }
5064                 return FALSE;
5066         } else {
5067                 const char *checkout_argv[] = {
5068                         "git", "checkout", "--", status->old.name, NULL
5069                 };
5071                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5072                         return FALSE;
5073                 return run_io_fg(checkout_argv, opt_cdup);
5074         }
5077 static enum request
5078 status_request(struct view *view, enum request request, struct line *line)
5080         struct status *status = line->data;
5082         switch (request) {
5083         case REQ_STATUS_UPDATE:
5084                 if (!status_update(view))
5085                         return REQ_NONE;
5086                 break;
5088         case REQ_STATUS_REVERT:
5089                 if (!status_revert(status, line->type, status_has_none(view, line)))
5090                         return REQ_NONE;
5091                 break;
5093         case REQ_STATUS_MERGE:
5094                 if (!status || status->status != 'U') {
5095                         report("Merging only possible for files with unmerged status ('U').");
5096                         return REQ_NONE;
5097                 }
5098                 open_mergetool(status->new.name);
5099                 break;
5101         case REQ_EDIT:
5102                 if (!status)
5103                         return request;
5104                 if (status->status == 'D') {
5105                         report("File has been deleted.");
5106                         return REQ_NONE;
5107                 }
5109                 open_editor(status->status != '?', status->new.name);
5110                 break;
5112         case REQ_VIEW_BLAME:
5113                 if (status) {
5114                         string_copy(opt_file, status->new.name);
5115                         opt_ref[0] = 0;
5116                 }
5117                 return request;
5119         case REQ_ENTER:
5120                 /* After returning the status view has been split to
5121                  * show the stage view. No further reloading is
5122                  * necessary. */
5123                 status_enter(view, line);
5124                 return REQ_NONE;
5126         case REQ_REFRESH:
5127                 /* Simply reload the view. */
5128                 break;
5130         default:
5131                 return request;
5132         }
5134         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5136         return REQ_NONE;
5139 static void
5140 status_select(struct view *view, struct line *line)
5142         struct status *status = line->data;
5143         char file[SIZEOF_STR] = "all files";
5144         const char *text;
5145         const char *key;
5147         if (status && !string_format(file, "'%s'", status->new.name))
5148                 return;
5150         if (!status && line[1].type == LINE_STAT_NONE)
5151                 line++;
5153         switch (line->type) {
5154         case LINE_STAT_STAGED:
5155                 text = "Press %s to unstage %s for commit";
5156                 break;
5158         case LINE_STAT_UNSTAGED:
5159                 text = "Press %s to stage %s for commit";
5160                 break;
5162         case LINE_STAT_UNTRACKED:
5163                 text = "Press %s to stage %s for addition";
5164                 break;
5166         case LINE_STAT_HEAD:
5167         case LINE_STAT_NONE:
5168                 text = "Nothing to update";
5169                 break;
5171         default:
5172                 die("line type %d not handled in switch", line->type);
5173         }
5175         if (status && status->status == 'U') {
5176                 text = "Press %s to resolve conflict in %s";
5177                 key = get_key(REQ_STATUS_MERGE);
5179         } else {
5180                 key = get_key(REQ_STATUS_UPDATE);
5181         }
5183         string_format(view->ref, text, key, file);
5186 static bool
5187 status_grep(struct view *view, struct line *line)
5189         struct status *status = line->data;
5190         enum { S_STATUS, S_NAME, S_END } state;
5191         char buf[2] = "?";
5192         regmatch_t pmatch;
5194         if (!status)
5195                 return FALSE;
5197         for (state = S_STATUS; state < S_END; state++) {
5198                 const char *text;
5200                 switch (state) {
5201                 case S_NAME:    text = status->new.name;        break;
5202                 case S_STATUS:
5203                         buf[0] = status->status;
5204                         text = buf;
5205                         break;
5207                 default:
5208                         return FALSE;
5209                 }
5211                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5212                         return TRUE;
5213         }
5215         return FALSE;
5218 static struct view_ops status_ops = {
5219         "file",
5220         NULL,
5221         status_open,
5222         NULL,
5223         status_draw,
5224         status_request,
5225         status_grep,
5226         status_select,
5227 };
5230 static bool
5231 stage_diff_write(struct io *io, struct line *line, struct line *end)
5233         while (line < end) {
5234                 if (!io_write(io, line->data, strlen(line->data)) ||
5235                     !io_write(io, "\n", 1))
5236                         return FALSE;
5237                 line++;
5238                 if (line->type == LINE_DIFF_CHUNK ||
5239                     line->type == LINE_DIFF_HEADER)
5240                         break;
5241         }
5243         return TRUE;
5246 static struct line *
5247 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5249         for (; view->line < line; line--)
5250                 if (line->type == type)
5251                         return line;
5253         return NULL;
5256 static bool
5257 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5259         const char *apply_argv[SIZEOF_ARG] = {
5260                 "git", "apply", "--whitespace=nowarn", NULL
5261         };
5262         struct line *diff_hdr;
5263         struct io io = {};
5264         int argc = 3;
5266         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5267         if (!diff_hdr)
5268                 return FALSE;
5270         if (!revert)
5271                 apply_argv[argc++] = "--cached";
5272         if (revert || stage_line_type == LINE_STAT_STAGED)
5273                 apply_argv[argc++] = "-R";
5274         apply_argv[argc++] = "-";
5275         apply_argv[argc++] = NULL;
5276         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5277                 return FALSE;
5279         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5280             !stage_diff_write(&io, chunk, view->line + view->lines))
5281                 chunk = NULL;
5283         done_io(&io);
5284         run_io_bg(update_index_argv);
5286         return chunk ? TRUE : FALSE;
5289 static bool
5290 stage_update(struct view *view, struct line *line)
5292         struct line *chunk = NULL;
5294         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5295                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5297         if (chunk) {
5298                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5299                         report("Failed to apply chunk");
5300                         return FALSE;
5301                 }
5303         } else if (!stage_status.status) {
5304                 view = VIEW(REQ_VIEW_STATUS);
5306                 for (line = view->line; line < view->line + view->lines; line++)
5307                         if (line->type == stage_line_type)
5308                                 break;
5310                 if (!status_update_files(view, line + 1)) {
5311                         report("Failed to update files");
5312                         return FALSE;
5313                 }
5315         } else if (!status_update_file(&stage_status, stage_line_type)) {
5316                 report("Failed to update file");
5317                 return FALSE;
5318         }
5320         return TRUE;
5323 static bool
5324 stage_revert(struct view *view, struct line *line)
5326         struct line *chunk = NULL;
5328         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5329                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5331         if (chunk) {
5332                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5333                         return FALSE;
5335                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5336                         report("Failed to revert chunk");
5337                         return FALSE;
5338                 }
5339                 return TRUE;
5341         } else {
5342                 return status_revert(stage_status.status ? &stage_status : NULL,
5343                                      stage_line_type, FALSE);
5344         }
5348 static void
5349 stage_next(struct view *view, struct line *line)
5351         int i;
5353         if (!stage_chunks) {
5354                 static size_t alloc = 0;
5355                 int *tmp;
5357                 for (line = view->line; line < view->line + view->lines; line++) {
5358                         if (line->type != LINE_DIFF_CHUNK)
5359                                 continue;
5361                         tmp = realloc_items(stage_chunk, &alloc,
5362                                             stage_chunks, sizeof(*tmp));
5363                         if (!tmp) {
5364                                 report("Allocation failure");
5365                                 return;
5366                         }
5368                         stage_chunk = tmp;
5369                         stage_chunk[stage_chunks++] = line - view->line;
5370                 }
5371         }
5373         for (i = 0; i < stage_chunks; i++) {
5374                 if (stage_chunk[i] > view->lineno) {
5375                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5376                         report("Chunk %d of %d", i + 1, stage_chunks);
5377                         return;
5378                 }
5379         }
5381         report("No next chunk found");
5384 static enum request
5385 stage_request(struct view *view, enum request request, struct line *line)
5387         switch (request) {
5388         case REQ_STATUS_UPDATE:
5389                 if (!stage_update(view, line))
5390                         return REQ_NONE;
5391                 break;
5393         case REQ_STATUS_REVERT:
5394                 if (!stage_revert(view, line))
5395                         return REQ_NONE;
5396                 break;
5398         case REQ_STAGE_NEXT:
5399                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5400                         report("File is untracked; press %s to add",
5401                                get_key(REQ_STATUS_UPDATE));
5402                         return REQ_NONE;
5403                 }
5404                 stage_next(view, line);
5405                 return REQ_NONE;
5407         case REQ_EDIT:
5408                 if (!stage_status.new.name[0])
5409                         return request;
5410                 if (stage_status.status == 'D') {
5411                         report("File has been deleted.");
5412                         return REQ_NONE;
5413                 }
5415                 open_editor(stage_status.status != '?', stage_status.new.name);
5416                 break;
5418         case REQ_REFRESH:
5419                 /* Reload everything ... */
5420                 break;
5422         case REQ_VIEW_BLAME:
5423                 if (stage_status.new.name[0]) {
5424                         string_copy(opt_file, stage_status.new.name);
5425                         opt_ref[0] = 0;
5426                 }
5427                 return request;
5429         case REQ_ENTER:
5430                 return pager_request(view, request, line);
5432         default:
5433                 return request;
5434         }
5436         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5437         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5439         /* Check whether the staged entry still exists, and close the
5440          * stage view if it doesn't. */
5441         if (!status_exists(&stage_status, stage_line_type)) {
5442                 status_restore(VIEW(REQ_VIEW_STATUS));
5443                 return REQ_VIEW_CLOSE;
5444         }
5446         if (stage_line_type == LINE_STAT_UNTRACKED) {
5447                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5448                         report("Cannot display a directory");
5449                         return REQ_NONE;
5450                 }
5452                 if (!prepare_update_file(view, stage_status.new.name)) {
5453                         report("Failed to open file: %s", strerror(errno));
5454                         return REQ_NONE;
5455                 }
5456         }
5457         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5459         return REQ_NONE;
5462 static struct view_ops stage_ops = {
5463         "line",
5464         NULL,
5465         NULL,
5466         pager_read,
5467         pager_draw,
5468         stage_request,
5469         pager_grep,
5470         pager_select,
5471 };
5474 /*
5475  * Revision graph
5476  */
5478 struct commit {
5479         char id[SIZEOF_REV];            /* SHA1 ID. */
5480         char title[128];                /* First line of the commit message. */
5481         char author[75];                /* Author of the commit. */
5482         struct tm time;                 /* Date from the author ident. */
5483         struct ref **refs;              /* Repository references. */
5484         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5485         size_t graph_size;              /* The width of the graph array. */
5486         bool has_parents;               /* Rewritten --parents seen. */
5487 };
5489 /* Size of rev graph with no  "padding" columns */
5490 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5492 struct rev_graph {
5493         struct rev_graph *prev, *next, *parents;
5494         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5495         size_t size;
5496         struct commit *commit;
5497         size_t pos;
5498         unsigned int boundary:1;
5499 };
5501 /* Parents of the commit being visualized. */
5502 static struct rev_graph graph_parents[4];
5504 /* The current stack of revisions on the graph. */
5505 static struct rev_graph graph_stacks[4] = {
5506         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5507         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5508         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5509         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5510 };
5512 static inline bool
5513 graph_parent_is_merge(struct rev_graph *graph)
5515         return graph->parents->size > 1;
5518 static inline void
5519 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5521         struct commit *commit = graph->commit;
5523         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5524                 commit->graph[commit->graph_size++] = symbol;
5527 static void
5528 clear_rev_graph(struct rev_graph *graph)
5530         graph->boundary = 0;
5531         graph->size = graph->pos = 0;
5532         graph->commit = NULL;
5533         memset(graph->parents, 0, sizeof(*graph->parents));
5536 static void
5537 done_rev_graph(struct rev_graph *graph)
5539         if (graph_parent_is_merge(graph) &&
5540             graph->pos < graph->size - 1 &&
5541             graph->next->size == graph->size + graph->parents->size - 1) {
5542                 size_t i = graph->pos + graph->parents->size - 1;
5544                 graph->commit->graph_size = i * 2;
5545                 while (i < graph->next->size - 1) {
5546                         append_to_rev_graph(graph, ' ');
5547                         append_to_rev_graph(graph, '\\');
5548                         i++;
5549                 }
5550         }
5552         clear_rev_graph(graph);
5555 static void
5556 push_rev_graph(struct rev_graph *graph, const char *parent)
5558         int i;
5560         /* "Collapse" duplicate parents lines.
5561          *
5562          * FIXME: This needs to also update update the drawn graph but
5563          * for now it just serves as a method for pruning graph lines. */
5564         for (i = 0; i < graph->size; i++)
5565                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5566                         return;
5568         if (graph->size < SIZEOF_REVITEMS) {
5569                 string_copy_rev(graph->rev[graph->size++], parent);
5570         }
5573 static chtype
5574 get_rev_graph_symbol(struct rev_graph *graph)
5576         chtype symbol;
5578         if (graph->boundary)
5579                 symbol = REVGRAPH_BOUND;
5580         else if (graph->parents->size == 0)
5581                 symbol = REVGRAPH_INIT;
5582         else if (graph_parent_is_merge(graph))
5583                 symbol = REVGRAPH_MERGE;
5584         else if (graph->pos >= graph->size)
5585                 symbol = REVGRAPH_BRANCH;
5586         else
5587                 symbol = REVGRAPH_COMMIT;
5589         return symbol;
5592 static void
5593 draw_rev_graph(struct rev_graph *graph)
5595         struct rev_filler {
5596                 chtype separator, line;
5597         };
5598         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5599         static struct rev_filler fillers[] = {
5600                 { ' ',  '|' },
5601                 { '`',  '.' },
5602                 { '\'', ' ' },
5603                 { '/',  ' ' },
5604         };
5605         chtype symbol = get_rev_graph_symbol(graph);
5606         struct rev_filler *filler;
5607         size_t i;
5609         if (opt_line_graphics)
5610                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5612         filler = &fillers[DEFAULT];
5614         for (i = 0; i < graph->pos; i++) {
5615                 append_to_rev_graph(graph, filler->line);
5616                 if (graph_parent_is_merge(graph->prev) &&
5617                     graph->prev->pos == i)
5618                         filler = &fillers[RSHARP];
5620                 append_to_rev_graph(graph, filler->separator);
5621         }
5623         /* Place the symbol for this revision. */
5624         append_to_rev_graph(graph, symbol);
5626         if (graph->prev->size > graph->size)
5627                 filler = &fillers[RDIAG];
5628         else
5629                 filler = &fillers[DEFAULT];
5631         i++;
5633         for (; i < graph->size; i++) {
5634                 append_to_rev_graph(graph, filler->separator);
5635                 append_to_rev_graph(graph, filler->line);
5636                 if (graph_parent_is_merge(graph->prev) &&
5637                     i < graph->prev->pos + graph->parents->size)
5638                         filler = &fillers[RSHARP];
5639                 if (graph->prev->size > graph->size)
5640                         filler = &fillers[LDIAG];
5641         }
5643         if (graph->prev->size > graph->size) {
5644                 append_to_rev_graph(graph, filler->separator);
5645                 if (filler->line != ' ')
5646                         append_to_rev_graph(graph, filler->line);
5647         }
5650 /* Prepare the next rev graph */
5651 static void
5652 prepare_rev_graph(struct rev_graph *graph)
5654         size_t i;
5656         /* First, traverse all lines of revisions up to the active one. */
5657         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5658                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5659                         break;
5661                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5662         }
5664         /* Interleave the new revision parent(s). */
5665         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5666                 push_rev_graph(graph->next, graph->parents->rev[i]);
5668         /* Lastly, put any remaining revisions. */
5669         for (i = graph->pos + 1; i < graph->size; i++)
5670                 push_rev_graph(graph->next, graph->rev[i]);
5673 static void
5674 update_rev_graph(struct view *view, struct rev_graph *graph)
5676         /* If this is the finalizing update ... */
5677         if (graph->commit)
5678                 prepare_rev_graph(graph);
5680         /* Graph visualization needs a one rev look-ahead,
5681          * so the first update doesn't visualize anything. */
5682         if (!graph->prev->commit)
5683                 return;
5685         if (view->lines > 2)
5686                 view->line[view->lines - 3].dirty = 1;
5687         if (view->lines > 1)
5688                 view->line[view->lines - 2].dirty = 1;
5689         draw_rev_graph(graph->prev);
5690         done_rev_graph(graph->prev->prev);
5694 /*
5695  * Main view backend
5696  */
5698 static const char *main_argv[SIZEOF_ARG] = {
5699         "git", "log", "--no-color", "--pretty=raw", "--parents",
5700                       "--topo-order", "%(head)", NULL
5701 };
5703 static bool
5704 main_draw(struct view *view, struct line *line, unsigned int lineno)
5706         struct commit *commit = line->data;
5708         if (!*commit->author)
5709                 return FALSE;
5711         if (opt_date && draw_date(view, &commit->time))
5712                 return TRUE;
5714         if (opt_author &&
5715             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5716                 return TRUE;
5718         if (opt_rev_graph && commit->graph_size &&
5719             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5720                 return TRUE;
5722         if (opt_show_refs && commit->refs) {
5723                 size_t i = 0;
5725                 do {
5726                         enum line_type type;
5728                         if (commit->refs[i]->head)
5729                                 type = LINE_MAIN_HEAD;
5730                         else if (commit->refs[i]->ltag)
5731                                 type = LINE_MAIN_LOCAL_TAG;
5732                         else if (commit->refs[i]->tag)
5733                                 type = LINE_MAIN_TAG;
5734                         else if (commit->refs[i]->tracked)
5735                                 type = LINE_MAIN_TRACKED;
5736                         else if (commit->refs[i]->remote)
5737                                 type = LINE_MAIN_REMOTE;
5738                         else
5739                                 type = LINE_MAIN_REF;
5741                         if (draw_text(view, type, "[", TRUE) ||
5742                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5743                             draw_text(view, type, "]", TRUE))
5744                                 return TRUE;
5746                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5747                                 return TRUE;
5748                 } while (commit->refs[i++]->next);
5749         }
5751         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5752         return TRUE;
5755 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5756 static bool
5757 main_read(struct view *view, char *line)
5759         static struct rev_graph *graph = graph_stacks;
5760         enum line_type type;
5761         struct commit *commit;
5763         if (!line) {
5764                 int i;
5766                 if (!view->lines && !view->parent)
5767                         die("No revisions match the given arguments.");
5768                 if (view->lines > 0) {
5769                         commit = view->line[view->lines - 1].data;
5770                         view->line[view->lines - 1].dirty = 1;
5771                         if (!*commit->author) {
5772                                 view->lines--;
5773                                 free(commit);
5774                                 graph->commit = NULL;
5775                         }
5776                 }
5777                 update_rev_graph(view, graph);
5779                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5780                         clear_rev_graph(&graph_stacks[i]);
5781                 return TRUE;
5782         }
5784         type = get_line_type(line);
5785         if (type == LINE_COMMIT) {
5786                 commit = calloc(1, sizeof(struct commit));
5787                 if (!commit)
5788                         return FALSE;
5790                 line += STRING_SIZE("commit ");
5791                 if (*line == '-') {
5792                         graph->boundary = 1;
5793                         line++;
5794                 }
5796                 string_copy_rev(commit->id, line);
5797                 commit->refs = get_refs(commit->id);
5798                 graph->commit = commit;
5799                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5801                 while ((line = strchr(line, ' '))) {
5802                         line++;
5803                         push_rev_graph(graph->parents, line);
5804                         commit->has_parents = TRUE;
5805                 }
5806                 return TRUE;
5807         }
5809         if (!view->lines)
5810                 return TRUE;
5811         commit = view->line[view->lines - 1].data;
5813         switch (type) {
5814         case LINE_PARENT:
5815                 if (commit->has_parents)
5816                         break;
5817                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5818                 break;
5820         case LINE_AUTHOR:
5821                 parse_author_line(line + STRING_SIZE("author "),
5822                                   commit->author, sizeof(commit->author),
5823                                   &commit->time);
5824                 update_rev_graph(view, graph);
5825                 graph = graph->next;
5826                 break;
5828         default:
5829                 /* Fill in the commit title if it has not already been set. */
5830                 if (commit->title[0])
5831                         break;
5833                 /* Require titles to start with a non-space character at the
5834                  * offset used by git log. */
5835                 if (strncmp(line, "    ", 4))
5836                         break;
5837                 line += 4;
5838                 /* Well, if the title starts with a whitespace character,
5839                  * try to be forgiving.  Otherwise we end up with no title. */
5840                 while (isspace(*line))
5841                         line++;
5842                 if (*line == '\0')
5843                         break;
5844                 /* FIXME: More graceful handling of titles; append "..." to
5845                  * shortened titles, etc. */
5847                 string_ncopy(commit->title, line, strlen(line));
5848                 view->line[view->lines - 1].dirty = 1;
5849         }
5851         return TRUE;
5854 static enum request
5855 main_request(struct view *view, enum request request, struct line *line)
5857         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5859         switch (request) {
5860         case REQ_ENTER:
5861                 open_view(view, REQ_VIEW_DIFF, flags);
5862                 break;
5863         case REQ_REFRESH:
5864                 load_refs();
5865                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5866                 break;
5867         default:
5868                 return request;
5869         }
5871         return REQ_NONE;
5874 static bool
5875 grep_refs(struct ref **refs, regex_t *regex)
5877         regmatch_t pmatch;
5878         size_t i = 0;
5880         if (!refs)
5881                 return FALSE;
5882         do {
5883                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5884                         return TRUE;
5885         } while (refs[i++]->next);
5887         return FALSE;
5890 static bool
5891 main_grep(struct view *view, struct line *line)
5893         struct commit *commit = line->data;
5894         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5895         char buf[DATE_COLS + 1];
5896         regmatch_t pmatch;
5898         for (state = S_TITLE; state < S_END; state++) {
5899                 char *text;
5901                 switch (state) {
5902                 case S_TITLE:   text = commit->title;   break;
5903                 case S_AUTHOR:
5904                         if (!opt_author)
5905                                 continue;
5906                         text = commit->author;
5907                         break;
5908                 case S_DATE:
5909                         if (!opt_date)
5910                                 continue;
5911                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5912                                 continue;
5913                         text = buf;
5914                         break;
5915                 case S_REFS:
5916                         if (!opt_show_refs)
5917                                 continue;
5918                         if (grep_refs(commit->refs, view->regex) == TRUE)
5919                                 return TRUE;
5920                         continue;
5921                 default:
5922                         return FALSE;
5923                 }
5925                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5926                         return TRUE;
5927         }
5929         return FALSE;
5932 static void
5933 main_select(struct view *view, struct line *line)
5935         struct commit *commit = line->data;
5937         string_copy_rev(view->ref, commit->id);
5938         string_copy_rev(ref_commit, view->ref);
5941 static struct view_ops main_ops = {
5942         "commit",
5943         main_argv,
5944         NULL,
5945         main_read,
5946         main_draw,
5947         main_request,
5948         main_grep,
5949         main_select,
5950 };
5953 /*
5954  * Unicode / UTF-8 handling
5955  *
5956  * NOTE: Much of the following code for dealing with unicode is derived from
5957  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5958  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5959  */
5961 /* I've (over)annotated a lot of code snippets because I am not entirely
5962  * confident that the approach taken by this small UTF-8 interface is correct.
5963  * --jonas */
5965 static inline int
5966 unicode_width(unsigned long c)
5968         if (c >= 0x1100 &&
5969            (c <= 0x115f                         /* Hangul Jamo */
5970             || c == 0x2329
5971             || c == 0x232a
5972             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5973                                                 /* CJK ... Yi */
5974             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5975             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5976             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5977             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5978             || (c >= 0xffe0  && c <= 0xffe6)
5979             || (c >= 0x20000 && c <= 0x2fffd)
5980             || (c >= 0x30000 && c <= 0x3fffd)))
5981                 return 2;
5983         if (c == '\t')
5984                 return opt_tab_size;
5986         return 1;
5989 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5990  * Illegal bytes are set one. */
5991 static const unsigned char utf8_bytes[256] = {
5992         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5993         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5994         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5995         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5996         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,
5997         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,
5998         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,
5999         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,
6000 };
6002 /* Decode UTF-8 multi-byte representation into a unicode character. */
6003 static inline unsigned long
6004 utf8_to_unicode(const char *string, size_t length)
6006         unsigned long unicode;
6008         switch (length) {
6009         case 1:
6010                 unicode  =   string[0];
6011                 break;
6012         case 2:
6013                 unicode  =  (string[0] & 0x1f) << 6;
6014                 unicode +=  (string[1] & 0x3f);
6015                 break;
6016         case 3:
6017                 unicode  =  (string[0] & 0x0f) << 12;
6018                 unicode += ((string[1] & 0x3f) << 6);
6019                 unicode +=  (string[2] & 0x3f);
6020                 break;
6021         case 4:
6022                 unicode  =  (string[0] & 0x0f) << 18;
6023                 unicode += ((string[1] & 0x3f) << 12);
6024                 unicode += ((string[2] & 0x3f) << 6);
6025                 unicode +=  (string[3] & 0x3f);
6026                 break;
6027         case 5:
6028                 unicode  =  (string[0] & 0x0f) << 24;
6029                 unicode += ((string[1] & 0x3f) << 18);
6030                 unicode += ((string[2] & 0x3f) << 12);
6031                 unicode += ((string[3] & 0x3f) << 6);
6032                 unicode +=  (string[4] & 0x3f);
6033                 break;
6034         case 6:
6035                 unicode  =  (string[0] & 0x01) << 30;
6036                 unicode += ((string[1] & 0x3f) << 24);
6037                 unicode += ((string[2] & 0x3f) << 18);
6038                 unicode += ((string[3] & 0x3f) << 12);
6039                 unicode += ((string[4] & 0x3f) << 6);
6040                 unicode +=  (string[5] & 0x3f);
6041                 break;
6042         default:
6043                 die("Invalid unicode length");
6044         }
6046         /* Invalid characters could return the special 0xfffd value but NUL
6047          * should be just as good. */
6048         return unicode > 0xffff ? 0 : unicode;
6051 /* Calculates how much of string can be shown within the given maximum width
6052  * and sets trimmed parameter to non-zero value if all of string could not be
6053  * shown. If the reserve flag is TRUE, it will reserve at least one
6054  * trailing character, which can be useful when drawing a delimiter.
6055  *
6056  * Returns the number of bytes to output from string to satisfy max_width. */
6057 static size_t
6058 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6060         const char *start = string;
6061         const char *end = strchr(string, '\0');
6062         unsigned char last_bytes = 0;
6063         size_t last_ucwidth = 0;
6065         *width = 0;
6066         *trimmed = 0;
6068         while (string < end) {
6069                 int c = *(unsigned char *) string;
6070                 unsigned char bytes = utf8_bytes[c];
6071                 size_t ucwidth;
6072                 unsigned long unicode;
6074                 if (string + bytes > end)
6075                         break;
6077                 /* Change representation to figure out whether
6078                  * it is a single- or double-width character. */
6080                 unicode = utf8_to_unicode(string, bytes);
6081                 /* FIXME: Graceful handling of invalid unicode character. */
6082                 if (!unicode)
6083                         break;
6085                 ucwidth = unicode_width(unicode);
6086                 *width  += ucwidth;
6087                 if (*width > max_width) {
6088                         *trimmed = 1;
6089                         *width -= ucwidth;
6090                         if (reserve && *width == max_width) {
6091                                 string -= last_bytes;
6092                                 *width -= last_ucwidth;
6093                         }
6094                         break;
6095                 }
6097                 string  += bytes;
6098                 last_bytes = bytes;
6099                 last_ucwidth = ucwidth;
6100         }
6102         return string - start;
6106 /*
6107  * Status management
6108  */
6110 /* Whether or not the curses interface has been initialized. */
6111 static bool cursed = FALSE;
6113 /* The status window is used for polling keystrokes. */
6114 static WINDOW *status_win;
6116 static bool status_empty = FALSE;
6118 /* Update status and title window. */
6119 static void
6120 report(const char *msg, ...)
6122         struct view *view = display[current_view];
6124         if (input_mode)
6125                 return;
6127         if (!view) {
6128                 char buf[SIZEOF_STR];
6129                 va_list args;
6131                 va_start(args, msg);
6132                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6133                         buf[sizeof(buf) - 1] = 0;
6134                         buf[sizeof(buf) - 2] = '.';
6135                         buf[sizeof(buf) - 3] = '.';
6136                         buf[sizeof(buf) - 4] = '.';
6137                 }
6138                 va_end(args);
6139                 die("%s", buf);
6140         }
6142         if (!status_empty || *msg) {
6143                 va_list args;
6145                 va_start(args, msg);
6147                 wmove(status_win, 0, 0);
6148                 if (*msg) {
6149                         vwprintw(status_win, msg, args);
6150                         status_empty = FALSE;
6151                 } else {
6152                         status_empty = TRUE;
6153                 }
6154                 wclrtoeol(status_win);
6155                 wrefresh(status_win);
6157                 va_end(args);
6158         }
6160         update_view_title(view);
6161         update_display_cursor(view);
6164 /* Controls when nodelay should be in effect when polling user input. */
6165 static void
6166 set_nonblocking_input(bool loading)
6168         static unsigned int loading_views;
6170         if ((loading == FALSE && loading_views-- == 1) ||
6171             (loading == TRUE  && loading_views++ == 0))
6172                 nodelay(status_win, loading);
6175 static void
6176 init_display(void)
6178         int x, y;
6180         /* Initialize the curses library */
6181         if (isatty(STDIN_FILENO)) {
6182                 cursed = !!initscr();
6183                 opt_tty = stdin;
6184         } else {
6185                 /* Leave stdin and stdout alone when acting as a pager. */
6186                 opt_tty = fopen("/dev/tty", "r+");
6187                 if (!opt_tty)
6188                         die("Failed to open /dev/tty");
6189                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6190         }
6192         if (!cursed)
6193                 die("Failed to initialize curses");
6195         nonl();         /* Tell curses not to do NL->CR/NL on output */
6196         cbreak();       /* Take input chars one at a time, no wait for \n */
6197         noecho();       /* Don't echo input */
6198         leaveok(stdscr, TRUE);
6200         if (has_colors())
6201                 init_colors();
6203         getmaxyx(stdscr, y, x);
6204         status_win = newwin(1, 0, y - 1, 0);
6205         if (!status_win)
6206                 die("Failed to create status window");
6208         /* Enable keyboard mapping */
6209         keypad(status_win, TRUE);
6210         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6212         TABSIZE = opt_tab_size;
6213         if (opt_line_graphics) {
6214                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6215         }
6218 static int
6219 get_input(bool prompting)
6221         struct view *view;
6222         int i, key;
6224         if (prompting)
6225                 input_mode = TRUE;
6227         while (true) {
6228                 foreach_view (view, i)
6229                         update_view(view);
6231                 /* Refresh, accept single keystroke of input */
6232                 key = wgetch(status_win);
6234                 /* wgetch() with nodelay() enabled returns ERR when
6235                  * there's no input. */
6236                 if (key == ERR) {
6237                         doupdate();
6239                 } else if (key == KEY_RESIZE) {
6240                         int height, width;
6242                         getmaxyx(stdscr, height, width);
6244                         /* Resize the status view first so the cursor is
6245                          * placed properly. */
6246                         wresize(status_win, 1, width);
6247                         mvwin(status_win, height - 1, 0);
6248                         wrefresh(status_win);
6249                         resize_display();
6250                         redraw_display(TRUE);
6252                 } else {
6253                         input_mode = FALSE;
6254                         return key;
6255                 }
6256         }
6259 static char *
6260 prompt_input(const char *prompt, input_handler handler, void *data)
6262         enum input_status status = INPUT_OK;
6263         static char buf[SIZEOF_STR];
6264         size_t pos = 0;
6266         buf[pos] = 0;
6268         while (status == INPUT_OK || status == INPUT_SKIP) {
6269                 int key;
6271                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6272                 wclrtoeol(status_win);
6274                 key = get_input(TRUE);
6275                 switch (key) {
6276                 case KEY_RETURN:
6277                 case KEY_ENTER:
6278                 case '\n':
6279                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6280                         break;
6282                 case KEY_BACKSPACE:
6283                         if (pos > 0)
6284                                 buf[--pos] = 0;
6285                         else
6286                                 status = INPUT_CANCEL;
6287                         break;
6289                 case KEY_ESC:
6290                         status = INPUT_CANCEL;
6291                         break;
6293                 default:
6294                         if (pos >= sizeof(buf)) {
6295                                 report("Input string too long");
6296                                 return NULL;
6297                         }
6299                         status = handler(data, buf, key);
6300                         if (status == INPUT_OK)
6301                                 buf[pos++] = (char) key;
6302                 }
6303         }
6305         /* Clear the status window */
6306         status_empty = FALSE;
6307         report("");
6309         if (status == INPUT_CANCEL)
6310                 return NULL;
6312         buf[pos++] = 0;
6314         return buf;
6317 static enum input_status
6318 prompt_yesno_handler(void *data, char *buf, int c)
6320         if (c == 'y' || c == 'Y')
6321                 return INPUT_STOP;
6322         if (c == 'n' || c == 'N')
6323                 return INPUT_CANCEL;
6324         return INPUT_SKIP;
6327 static bool
6328 prompt_yesno(const char *prompt)
6330         char prompt2[SIZEOF_STR];
6332         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6333                 return FALSE;
6335         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6338 static enum input_status
6339 read_prompt_handler(void *data, char *buf, int c)
6341         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6344 static char *
6345 read_prompt(const char *prompt)
6347         return prompt_input(prompt, read_prompt_handler, NULL);
6350 /*
6351  * Repository properties
6352  */
6354 static int
6355 git_properties(const char **argv, const char *separators,
6356                int (*read_property)(char *, size_t, char *, size_t))
6358         struct io io = {};
6360         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6361                 return read_properties(&io, separators, read_property);
6362         return ERR;
6365 static struct ref *refs = NULL;
6366 static size_t refs_alloc = 0;
6367 static size_t refs_size = 0;
6369 /* Id <-> ref store */
6370 static struct ref ***id_refs = NULL;
6371 static size_t id_refs_alloc = 0;
6372 static size_t id_refs_size = 0;
6374 static int
6375 compare_refs(const void *ref1_, const void *ref2_)
6377         const struct ref *ref1 = *(const struct ref **)ref1_;
6378         const struct ref *ref2 = *(const struct ref **)ref2_;
6380         if (ref1->tag != ref2->tag)
6381                 return ref2->tag - ref1->tag;
6382         if (ref1->ltag != ref2->ltag)
6383                 return ref2->ltag - ref2->ltag;
6384         if (ref1->head != ref2->head)
6385                 return ref2->head - ref1->head;
6386         if (ref1->tracked != ref2->tracked)
6387                 return ref2->tracked - ref1->tracked;
6388         if (ref1->remote != ref2->remote)
6389                 return ref2->remote - ref1->remote;
6390         return strcmp(ref1->name, ref2->name);
6393 static struct ref **
6394 get_refs(const char *id)
6396         struct ref ***tmp_id_refs;
6397         struct ref **ref_list = NULL;
6398         size_t ref_list_alloc = 0;
6399         size_t ref_list_size = 0;
6400         size_t i;
6402         for (i = 0; i < id_refs_size; i++)
6403                 if (!strcmp(id, id_refs[i][0]->id))
6404                         return id_refs[i];
6406         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6407                                     sizeof(*id_refs));
6408         if (!tmp_id_refs)
6409                 return NULL;
6411         id_refs = tmp_id_refs;
6413         for (i = 0; i < refs_size; i++) {
6414                 struct ref **tmp;
6416                 if (strcmp(id, refs[i].id))
6417                         continue;
6419                 tmp = realloc_items(ref_list, &ref_list_alloc,
6420                                     ref_list_size + 1, sizeof(*ref_list));
6421                 if (!tmp) {
6422                         if (ref_list)
6423                                 free(ref_list);
6424                         return NULL;
6425                 }
6427                 ref_list = tmp;
6428                 ref_list[ref_list_size] = &refs[i];
6429                 /* XXX: The properties of the commit chains ensures that we can
6430                  * safely modify the shared ref. The repo references will
6431                  * always be similar for the same id. */
6432                 ref_list[ref_list_size]->next = 1;
6434                 ref_list_size++;
6435         }
6437         if (ref_list) {
6438                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6439                 ref_list[ref_list_size - 1]->next = 0;
6440                 id_refs[id_refs_size++] = ref_list;
6441         }
6443         return ref_list;
6446 static int
6447 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6449         struct ref *ref;
6450         bool tag = FALSE;
6451         bool ltag = FALSE;
6452         bool remote = FALSE;
6453         bool tracked = FALSE;
6454         bool check_replace = FALSE;
6455         bool head = FALSE;
6457         if (!prefixcmp(name, "refs/tags/")) {
6458                 if (!suffixcmp(name, namelen, "^{}")) {
6459                         namelen -= 3;
6460                         name[namelen] = 0;
6461                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6462                                 check_replace = TRUE;
6463                 } else {
6464                         ltag = TRUE;
6465                 }
6467                 tag = TRUE;
6468                 namelen -= STRING_SIZE("refs/tags/");
6469                 name    += STRING_SIZE("refs/tags/");
6471         } else if (!prefixcmp(name, "refs/remotes/")) {
6472                 remote = TRUE;
6473                 namelen -= STRING_SIZE("refs/remotes/");
6474                 name    += STRING_SIZE("refs/remotes/");
6475                 tracked  = !strcmp(opt_remote, name);
6477         } else if (!prefixcmp(name, "refs/heads/")) {
6478                 namelen -= STRING_SIZE("refs/heads/");
6479                 name    += STRING_SIZE("refs/heads/");
6480                 head     = !strncmp(opt_head, name, namelen);
6482         } else if (!strcmp(name, "HEAD")) {
6483                 string_ncopy(opt_head_rev, id, idlen);
6484                 return OK;
6485         }
6487         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6488                 /* it's an annotated tag, replace the previous sha1 with the
6489                  * resolved commit id; relies on the fact git-ls-remote lists
6490                  * the commit id of an annotated tag right before the commit id
6491                  * it points to. */
6492                 refs[refs_size - 1].ltag = ltag;
6493                 string_copy_rev(refs[refs_size - 1].id, id);
6495                 return OK;
6496         }
6497         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6498         if (!refs)
6499                 return ERR;
6501         ref = &refs[refs_size++];
6502         ref->name = malloc(namelen + 1);
6503         if (!ref->name)
6504                 return ERR;
6506         strncpy(ref->name, name, namelen);
6507         ref->name[namelen] = 0;
6508         ref->head = head;
6509         ref->tag = tag;
6510         ref->ltag = ltag;
6511         ref->remote = remote;
6512         ref->tracked = tracked;
6513         string_copy_rev(ref->id, id);
6515         return OK;
6518 static int
6519 load_refs(void)
6521         static const char *ls_remote_argv[SIZEOF_ARG] = {
6522                 "git", "ls-remote", ".", NULL
6523         };
6524         static bool init = FALSE;
6526         if (!init) {
6527                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6528                 init = TRUE;
6529         }
6531         if (!*opt_git_dir)
6532                 return OK;
6534         while (refs_size > 0)
6535                 free(refs[--refs_size].name);
6536         while (id_refs_size > 0)
6537                 free(id_refs[--id_refs_size]);
6539         return git_properties(ls_remote_argv, "\t", read_ref);
6542 static int
6543 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6545         if (!strcmp(name, "i18n.commitencoding"))
6546                 string_ncopy(opt_encoding, value, valuelen);
6548         if (!strcmp(name, "core.editor"))
6549                 string_ncopy(opt_editor, value, valuelen);
6551         /* branch.<head>.remote */
6552         if (*opt_head &&
6553             !strncmp(name, "branch.", 7) &&
6554             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6555             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6556                 string_ncopy(opt_remote, value, valuelen);
6558         if (*opt_head && *opt_remote &&
6559             !strncmp(name, "branch.", 7) &&
6560             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6561             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6562                 size_t from = strlen(opt_remote);
6564                 if (!prefixcmp(value, "refs/heads/")) {
6565                         value += STRING_SIZE("refs/heads/");
6566                         valuelen -= STRING_SIZE("refs/heads/");
6567                 }
6569                 if (!string_format_from(opt_remote, &from, "/%s", value))
6570                         opt_remote[0] = 0;
6571         }
6573         return OK;
6576 static int
6577 load_git_config(void)
6579         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6581         return git_properties(config_list_argv, "=", read_repo_config_option);
6584 static int
6585 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6587         if (!opt_git_dir[0]) {
6588                 string_ncopy(opt_git_dir, name, namelen);
6590         } else if (opt_is_inside_work_tree == -1) {
6591                 /* This can be 3 different values depending on the
6592                  * version of git being used. If git-rev-parse does not
6593                  * understand --is-inside-work-tree it will simply echo
6594                  * the option else either "true" or "false" is printed.
6595                  * Default to true for the unknown case. */
6596                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6598         } else if (*name == '.') {
6599                 string_ncopy(opt_cdup, name, namelen);
6601         } else {
6602                 string_ncopy(opt_prefix, name, namelen);
6603         }
6605         return OK;
6608 static int
6609 load_repo_info(void)
6611         const char *head_argv[] = {
6612                 "git", "symbolic-ref", "HEAD", NULL
6613         };
6614         const char *rev_parse_argv[] = {
6615                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6616                         "--show-cdup", "--show-prefix", NULL
6617         };
6619         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6620                 chomp_string(opt_head);
6621                 if (!prefixcmp(opt_head, "refs/heads/")) {
6622                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6624                         memmove(opt_head, offset, strlen(offset) + 1);
6625                 }
6626         }
6628         return git_properties(rev_parse_argv, "=", read_repo_info);
6631 static int
6632 read_properties(struct io *io, const char *separators,
6633                 int (*read_property)(char *, size_t, char *, size_t))
6635         char *name;
6636         int state = OK;
6638         if (!start_io(io))
6639                 return ERR;
6641         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6642                 char *value;
6643                 size_t namelen;
6644                 size_t valuelen;
6646                 name = chomp_string(name);
6647                 namelen = strcspn(name, separators);
6649                 if (name[namelen]) {
6650                         name[namelen] = 0;
6651                         value = chomp_string(name + namelen + 1);
6652                         valuelen = strlen(value);
6654                 } else {
6655                         value = "";
6656                         valuelen = 0;
6657                 }
6659                 state = read_property(name, namelen, value, valuelen);
6660         }
6662         if (state != ERR && io_error(io))
6663                 state = ERR;
6664         done_io(io);
6666         return state;
6670 /*
6671  * Main
6672  */
6674 static void __NORETURN
6675 quit(int sig)
6677         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6678         if (cursed)
6679                 endwin();
6680         exit(0);
6683 static void __NORETURN
6684 die(const char *err, ...)
6686         va_list args;
6688         endwin();
6690         va_start(args, err);
6691         fputs("tig: ", stderr);
6692         vfprintf(stderr, err, args);
6693         fputs("\n", stderr);
6694         va_end(args);
6696         exit(1);
6699 static void
6700 warn(const char *msg, ...)
6702         va_list args;
6704         va_start(args, msg);
6705         fputs("tig warning: ", stderr);
6706         vfprintf(stderr, msg, args);
6707         fputs("\n", stderr);
6708         va_end(args);
6711 int
6712 main(int argc, const char *argv[])
6714         const char **run_argv = NULL;
6715         struct view *view;
6716         enum request request;
6717         size_t i;
6719         signal(SIGINT, quit);
6721         if (setlocale(LC_ALL, "")) {
6722                 char *codeset = nl_langinfo(CODESET);
6724                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6725         }
6727         if (load_repo_info() == ERR)
6728                 die("Failed to load repo info.");
6730         if (load_options() == ERR)
6731                 die("Failed to load user config.");
6733         if (load_git_config() == ERR)
6734                 die("Failed to load repo config.");
6736         request = parse_options(argc, argv, &run_argv);
6737         if (request == REQ_NONE)
6738                 return 0;
6740         /* Require a git repository unless when running in pager mode. */
6741         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6742                 die("Not a git repository");
6744         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6745                 opt_utf8 = FALSE;
6747         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6748                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6749                 if (opt_iconv == ICONV_NONE)
6750                         die("Failed to initialize character set conversion");
6751         }
6753         if (load_refs() == ERR)
6754                 die("Failed to load refs.");
6756         foreach_view (view, i)
6757                 argv_from_env(view->ops->argv, view->cmd_env);
6759         init_display();
6761         if (request == REQ_VIEW_PAGER || run_argv) {
6762                 if (request == REQ_VIEW_PAGER)
6763                         io_open(&VIEW(request)->io, "");
6764                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6765                         die("Failed to format arguments");
6766                 open_view(NULL, request, OPEN_PREPARED);
6767                 request = REQ_NONE;
6768         }
6770         while (view_driver(display[current_view], request)) {
6771                 int key = get_input(FALSE);
6773                 view = display[current_view];
6774                 request = get_keybinding(view->keymap, key);
6776                 /* Some low-level request handling. This keeps access to
6777                  * status_win restricted. */
6778                 switch (request) {
6779                 case REQ_PROMPT:
6780                 {
6781                         char *cmd = read_prompt(":");
6783                         if (cmd) {
6784                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6785                                 const char *argv[SIZEOF_ARG] = { "git" };
6786                                 int argc = 1;
6788                                 /* When running random commands, initially show the
6789                                  * command in the title. However, it maybe later be
6790                                  * overwritten if a commit line is selected. */
6791                                 string_ncopy(next->ref, cmd, strlen(cmd));
6793                                 if (!argv_from_string(argv, &argc, cmd)) {
6794                                         report("Too many arguments");
6795                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6796                                         report("Failed to format command");
6797                                 } else {
6798                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6799                                 }
6800                         }
6802                         request = REQ_NONE;
6803                         break;
6804                 }
6805                 case REQ_SEARCH:
6806                 case REQ_SEARCH_BACK:
6807                 {
6808                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6809                         char *search = read_prompt(prompt);
6811                         if (search)
6812                                 string_ncopy(opt_search, search, strlen(search));
6813                         else
6814                                 request = REQ_NONE;
6815                         break;
6816                 }
6817                 default:
6818                         break;
6819                 }
6820         }
6822         quit(0);
6824         return 0;