Code

Remove preallocation of view lines in help_open
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
173 enum input_status {
174         INPUT_OK,
175         INPUT_SKIP,
176         INPUT_STOP,
177         INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216         int namelen;
218         while (isspace(*name))
219                 name++;
221         namelen = strlen(name) - 1;
222         while (namelen > 0 && isspace(name[namelen]))
223                 name[namelen--] = 0;
225         return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231         va_list args;
232         size_t pos = bufpos ? *bufpos : 0;
234         va_start(args, fmt);
235         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236         va_end(args);
238         if (bufpos)
239                 *bufpos = pos;
241         return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245         string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248         string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253         size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257         /* Diff-Header == DIFF_HEADER */
258         for (i = 0; i < len; i++) {
259                 if (toupper(str1[i]) == toupper(str2[i]))
260                         continue;
262                 if (string_enum_sep(str1[i]) &&
263                     string_enum_sep(str2[i]))
264                         continue;
266                 return str1[i] - str2[i];
267         }
269         return 0;
272 #define prefixcmp(str1, str2) \
273         strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278         size_t len = slen >= 0 ? slen : strlen(str);
279         size_t suffixlen = strlen(suffix);
281         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288         int valuelen;
290         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291                 bool advance = cmd[valuelen] != 0;
293                 cmd[valuelen] = 0;
294                 argv[(*argc)++] = chomp_string(cmd);
295                 cmd += valuelen + advance;
296         }
298         if (*argc < SIZEOF_ARG)
299                 argv[*argc] = NULL;
300         return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306         char *env = argv ? getenv(name) : NULL;
307         int argc = 0;
309         if (env && *env)
310                 env = strdup(env);
311         if (env && !argv_from_string(argv, &argc, env))
312                 die("Too many arguments in the `%s` environment variable", name);
316 /*
317  * Executing external commands.
318  */
320 enum io_type {
321         IO_FD,                  /* File descriptor based IO. */
322         IO_BG,                  /* Execute command in the background. */
323         IO_FG,                  /* Execute command with same std{in,out,err}. */
324         IO_RD,                  /* Read only fork+exec IO. */
325         IO_WR,                  /* Write only fork+exec IO. */
326         IO_AP,                  /* Append fork+exec output to file. */
327 };
329 struct io {
330         enum io_type type;      /* The requested type of pipe. */
331         const char *dir;        /* Directory from which to execute. */
332         pid_t pid;              /* Pipe for reading or writing. */
333         int pipe;               /* Pipe end for reading or writing. */
334         int error;              /* Error status. */
335         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
336         char *buf;              /* Read buffer. */
337         size_t bufalloc;        /* Allocated buffer size. */
338         size_t bufsize;         /* Buffer content size. */
339         char *bufpos;           /* Current buffer position. */
340         unsigned int eof:1;     /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
346         io->pipe = -1;
347         io->pid = 0;
348         io->buf = io->bufpos = NULL;
349         io->bufalloc = io->bufsize = 0;
350         io->error = 0;
351         io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357         reset_io(io);
358         io->type = type;
359         io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364                 enum format_flags flags)
366         init_io(io, dir, IO_RD);
367         return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373         init_io(io, NULL, IO_FD);
374         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375         return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387         pid_t pid = io->pid;
389         if (io->pipe != -1)
390                 close(io->pipe);
391         free(io->buf);
392         reset_io(io);
394         while (pid > 0) {
395                 int status;
396                 pid_t waiting = waitpid(pid, &status, 0);
398                 if (waiting < 0) {
399                         if (errno == EINTR)
400                                 continue;
401                         report("waitpid failed (%s)", strerror(errno));
402                         return FALSE;
403                 }
405                 return waiting == pid &&
406                        !WIFSIGNALED(status) &&
407                        WIFEXITED(status) &&
408                        !WEXITSTATUS(status);
409         }
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         int pipefds[2] = { -1, -1 };
419         if (io->type == IO_FD)
420                 return TRUE;
422         if ((io->type == IO_RD || io->type == IO_WR) &&
423             pipe(pipefds) < 0)
424                 return FALSE;
425         else if (io->type == IO_AP)
426                 pipefds[1] = io->pipe;
428         if ((io->pid = fork())) {
429                 if (pipefds[!(io->type == IO_WR)] != -1)
430                         close(pipefds[!(io->type == IO_WR)]);
431                 if (io->pid != -1) {
432                         io->pipe = pipefds[!!(io->type == IO_WR)];
433                         return TRUE;
434                 }
436         } else {
437                 if (io->type != IO_FG) {
438                         int devnull = open("/dev/null", O_RDWR);
439                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
440                         int writefd = (io->type == IO_RD || io->type == IO_AP)
441                                                         ? pipefds[1] : devnull;
443                         dup2(readfd,  STDIN_FILENO);
444                         dup2(writefd, STDOUT_FILENO);
445                         dup2(devnull, STDERR_FILENO);
447                         close(devnull);
448                         if (pipefds[0] != -1)
449                                 close(pipefds[0]);
450                         if (pipefds[1] != -1)
451                                 close(pipefds[1]);
452                 }
454                 if (io->dir && *io->dir && chdir(io->dir) == -1)
455                         die("Failed to change directory: %s", strerror(errno));
457                 execvp(io->argv[0], (char *const*) io->argv);
458                 die("Failed to execute program: %s", strerror(errno));
459         }
461         if (pipefds[!!(io->type == IO_WR)] != -1)
462                 close(pipefds[!!(io->type == IO_WR)]);
463         return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469         init_io(io, dir, type);
470         if (!format_argv(io->argv, argv, FORMAT_NONE))
471                 return FALSE;
472         return start_io(io);
475 static int
476 run_io_do(struct io *io)
478         return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484         struct io io = {};
486         init_io(&io, NULL, IO_BG);
487         if (!format_argv(io.argv, argv, FORMAT_NONE))
488                 return FALSE;
489         return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495         struct io io = {};
497         init_io(&io, dir, IO_FG);
498         if (!format_argv(io.argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506         struct io io = {};
508         init_io(&io, NULL, IO_AP);
509         io.pipe = fd;
510         if (format_argv(io.argv, argv, flags))
511                 return run_io_do(&io);
512         close(fd);
513         return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519         return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525         return io->eof;
528 static int
529 io_error(struct io *io)
531         return io->error;
534 static bool
535 io_strerror(struct io *io)
537         return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543         struct timeval tv = { 0, 500 };
544         fd_set fds;
546         FD_ZERO(&fds);
547         FD_SET(io->pipe, &fds);
549         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555         do {
556                 ssize_t readsize = read(io->pipe, buf, bufsize);
558                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559                         continue;
560                 else if (readsize == -1)
561                         io->error = errno;
562                 else if (readsize == 0)
563                         io->eof = 1;
564                 return readsize;
565         } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571         char *eol;
572         ssize_t readsize;
574         if (!io->buf) {
575                 io->buf = io->bufpos = malloc(BUFSIZ);
576                 if (!io->buf)
577                         return NULL;
578                 io->bufalloc = BUFSIZ;
579                 io->bufsize = 0;
580         }
582         while (TRUE) {
583                 if (io->bufsize > 0) {
584                         eol = memchr(io->bufpos, c, io->bufsize);
585                         if (eol) {
586                                 char *line = io->bufpos;
588                                 *eol = 0;
589                                 io->bufpos = eol + 1;
590                                 io->bufsize -= io->bufpos - line;
591                                 return line;
592                         }
593                 }
595                 if (io_eof(io)) {
596                         if (io->bufsize) {
597                                 io->bufpos[io->bufsize] = 0;
598                                 io->bufsize = 0;
599                                 return io->bufpos;
600                         }
601                         return NULL;
602                 }
604                 if (!can_read)
605                         return NULL;
607                 if (io->bufsize > 0 && io->bufpos > io->buf)
608                         memmove(io->buf, io->bufpos, io->bufsize);
610                 io->bufpos = io->buf;
611                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612                 if (io_error(io))
613                         return NULL;
614                 io->bufsize += readsize;
615         }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621         size_t written = 0;
623         while (!io_error(io) && written < bufsize) {
624                 ssize_t size;
626                 size = write(io->pipe, buf + written, bufsize - written);
627                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628                         continue;
629                 else if (size == -1)
630                         io->error = errno;
631                 else
632                         written += size;
633         }
635         return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641         struct io io = {};
642         bool error;
644         if (!run_io_rd(&io, argv, FORMAT_NONE))
645                 return FALSE;
647         io.buf = io.bufpos = buf;
648         io.bufalloc = bufsize;
649         error = !io_get(&io, '\n', TRUE) && io_error(&io);
650         io.buf = NULL;
652         return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
657 /*
658  * User requests
659  */
661 #define REQ_INFO \
662         /* XXX: Keep the view request first and in sync with views[]. */ \
663         REQ_GROUP("View switching") \
664         REQ_(VIEW_MAIN,         "Show main view"), \
665         REQ_(VIEW_DIFF,         "Show diff view"), \
666         REQ_(VIEW_LOG,          "Show log view"), \
667         REQ_(VIEW_TREE,         "Show tree view"), \
668         REQ_(VIEW_BLOB,         "Show blob view"), \
669         REQ_(VIEW_BLAME,        "Show blame view"), \
670         REQ_(VIEW_HELP,         "Show help page"), \
671         REQ_(VIEW_PAGER,        "Show pager view"), \
672         REQ_(VIEW_STATUS,       "Show status view"), \
673         REQ_(VIEW_STAGE,        "Show stage view"), \
674         \
675         REQ_GROUP("View manipulation") \
676         REQ_(ENTER,             "Enter current line and scroll"), \
677         REQ_(NEXT,              "Move to next"), \
678         REQ_(PREVIOUS,          "Move to previous"), \
679         REQ_(PARENT,            "Move to parent"), \
680         REQ_(VIEW_NEXT,         "Move focus to next view"), \
681         REQ_(REFRESH,           "Reload and refresh"), \
682         REQ_(MAXIMIZE,          "Maximize the current view"), \
683         REQ_(VIEW_CLOSE,        "Close the current view"), \
684         REQ_(QUIT,              "Close all views and quit"), \
685         \
686         REQ_GROUP("View specific requests") \
687         REQ_(STATUS_UPDATE,     "Update file status"), \
688         REQ_(STATUS_REVERT,     "Revert file changes"), \
689         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
690         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
691         \
692         REQ_GROUP("Cursor navigation") \
693         REQ_(MOVE_UP,           "Move cursor one line up"), \
694         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
695         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
696         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
697         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
698         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
699         \
700         REQ_GROUP("Scrolling") \
701         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
702         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
703         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
704         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
705         \
706         REQ_GROUP("Searching") \
707         REQ_(SEARCH,            "Search the view"), \
708         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
709         REQ_(FIND_NEXT,         "Find next search match"), \
710         REQ_(FIND_PREV,         "Find previous search match"), \
711         \
712         REQ_GROUP("Option manipulation") \
713         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
714         REQ_(TOGGLE_DATE,       "Toggle date display"), \
715         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
716         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
717         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
718         \
719         REQ_GROUP("Misc") \
720         REQ_(PROMPT,            "Bring up the prompt"), \
721         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
722         REQ_(SHOW_VERSION,      "Show version information"), \
723         REQ_(STOP_LOADING,      "Stop all loading views"), \
724         REQ_(EDIT,              "Open in editor"), \
725         REQ_(NONE,              "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733         /* Offset all requests to avoid conflicts with ncurses getch values. */
734         REQ_OFFSET = KEY_MAX + 1,
735         REQ_INFO
737 #undef  REQ_GROUP
738 #undef  REQ_
739 };
741 struct request_info {
742         enum request request;
743         const char *name;
744         int namelen;
745         const char *help;
746 };
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751         REQ_INFO
752 #undef  REQ_GROUP
753 #undef  REQ_
754 };
756 static enum request
757 get_request(const char *name)
759         int namelen = strlen(name);
760         int i;
762         for (i = 0; i < ARRAY_SIZE(req_info); i++)
763                 if (req_info[i].namelen == namelen &&
764                     !string_enum_compare(req_info[i].name, name, namelen))
765                         return req_info[i].request;
767         return REQ_NONE;
771 /*
772  * Options
773  */
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig        [options] [revs] [--] [paths]\n"
779 "   or: tig show   [options] [revs] [--] [paths]\n"
780 "   or: tig blame  [rev] path\n"
781 "   or: tig status\n"
782 "   or: tig <      [git command output]\n"
783 "\n"
784 "Options:\n"
785 "  -v, --version   Show version and exit\n"
786 "  -h, --help      Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date                    = TRUE;
790 static bool opt_author                  = TRUE;
791 static bool opt_line_number             = FALSE;
792 static bool opt_line_graphics           = TRUE;
793 static bool opt_rev_graph               = FALSE;
794 static bool opt_show_refs               = TRUE;
795 static int opt_num_interval             = NUMBER_INTERVAL;
796 static int opt_tab_size                 = TAB_SIZE;
797 static int opt_author_cols              = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR]        = "";
799 static char opt_file[SIZEOF_STR]        = "";
800 static char opt_ref[SIZEOF_REF]         = "";
801 static char opt_head[SIZEOF_REF]        = "";
802 static char opt_head_rev[SIZEOF_REV]    = "";
803 static char opt_remote[SIZEOF_REF]      = "";
804 static char opt_encoding[20]            = "UTF-8";
805 static bool opt_utf8                    = TRUE;
806 static char opt_codeset[20]             = "UTF-8";
807 static iconv_t opt_iconv                = ICONV_NONE;
808 static char opt_search[SIZEOF_STR]      = "";
809 static char opt_cdup[SIZEOF_STR]        = "";
810 static char opt_prefix[SIZEOF_STR]      = "";
811 static char opt_git_dir[SIZEOF_STR]     = "";
812 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR]      = "";
814 static FILE *opt_tty                    = NULL;
816 #define is_initial_commit()     (!*opt_head_rev)
817 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822         enum request request = REQ_VIEW_MAIN;
823         const char *subcommand;
824         bool seen_dashdash = FALSE;
825         /* XXX: This is vulnerable to the user overriding options
826          * required for the main view parser. */
827         static const char *custom_argv[SIZEOF_ARG] = {
828                 "git", "log", "--no-color", "--pretty=raw", "--parents",
829                         "--topo-order", NULL
830         };
831         int i, j = 6;
833         if (!isatty(STDIN_FILENO))
834                 return REQ_VIEW_PAGER;
836         if (argc <= 1)
837                 return REQ_VIEW_MAIN;
839         subcommand = argv[1];
840         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841                 if (!strcmp(subcommand, "-S"))
842                         warn("`-S' has been deprecated; use `tig status' instead");
843                 if (argc > 2)
844                         warn("ignoring arguments after `%s'", subcommand);
845                 return REQ_VIEW_STATUS;
847         } else if (!strcmp(subcommand, "blame")) {
848                 if (argc <= 2 || argc > 4)
849                         die("invalid number of options to blame\n\n%s", usage);
851                 i = 2;
852                 if (argc == 4) {
853                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854                         i++;
855                 }
857                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858                 return REQ_VIEW_BLAME;
860         } else if (!strcmp(subcommand, "show")) {
861                 request = REQ_VIEW_DIFF;
863         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865                 warn("`tig %s' has been deprecated", subcommand);
867         } else {
868                 subcommand = NULL;
869         }
871         if (subcommand) {
872                 custom_argv[1] = subcommand;
873                 j = 2;
874         }
876         for (i = 1 + !!subcommand; i < argc; i++) {
877                 const char *opt = argv[i];
879                 if (seen_dashdash || !strcmp(opt, "--")) {
880                         seen_dashdash = TRUE;
882                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883                         printf("tig version %s\n", TIG_VERSION);
884                         return REQ_NONE;
886                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887                         printf("%s\n", usage);
888                         return REQ_NONE;
889                 }
891                 custom_argv[j++] = opt;
892                 if (j >= ARRAY_SIZE(custom_argv))
893                         die("command too long");
894         }
896         custom_argv[j] = NULL;
897         *run_argv = custom_argv;
899         return request;
903 /*
904  * Line-oriented content detection.
905  */
907 #define LINE_INFO \
908 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
910 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
911 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
913 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
921 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
922 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
925 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
927 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
929 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
932 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
933 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
935 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
936 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
937 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
938 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
939 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
940 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
941 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
942 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
943 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
944 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
945 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
946 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
950 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
951 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
952 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
954 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
955 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
956 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
957 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
958 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
959 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
960 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
962 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
963 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967         LINE_##type
968         LINE_INFO,
969         LINE_NONE
970 #undef  LINE
971 };
973 struct line_info {
974         const char *name;       /* Option name. */
975         int namelen;            /* Size of option name. */
976         const char *line;       /* The start of line to match. */
977         int linelen;            /* Size of string to match. */
978         int fg, bg, attr;       /* Color and text attributes for the lines. */
979 };
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984         LINE_INFO
985 #undef  LINE
986 };
988 static enum line_type
989 get_line_type(const char *line)
991         int linelen = strlen(line);
992         enum line_type type;
994         for (type = 0; type < ARRAY_SIZE(line_info); type++)
995                 /* Case insensitive search matches Signed-off-by lines better. */
996                 if (linelen >= line_info[type].linelen &&
997                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998                         return type;
1000         return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006         assert(type < ARRAY_SIZE(line_info));
1007         return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013         size_t namelen = strlen(name);
1014         enum line_type type;
1016         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017                 if (namelen == line_info[type].namelen &&
1018                     !string_enum_compare(line_info[type].name, name, namelen))
1019                         return &line_info[type];
1021         return NULL;
1024 static void
1025 init_colors(void)
1027         int default_bg = line_info[LINE_DEFAULT].bg;
1028         int default_fg = line_info[LINE_DEFAULT].fg;
1029         enum line_type type;
1031         start_color();
1033         if (assume_default_colors(default_fg, default_bg) == ERR) {
1034                 default_bg = COLOR_BLACK;
1035                 default_fg = COLOR_WHITE;
1036         }
1038         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039                 struct line_info *info = &line_info[type];
1040                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043                 init_pair(type, fg, bg);
1044         }
1047 struct line {
1048         enum line_type type;
1050         /* State flags */
1051         unsigned int selected:1;
1052         unsigned int dirty:1;
1053         unsigned int cleareol:1;
1055         void *data;             /* User data */
1056 };
1059 /*
1060  * Keys
1061  */
1063 struct keybinding {
1064         int alias;
1065         enum request request;
1066 };
1068 static struct keybinding default_keybindings[] = {
1069         /* View switching */
1070         { 'm',          REQ_VIEW_MAIN },
1071         { 'd',          REQ_VIEW_DIFF },
1072         { 'l',          REQ_VIEW_LOG },
1073         { 't',          REQ_VIEW_TREE },
1074         { 'f',          REQ_VIEW_BLOB },
1075         { 'B',          REQ_VIEW_BLAME },
1076         { 'p',          REQ_VIEW_PAGER },
1077         { 'h',          REQ_VIEW_HELP },
1078         { 'S',          REQ_VIEW_STATUS },
1079         { 'c',          REQ_VIEW_STAGE },
1081         /* View manipulation */
1082         { 'q',          REQ_VIEW_CLOSE },
1083         { KEY_TAB,      REQ_VIEW_NEXT },
1084         { KEY_RETURN,   REQ_ENTER },
1085         { KEY_UP,       REQ_PREVIOUS },
1086         { KEY_DOWN,     REQ_NEXT },
1087         { 'R',          REQ_REFRESH },
1088         { KEY_F(5),     REQ_REFRESH },
1089         { 'O',          REQ_MAXIMIZE },
1091         /* Cursor navigation */
1092         { 'k',          REQ_MOVE_UP },
1093         { 'j',          REQ_MOVE_DOWN },
1094         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1095         { KEY_END,      REQ_MOVE_LAST_LINE },
1096         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1097         { ' ',          REQ_MOVE_PAGE_DOWN },
1098         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1099         { 'b',          REQ_MOVE_PAGE_UP },
1100         { '-',          REQ_MOVE_PAGE_UP },
1102         /* Scrolling */
1103         { KEY_IC,       REQ_SCROLL_LINE_UP },
1104         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1105         { 'w',          REQ_SCROLL_PAGE_UP },
1106         { 's',          REQ_SCROLL_PAGE_DOWN },
1108         /* Searching */
1109         { '/',          REQ_SEARCH },
1110         { '?',          REQ_SEARCH_BACK },
1111         { 'n',          REQ_FIND_NEXT },
1112         { 'N',          REQ_FIND_PREV },
1114         /* Misc */
1115         { 'Q',          REQ_QUIT },
1116         { 'z',          REQ_STOP_LOADING },
1117         { 'v',          REQ_SHOW_VERSION },
1118         { 'r',          REQ_SCREEN_REDRAW },
1119         { '.',          REQ_TOGGLE_LINENO },
1120         { 'D',          REQ_TOGGLE_DATE },
1121         { 'A',          REQ_TOGGLE_AUTHOR },
1122         { 'g',          REQ_TOGGLE_REV_GRAPH },
1123         { 'F',          REQ_TOGGLE_REFS },
1124         { ':',          REQ_PROMPT },
1125         { 'u',          REQ_STATUS_UPDATE },
1126         { '!',          REQ_STATUS_REVERT },
1127         { 'M',          REQ_STATUS_MERGE },
1128         { '@',          REQ_STAGE_NEXT },
1129         { ',',          REQ_PARENT },
1130         { 'e',          REQ_EDIT },
1131 };
1133 #define KEYMAP_INFO \
1134         KEYMAP_(GENERIC), \
1135         KEYMAP_(MAIN), \
1136         KEYMAP_(DIFF), \
1137         KEYMAP_(LOG), \
1138         KEYMAP_(TREE), \
1139         KEYMAP_(BLOB), \
1140         KEYMAP_(BLAME), \
1141         KEYMAP_(PAGER), \
1142         KEYMAP_(HELP), \
1143         KEYMAP_(STATUS), \
1144         KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148         KEYMAP_INFO
1149 #undef  KEYMAP_
1150 };
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154         KEYMAP_INFO
1155 #undef  KEYMAP_
1156 };
1158 #define set_keymap(map, name) \
1159         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162         struct keybinding *data;
1163         size_t size;
1164 };
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171         struct keybinding_table *table = &keybindings[keymap];
1173         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174         if (!table->data)
1175                 die("Failed to allocate keybinding");
1176         table->data[table->size].alias = key;
1177         table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181  * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185         size_t i;
1187         for (i = 0; i < keybindings[keymap].size; i++)
1188                 if (keybindings[keymap].data[i].alias == key)
1189                         return keybindings[keymap].data[i].request;
1191         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193                         return keybindings[KEYMAP_GENERIC].data[i].request;
1195         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196                 if (default_keybindings[i].alias == key)
1197                         return default_keybindings[i].request;
1199         return (enum request) key;
1203 struct key {
1204         const char *name;
1205         int value;
1206 };
1208 static struct key key_table[] = {
1209         { "Enter",      KEY_RETURN },
1210         { "Space",      ' ' },
1211         { "Backspace",  KEY_BACKSPACE },
1212         { "Tab",        KEY_TAB },
1213         { "Escape",     KEY_ESC },
1214         { "Left",       KEY_LEFT },
1215         { "Right",      KEY_RIGHT },
1216         { "Up",         KEY_UP },
1217         { "Down",       KEY_DOWN },
1218         { "Insert",     KEY_IC },
1219         { "Delete",     KEY_DC },
1220         { "Hash",       '#' },
1221         { "Home",       KEY_HOME },
1222         { "End",        KEY_END },
1223         { "PageUp",     KEY_PPAGE },
1224         { "PageDown",   KEY_NPAGE },
1225         { "F1",         KEY_F(1) },
1226         { "F2",         KEY_F(2) },
1227         { "F3",         KEY_F(3) },
1228         { "F4",         KEY_F(4) },
1229         { "F5",         KEY_F(5) },
1230         { "F6",         KEY_F(6) },
1231         { "F7",         KEY_F(7) },
1232         { "F8",         KEY_F(8) },
1233         { "F9",         KEY_F(9) },
1234         { "F10",        KEY_F(10) },
1235         { "F11",        KEY_F(11) },
1236         { "F12",        KEY_F(12) },
1237 };
1239 static int
1240 get_key_value(const char *name)
1242         int i;
1244         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245                 if (!strcasecmp(key_table[i].name, name))
1246                         return key_table[i].value;
1248         if (strlen(name) == 1 && isprint(*name))
1249                 return (int) *name;
1251         return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257         static char key_char[] = "'X'";
1258         const char *seq = NULL;
1259         int key;
1261         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262                 if (key_table[key].value == key_value)
1263                         seq = key_table[key].name;
1265         if (seq == NULL &&
1266             key_value < 127 &&
1267             isprint(key_value)) {
1268                 key_char[1] = (char) key_value;
1269                 seq = key_char;
1270         }
1272         return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278         static char buf[BUFSIZ];
1279         size_t pos = 0;
1280         char *sep = "";
1281         int i;
1283         buf[pos] = 0;
1285         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286                 struct keybinding *keybinding = &default_keybindings[i];
1288                 if (keybinding->request != request)
1289                         continue;
1291                 if (!string_format_from(buf, &pos, "%s%s", sep,
1292                                         get_key_name(keybinding->alias)))
1293                         return "Too many keybindings!";
1294                 sep = ", ";
1295         }
1297         return buf;
1300 struct run_request {
1301         enum keymap keymap;
1302         int key;
1303         const char *argv[SIZEOF_ARG];
1304 };
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312         struct run_request *req;
1314         if (argc >= ARRAY_SIZE(req->argv) - 1)
1315                 return REQ_NONE;
1317         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318         if (!req)
1319                 return REQ_NONE;
1321         run_request = req;
1322         req = &run_request[run_requests];
1323         req->keymap = keymap;
1324         req->key = key;
1325         req->argv[0] = NULL;
1327         if (!format_argv(req->argv, argv, FORMAT_NONE))
1328                 return REQ_NONE;
1330         return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336         if (request <= REQ_NONE)
1337                 return NULL;
1338         return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345         const char *gc[] = { "git", "gc", NULL };
1346         struct {
1347                 enum keymap keymap;
1348                 int key;
1349                 int argc;
1350                 const char **argv;
1351         } reqs[] = {
1352                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354         };
1355         int i;
1357         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358                 enum request req;
1360                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361                 if (req != REQ_NONE)
1362                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1363         }
1366 /*
1367  * User config file handling.
1368  */
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372         COLOR_MAP(DEFAULT),
1373         COLOR_MAP(BLACK),
1374         COLOR_MAP(BLUE),
1375         COLOR_MAP(CYAN),
1376         COLOR_MAP(GREEN),
1377         COLOR_MAP(MAGENTA),
1378         COLOR_MAP(RED),
1379         COLOR_MAP(WHITE),
1380         COLOR_MAP(YELLOW),
1381 };
1383 #define set_color(color, name) \
1384         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388         ATTR_MAP(NORMAL),
1389         ATTR_MAP(BLINK),
1390         ATTR_MAP(BOLD),
1391         ATTR_MAP(DIM),
1392         ATTR_MAP(REVERSE),
1393         ATTR_MAP(STANDOUT),
1394         ATTR_MAP(UNDERLINE),
1395 };
1397 #define set_attribute(attr, name) \
1398         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int   config_lineno;
1401 static bool  config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408         struct line_info *info;
1410         if (argc != 3 && argc != 4) {
1411                 config_msg = "Wrong number of arguments given to color command";
1412                 return ERR;
1413         }
1415         info = get_line_info(argv[0]);
1416         if (!info) {
1417                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418                         info = get_line_info("delimiter");
1420                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421                         info = get_line_info("date");
1423                 } else {
1424                         config_msg = "Unknown color name";
1425                         return ERR;
1426                 }
1427         }
1429         if (set_color(&info->fg, argv[1]) == ERR ||
1430             set_color(&info->bg, argv[2]) == ERR) {
1431                 config_msg = "Unknown color";
1432                 return ERR;
1433         }
1435         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436                 config_msg = "Unknown attribute";
1437                 return ERR;
1438         }
1440         return OK;
1443 static bool parse_bool(const char *s)
1445         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446                 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452         int value = atoi(s);
1454         return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461         if (argc != 3) {
1462                 config_msg = "Wrong number of arguments given to set command";
1463                 return ERR;
1464         }
1466         if (strcmp(argv[1], "=")) {
1467                 config_msg = "No value assigned";
1468                 return ERR;
1469         }
1471         if (!strcmp(argv[0], "show-author")) {
1472                 opt_author = parse_bool(argv[2]);
1473                 return OK;
1474         }
1476         if (!strcmp(argv[0], "show-date")) {
1477                 opt_date = parse_bool(argv[2]);
1478                 return OK;
1479         }
1481         if (!strcmp(argv[0], "show-rev-graph")) {
1482                 opt_rev_graph = parse_bool(argv[2]);
1483                 return OK;
1484         }
1486         if (!strcmp(argv[0], "show-refs")) {
1487                 opt_show_refs = parse_bool(argv[2]);
1488                 return OK;
1489         }
1491         if (!strcmp(argv[0], "show-line-numbers")) {
1492                 opt_line_number = parse_bool(argv[2]);
1493                 return OK;
1494         }
1496         if (!strcmp(argv[0], "line-graphics")) {
1497                 opt_line_graphics = parse_bool(argv[2]);
1498                 return OK;
1499         }
1501         if (!strcmp(argv[0], "line-number-interval")) {
1502                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503                 return OK;
1504         }
1506         if (!strcmp(argv[0], "author-width")) {
1507                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508                 return OK;
1509         }
1511         if (!strcmp(argv[0], "tab-size")) {
1512                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513                 return OK;
1514         }
1516         if (!strcmp(argv[0], "commit-encoding")) {
1517                 const char *arg = argv[2];
1518                 int arglen = strlen(arg);
1520                 switch (arg[0]) {
1521                 case '"':
1522                 case '\'':
1523                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524                                 config_msg = "Unmatched quotation";
1525                                 return ERR;
1526                         }
1527                         arg += 1; arglen -= 2;
1528                 default:
1529                         string_ncopy(opt_encoding, arg, strlen(arg));
1530                         return OK;
1531                 }
1532         }
1534         config_msg = "Unknown variable name";
1535         return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542         enum request request;
1543         int keymap;
1544         int key;
1546         if (argc < 3) {
1547                 config_msg = "Wrong number of arguments given to bind command";
1548                 return ERR;
1549         }
1551         if (set_keymap(&keymap, argv[0]) == ERR) {
1552                 config_msg = "Unknown key map";
1553                 return ERR;
1554         }
1556         key = get_key_value(argv[1]);
1557         if (key == ERR) {
1558                 config_msg = "Unknown key";
1559                 return ERR;
1560         }
1562         request = get_request(argv[2]);
1563         if (request == REQ_NONE) {
1564                 struct {
1565                         const char *name;
1566                         enum request request;
1567                 } obsolete[] = {
1568                         { "cherry-pick",        REQ_NONE },
1569                         { "screen-resize",      REQ_NONE },
1570                         { "tree-parent",        REQ_PARENT },
1571                 };
1572                 size_t namelen = strlen(argv[2]);
1573                 int i;
1575                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576                         if (namelen != strlen(obsolete[i].name) ||
1577                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1578                                 continue;
1579                         if (obsolete[i].request != REQ_NONE)
1580                                 add_keybinding(keymap, obsolete[i].request, key);
1581                         config_msg = "Obsolete request name";
1582                         return ERR;
1583                 }
1584         }
1585         if (request == REQ_NONE && *argv[2]++ == '!')
1586                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587         if (request == REQ_NONE) {
1588                 config_msg = "Unknown request name";
1589                 return ERR;
1590         }
1592         add_keybinding(keymap, request, key);
1594         return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600         const char *argv[SIZEOF_ARG];
1601         int argc = 0;
1603         if (!argv_from_string(argv, &argc, value)) {
1604                 config_msg = "Too many option arguments";
1605                 return ERR;
1606         }
1608         if (!strcmp(opt, "color"))
1609                 return option_color_command(argc, argv);
1611         if (!strcmp(opt, "set"))
1612                 return option_set_command(argc, argv);
1614         if (!strcmp(opt, "bind"))
1615                 return option_bind_command(argc, argv);
1617         config_msg = "Unknown option command";
1618         return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624         int status = OK;
1626         config_lineno++;
1627         config_msg = "Internal error";
1629         /* Check for comment markers, since read_properties() will
1630          * only ensure opt and value are split at first " \t". */
1631         optlen = strcspn(opt, "#");
1632         if (optlen == 0)
1633                 return OK;
1635         if (opt[optlen] != 0) {
1636                 config_msg = "No option value";
1637                 status = ERR;
1639         }  else {
1640                 /* Look for comment endings in the value. */
1641                 size_t len = strcspn(value, "#");
1643                 if (len < valuelen) {
1644                         valuelen = len;
1645                         value[valuelen] = 0;
1646                 }
1648                 status = set_option(opt, value);
1649         }
1651         if (status == ERR) {
1652                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653                         config_lineno, (int) optlen, opt, config_msg);
1654                 config_errors = TRUE;
1655         }
1657         /* Always keep going if errors are encountered. */
1658         return OK;
1661 static void
1662 load_option_file(const char *path)
1664         struct io io = {};
1666         /* It's ok that the file doesn't exist. */
1667         if (!io_open(&io, path))
1668                 return;
1670         config_lineno = 0;
1671         config_errors = FALSE;
1673         if (read_properties(&io, " \t", read_option) == ERR ||
1674             config_errors == TRUE)
1675                 fprintf(stderr, "Errors while loading %s.\n", path);
1678 static int
1679 load_options(void)
1681         const char *home = getenv("HOME");
1682         const char *tigrc_user = getenv("TIGRC_USER");
1683         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684         char buf[SIZEOF_STR];
1686         add_builtin_run_requests();
1688         if (!tigrc_system) {
1689                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690                         return ERR;
1691                 tigrc_system = buf;
1692         }
1693         load_option_file(tigrc_system);
1695         if (!tigrc_user) {
1696                 if (!home || !string_format(buf, "%s/.tigrc", home))
1697                         return ERR;
1698                 tigrc_user = buf;
1699         }
1700         load_option_file(tigrc_user);
1702         return OK;
1706 /*
1707  * The viewer
1708  */
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 /* Reading from the prompt? */
1718 static bool input_mode = FALSE;
1720 #define foreach_displayed_view(view, i) \
1721         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1723 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1725 /* Current head and commit ID */
1726 static char ref_blob[SIZEOF_REF]        = "";
1727 static char ref_commit[SIZEOF_REF]      = "HEAD";
1728 static char ref_head[SIZEOF_REF]        = "HEAD";
1730 struct view {
1731         const char *name;       /* View name */
1732         const char *cmd_env;    /* Command line set via environment */
1733         const char *id;         /* Points to either of ref_{head,commit,blob} */
1735         struct view_ops *ops;   /* View operations */
1737         enum keymap keymap;     /* What keymap does this view have */
1738         bool git_dir;           /* Whether the view requires a git directory. */
1740         char ref[SIZEOF_REF];   /* Hovered commit reference */
1741         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1743         int height, width;      /* The width and height of the main window */
1744         WINDOW *win;            /* The main window */
1745         WINDOW *title;          /* The title window living below the main window */
1747         /* Navigation */
1748         unsigned long offset;   /* Offset of the window top */
1749         unsigned long lineno;   /* Current line number */
1750         unsigned long p_offset; /* Previous offset of the window top */
1751         unsigned long p_lineno; /* Previous current line number */
1752         bool p_restore;         /* Should the previous position be restored. */
1754         /* Searching */
1755         char grep[SIZEOF_STR];  /* Search string */
1756         regex_t *regex;         /* Pre-compiled regex */
1758         /* If non-NULL, points to the view that opened this view. If this view
1759          * is closed tig will switch back to the parent view. */
1760         struct view *parent;
1762         /* Buffering */
1763         size_t lines;           /* Total number of lines */
1764         struct line *line;      /* Line index */
1765         size_t line_alloc;      /* Total number of allocated lines */
1766         unsigned int digits;    /* Number of digits in the lines member. */
1768         /* Drawing */
1769         struct line *curline;   /* Line currently being drawn. */
1770         enum line_type curtype; /* Attribute currently used for drawing. */
1771         unsigned long col;      /* Column when drawing. */
1773         /* Loading */
1774         struct io io;
1775         struct io *pipe;
1776         time_t start_time;
1777         time_t update_secs;
1778 };
1780 struct view_ops {
1781         /* What type of content being displayed. Used in the title bar. */
1782         const char *type;
1783         /* Default command arguments. */
1784         const char **argv;
1785         /* Open and reads in all view content. */
1786         bool (*open)(struct view *view);
1787         /* Read one line; updates view->line. */
1788         bool (*read)(struct view *view, char *data);
1789         /* Draw one line; @lineno must be < view->height. */
1790         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1791         /* Depending on view handle a special requests. */
1792         enum request (*request)(struct view *view, enum request request, struct line *line);
1793         /* Search for regex in a line. */
1794         bool (*grep)(struct view *view, struct line *line);
1795         /* Select line */
1796         void (*select)(struct view *view, struct line *line);
1797 };
1799 static struct view_ops blame_ops;
1800 static struct view_ops blob_ops;
1801 static struct view_ops diff_ops;
1802 static struct view_ops help_ops;
1803 static struct view_ops log_ops;
1804 static struct view_ops main_ops;
1805 static struct view_ops pager_ops;
1806 static struct view_ops stage_ops;
1807 static struct view_ops status_ops;
1808 static struct view_ops tree_ops;
1810 #define VIEW_STR(name, env, ref, ops, map, git) \
1811         { name, #env, ref, ops, map, git }
1813 #define VIEW_(id, name, ops, git, ref) \
1814         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1817 static struct view views[] = {
1818         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1819         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1820         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1821         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1822         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1823         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1824         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1825         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1826         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1827         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1828 };
1830 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1831 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1833 #define foreach_view(view, i) \
1834         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1836 #define view_is_displayed(view) \
1837         (view == display[0] || view == display[1])
1840 enum line_graphic {
1841         LINE_GRAPHIC_VLINE
1842 };
1844 static int line_graphics[] = {
1845         /* LINE_GRAPHIC_VLINE: */ '|'
1846 };
1848 static inline void
1849 set_view_attr(struct view *view, enum line_type type)
1851         if (!view->curline->selected && view->curtype != type) {
1852                 wattrset(view->win, get_line_attr(type));
1853                 wchgat(view->win, -1, 0, type, NULL);
1854                 view->curtype = type;
1855         }
1858 static int
1859 draw_chars(struct view *view, enum line_type type, const char *string,
1860            int max_len, bool use_tilde)
1862         int len = 0;
1863         int col = 0;
1864         int trimmed = FALSE;
1866         if (max_len <= 0)
1867                 return 0;
1869         if (opt_utf8) {
1870                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1871         } else {
1872                 col = len = strlen(string);
1873                 if (len > max_len) {
1874                         if (use_tilde) {
1875                                 max_len -= 1;
1876                         }
1877                         col = len = max_len;
1878                         trimmed = TRUE;
1879                 }
1880         }
1882         set_view_attr(view, type);
1883         waddnstr(view->win, string, len);
1884         if (trimmed && use_tilde) {
1885                 set_view_attr(view, LINE_DELIMITER);
1886                 waddch(view->win, '~');
1887                 col++;
1888         }
1890         return col;
1893 static int
1894 draw_space(struct view *view, enum line_type type, int max, int spaces)
1896         static char space[] = "                    ";
1897         int col = 0;
1899         spaces = MIN(max, spaces);
1901         while (spaces > 0) {
1902                 int len = MIN(spaces, sizeof(space) - 1);
1904                 col += draw_chars(view, type, space, spaces, FALSE);
1905                 spaces -= len;
1906         }
1908         return col;
1911 static bool
1912 draw_lineno(struct view *view, unsigned int lineno)
1914         char number[10];
1915         int digits3 = view->digits < 3 ? 3 : view->digits;
1916         int max_number = MIN(digits3, STRING_SIZE(number));
1917         int max = view->width - view->col;
1918         int col;
1920         if (max < max_number)
1921                 max_number = max;
1923         lineno += view->offset + 1;
1924         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925                 static char fmt[] = "%1ld";
1927                 if (view->digits <= 9)
1928                         fmt[1] = '0' + digits3;
1930                 if (!string_format(number, fmt, lineno))
1931                         number[0] = 0;
1932                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933         } else {
1934                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935         }
1937         if (col < max) {
1938                 set_view_attr(view, LINE_DEFAULT);
1939                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940                 col++;
1941         }
1943         if (col < max)
1944                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1945         view->col += col;
1947         return view->width - view->col <= 0;
1950 static bool
1951 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1953         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1954         return view->width - view->col <= 0;
1957 static bool
1958 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1960         int max = view->width - view->col;
1961         int i;
1963         if (max < size)
1964                 size = max;
1966         set_view_attr(view, type);
1967         /* Using waddch() instead of waddnstr() ensures that
1968          * they'll be rendered correctly for the cursor line. */
1969         for (i = 0; i < size; i++)
1970                 waddch(view->win, graphic[i]);
1972         view->col += size;
1973         if (size < max) {
1974                 waddch(view->win, ' ');
1975                 view->col++;
1976         }
1978         return view->width - view->col <= 0;
1981 static bool
1982 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1984         int max = MIN(view->width - view->col, len);
1985         int col;
1987         if (text)
1988                 col = draw_chars(view, type, text, max - 1, trim);
1989         else
1990                 col = draw_space(view, type, max - 1, max - 1);
1992         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1993         return view->width - view->col <= 0;
1996 static bool
1997 draw_date(struct view *view, struct tm *time)
1999         char buf[DATE_COLS];
2000         char *date;
2001         int timelen = 0;
2003         if (time)
2004                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2005         date = timelen ? buf : NULL;
2007         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2010 static bool
2011 draw_view_line(struct view *view, unsigned int lineno)
2013         struct line *line;
2014         bool selected = (view->offset + lineno == view->lineno);
2015         bool draw_ok;
2017         assert(view_is_displayed(view));
2019         if (view->offset + lineno >= view->lines)
2020                 return FALSE;
2022         line = &view->line[view->offset + lineno];
2024         wmove(view->win, lineno, 0);
2025         if (line->cleareol)
2026                 wclrtoeol(view->win);
2027         view->col = 0;
2028         view->curline = line;
2029         view->curtype = LINE_NONE;
2030         line->selected = FALSE;
2031         line->dirty = line->cleareol = 0;
2033         if (selected) {
2034                 set_view_attr(view, LINE_CURSOR);
2035                 line->selected = TRUE;
2036                 view->ops->select(view, line);
2037         }
2039         scrollok(view->win, FALSE);
2040         draw_ok = view->ops->draw(view, line, lineno);
2041         scrollok(view->win, TRUE);
2043         return draw_ok;
2046 static void
2047 redraw_view_dirty(struct view *view)
2049         bool dirty = FALSE;
2050         int lineno;
2052         for (lineno = 0; lineno < view->height; lineno++) {
2053                 if (view->offset + lineno >= view->lines)
2054                         break;
2055                 if (!view->line[view->offset + lineno].dirty)
2056                         continue;
2057                 dirty = TRUE;
2058                 if (!draw_view_line(view, lineno))
2059                         break;
2060         }
2062         if (!dirty)
2063                 return;
2064         redrawwin(view->win);
2065         if (input_mode)
2066                 wnoutrefresh(view->win);
2067         else
2068                 wrefresh(view->win);
2071 static void
2072 redraw_view_from(struct view *view, int lineno)
2074         assert(0 <= lineno && lineno < view->height);
2076         for (; lineno < view->height; lineno++) {
2077                 if (!draw_view_line(view, lineno))
2078                         break;
2079         }
2081         redrawwin(view->win);
2082         if (input_mode)
2083                 wnoutrefresh(view->win);
2084         else
2085                 wrefresh(view->win);
2088 static void
2089 redraw_view(struct view *view)
2091         werase(view->win);
2092         redraw_view_from(view, 0);
2096 static void
2097 update_view_title(struct view *view)
2099         char buf[SIZEOF_STR];
2100         char state[SIZEOF_STR];
2101         size_t bufpos = 0, statelen = 0;
2103         assert(view_is_displayed(view));
2105         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2106                 unsigned int view_lines = view->offset + view->height;
2107                 unsigned int lines = view->lines
2108                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2109                                    : 0;
2111                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2112                                    view->ops->type,
2113                                    view->lineno + 1,
2114                                    view->lines,
2115                                    lines);
2117         }
2119         if (view->pipe) {
2120                 time_t secs = time(NULL) - view->start_time;
2122                 /* Three git seconds are a long time ... */
2123                 if (secs > 2)
2124                         string_format_from(state, &statelen, " loading %lds", secs);
2125         }
2127         string_format_from(buf, &bufpos, "[%s]", view->name);
2128         if (*view->ref && bufpos < view->width) {
2129                 size_t refsize = strlen(view->ref);
2130                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2132                 if (minsize < view->width)
2133                         refsize = view->width - minsize + 7;
2134                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2135         }
2137         if (statelen && bufpos < view->width) {
2138                 string_format_from(buf, &bufpos, "%s", state);
2139         }
2141         if (view == display[current_view])
2142                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2143         else
2144                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2146         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2147         wclrtoeol(view->title);
2148         wmove(view->title, 0, view->width - 1);
2150         if (input_mode)
2151                 wnoutrefresh(view->title);
2152         else
2153                 wrefresh(view->title);
2156 static void
2157 resize_display(void)
2159         int offset, i;
2160         struct view *base = display[0];
2161         struct view *view = display[1] ? display[1] : display[0];
2163         /* Setup window dimensions */
2165         getmaxyx(stdscr, base->height, base->width);
2167         /* Make room for the status window. */
2168         base->height -= 1;
2170         if (view != base) {
2171                 /* Horizontal split. */
2172                 view->width   = base->width;
2173                 view->height  = SCALE_SPLIT_VIEW(base->height);
2174                 base->height -= view->height;
2176                 /* Make room for the title bar. */
2177                 view->height -= 1;
2178         }
2180         /* Make room for the title bar. */
2181         base->height -= 1;
2183         offset = 0;
2185         foreach_displayed_view (view, i) {
2186                 if (!view->win) {
2187                         view->win = newwin(view->height, 0, offset, 0);
2188                         if (!view->win)
2189                                 die("Failed to create %s view", view->name);
2191                         scrollok(view->win, TRUE);
2193                         view->title = newwin(1, 0, offset + view->height, 0);
2194                         if (!view->title)
2195                                 die("Failed to create title window");
2197                 } else {
2198                         wresize(view->win, view->height, view->width);
2199                         mvwin(view->win,   offset, 0);
2200                         mvwin(view->title, offset + view->height, 0);
2201                 }
2203                 offset += view->height + 1;
2204         }
2207 static void
2208 redraw_display(bool clear)
2210         struct view *view;
2211         int i;
2213         foreach_displayed_view (view, i) {
2214                 if (clear)
2215                         wclear(view->win);
2216                 redraw_view(view);
2217                 update_view_title(view);
2218         }
2221 static void
2222 update_display_cursor(struct view *view)
2224         /* Move the cursor to the right-most column of the cursor line.
2225          *
2226          * XXX: This could turn out to be a bit expensive, but it ensures that
2227          * the cursor does not jump around. */
2228         if (view->lines) {
2229                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2230                 wrefresh(view->win);
2231         }
2234 static void
2235 toggle_view_option(bool *option, const char *help)
2237         *option = !*option;
2238         redraw_display(FALSE);
2239         report("%sabling %s", *option ? "En" : "Dis", help);
2242 /*
2243  * Navigation
2244  */
2246 /* Scrolling backend */
2247 static void
2248 do_scroll_view(struct view *view, int lines)
2250         bool redraw_current_line = FALSE;
2252         /* The rendering expects the new offset. */
2253         view->offset += lines;
2255         assert(0 <= view->offset && view->offset < view->lines);
2256         assert(lines);
2258         /* Move current line into the view. */
2259         if (view->lineno < view->offset) {
2260                 view->lineno = view->offset;
2261                 redraw_current_line = TRUE;
2262         } else if (view->lineno >= view->offset + view->height) {
2263                 view->lineno = view->offset + view->height - 1;
2264                 redraw_current_line = TRUE;
2265         }
2267         assert(view->offset <= view->lineno && view->lineno < view->lines);
2269         /* Redraw the whole screen if scrolling is pointless. */
2270         if (view->height < ABS(lines)) {
2271                 redraw_view(view);
2273         } else {
2274                 int line = lines > 0 ? view->height - lines : 0;
2275                 int end = line + ABS(lines);
2277                 wscrl(view->win, lines);
2279                 for (; line < end; line++) {
2280                         if (!draw_view_line(view, line))
2281                                 break;
2282                 }
2284                 if (redraw_current_line)
2285                         draw_view_line(view, view->lineno - view->offset);
2286         }
2288         redrawwin(view->win);
2289         wrefresh(view->win);
2290         report("");
2293 /* Scroll frontend */
2294 static void
2295 scroll_view(struct view *view, enum request request)
2297         int lines = 1;
2299         assert(view_is_displayed(view));
2301         switch (request) {
2302         case REQ_SCROLL_PAGE_DOWN:
2303                 lines = view->height;
2304         case REQ_SCROLL_LINE_DOWN:
2305                 if (view->offset + lines > view->lines)
2306                         lines = view->lines - view->offset;
2308                 if (lines == 0 || view->offset + view->height >= view->lines) {
2309                         report("Cannot scroll beyond the last line");
2310                         return;
2311                 }
2312                 break;
2314         case REQ_SCROLL_PAGE_UP:
2315                 lines = view->height;
2316         case REQ_SCROLL_LINE_UP:
2317                 if (lines > view->offset)
2318                         lines = view->offset;
2320                 if (lines == 0) {
2321                         report("Cannot scroll beyond the first line");
2322                         return;
2323                 }
2325                 lines = -lines;
2326                 break;
2328         default:
2329                 die("request %d not handled in switch", request);
2330         }
2332         do_scroll_view(view, lines);
2335 /* Cursor moving */
2336 static void
2337 move_view(struct view *view, enum request request)
2339         int scroll_steps = 0;
2340         int steps;
2342         switch (request) {
2343         case REQ_MOVE_FIRST_LINE:
2344                 steps = -view->lineno;
2345                 break;
2347         case REQ_MOVE_LAST_LINE:
2348                 steps = view->lines - view->lineno - 1;
2349                 break;
2351         case REQ_MOVE_PAGE_UP:
2352                 steps = view->height > view->lineno
2353                       ? -view->lineno : -view->height;
2354                 break;
2356         case REQ_MOVE_PAGE_DOWN:
2357                 steps = view->lineno + view->height >= view->lines
2358                       ? view->lines - view->lineno - 1 : view->height;
2359                 break;
2361         case REQ_MOVE_UP:
2362                 steps = -1;
2363                 break;
2365         case REQ_MOVE_DOWN:
2366                 steps = 1;
2367                 break;
2369         default:
2370                 die("request %d not handled in switch", request);
2371         }
2373         if (steps <= 0 && view->lineno == 0) {
2374                 report("Cannot move beyond the first line");
2375                 return;
2377         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2378                 report("Cannot move beyond the last line");
2379                 return;
2380         }
2382         /* Move the current line */
2383         view->lineno += steps;
2384         assert(0 <= view->lineno && view->lineno < view->lines);
2386         /* Check whether the view needs to be scrolled */
2387         if (view->lineno < view->offset ||
2388             view->lineno >= view->offset + view->height) {
2389                 scroll_steps = steps;
2390                 if (steps < 0 && -steps > view->offset) {
2391                         scroll_steps = -view->offset;
2393                 } else if (steps > 0) {
2394                         if (view->lineno == view->lines - 1 &&
2395                             view->lines > view->height) {
2396                                 scroll_steps = view->lines - view->offset - 1;
2397                                 if (scroll_steps >= view->height)
2398                                         scroll_steps -= view->height - 1;
2399                         }
2400                 }
2401         }
2403         if (!view_is_displayed(view)) {
2404                 view->offset += scroll_steps;
2405                 assert(0 <= view->offset && view->offset < view->lines);
2406                 view->ops->select(view, &view->line[view->lineno]);
2407                 return;
2408         }
2410         /* Repaint the old "current" line if we be scrolling */
2411         if (ABS(steps) < view->height)
2412                 draw_view_line(view, view->lineno - steps - view->offset);
2414         if (scroll_steps) {
2415                 do_scroll_view(view, scroll_steps);
2416                 return;
2417         }
2419         /* Draw the current line */
2420         draw_view_line(view, view->lineno - view->offset);
2422         redrawwin(view->win);
2423         wrefresh(view->win);
2424         report("");
2428 /*
2429  * Searching
2430  */
2432 static void search_view(struct view *view, enum request request);
2434 static void
2435 select_view_line(struct view *view, unsigned long lineno)
2437         if (lineno - view->offset >= view->height) {
2438                 view->offset = lineno;
2439                 view->lineno = lineno;
2440                 if (view_is_displayed(view))
2441                         redraw_view(view);
2443         } else {
2444                 unsigned long old_lineno = view->lineno - view->offset;
2446                 view->lineno = lineno;
2447                 if (view_is_displayed(view)) {
2448                         draw_view_line(view, old_lineno);
2449                         draw_view_line(view, view->lineno - view->offset);
2450                         redrawwin(view->win);
2451                         wrefresh(view->win);
2452                 } else {
2453                         view->ops->select(view, &view->line[view->lineno]);
2454                 }
2455         }
2458 static void
2459 find_next(struct view *view, enum request request)
2461         unsigned long lineno = view->lineno;
2462         int direction;
2464         if (!*view->grep) {
2465                 if (!*opt_search)
2466                         report("No previous search");
2467                 else
2468                         search_view(view, request);
2469                 return;
2470         }
2472         switch (request) {
2473         case REQ_SEARCH:
2474         case REQ_FIND_NEXT:
2475                 direction = 1;
2476                 break;
2478         case REQ_SEARCH_BACK:
2479         case REQ_FIND_PREV:
2480                 direction = -1;
2481                 break;
2483         default:
2484                 return;
2485         }
2487         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2488                 lineno += direction;
2490         /* Note, lineno is unsigned long so will wrap around in which case it
2491          * will become bigger than view->lines. */
2492         for (; lineno < view->lines; lineno += direction) {
2493                 if (view->ops->grep(view, &view->line[lineno])) {
2494                         select_view_line(view, lineno);
2495                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2496                         return;
2497                 }
2498         }
2500         report("No match found for '%s'", view->grep);
2503 static void
2504 search_view(struct view *view, enum request request)
2506         int regex_err;
2508         if (view->regex) {
2509                 regfree(view->regex);
2510                 *view->grep = 0;
2511         } else {
2512                 view->regex = calloc(1, sizeof(*view->regex));
2513                 if (!view->regex)
2514                         return;
2515         }
2517         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2518         if (regex_err != 0) {
2519                 char buf[SIZEOF_STR] = "unknown error";
2521                 regerror(regex_err, view->regex, buf, sizeof(buf));
2522                 report("Search failed: %s", buf);
2523                 return;
2524         }
2526         string_copy(view->grep, opt_search);
2528         find_next(view, request);
2531 /*
2532  * Incremental updating
2533  */
2535 static void
2536 reset_view(struct view *view)
2538         int i;
2540         for (i = 0; i < view->lines; i++)
2541                 free(view->line[i].data);
2542         free(view->line);
2544         view->p_offset = view->offset;
2545         view->p_lineno = view->lineno;
2547         view->line = NULL;
2548         view->offset = 0;
2549         view->lines  = 0;
2550         view->lineno = 0;
2551         view->line_alloc = 0;
2552         view->vid[0] = 0;
2553         view->update_secs = 0;
2556 static void
2557 free_argv(const char *argv[])
2559         int argc;
2561         for (argc = 0; argv[argc]; argc++)
2562                 free((void *) argv[argc]);
2565 static bool
2566 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2568         char buf[SIZEOF_STR];
2569         int argc;
2570         bool noreplace = flags == FORMAT_NONE;
2572         free_argv(dst_argv);
2574         for (argc = 0; src_argv[argc]; argc++) {
2575                 const char *arg = src_argv[argc];
2576                 size_t bufpos = 0;
2578                 while (arg) {
2579                         char *next = strstr(arg, "%(");
2580                         int len = next - arg;
2581                         const char *value;
2583                         if (!next || noreplace) {
2584                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2585                                         noreplace = TRUE;
2586                                 len = strlen(arg);
2587                                 value = "";
2589                         } else if (!prefixcmp(next, "%(directory)")) {
2590                                 value = opt_path;
2592                         } else if (!prefixcmp(next, "%(file)")) {
2593                                 value = opt_file;
2595                         } else if (!prefixcmp(next, "%(ref)")) {
2596                                 value = *opt_ref ? opt_ref : "HEAD";
2598                         } else if (!prefixcmp(next, "%(head)")) {
2599                                 value = ref_head;
2601                         } else if (!prefixcmp(next, "%(commit)")) {
2602                                 value = ref_commit;
2604                         } else if (!prefixcmp(next, "%(blob)")) {
2605                                 value = ref_blob;
2607                         } else {
2608                                 report("Unknown replacement: `%s`", next);
2609                                 return FALSE;
2610                         }
2612                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2613                                 return FALSE;
2615                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2616                 }
2618                 dst_argv[argc] = strdup(buf);
2619                 if (!dst_argv[argc])
2620                         break;
2621         }
2623         dst_argv[argc] = NULL;
2625         return src_argv[argc] == NULL;
2628 static bool
2629 restore_view_position(struct view *view)
2631         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2632                 return FALSE;
2634         /* Changing the view position cancels the restoring. */
2635         /* FIXME: Changing back to the first line is not detected. */
2636         if (view->offset != 0 || view->lineno != 0) {
2637                 view->p_restore = FALSE;
2638                 return FALSE;
2639         }
2641         if (view->p_lineno >= view->lines) {
2642                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2643                 if (view->p_offset >= view->p_lineno) {
2644                         unsigned long half = view->height / 2;
2646                         if (view->p_lineno > half)
2647                                 view->p_offset = view->p_lineno - half;
2648                         else
2649                                 view->p_offset = 0;
2650                 }
2651         }
2653         if (view_is_displayed(view) &&
2654             view->offset != view->p_offset &&
2655             view->lineno != view->p_lineno)
2656                 werase(view->win);
2658         view->offset = view->p_offset;
2659         view->lineno = view->p_lineno;
2660         view->p_restore = FALSE;
2662         return TRUE;
2665 static void
2666 end_update(struct view *view, bool force)
2668         if (!view->pipe)
2669                 return;
2670         while (!view->ops->read(view, NULL))
2671                 if (!force)
2672                         return;
2673         set_nonblocking_input(FALSE);
2674         if (force)
2675                 kill_io(view->pipe);
2676         done_io(view->pipe);
2677         view->pipe = NULL;
2680 static void
2681 setup_update(struct view *view, const char *vid)
2683         set_nonblocking_input(TRUE);
2684         reset_view(view);
2685         string_copy_rev(view->vid, vid);
2686         view->pipe = &view->io;
2687         view->start_time = time(NULL);
2690 static bool
2691 prepare_update(struct view *view, const char *argv[], const char *dir,
2692                enum format_flags flags)
2694         if (view->pipe)
2695                 end_update(view, TRUE);
2696         return init_io_rd(&view->io, argv, dir, flags);
2699 static bool
2700 prepare_update_file(struct view *view, const char *name)
2702         if (view->pipe)
2703                 end_update(view, TRUE);
2704         return io_open(&view->io, name);
2707 static bool
2708 begin_update(struct view *view, bool refresh)
2710         if (view->pipe)
2711                 end_update(view, TRUE);
2713         if (refresh) {
2714                 if (!start_io(&view->io))
2715                         return FALSE;
2717         } else {
2718                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2719                         opt_path[0] = 0;
2721                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2722                         return FALSE;
2724                 /* Put the current ref_* value to the view title ref
2725                  * member. This is needed by the blob view. Most other
2726                  * views sets it automatically after loading because the
2727                  * first line is a commit line. */
2728                 string_copy_rev(view->ref, view->id);
2729         }
2731         setup_update(view, view->id);
2733         return TRUE;
2736 #define ITEM_CHUNK_SIZE 256
2737 static void *
2738 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2740         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2741         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2743         if (mem == NULL || num_chunks != num_chunks_new) {
2744                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2745                 mem = realloc(mem, *size * item_size);
2746         }
2748         return mem;
2751 static struct line *
2752 realloc_lines(struct view *view, size_t line_size)
2754         size_t alloc = view->line_alloc;
2755         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2756                                          sizeof(*view->line));
2758         if (!tmp)
2759                 return NULL;
2761         view->line = tmp;
2762         view->line_alloc = alloc;
2763         return view->line;
2766 static bool
2767 update_view(struct view *view)
2769         char out_buffer[BUFSIZ * 2];
2770         char *line;
2771         /* Clear the view and redraw everything since the tree sorting
2772          * might have rearranged things. */
2773         bool redraw = view->lines == 0;
2774         bool can_read = TRUE;
2776         if (!view->pipe)
2777                 return TRUE;
2779         if (!io_can_read(view->pipe)) {
2780                 if (view->lines == 0) {
2781                         time_t secs = time(NULL) - view->start_time;
2783                         if (secs > view->update_secs) {
2784                                 if (view->update_secs == 0)
2785                                         redraw_view(view);
2786                                 update_view_title(view);
2787                                 view->update_secs = secs;
2788                         }
2789                 }
2790                 return TRUE;
2791         }
2793         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2794                 if (opt_iconv != ICONV_NONE) {
2795                         ICONV_CONST char *inbuf = line;
2796                         size_t inlen = strlen(line) + 1;
2798                         char *outbuf = out_buffer;
2799                         size_t outlen = sizeof(out_buffer);
2801                         size_t ret;
2803                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2804                         if (ret != (size_t) -1)
2805                                 line = out_buffer;
2806                 }
2808                 if (!view->ops->read(view, line)) {
2809                         report("Allocation failure");
2810                         end_update(view, TRUE);
2811                         return FALSE;
2812                 }
2813         }
2815         {
2816                 unsigned long lines = view->lines;
2817                 int digits;
2819                 for (digits = 0; lines; digits++)
2820                         lines /= 10;
2822                 /* Keep the displayed view in sync with line number scaling. */
2823                 if (digits != view->digits) {
2824                         view->digits = digits;
2825                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2826                                 redraw = TRUE;
2827                 }
2828         }
2830         if (io_error(view->pipe)) {
2831                 report("Failed to read: %s", io_strerror(view->pipe));
2832                 end_update(view, TRUE);
2834         } else if (io_eof(view->pipe)) {
2835                 report("");
2836                 end_update(view, FALSE);
2837         }
2839         if (restore_view_position(view))
2840                 redraw = TRUE;
2842         if (!view_is_displayed(view))
2843                 return TRUE;
2845         if (redraw)
2846                 redraw_view_from(view, 0);
2847         else
2848                 redraw_view_dirty(view);
2850         /* Update the title _after_ the redraw so that if the redraw picks up a
2851          * commit reference in view->ref it'll be available here. */
2852         update_view_title(view);
2853         return TRUE;
2856 static struct line *
2857 add_line_data(struct view *view, void *data, enum line_type type)
2859         struct line *line;
2861         if (!realloc_lines(view, view->lines + 1))
2862                 return NULL;
2864         line = &view->line[view->lines++];
2865         memset(line, 0, sizeof(*line));
2866         line->type = type;
2867         line->data = data;
2868         line->dirty = 1;
2870         return line;
2873 static struct line *
2874 add_line_text(struct view *view, const char *text, enum line_type type)
2876         char *data = text ? strdup(text) : NULL;
2878         return data ? add_line_data(view, data, type) : NULL;
2881 static struct line *
2882 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2884         char buf[SIZEOF_STR];
2885         va_list args;
2887         va_start(args, fmt);
2888         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2889                 buf[0] = 0;
2890         va_end(args);
2892         return buf[0] ? add_line_text(view, buf, type) : NULL;
2895 /*
2896  * View opening
2897  */
2899 enum open_flags {
2900         OPEN_DEFAULT = 0,       /* Use default view switching. */
2901         OPEN_SPLIT = 1,         /* Split current view. */
2902         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2903         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2904         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2905         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2906         OPEN_PREPARED = 32,     /* Open already prepared command. */
2907 };
2909 static void
2910 open_view(struct view *prev, enum request request, enum open_flags flags)
2912         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2913         bool split = !!(flags & OPEN_SPLIT);
2914         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2915         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2916         struct view *view = VIEW(request);
2917         int nviews = displayed_views();
2918         struct view *base_view = display[0];
2920         if (view == prev && nviews == 1 && !reload) {
2921                 report("Already in %s view", view->name);
2922                 return;
2923         }
2925         if (view->git_dir && !opt_git_dir[0]) {
2926                 report("The %s view is disabled in pager view", view->name);
2927                 return;
2928         }
2930         if (split) {
2931                 display[1] = view;
2932                 if (!backgrounded)
2933                         current_view = 1;
2934         } else if (!nomaximize) {
2935                 /* Maximize the current view. */
2936                 memset(display, 0, sizeof(display));
2937                 current_view = 0;
2938                 display[current_view] = view;
2939         }
2941         /* Resize the view when switching between split- and full-screen,
2942          * or when switching between two different full-screen views. */
2943         if (nviews != displayed_views() ||
2944             (nviews == 1 && base_view != display[0]))
2945                 resize_display();
2947         if (view->ops->open) {
2948                 if (view->pipe)
2949                         end_update(view, TRUE);
2950                 if (!view->ops->open(view)) {
2951                         report("Failed to load %s view", view->name);
2952                         return;
2953                 }
2954                 restore_view_position(view);
2956         } else if ((reload || strcmp(view->vid, view->id)) &&
2957                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2958                 report("Failed to load %s view", view->name);
2959                 return;
2960         }
2962         if (split && prev->lineno - prev->offset >= prev->height) {
2963                 /* Take the title line into account. */
2964                 int lines = prev->lineno - prev->offset - prev->height + 1;
2966                 /* Scroll the view that was split if the current line is
2967                  * outside the new limited view. */
2968                 do_scroll_view(prev, lines);
2969         }
2971         if (prev && view != prev) {
2972                 if (split && !backgrounded) {
2973                         /* "Blur" the previous view. */
2974                         update_view_title(prev);
2975                 }
2977                 view->parent = prev;
2978         }
2980         if (view->pipe && view->lines == 0) {
2981                 /* Clear the old view and let the incremental updating refill
2982                  * the screen. */
2983                 werase(view->win);
2984                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2985                 report("");
2986         } else if (view_is_displayed(view)) {
2987                 redraw_view(view);
2988                 report("");
2989         }
2991         /* If the view is backgrounded the above calls to report()
2992          * won't redraw the view title. */
2993         if (backgrounded)
2994                 update_view_title(view);
2997 static void
2998 open_external_viewer(const char *argv[], const char *dir)
3000         def_prog_mode();           /* save current tty modes */
3001         endwin();                  /* restore original tty modes */
3002         run_io_fg(argv, dir);
3003         fprintf(stderr, "Press Enter to continue");
3004         getc(opt_tty);
3005         reset_prog_mode();
3006         redraw_display(TRUE);
3009 static void
3010 open_mergetool(const char *file)
3012         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3014         open_external_viewer(mergetool_argv, opt_cdup);
3017 static void
3018 open_editor(bool from_root, const char *file)
3020         const char *editor_argv[] = { "vi", file, NULL };
3021         const char *editor;
3023         editor = getenv("GIT_EDITOR");
3024         if (!editor && *opt_editor)
3025                 editor = opt_editor;
3026         if (!editor)
3027                 editor = getenv("VISUAL");
3028         if (!editor)
3029                 editor = getenv("EDITOR");
3030         if (!editor)
3031                 editor = "vi";
3033         editor_argv[0] = editor;
3034         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3037 static void
3038 open_run_request(enum request request)
3040         struct run_request *req = get_run_request(request);
3041         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3043         if (!req) {
3044                 report("Unknown run request");
3045                 return;
3046         }
3048         if (format_argv(argv, req->argv, FORMAT_ALL))
3049                 open_external_viewer(argv, NULL);
3050         free_argv(argv);
3053 /*
3054  * User request switch noodle
3055  */
3057 static int
3058 view_driver(struct view *view, enum request request)
3060         int i;
3062         if (request == REQ_NONE) {
3063                 doupdate();
3064                 return TRUE;
3065         }
3067         if (request > REQ_NONE) {
3068                 open_run_request(request);
3069                 /* FIXME: When all views can refresh always do this. */
3070                 if (view == VIEW(REQ_VIEW_STATUS) ||
3071                     view == VIEW(REQ_VIEW_MAIN) ||
3072                     view == VIEW(REQ_VIEW_LOG) ||
3073                     view == VIEW(REQ_VIEW_STAGE))
3074                         request = REQ_REFRESH;
3075                 else
3076                         return TRUE;
3077         }
3079         if (view && view->lines) {
3080                 request = view->ops->request(view, request, &view->line[view->lineno]);
3081                 if (request == REQ_NONE)
3082                         return TRUE;
3083         }
3085         switch (request) {
3086         case REQ_MOVE_UP:
3087         case REQ_MOVE_DOWN:
3088         case REQ_MOVE_PAGE_UP:
3089         case REQ_MOVE_PAGE_DOWN:
3090         case REQ_MOVE_FIRST_LINE:
3091         case REQ_MOVE_LAST_LINE:
3092                 move_view(view, request);
3093                 break;
3095         case REQ_SCROLL_LINE_DOWN:
3096         case REQ_SCROLL_LINE_UP:
3097         case REQ_SCROLL_PAGE_DOWN:
3098         case REQ_SCROLL_PAGE_UP:
3099                 scroll_view(view, request);
3100                 break;
3102         case REQ_VIEW_BLAME:
3103                 if (!opt_file[0]) {
3104                         report("No file chosen, press %s to open tree view",
3105                                get_key(REQ_VIEW_TREE));
3106                         break;
3107                 }
3108                 open_view(view, request, OPEN_DEFAULT);
3109                 break;
3111         case REQ_VIEW_BLOB:
3112                 if (!ref_blob[0]) {
3113                         report("No file chosen, press %s to open tree view",
3114                                get_key(REQ_VIEW_TREE));
3115                         break;
3116                 }
3117                 open_view(view, request, OPEN_DEFAULT);
3118                 break;
3120         case REQ_VIEW_PAGER:
3121                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3122                         report("No pager content, press %s to run command from prompt",
3123                                get_key(REQ_PROMPT));
3124                         break;
3125                 }
3126                 open_view(view, request, OPEN_DEFAULT);
3127                 break;
3129         case REQ_VIEW_STAGE:
3130                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3131                         report("No stage content, press %s to open the status view and choose file",
3132                                get_key(REQ_VIEW_STATUS));
3133                         break;
3134                 }
3135                 open_view(view, request, OPEN_DEFAULT);
3136                 break;
3138         case REQ_VIEW_STATUS:
3139                 if (opt_is_inside_work_tree == FALSE) {
3140                         report("The status view requires a working tree");
3141                         break;
3142                 }
3143                 open_view(view, request, OPEN_DEFAULT);
3144                 break;
3146         case REQ_VIEW_MAIN:
3147         case REQ_VIEW_DIFF:
3148         case REQ_VIEW_LOG:
3149         case REQ_VIEW_TREE:
3150         case REQ_VIEW_HELP:
3151                 open_view(view, request, OPEN_DEFAULT);
3152                 break;
3154         case REQ_NEXT:
3155         case REQ_PREVIOUS:
3156                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3158                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3159                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3160                    (view == VIEW(REQ_VIEW_DIFF) &&
3161                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3162                    (view == VIEW(REQ_VIEW_STAGE) &&
3163                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3164                    (view == VIEW(REQ_VIEW_BLOB) &&
3165                      view->parent == VIEW(REQ_VIEW_TREE))) {
3166                         int line;
3168                         view = view->parent;
3169                         line = view->lineno;
3170                         move_view(view, request);
3171                         if (view_is_displayed(view))
3172                                 update_view_title(view);
3173                         if (line != view->lineno)
3174                                 view->ops->request(view, REQ_ENTER,
3175                                                    &view->line[view->lineno]);
3177                 } else {
3178                         move_view(view, request);
3179                 }
3180                 break;
3182         case REQ_VIEW_NEXT:
3183         {
3184                 int nviews = displayed_views();
3185                 int next_view = (current_view + 1) % nviews;
3187                 if (next_view == current_view) {
3188                         report("Only one view is displayed");
3189                         break;
3190                 }
3192                 current_view = next_view;
3193                 /* Blur out the title of the previous view. */
3194                 update_view_title(view);
3195                 report("");
3196                 break;
3197         }
3198         case REQ_REFRESH:
3199                 report("Refreshing is not yet supported for the %s view", view->name);
3200                 break;
3202         case REQ_MAXIMIZE:
3203                 if (displayed_views() == 2)
3204                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3205                 break;
3207         case REQ_TOGGLE_LINENO:
3208                 toggle_view_option(&opt_line_number, "line numbers");
3209                 break;
3211         case REQ_TOGGLE_DATE:
3212                 toggle_view_option(&opt_date, "date display");
3213                 break;
3215         case REQ_TOGGLE_AUTHOR:
3216                 toggle_view_option(&opt_author, "author display");
3217                 break;
3219         case REQ_TOGGLE_REV_GRAPH:
3220                 toggle_view_option(&opt_rev_graph, "revision graph display");
3221                 break;
3223         case REQ_TOGGLE_REFS:
3224                 toggle_view_option(&opt_show_refs, "reference display");
3225                 break;
3227         case REQ_SEARCH:
3228         case REQ_SEARCH_BACK:
3229                 search_view(view, request);
3230                 break;
3232         case REQ_FIND_NEXT:
3233         case REQ_FIND_PREV:
3234                 find_next(view, request);
3235                 break;
3237         case REQ_STOP_LOADING:
3238                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3239                         view = &views[i];
3240                         if (view->pipe)
3241                                 report("Stopped loading the %s view", view->name),
3242                         end_update(view, TRUE);
3243                 }
3244                 break;
3246         case REQ_SHOW_VERSION:
3247                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3248                 return TRUE;
3250         case REQ_SCREEN_REDRAW:
3251                 redraw_display(TRUE);
3252                 break;
3254         case REQ_EDIT:
3255                 report("Nothing to edit");
3256                 break;
3258         case REQ_ENTER:
3259                 report("Nothing to enter");
3260                 break;
3262         case REQ_VIEW_CLOSE:
3263                 /* XXX: Mark closed views by letting view->parent point to the
3264                  * view itself. Parents to closed view should never be
3265                  * followed. */
3266                 if (view->parent &&
3267                     view->parent->parent != view->parent) {
3268                         memset(display, 0, sizeof(display));
3269                         current_view = 0;
3270                         display[current_view] = view->parent;
3271                         view->parent = view;
3272                         resize_display();
3273                         redraw_display(FALSE);
3274                         report("");
3275                         break;
3276                 }
3277                 /* Fall-through */
3278         case REQ_QUIT:
3279                 return FALSE;
3281         default:
3282                 report("Unknown key, press 'h' for help");
3283                 return TRUE;
3284         }
3286         return TRUE;
3290 /*
3291  * View backend utilities
3292  */
3294 /* Parse author lines where the name may be empty:
3295  *      author  <email@address.tld> 1138474660 +0100
3296  */
3297 static void
3298 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3300         char *nameend = strchr(ident, '<');
3301         char *emailend = strchr(ident, '>');
3303         if (nameend && emailend)
3304                 *nameend = *emailend = 0;
3305         ident = chomp_string(ident);
3306         if (!*ident) {
3307                 if (nameend)
3308                         ident = chomp_string(nameend + 1);
3309                 if (!*ident)
3310                         ident = "Unknown";
3311         }
3313         string_ncopy_do(author, authorsize, ident, strlen(ident));
3315         /* Parse epoch and timezone */
3316         if (emailend && emailend[1] == ' ') {
3317                 char *secs = emailend + 2;
3318                 char *zone = strchr(secs, ' ');
3319                 time_t time = (time_t) atol(secs);
3321                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3322                         long tz;
3324                         zone++;
3325                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3326                         tz += ('0' - zone[2]) * 60 * 60;
3327                         tz += ('0' - zone[3]) * 60;
3328                         tz += ('0' - zone[4]) * 60;
3330                         if (zone[0] == '-')
3331                                 tz = -tz;
3333                         time -= tz;
3334                 }
3336                 gmtime_r(&time, tm);
3337         }
3340 static enum input_status
3341 select_commit_parent_handler(void *data, char *buf, int c)
3343         size_t parents = *(size_t *) data;
3344         int parent = 0;
3346         if (!isdigit(c))
3347                 return INPUT_SKIP;
3349         if (*buf)
3350                 parent = atoi(buf) * 10;
3351         parent += c - '0';
3353         if (parent > parents)
3354                 return INPUT_SKIP;
3355         return INPUT_OK;
3358 static bool
3359 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3361         char buf[SIZEOF_STR * 4];
3362         const char *revlist_argv[] = {
3363                 "git", "rev-list", "-1", "--parents", id, NULL
3364         };
3365         int parents;
3367         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3368             !*chomp_string(buf) ||
3369             (parents = (strlen(buf) / 40) - 1) < 0) {
3370                 report("Failed to get parent information");
3371                 return FALSE;
3373         } else if (parents == 0) {
3374                 report("The selected commit has no parents");
3375                 return FALSE;
3376         }
3378         if (parents > 1) {
3379                 char prompt[SIZEOF_STR];
3380                 char *result;
3382                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3383                         return FALSE;
3384                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3385                 if (!result)
3386                         return FALSE;
3387                 parents = atoi(result);
3388         }
3390         string_copy_rev(rev, &buf[41 * parents]);
3391         return TRUE;
3394 /*
3395  * Pager backend
3396  */
3398 static bool
3399 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3401         char *text = line->data;
3403         if (opt_line_number && draw_lineno(view, lineno))
3404                 return TRUE;
3406         draw_text(view, line->type, text, TRUE);
3407         return TRUE;
3410 static bool
3411 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3413         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3414         char refbuf[SIZEOF_STR];
3415         char *ref = NULL;
3417         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3418                 ref = chomp_string(refbuf);
3420         if (!ref || !*ref)
3421                 return TRUE;
3423         /* This is the only fatal call, since it can "corrupt" the buffer. */
3424         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3425                 return FALSE;
3427         return TRUE;
3430 static void
3431 add_pager_refs(struct view *view, struct line *line)
3433         char buf[SIZEOF_STR];
3434         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3435         struct ref **refs;
3436         size_t bufpos = 0, refpos = 0;
3437         const char *sep = "Refs: ";
3438         bool is_tag = FALSE;
3440         assert(line->type == LINE_COMMIT);
3442         refs = get_refs(commit_id);
3443         if (!refs) {
3444                 if (view == VIEW(REQ_VIEW_DIFF))
3445                         goto try_add_describe_ref;
3446                 return;
3447         }
3449         do {
3450                 struct ref *ref = refs[refpos];
3451                 const char *fmt = ref->tag    ? "%s[%s]" :
3452                                   ref->remote ? "%s<%s>" : "%s%s";
3454                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3455                         return;
3456                 sep = ", ";
3457                 if (ref->tag)
3458                         is_tag = TRUE;
3459         } while (refs[refpos++]->next);
3461         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3462 try_add_describe_ref:
3463                 /* Add <tag>-g<commit_id> "fake" reference. */
3464                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3465                         return;
3466         }
3468         if (bufpos == 0)
3469                 return;
3471         add_line_text(view, buf, LINE_PP_REFS);
3474 static bool
3475 pager_read(struct view *view, char *data)
3477         struct line *line;
3479         if (!data)
3480                 return TRUE;
3482         line = add_line_text(view, data, get_line_type(data));
3483         if (!line)
3484                 return FALSE;
3486         if (line->type == LINE_COMMIT &&
3487             (view == VIEW(REQ_VIEW_DIFF) ||
3488              view == VIEW(REQ_VIEW_LOG)))
3489                 add_pager_refs(view, line);
3491         return TRUE;
3494 static enum request
3495 pager_request(struct view *view, enum request request, struct line *line)
3497         int split = 0;
3499         if (request != REQ_ENTER)
3500                 return request;
3502         if (line->type == LINE_COMMIT &&
3503            (view == VIEW(REQ_VIEW_LOG) ||
3504             view == VIEW(REQ_VIEW_PAGER))) {
3505                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3506                 split = 1;
3507         }
3509         /* Always scroll the view even if it was split. That way
3510          * you can use Enter to scroll through the log view and
3511          * split open each commit diff. */
3512         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3514         /* FIXME: A minor workaround. Scrolling the view will call report("")
3515          * but if we are scrolling a non-current view this won't properly
3516          * update the view title. */
3517         if (split)
3518                 update_view_title(view);
3520         return REQ_NONE;
3523 static bool
3524 pager_grep(struct view *view, struct line *line)
3526         regmatch_t pmatch;
3527         char *text = line->data;
3529         if (!*text)
3530                 return FALSE;
3532         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3533                 return FALSE;
3535         return TRUE;
3538 static void
3539 pager_select(struct view *view, struct line *line)
3541         if (line->type == LINE_COMMIT) {
3542                 char *text = (char *)line->data + STRING_SIZE("commit ");
3544                 if (view != VIEW(REQ_VIEW_PAGER))
3545                         string_copy_rev(view->ref, text);
3546                 string_copy_rev(ref_commit, text);
3547         }
3550 static struct view_ops pager_ops = {
3551         "line",
3552         NULL,
3553         NULL,
3554         pager_read,
3555         pager_draw,
3556         pager_request,
3557         pager_grep,
3558         pager_select,
3559 };
3561 static const char *log_argv[SIZEOF_ARG] = {
3562         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3563 };
3565 static enum request
3566 log_request(struct view *view, enum request request, struct line *line)
3568         switch (request) {
3569         case REQ_REFRESH:
3570                 load_refs();
3571                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3572                 return REQ_NONE;
3573         default:
3574                 return pager_request(view, request, line);
3575         }
3578 static struct view_ops log_ops = {
3579         "line",
3580         log_argv,
3581         NULL,
3582         pager_read,
3583         pager_draw,
3584         log_request,
3585         pager_grep,
3586         pager_select,
3587 };
3589 static const char *diff_argv[SIZEOF_ARG] = {
3590         "git", "show", "--pretty=fuller", "--no-color", "--root",
3591                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3592 };
3594 static struct view_ops diff_ops = {
3595         "line",
3596         diff_argv,
3597         NULL,
3598         pager_read,
3599         pager_draw,
3600         pager_request,
3601         pager_grep,
3602         pager_select,
3603 };
3605 /*
3606  * Help backend
3607  */
3609 static bool
3610 help_open(struct view *view)
3612         int i;
3614         if (view->lines > 0)
3615                 return TRUE;
3617         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3619         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3620                 const char *key;
3622                 if (req_info[i].request == REQ_NONE)
3623                         continue;
3625                 if (!req_info[i].request) {
3626                         add_line_text(view, "", LINE_DEFAULT);
3627                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3628                         continue;
3629                 }
3631                 key = get_key(req_info[i].request);
3632                 if (!*key)
3633                         key = "(no key defined)";
3635                 add_line_format(view, LINE_DEFAULT, "    %-25s %s",
3636                                 key, req_info[i].help);
3637         }
3639         if (run_requests) {
3640                 add_line_text(view, "", LINE_DEFAULT);
3641                 add_line_text(view, "External commands:", LINE_DEFAULT);
3642         }
3644         for (i = 0; i < run_requests; i++) {
3645                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3646                 const char *key;
3647                 char cmd[SIZEOF_STR];
3648                 size_t bufpos;
3649                 int argc;
3651                 if (!req)
3652                         continue;
3654                 key = get_key_name(req->key);
3655                 if (!*key)
3656                         key = "(no key defined)";
3658                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3659                         if (!string_format_from(cmd, &bufpos, "%s%s",
3660                                                 argc ? " " : "", req->argv[argc]))
3661                                 return REQ_NONE;
3663                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3664                                 keymap_table[req->keymap].name, key, cmd);
3665         }
3667         return TRUE;
3670 static struct view_ops help_ops = {
3671         "line",
3672         NULL,
3673         help_open,
3674         NULL,
3675         pager_draw,
3676         pager_request,
3677         pager_grep,
3678         pager_select,
3679 };
3682 /*
3683  * Tree backend
3684  */
3686 struct tree_stack_entry {
3687         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3688         unsigned long lineno;           /* Line number to restore */
3689         char *name;                     /* Position of name in opt_path */
3690 };
3692 /* The top of the path stack. */
3693 static struct tree_stack_entry *tree_stack = NULL;
3694 unsigned long tree_lineno = 0;
3696 static void
3697 pop_tree_stack_entry(void)
3699         struct tree_stack_entry *entry = tree_stack;
3701         tree_lineno = entry->lineno;
3702         entry->name[0] = 0;
3703         tree_stack = entry->prev;
3704         free(entry);
3707 static void
3708 push_tree_stack_entry(const char *name, unsigned long lineno)
3710         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3711         size_t pathlen = strlen(opt_path);
3713         if (!entry)
3714                 return;
3716         entry->prev = tree_stack;
3717         entry->name = opt_path + pathlen;
3718         tree_stack = entry;
3720         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3721                 pop_tree_stack_entry();
3722                 return;
3723         }
3725         /* Move the current line to the first tree entry. */
3726         tree_lineno = 1;
3727         entry->lineno = lineno;
3730 /* Parse output from git-ls-tree(1):
3731  *
3732  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3733  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3734  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3735  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3736  */
3738 #define SIZEOF_TREE_ATTR \
3739         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3741 #define SIZEOF_TREE_MODE \
3742         STRING_SIZE("100644 ")
3744 #define TREE_ID_OFFSET \
3745         STRING_SIZE("100644 blob ")
3747 struct tree_entry {
3748         char id[SIZEOF_REV];
3749         mode_t mode;
3750         struct tm time;                 /* Date from the author ident. */
3751         char author[75];                /* Author of the commit. */
3752         char name[1];
3753 };
3755 static const char *
3756 tree_path(struct line *line)
3758         return ((struct tree_entry *) line->data)->name;
3762 static int
3763 tree_compare_entry(struct line *line1, struct line *line2)
3765         if (line1->type != line2->type)
3766                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3767         return strcmp(tree_path(line1), tree_path(line2));
3770 static struct line *
3771 tree_entry(struct view *view, enum line_type type, const char *path,
3772            const char *mode, const char *id)
3774         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3775         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3777         if (!entry || !line) {
3778                 free(entry);
3779                 return NULL;
3780         }
3782         strncpy(entry->name, path, strlen(path));
3783         if (mode)
3784                 entry->mode = strtoul(mode, NULL, 8);
3785         if (id)
3786                 string_copy_rev(entry->id, id);
3788         return line;
3791 static bool
3792 tree_read_date(struct view *view, char *text, bool *read_date)
3794         static char author_name[SIZEOF_STR];
3795         static struct tm author_time;
3797         if (!text && *read_date) {
3798                 *read_date = FALSE;
3799                 return TRUE;
3801         } else if (!text) {
3802                 char *path = *opt_path ? opt_path : ".";
3803                 /* Find next entry to process */
3804                 const char *log_file[] = {
3805                         "git", "log", "--no-color", "--pretty=raw",
3806                                 "--cc", "--raw", view->id, "--", path, NULL
3807                 };
3808                 struct io io = {};
3810                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3811                         report("Failed to load tree data");
3812                         return TRUE;
3813                 }
3815                 done_io(view->pipe);
3816                 view->io = io;
3817                 *read_date = TRUE;
3818                 return FALSE;
3820         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3821                 parse_author_line(text + STRING_SIZE("author "),
3822                                   author_name, sizeof(author_name), &author_time);
3824         } else if (*text == ':') {
3825                 char *pos;
3826                 size_t annotated = 1;
3827                 size_t i;
3829                 pos = strchr(text, '\t');
3830                 if (!pos)
3831                         return TRUE;
3832                 text = pos + 1;
3833                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3834                         text += strlen(opt_prefix);
3835                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3836                         text += strlen(opt_path);
3837                 pos = strchr(text, '/');
3838                 if (pos)
3839                         *pos = 0;
3841                 for (i = 1; i < view->lines; i++) {
3842                         struct line *line = &view->line[i];
3843                         struct tree_entry *entry = line->data;
3845                         annotated += !!*entry->author;
3846                         if (*entry->author || strcmp(entry->name, text))
3847                                 continue;
3849                         string_copy(entry->author, author_name);
3850                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3851                         line->dirty = 1;
3852                         break;
3853                 }
3855                 if (annotated == view->lines)
3856                         kill_io(view->pipe);
3857         }
3858         return TRUE;
3861 static bool
3862 tree_read(struct view *view, char *text)
3864         static bool read_date = FALSE;
3865         struct tree_entry *data;
3866         struct line *entry, *line;
3867         enum line_type type;
3868         size_t textlen = text ? strlen(text) : 0;
3869         char *path = text + SIZEOF_TREE_ATTR;
3871         if (read_date || !text)
3872                 return tree_read_date(view, text, &read_date);
3874         if (textlen <= SIZEOF_TREE_ATTR)
3875                 return FALSE;
3876         if (view->lines == 0 &&
3877             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3878                 return FALSE;
3880         /* Strip the path part ... */
3881         if (*opt_path) {
3882                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3883                 size_t striplen = strlen(opt_path);
3885                 if (pathlen > striplen)
3886                         memmove(path, path + striplen,
3887                                 pathlen - striplen + 1);
3889                 /* Insert "link" to parent directory. */
3890                 if (view->lines == 1 &&
3891                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3892                         return FALSE;
3893         }
3895         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3896         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3897         if (!entry)
3898                 return FALSE;
3899         data = entry->data;
3901         /* Skip "Directory ..." and ".." line. */
3902         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3903                 if (tree_compare_entry(line, entry) <= 0)
3904                         continue;
3906                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3908                 line->data = data;
3909                 line->type = type;
3910                 for (; line <= entry; line++)
3911                         line->dirty = line->cleareol = 1;
3912                 return TRUE;
3913         }
3915         if (tree_lineno > view->lineno) {
3916                 view->lineno = tree_lineno;
3917                 tree_lineno = 0;
3918         }
3920         return TRUE;
3923 static bool
3924 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3926         struct tree_entry *entry = line->data;
3928         if (line->type == LINE_TREE_PARENT) {
3929                 if (draw_text(view, line->type, "Directory path /", TRUE))
3930                         return TRUE;
3931         } else {
3932                 char mode[11] = "-r--r--r--";
3934                 if (S_ISDIR(entry->mode)) {
3935                         mode[3] = mode[6] = mode[9] = 'x';
3936                         mode[0] = 'd';
3937                 }
3938                 if (S_ISLNK(entry->mode))
3939                         mode[0] = 'l';
3940                 if (entry->mode & S_IWUSR)
3941                         mode[2] = 'w';
3942                 if (entry->mode & S_IXUSR)
3943                         mode[3] = 'x';
3944                 if (entry->mode & S_IXGRP)
3945                         mode[6] = 'x';
3946                 if (entry->mode & S_IXOTH)
3947                         mode[9] = 'x';
3948                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3949                         return TRUE;
3951                 if (opt_author &&
3952                     draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3953                         return TRUE;
3955                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3956                         return TRUE;
3957         }
3958         if (draw_text(view, line->type, entry->name, TRUE))
3959                 return TRUE;
3960         return TRUE;
3963 static void
3964 open_blob_editor()
3966         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3967         int fd = mkstemp(file);
3969         if (fd == -1)
3970                 report("Failed to create temporary file");
3971         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3972                 report("Failed to save blob data to file");
3973         else
3974                 open_editor(FALSE, file);
3975         if (fd != -1)
3976                 unlink(file);
3979 static enum request
3980 tree_request(struct view *view, enum request request, struct line *line)
3982         enum open_flags flags;
3984         switch (request) {
3985         case REQ_VIEW_BLAME:
3986                 if (line->type != LINE_TREE_FILE) {
3987                         report("Blame only supported for files");
3988                         return REQ_NONE;
3989                 }
3991                 string_copy(opt_ref, view->vid);
3992                 return request;
3994         case REQ_EDIT:
3995                 if (line->type != LINE_TREE_FILE) {
3996                         report("Edit only supported for files");
3997                 } else if (!is_head_commit(view->vid)) {
3998                         open_blob_editor();
3999                 } else {
4000                         open_editor(TRUE, opt_file);
4001                 }
4002                 return REQ_NONE;
4004         case REQ_PARENT:
4005                 if (!*opt_path) {
4006                         /* quit view if at top of tree */
4007                         return REQ_VIEW_CLOSE;
4008                 }
4009                 /* fake 'cd  ..' */
4010                 line = &view->line[1];
4011                 break;
4013         case REQ_ENTER:
4014                 break;
4016         default:
4017                 return request;
4018         }
4020         /* Cleanup the stack if the tree view is at a different tree. */
4021         while (!*opt_path && tree_stack)
4022                 pop_tree_stack_entry();
4024         switch (line->type) {
4025         case LINE_TREE_DIR:
4026                 /* Depending on whether it is a subdir or parent (updir?) link
4027                  * mangle the path buffer. */
4028                 if (line == &view->line[1] && *opt_path) {
4029                         pop_tree_stack_entry();
4031                 } else {
4032                         const char *basename = tree_path(line);
4034                         push_tree_stack_entry(basename, view->lineno);
4035                 }
4037                 /* Trees and subtrees share the same ID, so they are not not
4038                  * unique like blobs. */
4039                 flags = OPEN_RELOAD;
4040                 request = REQ_VIEW_TREE;
4041                 break;
4043         case LINE_TREE_FILE:
4044                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4045                 request = REQ_VIEW_BLOB;
4046                 break;
4048         default:
4049                 return REQ_NONE;
4050         }
4052         open_view(view, request, flags);
4053         if (request == REQ_VIEW_TREE)
4054                 view->lineno = tree_lineno;
4056         return REQ_NONE;
4059 static void
4060 tree_select(struct view *view, struct line *line)
4062         struct tree_entry *entry = line->data;
4064         if (line->type == LINE_TREE_FILE) {
4065                 string_copy_rev(ref_blob, entry->id);
4066                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4068         } else if (line->type != LINE_TREE_DIR) {
4069                 return;
4070         }
4072         string_copy_rev(view->ref, entry->id);
4075 static const char *tree_argv[SIZEOF_ARG] = {
4076         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4077 };
4079 static struct view_ops tree_ops = {
4080         "file",
4081         tree_argv,
4082         NULL,
4083         tree_read,
4084         tree_draw,
4085         tree_request,
4086         pager_grep,
4087         tree_select,
4088 };
4090 static bool
4091 blob_read(struct view *view, char *line)
4093         if (!line)
4094                 return TRUE;
4095         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4098 static enum request
4099 blob_request(struct view *view, enum request request, struct line *line)
4101         switch (request) {
4102         case REQ_EDIT:
4103                 open_blob_editor();
4104                 return REQ_NONE;
4105         default:
4106                 return pager_request(view, request, line);
4107         }
4110 static const char *blob_argv[SIZEOF_ARG] = {
4111         "git", "cat-file", "blob", "%(blob)", NULL
4112 };
4114 static struct view_ops blob_ops = {
4115         "line",
4116         blob_argv,
4117         NULL,
4118         blob_read,
4119         pager_draw,
4120         blob_request,
4121         pager_grep,
4122         pager_select,
4123 };
4125 /*
4126  * Blame backend
4127  *
4128  * Loading the blame view is a two phase job:
4129  *
4130  *  1. File content is read either using opt_file from the
4131  *     filesystem or using git-cat-file.
4132  *  2. Then blame information is incrementally added by
4133  *     reading output from git-blame.
4134  */
4136 static const char *blame_head_argv[] = {
4137         "git", "blame", "--incremental", "--", "%(file)", NULL
4138 };
4140 static const char *blame_ref_argv[] = {
4141         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4142 };
4144 static const char *blame_cat_file_argv[] = {
4145         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4146 };
4148 struct blame_commit {
4149         char id[SIZEOF_REV];            /* SHA1 ID. */
4150         char title[128];                /* First line of the commit message. */
4151         char author[75];                /* Author of the commit. */
4152         struct tm time;                 /* Date from the author ident. */
4153         char filename[128];             /* Name of file. */
4154         bool has_previous;              /* Was a "previous" line detected. */
4155 };
4157 struct blame {
4158         struct blame_commit *commit;
4159         char text[1];
4160 };
4162 static bool
4163 blame_open(struct view *view)
4165         if (*opt_ref || !io_open(&view->io, opt_file)) {
4166                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4167                         return FALSE;
4168         }
4170         setup_update(view, opt_file);
4171         string_format(view->ref, "%s ...", opt_file);
4173         return TRUE;
4176 static struct blame_commit *
4177 get_blame_commit(struct view *view, const char *id)
4179         size_t i;
4181         for (i = 0; i < view->lines; i++) {
4182                 struct blame *blame = view->line[i].data;
4184                 if (!blame->commit)
4185                         continue;
4187                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4188                         return blame->commit;
4189         }
4191         {
4192                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4194                 if (commit)
4195                         string_ncopy(commit->id, id, SIZEOF_REV);
4196                 return commit;
4197         }
4200 static bool
4201 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4203         const char *pos = *posref;
4205         *posref = NULL;
4206         pos = strchr(pos + 1, ' ');
4207         if (!pos || !isdigit(pos[1]))
4208                 return FALSE;
4209         *number = atoi(pos + 1);
4210         if (*number < min || *number > max)
4211                 return FALSE;
4213         *posref = pos;
4214         return TRUE;
4217 static struct blame_commit *
4218 parse_blame_commit(struct view *view, const char *text, int *blamed)
4220         struct blame_commit *commit;
4221         struct blame *blame;
4222         const char *pos = text + SIZEOF_REV - 1;
4223         size_t lineno;
4224         size_t group;
4226         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4227                 return NULL;
4229         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4230             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4231                 return NULL;
4233         commit = get_blame_commit(view, text);
4234         if (!commit)
4235                 return NULL;
4237         *blamed += group;
4238         while (group--) {
4239                 struct line *line = &view->line[lineno + group - 1];
4241                 blame = line->data;
4242                 blame->commit = commit;
4243                 line->dirty = 1;
4244         }
4246         return commit;
4249 static bool
4250 blame_read_file(struct view *view, const char *line, bool *read_file)
4252         if (!line) {
4253                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4254                 struct io io = {};
4256                 if (view->lines == 0 && !view->parent)
4257                         die("No blame exist for %s", view->vid);
4259                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4260                         report("Failed to load blame data");
4261                         return TRUE;
4262                 }
4264                 done_io(view->pipe);
4265                 view->io = io;
4266                 *read_file = FALSE;
4267                 return FALSE;
4269         } else {
4270                 size_t linelen = strlen(line);
4271                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4273                 blame->commit = NULL;
4274                 strncpy(blame->text, line, linelen);
4275                 blame->text[linelen] = 0;
4276                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4277         }
4280 static bool
4281 match_blame_header(const char *name, char **line)
4283         size_t namelen = strlen(name);
4284         bool matched = !strncmp(name, *line, namelen);
4286         if (matched)
4287                 *line += namelen;
4289         return matched;
4292 static bool
4293 blame_read(struct view *view, char *line)
4295         static struct blame_commit *commit = NULL;
4296         static int blamed = 0;
4297         static time_t author_time;
4298         static bool read_file = TRUE;
4300         if (read_file)
4301                 return blame_read_file(view, line, &read_file);
4303         if (!line) {
4304                 /* Reset all! */
4305                 commit = NULL;
4306                 blamed = 0;
4307                 read_file = TRUE;
4308                 string_format(view->ref, "%s", view->vid);
4309                 if (view_is_displayed(view)) {
4310                         update_view_title(view);
4311                         redraw_view_from(view, 0);
4312                 }
4313                 return TRUE;
4314         }
4316         if (!commit) {
4317                 commit = parse_blame_commit(view, line, &blamed);
4318                 string_format(view->ref, "%s %2d%%", view->vid,
4319                               view->lines ? blamed * 100 / view->lines : 0);
4321         } else if (match_blame_header("author ", &line)) {
4322                 string_ncopy(commit->author, line, strlen(line));
4324         } else if (match_blame_header("author-time ", &line)) {
4325                 author_time = (time_t) atol(line);
4327         } else if (match_blame_header("author-tz ", &line)) {
4328                 long tz;
4330                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4331                 tz += ('0' - line[2]) * 60 * 60;
4332                 tz += ('0' - line[3]) * 60;
4333                 tz += ('0' - line[4]) * 60;
4335                 if (line[0] == '-')
4336                         tz = -tz;
4338                 author_time -= tz;
4339                 gmtime_r(&author_time, &commit->time);
4341         } else if (match_blame_header("summary ", &line)) {
4342                 string_ncopy(commit->title, line, strlen(line));
4344         } else if (match_blame_header("previous ", &line)) {
4345                 commit->has_previous = TRUE;
4347         } else if (match_blame_header("filename ", &line)) {
4348                 string_ncopy(commit->filename, line, strlen(line));
4349                 commit = NULL;
4350         }
4352         return TRUE;
4355 static bool
4356 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4358         struct blame *blame = line->data;
4359         struct tm *time = NULL;
4360         const char *id = NULL, *author = NULL;
4362         if (blame->commit && *blame->commit->filename) {
4363                 id = blame->commit->id;
4364                 author = blame->commit->author;
4365                 time = &blame->commit->time;
4366         }
4368         if (opt_date && draw_date(view, time))
4369                 return TRUE;
4371         if (opt_author &&
4372             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4373                 return TRUE;
4375         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4376                 return TRUE;
4378         if (draw_lineno(view, lineno))
4379                 return TRUE;
4381         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4382         return TRUE;
4385 static bool
4386 check_blame_commit(struct blame *blame)
4388         if (!blame->commit)
4389                 report("Commit data not loaded yet");
4390         else if (!strcmp(blame->commit->id, NULL_ID))
4391                 report("No commit exist for the selected line");
4392         else
4393                 return TRUE;
4394         return FALSE;
4397 static enum request
4398 blame_request(struct view *view, enum request request, struct line *line)
4400         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4401         struct blame *blame = line->data;
4403         switch (request) {
4404         case REQ_VIEW_BLAME:
4405                 if (check_blame_commit(blame)) {
4406                         string_copy(opt_ref, blame->commit->id);
4407                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4408                 }
4409                 break;
4411         case REQ_PARENT:
4412                 if (check_blame_commit(blame) &&
4413                     select_commit_parent(blame->commit->id, opt_ref))
4414                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4415                 break;
4417         case REQ_ENTER:
4418                 if (!blame->commit) {
4419                         report("No commit loaded yet");
4420                         break;
4421                 }
4423                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4424                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4425                         break;
4427                 if (!strcmp(blame->commit->id, NULL_ID)) {
4428                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4429                         const char *diff_index_argv[] = {
4430                                 "git", "diff-index", "--root", "--patch-with-stat",
4431                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4432                         };
4434                         if (!blame->commit->has_previous) {
4435                                 diff_index_argv[1] = "diff";
4436                                 diff_index_argv[2] = "--no-color";
4437                                 diff_index_argv[6] = "--";
4438                                 diff_index_argv[7] = "/dev/null";
4439                         }
4441                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4442                                 report("Failed to allocate diff command");
4443                                 break;
4444                         }
4445                         flags |= OPEN_PREPARED;
4446                 }
4448                 open_view(view, REQ_VIEW_DIFF, flags);
4449                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4450                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4451                 break;
4453         default:
4454                 return request;
4455         }
4457         return REQ_NONE;
4460 static bool
4461 blame_grep(struct view *view, struct line *line)
4463         struct blame *blame = line->data;
4464         struct blame_commit *commit = blame->commit;
4465         regmatch_t pmatch;
4467 #define MATCH(text, on)                                                 \
4468         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4470         if (commit) {
4471                 char buf[DATE_COLS + 1];
4473                 if (MATCH(commit->title, 1) ||
4474                     MATCH(commit->author, opt_author) ||
4475                     MATCH(commit->id, opt_date))
4476                         return TRUE;
4478                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4479                     MATCH(buf, 1))
4480                         return TRUE;
4481         }
4483         return MATCH(blame->text, 1);
4485 #undef MATCH
4488 static void
4489 blame_select(struct view *view, struct line *line)
4491         struct blame *blame = line->data;
4492         struct blame_commit *commit = blame->commit;
4494         if (!commit)
4495                 return;
4497         if (!strcmp(commit->id, NULL_ID))
4498                 string_ncopy(ref_commit, "HEAD", 4);
4499         else
4500                 string_copy_rev(ref_commit, commit->id);
4503 static struct view_ops blame_ops = {
4504         "line",
4505         NULL,
4506         blame_open,
4507         blame_read,
4508         blame_draw,
4509         blame_request,
4510         blame_grep,
4511         blame_select,
4512 };
4514 /*
4515  * Status backend
4516  */
4518 struct status {
4519         char status;
4520         struct {
4521                 mode_t mode;
4522                 char rev[SIZEOF_REV];
4523                 char name[SIZEOF_STR];
4524         } old;
4525         struct {
4526                 mode_t mode;
4527                 char rev[SIZEOF_REV];
4528                 char name[SIZEOF_STR];
4529         } new;
4530 };
4532 static char status_onbranch[SIZEOF_STR];
4533 static struct status stage_status;
4534 static enum line_type stage_line_type;
4535 static size_t stage_chunks;
4536 static int *stage_chunk;
4538 /* This should work even for the "On branch" line. */
4539 static inline bool
4540 status_has_none(struct view *view, struct line *line)
4542         return line < view->line + view->lines && !line[1].data;
4545 /* Get fields from the diff line:
4546  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4547  */
4548 static inline bool
4549 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4551         const char *old_mode = buf +  1;
4552         const char *new_mode = buf +  8;
4553         const char *old_rev  = buf + 15;
4554         const char *new_rev  = buf + 56;
4555         const char *status   = buf + 97;
4557         if (bufsize < 98 ||
4558             old_mode[-1] != ':' ||
4559             new_mode[-1] != ' ' ||
4560             old_rev[-1]  != ' ' ||
4561             new_rev[-1]  != ' ' ||
4562             status[-1]   != ' ')
4563                 return FALSE;
4565         file->status = *status;
4567         string_copy_rev(file->old.rev, old_rev);
4568         string_copy_rev(file->new.rev, new_rev);
4570         file->old.mode = strtoul(old_mode, NULL, 8);
4571         file->new.mode = strtoul(new_mode, NULL, 8);
4573         file->old.name[0] = file->new.name[0] = 0;
4575         return TRUE;
4578 static bool
4579 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4581         struct status *file = NULL;
4582         struct status *unmerged = NULL;
4583         char *buf;
4584         struct io io = {};
4586         if (!run_io(&io, argv, NULL, IO_RD))
4587                 return FALSE;
4589         add_line_data(view, NULL, type);
4591         while ((buf = io_get(&io, 0, TRUE))) {
4592                 if (!file) {
4593                         file = calloc(1, sizeof(*file));
4594                         if (!file || !add_line_data(view, file, type))
4595                                 goto error_out;
4596                 }
4598                 /* Parse diff info part. */
4599                 if (status) {
4600                         file->status = status;
4601                         if (status == 'A')
4602                                 string_copy(file->old.rev, NULL_ID);
4604                 } else if (!file->status) {
4605                         if (!status_get_diff(file, buf, strlen(buf)))
4606                                 goto error_out;
4608                         buf = io_get(&io, 0, TRUE);
4609                         if (!buf)
4610                                 break;
4612                         /* Collapse all 'M'odified entries that follow a
4613                          * associated 'U'nmerged entry. */
4614                         if (file->status == 'U') {
4615                                 unmerged = file;
4617                         } else if (unmerged) {
4618                                 int collapse = !strcmp(buf, unmerged->new.name);
4620                                 unmerged = NULL;
4621                                 if (collapse) {
4622                                         free(file);
4623                                         file = NULL;
4624                                         view->lines--;
4625                                         continue;
4626                                 }
4627                         }
4628                 }
4630                 /* Grab the old name for rename/copy. */
4631                 if (!*file->old.name &&
4632                     (file->status == 'R' || file->status == 'C')) {
4633                         string_ncopy(file->old.name, buf, strlen(buf));
4635                         buf = io_get(&io, 0, TRUE);
4636                         if (!buf)
4637                                 break;
4638                 }
4640                 /* git-ls-files just delivers a NUL separated list of
4641                  * file names similar to the second half of the
4642                  * git-diff-* output. */
4643                 string_ncopy(file->new.name, buf, strlen(buf));
4644                 if (!*file->old.name)
4645                         string_copy(file->old.name, file->new.name);
4646                 file = NULL;
4647         }
4649         if (io_error(&io)) {
4650 error_out:
4651                 done_io(&io);
4652                 return FALSE;
4653         }
4655         if (!view->line[view->lines - 1].data)
4656                 add_line_data(view, NULL, LINE_STAT_NONE);
4658         done_io(&io);
4659         return TRUE;
4662 /* Don't show unmerged entries in the staged section. */
4663 static const char *status_diff_index_argv[] = {
4664         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4665                              "--cached", "-M", "HEAD", NULL
4666 };
4668 static const char *status_diff_files_argv[] = {
4669         "git", "diff-files", "-z", NULL
4670 };
4672 static const char *status_list_other_argv[] = {
4673         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4674 };
4676 static const char *status_list_no_head_argv[] = {
4677         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4678 };
4680 static const char *update_index_argv[] = {
4681         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4682 };
4684 /* Restore the previous line number to stay in the context or select a
4685  * line with something that can be updated. */
4686 static void
4687 status_restore(struct view *view)
4689         if (view->p_lineno >= view->lines)
4690                 view->p_lineno = view->lines - 1;
4691         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4692                 view->p_lineno++;
4693         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4694                 view->p_lineno--;
4696         /* If the above fails, always skip the "On branch" line. */
4697         if (view->p_lineno < view->lines)
4698                 view->lineno = view->p_lineno;
4699         else
4700                 view->lineno = 1;
4702         if (view->lineno < view->offset)
4703                 view->offset = view->lineno;
4704         else if (view->offset + view->height <= view->lineno)
4705                 view->offset = view->lineno - view->height + 1;
4707         view->p_restore = FALSE;
4710 /* First parse staged info using git-diff-index(1), then parse unstaged
4711  * info using git-diff-files(1), and finally untracked files using
4712  * git-ls-files(1). */
4713 static bool
4714 status_open(struct view *view)
4716         reset_view(view);
4718         add_line_data(view, NULL, LINE_STAT_HEAD);
4719         if (is_initial_commit())
4720                 string_copy(status_onbranch, "Initial commit");
4721         else if (!*opt_head)
4722                 string_copy(status_onbranch, "Not currently on any branch");
4723         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4724                 return FALSE;
4726         run_io_bg(update_index_argv);
4728         if (is_initial_commit()) {
4729                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4730                         return FALSE;
4731         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4732                 return FALSE;
4733         }
4735         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4736             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4737                 return FALSE;
4739         /* Restore the exact position or use the specialized restore
4740          * mode? */
4741         if (!view->p_restore)
4742                 status_restore(view);
4743         return TRUE;
4746 static bool
4747 status_draw(struct view *view, struct line *line, unsigned int lineno)
4749         struct status *status = line->data;
4750         enum line_type type;
4751         const char *text;
4753         if (!status) {
4754                 switch (line->type) {
4755                 case LINE_STAT_STAGED:
4756                         type = LINE_STAT_SECTION;
4757                         text = "Changes to be committed:";
4758                         break;
4760                 case LINE_STAT_UNSTAGED:
4761                         type = LINE_STAT_SECTION;
4762                         text = "Changed but not updated:";
4763                         break;
4765                 case LINE_STAT_UNTRACKED:
4766                         type = LINE_STAT_SECTION;
4767                         text = "Untracked files:";
4768                         break;
4770                 case LINE_STAT_NONE:
4771                         type = LINE_DEFAULT;
4772                         text = "    (no files)";
4773                         break;
4775                 case LINE_STAT_HEAD:
4776                         type = LINE_STAT_HEAD;
4777                         text = status_onbranch;
4778                         break;
4780                 default:
4781                         return FALSE;
4782                 }
4783         } else {
4784                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4786                 buf[0] = status->status;
4787                 if (draw_text(view, line->type, buf, TRUE))
4788                         return TRUE;
4789                 type = LINE_DEFAULT;
4790                 text = status->new.name;
4791         }
4793         draw_text(view, type, text, TRUE);
4794         return TRUE;
4797 static enum request
4798 status_enter(struct view *view, struct line *line)
4800         struct status *status = line->data;
4801         const char *oldpath = status ? status->old.name : NULL;
4802         /* Diffs for unmerged entries are empty when passing the new
4803          * path, so leave it empty. */
4804         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4805         const char *info;
4806         enum open_flags split;
4807         struct view *stage = VIEW(REQ_VIEW_STAGE);
4809         if (line->type == LINE_STAT_NONE ||
4810             (!status && line[1].type == LINE_STAT_NONE)) {
4811                 report("No file to diff");
4812                 return REQ_NONE;
4813         }
4815         switch (line->type) {
4816         case LINE_STAT_STAGED:
4817                 if (is_initial_commit()) {
4818                         const char *no_head_diff_argv[] = {
4819                                 "git", "diff", "--no-color", "--patch-with-stat",
4820                                         "--", "/dev/null", newpath, NULL
4821                         };
4823                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4824                                 return REQ_QUIT;
4825                 } else {
4826                         const char *index_show_argv[] = {
4827                                 "git", "diff-index", "--root", "--patch-with-stat",
4828                                         "-C", "-M", "--cached", "HEAD", "--",
4829                                         oldpath, newpath, NULL
4830                         };
4832                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4833                                 return REQ_QUIT;
4834                 }
4836                 if (status)
4837                         info = "Staged changes to %s";
4838                 else
4839                         info = "Staged changes";
4840                 break;
4842         case LINE_STAT_UNSTAGED:
4843         {
4844                 const char *files_show_argv[] = {
4845                         "git", "diff-files", "--root", "--patch-with-stat",
4846                                 "-C", "-M", "--", oldpath, newpath, NULL
4847                 };
4849                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4850                         return REQ_QUIT;
4851                 if (status)
4852                         info = "Unstaged changes to %s";
4853                 else
4854                         info = "Unstaged changes";
4855                 break;
4856         }
4857         case LINE_STAT_UNTRACKED:
4858                 if (!newpath) {
4859                         report("No file to show");
4860                         return REQ_NONE;
4861                 }
4863                 if (!suffixcmp(status->new.name, -1, "/")) {
4864                         report("Cannot display a directory");
4865                         return REQ_NONE;
4866                 }
4868                 if (!prepare_update_file(stage, newpath))
4869                         return REQ_QUIT;
4870                 info = "Untracked file %s";
4871                 break;
4873         case LINE_STAT_HEAD:
4874                 return REQ_NONE;
4876         default:
4877                 die("line type %d not handled in switch", line->type);
4878         }
4880         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4881         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4882         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4883                 if (status) {
4884                         stage_status = *status;
4885                 } else {
4886                         memset(&stage_status, 0, sizeof(stage_status));
4887                 }
4889                 stage_line_type = line->type;
4890                 stage_chunks = 0;
4891                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4892         }
4894         return REQ_NONE;
4897 static bool
4898 status_exists(struct status *status, enum line_type type)
4900         struct view *view = VIEW(REQ_VIEW_STATUS);
4901         unsigned long lineno;
4903         for (lineno = 0; lineno < view->lines; lineno++) {
4904                 struct line *line = &view->line[lineno];
4905                 struct status *pos = line->data;
4907                 if (line->type != type)
4908                         continue;
4909                 if (!pos && (!status || !status->status) && line[1].data) {
4910                         select_view_line(view, lineno);
4911                         return TRUE;
4912                 }
4913                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4914                         select_view_line(view, lineno);
4915                         return TRUE;
4916                 }
4917         }
4919         return FALSE;
4923 static bool
4924 status_update_prepare(struct io *io, enum line_type type)
4926         const char *staged_argv[] = {
4927                 "git", "update-index", "-z", "--index-info", NULL
4928         };
4929         const char *others_argv[] = {
4930                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4931         };
4933         switch (type) {
4934         case LINE_STAT_STAGED:
4935                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4937         case LINE_STAT_UNSTAGED:
4938                 return run_io(io, others_argv, opt_cdup, IO_WR);
4940         case LINE_STAT_UNTRACKED:
4941                 return run_io(io, others_argv, NULL, IO_WR);
4943         default:
4944                 die("line type %d not handled in switch", type);
4945                 return FALSE;
4946         }
4949 static bool
4950 status_update_write(struct io *io, struct status *status, enum line_type type)
4952         char buf[SIZEOF_STR];
4953         size_t bufsize = 0;
4955         switch (type) {
4956         case LINE_STAT_STAGED:
4957                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4958                                         status->old.mode,
4959                                         status->old.rev,
4960                                         status->old.name, 0))
4961                         return FALSE;
4962                 break;
4964         case LINE_STAT_UNSTAGED:
4965         case LINE_STAT_UNTRACKED:
4966                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4967                         return FALSE;
4968                 break;
4970         default:
4971                 die("line type %d not handled in switch", type);
4972         }
4974         return io_write(io, buf, bufsize);
4977 static bool
4978 status_update_file(struct status *status, enum line_type type)
4980         struct io io = {};
4981         bool result;
4983         if (!status_update_prepare(&io, type))
4984                 return FALSE;
4986         result = status_update_write(&io, status, type);
4987         done_io(&io);
4988         return result;
4991 static bool
4992 status_update_files(struct view *view, struct line *line)
4994         struct io io = {};
4995         bool result = TRUE;
4996         struct line *pos = view->line + view->lines;
4997         int files = 0;
4998         int file, done;
5000         if (!status_update_prepare(&io, line->type))
5001                 return FALSE;
5003         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5004                 files++;
5006         for (file = 0, done = 0; result && file < files; line++, file++) {
5007                 int almost_done = file * 100 / files;
5009                 if (almost_done > done) {
5010                         done = almost_done;
5011                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5012                                       file, files, done);
5013                         update_view_title(view);
5014                 }
5015                 result = status_update_write(&io, line->data, line->type);
5016         }
5018         done_io(&io);
5019         return result;
5022 static bool
5023 status_update(struct view *view)
5025         struct line *line = &view->line[view->lineno];
5027         assert(view->lines);
5029         if (!line->data) {
5030                 /* This should work even for the "On branch" line. */
5031                 if (line < view->line + view->lines && !line[1].data) {
5032                         report("Nothing to update");
5033                         return FALSE;
5034                 }
5036                 if (!status_update_files(view, line + 1)) {
5037                         report("Failed to update file status");
5038                         return FALSE;
5039                 }
5041         } else if (!status_update_file(line->data, line->type)) {
5042                 report("Failed to update file status");
5043                 return FALSE;
5044         }
5046         return TRUE;
5049 static bool
5050 status_revert(struct status *status, enum line_type type, bool has_none)
5052         if (!status || type != LINE_STAT_UNSTAGED) {
5053                 if (type == LINE_STAT_STAGED) {
5054                         report("Cannot revert changes to staged files");
5055                 } else if (type == LINE_STAT_UNTRACKED) {
5056                         report("Cannot revert changes to untracked files");
5057                 } else if (has_none) {
5058                         report("Nothing to revert");
5059                 } else {
5060                         report("Cannot revert changes to multiple files");
5061                 }
5062                 return FALSE;
5064         } else {
5065                 const char *checkout_argv[] = {
5066                         "git", "checkout", "--", status->old.name, NULL
5067                 };
5069                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5070                         return FALSE;
5071                 return run_io_fg(checkout_argv, opt_cdup);
5072         }
5075 static enum request
5076 status_request(struct view *view, enum request request, struct line *line)
5078         struct status *status = line->data;
5080         switch (request) {
5081         case REQ_STATUS_UPDATE:
5082                 if (!status_update(view))
5083                         return REQ_NONE;
5084                 break;
5086         case REQ_STATUS_REVERT:
5087                 if (!status_revert(status, line->type, status_has_none(view, line)))
5088                         return REQ_NONE;
5089                 break;
5091         case REQ_STATUS_MERGE:
5092                 if (!status || status->status != 'U') {
5093                         report("Merging only possible for files with unmerged status ('U').");
5094                         return REQ_NONE;
5095                 }
5096                 open_mergetool(status->new.name);
5097                 break;
5099         case REQ_EDIT:
5100                 if (!status)
5101                         return request;
5102                 if (status->status == 'D') {
5103                         report("File has been deleted.");
5104                         return REQ_NONE;
5105                 }
5107                 open_editor(status->status != '?', status->new.name);
5108                 break;
5110         case REQ_VIEW_BLAME:
5111                 if (status) {
5112                         string_copy(opt_file, status->new.name);
5113                         opt_ref[0] = 0;
5114                 }
5115                 return request;
5117         case REQ_ENTER:
5118                 /* After returning the status view has been split to
5119                  * show the stage view. No further reloading is
5120                  * necessary. */
5121                 status_enter(view, line);
5122                 return REQ_NONE;
5124         case REQ_REFRESH:
5125                 /* Simply reload the view. */
5126                 break;
5128         default:
5129                 return request;
5130         }
5132         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5134         return REQ_NONE;
5137 static void
5138 status_select(struct view *view, struct line *line)
5140         struct status *status = line->data;
5141         char file[SIZEOF_STR] = "all files";
5142         const char *text;
5143         const char *key;
5145         if (status && !string_format(file, "'%s'", status->new.name))
5146                 return;
5148         if (!status && line[1].type == LINE_STAT_NONE)
5149                 line++;
5151         switch (line->type) {
5152         case LINE_STAT_STAGED:
5153                 text = "Press %s to unstage %s for commit";
5154                 break;
5156         case LINE_STAT_UNSTAGED:
5157                 text = "Press %s to stage %s for commit";
5158                 break;
5160         case LINE_STAT_UNTRACKED:
5161                 text = "Press %s to stage %s for addition";
5162                 break;
5164         case LINE_STAT_HEAD:
5165         case LINE_STAT_NONE:
5166                 text = "Nothing to update";
5167                 break;
5169         default:
5170                 die("line type %d not handled in switch", line->type);
5171         }
5173         if (status && status->status == 'U') {
5174                 text = "Press %s to resolve conflict in %s";
5175                 key = get_key(REQ_STATUS_MERGE);
5177         } else {
5178                 key = get_key(REQ_STATUS_UPDATE);
5179         }
5181         string_format(view->ref, text, key, file);
5184 static bool
5185 status_grep(struct view *view, struct line *line)
5187         struct status *status = line->data;
5188         enum { S_STATUS, S_NAME, S_END } state;
5189         char buf[2] = "?";
5190         regmatch_t pmatch;
5192         if (!status)
5193                 return FALSE;
5195         for (state = S_STATUS; state < S_END; state++) {
5196                 const char *text;
5198                 switch (state) {
5199                 case S_NAME:    text = status->new.name;        break;
5200                 case S_STATUS:
5201                         buf[0] = status->status;
5202                         text = buf;
5203                         break;
5205                 default:
5206                         return FALSE;
5207                 }
5209                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5210                         return TRUE;
5211         }
5213         return FALSE;
5216 static struct view_ops status_ops = {
5217         "file",
5218         NULL,
5219         status_open,
5220         NULL,
5221         status_draw,
5222         status_request,
5223         status_grep,
5224         status_select,
5225 };
5228 static bool
5229 stage_diff_write(struct io *io, struct line *line, struct line *end)
5231         while (line < end) {
5232                 if (!io_write(io, line->data, strlen(line->data)) ||
5233                     !io_write(io, "\n", 1))
5234                         return FALSE;
5235                 line++;
5236                 if (line->type == LINE_DIFF_CHUNK ||
5237                     line->type == LINE_DIFF_HEADER)
5238                         break;
5239         }
5241         return TRUE;
5244 static struct line *
5245 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5247         for (; view->line < line; line--)
5248                 if (line->type == type)
5249                         return line;
5251         return NULL;
5254 static bool
5255 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5257         const char *apply_argv[SIZEOF_ARG] = {
5258                 "git", "apply", "--whitespace=nowarn", NULL
5259         };
5260         struct line *diff_hdr;
5261         struct io io = {};
5262         int argc = 3;
5264         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5265         if (!diff_hdr)
5266                 return FALSE;
5268         if (!revert)
5269                 apply_argv[argc++] = "--cached";
5270         if (revert || stage_line_type == LINE_STAT_STAGED)
5271                 apply_argv[argc++] = "-R";
5272         apply_argv[argc++] = "-";
5273         apply_argv[argc++] = NULL;
5274         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5275                 return FALSE;
5277         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5278             !stage_diff_write(&io, chunk, view->line + view->lines))
5279                 chunk = NULL;
5281         done_io(&io);
5282         run_io_bg(update_index_argv);
5284         return chunk ? TRUE : FALSE;
5287 static bool
5288 stage_update(struct view *view, struct line *line)
5290         struct line *chunk = NULL;
5292         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5293                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5295         if (chunk) {
5296                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5297                         report("Failed to apply chunk");
5298                         return FALSE;
5299                 }
5301         } else if (!stage_status.status) {
5302                 view = VIEW(REQ_VIEW_STATUS);
5304                 for (line = view->line; line < view->line + view->lines; line++)
5305                         if (line->type == stage_line_type)
5306                                 break;
5308                 if (!status_update_files(view, line + 1)) {
5309                         report("Failed to update files");
5310                         return FALSE;
5311                 }
5313         } else if (!status_update_file(&stage_status, stage_line_type)) {
5314                 report("Failed to update file");
5315                 return FALSE;
5316         }
5318         return TRUE;
5321 static bool
5322 stage_revert(struct view *view, struct line *line)
5324         struct line *chunk = NULL;
5326         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5327                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5329         if (chunk) {
5330                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5331                         return FALSE;
5333                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5334                         report("Failed to revert chunk");
5335                         return FALSE;
5336                 }
5337                 return TRUE;
5339         } else {
5340                 return status_revert(stage_status.status ? &stage_status : NULL,
5341                                      stage_line_type, FALSE);
5342         }
5346 static void
5347 stage_next(struct view *view, struct line *line)
5349         int i;
5351         if (!stage_chunks) {
5352                 static size_t alloc = 0;
5353                 int *tmp;
5355                 for (line = view->line; line < view->line + view->lines; line++) {
5356                         if (line->type != LINE_DIFF_CHUNK)
5357                                 continue;
5359                         tmp = realloc_items(stage_chunk, &alloc,
5360                                             stage_chunks, sizeof(*tmp));
5361                         if (!tmp) {
5362                                 report("Allocation failure");
5363                                 return;
5364                         }
5366                         stage_chunk = tmp;
5367                         stage_chunk[stage_chunks++] = line - view->line;
5368                 }
5369         }
5371         for (i = 0; i < stage_chunks; i++) {
5372                 if (stage_chunk[i] > view->lineno) {
5373                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5374                         report("Chunk %d of %d", i + 1, stage_chunks);
5375                         return;
5376                 }
5377         }
5379         report("No next chunk found");
5382 static enum request
5383 stage_request(struct view *view, enum request request, struct line *line)
5385         switch (request) {
5386         case REQ_STATUS_UPDATE:
5387                 if (!stage_update(view, line))
5388                         return REQ_NONE;
5389                 break;
5391         case REQ_STATUS_REVERT:
5392                 if (!stage_revert(view, line))
5393                         return REQ_NONE;
5394                 break;
5396         case REQ_STAGE_NEXT:
5397                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5398                         report("File is untracked; press %s to add",
5399                                get_key(REQ_STATUS_UPDATE));
5400                         return REQ_NONE;
5401                 }
5402                 stage_next(view, line);
5403                 return REQ_NONE;
5405         case REQ_EDIT:
5406                 if (!stage_status.new.name[0])
5407                         return request;
5408                 if (stage_status.status == 'D') {
5409                         report("File has been deleted.");
5410                         return REQ_NONE;
5411                 }
5413                 open_editor(stage_status.status != '?', stage_status.new.name);
5414                 break;
5416         case REQ_REFRESH:
5417                 /* Reload everything ... */
5418                 break;
5420         case REQ_VIEW_BLAME:
5421                 if (stage_status.new.name[0]) {
5422                         string_copy(opt_file, stage_status.new.name);
5423                         opt_ref[0] = 0;
5424                 }
5425                 return request;
5427         case REQ_ENTER:
5428                 return pager_request(view, request, line);
5430         default:
5431                 return request;
5432         }
5434         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5435         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5437         /* Check whether the staged entry still exists, and close the
5438          * stage view if it doesn't. */
5439         if (!status_exists(&stage_status, stage_line_type)) {
5440                 status_restore(VIEW(REQ_VIEW_STATUS));
5441                 return REQ_VIEW_CLOSE;
5442         }
5444         if (stage_line_type == LINE_STAT_UNTRACKED) {
5445                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5446                         report("Cannot display a directory");
5447                         return REQ_NONE;
5448                 }
5450                 if (!prepare_update_file(view, stage_status.new.name)) {
5451                         report("Failed to open file: %s", strerror(errno));
5452                         return REQ_NONE;
5453                 }
5454         }
5455         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5457         return REQ_NONE;
5460 static struct view_ops stage_ops = {
5461         "line",
5462         NULL,
5463         NULL,
5464         pager_read,
5465         pager_draw,
5466         stage_request,
5467         pager_grep,
5468         pager_select,
5469 };
5472 /*
5473  * Revision graph
5474  */
5476 struct commit {
5477         char id[SIZEOF_REV];            /* SHA1 ID. */
5478         char title[128];                /* First line of the commit message. */
5479         char author[75];                /* Author of the commit. */
5480         struct tm time;                 /* Date from the author ident. */
5481         struct ref **refs;              /* Repository references. */
5482         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5483         size_t graph_size;              /* The width of the graph array. */
5484         bool has_parents;               /* Rewritten --parents seen. */
5485 };
5487 /* Size of rev graph with no  "padding" columns */
5488 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5490 struct rev_graph {
5491         struct rev_graph *prev, *next, *parents;
5492         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5493         size_t size;
5494         struct commit *commit;
5495         size_t pos;
5496         unsigned int boundary:1;
5497 };
5499 /* Parents of the commit being visualized. */
5500 static struct rev_graph graph_parents[4];
5502 /* The current stack of revisions on the graph. */
5503 static struct rev_graph graph_stacks[4] = {
5504         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5505         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5506         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5507         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5508 };
5510 static inline bool
5511 graph_parent_is_merge(struct rev_graph *graph)
5513         return graph->parents->size > 1;
5516 static inline void
5517 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5519         struct commit *commit = graph->commit;
5521         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5522                 commit->graph[commit->graph_size++] = symbol;
5525 static void
5526 clear_rev_graph(struct rev_graph *graph)
5528         graph->boundary = 0;
5529         graph->size = graph->pos = 0;
5530         graph->commit = NULL;
5531         memset(graph->parents, 0, sizeof(*graph->parents));
5534 static void
5535 done_rev_graph(struct rev_graph *graph)
5537         if (graph_parent_is_merge(graph) &&
5538             graph->pos < graph->size - 1 &&
5539             graph->next->size == graph->size + graph->parents->size - 1) {
5540                 size_t i = graph->pos + graph->parents->size - 1;
5542                 graph->commit->graph_size = i * 2;
5543                 while (i < graph->next->size - 1) {
5544                         append_to_rev_graph(graph, ' ');
5545                         append_to_rev_graph(graph, '\\');
5546                         i++;
5547                 }
5548         }
5550         clear_rev_graph(graph);
5553 static void
5554 push_rev_graph(struct rev_graph *graph, const char *parent)
5556         int i;
5558         /* "Collapse" duplicate parents lines.
5559          *
5560          * FIXME: This needs to also update update the drawn graph but
5561          * for now it just serves as a method for pruning graph lines. */
5562         for (i = 0; i < graph->size; i++)
5563                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5564                         return;
5566         if (graph->size < SIZEOF_REVITEMS) {
5567                 string_copy_rev(graph->rev[graph->size++], parent);
5568         }
5571 static chtype
5572 get_rev_graph_symbol(struct rev_graph *graph)
5574         chtype symbol;
5576         if (graph->boundary)
5577                 symbol = REVGRAPH_BOUND;
5578         else if (graph->parents->size == 0)
5579                 symbol = REVGRAPH_INIT;
5580         else if (graph_parent_is_merge(graph))
5581                 symbol = REVGRAPH_MERGE;
5582         else if (graph->pos >= graph->size)
5583                 symbol = REVGRAPH_BRANCH;
5584         else
5585                 symbol = REVGRAPH_COMMIT;
5587         return symbol;
5590 static void
5591 draw_rev_graph(struct rev_graph *graph)
5593         struct rev_filler {
5594                 chtype separator, line;
5595         };
5596         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5597         static struct rev_filler fillers[] = {
5598                 { ' ',  '|' },
5599                 { '`',  '.' },
5600                 { '\'', ' ' },
5601                 { '/',  ' ' },
5602         };
5603         chtype symbol = get_rev_graph_symbol(graph);
5604         struct rev_filler *filler;
5605         size_t i;
5607         if (opt_line_graphics)
5608                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5610         filler = &fillers[DEFAULT];
5612         for (i = 0; i < graph->pos; i++) {
5613                 append_to_rev_graph(graph, filler->line);
5614                 if (graph_parent_is_merge(graph->prev) &&
5615                     graph->prev->pos == i)
5616                         filler = &fillers[RSHARP];
5618                 append_to_rev_graph(graph, filler->separator);
5619         }
5621         /* Place the symbol for this revision. */
5622         append_to_rev_graph(graph, symbol);
5624         if (graph->prev->size > graph->size)
5625                 filler = &fillers[RDIAG];
5626         else
5627                 filler = &fillers[DEFAULT];
5629         i++;
5631         for (; i < graph->size; i++) {
5632                 append_to_rev_graph(graph, filler->separator);
5633                 append_to_rev_graph(graph, filler->line);
5634                 if (graph_parent_is_merge(graph->prev) &&
5635                     i < graph->prev->pos + graph->parents->size)
5636                         filler = &fillers[RSHARP];
5637                 if (graph->prev->size > graph->size)
5638                         filler = &fillers[LDIAG];
5639         }
5641         if (graph->prev->size > graph->size) {
5642                 append_to_rev_graph(graph, filler->separator);
5643                 if (filler->line != ' ')
5644                         append_to_rev_graph(graph, filler->line);
5645         }
5648 /* Prepare the next rev graph */
5649 static void
5650 prepare_rev_graph(struct rev_graph *graph)
5652         size_t i;
5654         /* First, traverse all lines of revisions up to the active one. */
5655         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5656                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5657                         break;
5659                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5660         }
5662         /* Interleave the new revision parent(s). */
5663         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5664                 push_rev_graph(graph->next, graph->parents->rev[i]);
5666         /* Lastly, put any remaining revisions. */
5667         for (i = graph->pos + 1; i < graph->size; i++)
5668                 push_rev_graph(graph->next, graph->rev[i]);
5671 static void
5672 update_rev_graph(struct view *view, struct rev_graph *graph)
5674         /* If this is the finalizing update ... */
5675         if (graph->commit)
5676                 prepare_rev_graph(graph);
5678         /* Graph visualization needs a one rev look-ahead,
5679          * so the first update doesn't visualize anything. */
5680         if (!graph->prev->commit)
5681                 return;
5683         if (view->lines > 2)
5684                 view->line[view->lines - 3].dirty = 1;
5685         if (view->lines > 1)
5686                 view->line[view->lines - 2].dirty = 1;
5687         draw_rev_graph(graph->prev);
5688         done_rev_graph(graph->prev->prev);
5692 /*
5693  * Main view backend
5694  */
5696 static const char *main_argv[SIZEOF_ARG] = {
5697         "git", "log", "--no-color", "--pretty=raw", "--parents",
5698                       "--topo-order", "%(head)", NULL
5699 };
5701 static bool
5702 main_draw(struct view *view, struct line *line, unsigned int lineno)
5704         struct commit *commit = line->data;
5706         if (!*commit->author)
5707                 return FALSE;
5709         if (opt_date && draw_date(view, &commit->time))
5710                 return TRUE;
5712         if (opt_author &&
5713             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5714                 return TRUE;
5716         if (opt_rev_graph && commit->graph_size &&
5717             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5718                 return TRUE;
5720         if (opt_show_refs && commit->refs) {
5721                 size_t i = 0;
5723                 do {
5724                         enum line_type type;
5726                         if (commit->refs[i]->head)
5727                                 type = LINE_MAIN_HEAD;
5728                         else if (commit->refs[i]->ltag)
5729                                 type = LINE_MAIN_LOCAL_TAG;
5730                         else if (commit->refs[i]->tag)
5731                                 type = LINE_MAIN_TAG;
5732                         else if (commit->refs[i]->tracked)
5733                                 type = LINE_MAIN_TRACKED;
5734                         else if (commit->refs[i]->remote)
5735                                 type = LINE_MAIN_REMOTE;
5736                         else
5737                                 type = LINE_MAIN_REF;
5739                         if (draw_text(view, type, "[", TRUE) ||
5740                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5741                             draw_text(view, type, "]", TRUE))
5742                                 return TRUE;
5744                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5745                                 return TRUE;
5746                 } while (commit->refs[i++]->next);
5747         }
5749         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5750         return TRUE;
5753 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5754 static bool
5755 main_read(struct view *view, char *line)
5757         static struct rev_graph *graph = graph_stacks;
5758         enum line_type type;
5759         struct commit *commit;
5761         if (!line) {
5762                 int i;
5764                 if (!view->lines && !view->parent)
5765                         die("No revisions match the given arguments.");
5766                 if (view->lines > 0) {
5767                         commit = view->line[view->lines - 1].data;
5768                         view->line[view->lines - 1].dirty = 1;
5769                         if (!*commit->author) {
5770                                 view->lines--;
5771                                 free(commit);
5772                                 graph->commit = NULL;
5773                         }
5774                 }
5775                 update_rev_graph(view, graph);
5777                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5778                         clear_rev_graph(&graph_stacks[i]);
5779                 return TRUE;
5780         }
5782         type = get_line_type(line);
5783         if (type == LINE_COMMIT) {
5784                 commit = calloc(1, sizeof(struct commit));
5785                 if (!commit)
5786                         return FALSE;
5788                 line += STRING_SIZE("commit ");
5789                 if (*line == '-') {
5790                         graph->boundary = 1;
5791                         line++;
5792                 }
5794                 string_copy_rev(commit->id, line);
5795                 commit->refs = get_refs(commit->id);
5796                 graph->commit = commit;
5797                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5799                 while ((line = strchr(line, ' '))) {
5800                         line++;
5801                         push_rev_graph(graph->parents, line);
5802                         commit->has_parents = TRUE;
5803                 }
5804                 return TRUE;
5805         }
5807         if (!view->lines)
5808                 return TRUE;
5809         commit = view->line[view->lines - 1].data;
5811         switch (type) {
5812         case LINE_PARENT:
5813                 if (commit->has_parents)
5814                         break;
5815                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5816                 break;
5818         case LINE_AUTHOR:
5819                 parse_author_line(line + STRING_SIZE("author "),
5820                                   commit->author, sizeof(commit->author),
5821                                   &commit->time);
5822                 update_rev_graph(view, graph);
5823                 graph = graph->next;
5824                 break;
5826         default:
5827                 /* Fill in the commit title if it has not already been set. */
5828                 if (commit->title[0])
5829                         break;
5831                 /* Require titles to start with a non-space character at the
5832                  * offset used by git log. */
5833                 if (strncmp(line, "    ", 4))
5834                         break;
5835                 line += 4;
5836                 /* Well, if the title starts with a whitespace character,
5837                  * try to be forgiving.  Otherwise we end up with no title. */
5838                 while (isspace(*line))
5839                         line++;
5840                 if (*line == '\0')
5841                         break;
5842                 /* FIXME: More graceful handling of titles; append "..." to
5843                  * shortened titles, etc. */
5845                 string_ncopy(commit->title, line, strlen(line));
5846                 view->line[view->lines - 1].dirty = 1;
5847         }
5849         return TRUE;
5852 static enum request
5853 main_request(struct view *view, enum request request, struct line *line)
5855         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5857         switch (request) {
5858         case REQ_ENTER:
5859                 open_view(view, REQ_VIEW_DIFF, flags);
5860                 break;
5861         case REQ_REFRESH:
5862                 load_refs();
5863                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5864                 break;
5865         default:
5866                 return request;
5867         }
5869         return REQ_NONE;
5872 static bool
5873 grep_refs(struct ref **refs, regex_t *regex)
5875         regmatch_t pmatch;
5876         size_t i = 0;
5878         if (!refs)
5879                 return FALSE;
5880         do {
5881                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5882                         return TRUE;
5883         } while (refs[i++]->next);
5885         return FALSE;
5888 static bool
5889 main_grep(struct view *view, struct line *line)
5891         struct commit *commit = line->data;
5892         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5893         char buf[DATE_COLS + 1];
5894         regmatch_t pmatch;
5896         for (state = S_TITLE; state < S_END; state++) {
5897                 char *text;
5899                 switch (state) {
5900                 case S_TITLE:   text = commit->title;   break;
5901                 case S_AUTHOR:
5902                         if (!opt_author)
5903                                 continue;
5904                         text = commit->author;
5905                         break;
5906                 case S_DATE:
5907                         if (!opt_date)
5908                                 continue;
5909                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5910                                 continue;
5911                         text = buf;
5912                         break;
5913                 case S_REFS:
5914                         if (!opt_show_refs)
5915                                 continue;
5916                         if (grep_refs(commit->refs, view->regex) == TRUE)
5917                                 return TRUE;
5918                         continue;
5919                 default:
5920                         return FALSE;
5921                 }
5923                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5924                         return TRUE;
5925         }
5927         return FALSE;
5930 static void
5931 main_select(struct view *view, struct line *line)
5933         struct commit *commit = line->data;
5935         string_copy_rev(view->ref, commit->id);
5936         string_copy_rev(ref_commit, view->ref);
5939 static struct view_ops main_ops = {
5940         "commit",
5941         main_argv,
5942         NULL,
5943         main_read,
5944         main_draw,
5945         main_request,
5946         main_grep,
5947         main_select,
5948 };
5951 /*
5952  * Unicode / UTF-8 handling
5953  *
5954  * NOTE: Much of the following code for dealing with unicode is derived from
5955  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5956  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5957  */
5959 /* I've (over)annotated a lot of code snippets because I am not entirely
5960  * confident that the approach taken by this small UTF-8 interface is correct.
5961  * --jonas */
5963 static inline int
5964 unicode_width(unsigned long c)
5966         if (c >= 0x1100 &&
5967            (c <= 0x115f                         /* Hangul Jamo */
5968             || c == 0x2329
5969             || c == 0x232a
5970             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5971                                                 /* CJK ... Yi */
5972             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5973             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5974             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5975             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5976             || (c >= 0xffe0  && c <= 0xffe6)
5977             || (c >= 0x20000 && c <= 0x2fffd)
5978             || (c >= 0x30000 && c <= 0x3fffd)))
5979                 return 2;
5981         if (c == '\t')
5982                 return opt_tab_size;
5984         return 1;
5987 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5988  * Illegal bytes are set one. */
5989 static const unsigned char utf8_bytes[256] = {
5990         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,
5991         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,
5992         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5993         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5994         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5995         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5996         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,
5997         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,
5998 };
6000 /* Decode UTF-8 multi-byte representation into a unicode character. */
6001 static inline unsigned long
6002 utf8_to_unicode(const char *string, size_t length)
6004         unsigned long unicode;
6006         switch (length) {
6007         case 1:
6008                 unicode  =   string[0];
6009                 break;
6010         case 2:
6011                 unicode  =  (string[0] & 0x1f) << 6;
6012                 unicode +=  (string[1] & 0x3f);
6013                 break;
6014         case 3:
6015                 unicode  =  (string[0] & 0x0f) << 12;
6016                 unicode += ((string[1] & 0x3f) << 6);
6017                 unicode +=  (string[2] & 0x3f);
6018                 break;
6019         case 4:
6020                 unicode  =  (string[0] & 0x0f) << 18;
6021                 unicode += ((string[1] & 0x3f) << 12);
6022                 unicode += ((string[2] & 0x3f) << 6);
6023                 unicode +=  (string[3] & 0x3f);
6024                 break;
6025         case 5:
6026                 unicode  =  (string[0] & 0x0f) << 24;
6027                 unicode += ((string[1] & 0x3f) << 18);
6028                 unicode += ((string[2] & 0x3f) << 12);
6029                 unicode += ((string[3] & 0x3f) << 6);
6030                 unicode +=  (string[4] & 0x3f);
6031                 break;
6032         case 6:
6033                 unicode  =  (string[0] & 0x01) << 30;
6034                 unicode += ((string[1] & 0x3f) << 24);
6035                 unicode += ((string[2] & 0x3f) << 18);
6036                 unicode += ((string[3] & 0x3f) << 12);
6037                 unicode += ((string[4] & 0x3f) << 6);
6038                 unicode +=  (string[5] & 0x3f);
6039                 break;
6040         default:
6041                 die("Invalid unicode length");
6042         }
6044         /* Invalid characters could return the special 0xfffd value but NUL
6045          * should be just as good. */
6046         return unicode > 0xffff ? 0 : unicode;
6049 /* Calculates how much of string can be shown within the given maximum width
6050  * and sets trimmed parameter to non-zero value if all of string could not be
6051  * shown. If the reserve flag is TRUE, it will reserve at least one
6052  * trailing character, which can be useful when drawing a delimiter.
6053  *
6054  * Returns the number of bytes to output from string to satisfy max_width. */
6055 static size_t
6056 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6058         const char *start = string;
6059         const char *end = strchr(string, '\0');
6060         unsigned char last_bytes = 0;
6061         size_t last_ucwidth = 0;
6063         *width = 0;
6064         *trimmed = 0;
6066         while (string < end) {
6067                 int c = *(unsigned char *) string;
6068                 unsigned char bytes = utf8_bytes[c];
6069                 size_t ucwidth;
6070                 unsigned long unicode;
6072                 if (string + bytes > end)
6073                         break;
6075                 /* Change representation to figure out whether
6076                  * it is a single- or double-width character. */
6078                 unicode = utf8_to_unicode(string, bytes);
6079                 /* FIXME: Graceful handling of invalid unicode character. */
6080                 if (!unicode)
6081                         break;
6083                 ucwidth = unicode_width(unicode);
6084                 *width  += ucwidth;
6085                 if (*width > max_width) {
6086                         *trimmed = 1;
6087                         *width -= ucwidth;
6088                         if (reserve && *width == max_width) {
6089                                 string -= last_bytes;
6090                                 *width -= last_ucwidth;
6091                         }
6092                         break;
6093                 }
6095                 string  += bytes;
6096                 last_bytes = bytes;
6097                 last_ucwidth = ucwidth;
6098         }
6100         return string - start;
6104 /*
6105  * Status management
6106  */
6108 /* Whether or not the curses interface has been initialized. */
6109 static bool cursed = FALSE;
6111 /* The status window is used for polling keystrokes. */
6112 static WINDOW *status_win;
6114 static bool status_empty = TRUE;
6116 /* Update status and title window. */
6117 static void
6118 report(const char *msg, ...)
6120         struct view *view = display[current_view];
6122         if (input_mode)
6123                 return;
6125         if (!view) {
6126                 char buf[SIZEOF_STR];
6127                 va_list args;
6129                 va_start(args, msg);
6130                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6131                         buf[sizeof(buf) - 1] = 0;
6132                         buf[sizeof(buf) - 2] = '.';
6133                         buf[sizeof(buf) - 3] = '.';
6134                         buf[sizeof(buf) - 4] = '.';
6135                 }
6136                 va_end(args);
6137                 die("%s", buf);
6138         }
6140         if (!status_empty || *msg) {
6141                 va_list args;
6143                 va_start(args, msg);
6145                 wmove(status_win, 0, 0);
6146                 if (*msg) {
6147                         vwprintw(status_win, msg, args);
6148                         status_empty = FALSE;
6149                 } else {
6150                         status_empty = TRUE;
6151                 }
6152                 wclrtoeol(status_win);
6153                 wrefresh(status_win);
6155                 va_end(args);
6156         }
6158         update_view_title(view);
6159         update_display_cursor(view);
6162 /* Controls when nodelay should be in effect when polling user input. */
6163 static void
6164 set_nonblocking_input(bool loading)
6166         static unsigned int loading_views;
6168         if ((loading == FALSE && loading_views-- == 1) ||
6169             (loading == TRUE  && loading_views++ == 0))
6170                 nodelay(status_win, loading);
6173 static void
6174 init_display(void)
6176         int x, y;
6178         /* Initialize the curses library */
6179         if (isatty(STDIN_FILENO)) {
6180                 cursed = !!initscr();
6181                 opt_tty = stdin;
6182         } else {
6183                 /* Leave stdin and stdout alone when acting as a pager. */
6184                 opt_tty = fopen("/dev/tty", "r+");
6185                 if (!opt_tty)
6186                         die("Failed to open /dev/tty");
6187                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6188         }
6190         if (!cursed)
6191                 die("Failed to initialize curses");
6193         nonl();         /* Tell curses not to do NL->CR/NL on output */
6194         cbreak();       /* Take input chars one at a time, no wait for \n */
6195         noecho();       /* Don't echo input */
6196         leaveok(stdscr, TRUE);
6198         if (has_colors())
6199                 init_colors();
6201         getmaxyx(stdscr, y, x);
6202         status_win = newwin(1, 0, y - 1, 0);
6203         if (!status_win)
6204                 die("Failed to create status window");
6206         /* Enable keyboard mapping */
6207         keypad(status_win, TRUE);
6208         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6210         TABSIZE = opt_tab_size;
6211         if (opt_line_graphics) {
6212                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6213         }
6216 static int
6217 get_input(bool prompting)
6219         struct view *view;
6220         int i, key;
6222         if (prompting)
6223                 input_mode = TRUE;
6225         while (true) {
6226                 foreach_view (view, i)
6227                         update_view(view);
6229                 /* Refresh, accept single keystroke of input */
6230                 key = wgetch(status_win);
6232                 /* wgetch() with nodelay() enabled returns ERR when
6233                  * there's no input. */
6234                 if (key == ERR) {
6235                         doupdate();
6237                 } else if (key == KEY_RESIZE) {
6238                         int height, width;
6240                         getmaxyx(stdscr, height, width);
6242                         /* Resize the status view and let the view driver take
6243                          * care of resizing the displayed views. */
6244                         resize_display();
6245                         redraw_display(TRUE);
6246                         wresize(status_win, 1, width);
6247                         mvwin(status_win, height - 1, 0);
6248                         wrefresh(status_win);
6250                 } else {
6251                         input_mode = FALSE;
6252                         return key;
6253                 }
6254         }
6257 static char *
6258 prompt_input(const char *prompt, input_handler handler, void *data)
6260         enum input_status status = INPUT_OK;
6261         static char buf[SIZEOF_STR];
6262         size_t pos = 0;
6264         buf[pos] = 0;
6266         while (status == INPUT_OK || status == INPUT_SKIP) {
6267                 int key;
6269                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6270                 wclrtoeol(status_win);
6272                 key = get_input(TRUE);
6273                 switch (key) {
6274                 case KEY_RETURN:
6275                 case KEY_ENTER:
6276                 case '\n':
6277                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6278                         break;
6280                 case KEY_BACKSPACE:
6281                         if (pos > 0)
6282                                 buf[--pos] = 0;
6283                         else
6284                                 status = INPUT_CANCEL;
6285                         break;
6287                 case KEY_ESC:
6288                         status = INPUT_CANCEL;
6289                         break;
6291                 default:
6292                         if (pos >= sizeof(buf)) {
6293                                 report("Input string too long");
6294                                 return NULL;
6295                         }
6297                         status = handler(data, buf, key);
6298                         if (status == INPUT_OK)
6299                                 buf[pos++] = (char) key;
6300                 }
6301         }
6303         /* Clear the status window */
6304         status_empty = FALSE;
6305         report("");
6307         if (status == INPUT_CANCEL)
6308                 return NULL;
6310         buf[pos++] = 0;
6312         return buf;
6315 static enum input_status
6316 prompt_yesno_handler(void *data, char *buf, int c)
6318         if (c == 'y' || c == 'Y')
6319                 return INPUT_STOP;
6320         if (c == 'n' || c == 'N')
6321                 return INPUT_CANCEL;
6322         return INPUT_SKIP;
6325 static bool
6326 prompt_yesno(const char *prompt)
6328         char prompt2[SIZEOF_STR];
6330         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6331                 return FALSE;
6333         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6336 static enum input_status
6337 read_prompt_handler(void *data, char *buf, int c)
6339         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6342 static char *
6343 read_prompt(const char *prompt)
6345         return prompt_input(prompt, read_prompt_handler, NULL);
6348 /*
6349  * Repository properties
6350  */
6352 static int
6353 git_properties(const char **argv, const char *separators,
6354                int (*read_property)(char *, size_t, char *, size_t))
6356         struct io io = {};
6358         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6359                 return read_properties(&io, separators, read_property);
6360         return ERR;
6363 static struct ref *refs = NULL;
6364 static size_t refs_alloc = 0;
6365 static size_t refs_size = 0;
6367 /* Id <-> ref store */
6368 static struct ref ***id_refs = NULL;
6369 static size_t id_refs_alloc = 0;
6370 static size_t id_refs_size = 0;
6372 static int
6373 compare_refs(const void *ref1_, const void *ref2_)
6375         const struct ref *ref1 = *(const struct ref **)ref1_;
6376         const struct ref *ref2 = *(const struct ref **)ref2_;
6378         if (ref1->tag != ref2->tag)
6379                 return ref2->tag - ref1->tag;
6380         if (ref1->ltag != ref2->ltag)
6381                 return ref2->ltag - ref2->ltag;
6382         if (ref1->head != ref2->head)
6383                 return ref2->head - ref1->head;
6384         if (ref1->tracked != ref2->tracked)
6385                 return ref2->tracked - ref1->tracked;
6386         if (ref1->remote != ref2->remote)
6387                 return ref2->remote - ref1->remote;
6388         return strcmp(ref1->name, ref2->name);
6391 static struct ref **
6392 get_refs(const char *id)
6394         struct ref ***tmp_id_refs;
6395         struct ref **ref_list = NULL;
6396         size_t ref_list_alloc = 0;
6397         size_t ref_list_size = 0;
6398         size_t i;
6400         for (i = 0; i < id_refs_size; i++)
6401                 if (!strcmp(id, id_refs[i][0]->id))
6402                         return id_refs[i];
6404         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6405                                     sizeof(*id_refs));
6406         if (!tmp_id_refs)
6407                 return NULL;
6409         id_refs = tmp_id_refs;
6411         for (i = 0; i < refs_size; i++) {
6412                 struct ref **tmp;
6414                 if (strcmp(id, refs[i].id))
6415                         continue;
6417                 tmp = realloc_items(ref_list, &ref_list_alloc,
6418                                     ref_list_size + 1, sizeof(*ref_list));
6419                 if (!tmp) {
6420                         if (ref_list)
6421                                 free(ref_list);
6422                         return NULL;
6423                 }
6425                 ref_list = tmp;
6426                 ref_list[ref_list_size] = &refs[i];
6427                 /* XXX: The properties of the commit chains ensures that we can
6428                  * safely modify the shared ref. The repo references will
6429                  * always be similar for the same id. */
6430                 ref_list[ref_list_size]->next = 1;
6432                 ref_list_size++;
6433         }
6435         if (ref_list) {
6436                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6437                 ref_list[ref_list_size - 1]->next = 0;
6438                 id_refs[id_refs_size++] = ref_list;
6439         }
6441         return ref_list;
6444 static int
6445 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6447         struct ref *ref;
6448         bool tag = FALSE;
6449         bool ltag = FALSE;
6450         bool remote = FALSE;
6451         bool tracked = FALSE;
6452         bool check_replace = FALSE;
6453         bool head = FALSE;
6455         if (!prefixcmp(name, "refs/tags/")) {
6456                 if (!suffixcmp(name, namelen, "^{}")) {
6457                         namelen -= 3;
6458                         name[namelen] = 0;
6459                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6460                                 check_replace = TRUE;
6461                 } else {
6462                         ltag = TRUE;
6463                 }
6465                 tag = TRUE;
6466                 namelen -= STRING_SIZE("refs/tags/");
6467                 name    += STRING_SIZE("refs/tags/");
6469         } else if (!prefixcmp(name, "refs/remotes/")) {
6470                 remote = TRUE;
6471                 namelen -= STRING_SIZE("refs/remotes/");
6472                 name    += STRING_SIZE("refs/remotes/");
6473                 tracked  = !strcmp(opt_remote, name);
6475         } else if (!prefixcmp(name, "refs/heads/")) {
6476                 namelen -= STRING_SIZE("refs/heads/");
6477                 name    += STRING_SIZE("refs/heads/");
6478                 head     = !strncmp(opt_head, name, namelen);
6480         } else if (!strcmp(name, "HEAD")) {
6481                 string_ncopy(opt_head_rev, id, idlen);
6482                 return OK;
6483         }
6485         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6486                 /* it's an annotated tag, replace the previous sha1 with the
6487                  * resolved commit id; relies on the fact git-ls-remote lists
6488                  * the commit id of an annotated tag right before the commit id
6489                  * it points to. */
6490                 refs[refs_size - 1].ltag = ltag;
6491                 string_copy_rev(refs[refs_size - 1].id, id);
6493                 return OK;
6494         }
6495         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6496         if (!refs)
6497                 return ERR;
6499         ref = &refs[refs_size++];
6500         ref->name = malloc(namelen + 1);
6501         if (!ref->name)
6502                 return ERR;
6504         strncpy(ref->name, name, namelen);
6505         ref->name[namelen] = 0;
6506         ref->head = head;
6507         ref->tag = tag;
6508         ref->ltag = ltag;
6509         ref->remote = remote;
6510         ref->tracked = tracked;
6511         string_copy_rev(ref->id, id);
6513         return OK;
6516 static int
6517 load_refs(void)
6519         static const char *ls_remote_argv[SIZEOF_ARG] = {
6520                 "git", "ls-remote", ".", NULL
6521         };
6522         static bool init = FALSE;
6524         if (!init) {
6525                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6526                 init = TRUE;
6527         }
6529         if (!*opt_git_dir)
6530                 return OK;
6532         while (refs_size > 0)
6533                 free(refs[--refs_size].name);
6534         while (id_refs_size > 0)
6535                 free(id_refs[--id_refs_size]);
6537         return git_properties(ls_remote_argv, "\t", read_ref);
6540 static int
6541 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6543         if (!strcmp(name, "i18n.commitencoding"))
6544                 string_ncopy(opt_encoding, value, valuelen);
6546         if (!strcmp(name, "core.editor"))
6547                 string_ncopy(opt_editor, value, valuelen);
6549         /* branch.<head>.remote */
6550         if (*opt_head &&
6551             !strncmp(name, "branch.", 7) &&
6552             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6553             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6554                 string_ncopy(opt_remote, value, valuelen);
6556         if (*opt_head && *opt_remote &&
6557             !strncmp(name, "branch.", 7) &&
6558             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6559             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6560                 size_t from = strlen(opt_remote);
6562                 if (!prefixcmp(value, "refs/heads/")) {
6563                         value += STRING_SIZE("refs/heads/");
6564                         valuelen -= STRING_SIZE("refs/heads/");
6565                 }
6567                 if (!string_format_from(opt_remote, &from, "/%s", value))
6568                         opt_remote[0] = 0;
6569         }
6571         return OK;
6574 static int
6575 load_git_config(void)
6577         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6579         return git_properties(config_list_argv, "=", read_repo_config_option);
6582 static int
6583 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6585         if (!opt_git_dir[0]) {
6586                 string_ncopy(opt_git_dir, name, namelen);
6588         } else if (opt_is_inside_work_tree == -1) {
6589                 /* This can be 3 different values depending on the
6590                  * version of git being used. If git-rev-parse does not
6591                  * understand --is-inside-work-tree it will simply echo
6592                  * the option else either "true" or "false" is printed.
6593                  * Default to true for the unknown case. */
6594                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6596         } else if (*name == '.') {
6597                 string_ncopy(opt_cdup, name, namelen);
6599         } else {
6600                 string_ncopy(opt_prefix, name, namelen);
6601         }
6603         return OK;
6606 static int
6607 load_repo_info(void)
6609         const char *head_argv[] = {
6610                 "git", "symbolic-ref", "HEAD", NULL
6611         };
6612         const char *rev_parse_argv[] = {
6613                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6614                         "--show-cdup", "--show-prefix", NULL
6615         };
6617         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6618                 chomp_string(opt_head);
6619                 if (!prefixcmp(opt_head, "refs/heads/")) {
6620                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6622                         memmove(opt_head, offset, strlen(offset) + 1);
6623                 }
6624         }
6626         return git_properties(rev_parse_argv, "=", read_repo_info);
6629 static int
6630 read_properties(struct io *io, const char *separators,
6631                 int (*read_property)(char *, size_t, char *, size_t))
6633         char *name;
6634         int state = OK;
6636         if (!start_io(io))
6637                 return ERR;
6639         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6640                 char *value;
6641                 size_t namelen;
6642                 size_t valuelen;
6644                 name = chomp_string(name);
6645                 namelen = strcspn(name, separators);
6647                 if (name[namelen]) {
6648                         name[namelen] = 0;
6649                         value = chomp_string(name + namelen + 1);
6650                         valuelen = strlen(value);
6652                 } else {
6653                         value = "";
6654                         valuelen = 0;
6655                 }
6657                 state = read_property(name, namelen, value, valuelen);
6658         }
6660         if (state != ERR && io_error(io))
6661                 state = ERR;
6662         done_io(io);
6664         return state;
6668 /*
6669  * Main
6670  */
6672 static void __NORETURN
6673 quit(int sig)
6675         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6676         if (cursed)
6677                 endwin();
6678         exit(0);
6681 static void __NORETURN
6682 die(const char *err, ...)
6684         va_list args;
6686         endwin();
6688         va_start(args, err);
6689         fputs("tig: ", stderr);
6690         vfprintf(stderr, err, args);
6691         fputs("\n", stderr);
6692         va_end(args);
6694         exit(1);
6697 static void
6698 warn(const char *msg, ...)
6700         va_list args;
6702         va_start(args, msg);
6703         fputs("tig warning: ", stderr);
6704         vfprintf(stderr, msg, args);
6705         fputs("\n", stderr);
6706         va_end(args);
6709 int
6710 main(int argc, const char *argv[])
6712         const char **run_argv = NULL;
6713         struct view *view;
6714         enum request request;
6715         size_t i;
6717         signal(SIGINT, quit);
6719         if (setlocale(LC_ALL, "")) {
6720                 char *codeset = nl_langinfo(CODESET);
6722                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6723         }
6725         if (load_repo_info() == ERR)
6726                 die("Failed to load repo info.");
6728         if (load_options() == ERR)
6729                 die("Failed to load user config.");
6731         if (load_git_config() == ERR)
6732                 die("Failed to load repo config.");
6734         request = parse_options(argc, argv, &run_argv);
6735         if (request == REQ_NONE)
6736                 return 0;
6738         /* Require a git repository unless when running in pager mode. */
6739         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6740                 die("Not a git repository");
6742         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6743                 opt_utf8 = FALSE;
6745         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6746                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6747                 if (opt_iconv == ICONV_NONE)
6748                         die("Failed to initialize character set conversion");
6749         }
6751         if (load_refs() == ERR)
6752                 die("Failed to load refs.");
6754         foreach_view (view, i)
6755                 argv_from_env(view->ops->argv, view->cmd_env);
6757         init_display();
6759         if (request == REQ_VIEW_PAGER || run_argv) {
6760                 if (request == REQ_VIEW_PAGER)
6761                         io_open(&VIEW(request)->io, "");
6762                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6763                         die("Failed to format arguments");
6764                 open_view(NULL, request, OPEN_PREPARED);
6765                 request = REQ_NONE;
6766         }
6768         while (view_driver(display[current_view], request)) {
6769                 int key = get_input(FALSE);
6771                 view = display[current_view];
6772                 request = get_keybinding(view->keymap, key);
6774                 /* Some low-level request handling. This keeps access to
6775                  * status_win restricted. */
6776                 switch (request) {
6777                 case REQ_PROMPT:
6778                 {
6779                         char *cmd = read_prompt(":");
6781                         if (cmd) {
6782                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6783                                 const char *argv[SIZEOF_ARG] = { "git" };
6784                                 int argc = 1;
6786                                 /* When running random commands, initially show the
6787                                  * command in the title. However, it maybe later be
6788                                  * overwritten if a commit line is selected. */
6789                                 string_ncopy(next->ref, cmd, strlen(cmd));
6791                                 if (!argv_from_string(argv, &argc, cmd)) {
6792                                         report("Too many arguments");
6793                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6794                                         report("Failed to format command");
6795                                 } else {
6796                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6797                                 }
6798                         }
6800                         request = REQ_NONE;
6801                         break;
6802                 }
6803                 case REQ_SEARCH:
6804                 case REQ_SEARCH_BACK:
6805                 {
6806                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6807                         char *search = read_prompt(prompt);
6809                         if (search)
6810                                 string_ncopy(opt_search, search, strlen(search));
6811                         else
6812                                 request = REQ_NONE;
6813                         break;
6814                 }
6815                 default:
6816                         break;
6817                 }
6818         }
6820         quit(0);
6822         return 0;