Code

Refactor author drawing into draw_author
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
173 enum input_status {
174         INPUT_OK,
175         INPUT_SKIP,
176         INPUT_STOP,
177         INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static char *
214 chomp_string(char *name)
216         int namelen;
218         while (isspace(*name))
219                 name++;
221         namelen = strlen(name) - 1;
222         while (namelen > 0 && isspace(name[namelen]))
223                 name[namelen--] = 0;
225         return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231         va_list args;
232         size_t pos = bufpos ? *bufpos : 0;
234         va_start(args, fmt);
235         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236         va_end(args);
238         if (bufpos)
239                 *bufpos = pos;
241         return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245         string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248         string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253         size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257         /* Diff-Header == DIFF_HEADER */
258         for (i = 0; i < len; i++) {
259                 if (toupper(str1[i]) == toupper(str2[i]))
260                         continue;
262                 if (string_enum_sep(str1[i]) &&
263                     string_enum_sep(str2[i]))
264                         continue;
266                 return str1[i] - str2[i];
267         }
269         return 0;
272 #define prefixcmp(str1, str2) \
273         strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278         size_t len = slen >= 0 ? slen : strlen(str);
279         size_t suffixlen = strlen(suffix);
281         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288         int valuelen;
290         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291                 bool advance = cmd[valuelen] != 0;
293                 cmd[valuelen] = 0;
294                 argv[(*argc)++] = chomp_string(cmd);
295                 cmd = chomp_string(cmd + valuelen + advance);
296         }
298         if (*argc < SIZEOF_ARG)
299                 argv[*argc] = NULL;
300         return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306         char *env = argv ? getenv(name) : NULL;
307         int argc = 0;
309         if (env && *env)
310                 env = strdup(env);
311         if (env && !argv_from_string(argv, &argc, env))
312                 die("Too many arguments in the `%s` environment variable", name);
316 /*
317  * Executing external commands.
318  */
320 enum io_type {
321         IO_FD,                  /* File descriptor based IO. */
322         IO_BG,                  /* Execute command in the background. */
323         IO_FG,                  /* Execute command with same std{in,out,err}. */
324         IO_RD,                  /* Read only fork+exec IO. */
325         IO_WR,                  /* Write only fork+exec IO. */
326         IO_AP,                  /* Append fork+exec output to file. */
327 };
329 struct io {
330         enum io_type type;      /* The requested type of pipe. */
331         const char *dir;        /* Directory from which to execute. */
332         pid_t pid;              /* Pipe for reading or writing. */
333         int pipe;               /* Pipe end for reading or writing. */
334         int error;              /* Error status. */
335         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
336         char *buf;              /* Read buffer. */
337         size_t bufalloc;        /* Allocated buffer size. */
338         size_t bufsize;         /* Buffer content size. */
339         char *bufpos;           /* Current buffer position. */
340         unsigned int eof:1;     /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
346         io->pipe = -1;
347         io->pid = 0;
348         io->buf = io->bufpos = NULL;
349         io->bufalloc = io->bufsize = 0;
350         io->error = 0;
351         io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357         reset_io(io);
358         io->type = type;
359         io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364                 enum format_flags flags)
366         init_io(io, dir, IO_RD);
367         return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373         init_io(io, NULL, IO_FD);
374         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375         return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387         pid_t pid = io->pid;
389         if (io->pipe != -1)
390                 close(io->pipe);
391         free(io->buf);
392         reset_io(io);
394         while (pid > 0) {
395                 int status;
396                 pid_t waiting = waitpid(pid, &status, 0);
398                 if (waiting < 0) {
399                         if (errno == EINTR)
400                                 continue;
401                         report("waitpid failed (%s)", strerror(errno));
402                         return FALSE;
403                 }
405                 return waiting == pid &&
406                        !WIFSIGNALED(status) &&
407                        WIFEXITED(status) &&
408                        !WEXITSTATUS(status);
409         }
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         int pipefds[2] = { -1, -1 };
419         if (io->type == IO_FD)
420                 return TRUE;
422         if ((io->type == IO_RD || io->type == IO_WR) &&
423             pipe(pipefds) < 0)
424                 return FALSE;
425         else if (io->type == IO_AP)
426                 pipefds[1] = io->pipe;
428         if ((io->pid = fork())) {
429                 if (pipefds[!(io->type == IO_WR)] != -1)
430                         close(pipefds[!(io->type == IO_WR)]);
431                 if (io->pid != -1) {
432                         io->pipe = pipefds[!!(io->type == IO_WR)];
433                         return TRUE;
434                 }
436         } else {
437                 if (io->type != IO_FG) {
438                         int devnull = open("/dev/null", O_RDWR);
439                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
440                         int writefd = (io->type == IO_RD || io->type == IO_AP)
441                                                         ? pipefds[1] : devnull;
443                         dup2(readfd,  STDIN_FILENO);
444                         dup2(writefd, STDOUT_FILENO);
445                         dup2(devnull, STDERR_FILENO);
447                         close(devnull);
448                         if (pipefds[0] != -1)
449                                 close(pipefds[0]);
450                         if (pipefds[1] != -1)
451                                 close(pipefds[1]);
452                 }
454                 if (io->dir && *io->dir && chdir(io->dir) == -1)
455                         die("Failed to change directory: %s", strerror(errno));
457                 execvp(io->argv[0], (char *const*) io->argv);
458                 die("Failed to execute program: %s", strerror(errno));
459         }
461         if (pipefds[!!(io->type == IO_WR)] != -1)
462                 close(pipefds[!!(io->type == IO_WR)]);
463         return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469         init_io(io, dir, type);
470         if (!format_argv(io->argv, argv, FORMAT_NONE))
471                 return FALSE;
472         return start_io(io);
475 static int
476 run_io_do(struct io *io)
478         return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484         struct io io = {};
486         init_io(&io, NULL, IO_BG);
487         if (!format_argv(io.argv, argv, FORMAT_NONE))
488                 return FALSE;
489         return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495         struct io io = {};
497         init_io(&io, dir, IO_FG);
498         if (!format_argv(io.argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506         struct io io = {};
508         init_io(&io, NULL, IO_AP);
509         io.pipe = fd;
510         if (format_argv(io.argv, argv, flags))
511                 return run_io_do(&io);
512         close(fd);
513         return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519         return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525         return io->eof;
528 static int
529 io_error(struct io *io)
531         return io->error;
534 static bool
535 io_strerror(struct io *io)
537         return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543         struct timeval tv = { 0, 500 };
544         fd_set fds;
546         FD_ZERO(&fds);
547         FD_SET(io->pipe, &fds);
549         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555         do {
556                 ssize_t readsize = read(io->pipe, buf, bufsize);
558                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559                         continue;
560                 else if (readsize == -1)
561                         io->error = errno;
562                 else if (readsize == 0)
563                         io->eof = 1;
564                 return readsize;
565         } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571         char *eol;
572         ssize_t readsize;
574         if (!io->buf) {
575                 io->buf = io->bufpos = malloc(BUFSIZ);
576                 if (!io->buf)
577                         return NULL;
578                 io->bufalloc = BUFSIZ;
579                 io->bufsize = 0;
580         }
582         while (TRUE) {
583                 if (io->bufsize > 0) {
584                         eol = memchr(io->bufpos, c, io->bufsize);
585                         if (eol) {
586                                 char *line = io->bufpos;
588                                 *eol = 0;
589                                 io->bufpos = eol + 1;
590                                 io->bufsize -= io->bufpos - line;
591                                 return line;
592                         }
593                 }
595                 if (io_eof(io)) {
596                         if (io->bufsize) {
597                                 io->bufpos[io->bufsize] = 0;
598                                 io->bufsize = 0;
599                                 return io->bufpos;
600                         }
601                         return NULL;
602                 }
604                 if (!can_read)
605                         return NULL;
607                 if (io->bufsize > 0 && io->bufpos > io->buf)
608                         memmove(io->buf, io->bufpos, io->bufsize);
610                 io->bufpos = io->buf;
611                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612                 if (io_error(io))
613                         return NULL;
614                 io->bufsize += readsize;
615         }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621         size_t written = 0;
623         while (!io_error(io) && written < bufsize) {
624                 ssize_t size;
626                 size = write(io->pipe, buf + written, bufsize - written);
627                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628                         continue;
629                 else if (size == -1)
630                         io->error = errno;
631                 else
632                         written += size;
633         }
635         return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641         struct io io = {};
642         bool error;
644         if (!run_io_rd(&io, argv, FORMAT_NONE))
645                 return FALSE;
647         io.buf = io.bufpos = buf;
648         io.bufalloc = bufsize;
649         error = !io_get(&io, '\n', TRUE) && io_error(&io);
650         io.buf = NULL;
652         return done_io(&io) || error;
655 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
657 /*
658  * User requests
659  */
661 #define REQ_INFO \
662         /* XXX: Keep the view request first and in sync with views[]. */ \
663         REQ_GROUP("View switching") \
664         REQ_(VIEW_MAIN,         "Show main view"), \
665         REQ_(VIEW_DIFF,         "Show diff view"), \
666         REQ_(VIEW_LOG,          "Show log view"), \
667         REQ_(VIEW_TREE,         "Show tree view"), \
668         REQ_(VIEW_BLOB,         "Show blob view"), \
669         REQ_(VIEW_BLAME,        "Show blame view"), \
670         REQ_(VIEW_HELP,         "Show help page"), \
671         REQ_(VIEW_PAGER,        "Show pager view"), \
672         REQ_(VIEW_STATUS,       "Show status view"), \
673         REQ_(VIEW_STAGE,        "Show stage view"), \
674         \
675         REQ_GROUP("View manipulation") \
676         REQ_(ENTER,             "Enter current line and scroll"), \
677         REQ_(NEXT,              "Move to next"), \
678         REQ_(PREVIOUS,          "Move to previous"), \
679         REQ_(PARENT,            "Move to parent"), \
680         REQ_(VIEW_NEXT,         "Move focus to next view"), \
681         REQ_(REFRESH,           "Reload and refresh"), \
682         REQ_(MAXIMIZE,          "Maximize the current view"), \
683         REQ_(VIEW_CLOSE,        "Close the current view"), \
684         REQ_(QUIT,              "Close all views and quit"), \
685         \
686         REQ_GROUP("View specific requests") \
687         REQ_(STATUS_UPDATE,     "Update file status"), \
688         REQ_(STATUS_REVERT,     "Revert file changes"), \
689         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
690         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
691         \
692         REQ_GROUP("Cursor navigation") \
693         REQ_(MOVE_UP,           "Move cursor one line up"), \
694         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
695         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
696         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
697         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
698         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
699         \
700         REQ_GROUP("Scrolling") \
701         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
702         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
703         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
704         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
705         \
706         REQ_GROUP("Searching") \
707         REQ_(SEARCH,            "Search the view"), \
708         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
709         REQ_(FIND_NEXT,         "Find next search match"), \
710         REQ_(FIND_PREV,         "Find previous search match"), \
711         \
712         REQ_GROUP("Option manipulation") \
713         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
714         REQ_(TOGGLE_DATE,       "Toggle date display"), \
715         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
716         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
717         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
718         \
719         REQ_GROUP("Misc") \
720         REQ_(PROMPT,            "Bring up the prompt"), \
721         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
722         REQ_(SHOW_VERSION,      "Show version information"), \
723         REQ_(STOP_LOADING,      "Stop all loading views"), \
724         REQ_(EDIT,              "Open in editor"), \
725         REQ_(NONE,              "Do nothing")
728 /* User action requests. */
729 enum request {
730 #define REQ_GROUP(help)
731 #define REQ_(req, help) REQ_##req
733         /* Offset all requests to avoid conflicts with ncurses getch values. */
734         REQ_OFFSET = KEY_MAX + 1,
735         REQ_INFO
737 #undef  REQ_GROUP
738 #undef  REQ_
739 };
741 struct request_info {
742         enum request request;
743         const char *name;
744         int namelen;
745         const char *help;
746 };
748 static struct request_info req_info[] = {
749 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
750 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
751         REQ_INFO
752 #undef  REQ_GROUP
753 #undef  REQ_
754 };
756 static enum request
757 get_request(const char *name)
759         int namelen = strlen(name);
760         int i;
762         for (i = 0; i < ARRAY_SIZE(req_info); i++)
763                 if (req_info[i].namelen == namelen &&
764                     !string_enum_compare(req_info[i].name, name, namelen))
765                         return req_info[i].request;
767         return REQ_NONE;
771 /*
772  * Options
773  */
775 static const char usage[] =
776 "tig " TIG_VERSION " (" __DATE__ ")\n"
777 "\n"
778 "Usage: tig        [options] [revs] [--] [paths]\n"
779 "   or: tig show   [options] [revs] [--] [paths]\n"
780 "   or: tig blame  [rev] path\n"
781 "   or: tig status\n"
782 "   or: tig <      [git command output]\n"
783 "\n"
784 "Options:\n"
785 "  -v, --version   Show version and exit\n"
786 "  -h, --help      Show help message and exit";
788 /* Option and state variables. */
789 static bool opt_date                    = TRUE;
790 static bool opt_author                  = TRUE;
791 static bool opt_line_number             = FALSE;
792 static bool opt_line_graphics           = TRUE;
793 static bool opt_rev_graph               = FALSE;
794 static bool opt_show_refs               = TRUE;
795 static int opt_num_interval             = NUMBER_INTERVAL;
796 static int opt_tab_size                 = TAB_SIZE;
797 static int opt_author_cols              = AUTHOR_COLS-1;
798 static char opt_path[SIZEOF_STR]        = "";
799 static char opt_file[SIZEOF_STR]        = "";
800 static char opt_ref[SIZEOF_REF]         = "";
801 static char opt_head[SIZEOF_REF]        = "";
802 static char opt_head_rev[SIZEOF_REV]    = "";
803 static char opt_remote[SIZEOF_REF]      = "";
804 static char opt_encoding[20]            = "UTF-8";
805 static bool opt_utf8                    = TRUE;
806 static char opt_codeset[20]             = "UTF-8";
807 static iconv_t opt_iconv                = ICONV_NONE;
808 static char opt_search[SIZEOF_STR]      = "";
809 static char opt_cdup[SIZEOF_STR]        = "";
810 static char opt_prefix[SIZEOF_STR]      = "";
811 static char opt_git_dir[SIZEOF_STR]     = "";
812 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
813 static char opt_editor[SIZEOF_STR]      = "";
814 static FILE *opt_tty                    = NULL;
816 #define is_initial_commit()     (!*opt_head_rev)
817 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
819 static enum request
820 parse_options(int argc, const char *argv[], const char ***run_argv)
822         enum request request = REQ_VIEW_MAIN;
823         const char *subcommand;
824         bool seen_dashdash = FALSE;
825         /* XXX: This is vulnerable to the user overriding options
826          * required for the main view parser. */
827         static const char *custom_argv[SIZEOF_ARG] = {
828                 "git", "log", "--no-color", "--pretty=raw", "--parents",
829                         "--topo-order", NULL
830         };
831         int i, j = 6;
833         if (!isatty(STDIN_FILENO))
834                 return REQ_VIEW_PAGER;
836         if (argc <= 1)
837                 return REQ_VIEW_MAIN;
839         subcommand = argv[1];
840         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
841                 if (!strcmp(subcommand, "-S"))
842                         warn("`-S' has been deprecated; use `tig status' instead");
843                 if (argc > 2)
844                         warn("ignoring arguments after `%s'", subcommand);
845                 return REQ_VIEW_STATUS;
847         } else if (!strcmp(subcommand, "blame")) {
848                 if (argc <= 2 || argc > 4)
849                         die("invalid number of options to blame\n\n%s", usage);
851                 i = 2;
852                 if (argc == 4) {
853                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
854                         i++;
855                 }
857                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
858                 return REQ_VIEW_BLAME;
860         } else if (!strcmp(subcommand, "show")) {
861                 request = REQ_VIEW_DIFF;
863         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
864                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
865                 warn("`tig %s' has been deprecated", subcommand);
867         } else {
868                 subcommand = NULL;
869         }
871         if (subcommand) {
872                 custom_argv[1] = subcommand;
873                 j = 2;
874         }
876         for (i = 1 + !!subcommand; i < argc; i++) {
877                 const char *opt = argv[i];
879                 if (seen_dashdash || !strcmp(opt, "--")) {
880                         seen_dashdash = TRUE;
882                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
883                         printf("tig version %s\n", TIG_VERSION);
884                         return REQ_NONE;
886                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
887                         printf("%s\n", usage);
888                         return REQ_NONE;
889                 }
891                 custom_argv[j++] = opt;
892                 if (j >= ARRAY_SIZE(custom_argv))
893                         die("command too long");
894         }
896         custom_argv[j] = NULL;
897         *run_argv = custom_argv;
899         return request;
903 /*
904  * Line-oriented content detection.
905  */
907 #define LINE_INFO \
908 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
910 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
911 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
913 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
920 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
921 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
922 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
924 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
925 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
927 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
928 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
929 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
932 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
933 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
934 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
935 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
936 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
937 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
938 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
939 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
940 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
941 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
942 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
943 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
944 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
945 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
946 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
947 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
949 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
950 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
951 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
952 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
953 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
954 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
955 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
956 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
957 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
958 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
959 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
960 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
962 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
963 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
965 enum line_type {
966 #define LINE(type, line, fg, bg, attr) \
967         LINE_##type
968         LINE_INFO,
969         LINE_NONE
970 #undef  LINE
971 };
973 struct line_info {
974         const char *name;       /* Option name. */
975         int namelen;            /* Size of option name. */
976         const char *line;       /* The start of line to match. */
977         int linelen;            /* Size of string to match. */
978         int fg, bg, attr;       /* Color and text attributes for the lines. */
979 };
981 static struct line_info line_info[] = {
982 #define LINE(type, line, fg, bg, attr) \
983         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984         LINE_INFO
985 #undef  LINE
986 };
988 static enum line_type
989 get_line_type(const char *line)
991         int linelen = strlen(line);
992         enum line_type type;
994         for (type = 0; type < ARRAY_SIZE(line_info); type++)
995                 /* Case insensitive search matches Signed-off-by lines better. */
996                 if (linelen >= line_info[type].linelen &&
997                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
998                         return type;
1000         return LINE_DEFAULT;
1003 static inline int
1004 get_line_attr(enum line_type type)
1006         assert(type < ARRAY_SIZE(line_info));
1007         return COLOR_PAIR(type) | line_info[type].attr;
1010 static struct line_info *
1011 get_line_info(const char *name)
1013         size_t namelen = strlen(name);
1014         enum line_type type;
1016         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017                 if (namelen == line_info[type].namelen &&
1018                     !string_enum_compare(line_info[type].name, name, namelen))
1019                         return &line_info[type];
1021         return NULL;
1024 static void
1025 init_colors(void)
1027         int default_bg = line_info[LINE_DEFAULT].bg;
1028         int default_fg = line_info[LINE_DEFAULT].fg;
1029         enum line_type type;
1031         start_color();
1033         if (assume_default_colors(default_fg, default_bg) == ERR) {
1034                 default_bg = COLOR_BLACK;
1035                 default_fg = COLOR_WHITE;
1036         }
1038         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1039                 struct line_info *info = &line_info[type];
1040                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1041                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1043                 init_pair(type, fg, bg);
1044         }
1047 struct line {
1048         enum line_type type;
1050         /* State flags */
1051         unsigned int selected:1;
1052         unsigned int dirty:1;
1053         unsigned int cleareol:1;
1055         void *data;             /* User data */
1056 };
1059 /*
1060  * Keys
1061  */
1063 struct keybinding {
1064         int alias;
1065         enum request request;
1066 };
1068 static struct keybinding default_keybindings[] = {
1069         /* View switching */
1070         { 'm',          REQ_VIEW_MAIN },
1071         { 'd',          REQ_VIEW_DIFF },
1072         { 'l',          REQ_VIEW_LOG },
1073         { 't',          REQ_VIEW_TREE },
1074         { 'f',          REQ_VIEW_BLOB },
1075         { 'B',          REQ_VIEW_BLAME },
1076         { 'p',          REQ_VIEW_PAGER },
1077         { 'h',          REQ_VIEW_HELP },
1078         { 'S',          REQ_VIEW_STATUS },
1079         { 'c',          REQ_VIEW_STAGE },
1081         /* View manipulation */
1082         { 'q',          REQ_VIEW_CLOSE },
1083         { KEY_TAB,      REQ_VIEW_NEXT },
1084         { KEY_RETURN,   REQ_ENTER },
1085         { KEY_UP,       REQ_PREVIOUS },
1086         { KEY_DOWN,     REQ_NEXT },
1087         { 'R',          REQ_REFRESH },
1088         { KEY_F(5),     REQ_REFRESH },
1089         { 'O',          REQ_MAXIMIZE },
1091         /* Cursor navigation */
1092         { 'k',          REQ_MOVE_UP },
1093         { 'j',          REQ_MOVE_DOWN },
1094         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1095         { KEY_END,      REQ_MOVE_LAST_LINE },
1096         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1097         { ' ',          REQ_MOVE_PAGE_DOWN },
1098         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1099         { 'b',          REQ_MOVE_PAGE_UP },
1100         { '-',          REQ_MOVE_PAGE_UP },
1102         /* Scrolling */
1103         { KEY_IC,       REQ_SCROLL_LINE_UP },
1104         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1105         { 'w',          REQ_SCROLL_PAGE_UP },
1106         { 's',          REQ_SCROLL_PAGE_DOWN },
1108         /* Searching */
1109         { '/',          REQ_SEARCH },
1110         { '?',          REQ_SEARCH_BACK },
1111         { 'n',          REQ_FIND_NEXT },
1112         { 'N',          REQ_FIND_PREV },
1114         /* Misc */
1115         { 'Q',          REQ_QUIT },
1116         { 'z',          REQ_STOP_LOADING },
1117         { 'v',          REQ_SHOW_VERSION },
1118         { 'r',          REQ_SCREEN_REDRAW },
1119         { '.',          REQ_TOGGLE_LINENO },
1120         { 'D',          REQ_TOGGLE_DATE },
1121         { 'A',          REQ_TOGGLE_AUTHOR },
1122         { 'g',          REQ_TOGGLE_REV_GRAPH },
1123         { 'F',          REQ_TOGGLE_REFS },
1124         { ':',          REQ_PROMPT },
1125         { 'u',          REQ_STATUS_UPDATE },
1126         { '!',          REQ_STATUS_REVERT },
1127         { 'M',          REQ_STATUS_MERGE },
1128         { '@',          REQ_STAGE_NEXT },
1129         { ',',          REQ_PARENT },
1130         { 'e',          REQ_EDIT },
1131 };
1133 #define KEYMAP_INFO \
1134         KEYMAP_(GENERIC), \
1135         KEYMAP_(MAIN), \
1136         KEYMAP_(DIFF), \
1137         KEYMAP_(LOG), \
1138         KEYMAP_(TREE), \
1139         KEYMAP_(BLOB), \
1140         KEYMAP_(BLAME), \
1141         KEYMAP_(PAGER), \
1142         KEYMAP_(HELP), \
1143         KEYMAP_(STATUS), \
1144         KEYMAP_(STAGE)
1146 enum keymap {
1147 #define KEYMAP_(name) KEYMAP_##name
1148         KEYMAP_INFO
1149 #undef  KEYMAP_
1150 };
1152 static struct int_map keymap_table[] = {
1153 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1154         KEYMAP_INFO
1155 #undef  KEYMAP_
1156 };
1158 #define set_keymap(map, name) \
1159         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1161 struct keybinding_table {
1162         struct keybinding *data;
1163         size_t size;
1164 };
1166 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1168 static void
1169 add_keybinding(enum keymap keymap, enum request request, int key)
1171         struct keybinding_table *table = &keybindings[keymap];
1173         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1174         if (!table->data)
1175                 die("Failed to allocate keybinding");
1176         table->data[table->size].alias = key;
1177         table->data[table->size++].request = request;
1180 /* Looks for a key binding first in the given map, then in the generic map, and
1181  * lastly in the default keybindings. */
1182 static enum request
1183 get_keybinding(enum keymap keymap, int key)
1185         size_t i;
1187         for (i = 0; i < keybindings[keymap].size; i++)
1188                 if (keybindings[keymap].data[i].alias == key)
1189                         return keybindings[keymap].data[i].request;
1191         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1192                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1193                         return keybindings[KEYMAP_GENERIC].data[i].request;
1195         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1196                 if (default_keybindings[i].alias == key)
1197                         return default_keybindings[i].request;
1199         return (enum request) key;
1203 struct key {
1204         const char *name;
1205         int value;
1206 };
1208 static struct key key_table[] = {
1209         { "Enter",      KEY_RETURN },
1210         { "Space",      ' ' },
1211         { "Backspace",  KEY_BACKSPACE },
1212         { "Tab",        KEY_TAB },
1213         { "Escape",     KEY_ESC },
1214         { "Left",       KEY_LEFT },
1215         { "Right",      KEY_RIGHT },
1216         { "Up",         KEY_UP },
1217         { "Down",       KEY_DOWN },
1218         { "Insert",     KEY_IC },
1219         { "Delete",     KEY_DC },
1220         { "Hash",       '#' },
1221         { "Home",       KEY_HOME },
1222         { "End",        KEY_END },
1223         { "PageUp",     KEY_PPAGE },
1224         { "PageDown",   KEY_NPAGE },
1225         { "F1",         KEY_F(1) },
1226         { "F2",         KEY_F(2) },
1227         { "F3",         KEY_F(3) },
1228         { "F4",         KEY_F(4) },
1229         { "F5",         KEY_F(5) },
1230         { "F6",         KEY_F(6) },
1231         { "F7",         KEY_F(7) },
1232         { "F8",         KEY_F(8) },
1233         { "F9",         KEY_F(9) },
1234         { "F10",        KEY_F(10) },
1235         { "F11",        KEY_F(11) },
1236         { "F12",        KEY_F(12) },
1237 };
1239 static int
1240 get_key_value(const char *name)
1242         int i;
1244         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1245                 if (!strcasecmp(key_table[i].name, name))
1246                         return key_table[i].value;
1248         if (strlen(name) == 1 && isprint(*name))
1249                 return (int) *name;
1251         return ERR;
1254 static const char *
1255 get_key_name(int key_value)
1257         static char key_char[] = "'X'";
1258         const char *seq = NULL;
1259         int key;
1261         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1262                 if (key_table[key].value == key_value)
1263                         seq = key_table[key].name;
1265         if (seq == NULL &&
1266             key_value < 127 &&
1267             isprint(key_value)) {
1268                 key_char[1] = (char) key_value;
1269                 seq = key_char;
1270         }
1272         return seq ? seq : "(no key)";
1275 static const char *
1276 get_key(enum request request)
1278         static char buf[BUFSIZ];
1279         size_t pos = 0;
1280         char *sep = "";
1281         int i;
1283         buf[pos] = 0;
1285         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1286                 struct keybinding *keybinding = &default_keybindings[i];
1288                 if (keybinding->request != request)
1289                         continue;
1291                 if (!string_format_from(buf, &pos, "%s%s", sep,
1292                                         get_key_name(keybinding->alias)))
1293                         return "Too many keybindings!";
1294                 sep = ", ";
1295         }
1297         return buf;
1300 struct run_request {
1301         enum keymap keymap;
1302         int key;
1303         const char *argv[SIZEOF_ARG];
1304 };
1306 static struct run_request *run_request;
1307 static size_t run_requests;
1309 static enum request
1310 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1312         struct run_request *req;
1314         if (argc >= ARRAY_SIZE(req->argv) - 1)
1315                 return REQ_NONE;
1317         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1318         if (!req)
1319                 return REQ_NONE;
1321         run_request = req;
1322         req = &run_request[run_requests];
1323         req->keymap = keymap;
1324         req->key = key;
1325         req->argv[0] = NULL;
1327         if (!format_argv(req->argv, argv, FORMAT_NONE))
1328                 return REQ_NONE;
1330         return REQ_NONE + ++run_requests;
1333 static struct run_request *
1334 get_run_request(enum request request)
1336         if (request <= REQ_NONE)
1337                 return NULL;
1338         return &run_request[request - REQ_NONE - 1];
1341 static void
1342 add_builtin_run_requests(void)
1344         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1345         const char *gc[] = { "git", "gc", NULL };
1346         struct {
1347                 enum keymap keymap;
1348                 int key;
1349                 int argc;
1350                 const char **argv;
1351         } reqs[] = {
1352                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1353                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354         };
1355         int i;
1357         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1358                 enum request req;
1360                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1361                 if (req != REQ_NONE)
1362                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1363         }
1366 /*
1367  * User config file handling.
1368  */
1370 static struct int_map color_map[] = {
1371 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1372         COLOR_MAP(DEFAULT),
1373         COLOR_MAP(BLACK),
1374         COLOR_MAP(BLUE),
1375         COLOR_MAP(CYAN),
1376         COLOR_MAP(GREEN),
1377         COLOR_MAP(MAGENTA),
1378         COLOR_MAP(RED),
1379         COLOR_MAP(WHITE),
1380         COLOR_MAP(YELLOW),
1381 };
1383 #define set_color(color, name) \
1384         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1386 static struct int_map attr_map[] = {
1387 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1388         ATTR_MAP(NORMAL),
1389         ATTR_MAP(BLINK),
1390         ATTR_MAP(BOLD),
1391         ATTR_MAP(DIM),
1392         ATTR_MAP(REVERSE),
1393         ATTR_MAP(STANDOUT),
1394         ATTR_MAP(UNDERLINE),
1395 };
1397 #define set_attribute(attr, name) \
1398         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1400 static int   config_lineno;
1401 static bool  config_errors;
1402 static const char *config_msg;
1404 /* Wants: object fgcolor bgcolor [attr] */
1405 static int
1406 option_color_command(int argc, const char *argv[])
1408         struct line_info *info;
1410         if (argc != 3 && argc != 4) {
1411                 config_msg = "Wrong number of arguments given to color command";
1412                 return ERR;
1413         }
1415         info = get_line_info(argv[0]);
1416         if (!info) {
1417                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1418                         info = get_line_info("delimiter");
1420                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1421                         info = get_line_info("date");
1423                 } else {
1424                         config_msg = "Unknown color name";
1425                         return ERR;
1426                 }
1427         }
1429         if (set_color(&info->fg, argv[1]) == ERR ||
1430             set_color(&info->bg, argv[2]) == ERR) {
1431                 config_msg = "Unknown color";
1432                 return ERR;
1433         }
1435         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1436                 config_msg = "Unknown attribute";
1437                 return ERR;
1438         }
1440         return OK;
1443 static bool parse_bool(const char *s)
1445         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1446                 !strcmp(s, "yes")) ? TRUE : FALSE;
1449 static int
1450 parse_int(const char *s, int default_value, int min, int max)
1452         int value = atoi(s);
1454         return (value < min || value > max) ? default_value : value;
1457 /* Wants: name = value */
1458 static int
1459 option_set_command(int argc, const char *argv[])
1461         if (argc != 3) {
1462                 config_msg = "Wrong number of arguments given to set command";
1463                 return ERR;
1464         }
1466         if (strcmp(argv[1], "=")) {
1467                 config_msg = "No value assigned";
1468                 return ERR;
1469         }
1471         if (!strcmp(argv[0], "show-author")) {
1472                 opt_author = parse_bool(argv[2]);
1473                 return OK;
1474         }
1476         if (!strcmp(argv[0], "show-date")) {
1477                 opt_date = parse_bool(argv[2]);
1478                 return OK;
1479         }
1481         if (!strcmp(argv[0], "show-rev-graph")) {
1482                 opt_rev_graph = parse_bool(argv[2]);
1483                 return OK;
1484         }
1486         if (!strcmp(argv[0], "show-refs")) {
1487                 opt_show_refs = parse_bool(argv[2]);
1488                 return OK;
1489         }
1491         if (!strcmp(argv[0], "show-line-numbers")) {
1492                 opt_line_number = parse_bool(argv[2]);
1493                 return OK;
1494         }
1496         if (!strcmp(argv[0], "line-graphics")) {
1497                 opt_line_graphics = parse_bool(argv[2]);
1498                 return OK;
1499         }
1501         if (!strcmp(argv[0], "line-number-interval")) {
1502                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1503                 return OK;
1504         }
1506         if (!strcmp(argv[0], "author-width")) {
1507                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1508                 return OK;
1509         }
1511         if (!strcmp(argv[0], "tab-size")) {
1512                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1513                 return OK;
1514         }
1516         if (!strcmp(argv[0], "commit-encoding")) {
1517                 const char *arg = argv[2];
1518                 int arglen = strlen(arg);
1520                 switch (arg[0]) {
1521                 case '"':
1522                 case '\'':
1523                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1524                                 config_msg = "Unmatched quotation";
1525                                 return ERR;
1526                         }
1527                         arg += 1; arglen -= 2;
1528                 default:
1529                         string_ncopy(opt_encoding, arg, strlen(arg));
1530                         return OK;
1531                 }
1532         }
1534         config_msg = "Unknown variable name";
1535         return ERR;
1538 /* Wants: mode request key */
1539 static int
1540 option_bind_command(int argc, const char *argv[])
1542         enum request request;
1543         int keymap;
1544         int key;
1546         if (argc < 3) {
1547                 config_msg = "Wrong number of arguments given to bind command";
1548                 return ERR;
1549         }
1551         if (set_keymap(&keymap, argv[0]) == ERR) {
1552                 config_msg = "Unknown key map";
1553                 return ERR;
1554         }
1556         key = get_key_value(argv[1]);
1557         if (key == ERR) {
1558                 config_msg = "Unknown key";
1559                 return ERR;
1560         }
1562         request = get_request(argv[2]);
1563         if (request == REQ_NONE) {
1564                 struct {
1565                         const char *name;
1566                         enum request request;
1567                 } obsolete[] = {
1568                         { "cherry-pick",        REQ_NONE },
1569                         { "screen-resize",      REQ_NONE },
1570                         { "tree-parent",        REQ_PARENT },
1571                 };
1572                 size_t namelen = strlen(argv[2]);
1573                 int i;
1575                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1576                         if (namelen != strlen(obsolete[i].name) ||
1577                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1578                                 continue;
1579                         if (obsolete[i].request != REQ_NONE)
1580                                 add_keybinding(keymap, obsolete[i].request, key);
1581                         config_msg = "Obsolete request name";
1582                         return ERR;
1583                 }
1584         }
1585         if (request == REQ_NONE && *argv[2]++ == '!')
1586                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1587         if (request == REQ_NONE) {
1588                 config_msg = "Unknown request name";
1589                 return ERR;
1590         }
1592         add_keybinding(keymap, request, key);
1594         return OK;
1597 static int
1598 set_option(const char *opt, char *value)
1600         const char *argv[SIZEOF_ARG];
1601         int argc = 0;
1603         if (!argv_from_string(argv, &argc, value)) {
1604                 config_msg = "Too many option arguments";
1605                 return ERR;
1606         }
1608         if (!strcmp(opt, "color"))
1609                 return option_color_command(argc, argv);
1611         if (!strcmp(opt, "set"))
1612                 return option_set_command(argc, argv);
1614         if (!strcmp(opt, "bind"))
1615                 return option_bind_command(argc, argv);
1617         config_msg = "Unknown option command";
1618         return ERR;
1621 static int
1622 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1624         int status = OK;
1626         config_lineno++;
1627         config_msg = "Internal error";
1629         /* Check for comment markers, since read_properties() will
1630          * only ensure opt and value are split at first " \t". */
1631         optlen = strcspn(opt, "#");
1632         if (optlen == 0)
1633                 return OK;
1635         if (opt[optlen] != 0) {
1636                 config_msg = "No option value";
1637                 status = ERR;
1639         }  else {
1640                 /* Look for comment endings in the value. */
1641                 size_t len = strcspn(value, "#");
1643                 if (len < valuelen) {
1644                         valuelen = len;
1645                         value[valuelen] = 0;
1646                 }
1648                 status = set_option(opt, value);
1649         }
1651         if (status == ERR) {
1652                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1653                         config_lineno, (int) optlen, opt, config_msg);
1654                 config_errors = TRUE;
1655         }
1657         /* Always keep going if errors are encountered. */
1658         return OK;
1661 static void
1662 load_option_file(const char *path)
1664         struct io io = {};
1666         /* It's ok that the file doesn't exist. */
1667         if (!io_open(&io, path))
1668                 return;
1670         config_lineno = 0;
1671         config_errors = FALSE;
1673         if (read_properties(&io, " \t", read_option) == ERR ||
1674             config_errors == TRUE)
1675                 fprintf(stderr, "Errors while loading %s.\n", path);
1678 static int
1679 load_options(void)
1681         const char *home = getenv("HOME");
1682         const char *tigrc_user = getenv("TIGRC_USER");
1683         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1684         char buf[SIZEOF_STR];
1686         add_builtin_run_requests();
1688         if (!tigrc_system) {
1689                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1690                         return ERR;
1691                 tigrc_system = buf;
1692         }
1693         load_option_file(tigrc_system);
1695         if (!tigrc_user) {
1696                 if (!home || !string_format(buf, "%s/.tigrc", home))
1697                         return ERR;
1698                 tigrc_user = buf;
1699         }
1700         load_option_file(tigrc_user);
1702         return OK;
1706 /*
1707  * The viewer
1708  */
1710 struct view;
1711 struct view_ops;
1713 /* The display array of active views and the index of the current view. */
1714 static struct view *display[2];
1715 static unsigned int current_view;
1717 /* Reading from the prompt? */
1718 static bool input_mode = FALSE;
1720 #define foreach_displayed_view(view, i) \
1721         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1723 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1725 /* Current head and commit ID */
1726 static char ref_blob[SIZEOF_REF]        = "";
1727 static char ref_commit[SIZEOF_REF]      = "HEAD";
1728 static char ref_head[SIZEOF_REF]        = "HEAD";
1730 struct view {
1731         const char *name;       /* View name */
1732         const char *cmd_env;    /* Command line set via environment */
1733         const char *id;         /* Points to either of ref_{head,commit,blob} */
1735         struct view_ops *ops;   /* View operations */
1737         enum keymap keymap;     /* What keymap does this view have */
1738         bool git_dir;           /* Whether the view requires a git directory. */
1740         char ref[SIZEOF_REF];   /* Hovered commit reference */
1741         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1743         int height, width;      /* The width and height of the main window */
1744         WINDOW *win;            /* The main window */
1745         WINDOW *title;          /* The title window living below the main window */
1747         /* Navigation */
1748         unsigned long offset;   /* Offset of the window top */
1749         unsigned long lineno;   /* Current line number */
1750         unsigned long p_offset; /* Previous offset of the window top */
1751         unsigned long p_lineno; /* Previous current line number */
1752         bool p_restore;         /* Should the previous position be restored. */
1754         /* Searching */
1755         char grep[SIZEOF_STR];  /* Search string */
1756         regex_t *regex;         /* Pre-compiled regex */
1758         /* If non-NULL, points to the view that opened this view. If this view
1759          * is closed tig will switch back to the parent view. */
1760         struct view *parent;
1762         /* Buffering */
1763         size_t lines;           /* Total number of lines */
1764         struct line *line;      /* Line index */
1765         size_t line_alloc;      /* Total number of allocated lines */
1766         unsigned int digits;    /* Number of digits in the lines member. */
1768         /* Drawing */
1769         struct line *curline;   /* Line currently being drawn. */
1770         enum line_type curtype; /* Attribute currently used for drawing. */
1771         unsigned long col;      /* Column when drawing. */
1773         /* Loading */
1774         struct io io;
1775         struct io *pipe;
1776         time_t start_time;
1777         time_t update_secs;
1778 };
1780 struct view_ops {
1781         /* What type of content being displayed. Used in the title bar. */
1782         const char *type;
1783         /* Default command arguments. */
1784         const char **argv;
1785         /* Open and reads in all view content. */
1786         bool (*open)(struct view *view);
1787         /* Read one line; updates view->line. */
1788         bool (*read)(struct view *view, char *data);
1789         /* Draw one line; @lineno must be < view->height. */
1790         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1791         /* Depending on view handle a special requests. */
1792         enum request (*request)(struct view *view, enum request request, struct line *line);
1793         /* Search for regex in a line. */
1794         bool (*grep)(struct view *view, struct line *line);
1795         /* Select line */
1796         void (*select)(struct view *view, struct line *line);
1797 };
1799 static struct view_ops blame_ops;
1800 static struct view_ops blob_ops;
1801 static struct view_ops diff_ops;
1802 static struct view_ops help_ops;
1803 static struct view_ops log_ops;
1804 static struct view_ops main_ops;
1805 static struct view_ops pager_ops;
1806 static struct view_ops stage_ops;
1807 static struct view_ops status_ops;
1808 static struct view_ops tree_ops;
1810 #define VIEW_STR(name, env, ref, ops, map, git) \
1811         { name, #env, ref, ops, map, git }
1813 #define VIEW_(id, name, ops, git, ref) \
1814         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1817 static struct view views[] = {
1818         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1819         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1820         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1821         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1822         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1823         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1824         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1825         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1826         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1827         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1828 };
1830 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1831 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1833 #define foreach_view(view, i) \
1834         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1836 #define view_is_displayed(view) \
1837         (view == display[0] || view == display[1])
1840 enum line_graphic {
1841         LINE_GRAPHIC_VLINE
1842 };
1844 static int line_graphics[] = {
1845         /* LINE_GRAPHIC_VLINE: */ '|'
1846 };
1848 static inline void
1849 set_view_attr(struct view *view, enum line_type type)
1851         if (!view->curline->selected && view->curtype != type) {
1852                 wattrset(view->win, get_line_attr(type));
1853                 wchgat(view->win, -1, 0, type, NULL);
1854                 view->curtype = type;
1855         }
1858 static int
1859 draw_chars(struct view *view, enum line_type type, const char *string,
1860            int max_len, bool use_tilde)
1862         int len = 0;
1863         int col = 0;
1864         int trimmed = FALSE;
1866         if (max_len <= 0)
1867                 return 0;
1869         if (opt_utf8) {
1870                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1871         } else {
1872                 col = len = strlen(string);
1873                 if (len > max_len) {
1874                         if (use_tilde) {
1875                                 max_len -= 1;
1876                         }
1877                         col = len = max_len;
1878                         trimmed = TRUE;
1879                 }
1880         }
1882         set_view_attr(view, type);
1883         waddnstr(view->win, string, len);
1884         if (trimmed && use_tilde) {
1885                 set_view_attr(view, LINE_DELIMITER);
1886                 waddch(view->win, '~');
1887                 col++;
1888         }
1890         return col;
1893 static int
1894 draw_space(struct view *view, enum line_type type, int max, int spaces)
1896         static char space[] = "                    ";
1897         int col = 0;
1899         spaces = MIN(max, spaces);
1901         while (spaces > 0) {
1902                 int len = MIN(spaces, sizeof(space) - 1);
1904                 col += draw_chars(view, type, space, spaces, FALSE);
1905                 spaces -= len;
1906         }
1908         return col;
1911 static bool
1912 draw_lineno(struct view *view, unsigned int lineno)
1914         char number[10];
1915         int digits3 = view->digits < 3 ? 3 : view->digits;
1916         int max_number = MIN(digits3, STRING_SIZE(number));
1917         int max = view->width - view->col;
1918         int col;
1920         if (max < max_number)
1921                 max_number = max;
1923         lineno += view->offset + 1;
1924         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925                 static char fmt[] = "%1ld";
1927                 if (view->digits <= 9)
1928                         fmt[1] = '0' + digits3;
1930                 if (!string_format(number, fmt, lineno))
1931                         number[0] = 0;
1932                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933         } else {
1934                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935         }
1937         if (col < max) {
1938                 set_view_attr(view, LINE_DEFAULT);
1939                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940                 col++;
1941         }
1943         if (col < max)
1944                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1945         view->col += col;
1947         return view->width - view->col <= 0;
1950 static bool
1951 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1953         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1954         return view->width - view->col <= 0;
1957 static bool
1958 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1960         int max = view->width - view->col;
1961         int i;
1963         if (max < size)
1964                 size = max;
1966         set_view_attr(view, type);
1967         /* Using waddch() instead of waddnstr() ensures that
1968          * they'll be rendered correctly for the cursor line. */
1969         for (i = 0; i < size; i++)
1970                 waddch(view->win, graphic[i]);
1972         view->col += size;
1973         if (size < max) {
1974                 waddch(view->win, ' ');
1975                 view->col++;
1976         }
1978         return view->width - view->col <= 0;
1981 static bool
1982 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1984         int max = MIN(view->width - view->col, len);
1985         int col;
1987         if (text)
1988                 col = draw_chars(view, type, text, max - 1, trim);
1989         else
1990                 col = draw_space(view, type, max - 1, max - 1);
1992         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1993         return view->width - view->col <= 0;
1996 static bool
1997 draw_date(struct view *view, struct tm *time)
1999         char buf[DATE_COLS];
2000         char *date;
2001         int timelen = 0;
2003         if (time)
2004                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2005         date = timelen ? buf : NULL;
2007         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2010 static bool
2011 draw_author(struct view *view, const char *author)
2013         return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE);
2016 static bool
2017 draw_view_line(struct view *view, unsigned int lineno)
2019         struct line *line;
2020         bool selected = (view->offset + lineno == view->lineno);
2022         assert(view_is_displayed(view));
2024         if (view->offset + lineno >= view->lines)
2025                 return FALSE;
2027         line = &view->line[view->offset + lineno];
2029         wmove(view->win, lineno, 0);
2030         if (line->cleareol)
2031                 wclrtoeol(view->win);
2032         view->col = 0;
2033         view->curline = line;
2034         view->curtype = LINE_NONE;
2035         line->selected = FALSE;
2036         line->dirty = line->cleareol = 0;
2038         if (selected) {
2039                 set_view_attr(view, LINE_CURSOR);
2040                 line->selected = TRUE;
2041                 view->ops->select(view, line);
2042         }
2044         return view->ops->draw(view, line, lineno);
2047 static void
2048 redraw_view_dirty(struct view *view)
2050         bool dirty = FALSE;
2051         int lineno;
2053         for (lineno = 0; lineno < view->height; lineno++) {
2054                 if (view->offset + lineno >= view->lines)
2055                         break;
2056                 if (!view->line[view->offset + lineno].dirty)
2057                         continue;
2058                 dirty = TRUE;
2059                 if (!draw_view_line(view, lineno))
2060                         break;
2061         }
2063         if (!dirty)
2064                 return;
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         if (input_mode)
2082                 wnoutrefresh(view->win);
2083         else
2084                 wrefresh(view->win);
2087 static void
2088 redraw_view(struct view *view)
2090         werase(view->win);
2091         redraw_view_from(view, 0);
2095 static void
2096 update_display_cursor(struct view *view)
2098         /* Move the cursor to the right-most column of the cursor line.
2099          *
2100          * XXX: This could turn out to be a bit expensive, but it ensures that
2101          * the cursor does not jump around. */
2102         if (view->lines) {
2103                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2104                 wrefresh(view->win);
2105         }
2108 static void
2109 update_view_title(struct view *view)
2111         char buf[SIZEOF_STR];
2112         char state[SIZEOF_STR];
2113         size_t bufpos = 0, statelen = 0;
2115         assert(view_is_displayed(view));
2117         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2118                 unsigned int view_lines = view->offset + view->height;
2119                 unsigned int lines = view->lines
2120                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2121                                    : 0;
2123                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2124                                    view->ops->type,
2125                                    view->lineno + 1,
2126                                    view->lines,
2127                                    lines);
2129         }
2131         if (view->pipe) {
2132                 time_t secs = time(NULL) - view->start_time;
2134                 /* Three git seconds are a long time ... */
2135                 if (secs > 2)
2136                         string_format_from(state, &statelen, " loading %lds", secs);
2137         }
2139         string_format_from(buf, &bufpos, "[%s]", view->name);
2140         if (*view->ref && bufpos < view->width) {
2141                 size_t refsize = strlen(view->ref);
2142                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2144                 if (minsize < view->width)
2145                         refsize = view->width - minsize + 7;
2146                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2147         }
2149         if (statelen && bufpos < view->width) {
2150                 string_format_from(buf, &bufpos, "%s", state);
2151         }
2153         if (view == display[current_view])
2154                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2155         else
2156                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2158         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2159         wclrtoeol(view->title);
2160         wmove(view->title, 0, view->width - 1);
2162         if (input_mode)
2163                 wnoutrefresh(view->title);
2164         else
2165                 wrefresh(view->title);
2168 static void
2169 resize_display(void)
2171         int offset, i;
2172         struct view *base = display[0];
2173         struct view *view = display[1] ? display[1] : display[0];
2175         /* Setup window dimensions */
2177         getmaxyx(stdscr, base->height, base->width);
2179         /* Make room for the status window. */
2180         base->height -= 1;
2182         if (view != base) {
2183                 /* Horizontal split. */
2184                 view->width   = base->width;
2185                 view->height  = SCALE_SPLIT_VIEW(base->height);
2186                 base->height -= view->height;
2188                 /* Make room for the title bar. */
2189                 view->height -= 1;
2190         }
2192         /* Make room for the title bar. */
2193         base->height -= 1;
2195         offset = 0;
2197         foreach_displayed_view (view, i) {
2198                 if (!view->win) {
2199                         view->win = newwin(view->height, 0, offset, 0);
2200                         if (!view->win)
2201                                 die("Failed to create %s view", view->name);
2203                         scrollok(view->win, FALSE);
2205                         view->title = newwin(1, 0, offset + view->height, 0);
2206                         if (!view->title)
2207                                 die("Failed to create title window");
2209                 } else {
2210                         wresize(view->win, view->height, view->width);
2211                         mvwin(view->win,   offset, 0);
2212                         mvwin(view->title, offset + view->height, 0);
2213                 }
2215                 offset += view->height + 1;
2216         }
2219 static void
2220 redraw_display(bool clear)
2222         struct view *view;
2223         int i;
2225         foreach_displayed_view (view, i) {
2226                 if (clear)
2227                         wclear(view->win);
2228                 redraw_view(view);
2229                 update_view_title(view);
2230         }
2232         if (display[current_view])
2233                 update_display_cursor(display[current_view]);
2236 static void
2237 toggle_view_option(bool *option, const char *help)
2239         *option = !*option;
2240         redraw_display(FALSE);
2241         report("%sabling %s", *option ? "En" : "Dis", help);
2244 /*
2245  * Navigation
2246  */
2248 /* Scrolling backend */
2249 static void
2250 do_scroll_view(struct view *view, int lines)
2252         bool redraw_current_line = FALSE;
2254         /* The rendering expects the new offset. */
2255         view->offset += lines;
2257         assert(0 <= view->offset && view->offset < view->lines);
2258         assert(lines);
2260         /* Move current line into the view. */
2261         if (view->lineno < view->offset) {
2262                 view->lineno = view->offset;
2263                 redraw_current_line = TRUE;
2264         } else if (view->lineno >= view->offset + view->height) {
2265                 view->lineno = view->offset + view->height - 1;
2266                 redraw_current_line = TRUE;
2267         }
2269         assert(view->offset <= view->lineno && view->lineno < view->lines);
2271         /* Redraw the whole screen if scrolling is pointless. */
2272         if (view->height < ABS(lines)) {
2273                 redraw_view(view);
2275         } else {
2276                 int line = lines > 0 ? view->height - lines : 0;
2277                 int end = line + ABS(lines);
2279                 scrollok(view->win, TRUE);
2280                 wscrl(view->win, lines);
2281                 scrollok(view->win, FALSE);
2283                 while (line < end && draw_view_line(view, line))
2284                         line++;
2286                 if (redraw_current_line)
2287                         draw_view_line(view, view->lineno - view->offset);
2288                 /* FIXME: Stupid hack to workaround bug where the message from
2289                  * scrolling up one line when impossible followed by scrolling
2290                  * down one line is not removed by the next action. */
2291                 if (lines > 0)
2292                         report("");
2293                 wrefresh(view->win);
2294         }
2296         report("");
2299 /* Scroll frontend */
2300 static void
2301 scroll_view(struct view *view, enum request request)
2303         int lines = 1;
2305         assert(view_is_displayed(view));
2307         switch (request) {
2308         case REQ_SCROLL_PAGE_DOWN:
2309                 lines = view->height;
2310         case REQ_SCROLL_LINE_DOWN:
2311                 if (view->offset + lines > view->lines)
2312                         lines = view->lines - view->offset;
2314                 if (lines == 0 || view->offset + view->height >= view->lines) {
2315                         report("Cannot scroll beyond the last line");
2316                         return;
2317                 }
2318                 break;
2320         case REQ_SCROLL_PAGE_UP:
2321                 lines = view->height;
2322         case REQ_SCROLL_LINE_UP:
2323                 if (lines > view->offset)
2324                         lines = view->offset;
2326                 if (lines == 0) {
2327                         report("Cannot scroll beyond the first line");
2328                         return;
2329                 }
2331                 lines = -lines;
2332                 break;
2334         default:
2335                 die("request %d not handled in switch", request);
2336         }
2338         do_scroll_view(view, lines);
2341 /* Cursor moving */
2342 static void
2343 move_view(struct view *view, enum request request)
2345         int scroll_steps = 0;
2346         int steps;
2348         switch (request) {
2349         case REQ_MOVE_FIRST_LINE:
2350                 steps = -view->lineno;
2351                 break;
2353         case REQ_MOVE_LAST_LINE:
2354                 steps = view->lines - view->lineno - 1;
2355                 break;
2357         case REQ_MOVE_PAGE_UP:
2358                 steps = view->height > view->lineno
2359                       ? -view->lineno : -view->height;
2360                 break;
2362         case REQ_MOVE_PAGE_DOWN:
2363                 steps = view->lineno + view->height >= view->lines
2364                       ? view->lines - view->lineno - 1 : view->height;
2365                 break;
2367         case REQ_MOVE_UP:
2368                 steps = -1;
2369                 break;
2371         case REQ_MOVE_DOWN:
2372                 steps = 1;
2373                 break;
2375         default:
2376                 die("request %d not handled in switch", request);
2377         }
2379         if (steps <= 0 && view->lineno == 0) {
2380                 report("Cannot move beyond the first line");
2381                 return;
2383         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2384                 report("Cannot move beyond the last line");
2385                 return;
2386         }
2388         /* Move the current line */
2389         view->lineno += steps;
2390         assert(0 <= view->lineno && view->lineno < view->lines);
2392         /* Check whether the view needs to be scrolled */
2393         if (view->lineno < view->offset ||
2394             view->lineno >= view->offset + view->height) {
2395                 scroll_steps = steps;
2396                 if (steps < 0 && -steps > view->offset) {
2397                         scroll_steps = -view->offset;
2399                 } else if (steps > 0) {
2400                         if (view->lineno == view->lines - 1 &&
2401                             view->lines > view->height) {
2402                                 scroll_steps = view->lines - view->offset - 1;
2403                                 if (scroll_steps >= view->height)
2404                                         scroll_steps -= view->height - 1;
2405                         }
2406                 }
2407         }
2409         if (!view_is_displayed(view)) {
2410                 view->offset += scroll_steps;
2411                 assert(0 <= view->offset && view->offset < view->lines);
2412                 view->ops->select(view, &view->line[view->lineno]);
2413                 return;
2414         }
2416         /* Repaint the old "current" line if we be scrolling */
2417         if (ABS(steps) < view->height)
2418                 draw_view_line(view, view->lineno - steps - view->offset);
2420         if (scroll_steps) {
2421                 do_scroll_view(view, scroll_steps);
2422                 return;
2423         }
2425         /* Draw the current line */
2426         draw_view_line(view, view->lineno - view->offset);
2428         wrefresh(view->win);
2429         report("");
2433 /*
2434  * Searching
2435  */
2437 static void search_view(struct view *view, enum request request);
2439 static void
2440 select_view_line(struct view *view, unsigned long lineno)
2442         if (lineno - view->offset >= view->height) {
2443                 view->offset = lineno;
2444                 view->lineno = lineno;
2445                 if (view_is_displayed(view))
2446                         redraw_view(view);
2448         } else {
2449                 unsigned long old_lineno = view->lineno - view->offset;
2451                 view->lineno = lineno;
2452                 if (view_is_displayed(view)) {
2453                         draw_view_line(view, old_lineno);
2454                         draw_view_line(view, view->lineno - view->offset);
2455                         wrefresh(view->win);
2456                 } else {
2457                         view->ops->select(view, &view->line[view->lineno]);
2458                 }
2459         }
2462 static void
2463 find_next(struct view *view, enum request request)
2465         unsigned long lineno = view->lineno;
2466         int direction;
2468         if (!*view->grep) {
2469                 if (!*opt_search)
2470                         report("No previous search");
2471                 else
2472                         search_view(view, request);
2473                 return;
2474         }
2476         switch (request) {
2477         case REQ_SEARCH:
2478         case REQ_FIND_NEXT:
2479                 direction = 1;
2480                 break;
2482         case REQ_SEARCH_BACK:
2483         case REQ_FIND_PREV:
2484                 direction = -1;
2485                 break;
2487         default:
2488                 return;
2489         }
2491         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2492                 lineno += direction;
2494         /* Note, lineno is unsigned long so will wrap around in which case it
2495          * will become bigger than view->lines. */
2496         for (; lineno < view->lines; lineno += direction) {
2497                 if (view->ops->grep(view, &view->line[lineno])) {
2498                         select_view_line(view, lineno);
2499                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2500                         return;
2501                 }
2502         }
2504         report("No match found for '%s'", view->grep);
2507 static void
2508 search_view(struct view *view, enum request request)
2510         int regex_err;
2512         if (view->regex) {
2513                 regfree(view->regex);
2514                 *view->grep = 0;
2515         } else {
2516                 view->regex = calloc(1, sizeof(*view->regex));
2517                 if (!view->regex)
2518                         return;
2519         }
2521         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2522         if (regex_err != 0) {
2523                 char buf[SIZEOF_STR] = "unknown error";
2525                 regerror(regex_err, view->regex, buf, sizeof(buf));
2526                 report("Search failed: %s", buf);
2527                 return;
2528         }
2530         string_copy(view->grep, opt_search);
2532         find_next(view, request);
2535 /*
2536  * Incremental updating
2537  */
2539 static void
2540 reset_view(struct view *view)
2542         int i;
2544         for (i = 0; i < view->lines; i++)
2545                 free(view->line[i].data);
2546         free(view->line);
2548         view->p_offset = view->offset;
2549         view->p_lineno = view->lineno;
2551         view->line = NULL;
2552         view->offset = 0;
2553         view->lines  = 0;
2554         view->lineno = 0;
2555         view->line_alloc = 0;
2556         view->vid[0] = 0;
2557         view->update_secs = 0;
2560 static void
2561 free_argv(const char *argv[])
2563         int argc;
2565         for (argc = 0; argv[argc]; argc++)
2566                 free((void *) argv[argc]);
2569 static bool
2570 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2572         char buf[SIZEOF_STR];
2573         int argc;
2574         bool noreplace = flags == FORMAT_NONE;
2576         free_argv(dst_argv);
2578         for (argc = 0; src_argv[argc]; argc++) {
2579                 const char *arg = src_argv[argc];
2580                 size_t bufpos = 0;
2582                 while (arg) {
2583                         char *next = strstr(arg, "%(");
2584                         int len = next - arg;
2585                         const char *value;
2587                         if (!next || noreplace) {
2588                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2589                                         noreplace = TRUE;
2590                                 len = strlen(arg);
2591                                 value = "";
2593                         } else if (!prefixcmp(next, "%(directory)")) {
2594                                 value = opt_path;
2596                         } else if (!prefixcmp(next, "%(file)")) {
2597                                 value = opt_file;
2599                         } else if (!prefixcmp(next, "%(ref)")) {
2600                                 value = *opt_ref ? opt_ref : "HEAD";
2602                         } else if (!prefixcmp(next, "%(head)")) {
2603                                 value = ref_head;
2605                         } else if (!prefixcmp(next, "%(commit)")) {
2606                                 value = ref_commit;
2608                         } else if (!prefixcmp(next, "%(blob)")) {
2609                                 value = ref_blob;
2611                         } else {
2612                                 report("Unknown replacement: `%s`", next);
2613                                 return FALSE;
2614                         }
2616                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2617                                 return FALSE;
2619                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2620                 }
2622                 dst_argv[argc] = strdup(buf);
2623                 if (!dst_argv[argc])
2624                         break;
2625         }
2627         dst_argv[argc] = NULL;
2629         return src_argv[argc] == NULL;
2632 static bool
2633 restore_view_position(struct view *view)
2635         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2636                 return FALSE;
2638         /* Changing the view position cancels the restoring. */
2639         /* FIXME: Changing back to the first line is not detected. */
2640         if (view->offset != 0 || view->lineno != 0) {
2641                 view->p_restore = FALSE;
2642                 return FALSE;
2643         }
2645         if (view->p_lineno >= view->lines) {
2646                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2647                 if (view->p_offset >= view->p_lineno) {
2648                         unsigned long half = view->height / 2;
2650                         if (view->p_lineno > half)
2651                                 view->p_offset = view->p_lineno - half;
2652                         else
2653                                 view->p_offset = 0;
2654                 }
2655         }
2657         if (view_is_displayed(view) &&
2658             view->offset != view->p_offset &&
2659             view->lineno != view->p_lineno)
2660                 werase(view->win);
2662         view->offset = view->p_offset;
2663         view->lineno = view->p_lineno;
2664         view->p_restore = FALSE;
2666         return TRUE;
2669 static void
2670 end_update(struct view *view, bool force)
2672         if (!view->pipe)
2673                 return;
2674         while (!view->ops->read(view, NULL))
2675                 if (!force)
2676                         return;
2677         set_nonblocking_input(FALSE);
2678         if (force)
2679                 kill_io(view->pipe);
2680         done_io(view->pipe);
2681         view->pipe = NULL;
2684 static void
2685 setup_update(struct view *view, const char *vid)
2687         set_nonblocking_input(TRUE);
2688         reset_view(view);
2689         string_copy_rev(view->vid, vid);
2690         view->pipe = &view->io;
2691         view->start_time = time(NULL);
2694 static bool
2695 prepare_update(struct view *view, const char *argv[], const char *dir,
2696                enum format_flags flags)
2698         if (view->pipe)
2699                 end_update(view, TRUE);
2700         return init_io_rd(&view->io, argv, dir, flags);
2703 static bool
2704 prepare_update_file(struct view *view, const char *name)
2706         if (view->pipe)
2707                 end_update(view, TRUE);
2708         return io_open(&view->io, name);
2711 static bool
2712 begin_update(struct view *view, bool refresh)
2714         if (view->pipe)
2715                 end_update(view, TRUE);
2717         if (refresh) {
2718                 if (!start_io(&view->io))
2719                         return FALSE;
2721         } else {
2722                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2723                         opt_path[0] = 0;
2725                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2726                         return FALSE;
2728                 /* Put the current ref_* value to the view title ref
2729                  * member. This is needed by the blob view. Most other
2730                  * views sets it automatically after loading because the
2731                  * first line is a commit line. */
2732                 string_copy_rev(view->ref, view->id);
2733         }
2735         setup_update(view, view->id);
2737         return TRUE;
2740 #define ITEM_CHUNK_SIZE 256
2741 static void *
2742 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2744         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2745         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2747         if (mem == NULL || num_chunks != num_chunks_new) {
2748                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2749                 mem = realloc(mem, *size * item_size);
2750         }
2752         return mem;
2755 static struct line *
2756 realloc_lines(struct view *view, size_t line_size)
2758         size_t alloc = view->line_alloc;
2759         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2760                                          sizeof(*view->line));
2762         if (!tmp)
2763                 return NULL;
2765         view->line = tmp;
2766         view->line_alloc = alloc;
2767         return view->line;
2770 static bool
2771 update_view(struct view *view)
2773         char out_buffer[BUFSIZ * 2];
2774         char *line;
2775         /* Clear the view and redraw everything since the tree sorting
2776          * might have rearranged things. */
2777         bool redraw = view->lines == 0;
2778         bool can_read = TRUE;
2780         if (!view->pipe)
2781                 return TRUE;
2783         if (!io_can_read(view->pipe)) {
2784                 if (view->lines == 0) {
2785                         time_t secs = time(NULL) - view->start_time;
2787                         if (secs > view->update_secs) {
2788                                 if (view->update_secs == 0)
2789                                         redraw_view(view);
2790                                 update_view_title(view);
2791                                 view->update_secs = secs;
2792                         }
2793                 }
2794                 return TRUE;
2795         }
2797         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2798                 if (opt_iconv != ICONV_NONE) {
2799                         ICONV_CONST char *inbuf = line;
2800                         size_t inlen = strlen(line) + 1;
2802                         char *outbuf = out_buffer;
2803                         size_t outlen = sizeof(out_buffer);
2805                         size_t ret;
2807                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2808                         if (ret != (size_t) -1)
2809                                 line = out_buffer;
2810                 }
2812                 if (!view->ops->read(view, line)) {
2813                         report("Allocation failure");
2814                         end_update(view, TRUE);
2815                         return FALSE;
2816                 }
2817         }
2819         {
2820                 unsigned long lines = view->lines;
2821                 int digits;
2823                 for (digits = 0; lines; digits++)
2824                         lines /= 10;
2826                 /* Keep the displayed view in sync with line number scaling. */
2827                 if (digits != view->digits) {
2828                         view->digits = digits;
2829                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2830                                 redraw = TRUE;
2831                 }
2832         }
2834         if (io_error(view->pipe)) {
2835                 report("Failed to read: %s", io_strerror(view->pipe));
2836                 end_update(view, TRUE);
2838         } else if (io_eof(view->pipe)) {
2839                 report("");
2840                 end_update(view, FALSE);
2841         }
2843         if (restore_view_position(view))
2844                 redraw = TRUE;
2846         if (!view_is_displayed(view))
2847                 return TRUE;
2849         if (redraw)
2850                 redraw_view_from(view, 0);
2851         else
2852                 redraw_view_dirty(view);
2854         /* Update the title _after_ the redraw so that if the redraw picks up a
2855          * commit reference in view->ref it'll be available here. */
2856         update_view_title(view);
2857         update_display_cursor(view);
2858         return TRUE;
2861 static struct line *
2862 add_line_data(struct view *view, void *data, enum line_type type)
2864         struct line *line;
2866         if (!realloc_lines(view, view->lines + 1))
2867                 return NULL;
2869         line = &view->line[view->lines++];
2870         memset(line, 0, sizeof(*line));
2871         line->type = type;
2872         line->data = data;
2873         line->dirty = 1;
2875         return line;
2878 static struct line *
2879 add_line_text(struct view *view, const char *text, enum line_type type)
2881         char *data = text ? strdup(text) : NULL;
2883         return data ? add_line_data(view, data, type) : NULL;
2886 static struct line *
2887 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2889         char buf[SIZEOF_STR];
2890         va_list args;
2892         va_start(args, fmt);
2893         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2894                 buf[0] = 0;
2895         va_end(args);
2897         return buf[0] ? add_line_text(view, buf, type) : NULL;
2900 /*
2901  * View opening
2902  */
2904 enum open_flags {
2905         OPEN_DEFAULT = 0,       /* Use default view switching. */
2906         OPEN_SPLIT = 1,         /* Split current view. */
2907         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2908         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2909         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2910         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2911         OPEN_PREPARED = 32,     /* Open already prepared command. */
2912 };
2914 static void
2915 open_view(struct view *prev, enum request request, enum open_flags flags)
2917         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2918         bool split = !!(flags & OPEN_SPLIT);
2919         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2920         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2921         struct view *view = VIEW(request);
2922         int nviews = displayed_views();
2923         struct view *base_view = display[0];
2925         if (view == prev && nviews == 1 && !reload) {
2926                 report("Already in %s view", view->name);
2927                 return;
2928         }
2930         if (view->git_dir && !opt_git_dir[0]) {
2931                 report("The %s view is disabled in pager view", view->name);
2932                 return;
2933         }
2935         if (split) {
2936                 display[1] = view;
2937                 if (!backgrounded)
2938                         current_view = 1;
2939         } else if (!nomaximize) {
2940                 /* Maximize the current view. */
2941                 memset(display, 0, sizeof(display));
2942                 current_view = 0;
2943                 display[current_view] = view;
2944         }
2946         /* Resize the view when switching between split- and full-screen,
2947          * or when switching between two different full-screen views. */
2948         if (nviews != displayed_views() ||
2949             (nviews == 1 && base_view != display[0]))
2950                 resize_display();
2952         if (view->ops->open) {
2953                 if (view->pipe)
2954                         end_update(view, TRUE);
2955                 if (!view->ops->open(view)) {
2956                         report("Failed to load %s view", view->name);
2957                         return;
2958                 }
2959                 restore_view_position(view);
2961         } else if ((reload || strcmp(view->vid, view->id)) &&
2962                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2963                 report("Failed to load %s view", view->name);
2964                 return;
2965         }
2967         if (split && prev->lineno - prev->offset >= prev->height) {
2968                 /* Take the title line into account. */
2969                 int lines = prev->lineno - prev->offset - prev->height + 1;
2971                 /* Scroll the view that was split if the current line is
2972                  * outside the new limited view. */
2973                 do_scroll_view(prev, lines);
2974         }
2976         if (prev && view != prev) {
2977                 if (split && !backgrounded) {
2978                         /* "Blur" the previous view. */
2979                         update_view_title(prev);
2980                 }
2982                 view->parent = prev;
2983         }
2985         if (view->pipe && view->lines == 0) {
2986                 /* Clear the old view and let the incremental updating refill
2987                  * the screen. */
2988                 werase(view->win);
2989                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2990                 report("");
2991         } else if (view_is_displayed(view)) {
2992                 redraw_view(view);
2993                 report("");
2994         }
2996         /* If the view is backgrounded the above calls to report()
2997          * won't redraw the view title. */
2998         if (backgrounded)
2999                 update_view_title(view);
3002 static void
3003 open_external_viewer(const char *argv[], const char *dir)
3005         def_prog_mode();           /* save current tty modes */
3006         endwin();                  /* restore original tty modes */
3007         run_io_fg(argv, dir);
3008         fprintf(stderr, "Press Enter to continue");
3009         getc(opt_tty);
3010         reset_prog_mode();
3011         redraw_display(TRUE);
3014 static void
3015 open_mergetool(const char *file)
3017         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3019         open_external_viewer(mergetool_argv, opt_cdup);
3022 static void
3023 open_editor(bool from_root, const char *file)
3025         const char *editor_argv[] = { "vi", file, NULL };
3026         const char *editor;
3028         editor = getenv("GIT_EDITOR");
3029         if (!editor && *opt_editor)
3030                 editor = opt_editor;
3031         if (!editor)
3032                 editor = getenv("VISUAL");
3033         if (!editor)
3034                 editor = getenv("EDITOR");
3035         if (!editor)
3036                 editor = "vi";
3038         editor_argv[0] = editor;
3039         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3042 static void
3043 open_run_request(enum request request)
3045         struct run_request *req = get_run_request(request);
3046         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3048         if (!req) {
3049                 report("Unknown run request");
3050                 return;
3051         }
3053         if (format_argv(argv, req->argv, FORMAT_ALL))
3054                 open_external_viewer(argv, NULL);
3055         free_argv(argv);
3058 /*
3059  * User request switch noodle
3060  */
3062 static int
3063 view_driver(struct view *view, enum request request)
3065         int i;
3067         if (request == REQ_NONE) {
3068                 doupdate();
3069                 return TRUE;
3070         }
3072         if (request > REQ_NONE) {
3073                 open_run_request(request);
3074                 /* FIXME: When all views can refresh always do this. */
3075                 if (view == VIEW(REQ_VIEW_STATUS) ||
3076                     view == VIEW(REQ_VIEW_MAIN) ||
3077                     view == VIEW(REQ_VIEW_LOG) ||
3078                     view == VIEW(REQ_VIEW_STAGE))
3079                         request = REQ_REFRESH;
3080                 else
3081                         return TRUE;
3082         }
3084         if (view && view->lines) {
3085                 request = view->ops->request(view, request, &view->line[view->lineno]);
3086                 if (request == REQ_NONE)
3087                         return TRUE;
3088         }
3090         switch (request) {
3091         case REQ_MOVE_UP:
3092         case REQ_MOVE_DOWN:
3093         case REQ_MOVE_PAGE_UP:
3094         case REQ_MOVE_PAGE_DOWN:
3095         case REQ_MOVE_FIRST_LINE:
3096         case REQ_MOVE_LAST_LINE:
3097                 move_view(view, request);
3098                 break;
3100         case REQ_SCROLL_LINE_DOWN:
3101         case REQ_SCROLL_LINE_UP:
3102         case REQ_SCROLL_PAGE_DOWN:
3103         case REQ_SCROLL_PAGE_UP:
3104                 scroll_view(view, request);
3105                 break;
3107         case REQ_VIEW_BLAME:
3108                 if (!opt_file[0]) {
3109                         report("No file chosen, press %s to open tree view",
3110                                get_key(REQ_VIEW_TREE));
3111                         break;
3112                 }
3113                 open_view(view, request, OPEN_DEFAULT);
3114                 break;
3116         case REQ_VIEW_BLOB:
3117                 if (!ref_blob[0]) {
3118                         report("No file chosen, press %s to open tree view",
3119                                get_key(REQ_VIEW_TREE));
3120                         break;
3121                 }
3122                 open_view(view, request, OPEN_DEFAULT);
3123                 break;
3125         case REQ_VIEW_PAGER:
3126                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3127                         report("No pager content, press %s to run command from prompt",
3128                                get_key(REQ_PROMPT));
3129                         break;
3130                 }
3131                 open_view(view, request, OPEN_DEFAULT);
3132                 break;
3134         case REQ_VIEW_STAGE:
3135                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3136                         report("No stage content, press %s to open the status view and choose file",
3137                                get_key(REQ_VIEW_STATUS));
3138                         break;
3139                 }
3140                 open_view(view, request, OPEN_DEFAULT);
3141                 break;
3143         case REQ_VIEW_STATUS:
3144                 if (opt_is_inside_work_tree == FALSE) {
3145                         report("The status view requires a working tree");
3146                         break;
3147                 }
3148                 open_view(view, request, OPEN_DEFAULT);
3149                 break;
3151         case REQ_VIEW_MAIN:
3152         case REQ_VIEW_DIFF:
3153         case REQ_VIEW_LOG:
3154         case REQ_VIEW_TREE:
3155         case REQ_VIEW_HELP:
3156                 open_view(view, request, OPEN_DEFAULT);
3157                 break;
3159         case REQ_NEXT:
3160         case REQ_PREVIOUS:
3161                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3163                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3164                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3165                    (view == VIEW(REQ_VIEW_DIFF) &&
3166                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3167                    (view == VIEW(REQ_VIEW_STAGE) &&
3168                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3169                    (view == VIEW(REQ_VIEW_BLOB) &&
3170                      view->parent == VIEW(REQ_VIEW_TREE))) {
3171                         int line;
3173                         view = view->parent;
3174                         line = view->lineno;
3175                         move_view(view, request);
3176                         if (view_is_displayed(view))
3177                                 update_view_title(view);
3178                         if (line != view->lineno)
3179                                 view->ops->request(view, REQ_ENTER,
3180                                                    &view->line[view->lineno]);
3182                 } else {
3183                         move_view(view, request);
3184                 }
3185                 break;
3187         case REQ_VIEW_NEXT:
3188         {
3189                 int nviews = displayed_views();
3190                 int next_view = (current_view + 1) % nviews;
3192                 if (next_view == current_view) {
3193                         report("Only one view is displayed");
3194                         break;
3195                 }
3197                 current_view = next_view;
3198                 /* Blur out the title of the previous view. */
3199                 update_view_title(view);
3200                 report("");
3201                 break;
3202         }
3203         case REQ_REFRESH:
3204                 report("Refreshing is not yet supported for the %s view", view->name);
3205                 break;
3207         case REQ_MAXIMIZE:
3208                 if (displayed_views() == 2)
3209                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3210                 break;
3212         case REQ_TOGGLE_LINENO:
3213                 toggle_view_option(&opt_line_number, "line numbers");
3214                 break;
3216         case REQ_TOGGLE_DATE:
3217                 toggle_view_option(&opt_date, "date display");
3218                 break;
3220         case REQ_TOGGLE_AUTHOR:
3221                 toggle_view_option(&opt_author, "author display");
3222                 break;
3224         case REQ_TOGGLE_REV_GRAPH:
3225                 toggle_view_option(&opt_rev_graph, "revision graph display");
3226                 break;
3228         case REQ_TOGGLE_REFS:
3229                 toggle_view_option(&opt_show_refs, "reference display");
3230                 break;
3232         case REQ_SEARCH:
3233         case REQ_SEARCH_BACK:
3234                 search_view(view, request);
3235                 break;
3237         case REQ_FIND_NEXT:
3238         case REQ_FIND_PREV:
3239                 find_next(view, request);
3240                 break;
3242         case REQ_STOP_LOADING:
3243                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3244                         view = &views[i];
3245                         if (view->pipe)
3246                                 report("Stopped loading the %s view", view->name),
3247                         end_update(view, TRUE);
3248                 }
3249                 break;
3251         case REQ_SHOW_VERSION:
3252                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3253                 return TRUE;
3255         case REQ_SCREEN_REDRAW:
3256                 redraw_display(TRUE);
3257                 break;
3259         case REQ_EDIT:
3260                 report("Nothing to edit");
3261                 break;
3263         case REQ_ENTER:
3264                 report("Nothing to enter");
3265                 break;
3267         case REQ_VIEW_CLOSE:
3268                 /* XXX: Mark closed views by letting view->parent point to the
3269                  * view itself. Parents to closed view should never be
3270                  * followed. */
3271                 if (view->parent &&
3272                     view->parent->parent != view->parent) {
3273                         memset(display, 0, sizeof(display));
3274                         current_view = 0;
3275                         display[current_view] = view->parent;
3276                         view->parent = view;
3277                         resize_display();
3278                         redraw_display(FALSE);
3279                         report("");
3280                         break;
3281                 }
3282                 /* Fall-through */
3283         case REQ_QUIT:
3284                 return FALSE;
3286         default:
3287                 report("Unknown key, press 'h' for help");
3288                 return TRUE;
3289         }
3291         return TRUE;
3295 /*
3296  * View backend utilities
3297  */
3299 /* Parse author lines where the name may be empty:
3300  *      author  <email@address.tld> 1138474660 +0100
3301  */
3302 static void
3303 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3305         char *nameend = strchr(ident, '<');
3306         char *emailend = strchr(ident, '>');
3308         if (nameend && emailend)
3309                 *nameend = *emailend = 0;
3310         ident = chomp_string(ident);
3311         if (!*ident) {
3312                 if (nameend)
3313                         ident = chomp_string(nameend + 1);
3314                 if (!*ident)
3315                         ident = "Unknown";
3316         }
3318         string_ncopy_do(author, authorsize, ident, strlen(ident));
3320         /* Parse epoch and timezone */
3321         if (emailend && emailend[1] == ' ') {
3322                 char *secs = emailend + 2;
3323                 char *zone = strchr(secs, ' ');
3324                 time_t time = (time_t) atol(secs);
3326                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3327                         long tz;
3329                         zone++;
3330                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3331                         tz += ('0' - zone[2]) * 60 * 60;
3332                         tz += ('0' - zone[3]) * 60;
3333                         tz += ('0' - zone[4]) * 60;
3335                         if (zone[0] == '-')
3336                                 tz = -tz;
3338                         time -= tz;
3339                 }
3341                 gmtime_r(&time, tm);
3342         }
3345 static enum input_status
3346 select_commit_parent_handler(void *data, char *buf, int c)
3348         size_t parents = *(size_t *) data;
3349         int parent = 0;
3351         if (!isdigit(c))
3352                 return INPUT_SKIP;
3354         if (*buf)
3355                 parent = atoi(buf) * 10;
3356         parent += c - '0';
3358         if (parent > parents)
3359                 return INPUT_SKIP;
3360         return INPUT_OK;
3363 static bool
3364 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3366         char buf[SIZEOF_STR * 4];
3367         const char *revlist_argv[] = {
3368                 "git", "rev-list", "-1", "--parents", id, NULL
3369         };
3370         int parents;
3372         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3373             !*chomp_string(buf) ||
3374             (parents = (strlen(buf) / 40) - 1) < 0) {
3375                 report("Failed to get parent information");
3376                 return FALSE;
3378         } else if (parents == 0) {
3379                 report("The selected commit has no parents");
3380                 return FALSE;
3381         }
3383         if (parents > 1) {
3384                 char prompt[SIZEOF_STR];
3385                 char *result;
3387                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3388                         return FALSE;
3389                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3390                 if (!result)
3391                         return FALSE;
3392                 parents = atoi(result);
3393         }
3395         string_copy_rev(rev, &buf[41 * parents]);
3396         return TRUE;
3399 /*
3400  * Pager backend
3401  */
3403 static bool
3404 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3406         char *text = line->data;
3408         if (opt_line_number && draw_lineno(view, lineno))
3409                 return TRUE;
3411         draw_text(view, line->type, text, TRUE);
3412         return TRUE;
3415 static bool
3416 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3418         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3419         char refbuf[SIZEOF_STR];
3420         char *ref = NULL;
3422         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3423                 ref = chomp_string(refbuf);
3425         if (!ref || !*ref)
3426                 return TRUE;
3428         /* This is the only fatal call, since it can "corrupt" the buffer. */
3429         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3430                 return FALSE;
3432         return TRUE;
3435 static void
3436 add_pager_refs(struct view *view, struct line *line)
3438         char buf[SIZEOF_STR];
3439         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3440         struct ref **refs;
3441         size_t bufpos = 0, refpos = 0;
3442         const char *sep = "Refs: ";
3443         bool is_tag = FALSE;
3445         assert(line->type == LINE_COMMIT);
3447         refs = get_refs(commit_id);
3448         if (!refs) {
3449                 if (view == VIEW(REQ_VIEW_DIFF))
3450                         goto try_add_describe_ref;
3451                 return;
3452         }
3454         do {
3455                 struct ref *ref = refs[refpos];
3456                 const char *fmt = ref->tag    ? "%s[%s]" :
3457                                   ref->remote ? "%s<%s>" : "%s%s";
3459                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3460                         return;
3461                 sep = ", ";
3462                 if (ref->tag)
3463                         is_tag = TRUE;
3464         } while (refs[refpos++]->next);
3466         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3467 try_add_describe_ref:
3468                 /* Add <tag>-g<commit_id> "fake" reference. */
3469                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3470                         return;
3471         }
3473         if (bufpos == 0)
3474                 return;
3476         add_line_text(view, buf, LINE_PP_REFS);
3479 static bool
3480 pager_read(struct view *view, char *data)
3482         struct line *line;
3484         if (!data)
3485                 return TRUE;
3487         line = add_line_text(view, data, get_line_type(data));
3488         if (!line)
3489                 return FALSE;
3491         if (line->type == LINE_COMMIT &&
3492             (view == VIEW(REQ_VIEW_DIFF) ||
3493              view == VIEW(REQ_VIEW_LOG)))
3494                 add_pager_refs(view, line);
3496         return TRUE;
3499 static enum request
3500 pager_request(struct view *view, enum request request, struct line *line)
3502         int split = 0;
3504         if (request != REQ_ENTER)
3505                 return request;
3507         if (line->type == LINE_COMMIT &&
3508            (view == VIEW(REQ_VIEW_LOG) ||
3509             view == VIEW(REQ_VIEW_PAGER))) {
3510                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3511                 split = 1;
3512         }
3514         /* Always scroll the view even if it was split. That way
3515          * you can use Enter to scroll through the log view and
3516          * split open each commit diff. */
3517         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3519         /* FIXME: A minor workaround. Scrolling the view will call report("")
3520          * but if we are scrolling a non-current view this won't properly
3521          * update the view title. */
3522         if (split)
3523                 update_view_title(view);
3525         return REQ_NONE;
3528 static bool
3529 pager_grep(struct view *view, struct line *line)
3531         regmatch_t pmatch;
3532         char *text = line->data;
3534         if (!*text)
3535                 return FALSE;
3537         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3538                 return FALSE;
3540         return TRUE;
3543 static void
3544 pager_select(struct view *view, struct line *line)
3546         if (line->type == LINE_COMMIT) {
3547                 char *text = (char *)line->data + STRING_SIZE("commit ");
3549                 if (view != VIEW(REQ_VIEW_PAGER))
3550                         string_copy_rev(view->ref, text);
3551                 string_copy_rev(ref_commit, text);
3552         }
3555 static struct view_ops pager_ops = {
3556         "line",
3557         NULL,
3558         NULL,
3559         pager_read,
3560         pager_draw,
3561         pager_request,
3562         pager_grep,
3563         pager_select,
3564 };
3566 static const char *log_argv[SIZEOF_ARG] = {
3567         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3568 };
3570 static enum request
3571 log_request(struct view *view, enum request request, struct line *line)
3573         switch (request) {
3574         case REQ_REFRESH:
3575                 load_refs();
3576                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3577                 return REQ_NONE;
3578         default:
3579                 return pager_request(view, request, line);
3580         }
3583 static struct view_ops log_ops = {
3584         "line",
3585         log_argv,
3586         NULL,
3587         pager_read,
3588         pager_draw,
3589         log_request,
3590         pager_grep,
3591         pager_select,
3592 };
3594 static const char *diff_argv[SIZEOF_ARG] = {
3595         "git", "show", "--pretty=fuller", "--no-color", "--root",
3596                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3597 };
3599 static struct view_ops diff_ops = {
3600         "line",
3601         diff_argv,
3602         NULL,
3603         pager_read,
3604         pager_draw,
3605         pager_request,
3606         pager_grep,
3607         pager_select,
3608 };
3610 /*
3611  * Help backend
3612  */
3614 static bool
3615 help_open(struct view *view)
3617         char buf[SIZEOF_STR];
3618         size_t bufpos;
3619         int i;
3621         if (view->lines > 0)
3622                 return TRUE;
3624         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3626         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3627                 const char *key;
3629                 if (req_info[i].request == REQ_NONE)
3630                         continue;
3632                 if (!req_info[i].request) {
3633                         add_line_text(view, "", LINE_DEFAULT);
3634                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3635                         continue;
3636                 }
3638                 key = get_key(req_info[i].request);
3639                 if (!*key)
3640                         key = "(no key defined)";
3642                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3643                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3644                         if (buf[bufpos] == '_')
3645                                 buf[bufpos] = '-';
3646                 }
3648                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3649                                 key, buf, req_info[i].help);
3650         }
3652         if (run_requests) {
3653                 add_line_text(view, "", LINE_DEFAULT);
3654                 add_line_text(view, "External commands:", LINE_DEFAULT);
3655         }
3657         for (i = 0; i < run_requests; i++) {
3658                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3659                 const char *key;
3660                 int argc;
3662                 if (!req)
3663                         continue;
3665                 key = get_key_name(req->key);
3666                 if (!*key)
3667                         key = "(no key defined)";
3669                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3670                         if (!string_format_from(buf, &bufpos, "%s%s",
3671                                                 argc ? " " : "", req->argv[argc]))
3672                                 return REQ_NONE;
3674                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3675                                 keymap_table[req->keymap].name, key, buf);
3676         }
3678         return TRUE;
3681 static struct view_ops help_ops = {
3682         "line",
3683         NULL,
3684         help_open,
3685         NULL,
3686         pager_draw,
3687         pager_request,
3688         pager_grep,
3689         pager_select,
3690 };
3693 /*
3694  * Tree backend
3695  */
3697 struct tree_stack_entry {
3698         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3699         unsigned long lineno;           /* Line number to restore */
3700         char *name;                     /* Position of name in opt_path */
3701 };
3703 /* The top of the path stack. */
3704 static struct tree_stack_entry *tree_stack = NULL;
3705 unsigned long tree_lineno = 0;
3707 static void
3708 pop_tree_stack_entry(void)
3710         struct tree_stack_entry *entry = tree_stack;
3712         tree_lineno = entry->lineno;
3713         entry->name[0] = 0;
3714         tree_stack = entry->prev;
3715         free(entry);
3718 static void
3719 push_tree_stack_entry(const char *name, unsigned long lineno)
3721         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3722         size_t pathlen = strlen(opt_path);
3724         if (!entry)
3725                 return;
3727         entry->prev = tree_stack;
3728         entry->name = opt_path + pathlen;
3729         tree_stack = entry;
3731         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3732                 pop_tree_stack_entry();
3733                 return;
3734         }
3736         /* Move the current line to the first tree entry. */
3737         tree_lineno = 1;
3738         entry->lineno = lineno;
3741 /* Parse output from git-ls-tree(1):
3742  *
3743  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3744  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3745  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3746  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3747  */
3749 #define SIZEOF_TREE_ATTR \
3750         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3752 #define SIZEOF_TREE_MODE \
3753         STRING_SIZE("100644 ")
3755 #define TREE_ID_OFFSET \
3756         STRING_SIZE("100644 blob ")
3758 struct tree_entry {
3759         char id[SIZEOF_REV];
3760         mode_t mode;
3761         struct tm time;                 /* Date from the author ident. */
3762         char author[75];                /* Author of the commit. */
3763         char name[1];
3764 };
3766 static const char *
3767 tree_path(struct line *line)
3769         return ((struct tree_entry *) line->data)->name;
3773 static int
3774 tree_compare_entry(struct line *line1, struct line *line2)
3776         if (line1->type != line2->type)
3777                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3778         return strcmp(tree_path(line1), tree_path(line2));
3781 static struct line *
3782 tree_entry(struct view *view, enum line_type type, const char *path,
3783            const char *mode, const char *id)
3785         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3786         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3788         if (!entry || !line) {
3789                 free(entry);
3790                 return NULL;
3791         }
3793         strncpy(entry->name, path, strlen(path));
3794         if (mode)
3795                 entry->mode = strtoul(mode, NULL, 8);
3796         if (id)
3797                 string_copy_rev(entry->id, id);
3799         return line;
3802 static bool
3803 tree_read_date(struct view *view, char *text, bool *read_date)
3805         static char author_name[SIZEOF_STR];
3806         static struct tm author_time;
3808         if (!text && *read_date) {
3809                 *read_date = FALSE;
3810                 return TRUE;
3812         } else if (!text) {
3813                 char *path = *opt_path ? opt_path : ".";
3814                 /* Find next entry to process */
3815                 const char *log_file[] = {
3816                         "git", "log", "--no-color", "--pretty=raw",
3817                                 "--cc", "--raw", view->id, "--", path, NULL
3818                 };
3819                 struct io io = {};
3821                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3822                         report("Failed to load tree data");
3823                         return TRUE;
3824                 }
3826                 done_io(view->pipe);
3827                 view->io = io;
3828                 *read_date = TRUE;
3829                 return FALSE;
3831         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3832                 parse_author_line(text + STRING_SIZE("author "),
3833                                   author_name, sizeof(author_name), &author_time);
3835         } else if (*text == ':') {
3836                 char *pos;
3837                 size_t annotated = 1;
3838                 size_t i;
3840                 pos = strchr(text, '\t');
3841                 if (!pos)
3842                         return TRUE;
3843                 text = pos + 1;
3844                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3845                         text += strlen(opt_prefix);
3846                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3847                         text += strlen(opt_path);
3848                 pos = strchr(text, '/');
3849                 if (pos)
3850                         *pos = 0;
3852                 for (i = 1; i < view->lines; i++) {
3853                         struct line *line = &view->line[i];
3854                         struct tree_entry *entry = line->data;
3856                         annotated += !!*entry->author;
3857                         if (*entry->author || strcmp(entry->name, text))
3858                                 continue;
3860                         string_copy(entry->author, author_name);
3861                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3862                         line->dirty = 1;
3863                         break;
3864                 }
3866                 if (annotated == view->lines)
3867                         kill_io(view->pipe);
3868         }
3869         return TRUE;
3872 static bool
3873 tree_read(struct view *view, char *text)
3875         static bool read_date = FALSE;
3876         struct tree_entry *data;
3877         struct line *entry, *line;
3878         enum line_type type;
3879         size_t textlen = text ? strlen(text) : 0;
3880         char *path = text + SIZEOF_TREE_ATTR;
3882         if (read_date || !text)
3883                 return tree_read_date(view, text, &read_date);
3885         if (textlen <= SIZEOF_TREE_ATTR)
3886                 return FALSE;
3887         if (view->lines == 0 &&
3888             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3889                 return FALSE;
3891         /* Strip the path part ... */
3892         if (*opt_path) {
3893                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3894                 size_t striplen = strlen(opt_path);
3896                 if (pathlen > striplen)
3897                         memmove(path, path + striplen,
3898                                 pathlen - striplen + 1);
3900                 /* Insert "link" to parent directory. */
3901                 if (view->lines == 1 &&
3902                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3903                         return FALSE;
3904         }
3906         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3907         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3908         if (!entry)
3909                 return FALSE;
3910         data = entry->data;
3912         /* Skip "Directory ..." and ".." line. */
3913         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3914                 if (tree_compare_entry(line, entry) <= 0)
3915                         continue;
3917                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3919                 line->data = data;
3920                 line->type = type;
3921                 for (; line <= entry; line++)
3922                         line->dirty = line->cleareol = 1;
3923                 return TRUE;
3924         }
3926         if (tree_lineno > view->lineno) {
3927                 view->lineno = tree_lineno;
3928                 tree_lineno = 0;
3929         }
3931         return TRUE;
3934 static bool
3935 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3937         struct tree_entry *entry = line->data;
3939         if (line->type == LINE_TREE_PARENT) {
3940                 if (draw_text(view, line->type, "Directory path /", TRUE))
3941                         return TRUE;
3942         } else {
3943                 char mode[11] = "-r--r--r--";
3945                 if (S_ISDIR(entry->mode)) {
3946                         mode[3] = mode[6] = mode[9] = 'x';
3947                         mode[0] = 'd';
3948                 }
3949                 if (S_ISLNK(entry->mode))
3950                         mode[0] = 'l';
3951                 if (entry->mode & S_IWUSR)
3952                         mode[2] = 'w';
3953                 if (entry->mode & S_IXUSR)
3954                         mode[3] = 'x';
3955                 if (entry->mode & S_IXGRP)
3956                         mode[6] = 'x';
3957                 if (entry->mode & S_IXOTH)
3958                         mode[9] = 'x';
3959                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3960                         return TRUE;
3962                 if (opt_author && draw_author(view, entry->author))
3963                         return TRUE;
3965                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3966                         return TRUE;
3967         }
3968         if (draw_text(view, line->type, entry->name, TRUE))
3969                 return TRUE;
3970         return TRUE;
3973 static void
3974 open_blob_editor()
3976         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3977         int fd = mkstemp(file);
3979         if (fd == -1)
3980                 report("Failed to create temporary file");
3981         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3982                 report("Failed to save blob data to file");
3983         else
3984                 open_editor(FALSE, file);
3985         if (fd != -1)
3986                 unlink(file);
3989 static enum request
3990 tree_request(struct view *view, enum request request, struct line *line)
3992         enum open_flags flags;
3994         switch (request) {
3995         case REQ_VIEW_BLAME:
3996                 if (line->type != LINE_TREE_FILE) {
3997                         report("Blame only supported for files");
3998                         return REQ_NONE;
3999                 }
4001                 string_copy(opt_ref, view->vid);
4002                 return request;
4004         case REQ_EDIT:
4005                 if (line->type != LINE_TREE_FILE) {
4006                         report("Edit only supported for files");
4007                 } else if (!is_head_commit(view->vid)) {
4008                         open_blob_editor();
4009                 } else {
4010                         open_editor(TRUE, opt_file);
4011                 }
4012                 return REQ_NONE;
4014         case REQ_PARENT:
4015                 if (!*opt_path) {
4016                         /* quit view if at top of tree */
4017                         return REQ_VIEW_CLOSE;
4018                 }
4019                 /* fake 'cd  ..' */
4020                 line = &view->line[1];
4021                 break;
4023         case REQ_ENTER:
4024                 break;
4026         default:
4027                 return request;
4028         }
4030         /* Cleanup the stack if the tree view is at a different tree. */
4031         while (!*opt_path && tree_stack)
4032                 pop_tree_stack_entry();
4034         switch (line->type) {
4035         case LINE_TREE_DIR:
4036                 /* Depending on whether it is a subdir or parent (updir?) link
4037                  * mangle the path buffer. */
4038                 if (line == &view->line[1] && *opt_path) {
4039                         pop_tree_stack_entry();
4041                 } else {
4042                         const char *basename = tree_path(line);
4044                         push_tree_stack_entry(basename, view->lineno);
4045                 }
4047                 /* Trees and subtrees share the same ID, so they are not not
4048                  * unique like blobs. */
4049                 flags = OPEN_RELOAD;
4050                 request = REQ_VIEW_TREE;
4051                 break;
4053         case LINE_TREE_FILE:
4054                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4055                 request = REQ_VIEW_BLOB;
4056                 break;
4058         default:
4059                 return REQ_NONE;
4060         }
4062         open_view(view, request, flags);
4063         if (request == REQ_VIEW_TREE)
4064                 view->lineno = tree_lineno;
4066         return REQ_NONE;
4069 static void
4070 tree_select(struct view *view, struct line *line)
4072         struct tree_entry *entry = line->data;
4074         if (line->type == LINE_TREE_FILE) {
4075                 string_copy_rev(ref_blob, entry->id);
4076                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4078         } else if (line->type != LINE_TREE_DIR) {
4079                 return;
4080         }
4082         string_copy_rev(view->ref, entry->id);
4085 static const char *tree_argv[SIZEOF_ARG] = {
4086         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4087 };
4089 static struct view_ops tree_ops = {
4090         "file",
4091         tree_argv,
4092         NULL,
4093         tree_read,
4094         tree_draw,
4095         tree_request,
4096         pager_grep,
4097         tree_select,
4098 };
4100 static bool
4101 blob_read(struct view *view, char *line)
4103         if (!line)
4104                 return TRUE;
4105         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4108 static enum request
4109 blob_request(struct view *view, enum request request, struct line *line)
4111         switch (request) {
4112         case REQ_EDIT:
4113                 open_blob_editor();
4114                 return REQ_NONE;
4115         default:
4116                 return pager_request(view, request, line);
4117         }
4120 static const char *blob_argv[SIZEOF_ARG] = {
4121         "git", "cat-file", "blob", "%(blob)", NULL
4122 };
4124 static struct view_ops blob_ops = {
4125         "line",
4126         blob_argv,
4127         NULL,
4128         blob_read,
4129         pager_draw,
4130         blob_request,
4131         pager_grep,
4132         pager_select,
4133 };
4135 /*
4136  * Blame backend
4137  *
4138  * Loading the blame view is a two phase job:
4139  *
4140  *  1. File content is read either using opt_file from the
4141  *     filesystem or using git-cat-file.
4142  *  2. Then blame information is incrementally added by
4143  *     reading output from git-blame.
4144  */
4146 static const char *blame_head_argv[] = {
4147         "git", "blame", "--incremental", "--", "%(file)", NULL
4148 };
4150 static const char *blame_ref_argv[] = {
4151         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4152 };
4154 static const char *blame_cat_file_argv[] = {
4155         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4156 };
4158 struct blame_commit {
4159         char id[SIZEOF_REV];            /* SHA1 ID. */
4160         char title[128];                /* First line of the commit message. */
4161         char author[75];                /* Author of the commit. */
4162         struct tm time;                 /* Date from the author ident. */
4163         char filename[128];             /* Name of file. */
4164         bool has_previous;              /* Was a "previous" line detected. */
4165 };
4167 struct blame {
4168         struct blame_commit *commit;
4169         char text[1];
4170 };
4172 static bool
4173 blame_open(struct view *view)
4175         if (*opt_ref || !io_open(&view->io, opt_file)) {
4176                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4177                         return FALSE;
4178         }
4180         setup_update(view, opt_file);
4181         string_format(view->ref, "%s ...", opt_file);
4183         return TRUE;
4186 static struct blame_commit *
4187 get_blame_commit(struct view *view, const char *id)
4189         size_t i;
4191         for (i = 0; i < view->lines; i++) {
4192                 struct blame *blame = view->line[i].data;
4194                 if (!blame->commit)
4195                         continue;
4197                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4198                         return blame->commit;
4199         }
4201         {
4202                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4204                 if (commit)
4205                         string_ncopy(commit->id, id, SIZEOF_REV);
4206                 return commit;
4207         }
4210 static bool
4211 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4213         const char *pos = *posref;
4215         *posref = NULL;
4216         pos = strchr(pos + 1, ' ');
4217         if (!pos || !isdigit(pos[1]))
4218                 return FALSE;
4219         *number = atoi(pos + 1);
4220         if (*number < min || *number > max)
4221                 return FALSE;
4223         *posref = pos;
4224         return TRUE;
4227 static struct blame_commit *
4228 parse_blame_commit(struct view *view, const char *text, int *blamed)
4230         struct blame_commit *commit;
4231         struct blame *blame;
4232         const char *pos = text + SIZEOF_REV - 1;
4233         size_t lineno;
4234         size_t group;
4236         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4237                 return NULL;
4239         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4240             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4241                 return NULL;
4243         commit = get_blame_commit(view, text);
4244         if (!commit)
4245                 return NULL;
4247         *blamed += group;
4248         while (group--) {
4249                 struct line *line = &view->line[lineno + group - 1];
4251                 blame = line->data;
4252                 blame->commit = commit;
4253                 line->dirty = 1;
4254         }
4256         return commit;
4259 static bool
4260 blame_read_file(struct view *view, const char *line, bool *read_file)
4262         if (!line) {
4263                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4264                 struct io io = {};
4266                 if (view->lines == 0 && !view->parent)
4267                         die("No blame exist for %s", view->vid);
4269                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4270                         report("Failed to load blame data");
4271                         return TRUE;
4272                 }
4274                 done_io(view->pipe);
4275                 view->io = io;
4276                 *read_file = FALSE;
4277                 return FALSE;
4279         } else {
4280                 size_t linelen = strlen(line);
4281                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4283                 blame->commit = NULL;
4284                 strncpy(blame->text, line, linelen);
4285                 blame->text[linelen] = 0;
4286                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4287         }
4290 static bool
4291 match_blame_header(const char *name, char **line)
4293         size_t namelen = strlen(name);
4294         bool matched = !strncmp(name, *line, namelen);
4296         if (matched)
4297                 *line += namelen;
4299         return matched;
4302 static bool
4303 blame_read(struct view *view, char *line)
4305         static struct blame_commit *commit = NULL;
4306         static int blamed = 0;
4307         static time_t author_time;
4308         static bool read_file = TRUE;
4310         if (read_file)
4311                 return blame_read_file(view, line, &read_file);
4313         if (!line) {
4314                 /* Reset all! */
4315                 commit = NULL;
4316                 blamed = 0;
4317                 read_file = TRUE;
4318                 string_format(view->ref, "%s", view->vid);
4319                 if (view_is_displayed(view)) {
4320                         update_view_title(view);
4321                         redraw_view_from(view, 0);
4322                 }
4323                 return TRUE;
4324         }
4326         if (!commit) {
4327                 commit = parse_blame_commit(view, line, &blamed);
4328                 string_format(view->ref, "%s %2d%%", view->vid,
4329                               view->lines ? blamed * 100 / view->lines : 0);
4331         } else if (match_blame_header("author ", &line)) {
4332                 string_ncopy(commit->author, line, strlen(line));
4334         } else if (match_blame_header("author-time ", &line)) {
4335                 author_time = (time_t) atol(line);
4337         } else if (match_blame_header("author-tz ", &line)) {
4338                 long tz;
4340                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4341                 tz += ('0' - line[2]) * 60 * 60;
4342                 tz += ('0' - line[3]) * 60;
4343                 tz += ('0' - line[4]) * 60;
4345                 if (line[0] == '-')
4346                         tz = -tz;
4348                 author_time -= tz;
4349                 gmtime_r(&author_time, &commit->time);
4351         } else if (match_blame_header("summary ", &line)) {
4352                 string_ncopy(commit->title, line, strlen(line));
4354         } else if (match_blame_header("previous ", &line)) {
4355                 commit->has_previous = TRUE;
4357         } else if (match_blame_header("filename ", &line)) {
4358                 string_ncopy(commit->filename, line, strlen(line));
4359                 commit = NULL;
4360         }
4362         return TRUE;
4365 static bool
4366 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4368         struct blame *blame = line->data;
4369         struct tm *time = NULL;
4370         const char *id = NULL, *author = NULL;
4372         if (blame->commit && *blame->commit->filename) {
4373                 id = blame->commit->id;
4374                 author = blame->commit->author;
4375                 time = &blame->commit->time;
4376         }
4378         if (opt_date && draw_date(view, time))
4379                 return TRUE;
4381         if (opt_author && draw_author(view, author))
4382                 return TRUE;
4384         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4385                 return TRUE;
4387         if (draw_lineno(view, lineno))
4388                 return TRUE;
4390         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4391         return TRUE;
4394 static bool
4395 check_blame_commit(struct blame *blame)
4397         if (!blame->commit)
4398                 report("Commit data not loaded yet");
4399         else if (!strcmp(blame->commit->id, NULL_ID))
4400                 report("No commit exist for the selected line");
4401         else
4402                 return TRUE;
4403         return FALSE;
4406 static enum request
4407 blame_request(struct view *view, enum request request, struct line *line)
4409         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4410         struct blame *blame = line->data;
4412         switch (request) {
4413         case REQ_VIEW_BLAME:
4414                 if (check_blame_commit(blame)) {
4415                         string_copy(opt_ref, blame->commit->id);
4416                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4417                 }
4418                 break;
4420         case REQ_PARENT:
4421                 if (check_blame_commit(blame) &&
4422                     select_commit_parent(blame->commit->id, opt_ref))
4423                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4424                 break;
4426         case REQ_ENTER:
4427                 if (!blame->commit) {
4428                         report("No commit loaded yet");
4429                         break;
4430                 }
4432                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4433                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4434                         break;
4436                 if (!strcmp(blame->commit->id, NULL_ID)) {
4437                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4438                         const char *diff_index_argv[] = {
4439                                 "git", "diff-index", "--root", "--patch-with-stat",
4440                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4441                         };
4443                         if (!blame->commit->has_previous) {
4444                                 diff_index_argv[1] = "diff";
4445                                 diff_index_argv[2] = "--no-color";
4446                                 diff_index_argv[6] = "--";
4447                                 diff_index_argv[7] = "/dev/null";
4448                         }
4450                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4451                                 report("Failed to allocate diff command");
4452                                 break;
4453                         }
4454                         flags |= OPEN_PREPARED;
4455                 }
4457                 open_view(view, REQ_VIEW_DIFF, flags);
4458                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4459                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4460                 break;
4462         default:
4463                 return request;
4464         }
4466         return REQ_NONE;
4469 static bool
4470 blame_grep(struct view *view, struct line *line)
4472         struct blame *blame = line->data;
4473         struct blame_commit *commit = blame->commit;
4474         regmatch_t pmatch;
4476 #define MATCH(text, on)                                                 \
4477         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4479         if (commit) {
4480                 char buf[DATE_COLS + 1];
4482                 if (MATCH(commit->title, 1) ||
4483                     MATCH(commit->author, opt_author) ||
4484                     MATCH(commit->id, opt_date))
4485                         return TRUE;
4487                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4488                     MATCH(buf, 1))
4489                         return TRUE;
4490         }
4492         return MATCH(blame->text, 1);
4494 #undef MATCH
4497 static void
4498 blame_select(struct view *view, struct line *line)
4500         struct blame *blame = line->data;
4501         struct blame_commit *commit = blame->commit;
4503         if (!commit)
4504                 return;
4506         if (!strcmp(commit->id, NULL_ID))
4507                 string_ncopy(ref_commit, "HEAD", 4);
4508         else
4509                 string_copy_rev(ref_commit, commit->id);
4512 static struct view_ops blame_ops = {
4513         "line",
4514         NULL,
4515         blame_open,
4516         blame_read,
4517         blame_draw,
4518         blame_request,
4519         blame_grep,
4520         blame_select,
4521 };
4523 /*
4524  * Status backend
4525  */
4527 struct status {
4528         char status;
4529         struct {
4530                 mode_t mode;
4531                 char rev[SIZEOF_REV];
4532                 char name[SIZEOF_STR];
4533         } old;
4534         struct {
4535                 mode_t mode;
4536                 char rev[SIZEOF_REV];
4537                 char name[SIZEOF_STR];
4538         } new;
4539 };
4541 static char status_onbranch[SIZEOF_STR];
4542 static struct status stage_status;
4543 static enum line_type stage_line_type;
4544 static size_t stage_chunks;
4545 static int *stage_chunk;
4547 /* This should work even for the "On branch" line. */
4548 static inline bool
4549 status_has_none(struct view *view, struct line *line)
4551         return line < view->line + view->lines && !line[1].data;
4554 /* Get fields from the diff line:
4555  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4556  */
4557 static inline bool
4558 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4560         const char *old_mode = buf +  1;
4561         const char *new_mode = buf +  8;
4562         const char *old_rev  = buf + 15;
4563         const char *new_rev  = buf + 56;
4564         const char *status   = buf + 97;
4566         if (bufsize < 98 ||
4567             old_mode[-1] != ':' ||
4568             new_mode[-1] != ' ' ||
4569             old_rev[-1]  != ' ' ||
4570             new_rev[-1]  != ' ' ||
4571             status[-1]   != ' ')
4572                 return FALSE;
4574         file->status = *status;
4576         string_copy_rev(file->old.rev, old_rev);
4577         string_copy_rev(file->new.rev, new_rev);
4579         file->old.mode = strtoul(old_mode, NULL, 8);
4580         file->new.mode = strtoul(new_mode, NULL, 8);
4582         file->old.name[0] = file->new.name[0] = 0;
4584         return TRUE;
4587 static bool
4588 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4590         struct status *file = NULL;
4591         struct status *unmerged = NULL;
4592         char *buf;
4593         struct io io = {};
4595         if (!run_io(&io, argv, NULL, IO_RD))
4596                 return FALSE;
4598         add_line_data(view, NULL, type);
4600         while ((buf = io_get(&io, 0, TRUE))) {
4601                 if (!file) {
4602                         file = calloc(1, sizeof(*file));
4603                         if (!file || !add_line_data(view, file, type))
4604                                 goto error_out;
4605                 }
4607                 /* Parse diff info part. */
4608                 if (status) {
4609                         file->status = status;
4610                         if (status == 'A')
4611                                 string_copy(file->old.rev, NULL_ID);
4613                 } else if (!file->status) {
4614                         if (!status_get_diff(file, buf, strlen(buf)))
4615                                 goto error_out;
4617                         buf = io_get(&io, 0, TRUE);
4618                         if (!buf)
4619                                 break;
4621                         /* Collapse all 'M'odified entries that follow a
4622                          * associated 'U'nmerged entry. */
4623                         if (file->status == 'U') {
4624                                 unmerged = file;
4626                         } else if (unmerged) {
4627                                 int collapse = !strcmp(buf, unmerged->new.name);
4629                                 unmerged = NULL;
4630                                 if (collapse) {
4631                                         free(file);
4632                                         file = NULL;
4633                                         view->lines--;
4634                                         continue;
4635                                 }
4636                         }
4637                 }
4639                 /* Grab the old name for rename/copy. */
4640                 if (!*file->old.name &&
4641                     (file->status == 'R' || file->status == 'C')) {
4642                         string_ncopy(file->old.name, buf, strlen(buf));
4644                         buf = io_get(&io, 0, TRUE);
4645                         if (!buf)
4646                                 break;
4647                 }
4649                 /* git-ls-files just delivers a NUL separated list of
4650                  * file names similar to the second half of the
4651                  * git-diff-* output. */
4652                 string_ncopy(file->new.name, buf, strlen(buf));
4653                 if (!*file->old.name)
4654                         string_copy(file->old.name, file->new.name);
4655                 file = NULL;
4656         }
4658         if (io_error(&io)) {
4659 error_out:
4660                 done_io(&io);
4661                 return FALSE;
4662         }
4664         if (!view->line[view->lines - 1].data)
4665                 add_line_data(view, NULL, LINE_STAT_NONE);
4667         done_io(&io);
4668         return TRUE;
4671 /* Don't show unmerged entries in the staged section. */
4672 static const char *status_diff_index_argv[] = {
4673         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4674                              "--cached", "-M", "HEAD", NULL
4675 };
4677 static const char *status_diff_files_argv[] = {
4678         "git", "diff-files", "-z", NULL
4679 };
4681 static const char *status_list_other_argv[] = {
4682         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4683 };
4685 static const char *status_list_no_head_argv[] = {
4686         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4687 };
4689 static const char *update_index_argv[] = {
4690         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4691 };
4693 /* Restore the previous line number to stay in the context or select a
4694  * line with something that can be updated. */
4695 static void
4696 status_restore(struct view *view)
4698         if (view->p_lineno >= view->lines)
4699                 view->p_lineno = view->lines - 1;
4700         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4701                 view->p_lineno++;
4702         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4703                 view->p_lineno--;
4705         /* If the above fails, always skip the "On branch" line. */
4706         if (view->p_lineno < view->lines)
4707                 view->lineno = view->p_lineno;
4708         else
4709                 view->lineno = 1;
4711         if (view->lineno < view->offset)
4712                 view->offset = view->lineno;
4713         else if (view->offset + view->height <= view->lineno)
4714                 view->offset = view->lineno - view->height + 1;
4716         view->p_restore = FALSE;
4719 /* First parse staged info using git-diff-index(1), then parse unstaged
4720  * info using git-diff-files(1), and finally untracked files using
4721  * git-ls-files(1). */
4722 static bool
4723 status_open(struct view *view)
4725         reset_view(view);
4727         add_line_data(view, NULL, LINE_STAT_HEAD);
4728         if (is_initial_commit())
4729                 string_copy(status_onbranch, "Initial commit");
4730         else if (!*opt_head)
4731                 string_copy(status_onbranch, "Not currently on any branch");
4732         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4733                 return FALSE;
4735         run_io_bg(update_index_argv);
4737         if (is_initial_commit()) {
4738                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4739                         return FALSE;
4740         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4741                 return FALSE;
4742         }
4744         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4745             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4746                 return FALSE;
4748         /* Restore the exact position or use the specialized restore
4749          * mode? */
4750         if (!view->p_restore)
4751                 status_restore(view);
4752         return TRUE;
4755 static bool
4756 status_draw(struct view *view, struct line *line, unsigned int lineno)
4758         struct status *status = line->data;
4759         enum line_type type;
4760         const char *text;
4762         if (!status) {
4763                 switch (line->type) {
4764                 case LINE_STAT_STAGED:
4765                         type = LINE_STAT_SECTION;
4766                         text = "Changes to be committed:";
4767                         break;
4769                 case LINE_STAT_UNSTAGED:
4770                         type = LINE_STAT_SECTION;
4771                         text = "Changed but not updated:";
4772                         break;
4774                 case LINE_STAT_UNTRACKED:
4775                         type = LINE_STAT_SECTION;
4776                         text = "Untracked files:";
4777                         break;
4779                 case LINE_STAT_NONE:
4780                         type = LINE_DEFAULT;
4781                         text = "    (no files)";
4782                         break;
4784                 case LINE_STAT_HEAD:
4785                         type = LINE_STAT_HEAD;
4786                         text = status_onbranch;
4787                         break;
4789                 default:
4790                         return FALSE;
4791                 }
4792         } else {
4793                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4795                 buf[0] = status->status;
4796                 if (draw_text(view, line->type, buf, TRUE))
4797                         return TRUE;
4798                 type = LINE_DEFAULT;
4799                 text = status->new.name;
4800         }
4802         draw_text(view, type, text, TRUE);
4803         return TRUE;
4806 static enum request
4807 status_enter(struct view *view, struct line *line)
4809         struct status *status = line->data;
4810         const char *oldpath = status ? status->old.name : NULL;
4811         /* Diffs for unmerged entries are empty when passing the new
4812          * path, so leave it empty. */
4813         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4814         const char *info;
4815         enum open_flags split;
4816         struct view *stage = VIEW(REQ_VIEW_STAGE);
4818         if (line->type == LINE_STAT_NONE ||
4819             (!status && line[1].type == LINE_STAT_NONE)) {
4820                 report("No file to diff");
4821                 return REQ_NONE;
4822         }
4824         switch (line->type) {
4825         case LINE_STAT_STAGED:
4826                 if (is_initial_commit()) {
4827                         const char *no_head_diff_argv[] = {
4828                                 "git", "diff", "--no-color", "--patch-with-stat",
4829                                         "--", "/dev/null", newpath, NULL
4830                         };
4832                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4833                                 return REQ_QUIT;
4834                 } else {
4835                         const char *index_show_argv[] = {
4836                                 "git", "diff-index", "--root", "--patch-with-stat",
4837                                         "-C", "-M", "--cached", "HEAD", "--",
4838                                         oldpath, newpath, NULL
4839                         };
4841                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4842                                 return REQ_QUIT;
4843                 }
4845                 if (status)
4846                         info = "Staged changes to %s";
4847                 else
4848                         info = "Staged changes";
4849                 break;
4851         case LINE_STAT_UNSTAGED:
4852         {
4853                 const char *files_show_argv[] = {
4854                         "git", "diff-files", "--root", "--patch-with-stat",
4855                                 "-C", "-M", "--", oldpath, newpath, NULL
4856                 };
4858                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4859                         return REQ_QUIT;
4860                 if (status)
4861                         info = "Unstaged changes to %s";
4862                 else
4863                         info = "Unstaged changes";
4864                 break;
4865         }
4866         case LINE_STAT_UNTRACKED:
4867                 if (!newpath) {
4868                         report("No file to show");
4869                         return REQ_NONE;
4870                 }
4872                 if (!suffixcmp(status->new.name, -1, "/")) {
4873                         report("Cannot display a directory");
4874                         return REQ_NONE;
4875                 }
4877                 if (!prepare_update_file(stage, newpath))
4878                         return REQ_QUIT;
4879                 info = "Untracked file %s";
4880                 break;
4882         case LINE_STAT_HEAD:
4883                 return REQ_NONE;
4885         default:
4886                 die("line type %d not handled in switch", line->type);
4887         }
4889         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4890         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4891         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4892                 if (status) {
4893                         stage_status = *status;
4894                 } else {
4895                         memset(&stage_status, 0, sizeof(stage_status));
4896                 }
4898                 stage_line_type = line->type;
4899                 stage_chunks = 0;
4900                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4901         }
4903         return REQ_NONE;
4906 static bool
4907 status_exists(struct status *status, enum line_type type)
4909         struct view *view = VIEW(REQ_VIEW_STATUS);
4910         unsigned long lineno;
4912         for (lineno = 0; lineno < view->lines; lineno++) {
4913                 struct line *line = &view->line[lineno];
4914                 struct status *pos = line->data;
4916                 if (line->type != type)
4917                         continue;
4918                 if (!pos && (!status || !status->status) && line[1].data) {
4919                         select_view_line(view, lineno);
4920                         return TRUE;
4921                 }
4922                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4923                         select_view_line(view, lineno);
4924                         return TRUE;
4925                 }
4926         }
4928         return FALSE;
4932 static bool
4933 status_update_prepare(struct io *io, enum line_type type)
4935         const char *staged_argv[] = {
4936                 "git", "update-index", "-z", "--index-info", NULL
4937         };
4938         const char *others_argv[] = {
4939                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4940         };
4942         switch (type) {
4943         case LINE_STAT_STAGED:
4944                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4946         case LINE_STAT_UNSTAGED:
4947                 return run_io(io, others_argv, opt_cdup, IO_WR);
4949         case LINE_STAT_UNTRACKED:
4950                 return run_io(io, others_argv, NULL, IO_WR);
4952         default:
4953                 die("line type %d not handled in switch", type);
4954                 return FALSE;
4955         }
4958 static bool
4959 status_update_write(struct io *io, struct status *status, enum line_type type)
4961         char buf[SIZEOF_STR];
4962         size_t bufsize = 0;
4964         switch (type) {
4965         case LINE_STAT_STAGED:
4966                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4967                                         status->old.mode,
4968                                         status->old.rev,
4969                                         status->old.name, 0))
4970                         return FALSE;
4971                 break;
4973         case LINE_STAT_UNSTAGED:
4974         case LINE_STAT_UNTRACKED:
4975                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4976                         return FALSE;
4977                 break;
4979         default:
4980                 die("line type %d not handled in switch", type);
4981         }
4983         return io_write(io, buf, bufsize);
4986 static bool
4987 status_update_file(struct status *status, enum line_type type)
4989         struct io io = {};
4990         bool result;
4992         if (!status_update_prepare(&io, type))
4993                 return FALSE;
4995         result = status_update_write(&io, status, type);
4996         done_io(&io);
4997         return result;
5000 static bool
5001 status_update_files(struct view *view, struct line *line)
5003         struct io io = {};
5004         bool result = TRUE;
5005         struct line *pos = view->line + view->lines;
5006         int files = 0;
5007         int file, done;
5009         if (!status_update_prepare(&io, line->type))
5010                 return FALSE;
5012         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5013                 files++;
5015         for (file = 0, done = 0; result && file < files; line++, file++) {
5016                 int almost_done = file * 100 / files;
5018                 if (almost_done > done) {
5019                         done = almost_done;
5020                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5021                                       file, files, done);
5022                         update_view_title(view);
5023                 }
5024                 result = status_update_write(&io, line->data, line->type);
5025         }
5027         done_io(&io);
5028         return result;
5031 static bool
5032 status_update(struct view *view)
5034         struct line *line = &view->line[view->lineno];
5036         assert(view->lines);
5038         if (!line->data) {
5039                 /* This should work even for the "On branch" line. */
5040                 if (line < view->line + view->lines && !line[1].data) {
5041                         report("Nothing to update");
5042                         return FALSE;
5043                 }
5045                 if (!status_update_files(view, line + 1)) {
5046                         report("Failed to update file status");
5047                         return FALSE;
5048                 }
5050         } else if (!status_update_file(line->data, line->type)) {
5051                 report("Failed to update file status");
5052                 return FALSE;
5053         }
5055         return TRUE;
5058 static bool
5059 status_revert(struct status *status, enum line_type type, bool has_none)
5061         if (!status || type != LINE_STAT_UNSTAGED) {
5062                 if (type == LINE_STAT_STAGED) {
5063                         report("Cannot revert changes to staged files");
5064                 } else if (type == LINE_STAT_UNTRACKED) {
5065                         report("Cannot revert changes to untracked files");
5066                 } else if (has_none) {
5067                         report("Nothing to revert");
5068                 } else {
5069                         report("Cannot revert changes to multiple files");
5070                 }
5071                 return FALSE;
5073         } else {
5074                 const char *checkout_argv[] = {
5075                         "git", "checkout", "--", status->old.name, NULL
5076                 };
5078                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5079                         return FALSE;
5080                 return run_io_fg(checkout_argv, opt_cdup);
5081         }
5084 static enum request
5085 status_request(struct view *view, enum request request, struct line *line)
5087         struct status *status = line->data;
5089         switch (request) {
5090         case REQ_STATUS_UPDATE:
5091                 if (!status_update(view))
5092                         return REQ_NONE;
5093                 break;
5095         case REQ_STATUS_REVERT:
5096                 if (!status_revert(status, line->type, status_has_none(view, line)))
5097                         return REQ_NONE;
5098                 break;
5100         case REQ_STATUS_MERGE:
5101                 if (!status || status->status != 'U') {
5102                         report("Merging only possible for files with unmerged status ('U').");
5103                         return REQ_NONE;
5104                 }
5105                 open_mergetool(status->new.name);
5106                 break;
5108         case REQ_EDIT:
5109                 if (!status)
5110                         return request;
5111                 if (status->status == 'D') {
5112                         report("File has been deleted.");
5113                         return REQ_NONE;
5114                 }
5116                 open_editor(status->status != '?', status->new.name);
5117                 break;
5119         case REQ_VIEW_BLAME:
5120                 if (status) {
5121                         string_copy(opt_file, status->new.name);
5122                         opt_ref[0] = 0;
5123                 }
5124                 return request;
5126         case REQ_ENTER:
5127                 /* After returning the status view has been split to
5128                  * show the stage view. No further reloading is
5129                  * necessary. */
5130                 status_enter(view, line);
5131                 return REQ_NONE;
5133         case REQ_REFRESH:
5134                 /* Simply reload the view. */
5135                 break;
5137         default:
5138                 return request;
5139         }
5141         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5143         return REQ_NONE;
5146 static void
5147 status_select(struct view *view, struct line *line)
5149         struct status *status = line->data;
5150         char file[SIZEOF_STR] = "all files";
5151         const char *text;
5152         const char *key;
5154         if (status && !string_format(file, "'%s'", status->new.name))
5155                 return;
5157         if (!status && line[1].type == LINE_STAT_NONE)
5158                 line++;
5160         switch (line->type) {
5161         case LINE_STAT_STAGED:
5162                 text = "Press %s to unstage %s for commit";
5163                 break;
5165         case LINE_STAT_UNSTAGED:
5166                 text = "Press %s to stage %s for commit";
5167                 break;
5169         case LINE_STAT_UNTRACKED:
5170                 text = "Press %s to stage %s for addition";
5171                 break;
5173         case LINE_STAT_HEAD:
5174         case LINE_STAT_NONE:
5175                 text = "Nothing to update";
5176                 break;
5178         default:
5179                 die("line type %d not handled in switch", line->type);
5180         }
5182         if (status && status->status == 'U') {
5183                 text = "Press %s to resolve conflict in %s";
5184                 key = get_key(REQ_STATUS_MERGE);
5186         } else {
5187                 key = get_key(REQ_STATUS_UPDATE);
5188         }
5190         string_format(view->ref, text, key, file);
5193 static bool
5194 status_grep(struct view *view, struct line *line)
5196         struct status *status = line->data;
5197         enum { S_STATUS, S_NAME, S_END } state;
5198         char buf[2] = "?";
5199         regmatch_t pmatch;
5201         if (!status)
5202                 return FALSE;
5204         for (state = S_STATUS; state < S_END; state++) {
5205                 const char *text;
5207                 switch (state) {
5208                 case S_NAME:    text = status->new.name;        break;
5209                 case S_STATUS:
5210                         buf[0] = status->status;
5211                         text = buf;
5212                         break;
5214                 default:
5215                         return FALSE;
5216                 }
5218                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5219                         return TRUE;
5220         }
5222         return FALSE;
5225 static struct view_ops status_ops = {
5226         "file",
5227         NULL,
5228         status_open,
5229         NULL,
5230         status_draw,
5231         status_request,
5232         status_grep,
5233         status_select,
5234 };
5237 static bool
5238 stage_diff_write(struct io *io, struct line *line, struct line *end)
5240         while (line < end) {
5241                 if (!io_write(io, line->data, strlen(line->data)) ||
5242                     !io_write(io, "\n", 1))
5243                         return FALSE;
5244                 line++;
5245                 if (line->type == LINE_DIFF_CHUNK ||
5246                     line->type == LINE_DIFF_HEADER)
5247                         break;
5248         }
5250         return TRUE;
5253 static struct line *
5254 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5256         for (; view->line < line; line--)
5257                 if (line->type == type)
5258                         return line;
5260         return NULL;
5263 static bool
5264 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5266         const char *apply_argv[SIZEOF_ARG] = {
5267                 "git", "apply", "--whitespace=nowarn", NULL
5268         };
5269         struct line *diff_hdr;
5270         struct io io = {};
5271         int argc = 3;
5273         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5274         if (!diff_hdr)
5275                 return FALSE;
5277         if (!revert)
5278                 apply_argv[argc++] = "--cached";
5279         if (revert || stage_line_type == LINE_STAT_STAGED)
5280                 apply_argv[argc++] = "-R";
5281         apply_argv[argc++] = "-";
5282         apply_argv[argc++] = NULL;
5283         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5284                 return FALSE;
5286         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5287             !stage_diff_write(&io, chunk, view->line + view->lines))
5288                 chunk = NULL;
5290         done_io(&io);
5291         run_io_bg(update_index_argv);
5293         return chunk ? TRUE : FALSE;
5296 static bool
5297 stage_update(struct view *view, struct line *line)
5299         struct line *chunk = NULL;
5301         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5302                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5304         if (chunk) {
5305                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5306                         report("Failed to apply chunk");
5307                         return FALSE;
5308                 }
5310         } else if (!stage_status.status) {
5311                 view = VIEW(REQ_VIEW_STATUS);
5313                 for (line = view->line; line < view->line + view->lines; line++)
5314                         if (line->type == stage_line_type)
5315                                 break;
5317                 if (!status_update_files(view, line + 1)) {
5318                         report("Failed to update files");
5319                         return FALSE;
5320                 }
5322         } else if (!status_update_file(&stage_status, stage_line_type)) {
5323                 report("Failed to update file");
5324                 return FALSE;
5325         }
5327         return TRUE;
5330 static bool
5331 stage_revert(struct view *view, struct line *line)
5333         struct line *chunk = NULL;
5335         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5336                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5338         if (chunk) {
5339                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5340                         return FALSE;
5342                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5343                         report("Failed to revert chunk");
5344                         return FALSE;
5345                 }
5346                 return TRUE;
5348         } else {
5349                 return status_revert(stage_status.status ? &stage_status : NULL,
5350                                      stage_line_type, FALSE);
5351         }
5355 static void
5356 stage_next(struct view *view, struct line *line)
5358         int i;
5360         if (!stage_chunks) {
5361                 static size_t alloc = 0;
5362                 int *tmp;
5364                 for (line = view->line; line < view->line + view->lines; line++) {
5365                         if (line->type != LINE_DIFF_CHUNK)
5366                                 continue;
5368                         tmp = realloc_items(stage_chunk, &alloc,
5369                                             stage_chunks, sizeof(*tmp));
5370                         if (!tmp) {
5371                                 report("Allocation failure");
5372                                 return;
5373                         }
5375                         stage_chunk = tmp;
5376                         stage_chunk[stage_chunks++] = line - view->line;
5377                 }
5378         }
5380         for (i = 0; i < stage_chunks; i++) {
5381                 if (stage_chunk[i] > view->lineno) {
5382                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5383                         report("Chunk %d of %d", i + 1, stage_chunks);
5384                         return;
5385                 }
5386         }
5388         report("No next chunk found");
5391 static enum request
5392 stage_request(struct view *view, enum request request, struct line *line)
5394         switch (request) {
5395         case REQ_STATUS_UPDATE:
5396                 if (!stage_update(view, line))
5397                         return REQ_NONE;
5398                 break;
5400         case REQ_STATUS_REVERT:
5401                 if (!stage_revert(view, line))
5402                         return REQ_NONE;
5403                 break;
5405         case REQ_STAGE_NEXT:
5406                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5407                         report("File is untracked; press %s to add",
5408                                get_key(REQ_STATUS_UPDATE));
5409                         return REQ_NONE;
5410                 }
5411                 stage_next(view, line);
5412                 return REQ_NONE;
5414         case REQ_EDIT:
5415                 if (!stage_status.new.name[0])
5416                         return request;
5417                 if (stage_status.status == 'D') {
5418                         report("File has been deleted.");
5419                         return REQ_NONE;
5420                 }
5422                 open_editor(stage_status.status != '?', stage_status.new.name);
5423                 break;
5425         case REQ_REFRESH:
5426                 /* Reload everything ... */
5427                 break;
5429         case REQ_VIEW_BLAME:
5430                 if (stage_status.new.name[0]) {
5431                         string_copy(opt_file, stage_status.new.name);
5432                         opt_ref[0] = 0;
5433                 }
5434                 return request;
5436         case REQ_ENTER:
5437                 return pager_request(view, request, line);
5439         default:
5440                 return request;
5441         }
5443         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5444         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5446         /* Check whether the staged entry still exists, and close the
5447          * stage view if it doesn't. */
5448         if (!status_exists(&stage_status, stage_line_type)) {
5449                 status_restore(VIEW(REQ_VIEW_STATUS));
5450                 return REQ_VIEW_CLOSE;
5451         }
5453         if (stage_line_type == LINE_STAT_UNTRACKED) {
5454                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5455                         report("Cannot display a directory");
5456                         return REQ_NONE;
5457                 }
5459                 if (!prepare_update_file(view, stage_status.new.name)) {
5460                         report("Failed to open file: %s", strerror(errno));
5461                         return REQ_NONE;
5462                 }
5463         }
5464         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5466         return REQ_NONE;
5469 static struct view_ops stage_ops = {
5470         "line",
5471         NULL,
5472         NULL,
5473         pager_read,
5474         pager_draw,
5475         stage_request,
5476         pager_grep,
5477         pager_select,
5478 };
5481 /*
5482  * Revision graph
5483  */
5485 struct commit {
5486         char id[SIZEOF_REV];            /* SHA1 ID. */
5487         char title[128];                /* First line of the commit message. */
5488         char author[75];                /* Author of the commit. */
5489         struct tm time;                 /* Date from the author ident. */
5490         struct ref **refs;              /* Repository references. */
5491         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5492         size_t graph_size;              /* The width of the graph array. */
5493         bool has_parents;               /* Rewritten --parents seen. */
5494 };
5496 /* Size of rev graph with no  "padding" columns */
5497 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5499 struct rev_graph {
5500         struct rev_graph *prev, *next, *parents;
5501         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5502         size_t size;
5503         struct commit *commit;
5504         size_t pos;
5505         unsigned int boundary:1;
5506 };
5508 /* Parents of the commit being visualized. */
5509 static struct rev_graph graph_parents[4];
5511 /* The current stack of revisions on the graph. */
5512 static struct rev_graph graph_stacks[4] = {
5513         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5514         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5515         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5516         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5517 };
5519 static inline bool
5520 graph_parent_is_merge(struct rev_graph *graph)
5522         return graph->parents->size > 1;
5525 static inline void
5526 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5528         struct commit *commit = graph->commit;
5530         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5531                 commit->graph[commit->graph_size++] = symbol;
5534 static void
5535 clear_rev_graph(struct rev_graph *graph)
5537         graph->boundary = 0;
5538         graph->size = graph->pos = 0;
5539         graph->commit = NULL;
5540         memset(graph->parents, 0, sizeof(*graph->parents));
5543 static void
5544 done_rev_graph(struct rev_graph *graph)
5546         if (graph_parent_is_merge(graph) &&
5547             graph->pos < graph->size - 1 &&
5548             graph->next->size == graph->size + graph->parents->size - 1) {
5549                 size_t i = graph->pos + graph->parents->size - 1;
5551                 graph->commit->graph_size = i * 2;
5552                 while (i < graph->next->size - 1) {
5553                         append_to_rev_graph(graph, ' ');
5554                         append_to_rev_graph(graph, '\\');
5555                         i++;
5556                 }
5557         }
5559         clear_rev_graph(graph);
5562 static void
5563 push_rev_graph(struct rev_graph *graph, const char *parent)
5565         int i;
5567         /* "Collapse" duplicate parents lines.
5568          *
5569          * FIXME: This needs to also update update the drawn graph but
5570          * for now it just serves as a method for pruning graph lines. */
5571         for (i = 0; i < graph->size; i++)
5572                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5573                         return;
5575         if (graph->size < SIZEOF_REVITEMS) {
5576                 string_copy_rev(graph->rev[graph->size++], parent);
5577         }
5580 static chtype
5581 get_rev_graph_symbol(struct rev_graph *graph)
5583         chtype symbol;
5585         if (graph->boundary)
5586                 symbol = REVGRAPH_BOUND;
5587         else if (graph->parents->size == 0)
5588                 symbol = REVGRAPH_INIT;
5589         else if (graph_parent_is_merge(graph))
5590                 symbol = REVGRAPH_MERGE;
5591         else if (graph->pos >= graph->size)
5592                 symbol = REVGRAPH_BRANCH;
5593         else
5594                 symbol = REVGRAPH_COMMIT;
5596         return symbol;
5599 static void
5600 draw_rev_graph(struct rev_graph *graph)
5602         struct rev_filler {
5603                 chtype separator, line;
5604         };
5605         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5606         static struct rev_filler fillers[] = {
5607                 { ' ',  '|' },
5608                 { '`',  '.' },
5609                 { '\'', ' ' },
5610                 { '/',  ' ' },
5611         };
5612         chtype symbol = get_rev_graph_symbol(graph);
5613         struct rev_filler *filler;
5614         size_t i;
5616         if (opt_line_graphics)
5617                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5619         filler = &fillers[DEFAULT];
5621         for (i = 0; i < graph->pos; i++) {
5622                 append_to_rev_graph(graph, filler->line);
5623                 if (graph_parent_is_merge(graph->prev) &&
5624                     graph->prev->pos == i)
5625                         filler = &fillers[RSHARP];
5627                 append_to_rev_graph(graph, filler->separator);
5628         }
5630         /* Place the symbol for this revision. */
5631         append_to_rev_graph(graph, symbol);
5633         if (graph->prev->size > graph->size)
5634                 filler = &fillers[RDIAG];
5635         else
5636                 filler = &fillers[DEFAULT];
5638         i++;
5640         for (; i < graph->size; i++) {
5641                 append_to_rev_graph(graph, filler->separator);
5642                 append_to_rev_graph(graph, filler->line);
5643                 if (graph_parent_is_merge(graph->prev) &&
5644                     i < graph->prev->pos + graph->parents->size)
5645                         filler = &fillers[RSHARP];
5646                 if (graph->prev->size > graph->size)
5647                         filler = &fillers[LDIAG];
5648         }
5650         if (graph->prev->size > graph->size) {
5651                 append_to_rev_graph(graph, filler->separator);
5652                 if (filler->line != ' ')
5653                         append_to_rev_graph(graph, filler->line);
5654         }
5657 /* Prepare the next rev graph */
5658 static void
5659 prepare_rev_graph(struct rev_graph *graph)
5661         size_t i;
5663         /* First, traverse all lines of revisions up to the active one. */
5664         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5665                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5666                         break;
5668                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5669         }
5671         /* Interleave the new revision parent(s). */
5672         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5673                 push_rev_graph(graph->next, graph->parents->rev[i]);
5675         /* Lastly, put any remaining revisions. */
5676         for (i = graph->pos + 1; i < graph->size; i++)
5677                 push_rev_graph(graph->next, graph->rev[i]);
5680 static void
5681 update_rev_graph(struct view *view, struct rev_graph *graph)
5683         /* If this is the finalizing update ... */
5684         if (graph->commit)
5685                 prepare_rev_graph(graph);
5687         /* Graph visualization needs a one rev look-ahead,
5688          * so the first update doesn't visualize anything. */
5689         if (!graph->prev->commit)
5690                 return;
5692         if (view->lines > 2)
5693                 view->line[view->lines - 3].dirty = 1;
5694         if (view->lines > 1)
5695                 view->line[view->lines - 2].dirty = 1;
5696         draw_rev_graph(graph->prev);
5697         done_rev_graph(graph->prev->prev);
5701 /*
5702  * Main view backend
5703  */
5705 static const char *main_argv[SIZEOF_ARG] = {
5706         "git", "log", "--no-color", "--pretty=raw", "--parents",
5707                       "--topo-order", "%(head)", NULL
5708 };
5710 static bool
5711 main_draw(struct view *view, struct line *line, unsigned int lineno)
5713         struct commit *commit = line->data;
5715         if (!*commit->author)
5716                 return FALSE;
5718         if (opt_date && draw_date(view, &commit->time))
5719                 return TRUE;
5721         if (opt_author && draw_author(view, commit->author))
5722                 return TRUE;
5724         if (opt_rev_graph && commit->graph_size &&
5725             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5726                 return TRUE;
5728         if (opt_show_refs && commit->refs) {
5729                 size_t i = 0;
5731                 do {
5732                         enum line_type type;
5734                         if (commit->refs[i]->head)
5735                                 type = LINE_MAIN_HEAD;
5736                         else if (commit->refs[i]->ltag)
5737                                 type = LINE_MAIN_LOCAL_TAG;
5738                         else if (commit->refs[i]->tag)
5739                                 type = LINE_MAIN_TAG;
5740                         else if (commit->refs[i]->tracked)
5741                                 type = LINE_MAIN_TRACKED;
5742                         else if (commit->refs[i]->remote)
5743                                 type = LINE_MAIN_REMOTE;
5744                         else
5745                                 type = LINE_MAIN_REF;
5747                         if (draw_text(view, type, "[", TRUE) ||
5748                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5749                             draw_text(view, type, "]", TRUE))
5750                                 return TRUE;
5752                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5753                                 return TRUE;
5754                 } while (commit->refs[i++]->next);
5755         }
5757         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5758         return TRUE;
5761 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5762 static bool
5763 main_read(struct view *view, char *line)
5765         static struct rev_graph *graph = graph_stacks;
5766         enum line_type type;
5767         struct commit *commit;
5769         if (!line) {
5770                 int i;
5772                 if (!view->lines && !view->parent)
5773                         die("No revisions match the given arguments.");
5774                 if (view->lines > 0) {
5775                         commit = view->line[view->lines - 1].data;
5776                         view->line[view->lines - 1].dirty = 1;
5777                         if (!*commit->author) {
5778                                 view->lines--;
5779                                 free(commit);
5780                                 graph->commit = NULL;
5781                         }
5782                 }
5783                 update_rev_graph(view, graph);
5785                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5786                         clear_rev_graph(&graph_stacks[i]);
5787                 return TRUE;
5788         }
5790         type = get_line_type(line);
5791         if (type == LINE_COMMIT) {
5792                 commit = calloc(1, sizeof(struct commit));
5793                 if (!commit)
5794                         return FALSE;
5796                 line += STRING_SIZE("commit ");
5797                 if (*line == '-') {
5798                         graph->boundary = 1;
5799                         line++;
5800                 }
5802                 string_copy_rev(commit->id, line);
5803                 commit->refs = get_refs(commit->id);
5804                 graph->commit = commit;
5805                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5807                 while ((line = strchr(line, ' '))) {
5808                         line++;
5809                         push_rev_graph(graph->parents, line);
5810                         commit->has_parents = TRUE;
5811                 }
5812                 return TRUE;
5813         }
5815         if (!view->lines)
5816                 return TRUE;
5817         commit = view->line[view->lines - 1].data;
5819         switch (type) {
5820         case LINE_PARENT:
5821                 if (commit->has_parents)
5822                         break;
5823                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5824                 break;
5826         case LINE_AUTHOR:
5827                 parse_author_line(line + STRING_SIZE("author "),
5828                                   commit->author, sizeof(commit->author),
5829                                   &commit->time);
5830                 update_rev_graph(view, graph);
5831                 graph = graph->next;
5832                 break;
5834         default:
5835                 /* Fill in the commit title if it has not already been set. */
5836                 if (commit->title[0])
5837                         break;
5839                 /* Require titles to start with a non-space character at the
5840                  * offset used by git log. */
5841                 if (strncmp(line, "    ", 4))
5842                         break;
5843                 line += 4;
5844                 /* Well, if the title starts with a whitespace character,
5845                  * try to be forgiving.  Otherwise we end up with no title. */
5846                 while (isspace(*line))
5847                         line++;
5848                 if (*line == '\0')
5849                         break;
5850                 /* FIXME: More graceful handling of titles; append "..." to
5851                  * shortened titles, etc. */
5853                 string_ncopy(commit->title, line, strlen(line));
5854                 view->line[view->lines - 1].dirty = 1;
5855         }
5857         return TRUE;
5860 static enum request
5861 main_request(struct view *view, enum request request, struct line *line)
5863         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5865         switch (request) {
5866         case REQ_ENTER:
5867                 open_view(view, REQ_VIEW_DIFF, flags);
5868                 break;
5869         case REQ_REFRESH:
5870                 load_refs();
5871                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5872                 break;
5873         default:
5874                 return request;
5875         }
5877         return REQ_NONE;
5880 static bool
5881 grep_refs(struct ref **refs, regex_t *regex)
5883         regmatch_t pmatch;
5884         size_t i = 0;
5886         if (!refs)
5887                 return FALSE;
5888         do {
5889                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5890                         return TRUE;
5891         } while (refs[i++]->next);
5893         return FALSE;
5896 static bool
5897 main_grep(struct view *view, struct line *line)
5899         struct commit *commit = line->data;
5900         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5901         char buf[DATE_COLS + 1];
5902         regmatch_t pmatch;
5904         for (state = S_TITLE; state < S_END; state++) {
5905                 char *text;
5907                 switch (state) {
5908                 case S_TITLE:   text = commit->title;   break;
5909                 case S_AUTHOR:
5910                         if (!opt_author)
5911                                 continue;
5912                         text = commit->author;
5913                         break;
5914                 case S_DATE:
5915                         if (!opt_date)
5916                                 continue;
5917                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5918                                 continue;
5919                         text = buf;
5920                         break;
5921                 case S_REFS:
5922                         if (!opt_show_refs)
5923                                 continue;
5924                         if (grep_refs(commit->refs, view->regex) == TRUE)
5925                                 return TRUE;
5926                         continue;
5927                 default:
5928                         return FALSE;
5929                 }
5931                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5932                         return TRUE;
5933         }
5935         return FALSE;
5938 static void
5939 main_select(struct view *view, struct line *line)
5941         struct commit *commit = line->data;
5943         string_copy_rev(view->ref, commit->id);
5944         string_copy_rev(ref_commit, view->ref);
5947 static struct view_ops main_ops = {
5948         "commit",
5949         main_argv,
5950         NULL,
5951         main_read,
5952         main_draw,
5953         main_request,
5954         main_grep,
5955         main_select,
5956 };
5959 /*
5960  * Unicode / UTF-8 handling
5961  *
5962  * NOTE: Much of the following code for dealing with unicode is derived from
5963  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5964  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5965  */
5967 /* I've (over)annotated a lot of code snippets because I am not entirely
5968  * confident that the approach taken by this small UTF-8 interface is correct.
5969  * --jonas */
5971 static inline int
5972 unicode_width(unsigned long c)
5974         if (c >= 0x1100 &&
5975            (c <= 0x115f                         /* Hangul Jamo */
5976             || c == 0x2329
5977             || c == 0x232a
5978             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5979                                                 /* CJK ... Yi */
5980             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5981             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5982             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5983             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5984             || (c >= 0xffe0  && c <= 0xffe6)
5985             || (c >= 0x20000 && c <= 0x2fffd)
5986             || (c >= 0x30000 && c <= 0x3fffd)))
5987                 return 2;
5989         if (c == '\t')
5990                 return opt_tab_size;
5992         return 1;
5995 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5996  * Illegal bytes are set one. */
5997 static const unsigned char utf8_bytes[256] = {
5998         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,
5999         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,
6000         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,
6001         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,
6002         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,
6003         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,
6004         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,
6005         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,
6006 };
6008 /* Decode UTF-8 multi-byte representation into a unicode character. */
6009 static inline unsigned long
6010 utf8_to_unicode(const char *string, size_t length)
6012         unsigned long unicode;
6014         switch (length) {
6015         case 1:
6016                 unicode  =   string[0];
6017                 break;
6018         case 2:
6019                 unicode  =  (string[0] & 0x1f) << 6;
6020                 unicode +=  (string[1] & 0x3f);
6021                 break;
6022         case 3:
6023                 unicode  =  (string[0] & 0x0f) << 12;
6024                 unicode += ((string[1] & 0x3f) << 6);
6025                 unicode +=  (string[2] & 0x3f);
6026                 break;
6027         case 4:
6028                 unicode  =  (string[0] & 0x0f) << 18;
6029                 unicode += ((string[1] & 0x3f) << 12);
6030                 unicode += ((string[2] & 0x3f) << 6);
6031                 unicode +=  (string[3] & 0x3f);
6032                 break;
6033         case 5:
6034                 unicode  =  (string[0] & 0x0f) << 24;
6035                 unicode += ((string[1] & 0x3f) << 18);
6036                 unicode += ((string[2] & 0x3f) << 12);
6037                 unicode += ((string[3] & 0x3f) << 6);
6038                 unicode +=  (string[4] & 0x3f);
6039                 break;
6040         case 6:
6041                 unicode  =  (string[0] & 0x01) << 30;
6042                 unicode += ((string[1] & 0x3f) << 24);
6043                 unicode += ((string[2] & 0x3f) << 18);
6044                 unicode += ((string[3] & 0x3f) << 12);
6045                 unicode += ((string[4] & 0x3f) << 6);
6046                 unicode +=  (string[5] & 0x3f);
6047                 break;
6048         default:
6049                 die("Invalid unicode length");
6050         }
6052         /* Invalid characters could return the special 0xfffd value but NUL
6053          * should be just as good. */
6054         return unicode > 0xffff ? 0 : unicode;
6057 /* Calculates how much of string can be shown within the given maximum width
6058  * and sets trimmed parameter to non-zero value if all of string could not be
6059  * shown. If the reserve flag is TRUE, it will reserve at least one
6060  * trailing character, which can be useful when drawing a delimiter.
6061  *
6062  * Returns the number of bytes to output from string to satisfy max_width. */
6063 static size_t
6064 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6066         const char *start = string;
6067         const char *end = strchr(string, '\0');
6068         unsigned char last_bytes = 0;
6069         size_t last_ucwidth = 0;
6071         *width = 0;
6072         *trimmed = 0;
6074         while (string < end) {
6075                 int c = *(unsigned char *) string;
6076                 unsigned char bytes = utf8_bytes[c];
6077                 size_t ucwidth;
6078                 unsigned long unicode;
6080                 if (string + bytes > end)
6081                         break;
6083                 /* Change representation to figure out whether
6084                  * it is a single- or double-width character. */
6086                 unicode = utf8_to_unicode(string, bytes);
6087                 /* FIXME: Graceful handling of invalid unicode character. */
6088                 if (!unicode)
6089                         break;
6091                 ucwidth = unicode_width(unicode);
6092                 *width  += ucwidth;
6093                 if (*width > max_width) {
6094                         *trimmed = 1;
6095                         *width -= ucwidth;
6096                         if (reserve && *width == max_width) {
6097                                 string -= last_bytes;
6098                                 *width -= last_ucwidth;
6099                         }
6100                         break;
6101                 }
6103                 string  += bytes;
6104                 last_bytes = bytes;
6105                 last_ucwidth = ucwidth;
6106         }
6108         return string - start;
6112 /*
6113  * Status management
6114  */
6116 /* Whether or not the curses interface has been initialized. */
6117 static bool cursed = FALSE;
6119 /* The status window is used for polling keystrokes. */
6120 static WINDOW *status_win;
6122 static bool status_empty = FALSE;
6124 /* Update status and title window. */
6125 static void
6126 report(const char *msg, ...)
6128         struct view *view = display[current_view];
6130         if (input_mode)
6131                 return;
6133         if (!view) {
6134                 char buf[SIZEOF_STR];
6135                 va_list args;
6137                 va_start(args, msg);
6138                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6139                         buf[sizeof(buf) - 1] = 0;
6140                         buf[sizeof(buf) - 2] = '.';
6141                         buf[sizeof(buf) - 3] = '.';
6142                         buf[sizeof(buf) - 4] = '.';
6143                 }
6144                 va_end(args);
6145                 die("%s", buf);
6146         }
6148         if (!status_empty || *msg) {
6149                 va_list args;
6151                 va_start(args, msg);
6153                 wmove(status_win, 0, 0);
6154                 if (*msg) {
6155                         vwprintw(status_win, msg, args);
6156                         status_empty = FALSE;
6157                 } else {
6158                         status_empty = TRUE;
6159                 }
6160                 wclrtoeol(status_win);
6161                 wrefresh(status_win);
6163                 va_end(args);
6164         }
6166         update_view_title(view);
6167         update_display_cursor(view);
6170 /* Controls when nodelay should be in effect when polling user input. */
6171 static void
6172 set_nonblocking_input(bool loading)
6174         static unsigned int loading_views;
6176         if ((loading == FALSE && loading_views-- == 1) ||
6177             (loading == TRUE  && loading_views++ == 0))
6178                 nodelay(status_win, loading);
6181 static void
6182 init_display(void)
6184         int x, y;
6186         /* Initialize the curses library */
6187         if (isatty(STDIN_FILENO)) {
6188                 cursed = !!initscr();
6189                 opt_tty = stdin;
6190         } else {
6191                 /* Leave stdin and stdout alone when acting as a pager. */
6192                 opt_tty = fopen("/dev/tty", "r+");
6193                 if (!opt_tty)
6194                         die("Failed to open /dev/tty");
6195                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6196         }
6198         if (!cursed)
6199                 die("Failed to initialize curses");
6201         nonl();         /* Tell curses not to do NL->CR/NL on output */
6202         cbreak();       /* Take input chars one at a time, no wait for \n */
6203         noecho();       /* Don't echo input */
6204         leaveok(stdscr, TRUE);
6206         if (has_colors())
6207                 init_colors();
6209         getmaxyx(stdscr, y, x);
6210         status_win = newwin(1, 0, y - 1, 0);
6211         if (!status_win)
6212                 die("Failed to create status window");
6214         /* Enable keyboard mapping */
6215         keypad(status_win, TRUE);
6216         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6218         TABSIZE = opt_tab_size;
6219         if (opt_line_graphics) {
6220                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6221         }
6224 static int
6225 get_input(bool prompting)
6227         struct view *view;
6228         int i, key;
6230         if (prompting)
6231                 input_mode = TRUE;
6233         while (true) {
6234                 foreach_view (view, i)
6235                         update_view(view);
6237                 /* Refresh, accept single keystroke of input */
6238                 key = wgetch(status_win);
6240                 /* wgetch() with nodelay() enabled returns ERR when
6241                  * there's no input. */
6242                 if (key == ERR) {
6243                         doupdate();
6245                 } else if (key == KEY_RESIZE) {
6246                         int height, width;
6248                         getmaxyx(stdscr, height, width);
6250                         /* Resize the status view first so the cursor is
6251                          * placed properly. */
6252                         wresize(status_win, 1, width);
6253                         mvwin(status_win, height - 1, 0);
6254                         wrefresh(status_win);
6255                         resize_display();
6256                         redraw_display(TRUE);
6258                 } else {
6259                         input_mode = FALSE;
6260                         return key;
6261                 }
6262         }
6265 static char *
6266 prompt_input(const char *prompt, input_handler handler, void *data)
6268         enum input_status status = INPUT_OK;
6269         static char buf[SIZEOF_STR];
6270         size_t pos = 0;
6272         buf[pos] = 0;
6274         while (status == INPUT_OK || status == INPUT_SKIP) {
6275                 int key;
6277                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6278                 wclrtoeol(status_win);
6280                 key = get_input(TRUE);
6281                 switch (key) {
6282                 case KEY_RETURN:
6283                 case KEY_ENTER:
6284                 case '\n':
6285                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6286                         break;
6288                 case KEY_BACKSPACE:
6289                         if (pos > 0)
6290                                 buf[--pos] = 0;
6291                         else
6292                                 status = INPUT_CANCEL;
6293                         break;
6295                 case KEY_ESC:
6296                         status = INPUT_CANCEL;
6297                         break;
6299                 default:
6300                         if (pos >= sizeof(buf)) {
6301                                 report("Input string too long");
6302                                 return NULL;
6303                         }
6305                         status = handler(data, buf, key);
6306                         if (status == INPUT_OK)
6307                                 buf[pos++] = (char) key;
6308                 }
6309         }
6311         /* Clear the status window */
6312         status_empty = FALSE;
6313         report("");
6315         if (status == INPUT_CANCEL)
6316                 return NULL;
6318         buf[pos++] = 0;
6320         return buf;
6323 static enum input_status
6324 prompt_yesno_handler(void *data, char *buf, int c)
6326         if (c == 'y' || c == 'Y')
6327                 return INPUT_STOP;
6328         if (c == 'n' || c == 'N')
6329                 return INPUT_CANCEL;
6330         return INPUT_SKIP;
6333 static bool
6334 prompt_yesno(const char *prompt)
6336         char prompt2[SIZEOF_STR];
6338         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6339                 return FALSE;
6341         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6344 static enum input_status
6345 read_prompt_handler(void *data, char *buf, int c)
6347         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6350 static char *
6351 read_prompt(const char *prompt)
6353         return prompt_input(prompt, read_prompt_handler, NULL);
6356 /*
6357  * Repository properties
6358  */
6360 static int
6361 git_properties(const char **argv, const char *separators,
6362                int (*read_property)(char *, size_t, char *, size_t))
6364         struct io io = {};
6366         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6367                 return read_properties(&io, separators, read_property);
6368         return ERR;
6371 static struct ref *refs = NULL;
6372 static size_t refs_alloc = 0;
6373 static size_t refs_size = 0;
6375 /* Id <-> ref store */
6376 static struct ref ***id_refs = NULL;
6377 static size_t id_refs_alloc = 0;
6378 static size_t id_refs_size = 0;
6380 static int
6381 compare_refs(const void *ref1_, const void *ref2_)
6383         const struct ref *ref1 = *(const struct ref **)ref1_;
6384         const struct ref *ref2 = *(const struct ref **)ref2_;
6386         if (ref1->tag != ref2->tag)
6387                 return ref2->tag - ref1->tag;
6388         if (ref1->ltag != ref2->ltag)
6389                 return ref2->ltag - ref2->ltag;
6390         if (ref1->head != ref2->head)
6391                 return ref2->head - ref1->head;
6392         if (ref1->tracked != ref2->tracked)
6393                 return ref2->tracked - ref1->tracked;
6394         if (ref1->remote != ref2->remote)
6395                 return ref2->remote - ref1->remote;
6396         return strcmp(ref1->name, ref2->name);
6399 static struct ref **
6400 get_refs(const char *id)
6402         struct ref ***tmp_id_refs;
6403         struct ref **ref_list = NULL;
6404         size_t ref_list_alloc = 0;
6405         size_t ref_list_size = 0;
6406         size_t i;
6408         for (i = 0; i < id_refs_size; i++)
6409                 if (!strcmp(id, id_refs[i][0]->id))
6410                         return id_refs[i];
6412         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6413                                     sizeof(*id_refs));
6414         if (!tmp_id_refs)
6415                 return NULL;
6417         id_refs = tmp_id_refs;
6419         for (i = 0; i < refs_size; i++) {
6420                 struct ref **tmp;
6422                 if (strcmp(id, refs[i].id))
6423                         continue;
6425                 tmp = realloc_items(ref_list, &ref_list_alloc,
6426                                     ref_list_size + 1, sizeof(*ref_list));
6427                 if (!tmp) {
6428                         if (ref_list)
6429                                 free(ref_list);
6430                         return NULL;
6431                 }
6433                 ref_list = tmp;
6434                 ref_list[ref_list_size] = &refs[i];
6435                 /* XXX: The properties of the commit chains ensures that we can
6436                  * safely modify the shared ref. The repo references will
6437                  * always be similar for the same id. */
6438                 ref_list[ref_list_size]->next = 1;
6440                 ref_list_size++;
6441         }
6443         if (ref_list) {
6444                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6445                 ref_list[ref_list_size - 1]->next = 0;
6446                 id_refs[id_refs_size++] = ref_list;
6447         }
6449         return ref_list;
6452 static int
6453 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6455         struct ref *ref;
6456         bool tag = FALSE;
6457         bool ltag = FALSE;
6458         bool remote = FALSE;
6459         bool tracked = FALSE;
6460         bool check_replace = FALSE;
6461         bool head = FALSE;
6463         if (!prefixcmp(name, "refs/tags/")) {
6464                 if (!suffixcmp(name, namelen, "^{}")) {
6465                         namelen -= 3;
6466                         name[namelen] = 0;
6467                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6468                                 check_replace = TRUE;
6469                 } else {
6470                         ltag = TRUE;
6471                 }
6473                 tag = TRUE;
6474                 namelen -= STRING_SIZE("refs/tags/");
6475                 name    += STRING_SIZE("refs/tags/");
6477         } else if (!prefixcmp(name, "refs/remotes/")) {
6478                 remote = TRUE;
6479                 namelen -= STRING_SIZE("refs/remotes/");
6480                 name    += STRING_SIZE("refs/remotes/");
6481                 tracked  = !strcmp(opt_remote, name);
6483         } else if (!prefixcmp(name, "refs/heads/")) {
6484                 namelen -= STRING_SIZE("refs/heads/");
6485                 name    += STRING_SIZE("refs/heads/");
6486                 head     = !strncmp(opt_head, name, namelen);
6488         } else if (!strcmp(name, "HEAD")) {
6489                 string_ncopy(opt_head_rev, id, idlen);
6490                 return OK;
6491         }
6493         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6494                 /* it's an annotated tag, replace the previous sha1 with the
6495                  * resolved commit id; relies on the fact git-ls-remote lists
6496                  * the commit id of an annotated tag right before the commit id
6497                  * it points to. */
6498                 refs[refs_size - 1].ltag = ltag;
6499                 string_copy_rev(refs[refs_size - 1].id, id);
6501                 return OK;
6502         }
6503         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6504         if (!refs)
6505                 return ERR;
6507         ref = &refs[refs_size++];
6508         ref->name = malloc(namelen + 1);
6509         if (!ref->name)
6510                 return ERR;
6512         strncpy(ref->name, name, namelen);
6513         ref->name[namelen] = 0;
6514         ref->head = head;
6515         ref->tag = tag;
6516         ref->ltag = ltag;
6517         ref->remote = remote;
6518         ref->tracked = tracked;
6519         string_copy_rev(ref->id, id);
6521         return OK;
6524 static int
6525 load_refs(void)
6527         static const char *ls_remote_argv[SIZEOF_ARG] = {
6528                 "git", "ls-remote", ".", NULL
6529         };
6530         static bool init = FALSE;
6532         if (!init) {
6533                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6534                 init = TRUE;
6535         }
6537         if (!*opt_git_dir)
6538                 return OK;
6540         while (refs_size > 0)
6541                 free(refs[--refs_size].name);
6542         while (id_refs_size > 0)
6543                 free(id_refs[--id_refs_size]);
6545         return git_properties(ls_remote_argv, "\t", read_ref);
6548 static int
6549 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6551         if (!strcmp(name, "i18n.commitencoding"))
6552                 string_ncopy(opt_encoding, value, valuelen);
6554         if (!strcmp(name, "core.editor"))
6555                 string_ncopy(opt_editor, value, valuelen);
6557         /* branch.<head>.remote */
6558         if (*opt_head &&
6559             !strncmp(name, "branch.", 7) &&
6560             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6561             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6562                 string_ncopy(opt_remote, value, valuelen);
6564         if (*opt_head && *opt_remote &&
6565             !strncmp(name, "branch.", 7) &&
6566             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6567             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6568                 size_t from = strlen(opt_remote);
6570                 if (!prefixcmp(value, "refs/heads/")) {
6571                         value += STRING_SIZE("refs/heads/");
6572                         valuelen -= STRING_SIZE("refs/heads/");
6573                 }
6575                 if (!string_format_from(opt_remote, &from, "/%s", value))
6576                         opt_remote[0] = 0;
6577         }
6579         return OK;
6582 static int
6583 load_git_config(void)
6585         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6587         return git_properties(config_list_argv, "=", read_repo_config_option);
6590 static int
6591 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6593         if (!opt_git_dir[0]) {
6594                 string_ncopy(opt_git_dir, name, namelen);
6596         } else if (opt_is_inside_work_tree == -1) {
6597                 /* This can be 3 different values depending on the
6598                  * version of git being used. If git-rev-parse does not
6599                  * understand --is-inside-work-tree it will simply echo
6600                  * the option else either "true" or "false" is printed.
6601                  * Default to true for the unknown case. */
6602                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6604         } else if (*name == '.') {
6605                 string_ncopy(opt_cdup, name, namelen);
6607         } else {
6608                 string_ncopy(opt_prefix, name, namelen);
6609         }
6611         return OK;
6614 static int
6615 load_repo_info(void)
6617         const char *head_argv[] = {
6618                 "git", "symbolic-ref", "HEAD", NULL
6619         };
6620         const char *rev_parse_argv[] = {
6621                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6622                         "--show-cdup", "--show-prefix", NULL
6623         };
6625         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6626                 chomp_string(opt_head);
6627                 if (!prefixcmp(opt_head, "refs/heads/")) {
6628                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6630                         memmove(opt_head, offset, strlen(offset) + 1);
6631                 }
6632         }
6634         return git_properties(rev_parse_argv, "=", read_repo_info);
6637 static int
6638 read_properties(struct io *io, const char *separators,
6639                 int (*read_property)(char *, size_t, char *, size_t))
6641         char *name;
6642         int state = OK;
6644         if (!start_io(io))
6645                 return ERR;
6647         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6648                 char *value;
6649                 size_t namelen;
6650                 size_t valuelen;
6652                 name = chomp_string(name);
6653                 namelen = strcspn(name, separators);
6655                 if (name[namelen]) {
6656                         name[namelen] = 0;
6657                         value = chomp_string(name + namelen + 1);
6658                         valuelen = strlen(value);
6660                 } else {
6661                         value = "";
6662                         valuelen = 0;
6663                 }
6665                 state = read_property(name, namelen, value, valuelen);
6666         }
6668         if (state != ERR && io_error(io))
6669                 state = ERR;
6670         done_io(io);
6672         return state;
6676 /*
6677  * Main
6678  */
6680 static void __NORETURN
6681 quit(int sig)
6683         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6684         if (cursed)
6685                 endwin();
6686         exit(0);
6689 static void __NORETURN
6690 die(const char *err, ...)
6692         va_list args;
6694         endwin();
6696         va_start(args, err);
6697         fputs("tig: ", stderr);
6698         vfprintf(stderr, err, args);
6699         fputs("\n", stderr);
6700         va_end(args);
6702         exit(1);
6705 static void
6706 warn(const char *msg, ...)
6708         va_list args;
6710         va_start(args, msg);
6711         fputs("tig warning: ", stderr);
6712         vfprintf(stderr, msg, args);
6713         fputs("\n", stderr);
6714         va_end(args);
6717 int
6718 main(int argc, const char *argv[])
6720         const char **run_argv = NULL;
6721         struct view *view;
6722         enum request request;
6723         size_t i;
6725         signal(SIGINT, quit);
6727         if (setlocale(LC_ALL, "")) {
6728                 char *codeset = nl_langinfo(CODESET);
6730                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6731         }
6733         if (load_repo_info() == ERR)
6734                 die("Failed to load repo info.");
6736         if (load_options() == ERR)
6737                 die("Failed to load user config.");
6739         if (load_git_config() == ERR)
6740                 die("Failed to load repo config.");
6742         request = parse_options(argc, argv, &run_argv);
6743         if (request == REQ_NONE)
6744                 return 0;
6746         /* Require a git repository unless when running in pager mode. */
6747         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6748                 die("Not a git repository");
6750         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6751                 opt_utf8 = FALSE;
6753         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6754                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6755                 if (opt_iconv == ICONV_NONE)
6756                         die("Failed to initialize character set conversion");
6757         }
6759         if (load_refs() == ERR)
6760                 die("Failed to load refs.");
6762         foreach_view (view, i)
6763                 argv_from_env(view->ops->argv, view->cmd_env);
6765         init_display();
6767         if (request == REQ_VIEW_PAGER || run_argv) {
6768                 if (request == REQ_VIEW_PAGER)
6769                         io_open(&VIEW(request)->io, "");
6770                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6771                         die("Failed to format arguments");
6772                 open_view(NULL, request, OPEN_PREPARED);
6773                 request = REQ_NONE;
6774         }
6776         while (view_driver(display[current_view], request)) {
6777                 int key = get_input(FALSE);
6779                 view = display[current_view];
6780                 request = get_keybinding(view->keymap, key);
6782                 /* Some low-level request handling. This keeps access to
6783                  * status_win restricted. */
6784                 switch (request) {
6785                 case REQ_PROMPT:
6786                 {
6787                         char *cmd = read_prompt(":");
6789                         if (cmd) {
6790                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6791                                 const char *argv[SIZEOF_ARG] = { "git" };
6792                                 int argc = 1;
6794                                 /* When running random commands, initially show the
6795                                  * command in the title. However, it maybe later be
6796                                  * overwritten if a commit line is selected. */
6797                                 string_ncopy(next->ref, cmd, strlen(cmd));
6799                                 if (!argv_from_string(argv, &argc, cmd)) {
6800                                         report("Too many arguments");
6801                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6802                                         report("Failed to format command");
6803                                 } else {
6804                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6805                                 }
6806                         }
6808                         request = REQ_NONE;
6809                         break;
6810                 }
6811                 case REQ_SEARCH:
6812                 case REQ_SEARCH_BACK:
6813                 {
6814                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6815                         char *search = read_prompt(prompt);
6817                         if (search)
6818                                 string_ncopy(opt_search, search, strlen(search));
6819                         else
6820                                 request = REQ_NONE;
6821                         break;
6822                 }
6823                 default:
6824                         break;
6825                 }
6826         }
6828         quit(0);
6830         return 0;