Code

Abbreviate author names to initials when author-width < 6
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
173 enum input_status {
174         INPUT_OK,
175         INPUT_SKIP,
176         INPUT_STOP,
177         INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216         int namelen;
218         while (isspace(*name))
219                 name++;
221         namelen = strlen(name) - 1;
222         while (namelen > 0 && isspace(name[namelen]))
223                 name[namelen--] = 0;
225         return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231         va_list args;
232         size_t pos = bufpos ? *bufpos : 0;
234         va_start(args, fmt);
235         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236         va_end(args);
238         if (bufpos)
239                 *bufpos = pos;
241         return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245         string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248         string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253         size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257         /* Diff-Header == DIFF_HEADER */
258         for (i = 0; i < len; i++) {
259                 if (toupper(str1[i]) == toupper(str2[i]))
260                         continue;
262                 if (string_enum_sep(str1[i]) &&
263                     string_enum_sep(str2[i]))
264                         continue;
266                 return str1[i] - str2[i];
267         }
269         return 0;
272 #define prefixcmp(str1, str2) \
273         strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278         size_t len = slen >= 0 ? slen : strlen(str);
279         size_t suffixlen = strlen(suffix);
281         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288         int valuelen;
290         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291                 bool advance = cmd[valuelen] != 0;
293                 cmd[valuelen] = 0;
294                 argv[(*argc)++] = chomp_string(cmd);
295                 cmd = chomp_string(cmd + valuelen + advance);
296         }
298         if (*argc < SIZEOF_ARG)
299                 argv[*argc] = NULL;
300         return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306         char *env = argv ? getenv(name) : NULL;
307         int argc = 0;
309         if (env && *env)
310                 env = strdup(env);
311         if (env && !argv_from_string(argv, &argc, env))
312                 die("Too many arguments in the `%s` environment variable", name);
316 /*
317  * Executing external commands.
318  */
320 enum io_type {
321         IO_FD,                  /* File descriptor based IO. */
322         IO_BG,                  /* Execute command in the background. */
323         IO_FG,                  /* Execute command with same std{in,out,err}. */
324         IO_RD,                  /* Read only fork+exec IO. */
325         IO_WR,                  /* Write only fork+exec IO. */
326         IO_AP,                  /* Append fork+exec output to file. */
327 };
329 struct io {
330         enum io_type type;      /* The requested type of pipe. */
331         const char *dir;        /* Directory from which to execute. */
332         pid_t pid;              /* Pipe for reading or writing. */
333         int pipe;               /* Pipe end for reading or writing. */
334         int error;              /* Error status. */
335         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
336         char *buf;              /* Read buffer. */
337         size_t bufalloc;        /* Allocated buffer size. */
338         size_t bufsize;         /* Buffer content size. */
339         char *bufpos;           /* Current buffer position. */
340         unsigned int eof:1;     /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
346         io->pipe = -1;
347         io->pid = 0;
348         io->buf = io->bufpos = NULL;
349         io->bufalloc = io->bufsize = 0;
350         io->error = 0;
351         io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357         reset_io(io);
358         io->type = type;
359         io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364                 enum format_flags flags)
366         init_io(io, dir, IO_RD);
367         return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373         init_io(io, NULL, IO_FD);
374         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375         return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387         pid_t pid = io->pid;
389         if (io->pipe != -1)
390                 close(io->pipe);
391         free(io->buf);
392         reset_io(io);
394         while (pid > 0) {
395                 int status;
396                 pid_t waiting = waitpid(pid, &status, 0);
398                 if (waiting < 0) {
399                         if (errno == EINTR)
400                                 continue;
401                         report("waitpid failed (%s)", strerror(errno));
402                         return FALSE;
403                 }
405                 return waiting == pid &&
406                        !WIFSIGNALED(status) &&
407                        WIFEXITED(status) &&
408                        !WEXITSTATUS(status);
409         }
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         int pipefds[2] = { -1, -1 };
419         if (io->type == IO_FD)
420                 return TRUE;
422         if ((io->type == IO_RD || io->type == IO_WR) &&
423             pipe(pipefds) < 0)
424                 return FALSE;
425         else if (io->type == IO_AP)
426                 pipefds[1] = io->pipe;
428         if ((io->pid = fork())) {
429                 if (pipefds[!(io->type == IO_WR)] != -1)
430                         close(pipefds[!(io->type == IO_WR)]);
431                 if (io->pid != -1) {
432                         io->pipe = pipefds[!!(io->type == IO_WR)];
433                         return TRUE;
434                 }
436         } else {
437                 if (io->type != IO_FG) {
438                         int devnull = open("/dev/null", O_RDWR);
439                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
440                         int writefd = (io->type == IO_RD || io->type == IO_AP)
441                                                         ? pipefds[1] : devnull;
443                         dup2(readfd,  STDIN_FILENO);
444                         dup2(writefd, STDOUT_FILENO);
445                         dup2(devnull, STDERR_FILENO);
447                         close(devnull);
448                         if (pipefds[0] != -1)
449                                 close(pipefds[0]);
450                         if (pipefds[1] != -1)
451                                 close(pipefds[1]);
452                 }
454                 if (io->dir && *io->dir && chdir(io->dir) == -1)
455                         die("Failed to change directory: %s", strerror(errno));
457                 execvp(io->argv[0], (char *const*) io->argv);
458                 die("Failed to execute program: %s", strerror(errno));
459         }
461         if (pipefds[!!(io->type == IO_WR)] != -1)
462                 close(pipefds[!!(io->type == IO_WR)]);
463         return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469         init_io(io, dir, type);
470         if (!format_argv(io->argv, argv, FORMAT_NONE))
471                 return FALSE;
472         return start_io(io);
475 static int
476 run_io_do(struct io *io)
478         return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484         struct io io = {};
486         init_io(&io, NULL, IO_BG);
487         if (!format_argv(io.argv, argv, FORMAT_NONE))
488                 return FALSE;
489         return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495         struct io io = {};
497         init_io(&io, dir, IO_FG);
498         if (!format_argv(io.argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506         struct io io = {};
508         init_io(&io, NULL, IO_AP);
509         io.pipe = fd;
510         if (format_argv(io.argv, argv, flags))
511                 return run_io_do(&io);
512         close(fd);
513         return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519         return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525         return io->eof;
528 static int
529 io_error(struct io *io)
531         return io->error;
534 static bool
535 io_strerror(struct io *io)
537         return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543         struct timeval tv = { 0, 500 };
544         fd_set fds;
546         FD_ZERO(&fds);
547         FD_SET(io->pipe, &fds);
549         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555         do {
556                 ssize_t readsize = read(io->pipe, buf, bufsize);
558                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559                         continue;
560                 else if (readsize == -1)
561                         io->error = errno;
562                 else if (readsize == 0)
563                         io->eof = 1;
564                 return readsize;
565         } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571         char *eol;
572         ssize_t readsize;
574         if (!io->buf) {
575                 io->buf = io->bufpos = malloc(BUFSIZ);
576                 if (!io->buf)
577                         return NULL;
578                 io->bufalloc = BUFSIZ;
579                 io->bufsize = 0;
580         }
582         while (TRUE) {
583                 if (io->bufsize > 0) {
584                         eol = memchr(io->bufpos, c, io->bufsize);
585                         if (eol) {
586                                 char *line = io->bufpos;
588                                 *eol = 0;
589                                 io->bufpos = eol + 1;
590                                 io->bufsize -= io->bufpos - line;
591                                 return line;
592                         }
593                 }
595                 if (io_eof(io)) {
596                         if (io->bufsize) {
597                                 io->bufpos[io->bufsize] = 0;
598                                 io->bufsize = 0;
599                                 return io->bufpos;
600                         }
601                         return NULL;
602                 }
604                 if (!can_read)
605                         return NULL;
607                 if (io->bufsize > 0 && io->bufpos > io->buf)
608                         memmove(io->buf, io->bufpos, io->bufsize);
610                 io->bufpos = io->buf;
611                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612                 if (io_error(io))
613                         return NULL;
614                 io->bufsize += readsize;
615         }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621         size_t written = 0;
623         while (!io_error(io) && written < bufsize) {
624                 ssize_t size;
626                 size = write(io->pipe, buf + written, bufsize - written);
627                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628                         continue;
629                 else if (size == -1)
630                         io->error = errno;
631                 else
632                         written += size;
633         }
635         return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641         struct io io = {};
642         bool error;
644         if (!run_io_rd(&io, argv, FORMAT_NONE))
645                 return FALSE;
647         io.buf = io.bufpos = buf;
648         io.bufalloc = bufsize;
649         error = !io_get(&io, '\n', TRUE) && io_error(&io);
650         io.buf = NULL;
652         return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
657 /*
658  * User requests
659  */
661 #define REQ_INFO \
662         /* XXX: Keep the view request first and in sync with views[]. */ \
663         REQ_GROUP("View switching") \
664         REQ_(VIEW_MAIN,         "Show main view"), \
665         REQ_(VIEW_DIFF,         "Show diff view"), \
666         REQ_(VIEW_LOG,          "Show log view"), \
667         REQ_(VIEW_TREE,         "Show tree view"), \
668         REQ_(VIEW_BLOB,         "Show blob view"), \
669         REQ_(VIEW_BLAME,        "Show blame view"), \
670         REQ_(VIEW_HELP,         "Show help page"), \
671         REQ_(VIEW_PAGER,        "Show pager view"), \
672         REQ_(VIEW_STATUS,       "Show status view"), \
673         REQ_(VIEW_STAGE,        "Show stage view"), \
674         \
675         REQ_GROUP("View manipulation") \
676         REQ_(ENTER,             "Enter current line and scroll"), \
677         REQ_(NEXT,              "Move to next"), \
678         REQ_(PREVIOUS,          "Move to previous"), \
679         REQ_(PARENT,            "Move to parent"), \
680         REQ_(VIEW_NEXT,         "Move focus to next view"), \
681         REQ_(REFRESH,           "Reload and refresh"), \
682         REQ_(MAXIMIZE,          "Maximize the current view"), \
683         REQ_(VIEW_CLOSE,        "Close the current view"), \
684         REQ_(QUIT,              "Close all views and quit"), \
685         \
686         REQ_GROUP("View specific requests") \
687         REQ_(STATUS_UPDATE,     "Update file status"), \
688         REQ_(STATUS_REVERT,     "Revert file changes"), \
689         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
690         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
691         \
692         REQ_GROUP("Cursor navigation") \
693         REQ_(MOVE_UP,           "Move cursor one line up"), \
694         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
695         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
696         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
697         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
698         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
699         \
700         REQ_GROUP("Scrolling") \
701         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
702         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
703         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
704         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
705         \
706         REQ_GROUP("Searching") \
707         REQ_(SEARCH,            "Search the view"), \
708         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
709         REQ_(FIND_NEXT,         "Find next search match"), \
710         REQ_(FIND_PREV,         "Find previous search match"), \
711         \
712         REQ_GROUP("Option manipulation") \
713         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
714         REQ_(TOGGLE_DATE,       "Toggle date display"), \
715         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
716         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
717         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
718         \
719         REQ_GROUP("Misc") \
720         REQ_(PROMPT,            "Bring up the prompt"), \
721         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
722         REQ_(SHOW_VERSION,      "Show version information"), \
723         REQ_(STOP_LOADING,      "Stop all loading views"), \
724         REQ_(EDIT,              "Open in editor"), \
725         REQ_(NONE,              "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733         /* Offset all requests to avoid conflicts with ncurses getch values. */
734         REQ_OFFSET = KEY_MAX + 1,
735         REQ_INFO
737 #undef  REQ_GROUP
738 #undef  REQ_
739 };
741 struct request_info {
742         enum request request;
743         const char *name;
744         int namelen;
745         const char *help;
746 };
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751         REQ_INFO
752 #undef  REQ_GROUP
753 #undef  REQ_
754 };
756 static enum request
757 get_request(const char *name)
759         int namelen = strlen(name);
760         int i;
762         for (i = 0; i < ARRAY_SIZE(req_info); i++)
763                 if (req_info[i].namelen == namelen &&
764                     !string_enum_compare(req_info[i].name, name, namelen))
765                         return req_info[i].request;
767         return REQ_NONE;
771 /*
772  * Options
773  */
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig        [options] [revs] [--] [paths]\n"
779 "   or: tig show   [options] [revs] [--] [paths]\n"
780 "   or: tig blame  [rev] path\n"
781 "   or: tig status\n"
782 "   or: tig <      [git command output]\n"
783 "\n"
784 "Options:\n"
785 "  -v, --version   Show version and exit\n"
786 "  -h, --help      Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date                    = TRUE;
790 static bool opt_author                  = TRUE;
791 static bool opt_line_number             = FALSE;
792 static bool opt_line_graphics           = TRUE;
793 static bool opt_rev_graph               = FALSE;
794 static bool opt_show_refs               = TRUE;
795 static int opt_num_interval             = NUMBER_INTERVAL;
796 static int opt_tab_size                 = TAB_SIZE;
797 static int opt_author_cols              = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR]        = "";
799 static char opt_file[SIZEOF_STR]        = "";
800 static char opt_ref[SIZEOF_REF]         = "";
801 static char opt_head[SIZEOF_REF]        = "";
802 static char opt_head_rev[SIZEOF_REV]    = "";
803 static char opt_remote[SIZEOF_REF]      = "";
804 static char opt_encoding[20]            = "UTF-8";
805 static bool opt_utf8                    = TRUE;
806 static char opt_codeset[20]             = "UTF-8";
807 static iconv_t opt_iconv                = ICONV_NONE;
808 static char opt_search[SIZEOF_STR]      = "";
809 static char opt_cdup[SIZEOF_STR]        = "";
810 static char opt_prefix[SIZEOF_STR]      = "";
811 static char opt_git_dir[SIZEOF_STR]     = "";
812 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR]      = "";
814 static FILE *opt_tty                    = NULL;
816 #define is_initial_commit()     (!*opt_head_rev)
817 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822         enum request request = REQ_VIEW_MAIN;
823         const char *subcommand;
824         bool seen_dashdash = FALSE;
825         /* XXX: This is vulnerable to the user overriding options
826          * required for the main view parser. */
827         static const char *custom_argv[SIZEOF_ARG] = {
828                 "git", "log", "--no-color", "--pretty=raw", "--parents",
829                         "--topo-order", NULL
830         };
831         int i, j = 6;
833         if (!isatty(STDIN_FILENO))
834                 return REQ_VIEW_PAGER;
836         if (argc <= 1)
837                 return REQ_VIEW_MAIN;
839         subcommand = argv[1];
840         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841                 if (!strcmp(subcommand, "-S"))
842                         warn("`-S' has been deprecated; use `tig status' instead");
843                 if (argc > 2)
844                         warn("ignoring arguments after `%s'", subcommand);
845                 return REQ_VIEW_STATUS;
847         } else if (!strcmp(subcommand, "blame")) {
848                 if (argc <= 2 || argc > 4)
849                         die("invalid number of options to blame\n\n%s", usage);
851                 i = 2;
852                 if (argc == 4) {
853                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854                         i++;
855                 }
857                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858                 return REQ_VIEW_BLAME;
860         } else if (!strcmp(subcommand, "show")) {
861                 request = REQ_VIEW_DIFF;
863         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865                 warn("`tig %s' has been deprecated", subcommand);
867         } else {
868                 subcommand = NULL;
869         }
871         if (subcommand) {
872                 custom_argv[1] = subcommand;
873                 j = 2;
874         }
876         for (i = 1 + !!subcommand; i < argc; i++) {
877                 const char *opt = argv[i];
879                 if (seen_dashdash || !strcmp(opt, "--")) {
880                         seen_dashdash = TRUE;
882                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883                         printf("tig version %s\n", TIG_VERSION);
884                         return REQ_NONE;
886                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887                         printf("%s\n", usage);
888                         return REQ_NONE;
889                 }
891                 custom_argv[j++] = opt;
892                 if (j >= ARRAY_SIZE(custom_argv))
893                         die("command too long");
894         }
896         custom_argv[j] = NULL;
897         *run_argv = custom_argv;
899         return request;
903 /*
904  * Line-oriented content detection.
905  */
907 #define LINE_INFO \
908 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
910 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
911 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
913 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
921 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
922 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
925 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
927 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
929 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
932 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
933 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
935 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
936 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
937 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
938 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
939 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
940 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
941 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
942 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
943 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
944 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
945 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
946 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
950 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
951 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
952 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
954 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
955 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
956 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
957 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
958 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
959 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
960 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
962 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
963 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967         LINE_##type
968         LINE_INFO,
969         LINE_NONE
970 #undef  LINE
971 };
973 struct line_info {
974         const char *name;       /* Option name. */
975         int namelen;            /* Size of option name. */
976         const char *line;       /* The start of line to match. */
977         int linelen;            /* Size of string to match. */
978         int fg, bg, attr;       /* Color and text attributes for the lines. */
979 };
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984         LINE_INFO
985 #undef  LINE
986 };
988 static enum line_type
989 get_line_type(const char *line)
991         int linelen = strlen(line);
992         enum line_type type;
994         for (type = 0; type < ARRAY_SIZE(line_info); type++)
995                 /* Case insensitive search matches Signed-off-by lines better. */
996                 if (linelen >= line_info[type].linelen &&
997                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998                         return type;
1000         return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006         assert(type < ARRAY_SIZE(line_info));
1007         return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013         size_t namelen = strlen(name);
1014         enum line_type type;
1016         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017                 if (namelen == line_info[type].namelen &&
1018                     !string_enum_compare(line_info[type].name, name, namelen))
1019                         return &line_info[type];
1021         return NULL;
1024 static void
1025 init_colors(void)
1027         int default_bg = line_info[LINE_DEFAULT].bg;
1028         int default_fg = line_info[LINE_DEFAULT].fg;
1029         enum line_type type;
1031         start_color();
1033         if (assume_default_colors(default_fg, default_bg) == ERR) {
1034                 default_bg = COLOR_BLACK;
1035                 default_fg = COLOR_WHITE;
1036         }
1038         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039                 struct line_info *info = &line_info[type];
1040                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043                 init_pair(type, fg, bg);
1044         }
1047 struct line {
1048         enum line_type type;
1050         /* State flags */
1051         unsigned int selected:1;
1052         unsigned int dirty:1;
1053         unsigned int cleareol:1;
1055         void *data;             /* User data */
1056 };
1059 /*
1060  * Keys
1061  */
1063 struct keybinding {
1064         int alias;
1065         enum request request;
1066 };
1068 static struct keybinding default_keybindings[] = {
1069         /* View switching */
1070         { 'm',          REQ_VIEW_MAIN },
1071         { 'd',          REQ_VIEW_DIFF },
1072         { 'l',          REQ_VIEW_LOG },
1073         { 't',          REQ_VIEW_TREE },
1074         { 'f',          REQ_VIEW_BLOB },
1075         { 'B',          REQ_VIEW_BLAME },
1076         { 'p',          REQ_VIEW_PAGER },
1077         { 'h',          REQ_VIEW_HELP },
1078         { 'S',          REQ_VIEW_STATUS },
1079         { 'c',          REQ_VIEW_STAGE },
1081         /* View manipulation */
1082         { 'q',          REQ_VIEW_CLOSE },
1083         { KEY_TAB,      REQ_VIEW_NEXT },
1084         { KEY_RETURN,   REQ_ENTER },
1085         { KEY_UP,       REQ_PREVIOUS },
1086         { KEY_DOWN,     REQ_NEXT },
1087         { 'R',          REQ_REFRESH },
1088         { KEY_F(5),     REQ_REFRESH },
1089         { 'O',          REQ_MAXIMIZE },
1091         /* Cursor navigation */
1092         { 'k',          REQ_MOVE_UP },
1093         { 'j',          REQ_MOVE_DOWN },
1094         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1095         { KEY_END,      REQ_MOVE_LAST_LINE },
1096         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1097         { ' ',          REQ_MOVE_PAGE_DOWN },
1098         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1099         { 'b',          REQ_MOVE_PAGE_UP },
1100         { '-',          REQ_MOVE_PAGE_UP },
1102         /* Scrolling */
1103         { KEY_IC,       REQ_SCROLL_LINE_UP },
1104         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1105         { 'w',          REQ_SCROLL_PAGE_UP },
1106         { 's',          REQ_SCROLL_PAGE_DOWN },
1108         /* Searching */
1109         { '/',          REQ_SEARCH },
1110         { '?',          REQ_SEARCH_BACK },
1111         { 'n',          REQ_FIND_NEXT },
1112         { 'N',          REQ_FIND_PREV },
1114         /* Misc */
1115         { 'Q',          REQ_QUIT },
1116         { 'z',          REQ_STOP_LOADING },
1117         { 'v',          REQ_SHOW_VERSION },
1118         { 'r',          REQ_SCREEN_REDRAW },
1119         { '.',          REQ_TOGGLE_LINENO },
1120         { 'D',          REQ_TOGGLE_DATE },
1121         { 'A',          REQ_TOGGLE_AUTHOR },
1122         { 'g',          REQ_TOGGLE_REV_GRAPH },
1123         { 'F',          REQ_TOGGLE_REFS },
1124         { ':',          REQ_PROMPT },
1125         { 'u',          REQ_STATUS_UPDATE },
1126         { '!',          REQ_STATUS_REVERT },
1127         { 'M',          REQ_STATUS_MERGE },
1128         { '@',          REQ_STAGE_NEXT },
1129         { ',',          REQ_PARENT },
1130         { 'e',          REQ_EDIT },
1131 };
1133 #define KEYMAP_INFO \
1134         KEYMAP_(GENERIC), \
1135         KEYMAP_(MAIN), \
1136         KEYMAP_(DIFF), \
1137         KEYMAP_(LOG), \
1138         KEYMAP_(TREE), \
1139         KEYMAP_(BLOB), \
1140         KEYMAP_(BLAME), \
1141         KEYMAP_(PAGER), \
1142         KEYMAP_(HELP), \
1143         KEYMAP_(STATUS), \
1144         KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148         KEYMAP_INFO
1149 #undef  KEYMAP_
1150 };
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154         KEYMAP_INFO
1155 #undef  KEYMAP_
1156 };
1158 #define set_keymap(map, name) \
1159         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162         struct keybinding *data;
1163         size_t size;
1164 };
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171         struct keybinding_table *table = &keybindings[keymap];
1173         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174         if (!table->data)
1175                 die("Failed to allocate keybinding");
1176         table->data[table->size].alias = key;
1177         table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181  * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185         size_t i;
1187         for (i = 0; i < keybindings[keymap].size; i++)
1188                 if (keybindings[keymap].data[i].alias == key)
1189                         return keybindings[keymap].data[i].request;
1191         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193                         return keybindings[KEYMAP_GENERIC].data[i].request;
1195         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196                 if (default_keybindings[i].alias == key)
1197                         return default_keybindings[i].request;
1199         return (enum request) key;
1203 struct key {
1204         const char *name;
1205         int value;
1206 };
1208 static struct key key_table[] = {
1209         { "Enter",      KEY_RETURN },
1210         { "Space",      ' ' },
1211         { "Backspace",  KEY_BACKSPACE },
1212         { "Tab",        KEY_TAB },
1213         { "Escape",     KEY_ESC },
1214         { "Left",       KEY_LEFT },
1215         { "Right",      KEY_RIGHT },
1216         { "Up",         KEY_UP },
1217         { "Down",       KEY_DOWN },
1218         { "Insert",     KEY_IC },
1219         { "Delete",     KEY_DC },
1220         { "Hash",       '#' },
1221         { "Home",       KEY_HOME },
1222         { "End",        KEY_END },
1223         { "PageUp",     KEY_PPAGE },
1224         { "PageDown",   KEY_NPAGE },
1225         { "F1",         KEY_F(1) },
1226         { "F2",         KEY_F(2) },
1227         { "F3",         KEY_F(3) },
1228         { "F4",         KEY_F(4) },
1229         { "F5",         KEY_F(5) },
1230         { "F6",         KEY_F(6) },
1231         { "F7",         KEY_F(7) },
1232         { "F8",         KEY_F(8) },
1233         { "F9",         KEY_F(9) },
1234         { "F10",        KEY_F(10) },
1235         { "F11",        KEY_F(11) },
1236         { "F12",        KEY_F(12) },
1237 };
1239 static int
1240 get_key_value(const char *name)
1242         int i;
1244         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245                 if (!strcasecmp(key_table[i].name, name))
1246                         return key_table[i].value;
1248         if (strlen(name) == 1 && isprint(*name))
1249                 return (int) *name;
1251         return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257         static char key_char[] = "'X'";
1258         const char *seq = NULL;
1259         int key;
1261         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262                 if (key_table[key].value == key_value)
1263                         seq = key_table[key].name;
1265         if (seq == NULL &&
1266             key_value < 127 &&
1267             isprint(key_value)) {
1268                 key_char[1] = (char) key_value;
1269                 seq = key_char;
1270         }
1272         return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278         static char buf[BUFSIZ];
1279         size_t pos = 0;
1280         char *sep = "";
1281         int i;
1283         buf[pos] = 0;
1285         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286                 struct keybinding *keybinding = &default_keybindings[i];
1288                 if (keybinding->request != request)
1289                         continue;
1291                 if (!string_format_from(buf, &pos, "%s%s", sep,
1292                                         get_key_name(keybinding->alias)))
1293                         return "Too many keybindings!";
1294                 sep = ", ";
1295         }
1297         return buf;
1300 struct run_request {
1301         enum keymap keymap;
1302         int key;
1303         const char *argv[SIZEOF_ARG];
1304 };
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312         struct run_request *req;
1314         if (argc >= ARRAY_SIZE(req->argv) - 1)
1315                 return REQ_NONE;
1317         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318         if (!req)
1319                 return REQ_NONE;
1321         run_request = req;
1322         req = &run_request[run_requests];
1323         req->keymap = keymap;
1324         req->key = key;
1325         req->argv[0] = NULL;
1327         if (!format_argv(req->argv, argv, FORMAT_NONE))
1328                 return REQ_NONE;
1330         return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336         if (request <= REQ_NONE)
1337                 return NULL;
1338         return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345         const char *gc[] = { "git", "gc", NULL };
1346         struct {
1347                 enum keymap keymap;
1348                 int key;
1349                 int argc;
1350                 const char **argv;
1351         } reqs[] = {
1352                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354         };
1355         int i;
1357         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358                 enum request req;
1360                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361                 if (req != REQ_NONE)
1362                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1363         }
1366 /*
1367  * User config file handling.
1368  */
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372         COLOR_MAP(DEFAULT),
1373         COLOR_MAP(BLACK),
1374         COLOR_MAP(BLUE),
1375         COLOR_MAP(CYAN),
1376         COLOR_MAP(GREEN),
1377         COLOR_MAP(MAGENTA),
1378         COLOR_MAP(RED),
1379         COLOR_MAP(WHITE),
1380         COLOR_MAP(YELLOW),
1381 };
1383 #define set_color(color, name) \
1384         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388         ATTR_MAP(NORMAL),
1389         ATTR_MAP(BLINK),
1390         ATTR_MAP(BOLD),
1391         ATTR_MAP(DIM),
1392         ATTR_MAP(REVERSE),
1393         ATTR_MAP(STANDOUT),
1394         ATTR_MAP(UNDERLINE),
1395 };
1397 #define set_attribute(attr, name) \
1398         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int   config_lineno;
1401 static bool  config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408         struct line_info *info;
1410         if (argc != 3 && argc != 4) {
1411                 config_msg = "Wrong number of arguments given to color command";
1412                 return ERR;
1413         }
1415         info = get_line_info(argv[0]);
1416         if (!info) {
1417                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418                         info = get_line_info("delimiter");
1420                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421                         info = get_line_info("date");
1423                 } else {
1424                         config_msg = "Unknown color name";
1425                         return ERR;
1426                 }
1427         }
1429         if (set_color(&info->fg, argv[1]) == ERR ||
1430             set_color(&info->bg, argv[2]) == ERR) {
1431                 config_msg = "Unknown color";
1432                 return ERR;
1433         }
1435         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436                 config_msg = "Unknown attribute";
1437                 return ERR;
1438         }
1440         return OK;
1443 static bool parse_bool(const char *s)
1445         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446                 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452         int value = atoi(s);
1454         return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461         if (argc != 3) {
1462                 config_msg = "Wrong number of arguments given to set command";
1463                 return ERR;
1464         }
1466         if (strcmp(argv[1], "=")) {
1467                 config_msg = "No value assigned";
1468                 return ERR;
1469         }
1471         if (!strcmp(argv[0], "show-author")) {
1472                 opt_author = parse_bool(argv[2]);
1473                 return OK;
1474         }
1476         if (!strcmp(argv[0], "show-date")) {
1477                 opt_date = parse_bool(argv[2]);
1478                 return OK;
1479         }
1481         if (!strcmp(argv[0], "show-rev-graph")) {
1482                 opt_rev_graph = parse_bool(argv[2]);
1483                 return OK;
1484         }
1486         if (!strcmp(argv[0], "show-refs")) {
1487                 opt_show_refs = parse_bool(argv[2]);
1488                 return OK;
1489         }
1491         if (!strcmp(argv[0], "show-line-numbers")) {
1492                 opt_line_number = parse_bool(argv[2]);
1493                 return OK;
1494         }
1496         if (!strcmp(argv[0], "line-graphics")) {
1497                 opt_line_graphics = parse_bool(argv[2]);
1498                 return OK;
1499         }
1501         if (!strcmp(argv[0], "line-number-interval")) {
1502                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503                 return OK;
1504         }
1506         if (!strcmp(argv[0], "author-width")) {
1507                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508                 return OK;
1509         }
1511         if (!strcmp(argv[0], "tab-size")) {
1512                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513                 return OK;
1514         }
1516         if (!strcmp(argv[0], "commit-encoding")) {
1517                 const char *arg = argv[2];
1518                 int arglen = strlen(arg);
1520                 switch (arg[0]) {
1521                 case '"':
1522                 case '\'':
1523                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524                                 config_msg = "Unmatched quotation";
1525                                 return ERR;
1526                         }
1527                         arg += 1; arglen -= 2;
1528                 default:
1529                         string_ncopy(opt_encoding, arg, strlen(arg));
1530                         return OK;
1531                 }
1532         }
1534         config_msg = "Unknown variable name";
1535         return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542         enum request request;
1543         int keymap;
1544         int key;
1546         if (argc < 3) {
1547                 config_msg = "Wrong number of arguments given to bind command";
1548                 return ERR;
1549         }
1551         if (set_keymap(&keymap, argv[0]) == ERR) {
1552                 config_msg = "Unknown key map";
1553                 return ERR;
1554         }
1556         key = get_key_value(argv[1]);
1557         if (key == ERR) {
1558                 config_msg = "Unknown key";
1559                 return ERR;
1560         }
1562         request = get_request(argv[2]);
1563         if (request == REQ_NONE) {
1564                 struct {
1565                         const char *name;
1566                         enum request request;
1567                 } obsolete[] = {
1568                         { "cherry-pick",        REQ_NONE },
1569                         { "screen-resize",      REQ_NONE },
1570                         { "tree-parent",        REQ_PARENT },
1571                 };
1572                 size_t namelen = strlen(argv[2]);
1573                 int i;
1575                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576                         if (namelen != strlen(obsolete[i].name) ||
1577                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1578                                 continue;
1579                         if (obsolete[i].request != REQ_NONE)
1580                                 add_keybinding(keymap, obsolete[i].request, key);
1581                         config_msg = "Obsolete request name";
1582                         return ERR;
1583                 }
1584         }
1585         if (request == REQ_NONE && *argv[2]++ == '!')
1586                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587         if (request == REQ_NONE) {
1588                 config_msg = "Unknown request name";
1589                 return ERR;
1590         }
1592         add_keybinding(keymap, request, key);
1594         return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600         const char *argv[SIZEOF_ARG];
1601         int argc = 0;
1603         if (!argv_from_string(argv, &argc, value)) {
1604                 config_msg = "Too many option arguments";
1605                 return ERR;
1606         }
1608         if (!strcmp(opt, "color"))
1609                 return option_color_command(argc, argv);
1611         if (!strcmp(opt, "set"))
1612                 return option_set_command(argc, argv);
1614         if (!strcmp(opt, "bind"))
1615                 return option_bind_command(argc, argv);
1617         config_msg = "Unknown option command";
1618         return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624         int status = OK;
1626         config_lineno++;
1627         config_msg = "Internal error";
1629         /* Check for comment markers, since read_properties() will
1630          * only ensure opt and value are split at first " \t". */
1631         optlen = strcspn(opt, "#");
1632         if (optlen == 0)
1633                 return OK;
1635         if (opt[optlen] != 0) {
1636                 config_msg = "No option value";
1637                 status = ERR;
1639         }  else {
1640                 /* Look for comment endings in the value. */
1641                 size_t len = strcspn(value, "#");
1643                 if (len < valuelen) {
1644                         valuelen = len;
1645                         value[valuelen] = 0;
1646                 }
1648                 status = set_option(opt, value);
1649         }
1651         if (status == ERR) {
1652                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653                         config_lineno, (int) optlen, opt, config_msg);
1654                 config_errors = TRUE;
1655         }
1657         /* Always keep going if errors are encountered. */
1658         return OK;
1661 static void
1662 load_option_file(const char *path)
1664         struct io io = {};
1666         /* It's ok that the file doesn't exist. */
1667         if (!io_open(&io, path))
1668                 return;
1670         config_lineno = 0;
1671         config_errors = FALSE;
1673         if (read_properties(&io, " \t", read_option) == ERR ||
1674             config_errors == TRUE)
1675                 fprintf(stderr, "Errors while loading %s.\n", path);
1678 static int
1679 load_options(void)
1681         const char *home = getenv("HOME");
1682         const char *tigrc_user = getenv("TIGRC_USER");
1683         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684         char buf[SIZEOF_STR];
1686         add_builtin_run_requests();
1688         if (!tigrc_system) {
1689                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690                         return ERR;
1691                 tigrc_system = buf;
1692         }
1693         load_option_file(tigrc_system);
1695         if (!tigrc_user) {
1696                 if (!home || !string_format(buf, "%s/.tigrc", home))
1697                         return ERR;
1698                 tigrc_user = buf;
1699         }
1700         load_option_file(tigrc_user);
1702         return OK;
1706 /*
1707  * The viewer
1708  */
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 /* 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_author(struct view *view, const char *author)
2013         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2015         if (!trim) {
2016                 static char initials[10];
2017                 size_t pos;
2019 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2021                 memset(initials, 0, sizeof(initials));
2022                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2023                         while (is_initial_sep(*author))
2024                                 author++;
2025                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2026                         while (*author && !is_initial_sep(author[1]))
2027                                 author++;
2028                 }
2030                 author = initials;
2031         }
2033         return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2036 static bool
2037 draw_view_line(struct view *view, unsigned int lineno)
2039         struct line *line;
2040         bool selected = (view->offset + lineno == view->lineno);
2042         assert(view_is_displayed(view));
2044         if (view->offset + lineno >= view->lines)
2045                 return FALSE;
2047         line = &view->line[view->offset + lineno];
2049         wmove(view->win, lineno, 0);
2050         if (line->cleareol)
2051                 wclrtoeol(view->win);
2052         view->col = 0;
2053         view->curline = line;
2054         view->curtype = LINE_NONE;
2055         line->selected = FALSE;
2056         line->dirty = line->cleareol = 0;
2058         if (selected) {
2059                 set_view_attr(view, LINE_CURSOR);
2060                 line->selected = TRUE;
2061                 view->ops->select(view, line);
2062         }
2064         return view->ops->draw(view, line, lineno);
2067 static void
2068 redraw_view_dirty(struct view *view)
2070         bool dirty = FALSE;
2071         int lineno;
2073         for (lineno = 0; lineno < view->height; lineno++) {
2074                 if (view->offset + lineno >= view->lines)
2075                         break;
2076                 if (!view->line[view->offset + lineno].dirty)
2077                         continue;
2078                 dirty = TRUE;
2079                 if (!draw_view_line(view, lineno))
2080                         break;
2081         }
2083         if (!dirty)
2084                 return;
2085         if (input_mode)
2086                 wnoutrefresh(view->win);
2087         else
2088                 wrefresh(view->win);
2091 static void
2092 redraw_view_from(struct view *view, int lineno)
2094         assert(0 <= lineno && lineno < view->height);
2096         for (; lineno < view->height; lineno++) {
2097                 if (!draw_view_line(view, lineno))
2098                         break;
2099         }
2101         if (input_mode)
2102                 wnoutrefresh(view->win);
2103         else
2104                 wrefresh(view->win);
2107 static void
2108 redraw_view(struct view *view)
2110         werase(view->win);
2111         redraw_view_from(view, 0);
2115 static void
2116 update_display_cursor(struct view *view)
2118         /* Move the cursor to the right-most column of the cursor line.
2119          *
2120          * XXX: This could turn out to be a bit expensive, but it ensures that
2121          * the cursor does not jump around. */
2122         if (view->lines) {
2123                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2124                 wrefresh(view->win);
2125         }
2128 static void
2129 update_view_title(struct view *view)
2131         char buf[SIZEOF_STR];
2132         char state[SIZEOF_STR];
2133         size_t bufpos = 0, statelen = 0;
2135         assert(view_is_displayed(view));
2137         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2138                 unsigned int view_lines = view->offset + view->height;
2139                 unsigned int lines = view->lines
2140                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2141                                    : 0;
2143                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2144                                    view->ops->type,
2145                                    view->lineno + 1,
2146                                    view->lines,
2147                                    lines);
2149         }
2151         if (view->pipe) {
2152                 time_t secs = time(NULL) - view->start_time;
2154                 /* Three git seconds are a long time ... */
2155                 if (secs > 2)
2156                         string_format_from(state, &statelen, " loading %lds", secs);
2157         }
2159         string_format_from(buf, &bufpos, "[%s]", view->name);
2160         if (*view->ref && bufpos < view->width) {
2161                 size_t refsize = strlen(view->ref);
2162                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2164                 if (minsize < view->width)
2165                         refsize = view->width - minsize + 7;
2166                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2167         }
2169         if (statelen && bufpos < view->width) {
2170                 string_format_from(buf, &bufpos, "%s", state);
2171         }
2173         if (view == display[current_view])
2174                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2175         else
2176                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2178         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2179         wclrtoeol(view->title);
2180         wmove(view->title, 0, view->width - 1);
2182         if (input_mode)
2183                 wnoutrefresh(view->title);
2184         else
2185                 wrefresh(view->title);
2188 static void
2189 resize_display(void)
2191         int offset, i;
2192         struct view *base = display[0];
2193         struct view *view = display[1] ? display[1] : display[0];
2195         /* Setup window dimensions */
2197         getmaxyx(stdscr, base->height, base->width);
2199         /* Make room for the status window. */
2200         base->height -= 1;
2202         if (view != base) {
2203                 /* Horizontal split. */
2204                 view->width   = base->width;
2205                 view->height  = SCALE_SPLIT_VIEW(base->height);
2206                 base->height -= view->height;
2208                 /* Make room for the title bar. */
2209                 view->height -= 1;
2210         }
2212         /* Make room for the title bar. */
2213         base->height -= 1;
2215         offset = 0;
2217         foreach_displayed_view (view, i) {
2218                 if (!view->win) {
2219                         view->win = newwin(view->height, 0, offset, 0);
2220                         if (!view->win)
2221                                 die("Failed to create %s view", view->name);
2223                         scrollok(view->win, FALSE);
2225                         view->title = newwin(1, 0, offset + view->height, 0);
2226                         if (!view->title)
2227                                 die("Failed to create title window");
2229                 } else {
2230                         wresize(view->win, view->height, view->width);
2231                         mvwin(view->win,   offset, 0);
2232                         mvwin(view->title, offset + view->height, 0);
2233                 }
2235                 offset += view->height + 1;
2236         }
2239 static void
2240 redraw_display(bool clear)
2242         struct view *view;
2243         int i;
2245         foreach_displayed_view (view, i) {
2246                 if (clear)
2247                         wclear(view->win);
2248                 redraw_view(view);
2249                 update_view_title(view);
2250         }
2252         if (display[current_view])
2253                 update_display_cursor(display[current_view]);
2256 static void
2257 toggle_view_option(bool *option, const char *help)
2259         *option = !*option;
2260         redraw_display(FALSE);
2261         report("%sabling %s", *option ? "En" : "Dis", help);
2264 /*
2265  * Navigation
2266  */
2268 /* Scrolling backend */
2269 static void
2270 do_scroll_view(struct view *view, int lines)
2272         bool redraw_current_line = FALSE;
2274         /* The rendering expects the new offset. */
2275         view->offset += lines;
2277         assert(0 <= view->offset && view->offset < view->lines);
2278         assert(lines);
2280         /* Move current line into the view. */
2281         if (view->lineno < view->offset) {
2282                 view->lineno = view->offset;
2283                 redraw_current_line = TRUE;
2284         } else if (view->lineno >= view->offset + view->height) {
2285                 view->lineno = view->offset + view->height - 1;
2286                 redraw_current_line = TRUE;
2287         }
2289         assert(view->offset <= view->lineno && view->lineno < view->lines);
2291         /* Redraw the whole screen if scrolling is pointless. */
2292         if (view->height < ABS(lines)) {
2293                 redraw_view(view);
2295         } else {
2296                 int line = lines > 0 ? view->height - lines : 0;
2297                 int end = line + ABS(lines);
2299                 scrollok(view->win, TRUE);
2300                 wscrl(view->win, lines);
2301                 scrollok(view->win, FALSE);
2303                 while (line < end && draw_view_line(view, line))
2304                         line++;
2306                 if (redraw_current_line)
2307                         draw_view_line(view, view->lineno - view->offset);
2308                 /* FIXME: Stupid hack to workaround bug where the message from
2309                  * scrolling up one line when impossible followed by scrolling
2310                  * down one line is not removed by the next action. */
2311                 if (lines > 0)
2312                         report("");
2313                 wrefresh(view->win);
2314         }
2316         report("");
2319 /* Scroll frontend */
2320 static void
2321 scroll_view(struct view *view, enum request request)
2323         int lines = 1;
2325         assert(view_is_displayed(view));
2327         switch (request) {
2328         case REQ_SCROLL_PAGE_DOWN:
2329                 lines = view->height;
2330         case REQ_SCROLL_LINE_DOWN:
2331                 if (view->offset + lines > view->lines)
2332                         lines = view->lines - view->offset;
2334                 if (lines == 0 || view->offset + view->height >= view->lines) {
2335                         report("Cannot scroll beyond the last line");
2336                         return;
2337                 }
2338                 break;
2340         case REQ_SCROLL_PAGE_UP:
2341                 lines = view->height;
2342         case REQ_SCROLL_LINE_UP:
2343                 if (lines > view->offset)
2344                         lines = view->offset;
2346                 if (lines == 0) {
2347                         report("Cannot scroll beyond the first line");
2348                         return;
2349                 }
2351                 lines = -lines;
2352                 break;
2354         default:
2355                 die("request %d not handled in switch", request);
2356         }
2358         do_scroll_view(view, lines);
2361 /* Cursor moving */
2362 static void
2363 move_view(struct view *view, enum request request)
2365         int scroll_steps = 0;
2366         int steps;
2368         switch (request) {
2369         case REQ_MOVE_FIRST_LINE:
2370                 steps = -view->lineno;
2371                 break;
2373         case REQ_MOVE_LAST_LINE:
2374                 steps = view->lines - view->lineno - 1;
2375                 break;
2377         case REQ_MOVE_PAGE_UP:
2378                 steps = view->height > view->lineno
2379                       ? -view->lineno : -view->height;
2380                 break;
2382         case REQ_MOVE_PAGE_DOWN:
2383                 steps = view->lineno + view->height >= view->lines
2384                       ? view->lines - view->lineno - 1 : view->height;
2385                 break;
2387         case REQ_MOVE_UP:
2388                 steps = -1;
2389                 break;
2391         case REQ_MOVE_DOWN:
2392                 steps = 1;
2393                 break;
2395         default:
2396                 die("request %d not handled in switch", request);
2397         }
2399         if (steps <= 0 && view->lineno == 0) {
2400                 report("Cannot move beyond the first line");
2401                 return;
2403         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2404                 report("Cannot move beyond the last line");
2405                 return;
2406         }
2408         /* Move the current line */
2409         view->lineno += steps;
2410         assert(0 <= view->lineno && view->lineno < view->lines);
2412         /* Check whether the view needs to be scrolled */
2413         if (view->lineno < view->offset ||
2414             view->lineno >= view->offset + view->height) {
2415                 scroll_steps = steps;
2416                 if (steps < 0 && -steps > view->offset) {
2417                         scroll_steps = -view->offset;
2419                 } else if (steps > 0) {
2420                         if (view->lineno == view->lines - 1 &&
2421                             view->lines > view->height) {
2422                                 scroll_steps = view->lines - view->offset - 1;
2423                                 if (scroll_steps >= view->height)
2424                                         scroll_steps -= view->height - 1;
2425                         }
2426                 }
2427         }
2429         if (!view_is_displayed(view)) {
2430                 view->offset += scroll_steps;
2431                 assert(0 <= view->offset && view->offset < view->lines);
2432                 view->ops->select(view, &view->line[view->lineno]);
2433                 return;
2434         }
2436         /* Repaint the old "current" line if we be scrolling */
2437         if (ABS(steps) < view->height)
2438                 draw_view_line(view, view->lineno - steps - view->offset);
2440         if (scroll_steps) {
2441                 do_scroll_view(view, scroll_steps);
2442                 return;
2443         }
2445         /* Draw the current line */
2446         draw_view_line(view, view->lineno - view->offset);
2448         wrefresh(view->win);
2449         report("");
2453 /*
2454  * Searching
2455  */
2457 static void search_view(struct view *view, enum request request);
2459 static void
2460 select_view_line(struct view *view, unsigned long lineno)
2462         if (lineno - view->offset >= view->height) {
2463                 view->offset = lineno;
2464                 view->lineno = lineno;
2465                 if (view_is_displayed(view))
2466                         redraw_view(view);
2468         } else {
2469                 unsigned long old_lineno = view->lineno - view->offset;
2471                 view->lineno = lineno;
2472                 if (view_is_displayed(view)) {
2473                         draw_view_line(view, old_lineno);
2474                         draw_view_line(view, view->lineno - view->offset);
2475                         wrefresh(view->win);
2476                 } else {
2477                         view->ops->select(view, &view->line[view->lineno]);
2478                 }
2479         }
2482 static void
2483 find_next(struct view *view, enum request request)
2485         unsigned long lineno = view->lineno;
2486         int direction;
2488         if (!*view->grep) {
2489                 if (!*opt_search)
2490                         report("No previous search");
2491                 else
2492                         search_view(view, request);
2493                 return;
2494         }
2496         switch (request) {
2497         case REQ_SEARCH:
2498         case REQ_FIND_NEXT:
2499                 direction = 1;
2500                 break;
2502         case REQ_SEARCH_BACK:
2503         case REQ_FIND_PREV:
2504                 direction = -1;
2505                 break;
2507         default:
2508                 return;
2509         }
2511         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2512                 lineno += direction;
2514         /* Note, lineno is unsigned long so will wrap around in which case it
2515          * will become bigger than view->lines. */
2516         for (; lineno < view->lines; lineno += direction) {
2517                 if (view->ops->grep(view, &view->line[lineno])) {
2518                         select_view_line(view, lineno);
2519                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2520                         return;
2521                 }
2522         }
2524         report("No match found for '%s'", view->grep);
2527 static void
2528 search_view(struct view *view, enum request request)
2530         int regex_err;
2532         if (view->regex) {
2533                 regfree(view->regex);
2534                 *view->grep = 0;
2535         } else {
2536                 view->regex = calloc(1, sizeof(*view->regex));
2537                 if (!view->regex)
2538                         return;
2539         }
2541         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2542         if (regex_err != 0) {
2543                 char buf[SIZEOF_STR] = "unknown error";
2545                 regerror(regex_err, view->regex, buf, sizeof(buf));
2546                 report("Search failed: %s", buf);
2547                 return;
2548         }
2550         string_copy(view->grep, opt_search);
2552         find_next(view, request);
2555 /*
2556  * Incremental updating
2557  */
2559 static void
2560 reset_view(struct view *view)
2562         int i;
2564         for (i = 0; i < view->lines; i++)
2565                 free(view->line[i].data);
2566         free(view->line);
2568         view->p_offset = view->offset;
2569         view->p_lineno = view->lineno;
2571         view->line = NULL;
2572         view->offset = 0;
2573         view->lines  = 0;
2574         view->lineno = 0;
2575         view->line_alloc = 0;
2576         view->vid[0] = 0;
2577         view->update_secs = 0;
2580 static void
2581 free_argv(const char *argv[])
2583         int argc;
2585         for (argc = 0; argv[argc]; argc++)
2586                 free((void *) argv[argc]);
2589 static bool
2590 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2592         char buf[SIZEOF_STR];
2593         int argc;
2594         bool noreplace = flags == FORMAT_NONE;
2596         free_argv(dst_argv);
2598         for (argc = 0; src_argv[argc]; argc++) {
2599                 const char *arg = src_argv[argc];
2600                 size_t bufpos = 0;
2602                 while (arg) {
2603                         char *next = strstr(arg, "%(");
2604                         int len = next - arg;
2605                         const char *value;
2607                         if (!next || noreplace) {
2608                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2609                                         noreplace = TRUE;
2610                                 len = strlen(arg);
2611                                 value = "";
2613                         } else if (!prefixcmp(next, "%(directory)")) {
2614                                 value = opt_path;
2616                         } else if (!prefixcmp(next, "%(file)")) {
2617                                 value = opt_file;
2619                         } else if (!prefixcmp(next, "%(ref)")) {
2620                                 value = *opt_ref ? opt_ref : "HEAD";
2622                         } else if (!prefixcmp(next, "%(head)")) {
2623                                 value = ref_head;
2625                         } else if (!prefixcmp(next, "%(commit)")) {
2626                                 value = ref_commit;
2628                         } else if (!prefixcmp(next, "%(blob)")) {
2629                                 value = ref_blob;
2631                         } else {
2632                                 report("Unknown replacement: `%s`", next);
2633                                 return FALSE;
2634                         }
2636                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2637                                 return FALSE;
2639                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2640                 }
2642                 dst_argv[argc] = strdup(buf);
2643                 if (!dst_argv[argc])
2644                         break;
2645         }
2647         dst_argv[argc] = NULL;
2649         return src_argv[argc] == NULL;
2652 static bool
2653 restore_view_position(struct view *view)
2655         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2656                 return FALSE;
2658         /* Changing the view position cancels the restoring. */
2659         /* FIXME: Changing back to the first line is not detected. */
2660         if (view->offset != 0 || view->lineno != 0) {
2661                 view->p_restore = FALSE;
2662                 return FALSE;
2663         }
2665         if (view->p_lineno >= view->lines) {
2666                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2667                 if (view->p_offset >= view->p_lineno) {
2668                         unsigned long half = view->height / 2;
2670                         if (view->p_lineno > half)
2671                                 view->p_offset = view->p_lineno - half;
2672                         else
2673                                 view->p_offset = 0;
2674                 }
2675         }
2677         if (view_is_displayed(view) &&
2678             view->offset != view->p_offset &&
2679             view->lineno != view->p_lineno)
2680                 werase(view->win);
2682         view->offset = view->p_offset;
2683         view->lineno = view->p_lineno;
2684         view->p_restore = FALSE;
2686         return TRUE;
2689 static void
2690 end_update(struct view *view, bool force)
2692         if (!view->pipe)
2693                 return;
2694         while (!view->ops->read(view, NULL))
2695                 if (!force)
2696                         return;
2697         set_nonblocking_input(FALSE);
2698         if (force)
2699                 kill_io(view->pipe);
2700         done_io(view->pipe);
2701         view->pipe = NULL;
2704 static void
2705 setup_update(struct view *view, const char *vid)
2707         set_nonblocking_input(TRUE);
2708         reset_view(view);
2709         string_copy_rev(view->vid, vid);
2710         view->pipe = &view->io;
2711         view->start_time = time(NULL);
2714 static bool
2715 prepare_update(struct view *view, const char *argv[], const char *dir,
2716                enum format_flags flags)
2718         if (view->pipe)
2719                 end_update(view, TRUE);
2720         return init_io_rd(&view->io, argv, dir, flags);
2723 static bool
2724 prepare_update_file(struct view *view, const char *name)
2726         if (view->pipe)
2727                 end_update(view, TRUE);
2728         return io_open(&view->io, name);
2731 static bool
2732 begin_update(struct view *view, bool refresh)
2734         if (view->pipe)
2735                 end_update(view, TRUE);
2737         if (refresh) {
2738                 if (!start_io(&view->io))
2739                         return FALSE;
2741         } else {
2742                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2743                         opt_path[0] = 0;
2745                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2746                         return FALSE;
2748                 /* Put the current ref_* value to the view title ref
2749                  * member. This is needed by the blob view. Most other
2750                  * views sets it automatically after loading because the
2751                  * first line is a commit line. */
2752                 string_copy_rev(view->ref, view->id);
2753         }
2755         setup_update(view, view->id);
2757         return TRUE;
2760 #define ITEM_CHUNK_SIZE 256
2761 static void *
2762 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2764         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2765         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2767         if (mem == NULL || num_chunks != num_chunks_new) {
2768                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2769                 mem = realloc(mem, *size * item_size);
2770         }
2772         return mem;
2775 static struct line *
2776 realloc_lines(struct view *view, size_t line_size)
2778         size_t alloc = view->line_alloc;
2779         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2780                                          sizeof(*view->line));
2782         if (!tmp)
2783                 return NULL;
2785         view->line = tmp;
2786         view->line_alloc = alloc;
2787         return view->line;
2790 static bool
2791 update_view(struct view *view)
2793         char out_buffer[BUFSIZ * 2];
2794         char *line;
2795         /* Clear the view and redraw everything since the tree sorting
2796          * might have rearranged things. */
2797         bool redraw = view->lines == 0;
2798         bool can_read = TRUE;
2800         if (!view->pipe)
2801                 return TRUE;
2803         if (!io_can_read(view->pipe)) {
2804                 if (view->lines == 0) {
2805                         time_t secs = time(NULL) - view->start_time;
2807                         if (secs > view->update_secs) {
2808                                 if (view->update_secs == 0)
2809                                         redraw_view(view);
2810                                 update_view_title(view);
2811                                 view->update_secs = secs;
2812                         }
2813                 }
2814                 return TRUE;
2815         }
2817         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2818                 if (opt_iconv != ICONV_NONE) {
2819                         ICONV_CONST char *inbuf = line;
2820                         size_t inlen = strlen(line) + 1;
2822                         char *outbuf = out_buffer;
2823                         size_t outlen = sizeof(out_buffer);
2825                         size_t ret;
2827                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2828                         if (ret != (size_t) -1)
2829                                 line = out_buffer;
2830                 }
2832                 if (!view->ops->read(view, line)) {
2833                         report("Allocation failure");
2834                         end_update(view, TRUE);
2835                         return FALSE;
2836                 }
2837         }
2839         {
2840                 unsigned long lines = view->lines;
2841                 int digits;
2843                 for (digits = 0; lines; digits++)
2844                         lines /= 10;
2846                 /* Keep the displayed view in sync with line number scaling. */
2847                 if (digits != view->digits) {
2848                         view->digits = digits;
2849                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2850                                 redraw = TRUE;
2851                 }
2852         }
2854         if (io_error(view->pipe)) {
2855                 report("Failed to read: %s", io_strerror(view->pipe));
2856                 end_update(view, TRUE);
2858         } else if (io_eof(view->pipe)) {
2859                 report("");
2860                 end_update(view, FALSE);
2861         }
2863         if (restore_view_position(view))
2864                 redraw = TRUE;
2866         if (!view_is_displayed(view))
2867                 return TRUE;
2869         if (redraw)
2870                 redraw_view_from(view, 0);
2871         else
2872                 redraw_view_dirty(view);
2874         /* Update the title _after_ the redraw so that if the redraw picks up a
2875          * commit reference in view->ref it'll be available here. */
2876         update_view_title(view);
2877         update_display_cursor(view);
2878         return TRUE;
2881 static struct line *
2882 add_line_data(struct view *view, void *data, enum line_type type)
2884         struct line *line;
2886         if (!realloc_lines(view, view->lines + 1))
2887                 return NULL;
2889         line = &view->line[view->lines++];
2890         memset(line, 0, sizeof(*line));
2891         line->type = type;
2892         line->data = data;
2893         line->dirty = 1;
2895         return line;
2898 static struct line *
2899 add_line_text(struct view *view, const char *text, enum line_type type)
2901         char *data = text ? strdup(text) : NULL;
2903         return data ? add_line_data(view, data, type) : NULL;
2906 static struct line *
2907 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2909         char buf[SIZEOF_STR];
2910         va_list args;
2912         va_start(args, fmt);
2913         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2914                 buf[0] = 0;
2915         va_end(args);
2917         return buf[0] ? add_line_text(view, buf, type) : NULL;
2920 /*
2921  * View opening
2922  */
2924 enum open_flags {
2925         OPEN_DEFAULT = 0,       /* Use default view switching. */
2926         OPEN_SPLIT = 1,         /* Split current view. */
2927         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2928         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2929         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2930         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2931         OPEN_PREPARED = 32,     /* Open already prepared command. */
2932 };
2934 static void
2935 open_view(struct view *prev, enum request request, enum open_flags flags)
2937         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2938         bool split = !!(flags & OPEN_SPLIT);
2939         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2940         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2941         struct view *view = VIEW(request);
2942         int nviews = displayed_views();
2943         struct view *base_view = display[0];
2945         if (view == prev && nviews == 1 && !reload) {
2946                 report("Already in %s view", view->name);
2947                 return;
2948         }
2950         if (view->git_dir && !opt_git_dir[0]) {
2951                 report("The %s view is disabled in pager view", view->name);
2952                 return;
2953         }
2955         if (split) {
2956                 display[1] = view;
2957                 if (!backgrounded)
2958                         current_view = 1;
2959         } else if (!nomaximize) {
2960                 /* Maximize the current view. */
2961                 memset(display, 0, sizeof(display));
2962                 current_view = 0;
2963                 display[current_view] = view;
2964         }
2966         /* Resize the view when switching between split- and full-screen,
2967          * or when switching between two different full-screen views. */
2968         if (nviews != displayed_views() ||
2969             (nviews == 1 && base_view != display[0]))
2970                 resize_display();
2972         if (view->ops->open) {
2973                 if (view->pipe)
2974                         end_update(view, TRUE);
2975                 if (!view->ops->open(view)) {
2976                         report("Failed to load %s view", view->name);
2977                         return;
2978                 }
2979                 restore_view_position(view);
2981         } else if ((reload || strcmp(view->vid, view->id)) &&
2982                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2983                 report("Failed to load %s view", view->name);
2984                 return;
2985         }
2987         if (split && prev->lineno - prev->offset >= prev->height) {
2988                 /* Take the title line into account. */
2989                 int lines = prev->lineno - prev->offset - prev->height + 1;
2991                 /* Scroll the view that was split if the current line is
2992                  * outside the new limited view. */
2993                 do_scroll_view(prev, lines);
2994         }
2996         if (prev && view != prev) {
2997                 if (split && !backgrounded) {
2998                         /* "Blur" the previous view. */
2999                         update_view_title(prev);
3000                 }
3002                 view->parent = prev;
3003         }
3005         if (view->pipe && view->lines == 0) {
3006                 /* Clear the old view and let the incremental updating refill
3007                  * the screen. */
3008                 werase(view->win);
3009                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3010                 report("");
3011         } else if (view_is_displayed(view)) {
3012                 redraw_view(view);
3013                 report("");
3014         }
3016         /* If the view is backgrounded the above calls to report()
3017          * won't redraw the view title. */
3018         if (backgrounded)
3019                 update_view_title(view);
3022 static void
3023 open_external_viewer(const char *argv[], const char *dir)
3025         def_prog_mode();           /* save current tty modes */
3026         endwin();                  /* restore original tty modes */
3027         run_io_fg(argv, dir);
3028         fprintf(stderr, "Press Enter to continue");
3029         getc(opt_tty);
3030         reset_prog_mode();
3031         redraw_display(TRUE);
3034 static void
3035 open_mergetool(const char *file)
3037         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3039         open_external_viewer(mergetool_argv, opt_cdup);
3042 static void
3043 open_editor(bool from_root, const char *file)
3045         const char *editor_argv[] = { "vi", file, NULL };
3046         const char *editor;
3048         editor = getenv("GIT_EDITOR");
3049         if (!editor && *opt_editor)
3050                 editor = opt_editor;
3051         if (!editor)
3052                 editor = getenv("VISUAL");
3053         if (!editor)
3054                 editor = getenv("EDITOR");
3055         if (!editor)
3056                 editor = "vi";
3058         editor_argv[0] = editor;
3059         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3062 static void
3063 open_run_request(enum request request)
3065         struct run_request *req = get_run_request(request);
3066         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3068         if (!req) {
3069                 report("Unknown run request");
3070                 return;
3071         }
3073         if (format_argv(argv, req->argv, FORMAT_ALL))
3074                 open_external_viewer(argv, NULL);
3075         free_argv(argv);
3078 /*
3079  * User request switch noodle
3080  */
3082 static int
3083 view_driver(struct view *view, enum request request)
3085         int i;
3087         if (request == REQ_NONE) {
3088                 doupdate();
3089                 return TRUE;
3090         }
3092         if (request > REQ_NONE) {
3093                 open_run_request(request);
3094                 /* FIXME: When all views can refresh always do this. */
3095                 if (view == VIEW(REQ_VIEW_STATUS) ||
3096                     view == VIEW(REQ_VIEW_MAIN) ||
3097                     view == VIEW(REQ_VIEW_LOG) ||
3098                     view == VIEW(REQ_VIEW_STAGE))
3099                         request = REQ_REFRESH;
3100                 else
3101                         return TRUE;
3102         }
3104         if (view && view->lines) {
3105                 request = view->ops->request(view, request, &view->line[view->lineno]);
3106                 if (request == REQ_NONE)
3107                         return TRUE;
3108         }
3110         switch (request) {
3111         case REQ_MOVE_UP:
3112         case REQ_MOVE_DOWN:
3113         case REQ_MOVE_PAGE_UP:
3114         case REQ_MOVE_PAGE_DOWN:
3115         case REQ_MOVE_FIRST_LINE:
3116         case REQ_MOVE_LAST_LINE:
3117                 move_view(view, request);
3118                 break;
3120         case REQ_SCROLL_LINE_DOWN:
3121         case REQ_SCROLL_LINE_UP:
3122         case REQ_SCROLL_PAGE_DOWN:
3123         case REQ_SCROLL_PAGE_UP:
3124                 scroll_view(view, request);
3125                 break;
3127         case REQ_VIEW_BLAME:
3128                 if (!opt_file[0]) {
3129                         report("No file chosen, press %s to open tree view",
3130                                get_key(REQ_VIEW_TREE));
3131                         break;
3132                 }
3133                 open_view(view, request, OPEN_DEFAULT);
3134                 break;
3136         case REQ_VIEW_BLOB:
3137                 if (!ref_blob[0]) {
3138                         report("No file chosen, press %s to open tree view",
3139                                get_key(REQ_VIEW_TREE));
3140                         break;
3141                 }
3142                 open_view(view, request, OPEN_DEFAULT);
3143                 break;
3145         case REQ_VIEW_PAGER:
3146                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3147                         report("No pager content, press %s to run command from prompt",
3148                                get_key(REQ_PROMPT));
3149                         break;
3150                 }
3151                 open_view(view, request, OPEN_DEFAULT);
3152                 break;
3154         case REQ_VIEW_STAGE:
3155                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3156                         report("No stage content, press %s to open the status view and choose file",
3157                                get_key(REQ_VIEW_STATUS));
3158                         break;
3159                 }
3160                 open_view(view, request, OPEN_DEFAULT);
3161                 break;
3163         case REQ_VIEW_STATUS:
3164                 if (opt_is_inside_work_tree == FALSE) {
3165                         report("The status view requires a working tree");
3166                         break;
3167                 }
3168                 open_view(view, request, OPEN_DEFAULT);
3169                 break;
3171         case REQ_VIEW_MAIN:
3172         case REQ_VIEW_DIFF:
3173         case REQ_VIEW_LOG:
3174         case REQ_VIEW_TREE:
3175         case REQ_VIEW_HELP:
3176                 open_view(view, request, OPEN_DEFAULT);
3177                 break;
3179         case REQ_NEXT:
3180         case REQ_PREVIOUS:
3181                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3183                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3184                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3185                    (view == VIEW(REQ_VIEW_DIFF) &&
3186                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3187                    (view == VIEW(REQ_VIEW_STAGE) &&
3188                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3189                    (view == VIEW(REQ_VIEW_BLOB) &&
3190                      view->parent == VIEW(REQ_VIEW_TREE))) {
3191                         int line;
3193                         view = view->parent;
3194                         line = view->lineno;
3195                         move_view(view, request);
3196                         if (view_is_displayed(view))
3197                                 update_view_title(view);
3198                         if (line != view->lineno)
3199                                 view->ops->request(view, REQ_ENTER,
3200                                                    &view->line[view->lineno]);
3202                 } else {
3203                         move_view(view, request);
3204                 }
3205                 break;
3207         case REQ_VIEW_NEXT:
3208         {
3209                 int nviews = displayed_views();
3210                 int next_view = (current_view + 1) % nviews;
3212                 if (next_view == current_view) {
3213                         report("Only one view is displayed");
3214                         break;
3215                 }
3217                 current_view = next_view;
3218                 /* Blur out the title of the previous view. */
3219                 update_view_title(view);
3220                 report("");
3221                 break;
3222         }
3223         case REQ_REFRESH:
3224                 report("Refreshing is not yet supported for the %s view", view->name);
3225                 break;
3227         case REQ_MAXIMIZE:
3228                 if (displayed_views() == 2)
3229                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3230                 break;
3232         case REQ_TOGGLE_LINENO:
3233                 toggle_view_option(&opt_line_number, "line numbers");
3234                 break;
3236         case REQ_TOGGLE_DATE:
3237                 toggle_view_option(&opt_date, "date display");
3238                 break;
3240         case REQ_TOGGLE_AUTHOR:
3241                 toggle_view_option(&opt_author, "author display");
3242                 break;
3244         case REQ_TOGGLE_REV_GRAPH:
3245                 toggle_view_option(&opt_rev_graph, "revision graph display");
3246                 break;
3248         case REQ_TOGGLE_REFS:
3249                 toggle_view_option(&opt_show_refs, "reference display");
3250                 break;
3252         case REQ_SEARCH:
3253         case REQ_SEARCH_BACK:
3254                 search_view(view, request);
3255                 break;
3257         case REQ_FIND_NEXT:
3258         case REQ_FIND_PREV:
3259                 find_next(view, request);
3260                 break;
3262         case REQ_STOP_LOADING:
3263                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3264                         view = &views[i];
3265                         if (view->pipe)
3266                                 report("Stopped loading the %s view", view->name),
3267                         end_update(view, TRUE);
3268                 }
3269                 break;
3271         case REQ_SHOW_VERSION:
3272                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3273                 return TRUE;
3275         case REQ_SCREEN_REDRAW:
3276                 redraw_display(TRUE);
3277                 break;
3279         case REQ_EDIT:
3280                 report("Nothing to edit");
3281                 break;
3283         case REQ_ENTER:
3284                 report("Nothing to enter");
3285                 break;
3287         case REQ_VIEW_CLOSE:
3288                 /* XXX: Mark closed views by letting view->parent point to the
3289                  * view itself. Parents to closed view should never be
3290                  * followed. */
3291                 if (view->parent &&
3292                     view->parent->parent != view->parent) {
3293                         memset(display, 0, sizeof(display));
3294                         current_view = 0;
3295                         display[current_view] = view->parent;
3296                         view->parent = view;
3297                         resize_display();
3298                         redraw_display(FALSE);
3299                         report("");
3300                         break;
3301                 }
3302                 /* Fall-through */
3303         case REQ_QUIT:
3304                 return FALSE;
3306         default:
3307                 report("Unknown key, press 'h' for help");
3308                 return TRUE;
3309         }
3311         return TRUE;
3315 /*
3316  * View backend utilities
3317  */
3319 /* Parse author lines where the name may be empty:
3320  *      author  <email@address.tld> 1138474660 +0100
3321  */
3322 static void
3323 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3325         char *nameend = strchr(ident, '<');
3326         char *emailend = strchr(ident, '>');
3328         if (nameend && emailend)
3329                 *nameend = *emailend = 0;
3330         ident = chomp_string(ident);
3331         if (!*ident) {
3332                 if (nameend)
3333                         ident = chomp_string(nameend + 1);
3334                 if (!*ident)
3335                         ident = "Unknown";
3336         }
3338         string_ncopy_do(author, authorsize, ident, strlen(ident));
3340         /* Parse epoch and timezone */
3341         if (emailend && emailend[1] == ' ') {
3342                 char *secs = emailend + 2;
3343                 char *zone = strchr(secs, ' ');
3344                 time_t time = (time_t) atol(secs);
3346                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3347                         long tz;
3349                         zone++;
3350                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3351                         tz += ('0' - zone[2]) * 60 * 60;
3352                         tz += ('0' - zone[3]) * 60;
3353                         tz += ('0' - zone[4]) * 60;
3355                         if (zone[0] == '-')
3356                                 tz = -tz;
3358                         time -= tz;
3359                 }
3361                 gmtime_r(&time, tm);
3362         }
3365 static enum input_status
3366 select_commit_parent_handler(void *data, char *buf, int c)
3368         size_t parents = *(size_t *) data;
3369         int parent = 0;
3371         if (!isdigit(c))
3372                 return INPUT_SKIP;
3374         if (*buf)
3375                 parent = atoi(buf) * 10;
3376         parent += c - '0';
3378         if (parent > parents)
3379                 return INPUT_SKIP;
3380         return INPUT_OK;
3383 static bool
3384 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3386         char buf[SIZEOF_STR * 4];
3387         const char *revlist_argv[] = {
3388                 "git", "rev-list", "-1", "--parents", id, NULL
3389         };
3390         int parents;
3392         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3393             !*chomp_string(buf) ||
3394             (parents = (strlen(buf) / 40) - 1) < 0) {
3395                 report("Failed to get parent information");
3396                 return FALSE;
3398         } else if (parents == 0) {
3399                 report("The selected commit has no parents");
3400                 return FALSE;
3401         }
3403         if (parents > 1) {
3404                 char prompt[SIZEOF_STR];
3405                 char *result;
3407                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3408                         return FALSE;
3409                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3410                 if (!result)
3411                         return FALSE;
3412                 parents = atoi(result);
3413         }
3415         string_copy_rev(rev, &buf[41 * parents]);
3416         return TRUE;
3419 /*
3420  * Pager backend
3421  */
3423 static bool
3424 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3426         char *text = line->data;
3428         if (opt_line_number && draw_lineno(view, lineno))
3429                 return TRUE;
3431         draw_text(view, line->type, text, TRUE);
3432         return TRUE;
3435 static bool
3436 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3438         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3439         char refbuf[SIZEOF_STR];
3440         char *ref = NULL;
3442         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3443                 ref = chomp_string(refbuf);
3445         if (!ref || !*ref)
3446                 return TRUE;
3448         /* This is the only fatal call, since it can "corrupt" the buffer. */
3449         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3450                 return FALSE;
3452         return TRUE;
3455 static void
3456 add_pager_refs(struct view *view, struct line *line)
3458         char buf[SIZEOF_STR];
3459         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3460         struct ref **refs;
3461         size_t bufpos = 0, refpos = 0;
3462         const char *sep = "Refs: ";
3463         bool is_tag = FALSE;
3465         assert(line->type == LINE_COMMIT);
3467         refs = get_refs(commit_id);
3468         if (!refs) {
3469                 if (view == VIEW(REQ_VIEW_DIFF))
3470                         goto try_add_describe_ref;
3471                 return;
3472         }
3474         do {
3475                 struct ref *ref = refs[refpos];
3476                 const char *fmt = ref->tag    ? "%s[%s]" :
3477                                   ref->remote ? "%s<%s>" : "%s%s";
3479                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3480                         return;
3481                 sep = ", ";
3482                 if (ref->tag)
3483                         is_tag = TRUE;
3484         } while (refs[refpos++]->next);
3486         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3487 try_add_describe_ref:
3488                 /* Add <tag>-g<commit_id> "fake" reference. */
3489                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3490                         return;
3491         }
3493         if (bufpos == 0)
3494                 return;
3496         add_line_text(view, buf, LINE_PP_REFS);
3499 static bool
3500 pager_read(struct view *view, char *data)
3502         struct line *line;
3504         if (!data)
3505                 return TRUE;
3507         line = add_line_text(view, data, get_line_type(data));
3508         if (!line)
3509                 return FALSE;
3511         if (line->type == LINE_COMMIT &&
3512             (view == VIEW(REQ_VIEW_DIFF) ||
3513              view == VIEW(REQ_VIEW_LOG)))
3514                 add_pager_refs(view, line);
3516         return TRUE;
3519 static enum request
3520 pager_request(struct view *view, enum request request, struct line *line)
3522         int split = 0;
3524         if (request != REQ_ENTER)
3525                 return request;
3527         if (line->type == LINE_COMMIT &&
3528            (view == VIEW(REQ_VIEW_LOG) ||
3529             view == VIEW(REQ_VIEW_PAGER))) {
3530                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3531                 split = 1;
3532         }
3534         /* Always scroll the view even if it was split. That way
3535          * you can use Enter to scroll through the log view and
3536          * split open each commit diff. */
3537         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3539         /* FIXME: A minor workaround. Scrolling the view will call report("")
3540          * but if we are scrolling a non-current view this won't properly
3541          * update the view title. */
3542         if (split)
3543                 update_view_title(view);
3545         return REQ_NONE;
3548 static bool
3549 pager_grep(struct view *view, struct line *line)
3551         regmatch_t pmatch;
3552         char *text = line->data;
3554         if (!*text)
3555                 return FALSE;
3557         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3558                 return FALSE;
3560         return TRUE;
3563 static void
3564 pager_select(struct view *view, struct line *line)
3566         if (line->type == LINE_COMMIT) {
3567                 char *text = (char *)line->data + STRING_SIZE("commit ");
3569                 if (view != VIEW(REQ_VIEW_PAGER))
3570                         string_copy_rev(view->ref, text);
3571                 string_copy_rev(ref_commit, text);
3572         }
3575 static struct view_ops pager_ops = {
3576         "line",
3577         NULL,
3578         NULL,
3579         pager_read,
3580         pager_draw,
3581         pager_request,
3582         pager_grep,
3583         pager_select,
3584 };
3586 static const char *log_argv[SIZEOF_ARG] = {
3587         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3588 };
3590 static enum request
3591 log_request(struct view *view, enum request request, struct line *line)
3593         switch (request) {
3594         case REQ_REFRESH:
3595                 load_refs();
3596                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3597                 return REQ_NONE;
3598         default:
3599                 return pager_request(view, request, line);
3600         }
3603 static struct view_ops log_ops = {
3604         "line",
3605         log_argv,
3606         NULL,
3607         pager_read,
3608         pager_draw,
3609         log_request,
3610         pager_grep,
3611         pager_select,
3612 };
3614 static const char *diff_argv[SIZEOF_ARG] = {
3615         "git", "show", "--pretty=fuller", "--no-color", "--root",
3616                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3617 };
3619 static struct view_ops diff_ops = {
3620         "line",
3621         diff_argv,
3622         NULL,
3623         pager_read,
3624         pager_draw,
3625         pager_request,
3626         pager_grep,
3627         pager_select,
3628 };
3630 /*
3631  * Help backend
3632  */
3634 static bool
3635 help_open(struct view *view)
3637         char buf[SIZEOF_STR];
3638         size_t bufpos;
3639         int i;
3641         if (view->lines > 0)
3642                 return TRUE;
3644         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3646         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3647                 const char *key;
3649                 if (req_info[i].request == REQ_NONE)
3650                         continue;
3652                 if (!req_info[i].request) {
3653                         add_line_text(view, "", LINE_DEFAULT);
3654                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3655                         continue;
3656                 }
3658                 key = get_key(req_info[i].request);
3659                 if (!*key)
3660                         key = "(no key defined)";
3662                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3663                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3664                         if (buf[bufpos] == '_')
3665                                 buf[bufpos] = '-';
3666                 }
3668                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3669                                 key, buf, req_info[i].help);
3670         }
3672         if (run_requests) {
3673                 add_line_text(view, "", LINE_DEFAULT);
3674                 add_line_text(view, "External commands:", LINE_DEFAULT);
3675         }
3677         for (i = 0; i < run_requests; i++) {
3678                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3679                 const char *key;
3680                 int argc;
3682                 if (!req)
3683                         continue;
3685                 key = get_key_name(req->key);
3686                 if (!*key)
3687                         key = "(no key defined)";
3689                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3690                         if (!string_format_from(buf, &bufpos, "%s%s",
3691                                                 argc ? " " : "", req->argv[argc]))
3692                                 return REQ_NONE;
3694                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3695                                 keymap_table[req->keymap].name, key, buf);
3696         }
3698         return TRUE;
3701 static struct view_ops help_ops = {
3702         "line",
3703         NULL,
3704         help_open,
3705         NULL,
3706         pager_draw,
3707         pager_request,
3708         pager_grep,
3709         pager_select,
3710 };
3713 /*
3714  * Tree backend
3715  */
3717 struct tree_stack_entry {
3718         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3719         unsigned long lineno;           /* Line number to restore */
3720         char *name;                     /* Position of name in opt_path */
3721 };
3723 /* The top of the path stack. */
3724 static struct tree_stack_entry *tree_stack = NULL;
3725 unsigned long tree_lineno = 0;
3727 static void
3728 pop_tree_stack_entry(void)
3730         struct tree_stack_entry *entry = tree_stack;
3732         tree_lineno = entry->lineno;
3733         entry->name[0] = 0;
3734         tree_stack = entry->prev;
3735         free(entry);
3738 static void
3739 push_tree_stack_entry(const char *name, unsigned long lineno)
3741         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3742         size_t pathlen = strlen(opt_path);
3744         if (!entry)
3745                 return;
3747         entry->prev = tree_stack;
3748         entry->name = opt_path + pathlen;
3749         tree_stack = entry;
3751         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3752                 pop_tree_stack_entry();
3753                 return;
3754         }
3756         /* Move the current line to the first tree entry. */
3757         tree_lineno = 1;
3758         entry->lineno = lineno;
3761 /* Parse output from git-ls-tree(1):
3762  *
3763  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3764  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3765  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3766  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3767  */
3769 #define SIZEOF_TREE_ATTR \
3770         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3772 #define SIZEOF_TREE_MODE \
3773         STRING_SIZE("100644 ")
3775 #define TREE_ID_OFFSET \
3776         STRING_SIZE("100644 blob ")
3778 struct tree_entry {
3779         char id[SIZEOF_REV];
3780         mode_t mode;
3781         struct tm time;                 /* Date from the author ident. */
3782         char author[75];                /* Author of the commit. */
3783         char name[1];
3784 };
3786 static const char *
3787 tree_path(struct line *line)
3789         return ((struct tree_entry *) line->data)->name;
3793 static int
3794 tree_compare_entry(struct line *line1, struct line *line2)
3796         if (line1->type != line2->type)
3797                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3798         return strcmp(tree_path(line1), tree_path(line2));
3801 static struct line *
3802 tree_entry(struct view *view, enum line_type type, const char *path,
3803            const char *mode, const char *id)
3805         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3806         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3808         if (!entry || !line) {
3809                 free(entry);
3810                 return NULL;
3811         }
3813         strncpy(entry->name, path, strlen(path));
3814         if (mode)
3815                 entry->mode = strtoul(mode, NULL, 8);
3816         if (id)
3817                 string_copy_rev(entry->id, id);
3819         return line;
3822 static bool
3823 tree_read_date(struct view *view, char *text, bool *read_date)
3825         static char author_name[SIZEOF_STR];
3826         static struct tm author_time;
3828         if (!text && *read_date) {
3829                 *read_date = FALSE;
3830                 return TRUE;
3832         } else if (!text) {
3833                 char *path = *opt_path ? opt_path : ".";
3834                 /* Find next entry to process */
3835                 const char *log_file[] = {
3836                         "git", "log", "--no-color", "--pretty=raw",
3837                                 "--cc", "--raw", view->id, "--", path, NULL
3838                 };
3839                 struct io io = {};
3841                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3842                         report("Failed to load tree data");
3843                         return TRUE;
3844                 }
3846                 done_io(view->pipe);
3847                 view->io = io;
3848                 *read_date = TRUE;
3849                 return FALSE;
3851         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3852                 parse_author_line(text + STRING_SIZE("author "),
3853                                   author_name, sizeof(author_name), &author_time);
3855         } else if (*text == ':') {
3856                 char *pos;
3857                 size_t annotated = 1;
3858                 size_t i;
3860                 pos = strchr(text, '\t');
3861                 if (!pos)
3862                         return TRUE;
3863                 text = pos + 1;
3864                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3865                         text += strlen(opt_prefix);
3866                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3867                         text += strlen(opt_path);
3868                 pos = strchr(text, '/');
3869                 if (pos)
3870                         *pos = 0;
3872                 for (i = 1; i < view->lines; i++) {
3873                         struct line *line = &view->line[i];
3874                         struct tree_entry *entry = line->data;
3876                         annotated += !!*entry->author;
3877                         if (*entry->author || strcmp(entry->name, text))
3878                                 continue;
3880                         string_copy(entry->author, author_name);
3881                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3882                         line->dirty = 1;
3883                         break;
3884                 }
3886                 if (annotated == view->lines)
3887                         kill_io(view->pipe);
3888         }
3889         return TRUE;
3892 static bool
3893 tree_read(struct view *view, char *text)
3895         static bool read_date = FALSE;
3896         struct tree_entry *data;
3897         struct line *entry, *line;
3898         enum line_type type;
3899         size_t textlen = text ? strlen(text) : 0;
3900         char *path = text + SIZEOF_TREE_ATTR;
3902         if (read_date || !text)
3903                 return tree_read_date(view, text, &read_date);
3905         if (textlen <= SIZEOF_TREE_ATTR)
3906                 return FALSE;
3907         if (view->lines == 0 &&
3908             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3909                 return FALSE;
3911         /* Strip the path part ... */
3912         if (*opt_path) {
3913                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3914                 size_t striplen = strlen(opt_path);
3916                 if (pathlen > striplen)
3917                         memmove(path, path + striplen,
3918                                 pathlen - striplen + 1);
3920                 /* Insert "link" to parent directory. */
3921                 if (view->lines == 1 &&
3922                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3923                         return FALSE;
3924         }
3926         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3927         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3928         if (!entry)
3929                 return FALSE;
3930         data = entry->data;
3932         /* Skip "Directory ..." and ".." line. */
3933         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3934                 if (tree_compare_entry(line, entry) <= 0)
3935                         continue;
3937                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3939                 line->data = data;
3940                 line->type = type;
3941                 for (; line <= entry; line++)
3942                         line->dirty = line->cleareol = 1;
3943                 return TRUE;
3944         }
3946         if (tree_lineno > view->lineno) {
3947                 view->lineno = tree_lineno;
3948                 tree_lineno = 0;
3949         }
3951         return TRUE;
3954 static bool
3955 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3957         struct tree_entry *entry = line->data;
3959         if (line->type == LINE_TREE_PARENT) {
3960                 if (draw_text(view, line->type, "Directory path /", TRUE))
3961                         return TRUE;
3962         } else {
3963                 char mode[11] = "-r--r--r--";
3965                 if (S_ISDIR(entry->mode)) {
3966                         mode[3] = mode[6] = mode[9] = 'x';
3967                         mode[0] = 'd';
3968                 }
3969                 if (S_ISLNK(entry->mode))
3970                         mode[0] = 'l';
3971                 if (entry->mode & S_IWUSR)
3972                         mode[2] = 'w';
3973                 if (entry->mode & S_IXUSR)
3974                         mode[3] = 'x';
3975                 if (entry->mode & S_IXGRP)
3976                         mode[6] = 'x';
3977                 if (entry->mode & S_IXOTH)
3978                         mode[9] = 'x';
3979                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3980                         return TRUE;
3982                 if (opt_author && draw_author(view, entry->author))
3983                         return TRUE;
3985                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3986                         return TRUE;
3987         }
3988         if (draw_text(view, line->type, entry->name, TRUE))
3989                 return TRUE;
3990         return TRUE;
3993 static void
3994 open_blob_editor()
3996         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3997         int fd = mkstemp(file);
3999         if (fd == -1)
4000                 report("Failed to create temporary file");
4001         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4002                 report("Failed to save blob data to file");
4003         else
4004                 open_editor(FALSE, file);
4005         if (fd != -1)
4006                 unlink(file);
4009 static enum request
4010 tree_request(struct view *view, enum request request, struct line *line)
4012         enum open_flags flags;
4014         switch (request) {
4015         case REQ_VIEW_BLAME:
4016                 if (line->type != LINE_TREE_FILE) {
4017                         report("Blame only supported for files");
4018                         return REQ_NONE;
4019                 }
4021                 string_copy(opt_ref, view->vid);
4022                 return request;
4024         case REQ_EDIT:
4025                 if (line->type != LINE_TREE_FILE) {
4026                         report("Edit only supported for files");
4027                 } else if (!is_head_commit(view->vid)) {
4028                         open_blob_editor();
4029                 } else {
4030                         open_editor(TRUE, opt_file);
4031                 }
4032                 return REQ_NONE;
4034         case REQ_PARENT:
4035                 if (!*opt_path) {
4036                         /* quit view if at top of tree */
4037                         return REQ_VIEW_CLOSE;
4038                 }
4039                 /* fake 'cd  ..' */
4040                 line = &view->line[1];
4041                 break;
4043         case REQ_ENTER:
4044                 break;
4046         default:
4047                 return request;
4048         }
4050         /* Cleanup the stack if the tree view is at a different tree. */
4051         while (!*opt_path && tree_stack)
4052                 pop_tree_stack_entry();
4054         switch (line->type) {
4055         case LINE_TREE_DIR:
4056                 /* Depending on whether it is a subdir or parent (updir?) link
4057                  * mangle the path buffer. */
4058                 if (line == &view->line[1] && *opt_path) {
4059                         pop_tree_stack_entry();
4061                 } else {
4062                         const char *basename = tree_path(line);
4064                         push_tree_stack_entry(basename, view->lineno);
4065                 }
4067                 /* Trees and subtrees share the same ID, so they are not not
4068                  * unique like blobs. */
4069                 flags = OPEN_RELOAD;
4070                 request = REQ_VIEW_TREE;
4071                 break;
4073         case LINE_TREE_FILE:
4074                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4075                 request = REQ_VIEW_BLOB;
4076                 break;
4078         default:
4079                 return REQ_NONE;
4080         }
4082         open_view(view, request, flags);
4083         if (request == REQ_VIEW_TREE)
4084                 view->lineno = tree_lineno;
4086         return REQ_NONE;
4089 static void
4090 tree_select(struct view *view, struct line *line)
4092         struct tree_entry *entry = line->data;
4094         if (line->type == LINE_TREE_FILE) {
4095                 string_copy_rev(ref_blob, entry->id);
4096                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4098         } else if (line->type != LINE_TREE_DIR) {
4099                 return;
4100         }
4102         string_copy_rev(view->ref, entry->id);
4105 static const char *tree_argv[SIZEOF_ARG] = {
4106         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4107 };
4109 static struct view_ops tree_ops = {
4110         "file",
4111         tree_argv,
4112         NULL,
4113         tree_read,
4114         tree_draw,
4115         tree_request,
4116         pager_grep,
4117         tree_select,
4118 };
4120 static bool
4121 blob_read(struct view *view, char *line)
4123         if (!line)
4124                 return TRUE;
4125         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4128 static enum request
4129 blob_request(struct view *view, enum request request, struct line *line)
4131         switch (request) {
4132         case REQ_EDIT:
4133                 open_blob_editor();
4134                 return REQ_NONE;
4135         default:
4136                 return pager_request(view, request, line);
4137         }
4140 static const char *blob_argv[SIZEOF_ARG] = {
4141         "git", "cat-file", "blob", "%(blob)", NULL
4142 };
4144 static struct view_ops blob_ops = {
4145         "line",
4146         blob_argv,
4147         NULL,
4148         blob_read,
4149         pager_draw,
4150         blob_request,
4151         pager_grep,
4152         pager_select,
4153 };
4155 /*
4156  * Blame backend
4157  *
4158  * Loading the blame view is a two phase job:
4159  *
4160  *  1. File content is read either using opt_file from the
4161  *     filesystem or using git-cat-file.
4162  *  2. Then blame information is incrementally added by
4163  *     reading output from git-blame.
4164  */
4166 static const char *blame_head_argv[] = {
4167         "git", "blame", "--incremental", "--", "%(file)", NULL
4168 };
4170 static const char *blame_ref_argv[] = {
4171         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4172 };
4174 static const char *blame_cat_file_argv[] = {
4175         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4176 };
4178 struct blame_commit {
4179         char id[SIZEOF_REV];            /* SHA1 ID. */
4180         char title[128];                /* First line of the commit message. */
4181         char author[75];                /* Author of the commit. */
4182         struct tm time;                 /* Date from the author ident. */
4183         char filename[128];             /* Name of file. */
4184         bool has_previous;              /* Was a "previous" line detected. */
4185 };
4187 struct blame {
4188         struct blame_commit *commit;
4189         char text[1];
4190 };
4192 static bool
4193 blame_open(struct view *view)
4195         if (*opt_ref || !io_open(&view->io, opt_file)) {
4196                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4197                         return FALSE;
4198         }
4200         setup_update(view, opt_file);
4201         string_format(view->ref, "%s ...", opt_file);
4203         return TRUE;
4206 static struct blame_commit *
4207 get_blame_commit(struct view *view, const char *id)
4209         size_t i;
4211         for (i = 0; i < view->lines; i++) {
4212                 struct blame *blame = view->line[i].data;
4214                 if (!blame->commit)
4215                         continue;
4217                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4218                         return blame->commit;
4219         }
4221         {
4222                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4224                 if (commit)
4225                         string_ncopy(commit->id, id, SIZEOF_REV);
4226                 return commit;
4227         }
4230 static bool
4231 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4233         const char *pos = *posref;
4235         *posref = NULL;
4236         pos = strchr(pos + 1, ' ');
4237         if (!pos || !isdigit(pos[1]))
4238                 return FALSE;
4239         *number = atoi(pos + 1);
4240         if (*number < min || *number > max)
4241                 return FALSE;
4243         *posref = pos;
4244         return TRUE;
4247 static struct blame_commit *
4248 parse_blame_commit(struct view *view, const char *text, int *blamed)
4250         struct blame_commit *commit;
4251         struct blame *blame;
4252         const char *pos = text + SIZEOF_REV - 1;
4253         size_t lineno;
4254         size_t group;
4256         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4257                 return NULL;
4259         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4260             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4261                 return NULL;
4263         commit = get_blame_commit(view, text);
4264         if (!commit)
4265                 return NULL;
4267         *blamed += group;
4268         while (group--) {
4269                 struct line *line = &view->line[lineno + group - 1];
4271                 blame = line->data;
4272                 blame->commit = commit;
4273                 line->dirty = 1;
4274         }
4276         return commit;
4279 static bool
4280 blame_read_file(struct view *view, const char *line, bool *read_file)
4282         if (!line) {
4283                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4284                 struct io io = {};
4286                 if (view->lines == 0 && !view->parent)
4287                         die("No blame exist for %s", view->vid);
4289                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4290                         report("Failed to load blame data");
4291                         return TRUE;
4292                 }
4294                 done_io(view->pipe);
4295                 view->io = io;
4296                 *read_file = FALSE;
4297                 return FALSE;
4299         } else {
4300                 size_t linelen = strlen(line);
4301                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4303                 blame->commit = NULL;
4304                 strncpy(blame->text, line, linelen);
4305                 blame->text[linelen] = 0;
4306                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4307         }
4310 static bool
4311 match_blame_header(const char *name, char **line)
4313         size_t namelen = strlen(name);
4314         bool matched = !strncmp(name, *line, namelen);
4316         if (matched)
4317                 *line += namelen;
4319         return matched;
4322 static bool
4323 blame_read(struct view *view, char *line)
4325         static struct blame_commit *commit = NULL;
4326         static int blamed = 0;
4327         static time_t author_time;
4328         static bool read_file = TRUE;
4330         if (read_file)
4331                 return blame_read_file(view, line, &read_file);
4333         if (!line) {
4334                 /* Reset all! */
4335                 commit = NULL;
4336                 blamed = 0;
4337                 read_file = TRUE;
4338                 string_format(view->ref, "%s", view->vid);
4339                 if (view_is_displayed(view)) {
4340                         update_view_title(view);
4341                         redraw_view_from(view, 0);
4342                 }
4343                 return TRUE;
4344         }
4346         if (!commit) {
4347                 commit = parse_blame_commit(view, line, &blamed);
4348                 string_format(view->ref, "%s %2d%%", view->vid,
4349                               view->lines ? blamed * 100 / view->lines : 0);
4351         } else if (match_blame_header("author ", &line)) {
4352                 string_ncopy(commit->author, line, strlen(line));
4354         } else if (match_blame_header("author-time ", &line)) {
4355                 author_time = (time_t) atol(line);
4357         } else if (match_blame_header("author-tz ", &line)) {
4358                 long tz;
4360                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4361                 tz += ('0' - line[2]) * 60 * 60;
4362                 tz += ('0' - line[3]) * 60;
4363                 tz += ('0' - line[4]) * 60;
4365                 if (line[0] == '-')
4366                         tz = -tz;
4368                 author_time -= tz;
4369                 gmtime_r(&author_time, &commit->time);
4371         } else if (match_blame_header("summary ", &line)) {
4372                 string_ncopy(commit->title, line, strlen(line));
4374         } else if (match_blame_header("previous ", &line)) {
4375                 commit->has_previous = TRUE;
4377         } else if (match_blame_header("filename ", &line)) {
4378                 string_ncopy(commit->filename, line, strlen(line));
4379                 commit = NULL;
4380         }
4382         return TRUE;
4385 static bool
4386 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4388         struct blame *blame = line->data;
4389         struct tm *time = NULL;
4390         const char *id = NULL, *author = NULL;
4392         if (blame->commit && *blame->commit->filename) {
4393                 id = blame->commit->id;
4394                 author = blame->commit->author;
4395                 time = &blame->commit->time;
4396         }
4398         if (opt_date && draw_date(view, time))
4399                 return TRUE;
4401         if (opt_author && draw_author(view, author))
4402                 return TRUE;
4404         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4405                 return TRUE;
4407         if (draw_lineno(view, lineno))
4408                 return TRUE;
4410         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4411         return TRUE;
4414 static bool
4415 check_blame_commit(struct blame *blame)
4417         if (!blame->commit)
4418                 report("Commit data not loaded yet");
4419         else if (!strcmp(blame->commit->id, NULL_ID))
4420                 report("No commit exist for the selected line");
4421         else
4422                 return TRUE;
4423         return FALSE;
4426 static enum request
4427 blame_request(struct view *view, enum request request, struct line *line)
4429         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4430         struct blame *blame = line->data;
4432         switch (request) {
4433         case REQ_VIEW_BLAME:
4434                 if (check_blame_commit(blame)) {
4435                         string_copy(opt_ref, blame->commit->id);
4436                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4437                 }
4438                 break;
4440         case REQ_PARENT:
4441                 if (check_blame_commit(blame) &&
4442                     select_commit_parent(blame->commit->id, opt_ref))
4443                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4444                 break;
4446         case REQ_ENTER:
4447                 if (!blame->commit) {
4448                         report("No commit loaded yet");
4449                         break;
4450                 }
4452                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4453                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4454                         break;
4456                 if (!strcmp(blame->commit->id, NULL_ID)) {
4457                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4458                         const char *diff_index_argv[] = {
4459                                 "git", "diff-index", "--root", "--patch-with-stat",
4460                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4461                         };
4463                         if (!blame->commit->has_previous) {
4464                                 diff_index_argv[1] = "diff";
4465                                 diff_index_argv[2] = "--no-color";
4466                                 diff_index_argv[6] = "--";
4467                                 diff_index_argv[7] = "/dev/null";
4468                         }
4470                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4471                                 report("Failed to allocate diff command");
4472                                 break;
4473                         }
4474                         flags |= OPEN_PREPARED;
4475                 }
4477                 open_view(view, REQ_VIEW_DIFF, flags);
4478                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4479                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4480                 break;
4482         default:
4483                 return request;
4484         }
4486         return REQ_NONE;
4489 static bool
4490 blame_grep(struct view *view, struct line *line)
4492         struct blame *blame = line->data;
4493         struct blame_commit *commit = blame->commit;
4494         regmatch_t pmatch;
4496 #define MATCH(text, on)                                                 \
4497         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4499         if (commit) {
4500                 char buf[DATE_COLS + 1];
4502                 if (MATCH(commit->title, 1) ||
4503                     MATCH(commit->author, opt_author) ||
4504                     MATCH(commit->id, opt_date))
4505                         return TRUE;
4507                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4508                     MATCH(buf, 1))
4509                         return TRUE;
4510         }
4512         return MATCH(blame->text, 1);
4514 #undef MATCH
4517 static void
4518 blame_select(struct view *view, struct line *line)
4520         struct blame *blame = line->data;
4521         struct blame_commit *commit = blame->commit;
4523         if (!commit)
4524                 return;
4526         if (!strcmp(commit->id, NULL_ID))
4527                 string_ncopy(ref_commit, "HEAD", 4);
4528         else
4529                 string_copy_rev(ref_commit, commit->id);
4532 static struct view_ops blame_ops = {
4533         "line",
4534         NULL,
4535         blame_open,
4536         blame_read,
4537         blame_draw,
4538         blame_request,
4539         blame_grep,
4540         blame_select,
4541 };
4543 /*
4544  * Status backend
4545  */
4547 struct status {
4548         char status;
4549         struct {
4550                 mode_t mode;
4551                 char rev[SIZEOF_REV];
4552                 char name[SIZEOF_STR];
4553         } old;
4554         struct {
4555                 mode_t mode;
4556                 char rev[SIZEOF_REV];
4557                 char name[SIZEOF_STR];
4558         } new;
4559 };
4561 static char status_onbranch[SIZEOF_STR];
4562 static struct status stage_status;
4563 static enum line_type stage_line_type;
4564 static size_t stage_chunks;
4565 static int *stage_chunk;
4567 /* This should work even for the "On branch" line. */
4568 static inline bool
4569 status_has_none(struct view *view, struct line *line)
4571         return line < view->line + view->lines && !line[1].data;
4574 /* Get fields from the diff line:
4575  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4576  */
4577 static inline bool
4578 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4580         const char *old_mode = buf +  1;
4581         const char *new_mode = buf +  8;
4582         const char *old_rev  = buf + 15;
4583         const char *new_rev  = buf + 56;
4584         const char *status   = buf + 97;
4586         if (bufsize < 98 ||
4587             old_mode[-1] != ':' ||
4588             new_mode[-1] != ' ' ||
4589             old_rev[-1]  != ' ' ||
4590             new_rev[-1]  != ' ' ||
4591             status[-1]   != ' ')
4592                 return FALSE;
4594         file->status = *status;
4596         string_copy_rev(file->old.rev, old_rev);
4597         string_copy_rev(file->new.rev, new_rev);
4599         file->old.mode = strtoul(old_mode, NULL, 8);
4600         file->new.mode = strtoul(new_mode, NULL, 8);
4602         file->old.name[0] = file->new.name[0] = 0;
4604         return TRUE;
4607 static bool
4608 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4610         struct status *file = NULL;
4611         struct status *unmerged = NULL;
4612         char *buf;
4613         struct io io = {};
4615         if (!run_io(&io, argv, NULL, IO_RD))
4616                 return FALSE;
4618         add_line_data(view, NULL, type);
4620         while ((buf = io_get(&io, 0, TRUE))) {
4621                 if (!file) {
4622                         file = calloc(1, sizeof(*file));
4623                         if (!file || !add_line_data(view, file, type))
4624                                 goto error_out;
4625                 }
4627                 /* Parse diff info part. */
4628                 if (status) {
4629                         file->status = status;
4630                         if (status == 'A')
4631                                 string_copy(file->old.rev, NULL_ID);
4633                 } else if (!file->status) {
4634                         if (!status_get_diff(file, buf, strlen(buf)))
4635                                 goto error_out;
4637                         buf = io_get(&io, 0, TRUE);
4638                         if (!buf)
4639                                 break;
4641                         /* Collapse all 'M'odified entries that follow a
4642                          * associated 'U'nmerged entry. */
4643                         if (file->status == 'U') {
4644                                 unmerged = file;
4646                         } else if (unmerged) {
4647                                 int collapse = !strcmp(buf, unmerged->new.name);
4649                                 unmerged = NULL;
4650                                 if (collapse) {
4651                                         free(file);
4652                                         file = NULL;
4653                                         view->lines--;
4654                                         continue;
4655                                 }
4656                         }
4657                 }
4659                 /* Grab the old name for rename/copy. */
4660                 if (!*file->old.name &&
4661                     (file->status == 'R' || file->status == 'C')) {
4662                         string_ncopy(file->old.name, buf, strlen(buf));
4664                         buf = io_get(&io, 0, TRUE);
4665                         if (!buf)
4666                                 break;
4667                 }
4669                 /* git-ls-files just delivers a NUL separated list of
4670                  * file names similar to the second half of the
4671                  * git-diff-* output. */
4672                 string_ncopy(file->new.name, buf, strlen(buf));
4673                 if (!*file->old.name)
4674                         string_copy(file->old.name, file->new.name);
4675                 file = NULL;
4676         }
4678         if (io_error(&io)) {
4679 error_out:
4680                 done_io(&io);
4681                 return FALSE;
4682         }
4684         if (!view->line[view->lines - 1].data)
4685                 add_line_data(view, NULL, LINE_STAT_NONE);
4687         done_io(&io);
4688         return TRUE;
4691 /* Don't show unmerged entries in the staged section. */
4692 static const char *status_diff_index_argv[] = {
4693         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4694                              "--cached", "-M", "HEAD", NULL
4695 };
4697 static const char *status_diff_files_argv[] = {
4698         "git", "diff-files", "-z", NULL
4699 };
4701 static const char *status_list_other_argv[] = {
4702         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4703 };
4705 static const char *status_list_no_head_argv[] = {
4706         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4707 };
4709 static const char *update_index_argv[] = {
4710         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4711 };
4713 /* Restore the previous line number to stay in the context or select a
4714  * line with something that can be updated. */
4715 static void
4716 status_restore(struct view *view)
4718         if (view->p_lineno >= view->lines)
4719                 view->p_lineno = view->lines - 1;
4720         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4721                 view->p_lineno++;
4722         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4723                 view->p_lineno--;
4725         /* If the above fails, always skip the "On branch" line. */
4726         if (view->p_lineno < view->lines)
4727                 view->lineno = view->p_lineno;
4728         else
4729                 view->lineno = 1;
4731         if (view->lineno < view->offset)
4732                 view->offset = view->lineno;
4733         else if (view->offset + view->height <= view->lineno)
4734                 view->offset = view->lineno - view->height + 1;
4736         view->p_restore = FALSE;
4739 /* First parse staged info using git-diff-index(1), then parse unstaged
4740  * info using git-diff-files(1), and finally untracked files using
4741  * git-ls-files(1). */
4742 static bool
4743 status_open(struct view *view)
4745         reset_view(view);
4747         add_line_data(view, NULL, LINE_STAT_HEAD);
4748         if (is_initial_commit())
4749                 string_copy(status_onbranch, "Initial commit");
4750         else if (!*opt_head)
4751                 string_copy(status_onbranch, "Not currently on any branch");
4752         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4753                 return FALSE;
4755         run_io_bg(update_index_argv);
4757         if (is_initial_commit()) {
4758                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4759                         return FALSE;
4760         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4761                 return FALSE;
4762         }
4764         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4765             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4766                 return FALSE;
4768         /* Restore the exact position or use the specialized restore
4769          * mode? */
4770         if (!view->p_restore)
4771                 status_restore(view);
4772         return TRUE;
4775 static bool
4776 status_draw(struct view *view, struct line *line, unsigned int lineno)
4778         struct status *status = line->data;
4779         enum line_type type;
4780         const char *text;
4782         if (!status) {
4783                 switch (line->type) {
4784                 case LINE_STAT_STAGED:
4785                         type = LINE_STAT_SECTION;
4786                         text = "Changes to be committed:";
4787                         break;
4789                 case LINE_STAT_UNSTAGED:
4790                         type = LINE_STAT_SECTION;
4791                         text = "Changed but not updated:";
4792                         break;
4794                 case LINE_STAT_UNTRACKED:
4795                         type = LINE_STAT_SECTION;
4796                         text = "Untracked files:";
4797                         break;
4799                 case LINE_STAT_NONE:
4800                         type = LINE_DEFAULT;
4801                         text = "    (no files)";
4802                         break;
4804                 case LINE_STAT_HEAD:
4805                         type = LINE_STAT_HEAD;
4806                         text = status_onbranch;
4807                         break;
4809                 default:
4810                         return FALSE;
4811                 }
4812         } else {
4813                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4815                 buf[0] = status->status;
4816                 if (draw_text(view, line->type, buf, TRUE))
4817                         return TRUE;
4818                 type = LINE_DEFAULT;
4819                 text = status->new.name;
4820         }
4822         draw_text(view, type, text, TRUE);
4823         return TRUE;
4826 static enum request
4827 status_enter(struct view *view, struct line *line)
4829         struct status *status = line->data;
4830         const char *oldpath = status ? status->old.name : NULL;
4831         /* Diffs for unmerged entries are empty when passing the new
4832          * path, so leave it empty. */
4833         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4834         const char *info;
4835         enum open_flags split;
4836         struct view *stage = VIEW(REQ_VIEW_STAGE);
4838         if (line->type == LINE_STAT_NONE ||
4839             (!status && line[1].type == LINE_STAT_NONE)) {
4840                 report("No file to diff");
4841                 return REQ_NONE;
4842         }
4844         switch (line->type) {
4845         case LINE_STAT_STAGED:
4846                 if (is_initial_commit()) {
4847                         const char *no_head_diff_argv[] = {
4848                                 "git", "diff", "--no-color", "--patch-with-stat",
4849                                         "--", "/dev/null", newpath, NULL
4850                         };
4852                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4853                                 return REQ_QUIT;
4854                 } else {
4855                         const char *index_show_argv[] = {
4856                                 "git", "diff-index", "--root", "--patch-with-stat",
4857                                         "-C", "-M", "--cached", "HEAD", "--",
4858                                         oldpath, newpath, NULL
4859                         };
4861                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4862                                 return REQ_QUIT;
4863                 }
4865                 if (status)
4866                         info = "Staged changes to %s";
4867                 else
4868                         info = "Staged changes";
4869                 break;
4871         case LINE_STAT_UNSTAGED:
4872         {
4873                 const char *files_show_argv[] = {
4874                         "git", "diff-files", "--root", "--patch-with-stat",
4875                                 "-C", "-M", "--", oldpath, newpath, NULL
4876                 };
4878                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4879                         return REQ_QUIT;
4880                 if (status)
4881                         info = "Unstaged changes to %s";
4882                 else
4883                         info = "Unstaged changes";
4884                 break;
4885         }
4886         case LINE_STAT_UNTRACKED:
4887                 if (!newpath) {
4888                         report("No file to show");
4889                         return REQ_NONE;
4890                 }
4892                 if (!suffixcmp(status->new.name, -1, "/")) {
4893                         report("Cannot display a directory");
4894                         return REQ_NONE;
4895                 }
4897                 if (!prepare_update_file(stage, newpath))
4898                         return REQ_QUIT;
4899                 info = "Untracked file %s";
4900                 break;
4902         case LINE_STAT_HEAD:
4903                 return REQ_NONE;
4905         default:
4906                 die("line type %d not handled in switch", line->type);
4907         }
4909         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4910         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4911         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4912                 if (status) {
4913                         stage_status = *status;
4914                 } else {
4915                         memset(&stage_status, 0, sizeof(stage_status));
4916                 }
4918                 stage_line_type = line->type;
4919                 stage_chunks = 0;
4920                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4921         }
4923         return REQ_NONE;
4926 static bool
4927 status_exists(struct status *status, enum line_type type)
4929         struct view *view = VIEW(REQ_VIEW_STATUS);
4930         unsigned long lineno;
4932         for (lineno = 0; lineno < view->lines; lineno++) {
4933                 struct line *line = &view->line[lineno];
4934                 struct status *pos = line->data;
4936                 if (line->type != type)
4937                         continue;
4938                 if (!pos && (!status || !status->status) && line[1].data) {
4939                         select_view_line(view, lineno);
4940                         return TRUE;
4941                 }
4942                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4943                         select_view_line(view, lineno);
4944                         return TRUE;
4945                 }
4946         }
4948         return FALSE;
4952 static bool
4953 status_update_prepare(struct io *io, enum line_type type)
4955         const char *staged_argv[] = {
4956                 "git", "update-index", "-z", "--index-info", NULL
4957         };
4958         const char *others_argv[] = {
4959                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4960         };
4962         switch (type) {
4963         case LINE_STAT_STAGED:
4964                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4966         case LINE_STAT_UNSTAGED:
4967                 return run_io(io, others_argv, opt_cdup, IO_WR);
4969         case LINE_STAT_UNTRACKED:
4970                 return run_io(io, others_argv, NULL, IO_WR);
4972         default:
4973                 die("line type %d not handled in switch", type);
4974                 return FALSE;
4975         }
4978 static bool
4979 status_update_write(struct io *io, struct status *status, enum line_type type)
4981         char buf[SIZEOF_STR];
4982         size_t bufsize = 0;
4984         switch (type) {
4985         case LINE_STAT_STAGED:
4986                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4987                                         status->old.mode,
4988                                         status->old.rev,
4989                                         status->old.name, 0))
4990                         return FALSE;
4991                 break;
4993         case LINE_STAT_UNSTAGED:
4994         case LINE_STAT_UNTRACKED:
4995                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4996                         return FALSE;
4997                 break;
4999         default:
5000                 die("line type %d not handled in switch", type);
5001         }
5003         return io_write(io, buf, bufsize);
5006 static bool
5007 status_update_file(struct status *status, enum line_type type)
5009         struct io io = {};
5010         bool result;
5012         if (!status_update_prepare(&io, type))
5013                 return FALSE;
5015         result = status_update_write(&io, status, type);
5016         done_io(&io);
5017         return result;
5020 static bool
5021 status_update_files(struct view *view, struct line *line)
5023         struct io io = {};
5024         bool result = TRUE;
5025         struct line *pos = view->line + view->lines;
5026         int files = 0;
5027         int file, done;
5029         if (!status_update_prepare(&io, line->type))
5030                 return FALSE;
5032         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5033                 files++;
5035         for (file = 0, done = 0; result && file < files; line++, file++) {
5036                 int almost_done = file * 100 / files;
5038                 if (almost_done > done) {
5039                         done = almost_done;
5040                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5041                                       file, files, done);
5042                         update_view_title(view);
5043                 }
5044                 result = status_update_write(&io, line->data, line->type);
5045         }
5047         done_io(&io);
5048         return result;
5051 static bool
5052 status_update(struct view *view)
5054         struct line *line = &view->line[view->lineno];
5056         assert(view->lines);
5058         if (!line->data) {
5059                 /* This should work even for the "On branch" line. */
5060                 if (line < view->line + view->lines && !line[1].data) {
5061                         report("Nothing to update");
5062                         return FALSE;
5063                 }
5065                 if (!status_update_files(view, line + 1)) {
5066                         report("Failed to update file status");
5067                         return FALSE;
5068                 }
5070         } else if (!status_update_file(line->data, line->type)) {
5071                 report("Failed to update file status");
5072                 return FALSE;
5073         }
5075         return TRUE;
5078 static bool
5079 status_revert(struct status *status, enum line_type type, bool has_none)
5081         if (!status || type != LINE_STAT_UNSTAGED) {
5082                 if (type == LINE_STAT_STAGED) {
5083                         report("Cannot revert changes to staged files");
5084                 } else if (type == LINE_STAT_UNTRACKED) {
5085                         report("Cannot revert changes to untracked files");
5086                 } else if (has_none) {
5087                         report("Nothing to revert");
5088                 } else {
5089                         report("Cannot revert changes to multiple files");
5090                 }
5091                 return FALSE;
5093         } else {
5094                 const char *checkout_argv[] = {
5095                         "git", "checkout", "--", status->old.name, NULL
5096                 };
5098                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5099                         return FALSE;
5100                 return run_io_fg(checkout_argv, opt_cdup);
5101         }
5104 static enum request
5105 status_request(struct view *view, enum request request, struct line *line)
5107         struct status *status = line->data;
5109         switch (request) {
5110         case REQ_STATUS_UPDATE:
5111                 if (!status_update(view))
5112                         return REQ_NONE;
5113                 break;
5115         case REQ_STATUS_REVERT:
5116                 if (!status_revert(status, line->type, status_has_none(view, line)))
5117                         return REQ_NONE;
5118                 break;
5120         case REQ_STATUS_MERGE:
5121                 if (!status || status->status != 'U') {
5122                         report("Merging only possible for files with unmerged status ('U').");
5123                         return REQ_NONE;
5124                 }
5125                 open_mergetool(status->new.name);
5126                 break;
5128         case REQ_EDIT:
5129                 if (!status)
5130                         return request;
5131                 if (status->status == 'D') {
5132                         report("File has been deleted.");
5133                         return REQ_NONE;
5134                 }
5136                 open_editor(status->status != '?', status->new.name);
5137                 break;
5139         case REQ_VIEW_BLAME:
5140                 if (status) {
5141                         string_copy(opt_file, status->new.name);
5142                         opt_ref[0] = 0;
5143                 }
5144                 return request;
5146         case REQ_ENTER:
5147                 /* After returning the status view has been split to
5148                  * show the stage view. No further reloading is
5149                  * necessary. */
5150                 status_enter(view, line);
5151                 return REQ_NONE;
5153         case REQ_REFRESH:
5154                 /* Simply reload the view. */
5155                 break;
5157         default:
5158                 return request;
5159         }
5161         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5163         return REQ_NONE;
5166 static void
5167 status_select(struct view *view, struct line *line)
5169         struct status *status = line->data;
5170         char file[SIZEOF_STR] = "all files";
5171         const char *text;
5172         const char *key;
5174         if (status && !string_format(file, "'%s'", status->new.name))
5175                 return;
5177         if (!status && line[1].type == LINE_STAT_NONE)
5178                 line++;
5180         switch (line->type) {
5181         case LINE_STAT_STAGED:
5182                 text = "Press %s to unstage %s for commit";
5183                 break;
5185         case LINE_STAT_UNSTAGED:
5186                 text = "Press %s to stage %s for commit";
5187                 break;
5189         case LINE_STAT_UNTRACKED:
5190                 text = "Press %s to stage %s for addition";
5191                 break;
5193         case LINE_STAT_HEAD:
5194         case LINE_STAT_NONE:
5195                 text = "Nothing to update";
5196                 break;
5198         default:
5199                 die("line type %d not handled in switch", line->type);
5200         }
5202         if (status && status->status == 'U') {
5203                 text = "Press %s to resolve conflict in %s";
5204                 key = get_key(REQ_STATUS_MERGE);
5206         } else {
5207                 key = get_key(REQ_STATUS_UPDATE);
5208         }
5210         string_format(view->ref, text, key, file);
5213 static bool
5214 status_grep(struct view *view, struct line *line)
5216         struct status *status = line->data;
5217         enum { S_STATUS, S_NAME, S_END } state;
5218         char buf[2] = "?";
5219         regmatch_t pmatch;
5221         if (!status)
5222                 return FALSE;
5224         for (state = S_STATUS; state < S_END; state++) {
5225                 const char *text;
5227                 switch (state) {
5228                 case S_NAME:    text = status->new.name;        break;
5229                 case S_STATUS:
5230                         buf[0] = status->status;
5231                         text = buf;
5232                         break;
5234                 default:
5235                         return FALSE;
5236                 }
5238                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5239                         return TRUE;
5240         }
5242         return FALSE;
5245 static struct view_ops status_ops = {
5246         "file",
5247         NULL,
5248         status_open,
5249         NULL,
5250         status_draw,
5251         status_request,
5252         status_grep,
5253         status_select,
5254 };
5257 static bool
5258 stage_diff_write(struct io *io, struct line *line, struct line *end)
5260         while (line < end) {
5261                 if (!io_write(io, line->data, strlen(line->data)) ||
5262                     !io_write(io, "\n", 1))
5263                         return FALSE;
5264                 line++;
5265                 if (line->type == LINE_DIFF_CHUNK ||
5266                     line->type == LINE_DIFF_HEADER)
5267                         break;
5268         }
5270         return TRUE;
5273 static struct line *
5274 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5276         for (; view->line < line; line--)
5277                 if (line->type == type)
5278                         return line;
5280         return NULL;
5283 static bool
5284 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5286         const char *apply_argv[SIZEOF_ARG] = {
5287                 "git", "apply", "--whitespace=nowarn", NULL
5288         };
5289         struct line *diff_hdr;
5290         struct io io = {};
5291         int argc = 3;
5293         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5294         if (!diff_hdr)
5295                 return FALSE;
5297         if (!revert)
5298                 apply_argv[argc++] = "--cached";
5299         if (revert || stage_line_type == LINE_STAT_STAGED)
5300                 apply_argv[argc++] = "-R";
5301         apply_argv[argc++] = "-";
5302         apply_argv[argc++] = NULL;
5303         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5304                 return FALSE;
5306         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5307             !stage_diff_write(&io, chunk, view->line + view->lines))
5308                 chunk = NULL;
5310         done_io(&io);
5311         run_io_bg(update_index_argv);
5313         return chunk ? TRUE : FALSE;
5316 static bool
5317 stage_update(struct view *view, struct line *line)
5319         struct line *chunk = NULL;
5321         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5322                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5324         if (chunk) {
5325                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5326                         report("Failed to apply chunk");
5327                         return FALSE;
5328                 }
5330         } else if (!stage_status.status) {
5331                 view = VIEW(REQ_VIEW_STATUS);
5333                 for (line = view->line; line < view->line + view->lines; line++)
5334                         if (line->type == stage_line_type)
5335                                 break;
5337                 if (!status_update_files(view, line + 1)) {
5338                         report("Failed to update files");
5339                         return FALSE;
5340                 }
5342         } else if (!status_update_file(&stage_status, stage_line_type)) {
5343                 report("Failed to update file");
5344                 return FALSE;
5345         }
5347         return TRUE;
5350 static bool
5351 stage_revert(struct view *view, struct line *line)
5353         struct line *chunk = NULL;
5355         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5356                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5358         if (chunk) {
5359                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5360                         return FALSE;
5362                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5363                         report("Failed to revert chunk");
5364                         return FALSE;
5365                 }
5366                 return TRUE;
5368         } else {
5369                 return status_revert(stage_status.status ? &stage_status : NULL,
5370                                      stage_line_type, FALSE);
5371         }
5375 static void
5376 stage_next(struct view *view, struct line *line)
5378         int i;
5380         if (!stage_chunks) {
5381                 static size_t alloc = 0;
5382                 int *tmp;
5384                 for (line = view->line; line < view->line + view->lines; line++) {
5385                         if (line->type != LINE_DIFF_CHUNK)
5386                                 continue;
5388                         tmp = realloc_items(stage_chunk, &alloc,
5389                                             stage_chunks, sizeof(*tmp));
5390                         if (!tmp) {
5391                                 report("Allocation failure");
5392                                 return;
5393                         }
5395                         stage_chunk = tmp;
5396                         stage_chunk[stage_chunks++] = line - view->line;
5397                 }
5398         }
5400         for (i = 0; i < stage_chunks; i++) {
5401                 if (stage_chunk[i] > view->lineno) {
5402                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5403                         report("Chunk %d of %d", i + 1, stage_chunks);
5404                         return;
5405                 }
5406         }
5408         report("No next chunk found");
5411 static enum request
5412 stage_request(struct view *view, enum request request, struct line *line)
5414         switch (request) {
5415         case REQ_STATUS_UPDATE:
5416                 if (!stage_update(view, line))
5417                         return REQ_NONE;
5418                 break;
5420         case REQ_STATUS_REVERT:
5421                 if (!stage_revert(view, line))
5422                         return REQ_NONE;
5423                 break;
5425         case REQ_STAGE_NEXT:
5426                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5427                         report("File is untracked; press %s to add",
5428                                get_key(REQ_STATUS_UPDATE));
5429                         return REQ_NONE;
5430                 }
5431                 stage_next(view, line);
5432                 return REQ_NONE;
5434         case REQ_EDIT:
5435                 if (!stage_status.new.name[0])
5436                         return request;
5437                 if (stage_status.status == 'D') {
5438                         report("File has been deleted.");
5439                         return REQ_NONE;
5440                 }
5442                 open_editor(stage_status.status != '?', stage_status.new.name);
5443                 break;
5445         case REQ_REFRESH:
5446                 /* Reload everything ... */
5447                 break;
5449         case REQ_VIEW_BLAME:
5450                 if (stage_status.new.name[0]) {
5451                         string_copy(opt_file, stage_status.new.name);
5452                         opt_ref[0] = 0;
5453                 }
5454                 return request;
5456         case REQ_ENTER:
5457                 return pager_request(view, request, line);
5459         default:
5460                 return request;
5461         }
5463         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5464         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5466         /* Check whether the staged entry still exists, and close the
5467          * stage view if it doesn't. */
5468         if (!status_exists(&stage_status, stage_line_type)) {
5469                 status_restore(VIEW(REQ_VIEW_STATUS));
5470                 return REQ_VIEW_CLOSE;
5471         }
5473         if (stage_line_type == LINE_STAT_UNTRACKED) {
5474                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5475                         report("Cannot display a directory");
5476                         return REQ_NONE;
5477                 }
5479                 if (!prepare_update_file(view, stage_status.new.name)) {
5480                         report("Failed to open file: %s", strerror(errno));
5481                         return REQ_NONE;
5482                 }
5483         }
5484         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5486         return REQ_NONE;
5489 static struct view_ops stage_ops = {
5490         "line",
5491         NULL,
5492         NULL,
5493         pager_read,
5494         pager_draw,
5495         stage_request,
5496         pager_grep,
5497         pager_select,
5498 };
5501 /*
5502  * Revision graph
5503  */
5505 struct commit {
5506         char id[SIZEOF_REV];            /* SHA1 ID. */
5507         char title[128];                /* First line of the commit message. */
5508         char author[75];                /* Author of the commit. */
5509         struct tm time;                 /* Date from the author ident. */
5510         struct ref **refs;              /* Repository references. */
5511         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5512         size_t graph_size;              /* The width of the graph array. */
5513         bool has_parents;               /* Rewritten --parents seen. */
5514 };
5516 /* Size of rev graph with no  "padding" columns */
5517 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5519 struct rev_graph {
5520         struct rev_graph *prev, *next, *parents;
5521         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5522         size_t size;
5523         struct commit *commit;
5524         size_t pos;
5525         unsigned int boundary:1;
5526 };
5528 /* Parents of the commit being visualized. */
5529 static struct rev_graph graph_parents[4];
5531 /* The current stack of revisions on the graph. */
5532 static struct rev_graph graph_stacks[4] = {
5533         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5534         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5535         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5536         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5537 };
5539 static inline bool
5540 graph_parent_is_merge(struct rev_graph *graph)
5542         return graph->parents->size > 1;
5545 static inline void
5546 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5548         struct commit *commit = graph->commit;
5550         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5551                 commit->graph[commit->graph_size++] = symbol;
5554 static void
5555 clear_rev_graph(struct rev_graph *graph)
5557         graph->boundary = 0;
5558         graph->size = graph->pos = 0;
5559         graph->commit = NULL;
5560         memset(graph->parents, 0, sizeof(*graph->parents));
5563 static void
5564 done_rev_graph(struct rev_graph *graph)
5566         if (graph_parent_is_merge(graph) &&
5567             graph->pos < graph->size - 1 &&
5568             graph->next->size == graph->size + graph->parents->size - 1) {
5569                 size_t i = graph->pos + graph->parents->size - 1;
5571                 graph->commit->graph_size = i * 2;
5572                 while (i < graph->next->size - 1) {
5573                         append_to_rev_graph(graph, ' ');
5574                         append_to_rev_graph(graph, '\\');
5575                         i++;
5576                 }
5577         }
5579         clear_rev_graph(graph);
5582 static void
5583 push_rev_graph(struct rev_graph *graph, const char *parent)
5585         int i;
5587         /* "Collapse" duplicate parents lines.
5588          *
5589          * FIXME: This needs to also update update the drawn graph but
5590          * for now it just serves as a method for pruning graph lines. */
5591         for (i = 0; i < graph->size; i++)
5592                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5593                         return;
5595         if (graph->size < SIZEOF_REVITEMS) {
5596                 string_copy_rev(graph->rev[graph->size++], parent);
5597         }
5600 static chtype
5601 get_rev_graph_symbol(struct rev_graph *graph)
5603         chtype symbol;
5605         if (graph->boundary)
5606                 symbol = REVGRAPH_BOUND;
5607         else if (graph->parents->size == 0)
5608                 symbol = REVGRAPH_INIT;
5609         else if (graph_parent_is_merge(graph))
5610                 symbol = REVGRAPH_MERGE;
5611         else if (graph->pos >= graph->size)
5612                 symbol = REVGRAPH_BRANCH;
5613         else
5614                 symbol = REVGRAPH_COMMIT;
5616         return symbol;
5619 static void
5620 draw_rev_graph(struct rev_graph *graph)
5622         struct rev_filler {
5623                 chtype separator, line;
5624         };
5625         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5626         static struct rev_filler fillers[] = {
5627                 { ' ',  '|' },
5628                 { '`',  '.' },
5629                 { '\'', ' ' },
5630                 { '/',  ' ' },
5631         };
5632         chtype symbol = get_rev_graph_symbol(graph);
5633         struct rev_filler *filler;
5634         size_t i;
5636         if (opt_line_graphics)
5637                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5639         filler = &fillers[DEFAULT];
5641         for (i = 0; i < graph->pos; i++) {
5642                 append_to_rev_graph(graph, filler->line);
5643                 if (graph_parent_is_merge(graph->prev) &&
5644                     graph->prev->pos == i)
5645                         filler = &fillers[RSHARP];
5647                 append_to_rev_graph(graph, filler->separator);
5648         }
5650         /* Place the symbol for this revision. */
5651         append_to_rev_graph(graph, symbol);
5653         if (graph->prev->size > graph->size)
5654                 filler = &fillers[RDIAG];
5655         else
5656                 filler = &fillers[DEFAULT];
5658         i++;
5660         for (; i < graph->size; i++) {
5661                 append_to_rev_graph(graph, filler->separator);
5662                 append_to_rev_graph(graph, filler->line);
5663                 if (graph_parent_is_merge(graph->prev) &&
5664                     i < graph->prev->pos + graph->parents->size)
5665                         filler = &fillers[RSHARP];
5666                 if (graph->prev->size > graph->size)
5667                         filler = &fillers[LDIAG];
5668         }
5670         if (graph->prev->size > graph->size) {
5671                 append_to_rev_graph(graph, filler->separator);
5672                 if (filler->line != ' ')
5673                         append_to_rev_graph(graph, filler->line);
5674         }
5677 /* Prepare the next rev graph */
5678 static void
5679 prepare_rev_graph(struct rev_graph *graph)
5681         size_t i;
5683         /* First, traverse all lines of revisions up to the active one. */
5684         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5685                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5686                         break;
5688                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5689         }
5691         /* Interleave the new revision parent(s). */
5692         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5693                 push_rev_graph(graph->next, graph->parents->rev[i]);
5695         /* Lastly, put any remaining revisions. */
5696         for (i = graph->pos + 1; i < graph->size; i++)
5697                 push_rev_graph(graph->next, graph->rev[i]);
5700 static void
5701 update_rev_graph(struct view *view, struct rev_graph *graph)
5703         /* If this is the finalizing update ... */
5704         if (graph->commit)
5705                 prepare_rev_graph(graph);
5707         /* Graph visualization needs a one rev look-ahead,
5708          * so the first update doesn't visualize anything. */
5709         if (!graph->prev->commit)
5710                 return;
5712         if (view->lines > 2)
5713                 view->line[view->lines - 3].dirty = 1;
5714         if (view->lines > 1)
5715                 view->line[view->lines - 2].dirty = 1;
5716         draw_rev_graph(graph->prev);
5717         done_rev_graph(graph->prev->prev);
5721 /*
5722  * Main view backend
5723  */
5725 static const char *main_argv[SIZEOF_ARG] = {
5726         "git", "log", "--no-color", "--pretty=raw", "--parents",
5727                       "--topo-order", "%(head)", NULL
5728 };
5730 static bool
5731 main_draw(struct view *view, struct line *line, unsigned int lineno)
5733         struct commit *commit = line->data;
5735         if (!*commit->author)
5736                 return FALSE;
5738         if (opt_date && draw_date(view, &commit->time))
5739                 return TRUE;
5741         if (opt_author && draw_author(view, commit->author))
5742                 return TRUE;
5744         if (opt_rev_graph && commit->graph_size &&
5745             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5746                 return TRUE;
5748         if (opt_show_refs && commit->refs) {
5749                 size_t i = 0;
5751                 do {
5752                         enum line_type type;
5754                         if (commit->refs[i]->head)
5755                                 type = LINE_MAIN_HEAD;
5756                         else if (commit->refs[i]->ltag)
5757                                 type = LINE_MAIN_LOCAL_TAG;
5758                         else if (commit->refs[i]->tag)
5759                                 type = LINE_MAIN_TAG;
5760                         else if (commit->refs[i]->tracked)
5761                                 type = LINE_MAIN_TRACKED;
5762                         else if (commit->refs[i]->remote)
5763                                 type = LINE_MAIN_REMOTE;
5764                         else
5765                                 type = LINE_MAIN_REF;
5767                         if (draw_text(view, type, "[", TRUE) ||
5768                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5769                             draw_text(view, type, "]", TRUE))
5770                                 return TRUE;
5772                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5773                                 return TRUE;
5774                 } while (commit->refs[i++]->next);
5775         }
5777         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5778         return TRUE;
5781 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5782 static bool
5783 main_read(struct view *view, char *line)
5785         static struct rev_graph *graph = graph_stacks;
5786         enum line_type type;
5787         struct commit *commit;
5789         if (!line) {
5790                 int i;
5792                 if (!view->lines && !view->parent)
5793                         die("No revisions match the given arguments.");
5794                 if (view->lines > 0) {
5795                         commit = view->line[view->lines - 1].data;
5796                         view->line[view->lines - 1].dirty = 1;
5797                         if (!*commit->author) {
5798                                 view->lines--;
5799                                 free(commit);
5800                                 graph->commit = NULL;
5801                         }
5802                 }
5803                 update_rev_graph(view, graph);
5805                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5806                         clear_rev_graph(&graph_stacks[i]);
5807                 return TRUE;
5808         }
5810         type = get_line_type(line);
5811         if (type == LINE_COMMIT) {
5812                 commit = calloc(1, sizeof(struct commit));
5813                 if (!commit)
5814                         return FALSE;
5816                 line += STRING_SIZE("commit ");
5817                 if (*line == '-') {
5818                         graph->boundary = 1;
5819                         line++;
5820                 }
5822                 string_copy_rev(commit->id, line);
5823                 commit->refs = get_refs(commit->id);
5824                 graph->commit = commit;
5825                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5827                 while ((line = strchr(line, ' '))) {
5828                         line++;
5829                         push_rev_graph(graph->parents, line);
5830                         commit->has_parents = TRUE;
5831                 }
5832                 return TRUE;
5833         }
5835         if (!view->lines)
5836                 return TRUE;
5837         commit = view->line[view->lines - 1].data;
5839         switch (type) {
5840         case LINE_PARENT:
5841                 if (commit->has_parents)
5842                         break;
5843                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5844                 break;
5846         case LINE_AUTHOR:
5847                 parse_author_line(line + STRING_SIZE("author "),
5848                                   commit->author, sizeof(commit->author),
5849                                   &commit->time);
5850                 update_rev_graph(view, graph);
5851                 graph = graph->next;
5852                 break;
5854         default:
5855                 /* Fill in the commit title if it has not already been set. */
5856                 if (commit->title[0])
5857                         break;
5859                 /* Require titles to start with a non-space character at the
5860                  * offset used by git log. */
5861                 if (strncmp(line, "    ", 4))
5862                         break;
5863                 line += 4;
5864                 /* Well, if the title starts with a whitespace character,
5865                  * try to be forgiving.  Otherwise we end up with no title. */
5866                 while (isspace(*line))
5867                         line++;
5868                 if (*line == '\0')
5869                         break;
5870                 /* FIXME: More graceful handling of titles; append "..." to
5871                  * shortened titles, etc. */
5873                 string_ncopy(commit->title, line, strlen(line));
5874                 view->line[view->lines - 1].dirty = 1;
5875         }
5877         return TRUE;
5880 static enum request
5881 main_request(struct view *view, enum request request, struct line *line)
5883         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5885         switch (request) {
5886         case REQ_ENTER:
5887                 open_view(view, REQ_VIEW_DIFF, flags);
5888                 break;
5889         case REQ_REFRESH:
5890                 load_refs();
5891                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5892                 break;
5893         default:
5894                 return request;
5895         }
5897         return REQ_NONE;
5900 static bool
5901 grep_refs(struct ref **refs, regex_t *regex)
5903         regmatch_t pmatch;
5904         size_t i = 0;
5906         if (!refs)
5907                 return FALSE;
5908         do {
5909                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5910                         return TRUE;
5911         } while (refs[i++]->next);
5913         return FALSE;
5916 static bool
5917 main_grep(struct view *view, struct line *line)
5919         struct commit *commit = line->data;
5920         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5921         char buf[DATE_COLS + 1];
5922         regmatch_t pmatch;
5924         for (state = S_TITLE; state < S_END; state++) {
5925                 char *text;
5927                 switch (state) {
5928                 case S_TITLE:   text = commit->title;   break;
5929                 case S_AUTHOR:
5930                         if (!opt_author)
5931                                 continue;
5932                         text = commit->author;
5933                         break;
5934                 case S_DATE:
5935                         if (!opt_date)
5936                                 continue;
5937                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5938                                 continue;
5939                         text = buf;
5940                         break;
5941                 case S_REFS:
5942                         if (!opt_show_refs)
5943                                 continue;
5944                         if (grep_refs(commit->refs, view->regex) == TRUE)
5945                                 return TRUE;
5946                         continue;
5947                 default:
5948                         return FALSE;
5949                 }
5951                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5952                         return TRUE;
5953         }
5955         return FALSE;
5958 static void
5959 main_select(struct view *view, struct line *line)
5961         struct commit *commit = line->data;
5963         string_copy_rev(view->ref, commit->id);
5964         string_copy_rev(ref_commit, view->ref);
5967 static struct view_ops main_ops = {
5968         "commit",
5969         main_argv,
5970         NULL,
5971         main_read,
5972         main_draw,
5973         main_request,
5974         main_grep,
5975         main_select,
5976 };
5979 /*
5980  * Unicode / UTF-8 handling
5981  *
5982  * NOTE: Much of the following code for dealing with unicode is derived from
5983  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5984  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5985  */
5987 /* I've (over)annotated a lot of code snippets because I am not entirely
5988  * confident that the approach taken by this small UTF-8 interface is correct.
5989  * --jonas */
5991 static inline int
5992 unicode_width(unsigned long c)
5994         if (c >= 0x1100 &&
5995            (c <= 0x115f                         /* Hangul Jamo */
5996             || c == 0x2329
5997             || c == 0x232a
5998             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5999                                                 /* CJK ... Yi */
6000             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6001             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6002             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6003             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6004             || (c >= 0xffe0  && c <= 0xffe6)
6005             || (c >= 0x20000 && c <= 0x2fffd)
6006             || (c >= 0x30000 && c <= 0x3fffd)))
6007                 return 2;
6009         if (c == '\t')
6010                 return opt_tab_size;
6012         return 1;
6015 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6016  * Illegal bytes are set one. */
6017 static const unsigned char utf8_bytes[256] = {
6018         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,
6019         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,
6020         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,
6021         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,
6022         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,
6023         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,
6024         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,
6025         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,
6026 };
6028 /* Decode UTF-8 multi-byte representation into a unicode character. */
6029 static inline unsigned long
6030 utf8_to_unicode(const char *string, size_t length)
6032         unsigned long unicode;
6034         switch (length) {
6035         case 1:
6036                 unicode  =   string[0];
6037                 break;
6038         case 2:
6039                 unicode  =  (string[0] & 0x1f) << 6;
6040                 unicode +=  (string[1] & 0x3f);
6041                 break;
6042         case 3:
6043                 unicode  =  (string[0] & 0x0f) << 12;
6044                 unicode += ((string[1] & 0x3f) << 6);
6045                 unicode +=  (string[2] & 0x3f);
6046                 break;
6047         case 4:
6048                 unicode  =  (string[0] & 0x0f) << 18;
6049                 unicode += ((string[1] & 0x3f) << 12);
6050                 unicode += ((string[2] & 0x3f) << 6);
6051                 unicode +=  (string[3] & 0x3f);
6052                 break;
6053         case 5:
6054                 unicode  =  (string[0] & 0x0f) << 24;
6055                 unicode += ((string[1] & 0x3f) << 18);
6056                 unicode += ((string[2] & 0x3f) << 12);
6057                 unicode += ((string[3] & 0x3f) << 6);
6058                 unicode +=  (string[4] & 0x3f);
6059                 break;
6060         case 6:
6061                 unicode  =  (string[0] & 0x01) << 30;
6062                 unicode += ((string[1] & 0x3f) << 24);
6063                 unicode += ((string[2] & 0x3f) << 18);
6064                 unicode += ((string[3] & 0x3f) << 12);
6065                 unicode += ((string[4] & 0x3f) << 6);
6066                 unicode +=  (string[5] & 0x3f);
6067                 break;
6068         default:
6069                 die("Invalid unicode length");
6070         }
6072         /* Invalid characters could return the special 0xfffd value but NUL
6073          * should be just as good. */
6074         return unicode > 0xffff ? 0 : unicode;
6077 /* Calculates how much of string can be shown within the given maximum width
6078  * and sets trimmed parameter to non-zero value if all of string could not be
6079  * shown. If the reserve flag is TRUE, it will reserve at least one
6080  * trailing character, which can be useful when drawing a delimiter.
6081  *
6082  * Returns the number of bytes to output from string to satisfy max_width. */
6083 static size_t
6084 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6086         const char *start = string;
6087         const char *end = strchr(string, '\0');
6088         unsigned char last_bytes = 0;
6089         size_t last_ucwidth = 0;
6091         *width = 0;
6092         *trimmed = 0;
6094         while (string < end) {
6095                 int c = *(unsigned char *) string;
6096                 unsigned char bytes = utf8_bytes[c];
6097                 size_t ucwidth;
6098                 unsigned long unicode;
6100                 if (string + bytes > end)
6101                         break;
6103                 /* Change representation to figure out whether
6104                  * it is a single- or double-width character. */
6106                 unicode = utf8_to_unicode(string, bytes);
6107                 /* FIXME: Graceful handling of invalid unicode character. */
6108                 if (!unicode)
6109                         break;
6111                 ucwidth = unicode_width(unicode);
6112                 *width  += ucwidth;
6113                 if (*width > max_width) {
6114                         *trimmed = 1;
6115                         *width -= ucwidth;
6116                         if (reserve && *width == max_width) {
6117                                 string -= last_bytes;
6118                                 *width -= last_ucwidth;
6119                         }
6120                         break;
6121                 }
6123                 string  += bytes;
6124                 last_bytes = bytes;
6125                 last_ucwidth = ucwidth;
6126         }
6128         return string - start;
6132 /*
6133  * Status management
6134  */
6136 /* Whether or not the curses interface has been initialized. */
6137 static bool cursed = FALSE;
6139 /* The status window is used for polling keystrokes. */
6140 static WINDOW *status_win;
6142 static bool status_empty = FALSE;
6144 /* Update status and title window. */
6145 static void
6146 report(const char *msg, ...)
6148         struct view *view = display[current_view];
6150         if (input_mode)
6151                 return;
6153         if (!view) {
6154                 char buf[SIZEOF_STR];
6155                 va_list args;
6157                 va_start(args, msg);
6158                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6159                         buf[sizeof(buf) - 1] = 0;
6160                         buf[sizeof(buf) - 2] = '.';
6161                         buf[sizeof(buf) - 3] = '.';
6162                         buf[sizeof(buf) - 4] = '.';
6163                 }
6164                 va_end(args);
6165                 die("%s", buf);
6166         }
6168         if (!status_empty || *msg) {
6169                 va_list args;
6171                 va_start(args, msg);
6173                 wmove(status_win, 0, 0);
6174                 if (*msg) {
6175                         vwprintw(status_win, msg, args);
6176                         status_empty = FALSE;
6177                 } else {
6178                         status_empty = TRUE;
6179                 }
6180                 wclrtoeol(status_win);
6181                 wrefresh(status_win);
6183                 va_end(args);
6184         }
6186         update_view_title(view);
6187         update_display_cursor(view);
6190 /* Controls when nodelay should be in effect when polling user input. */
6191 static void
6192 set_nonblocking_input(bool loading)
6194         static unsigned int loading_views;
6196         if ((loading == FALSE && loading_views-- == 1) ||
6197             (loading == TRUE  && loading_views++ == 0))
6198                 nodelay(status_win, loading);
6201 static void
6202 init_display(void)
6204         int x, y;
6206         /* Initialize the curses library */
6207         if (isatty(STDIN_FILENO)) {
6208                 cursed = !!initscr();
6209                 opt_tty = stdin;
6210         } else {
6211                 /* Leave stdin and stdout alone when acting as a pager. */
6212                 opt_tty = fopen("/dev/tty", "r+");
6213                 if (!opt_tty)
6214                         die("Failed to open /dev/tty");
6215                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6216         }
6218         if (!cursed)
6219                 die("Failed to initialize curses");
6221         nonl();         /* Tell curses not to do NL->CR/NL on output */
6222         cbreak();       /* Take input chars one at a time, no wait for \n */
6223         noecho();       /* Don't echo input */
6224         leaveok(stdscr, TRUE);
6226         if (has_colors())
6227                 init_colors();
6229         getmaxyx(stdscr, y, x);
6230         status_win = newwin(1, 0, y - 1, 0);
6231         if (!status_win)
6232                 die("Failed to create status window");
6234         /* Enable keyboard mapping */
6235         keypad(status_win, TRUE);
6236         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6238         TABSIZE = opt_tab_size;
6239         if (opt_line_graphics) {
6240                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6241         }
6244 static int
6245 get_input(bool prompting)
6247         struct view *view;
6248         int i, key;
6250         if (prompting)
6251                 input_mode = TRUE;
6253         while (true) {
6254                 foreach_view (view, i)
6255                         update_view(view);
6257                 /* Refresh, accept single keystroke of input */
6258                 key = wgetch(status_win);
6260                 /* wgetch() with nodelay() enabled returns ERR when
6261                  * there's no input. */
6262                 if (key == ERR) {
6263                         doupdate();
6265                 } else if (key == KEY_RESIZE) {
6266                         int height, width;
6268                         getmaxyx(stdscr, height, width);
6270                         /* Resize the status view first so the cursor is
6271                          * placed properly. */
6272                         wresize(status_win, 1, width);
6273                         mvwin(status_win, height - 1, 0);
6274                         wrefresh(status_win);
6275                         resize_display();
6276                         redraw_display(TRUE);
6278                 } else {
6279                         input_mode = FALSE;
6280                         return key;
6281                 }
6282         }
6285 static char *
6286 prompt_input(const char *prompt, input_handler handler, void *data)
6288         enum input_status status = INPUT_OK;
6289         static char buf[SIZEOF_STR];
6290         size_t pos = 0;
6292         buf[pos] = 0;
6294         while (status == INPUT_OK || status == INPUT_SKIP) {
6295                 int key;
6297                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6298                 wclrtoeol(status_win);
6300                 key = get_input(TRUE);
6301                 switch (key) {
6302                 case KEY_RETURN:
6303                 case KEY_ENTER:
6304                 case '\n':
6305                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6306                         break;
6308                 case KEY_BACKSPACE:
6309                         if (pos > 0)
6310                                 buf[--pos] = 0;
6311                         else
6312                                 status = INPUT_CANCEL;
6313                         break;
6315                 case KEY_ESC:
6316                         status = INPUT_CANCEL;
6317                         break;
6319                 default:
6320                         if (pos >= sizeof(buf)) {
6321                                 report("Input string too long");
6322                                 return NULL;
6323                         }
6325                         status = handler(data, buf, key);
6326                         if (status == INPUT_OK)
6327                                 buf[pos++] = (char) key;
6328                 }
6329         }
6331         /* Clear the status window */
6332         status_empty = FALSE;
6333         report("");
6335         if (status == INPUT_CANCEL)
6336                 return NULL;
6338         buf[pos++] = 0;
6340         return buf;
6343 static enum input_status
6344 prompt_yesno_handler(void *data, char *buf, int c)
6346         if (c == 'y' || c == 'Y')
6347                 return INPUT_STOP;
6348         if (c == 'n' || c == 'N')
6349                 return INPUT_CANCEL;
6350         return INPUT_SKIP;
6353 static bool
6354 prompt_yesno(const char *prompt)
6356         char prompt2[SIZEOF_STR];
6358         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6359                 return FALSE;
6361         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6364 static enum input_status
6365 read_prompt_handler(void *data, char *buf, int c)
6367         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6370 static char *
6371 read_prompt(const char *prompt)
6373         return prompt_input(prompt, read_prompt_handler, NULL);
6376 /*
6377  * Repository properties
6378  */
6380 static int
6381 git_properties(const char **argv, const char *separators,
6382                int (*read_property)(char *, size_t, char *, size_t))
6384         struct io io = {};
6386         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6387                 return read_properties(&io, separators, read_property);
6388         return ERR;
6391 static struct ref *refs = NULL;
6392 static size_t refs_alloc = 0;
6393 static size_t refs_size = 0;
6395 /* Id <-> ref store */
6396 static struct ref ***id_refs = NULL;
6397 static size_t id_refs_alloc = 0;
6398 static size_t id_refs_size = 0;
6400 static int
6401 compare_refs(const void *ref1_, const void *ref2_)
6403         const struct ref *ref1 = *(const struct ref **)ref1_;
6404         const struct ref *ref2 = *(const struct ref **)ref2_;
6406         if (ref1->tag != ref2->tag)
6407                 return ref2->tag - ref1->tag;
6408         if (ref1->ltag != ref2->ltag)
6409                 return ref2->ltag - ref2->ltag;
6410         if (ref1->head != ref2->head)
6411                 return ref2->head - ref1->head;
6412         if (ref1->tracked != ref2->tracked)
6413                 return ref2->tracked - ref1->tracked;
6414         if (ref1->remote != ref2->remote)
6415                 return ref2->remote - ref1->remote;
6416         return strcmp(ref1->name, ref2->name);
6419 static struct ref **
6420 get_refs(const char *id)
6422         struct ref ***tmp_id_refs;
6423         struct ref **ref_list = NULL;
6424         size_t ref_list_alloc = 0;
6425         size_t ref_list_size = 0;
6426         size_t i;
6428         for (i = 0; i < id_refs_size; i++)
6429                 if (!strcmp(id, id_refs[i][0]->id))
6430                         return id_refs[i];
6432         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6433                                     sizeof(*id_refs));
6434         if (!tmp_id_refs)
6435                 return NULL;
6437         id_refs = tmp_id_refs;
6439         for (i = 0; i < refs_size; i++) {
6440                 struct ref **tmp;
6442                 if (strcmp(id, refs[i].id))
6443                         continue;
6445                 tmp = realloc_items(ref_list, &ref_list_alloc,
6446                                     ref_list_size + 1, sizeof(*ref_list));
6447                 if (!tmp) {
6448                         if (ref_list)
6449                                 free(ref_list);
6450                         return NULL;
6451                 }
6453                 ref_list = tmp;
6454                 ref_list[ref_list_size] = &refs[i];
6455                 /* XXX: The properties of the commit chains ensures that we can
6456                  * safely modify the shared ref. The repo references will
6457                  * always be similar for the same id. */
6458                 ref_list[ref_list_size]->next = 1;
6460                 ref_list_size++;
6461         }
6463         if (ref_list) {
6464                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6465                 ref_list[ref_list_size - 1]->next = 0;
6466                 id_refs[id_refs_size++] = ref_list;
6467         }
6469         return ref_list;
6472 static int
6473 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6475         struct ref *ref;
6476         bool tag = FALSE;
6477         bool ltag = FALSE;
6478         bool remote = FALSE;
6479         bool tracked = FALSE;
6480         bool check_replace = FALSE;
6481         bool head = FALSE;
6483         if (!prefixcmp(name, "refs/tags/")) {
6484                 if (!suffixcmp(name, namelen, "^{}")) {
6485                         namelen -= 3;
6486                         name[namelen] = 0;
6487                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6488                                 check_replace = TRUE;
6489                 } else {
6490                         ltag = TRUE;
6491                 }
6493                 tag = TRUE;
6494                 namelen -= STRING_SIZE("refs/tags/");
6495                 name    += STRING_SIZE("refs/tags/");
6497         } else if (!prefixcmp(name, "refs/remotes/")) {
6498                 remote = TRUE;
6499                 namelen -= STRING_SIZE("refs/remotes/");
6500                 name    += STRING_SIZE("refs/remotes/");
6501                 tracked  = !strcmp(opt_remote, name);
6503         } else if (!prefixcmp(name, "refs/heads/")) {
6504                 namelen -= STRING_SIZE("refs/heads/");
6505                 name    += STRING_SIZE("refs/heads/");
6506                 head     = !strncmp(opt_head, name, namelen);
6508         } else if (!strcmp(name, "HEAD")) {
6509                 string_ncopy(opt_head_rev, id, idlen);
6510                 return OK;
6511         }
6513         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6514                 /* it's an annotated tag, replace the previous sha1 with the
6515                  * resolved commit id; relies on the fact git-ls-remote lists
6516                  * the commit id of an annotated tag right before the commit id
6517                  * it points to. */
6518                 refs[refs_size - 1].ltag = ltag;
6519                 string_copy_rev(refs[refs_size - 1].id, id);
6521                 return OK;
6522         }
6523         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6524         if (!refs)
6525                 return ERR;
6527         ref = &refs[refs_size++];
6528         ref->name = malloc(namelen + 1);
6529         if (!ref->name)
6530                 return ERR;
6532         strncpy(ref->name, name, namelen);
6533         ref->name[namelen] = 0;
6534         ref->head = head;
6535         ref->tag = tag;
6536         ref->ltag = ltag;
6537         ref->remote = remote;
6538         ref->tracked = tracked;
6539         string_copy_rev(ref->id, id);
6541         return OK;
6544 static int
6545 load_refs(void)
6547         static const char *ls_remote_argv[SIZEOF_ARG] = {
6548                 "git", "ls-remote", ".", NULL
6549         };
6550         static bool init = FALSE;
6552         if (!init) {
6553                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6554                 init = TRUE;
6555         }
6557         if (!*opt_git_dir)
6558                 return OK;
6560         while (refs_size > 0)
6561                 free(refs[--refs_size].name);
6562         while (id_refs_size > 0)
6563                 free(id_refs[--id_refs_size]);
6565         return git_properties(ls_remote_argv, "\t", read_ref);
6568 static int
6569 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6571         if (!strcmp(name, "i18n.commitencoding"))
6572                 string_ncopy(opt_encoding, value, valuelen);
6574         if (!strcmp(name, "core.editor"))
6575                 string_ncopy(opt_editor, value, valuelen);
6577         /* branch.<head>.remote */
6578         if (*opt_head &&
6579             !strncmp(name, "branch.", 7) &&
6580             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6581             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6582                 string_ncopy(opt_remote, value, valuelen);
6584         if (*opt_head && *opt_remote &&
6585             !strncmp(name, "branch.", 7) &&
6586             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6587             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6588                 size_t from = strlen(opt_remote);
6590                 if (!prefixcmp(value, "refs/heads/")) {
6591                         value += STRING_SIZE("refs/heads/");
6592                         valuelen -= STRING_SIZE("refs/heads/");
6593                 }
6595                 if (!string_format_from(opt_remote, &from, "/%s", value))
6596                         opt_remote[0] = 0;
6597         }
6599         return OK;
6602 static int
6603 load_git_config(void)
6605         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6607         return git_properties(config_list_argv, "=", read_repo_config_option);
6610 static int
6611 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6613         if (!opt_git_dir[0]) {
6614                 string_ncopy(opt_git_dir, name, namelen);
6616         } else if (opt_is_inside_work_tree == -1) {
6617                 /* This can be 3 different values depending on the
6618                  * version of git being used. If git-rev-parse does not
6619                  * understand --is-inside-work-tree it will simply echo
6620                  * the option else either "true" or "false" is printed.
6621                  * Default to true for the unknown case. */
6622                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6624         } else if (*name == '.') {
6625                 string_ncopy(opt_cdup, name, namelen);
6627         } else {
6628                 string_ncopy(opt_prefix, name, namelen);
6629         }
6631         return OK;
6634 static int
6635 load_repo_info(void)
6637         const char *head_argv[] = {
6638                 "git", "symbolic-ref", "HEAD", NULL
6639         };
6640         const char *rev_parse_argv[] = {
6641                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6642                         "--show-cdup", "--show-prefix", NULL
6643         };
6645         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6646                 chomp_string(opt_head);
6647                 if (!prefixcmp(opt_head, "refs/heads/")) {
6648                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6650                         memmove(opt_head, offset, strlen(offset) + 1);
6651                 }
6652         }
6654         return git_properties(rev_parse_argv, "=", read_repo_info);
6657 static int
6658 read_properties(struct io *io, const char *separators,
6659                 int (*read_property)(char *, size_t, char *, size_t))
6661         char *name;
6662         int state = OK;
6664         if (!start_io(io))
6665                 return ERR;
6667         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6668                 char *value;
6669                 size_t namelen;
6670                 size_t valuelen;
6672                 name = chomp_string(name);
6673                 namelen = strcspn(name, separators);
6675                 if (name[namelen]) {
6676                         name[namelen] = 0;
6677                         value = chomp_string(name + namelen + 1);
6678                         valuelen = strlen(value);
6680                 } else {
6681                         value = "";
6682                         valuelen = 0;
6683                 }
6685                 state = read_property(name, namelen, value, valuelen);
6686         }
6688         if (state != ERR && io_error(io))
6689                 state = ERR;
6690         done_io(io);
6692         return state;
6696 /*
6697  * Main
6698  */
6700 static void __NORETURN
6701 quit(int sig)
6703         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6704         if (cursed)
6705                 endwin();
6706         exit(0);
6709 static void __NORETURN
6710 die(const char *err, ...)
6712         va_list args;
6714         endwin();
6716         va_start(args, err);
6717         fputs("tig: ", stderr);
6718         vfprintf(stderr, err, args);
6719         fputs("\n", stderr);
6720         va_end(args);
6722         exit(1);
6725 static void
6726 warn(const char *msg, ...)
6728         va_list args;
6730         va_start(args, msg);
6731         fputs("tig warning: ", stderr);
6732         vfprintf(stderr, msg, args);
6733         fputs("\n", stderr);
6734         va_end(args);
6737 int
6738 main(int argc, const char *argv[])
6740         const char **run_argv = NULL;
6741         struct view *view;
6742         enum request request;
6743         size_t i;
6745         signal(SIGINT, quit);
6747         if (setlocale(LC_ALL, "")) {
6748                 char *codeset = nl_langinfo(CODESET);
6750                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6751         }
6753         if (load_repo_info() == ERR)
6754                 die("Failed to load repo info.");
6756         if (load_options() == ERR)
6757                 die("Failed to load user config.");
6759         if (load_git_config() == ERR)
6760                 die("Failed to load repo config.");
6762         request = parse_options(argc, argv, &run_argv);
6763         if (request == REQ_NONE)
6764                 return 0;
6766         /* Require a git repository unless when running in pager mode. */
6767         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6768                 die("Not a git repository");
6770         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6771                 opt_utf8 = FALSE;
6773         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6774                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6775                 if (opt_iconv == ICONV_NONE)
6776                         die("Failed to initialize character set conversion");
6777         }
6779         if (load_refs() == ERR)
6780                 die("Failed to load refs.");
6782         foreach_view (view, i)
6783                 argv_from_env(view->ops->argv, view->cmd_env);
6785         init_display();
6787         if (request == REQ_VIEW_PAGER || run_argv) {
6788                 if (request == REQ_VIEW_PAGER)
6789                         io_open(&VIEW(request)->io, "");
6790                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6791                         die("Failed to format arguments");
6792                 open_view(NULL, request, OPEN_PREPARED);
6793                 request = REQ_NONE;
6794         }
6796         while (view_driver(display[current_view], request)) {
6797                 int key = get_input(FALSE);
6799                 view = display[current_view];
6800                 request = get_keybinding(view->keymap, key);
6802                 /* Some low-level request handling. This keeps access to
6803                  * status_win restricted. */
6804                 switch (request) {
6805                 case REQ_PROMPT:
6806                 {
6807                         char *cmd = read_prompt(":");
6809                         if (cmd) {
6810                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6811                                 const char *argv[SIZEOF_ARG] = { "git" };
6812                                 int argc = 1;
6814                                 /* When running random commands, initially show the
6815                                  * command in the title. However, it maybe later be
6816                                  * overwritten if a commit line is selected. */
6817                                 string_ncopy(next->ref, cmd, strlen(cmd));
6819                                 if (!argv_from_string(argv, &argc, cmd)) {
6820                                         report("Too many arguments");
6821                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6822                                         report("Failed to format command");
6823                                 } else {
6824                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6825                                 }
6826                         }
6828                         request = REQ_NONE;
6829                         break;
6830                 }
6831                 case REQ_SEARCH:
6832                 case REQ_SEARCH_BACK:
6833                 {
6834                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6835                         char *search = read_prompt(prompt);
6837                         if (search)
6838                                 string_ncopy(opt_search, search, strlen(search));
6839                         else
6840                                 request = REQ_NONE;
6841                         break;
6842                 }
6843                 default:
6844                         break;
6845                 }
6846         }
6848         quit(0);
6850         return 0;