Code

Fix tokenizing when parsing ~/.tigrc
[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_view_line(struct view *view, unsigned int lineno)
2013         struct line *line;
2014         bool selected = (view->offset + lineno == view->lineno);
2016         assert(view_is_displayed(view));
2018         if (view->offset + lineno >= view->lines)
2019                 return FALSE;
2021         line = &view->line[view->offset + lineno];
2023         wmove(view->win, lineno, 0);
2024         if (line->cleareol)
2025                 wclrtoeol(view->win);
2026         view->col = 0;
2027         view->curline = line;
2028         view->curtype = LINE_NONE;
2029         line->selected = FALSE;
2030         line->dirty = line->cleareol = 0;
2032         if (selected) {
2033                 set_view_attr(view, LINE_CURSOR);
2034                 line->selected = TRUE;
2035                 view->ops->select(view, line);
2036         }
2038         return view->ops->draw(view, line, lineno);
2041 static void
2042 redraw_view_dirty(struct view *view)
2044         bool dirty = FALSE;
2045         int lineno;
2047         for (lineno = 0; lineno < view->height; lineno++) {
2048                 if (view->offset + lineno >= view->lines)
2049                         break;
2050                 if (!view->line[view->offset + lineno].dirty)
2051                         continue;
2052                 dirty = TRUE;
2053                 if (!draw_view_line(view, lineno))
2054                         break;
2055         }
2057         if (!dirty)
2058                 return;
2059         if (input_mode)
2060                 wnoutrefresh(view->win);
2061         else
2062                 wrefresh(view->win);
2065 static void
2066 redraw_view_from(struct view *view, int lineno)
2068         assert(0 <= lineno && lineno < view->height);
2070         for (; lineno < view->height; lineno++) {
2071                 if (!draw_view_line(view, lineno))
2072                         break;
2073         }
2075         if (input_mode)
2076                 wnoutrefresh(view->win);
2077         else
2078                 wrefresh(view->win);
2081 static void
2082 redraw_view(struct view *view)
2084         werase(view->win);
2085         redraw_view_from(view, 0);
2089 static void
2090 update_display_cursor(struct view *view)
2092         /* Move the cursor to the right-most column of the cursor line.
2093          *
2094          * XXX: This could turn out to be a bit expensive, but it ensures that
2095          * the cursor does not jump around. */
2096         if (view->lines) {
2097                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2098                 wrefresh(view->win);
2099         }
2102 static void
2103 update_view_title(struct view *view)
2105         char buf[SIZEOF_STR];
2106         char state[SIZEOF_STR];
2107         size_t bufpos = 0, statelen = 0;
2109         assert(view_is_displayed(view));
2111         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2112                 unsigned int view_lines = view->offset + view->height;
2113                 unsigned int lines = view->lines
2114                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2115                                    : 0;
2117                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2118                                    view->ops->type,
2119                                    view->lineno + 1,
2120                                    view->lines,
2121                                    lines);
2123         }
2125         if (view->pipe) {
2126                 time_t secs = time(NULL) - view->start_time;
2128                 /* Three git seconds are a long time ... */
2129                 if (secs > 2)
2130                         string_format_from(state, &statelen, " loading %lds", secs);
2131         }
2133         string_format_from(buf, &bufpos, "[%s]", view->name);
2134         if (*view->ref && bufpos < view->width) {
2135                 size_t refsize = strlen(view->ref);
2136                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2138                 if (minsize < view->width)
2139                         refsize = view->width - minsize + 7;
2140                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2141         }
2143         if (statelen && bufpos < view->width) {
2144                 string_format_from(buf, &bufpos, "%s", state);
2145         }
2147         if (view == display[current_view])
2148                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2149         else
2150                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2152         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2153         wclrtoeol(view->title);
2154         wmove(view->title, 0, view->width - 1);
2156         if (input_mode)
2157                 wnoutrefresh(view->title);
2158         else
2159                 wrefresh(view->title);
2162 static void
2163 resize_display(void)
2165         int offset, i;
2166         struct view *base = display[0];
2167         struct view *view = display[1] ? display[1] : display[0];
2169         /* Setup window dimensions */
2171         getmaxyx(stdscr, base->height, base->width);
2173         /* Make room for the status window. */
2174         base->height -= 1;
2176         if (view != base) {
2177                 /* Horizontal split. */
2178                 view->width   = base->width;
2179                 view->height  = SCALE_SPLIT_VIEW(base->height);
2180                 base->height -= view->height;
2182                 /* Make room for the title bar. */
2183                 view->height -= 1;
2184         }
2186         /* Make room for the title bar. */
2187         base->height -= 1;
2189         offset = 0;
2191         foreach_displayed_view (view, i) {
2192                 if (!view->win) {
2193                         view->win = newwin(view->height, 0, offset, 0);
2194                         if (!view->win)
2195                                 die("Failed to create %s view", view->name);
2197                         scrollok(view->win, FALSE);
2199                         view->title = newwin(1, 0, offset + view->height, 0);
2200                         if (!view->title)
2201                                 die("Failed to create title window");
2203                 } else {
2204                         wresize(view->win, view->height, view->width);
2205                         mvwin(view->win,   offset, 0);
2206                         mvwin(view->title, offset + view->height, 0);
2207                 }
2209                 offset += view->height + 1;
2210         }
2213 static void
2214 redraw_display(bool clear)
2216         struct view *view;
2217         int i;
2219         foreach_displayed_view (view, i) {
2220                 if (clear)
2221                         wclear(view->win);
2222                 redraw_view(view);
2223                 update_view_title(view);
2224         }
2226         if (display[current_view])
2227                 update_display_cursor(display[current_view]);
2230 static void
2231 toggle_view_option(bool *option, const char *help)
2233         *option = !*option;
2234         redraw_display(FALSE);
2235         report("%sabling %s", *option ? "En" : "Dis", help);
2238 /*
2239  * Navigation
2240  */
2242 /* Scrolling backend */
2243 static void
2244 do_scroll_view(struct view *view, int lines)
2246         bool redraw_current_line = FALSE;
2248         /* The rendering expects the new offset. */
2249         view->offset += lines;
2251         assert(0 <= view->offset && view->offset < view->lines);
2252         assert(lines);
2254         /* Move current line into the view. */
2255         if (view->lineno < view->offset) {
2256                 view->lineno = view->offset;
2257                 redraw_current_line = TRUE;
2258         } else if (view->lineno >= view->offset + view->height) {
2259                 view->lineno = view->offset + view->height - 1;
2260                 redraw_current_line = TRUE;
2261         }
2263         assert(view->offset <= view->lineno && view->lineno < view->lines);
2265         /* Redraw the whole screen if scrolling is pointless. */
2266         if (view->height < ABS(lines)) {
2267                 redraw_view(view);
2269         } else {
2270                 int line = lines > 0 ? view->height - lines : 0;
2271                 int end = line + ABS(lines);
2273                 scrollok(view->win, TRUE);
2274                 wscrl(view->win, lines);
2275                 scrollok(view->win, FALSE);
2277                 while (line < end && draw_view_line(view, line))
2278                         line++;
2280                 if (redraw_current_line)
2281                         draw_view_line(view, view->lineno - view->offset);
2282                 /* FIXME: Stupid hack to workaround bug where the message from
2283                  * scrolling up one line when impossible followed by scrolling
2284                  * down one line is not removed by the next action. */
2285                 if (lines > 0)
2286                         report("");
2287                 wrefresh(view->win);
2288         }
2290         report("");
2293 /* Scroll frontend */
2294 static void
2295 scroll_view(struct view *view, enum request request)
2297         int lines = 1;
2299         assert(view_is_displayed(view));
2301         switch (request) {
2302         case REQ_SCROLL_PAGE_DOWN:
2303                 lines = view->height;
2304         case REQ_SCROLL_LINE_DOWN:
2305                 if (view->offset + lines > view->lines)
2306                         lines = view->lines - view->offset;
2308                 if (lines == 0 || view->offset + view->height >= view->lines) {
2309                         report("Cannot scroll beyond the last line");
2310                         return;
2311                 }
2312                 break;
2314         case REQ_SCROLL_PAGE_UP:
2315                 lines = view->height;
2316         case REQ_SCROLL_LINE_UP:
2317                 if (lines > view->offset)
2318                         lines = view->offset;
2320                 if (lines == 0) {
2321                         report("Cannot scroll beyond the first line");
2322                         return;
2323                 }
2325                 lines = -lines;
2326                 break;
2328         default:
2329                 die("request %d not handled in switch", request);
2330         }
2332         do_scroll_view(view, lines);
2335 /* Cursor moving */
2336 static void
2337 move_view(struct view *view, enum request request)
2339         int scroll_steps = 0;
2340         int steps;
2342         switch (request) {
2343         case REQ_MOVE_FIRST_LINE:
2344                 steps = -view->lineno;
2345                 break;
2347         case REQ_MOVE_LAST_LINE:
2348                 steps = view->lines - view->lineno - 1;
2349                 break;
2351         case REQ_MOVE_PAGE_UP:
2352                 steps = view->height > view->lineno
2353                       ? -view->lineno : -view->height;
2354                 break;
2356         case REQ_MOVE_PAGE_DOWN:
2357                 steps = view->lineno + view->height >= view->lines
2358                       ? view->lines - view->lineno - 1 : view->height;
2359                 break;
2361         case REQ_MOVE_UP:
2362                 steps = -1;
2363                 break;
2365         case REQ_MOVE_DOWN:
2366                 steps = 1;
2367                 break;
2369         default:
2370                 die("request %d not handled in switch", request);
2371         }
2373         if (steps <= 0 && view->lineno == 0) {
2374                 report("Cannot move beyond the first line");
2375                 return;
2377         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2378                 report("Cannot move beyond the last line");
2379                 return;
2380         }
2382         /* Move the current line */
2383         view->lineno += steps;
2384         assert(0 <= view->lineno && view->lineno < view->lines);
2386         /* Check whether the view needs to be scrolled */
2387         if (view->lineno < view->offset ||
2388             view->lineno >= view->offset + view->height) {
2389                 scroll_steps = steps;
2390                 if (steps < 0 && -steps > view->offset) {
2391                         scroll_steps = -view->offset;
2393                 } else if (steps > 0) {
2394                         if (view->lineno == view->lines - 1 &&
2395                             view->lines > view->height) {
2396                                 scroll_steps = view->lines - view->offset - 1;
2397                                 if (scroll_steps >= view->height)
2398                                         scroll_steps -= view->height - 1;
2399                         }
2400                 }
2401         }
2403         if (!view_is_displayed(view)) {
2404                 view->offset += scroll_steps;
2405                 assert(0 <= view->offset && view->offset < view->lines);
2406                 view->ops->select(view, &view->line[view->lineno]);
2407                 return;
2408         }
2410         /* Repaint the old "current" line if we be scrolling */
2411         if (ABS(steps) < view->height)
2412                 draw_view_line(view, view->lineno - steps - view->offset);
2414         if (scroll_steps) {
2415                 do_scroll_view(view, scroll_steps);
2416                 return;
2417         }
2419         /* Draw the current line */
2420         draw_view_line(view, view->lineno - view->offset);
2422         wrefresh(view->win);
2423         report("");
2427 /*
2428  * Searching
2429  */
2431 static void search_view(struct view *view, enum request request);
2433 static void
2434 select_view_line(struct view *view, unsigned long lineno)
2436         if (lineno - view->offset >= view->height) {
2437                 view->offset = lineno;
2438                 view->lineno = lineno;
2439                 if (view_is_displayed(view))
2440                         redraw_view(view);
2442         } else {
2443                 unsigned long old_lineno = view->lineno - view->offset;
2445                 view->lineno = lineno;
2446                 if (view_is_displayed(view)) {
2447                         draw_view_line(view, old_lineno);
2448                         draw_view_line(view, view->lineno - view->offset);
2449                         wrefresh(view->win);
2450                 } else {
2451                         view->ops->select(view, &view->line[view->lineno]);
2452                 }
2453         }
2456 static void
2457 find_next(struct view *view, enum request request)
2459         unsigned long lineno = view->lineno;
2460         int direction;
2462         if (!*view->grep) {
2463                 if (!*opt_search)
2464                         report("No previous search");
2465                 else
2466                         search_view(view, request);
2467                 return;
2468         }
2470         switch (request) {
2471         case REQ_SEARCH:
2472         case REQ_FIND_NEXT:
2473                 direction = 1;
2474                 break;
2476         case REQ_SEARCH_BACK:
2477         case REQ_FIND_PREV:
2478                 direction = -1;
2479                 break;
2481         default:
2482                 return;
2483         }
2485         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2486                 lineno += direction;
2488         /* Note, lineno is unsigned long so will wrap around in which case it
2489          * will become bigger than view->lines. */
2490         for (; lineno < view->lines; lineno += direction) {
2491                 if (view->ops->grep(view, &view->line[lineno])) {
2492                         select_view_line(view, lineno);
2493                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2494                         return;
2495                 }
2496         }
2498         report("No match found for '%s'", view->grep);
2501 static void
2502 search_view(struct view *view, enum request request)
2504         int regex_err;
2506         if (view->regex) {
2507                 regfree(view->regex);
2508                 *view->grep = 0;
2509         } else {
2510                 view->regex = calloc(1, sizeof(*view->regex));
2511                 if (!view->regex)
2512                         return;
2513         }
2515         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2516         if (regex_err != 0) {
2517                 char buf[SIZEOF_STR] = "unknown error";
2519                 regerror(regex_err, view->regex, buf, sizeof(buf));
2520                 report("Search failed: %s", buf);
2521                 return;
2522         }
2524         string_copy(view->grep, opt_search);
2526         find_next(view, request);
2529 /*
2530  * Incremental updating
2531  */
2533 static void
2534 reset_view(struct view *view)
2536         int i;
2538         for (i = 0; i < view->lines; i++)
2539                 free(view->line[i].data);
2540         free(view->line);
2542         view->p_offset = view->offset;
2543         view->p_lineno = view->lineno;
2545         view->line = NULL;
2546         view->offset = 0;
2547         view->lines  = 0;
2548         view->lineno = 0;
2549         view->line_alloc = 0;
2550         view->vid[0] = 0;
2551         view->update_secs = 0;
2554 static void
2555 free_argv(const char *argv[])
2557         int argc;
2559         for (argc = 0; argv[argc]; argc++)
2560                 free((void *) argv[argc]);
2563 static bool
2564 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2566         char buf[SIZEOF_STR];
2567         int argc;
2568         bool noreplace = flags == FORMAT_NONE;
2570         free_argv(dst_argv);
2572         for (argc = 0; src_argv[argc]; argc++) {
2573                 const char *arg = src_argv[argc];
2574                 size_t bufpos = 0;
2576                 while (arg) {
2577                         char *next = strstr(arg, "%(");
2578                         int len = next - arg;
2579                         const char *value;
2581                         if (!next || noreplace) {
2582                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2583                                         noreplace = TRUE;
2584                                 len = strlen(arg);
2585                                 value = "";
2587                         } else if (!prefixcmp(next, "%(directory)")) {
2588                                 value = opt_path;
2590                         } else if (!prefixcmp(next, "%(file)")) {
2591                                 value = opt_file;
2593                         } else if (!prefixcmp(next, "%(ref)")) {
2594                                 value = *opt_ref ? opt_ref : "HEAD";
2596                         } else if (!prefixcmp(next, "%(head)")) {
2597                                 value = ref_head;
2599                         } else if (!prefixcmp(next, "%(commit)")) {
2600                                 value = ref_commit;
2602                         } else if (!prefixcmp(next, "%(blob)")) {
2603                                 value = ref_blob;
2605                         } else {
2606                                 report("Unknown replacement: `%s`", next);
2607                                 return FALSE;
2608                         }
2610                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2611                                 return FALSE;
2613                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2614                 }
2616                 dst_argv[argc] = strdup(buf);
2617                 if (!dst_argv[argc])
2618                         break;
2619         }
2621         dst_argv[argc] = NULL;
2623         return src_argv[argc] == NULL;
2626 static bool
2627 restore_view_position(struct view *view)
2629         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2630                 return FALSE;
2632         /* Changing the view position cancels the restoring. */
2633         /* FIXME: Changing back to the first line is not detected. */
2634         if (view->offset != 0 || view->lineno != 0) {
2635                 view->p_restore = FALSE;
2636                 return FALSE;
2637         }
2639         if (view->p_lineno >= view->lines) {
2640                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2641                 if (view->p_offset >= view->p_lineno) {
2642                         unsigned long half = view->height / 2;
2644                         if (view->p_lineno > half)
2645                                 view->p_offset = view->p_lineno - half;
2646                         else
2647                                 view->p_offset = 0;
2648                 }
2649         }
2651         if (view_is_displayed(view) &&
2652             view->offset != view->p_offset &&
2653             view->lineno != view->p_lineno)
2654                 werase(view->win);
2656         view->offset = view->p_offset;
2657         view->lineno = view->p_lineno;
2658         view->p_restore = FALSE;
2660         return TRUE;
2663 static void
2664 end_update(struct view *view, bool force)
2666         if (!view->pipe)
2667                 return;
2668         while (!view->ops->read(view, NULL))
2669                 if (!force)
2670                         return;
2671         set_nonblocking_input(FALSE);
2672         if (force)
2673                 kill_io(view->pipe);
2674         done_io(view->pipe);
2675         view->pipe = NULL;
2678 static void
2679 setup_update(struct view *view, const char *vid)
2681         set_nonblocking_input(TRUE);
2682         reset_view(view);
2683         string_copy_rev(view->vid, vid);
2684         view->pipe = &view->io;
2685         view->start_time = time(NULL);
2688 static bool
2689 prepare_update(struct view *view, const char *argv[], const char *dir,
2690                enum format_flags flags)
2692         if (view->pipe)
2693                 end_update(view, TRUE);
2694         return init_io_rd(&view->io, argv, dir, flags);
2697 static bool
2698 prepare_update_file(struct view *view, const char *name)
2700         if (view->pipe)
2701                 end_update(view, TRUE);
2702         return io_open(&view->io, name);
2705 static bool
2706 begin_update(struct view *view, bool refresh)
2708         if (view->pipe)
2709                 end_update(view, TRUE);
2711         if (refresh) {
2712                 if (!start_io(&view->io))
2713                         return FALSE;
2715         } else {
2716                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2717                         opt_path[0] = 0;
2719                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2720                         return FALSE;
2722                 /* Put the current ref_* value to the view title ref
2723                  * member. This is needed by the blob view. Most other
2724                  * views sets it automatically after loading because the
2725                  * first line is a commit line. */
2726                 string_copy_rev(view->ref, view->id);
2727         }
2729         setup_update(view, view->id);
2731         return TRUE;
2734 #define ITEM_CHUNK_SIZE 256
2735 static void *
2736 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2738         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2739         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2741         if (mem == NULL || num_chunks != num_chunks_new) {
2742                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2743                 mem = realloc(mem, *size * item_size);
2744         }
2746         return mem;
2749 static struct line *
2750 realloc_lines(struct view *view, size_t line_size)
2752         size_t alloc = view->line_alloc;
2753         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2754                                          sizeof(*view->line));
2756         if (!tmp)
2757                 return NULL;
2759         view->line = tmp;
2760         view->line_alloc = alloc;
2761         return view->line;
2764 static bool
2765 update_view(struct view *view)
2767         char out_buffer[BUFSIZ * 2];
2768         char *line;
2769         /* Clear the view and redraw everything since the tree sorting
2770          * might have rearranged things. */
2771         bool redraw = view->lines == 0;
2772         bool can_read = TRUE;
2774         if (!view->pipe)
2775                 return TRUE;
2777         if (!io_can_read(view->pipe)) {
2778                 if (view->lines == 0) {
2779                         time_t secs = time(NULL) - view->start_time;
2781                         if (secs > view->update_secs) {
2782                                 if (view->update_secs == 0)
2783                                         redraw_view(view);
2784                                 update_view_title(view);
2785                                 view->update_secs = secs;
2786                         }
2787                 }
2788                 return TRUE;
2789         }
2791         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2792                 if (opt_iconv != ICONV_NONE) {
2793                         ICONV_CONST char *inbuf = line;
2794                         size_t inlen = strlen(line) + 1;
2796                         char *outbuf = out_buffer;
2797                         size_t outlen = sizeof(out_buffer);
2799                         size_t ret;
2801                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2802                         if (ret != (size_t) -1)
2803                                 line = out_buffer;
2804                 }
2806                 if (!view->ops->read(view, line)) {
2807                         report("Allocation failure");
2808                         end_update(view, TRUE);
2809                         return FALSE;
2810                 }
2811         }
2813         {
2814                 unsigned long lines = view->lines;
2815                 int digits;
2817                 for (digits = 0; lines; digits++)
2818                         lines /= 10;
2820                 /* Keep the displayed view in sync with line number scaling. */
2821                 if (digits != view->digits) {
2822                         view->digits = digits;
2823                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2824                                 redraw = TRUE;
2825                 }
2826         }
2828         if (io_error(view->pipe)) {
2829                 report("Failed to read: %s", io_strerror(view->pipe));
2830                 end_update(view, TRUE);
2832         } else if (io_eof(view->pipe)) {
2833                 report("");
2834                 end_update(view, FALSE);
2835         }
2837         if (restore_view_position(view))
2838                 redraw = TRUE;
2840         if (!view_is_displayed(view))
2841                 return TRUE;
2843         if (redraw)
2844                 redraw_view_from(view, 0);
2845         else
2846                 redraw_view_dirty(view);
2848         /* Update the title _after_ the redraw so that if the redraw picks up a
2849          * commit reference in view->ref it'll be available here. */
2850         update_view_title(view);
2851         update_display_cursor(view);
2852         return TRUE;
2855 static struct line *
2856 add_line_data(struct view *view, void *data, enum line_type type)
2858         struct line *line;
2860         if (!realloc_lines(view, view->lines + 1))
2861                 return NULL;
2863         line = &view->line[view->lines++];
2864         memset(line, 0, sizeof(*line));
2865         line->type = type;
2866         line->data = data;
2867         line->dirty = 1;
2869         return line;
2872 static struct line *
2873 add_line_text(struct view *view, const char *text, enum line_type type)
2875         char *data = text ? strdup(text) : NULL;
2877         return data ? add_line_data(view, data, type) : NULL;
2880 static struct line *
2881 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2883         char buf[SIZEOF_STR];
2884         va_list args;
2886         va_start(args, fmt);
2887         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2888                 buf[0] = 0;
2889         va_end(args);
2891         return buf[0] ? add_line_text(view, buf, type) : NULL;
2894 /*
2895  * View opening
2896  */
2898 enum open_flags {
2899         OPEN_DEFAULT = 0,       /* Use default view switching. */
2900         OPEN_SPLIT = 1,         /* Split current view. */
2901         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2902         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2903         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2904         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2905         OPEN_PREPARED = 32,     /* Open already prepared command. */
2906 };
2908 static void
2909 open_view(struct view *prev, enum request request, enum open_flags flags)
2911         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2912         bool split = !!(flags & OPEN_SPLIT);
2913         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2914         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2915         struct view *view = VIEW(request);
2916         int nviews = displayed_views();
2917         struct view *base_view = display[0];
2919         if (view == prev && nviews == 1 && !reload) {
2920                 report("Already in %s view", view->name);
2921                 return;
2922         }
2924         if (view->git_dir && !opt_git_dir[0]) {
2925                 report("The %s view is disabled in pager view", view->name);
2926                 return;
2927         }
2929         if (split) {
2930                 display[1] = view;
2931                 if (!backgrounded)
2932                         current_view = 1;
2933         } else if (!nomaximize) {
2934                 /* Maximize the current view. */
2935                 memset(display, 0, sizeof(display));
2936                 current_view = 0;
2937                 display[current_view] = view;
2938         }
2940         /* Resize the view when switching between split- and full-screen,
2941          * or when switching between two different full-screen views. */
2942         if (nviews != displayed_views() ||
2943             (nviews == 1 && base_view != display[0]))
2944                 resize_display();
2946         if (view->ops->open) {
2947                 if (view->pipe)
2948                         end_update(view, TRUE);
2949                 if (!view->ops->open(view)) {
2950                         report("Failed to load %s view", view->name);
2951                         return;
2952                 }
2953                 restore_view_position(view);
2955         } else if ((reload || strcmp(view->vid, view->id)) &&
2956                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2957                 report("Failed to load %s view", view->name);
2958                 return;
2959         }
2961         if (split && prev->lineno - prev->offset >= prev->height) {
2962                 /* Take the title line into account. */
2963                 int lines = prev->lineno - prev->offset - prev->height + 1;
2965                 /* Scroll the view that was split if the current line is
2966                  * outside the new limited view. */
2967                 do_scroll_view(prev, lines);
2968         }
2970         if (prev && view != prev) {
2971                 if (split && !backgrounded) {
2972                         /* "Blur" the previous view. */
2973                         update_view_title(prev);
2974                 }
2976                 view->parent = prev;
2977         }
2979         if (view->pipe && view->lines == 0) {
2980                 /* Clear the old view and let the incremental updating refill
2981                  * the screen. */
2982                 werase(view->win);
2983                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2984                 report("");
2985         } else if (view_is_displayed(view)) {
2986                 redraw_view(view);
2987                 report("");
2988         }
2990         /* If the view is backgrounded the above calls to report()
2991          * won't redraw the view title. */
2992         if (backgrounded)
2993                 update_view_title(view);
2996 static void
2997 open_external_viewer(const char *argv[], const char *dir)
2999         def_prog_mode();           /* save current tty modes */
3000         endwin();                  /* restore original tty modes */
3001         run_io_fg(argv, dir);
3002         fprintf(stderr, "Press Enter to continue");
3003         getc(opt_tty);
3004         reset_prog_mode();
3005         redraw_display(TRUE);
3008 static void
3009 open_mergetool(const char *file)
3011         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3013         open_external_viewer(mergetool_argv, opt_cdup);
3016 static void
3017 open_editor(bool from_root, const char *file)
3019         const char *editor_argv[] = { "vi", file, NULL };
3020         const char *editor;
3022         editor = getenv("GIT_EDITOR");
3023         if (!editor && *opt_editor)
3024                 editor = opt_editor;
3025         if (!editor)
3026                 editor = getenv("VISUAL");
3027         if (!editor)
3028                 editor = getenv("EDITOR");
3029         if (!editor)
3030                 editor = "vi";
3032         editor_argv[0] = editor;
3033         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3036 static void
3037 open_run_request(enum request request)
3039         struct run_request *req = get_run_request(request);
3040         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3042         if (!req) {
3043                 report("Unknown run request");
3044                 return;
3045         }
3047         if (format_argv(argv, req->argv, FORMAT_ALL))
3048                 open_external_viewer(argv, NULL);
3049         free_argv(argv);
3052 /*
3053  * User request switch noodle
3054  */
3056 static int
3057 view_driver(struct view *view, enum request request)
3059         int i;
3061         if (request == REQ_NONE) {
3062                 doupdate();
3063                 return TRUE;
3064         }
3066         if (request > REQ_NONE) {
3067                 open_run_request(request);
3068                 /* FIXME: When all views can refresh always do this. */
3069                 if (view == VIEW(REQ_VIEW_STATUS) ||
3070                     view == VIEW(REQ_VIEW_MAIN) ||
3071                     view == VIEW(REQ_VIEW_LOG) ||
3072                     view == VIEW(REQ_VIEW_STAGE))
3073                         request = REQ_REFRESH;
3074                 else
3075                         return TRUE;
3076         }
3078         if (view && view->lines) {
3079                 request = view->ops->request(view, request, &view->line[view->lineno]);
3080                 if (request == REQ_NONE)
3081                         return TRUE;
3082         }
3084         switch (request) {
3085         case REQ_MOVE_UP:
3086         case REQ_MOVE_DOWN:
3087         case REQ_MOVE_PAGE_UP:
3088         case REQ_MOVE_PAGE_DOWN:
3089         case REQ_MOVE_FIRST_LINE:
3090         case REQ_MOVE_LAST_LINE:
3091                 move_view(view, request);
3092                 break;
3094         case REQ_SCROLL_LINE_DOWN:
3095         case REQ_SCROLL_LINE_UP:
3096         case REQ_SCROLL_PAGE_DOWN:
3097         case REQ_SCROLL_PAGE_UP:
3098                 scroll_view(view, request);
3099                 break;
3101         case REQ_VIEW_BLAME:
3102                 if (!opt_file[0]) {
3103                         report("No file chosen, press %s to open tree view",
3104                                get_key(REQ_VIEW_TREE));
3105                         break;
3106                 }
3107                 open_view(view, request, OPEN_DEFAULT);
3108                 break;
3110         case REQ_VIEW_BLOB:
3111                 if (!ref_blob[0]) {
3112                         report("No file chosen, press %s to open tree view",
3113                                get_key(REQ_VIEW_TREE));
3114                         break;
3115                 }
3116                 open_view(view, request, OPEN_DEFAULT);
3117                 break;
3119         case REQ_VIEW_PAGER:
3120                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3121                         report("No pager content, press %s to run command from prompt",
3122                                get_key(REQ_PROMPT));
3123                         break;
3124                 }
3125                 open_view(view, request, OPEN_DEFAULT);
3126                 break;
3128         case REQ_VIEW_STAGE:
3129                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3130                         report("No stage content, press %s to open the status view and choose file",
3131                                get_key(REQ_VIEW_STATUS));
3132                         break;
3133                 }
3134                 open_view(view, request, OPEN_DEFAULT);
3135                 break;
3137         case REQ_VIEW_STATUS:
3138                 if (opt_is_inside_work_tree == FALSE) {
3139                         report("The status view requires a working tree");
3140                         break;
3141                 }
3142                 open_view(view, request, OPEN_DEFAULT);
3143                 break;
3145         case REQ_VIEW_MAIN:
3146         case REQ_VIEW_DIFF:
3147         case REQ_VIEW_LOG:
3148         case REQ_VIEW_TREE:
3149         case REQ_VIEW_HELP:
3150                 open_view(view, request, OPEN_DEFAULT);
3151                 break;
3153         case REQ_NEXT:
3154         case REQ_PREVIOUS:
3155                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3157                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3158                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3159                    (view == VIEW(REQ_VIEW_DIFF) &&
3160                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3161                    (view == VIEW(REQ_VIEW_STAGE) &&
3162                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3163                    (view == VIEW(REQ_VIEW_BLOB) &&
3164                      view->parent == VIEW(REQ_VIEW_TREE))) {
3165                         int line;
3167                         view = view->parent;
3168                         line = view->lineno;
3169                         move_view(view, request);
3170                         if (view_is_displayed(view))
3171                                 update_view_title(view);
3172                         if (line != view->lineno)
3173                                 view->ops->request(view, REQ_ENTER,
3174                                                    &view->line[view->lineno]);
3176                 } else {
3177                         move_view(view, request);
3178                 }
3179                 break;
3181         case REQ_VIEW_NEXT:
3182         {
3183                 int nviews = displayed_views();
3184                 int next_view = (current_view + 1) % nviews;
3186                 if (next_view == current_view) {
3187                         report("Only one view is displayed");
3188                         break;
3189                 }
3191                 current_view = next_view;
3192                 /* Blur out the title of the previous view. */
3193                 update_view_title(view);
3194                 report("");
3195                 break;
3196         }
3197         case REQ_REFRESH:
3198                 report("Refreshing is not yet supported for the %s view", view->name);
3199                 break;
3201         case REQ_MAXIMIZE:
3202                 if (displayed_views() == 2)
3203                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3204                 break;
3206         case REQ_TOGGLE_LINENO:
3207                 toggle_view_option(&opt_line_number, "line numbers");
3208                 break;
3210         case REQ_TOGGLE_DATE:
3211                 toggle_view_option(&opt_date, "date display");
3212                 break;
3214         case REQ_TOGGLE_AUTHOR:
3215                 toggle_view_option(&opt_author, "author display");
3216                 break;
3218         case REQ_TOGGLE_REV_GRAPH:
3219                 toggle_view_option(&opt_rev_graph, "revision graph display");
3220                 break;
3222         case REQ_TOGGLE_REFS:
3223                 toggle_view_option(&opt_show_refs, "reference display");
3224                 break;
3226         case REQ_SEARCH:
3227         case REQ_SEARCH_BACK:
3228                 search_view(view, request);
3229                 break;
3231         case REQ_FIND_NEXT:
3232         case REQ_FIND_PREV:
3233                 find_next(view, request);
3234                 break;
3236         case REQ_STOP_LOADING:
3237                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3238                         view = &views[i];
3239                         if (view->pipe)
3240                                 report("Stopped loading the %s view", view->name),
3241                         end_update(view, TRUE);
3242                 }
3243                 break;
3245         case REQ_SHOW_VERSION:
3246                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3247                 return TRUE;
3249         case REQ_SCREEN_REDRAW:
3250                 redraw_display(TRUE);
3251                 break;
3253         case REQ_EDIT:
3254                 report("Nothing to edit");
3255                 break;
3257         case REQ_ENTER:
3258                 report("Nothing to enter");
3259                 break;
3261         case REQ_VIEW_CLOSE:
3262                 /* XXX: Mark closed views by letting view->parent point to the
3263                  * view itself. Parents to closed view should never be
3264                  * followed. */
3265                 if (view->parent &&
3266                     view->parent->parent != view->parent) {
3267                         memset(display, 0, sizeof(display));
3268                         current_view = 0;
3269                         display[current_view] = view->parent;
3270                         view->parent = view;
3271                         resize_display();
3272                         redraw_display(FALSE);
3273                         report("");
3274                         break;
3275                 }
3276                 /* Fall-through */
3277         case REQ_QUIT:
3278                 return FALSE;
3280         default:
3281                 report("Unknown key, press 'h' for help");
3282                 return TRUE;
3283         }
3285         return TRUE;
3289 /*
3290  * View backend utilities
3291  */
3293 /* Parse author lines where the name may be empty:
3294  *      author  <email@address.tld> 1138474660 +0100
3295  */
3296 static void
3297 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3299         char *nameend = strchr(ident, '<');
3300         char *emailend = strchr(ident, '>');
3302         if (nameend && emailend)
3303                 *nameend = *emailend = 0;
3304         ident = chomp_string(ident);
3305         if (!*ident) {
3306                 if (nameend)
3307                         ident = chomp_string(nameend + 1);
3308                 if (!*ident)
3309                         ident = "Unknown";
3310         }
3312         string_ncopy_do(author, authorsize, ident, strlen(ident));
3314         /* Parse epoch and timezone */
3315         if (emailend && emailend[1] == ' ') {
3316                 char *secs = emailend + 2;
3317                 char *zone = strchr(secs, ' ');
3318                 time_t time = (time_t) atol(secs);
3320                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3321                         long tz;
3323                         zone++;
3324                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3325                         tz += ('0' - zone[2]) * 60 * 60;
3326                         tz += ('0' - zone[3]) * 60;
3327                         tz += ('0' - zone[4]) * 60;
3329                         if (zone[0] == '-')
3330                                 tz = -tz;
3332                         time -= tz;
3333                 }
3335                 gmtime_r(&time, tm);
3336         }
3339 static enum input_status
3340 select_commit_parent_handler(void *data, char *buf, int c)
3342         size_t parents = *(size_t *) data;
3343         int parent = 0;
3345         if (!isdigit(c))
3346                 return INPUT_SKIP;
3348         if (*buf)
3349                 parent = atoi(buf) * 10;
3350         parent += c - '0';
3352         if (parent > parents)
3353                 return INPUT_SKIP;
3354         return INPUT_OK;
3357 static bool
3358 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3360         char buf[SIZEOF_STR * 4];
3361         const char *revlist_argv[] = {
3362                 "git", "rev-list", "-1", "--parents", id, NULL
3363         };
3364         int parents;
3366         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3367             !*chomp_string(buf) ||
3368             (parents = (strlen(buf) / 40) - 1) < 0) {
3369                 report("Failed to get parent information");
3370                 return FALSE;
3372         } else if (parents == 0) {
3373                 report("The selected commit has no parents");
3374                 return FALSE;
3375         }
3377         if (parents > 1) {
3378                 char prompt[SIZEOF_STR];
3379                 char *result;
3381                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3382                         return FALSE;
3383                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3384                 if (!result)
3385                         return FALSE;
3386                 parents = atoi(result);
3387         }
3389         string_copy_rev(rev, &buf[41 * parents]);
3390         return TRUE;
3393 /*
3394  * Pager backend
3395  */
3397 static bool
3398 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3400         char *text = line->data;
3402         if (opt_line_number && draw_lineno(view, lineno))
3403                 return TRUE;
3405         draw_text(view, line->type, text, TRUE);
3406         return TRUE;
3409 static bool
3410 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3412         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3413         char refbuf[SIZEOF_STR];
3414         char *ref = NULL;
3416         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3417                 ref = chomp_string(refbuf);
3419         if (!ref || !*ref)
3420                 return TRUE;
3422         /* This is the only fatal call, since it can "corrupt" the buffer. */
3423         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3424                 return FALSE;
3426         return TRUE;
3429 static void
3430 add_pager_refs(struct view *view, struct line *line)
3432         char buf[SIZEOF_STR];
3433         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3434         struct ref **refs;
3435         size_t bufpos = 0, refpos = 0;
3436         const char *sep = "Refs: ";
3437         bool is_tag = FALSE;
3439         assert(line->type == LINE_COMMIT);
3441         refs = get_refs(commit_id);
3442         if (!refs) {
3443                 if (view == VIEW(REQ_VIEW_DIFF))
3444                         goto try_add_describe_ref;
3445                 return;
3446         }
3448         do {
3449                 struct ref *ref = refs[refpos];
3450                 const char *fmt = ref->tag    ? "%s[%s]" :
3451                                   ref->remote ? "%s<%s>" : "%s%s";
3453                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3454                         return;
3455                 sep = ", ";
3456                 if (ref->tag)
3457                         is_tag = TRUE;
3458         } while (refs[refpos++]->next);
3460         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3461 try_add_describe_ref:
3462                 /* Add <tag>-g<commit_id> "fake" reference. */
3463                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3464                         return;
3465         }
3467         if (bufpos == 0)
3468                 return;
3470         add_line_text(view, buf, LINE_PP_REFS);
3473 static bool
3474 pager_read(struct view *view, char *data)
3476         struct line *line;
3478         if (!data)
3479                 return TRUE;
3481         line = add_line_text(view, data, get_line_type(data));
3482         if (!line)
3483                 return FALSE;
3485         if (line->type == LINE_COMMIT &&
3486             (view == VIEW(REQ_VIEW_DIFF) ||
3487              view == VIEW(REQ_VIEW_LOG)))
3488                 add_pager_refs(view, line);
3490         return TRUE;
3493 static enum request
3494 pager_request(struct view *view, enum request request, struct line *line)
3496         int split = 0;
3498         if (request != REQ_ENTER)
3499                 return request;
3501         if (line->type == LINE_COMMIT &&
3502            (view == VIEW(REQ_VIEW_LOG) ||
3503             view == VIEW(REQ_VIEW_PAGER))) {
3504                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3505                 split = 1;
3506         }
3508         /* Always scroll the view even if it was split. That way
3509          * you can use Enter to scroll through the log view and
3510          * split open each commit diff. */
3511         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3513         /* FIXME: A minor workaround. Scrolling the view will call report("")
3514          * but if we are scrolling a non-current view this won't properly
3515          * update the view title. */
3516         if (split)
3517                 update_view_title(view);
3519         return REQ_NONE;
3522 static bool
3523 pager_grep(struct view *view, struct line *line)
3525         regmatch_t pmatch;
3526         char *text = line->data;
3528         if (!*text)
3529                 return FALSE;
3531         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3532                 return FALSE;
3534         return TRUE;
3537 static void
3538 pager_select(struct view *view, struct line *line)
3540         if (line->type == LINE_COMMIT) {
3541                 char *text = (char *)line->data + STRING_SIZE("commit ");
3543                 if (view != VIEW(REQ_VIEW_PAGER))
3544                         string_copy_rev(view->ref, text);
3545                 string_copy_rev(ref_commit, text);
3546         }
3549 static struct view_ops pager_ops = {
3550         "line",
3551         NULL,
3552         NULL,
3553         pager_read,
3554         pager_draw,
3555         pager_request,
3556         pager_grep,
3557         pager_select,
3558 };
3560 static const char *log_argv[SIZEOF_ARG] = {
3561         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3562 };
3564 static enum request
3565 log_request(struct view *view, enum request request, struct line *line)
3567         switch (request) {
3568         case REQ_REFRESH:
3569                 load_refs();
3570                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3571                 return REQ_NONE;
3572         default:
3573                 return pager_request(view, request, line);
3574         }
3577 static struct view_ops log_ops = {
3578         "line",
3579         log_argv,
3580         NULL,
3581         pager_read,
3582         pager_draw,
3583         log_request,
3584         pager_grep,
3585         pager_select,
3586 };
3588 static const char *diff_argv[SIZEOF_ARG] = {
3589         "git", "show", "--pretty=fuller", "--no-color", "--root",
3590                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3591 };
3593 static struct view_ops diff_ops = {
3594         "line",
3595         diff_argv,
3596         NULL,
3597         pager_read,
3598         pager_draw,
3599         pager_request,
3600         pager_grep,
3601         pager_select,
3602 };
3604 /*
3605  * Help backend
3606  */
3608 static bool
3609 help_open(struct view *view)
3611         char buf[SIZEOF_STR];
3612         size_t bufpos;
3613         int i;
3615         if (view->lines > 0)
3616                 return TRUE;
3618         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3620         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3621                 const char *key;
3623                 if (req_info[i].request == REQ_NONE)
3624                         continue;
3626                 if (!req_info[i].request) {
3627                         add_line_text(view, "", LINE_DEFAULT);
3628                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3629                         continue;
3630                 }
3632                 key = get_key(req_info[i].request);
3633                 if (!*key)
3634                         key = "(no key defined)";
3636                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3637                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3638                         if (buf[bufpos] == '_')
3639                                 buf[bufpos] = '-';
3640                 }
3642                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3643                                 key, buf, req_info[i].help);
3644         }
3646         if (run_requests) {
3647                 add_line_text(view, "", LINE_DEFAULT);
3648                 add_line_text(view, "External commands:", LINE_DEFAULT);
3649         }
3651         for (i = 0; i < run_requests; i++) {
3652                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3653                 const char *key;
3654                 int argc;
3656                 if (!req)
3657                         continue;
3659                 key = get_key_name(req->key);
3660                 if (!*key)
3661                         key = "(no key defined)";
3663                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3664                         if (!string_format_from(buf, &bufpos, "%s%s",
3665                                                 argc ? " " : "", req->argv[argc]))
3666                                 return REQ_NONE;
3668                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3669                                 keymap_table[req->keymap].name, key, buf);
3670         }
3672         return TRUE;
3675 static struct view_ops help_ops = {
3676         "line",
3677         NULL,
3678         help_open,
3679         NULL,
3680         pager_draw,
3681         pager_request,
3682         pager_grep,
3683         pager_select,
3684 };
3687 /*
3688  * Tree backend
3689  */
3691 struct tree_stack_entry {
3692         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3693         unsigned long lineno;           /* Line number to restore */
3694         char *name;                     /* Position of name in opt_path */
3695 };
3697 /* The top of the path stack. */
3698 static struct tree_stack_entry *tree_stack = NULL;
3699 unsigned long tree_lineno = 0;
3701 static void
3702 pop_tree_stack_entry(void)
3704         struct tree_stack_entry *entry = tree_stack;
3706         tree_lineno = entry->lineno;
3707         entry->name[0] = 0;
3708         tree_stack = entry->prev;
3709         free(entry);
3712 static void
3713 push_tree_stack_entry(const char *name, unsigned long lineno)
3715         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3716         size_t pathlen = strlen(opt_path);
3718         if (!entry)
3719                 return;
3721         entry->prev = tree_stack;
3722         entry->name = opt_path + pathlen;
3723         tree_stack = entry;
3725         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3726                 pop_tree_stack_entry();
3727                 return;
3728         }
3730         /* Move the current line to the first tree entry. */
3731         tree_lineno = 1;
3732         entry->lineno = lineno;
3735 /* Parse output from git-ls-tree(1):
3736  *
3737  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3738  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3739  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3740  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3741  */
3743 #define SIZEOF_TREE_ATTR \
3744         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3746 #define SIZEOF_TREE_MODE \
3747         STRING_SIZE("100644 ")
3749 #define TREE_ID_OFFSET \
3750         STRING_SIZE("100644 blob ")
3752 struct tree_entry {
3753         char id[SIZEOF_REV];
3754         mode_t mode;
3755         struct tm time;                 /* Date from the author ident. */
3756         char author[75];                /* Author of the commit. */
3757         char name[1];
3758 };
3760 static const char *
3761 tree_path(struct line *line)
3763         return ((struct tree_entry *) line->data)->name;
3767 static int
3768 tree_compare_entry(struct line *line1, struct line *line2)
3770         if (line1->type != line2->type)
3771                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3772         return strcmp(tree_path(line1), tree_path(line2));
3775 static struct line *
3776 tree_entry(struct view *view, enum line_type type, const char *path,
3777            const char *mode, const char *id)
3779         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3780         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3782         if (!entry || !line) {
3783                 free(entry);
3784                 return NULL;
3785         }
3787         strncpy(entry->name, path, strlen(path));
3788         if (mode)
3789                 entry->mode = strtoul(mode, NULL, 8);
3790         if (id)
3791                 string_copy_rev(entry->id, id);
3793         return line;
3796 static bool
3797 tree_read_date(struct view *view, char *text, bool *read_date)
3799         static char author_name[SIZEOF_STR];
3800         static struct tm author_time;
3802         if (!text && *read_date) {
3803                 *read_date = FALSE;
3804                 return TRUE;
3806         } else if (!text) {
3807                 char *path = *opt_path ? opt_path : ".";
3808                 /* Find next entry to process */
3809                 const char *log_file[] = {
3810                         "git", "log", "--no-color", "--pretty=raw",
3811                                 "--cc", "--raw", view->id, "--", path, NULL
3812                 };
3813                 struct io io = {};
3815                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3816                         report("Failed to load tree data");
3817                         return TRUE;
3818                 }
3820                 done_io(view->pipe);
3821                 view->io = io;
3822                 *read_date = TRUE;
3823                 return FALSE;
3825         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3826                 parse_author_line(text + STRING_SIZE("author "),
3827                                   author_name, sizeof(author_name), &author_time);
3829         } else if (*text == ':') {
3830                 char *pos;
3831                 size_t annotated = 1;
3832                 size_t i;
3834                 pos = strchr(text, '\t');
3835                 if (!pos)
3836                         return TRUE;
3837                 text = pos + 1;
3838                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3839                         text += strlen(opt_prefix);
3840                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3841                         text += strlen(opt_path);
3842                 pos = strchr(text, '/');
3843                 if (pos)
3844                         *pos = 0;
3846                 for (i = 1; i < view->lines; i++) {
3847                         struct line *line = &view->line[i];
3848                         struct tree_entry *entry = line->data;
3850                         annotated += !!*entry->author;
3851                         if (*entry->author || strcmp(entry->name, text))
3852                                 continue;
3854                         string_copy(entry->author, author_name);
3855                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3856                         line->dirty = 1;
3857                         break;
3858                 }
3860                 if (annotated == view->lines)
3861                         kill_io(view->pipe);
3862         }
3863         return TRUE;
3866 static bool
3867 tree_read(struct view *view, char *text)
3869         static bool read_date = FALSE;
3870         struct tree_entry *data;
3871         struct line *entry, *line;
3872         enum line_type type;
3873         size_t textlen = text ? strlen(text) : 0;
3874         char *path = text + SIZEOF_TREE_ATTR;
3876         if (read_date || !text)
3877                 return tree_read_date(view, text, &read_date);
3879         if (textlen <= SIZEOF_TREE_ATTR)
3880                 return FALSE;
3881         if (view->lines == 0 &&
3882             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3883                 return FALSE;
3885         /* Strip the path part ... */
3886         if (*opt_path) {
3887                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3888                 size_t striplen = strlen(opt_path);
3890                 if (pathlen > striplen)
3891                         memmove(path, path + striplen,
3892                                 pathlen - striplen + 1);
3894                 /* Insert "link" to parent directory. */
3895                 if (view->lines == 1 &&
3896                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3897                         return FALSE;
3898         }
3900         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3901         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3902         if (!entry)
3903                 return FALSE;
3904         data = entry->data;
3906         /* Skip "Directory ..." and ".." line. */
3907         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3908                 if (tree_compare_entry(line, entry) <= 0)
3909                         continue;
3911                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3913                 line->data = data;
3914                 line->type = type;
3915                 for (; line <= entry; line++)
3916                         line->dirty = line->cleareol = 1;
3917                 return TRUE;
3918         }
3920         if (tree_lineno > view->lineno) {
3921                 view->lineno = tree_lineno;
3922                 tree_lineno = 0;
3923         }
3925         return TRUE;
3928 static bool
3929 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3931         struct tree_entry *entry = line->data;
3933         if (line->type == LINE_TREE_PARENT) {
3934                 if (draw_text(view, line->type, "Directory path /", TRUE))
3935                         return TRUE;
3936         } else {
3937                 char mode[11] = "-r--r--r--";
3939                 if (S_ISDIR(entry->mode)) {
3940                         mode[3] = mode[6] = mode[9] = 'x';
3941                         mode[0] = 'd';
3942                 }
3943                 if (S_ISLNK(entry->mode))
3944                         mode[0] = 'l';
3945                 if (entry->mode & S_IWUSR)
3946                         mode[2] = 'w';
3947                 if (entry->mode & S_IXUSR)
3948                         mode[3] = 'x';
3949                 if (entry->mode & S_IXGRP)
3950                         mode[6] = 'x';
3951                 if (entry->mode & S_IXOTH)
3952                         mode[9] = 'x';
3953                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3954                         return TRUE;
3956                 if (opt_author &&
3957                     draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
3958                         return TRUE;
3960                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3961                         return TRUE;
3962         }
3963         if (draw_text(view, line->type, entry->name, TRUE))
3964                 return TRUE;
3965         return TRUE;
3968 static void
3969 open_blob_editor()
3971         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3972         int fd = mkstemp(file);
3974         if (fd == -1)
3975                 report("Failed to create temporary file");
3976         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3977                 report("Failed to save blob data to file");
3978         else
3979                 open_editor(FALSE, file);
3980         if (fd != -1)
3981                 unlink(file);
3984 static enum request
3985 tree_request(struct view *view, enum request request, struct line *line)
3987         enum open_flags flags;
3989         switch (request) {
3990         case REQ_VIEW_BLAME:
3991                 if (line->type != LINE_TREE_FILE) {
3992                         report("Blame only supported for files");
3993                         return REQ_NONE;
3994                 }
3996                 string_copy(opt_ref, view->vid);
3997                 return request;
3999         case REQ_EDIT:
4000                 if (line->type != LINE_TREE_FILE) {
4001                         report("Edit only supported for files");
4002                 } else if (!is_head_commit(view->vid)) {
4003                         open_blob_editor();
4004                 } else {
4005                         open_editor(TRUE, opt_file);
4006                 }
4007                 return REQ_NONE;
4009         case REQ_PARENT:
4010                 if (!*opt_path) {
4011                         /* quit view if at top of tree */
4012                         return REQ_VIEW_CLOSE;
4013                 }
4014                 /* fake 'cd  ..' */
4015                 line = &view->line[1];
4016                 break;
4018         case REQ_ENTER:
4019                 break;
4021         default:
4022                 return request;
4023         }
4025         /* Cleanup the stack if the tree view is at a different tree. */
4026         while (!*opt_path && tree_stack)
4027                 pop_tree_stack_entry();
4029         switch (line->type) {
4030         case LINE_TREE_DIR:
4031                 /* Depending on whether it is a subdir or parent (updir?) link
4032                  * mangle the path buffer. */
4033                 if (line == &view->line[1] && *opt_path) {
4034                         pop_tree_stack_entry();
4036                 } else {
4037                         const char *basename = tree_path(line);
4039                         push_tree_stack_entry(basename, view->lineno);
4040                 }
4042                 /* Trees and subtrees share the same ID, so they are not not
4043                  * unique like blobs. */
4044                 flags = OPEN_RELOAD;
4045                 request = REQ_VIEW_TREE;
4046                 break;
4048         case LINE_TREE_FILE:
4049                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4050                 request = REQ_VIEW_BLOB;
4051                 break;
4053         default:
4054                 return REQ_NONE;
4055         }
4057         open_view(view, request, flags);
4058         if (request == REQ_VIEW_TREE)
4059                 view->lineno = tree_lineno;
4061         return REQ_NONE;
4064 static void
4065 tree_select(struct view *view, struct line *line)
4067         struct tree_entry *entry = line->data;
4069         if (line->type == LINE_TREE_FILE) {
4070                 string_copy_rev(ref_blob, entry->id);
4071                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4073         } else if (line->type != LINE_TREE_DIR) {
4074                 return;
4075         }
4077         string_copy_rev(view->ref, entry->id);
4080 static const char *tree_argv[SIZEOF_ARG] = {
4081         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4082 };
4084 static struct view_ops tree_ops = {
4085         "file",
4086         tree_argv,
4087         NULL,
4088         tree_read,
4089         tree_draw,
4090         tree_request,
4091         pager_grep,
4092         tree_select,
4093 };
4095 static bool
4096 blob_read(struct view *view, char *line)
4098         if (!line)
4099                 return TRUE;
4100         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4103 static enum request
4104 blob_request(struct view *view, enum request request, struct line *line)
4106         switch (request) {
4107         case REQ_EDIT:
4108                 open_blob_editor();
4109                 return REQ_NONE;
4110         default:
4111                 return pager_request(view, request, line);
4112         }
4115 static const char *blob_argv[SIZEOF_ARG] = {
4116         "git", "cat-file", "blob", "%(blob)", NULL
4117 };
4119 static struct view_ops blob_ops = {
4120         "line",
4121         blob_argv,
4122         NULL,
4123         blob_read,
4124         pager_draw,
4125         blob_request,
4126         pager_grep,
4127         pager_select,
4128 };
4130 /*
4131  * Blame backend
4132  *
4133  * Loading the blame view is a two phase job:
4134  *
4135  *  1. File content is read either using opt_file from the
4136  *     filesystem or using git-cat-file.
4137  *  2. Then blame information is incrementally added by
4138  *     reading output from git-blame.
4139  */
4141 static const char *blame_head_argv[] = {
4142         "git", "blame", "--incremental", "--", "%(file)", NULL
4143 };
4145 static const char *blame_ref_argv[] = {
4146         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4147 };
4149 static const char *blame_cat_file_argv[] = {
4150         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4151 };
4153 struct blame_commit {
4154         char id[SIZEOF_REV];            /* SHA1 ID. */
4155         char title[128];                /* First line of the commit message. */
4156         char author[75];                /* Author of the commit. */
4157         struct tm time;                 /* Date from the author ident. */
4158         char filename[128];             /* Name of file. */
4159         bool has_previous;              /* Was a "previous" line detected. */
4160 };
4162 struct blame {
4163         struct blame_commit *commit;
4164         char text[1];
4165 };
4167 static bool
4168 blame_open(struct view *view)
4170         if (*opt_ref || !io_open(&view->io, opt_file)) {
4171                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4172                         return FALSE;
4173         }
4175         setup_update(view, opt_file);
4176         string_format(view->ref, "%s ...", opt_file);
4178         return TRUE;
4181 static struct blame_commit *
4182 get_blame_commit(struct view *view, const char *id)
4184         size_t i;
4186         for (i = 0; i < view->lines; i++) {
4187                 struct blame *blame = view->line[i].data;
4189                 if (!blame->commit)
4190                         continue;
4192                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4193                         return blame->commit;
4194         }
4196         {
4197                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4199                 if (commit)
4200                         string_ncopy(commit->id, id, SIZEOF_REV);
4201                 return commit;
4202         }
4205 static bool
4206 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4208         const char *pos = *posref;
4210         *posref = NULL;
4211         pos = strchr(pos + 1, ' ');
4212         if (!pos || !isdigit(pos[1]))
4213                 return FALSE;
4214         *number = atoi(pos + 1);
4215         if (*number < min || *number > max)
4216                 return FALSE;
4218         *posref = pos;
4219         return TRUE;
4222 static struct blame_commit *
4223 parse_blame_commit(struct view *view, const char *text, int *blamed)
4225         struct blame_commit *commit;
4226         struct blame *blame;
4227         const char *pos = text + SIZEOF_REV - 1;
4228         size_t lineno;
4229         size_t group;
4231         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4232                 return NULL;
4234         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4235             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4236                 return NULL;
4238         commit = get_blame_commit(view, text);
4239         if (!commit)
4240                 return NULL;
4242         *blamed += group;
4243         while (group--) {
4244                 struct line *line = &view->line[lineno + group - 1];
4246                 blame = line->data;
4247                 blame->commit = commit;
4248                 line->dirty = 1;
4249         }
4251         return commit;
4254 static bool
4255 blame_read_file(struct view *view, const char *line, bool *read_file)
4257         if (!line) {
4258                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4259                 struct io io = {};
4261                 if (view->lines == 0 && !view->parent)
4262                         die("No blame exist for %s", view->vid);
4264                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4265                         report("Failed to load blame data");
4266                         return TRUE;
4267                 }
4269                 done_io(view->pipe);
4270                 view->io = io;
4271                 *read_file = FALSE;
4272                 return FALSE;
4274         } else {
4275                 size_t linelen = strlen(line);
4276                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4278                 blame->commit = NULL;
4279                 strncpy(blame->text, line, linelen);
4280                 blame->text[linelen] = 0;
4281                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4282         }
4285 static bool
4286 match_blame_header(const char *name, char **line)
4288         size_t namelen = strlen(name);
4289         bool matched = !strncmp(name, *line, namelen);
4291         if (matched)
4292                 *line += namelen;
4294         return matched;
4297 static bool
4298 blame_read(struct view *view, char *line)
4300         static struct blame_commit *commit = NULL;
4301         static int blamed = 0;
4302         static time_t author_time;
4303         static bool read_file = TRUE;
4305         if (read_file)
4306                 return blame_read_file(view, line, &read_file);
4308         if (!line) {
4309                 /* Reset all! */
4310                 commit = NULL;
4311                 blamed = 0;
4312                 read_file = TRUE;
4313                 string_format(view->ref, "%s", view->vid);
4314                 if (view_is_displayed(view)) {
4315                         update_view_title(view);
4316                         redraw_view_from(view, 0);
4317                 }
4318                 return TRUE;
4319         }
4321         if (!commit) {
4322                 commit = parse_blame_commit(view, line, &blamed);
4323                 string_format(view->ref, "%s %2d%%", view->vid,
4324                               view->lines ? blamed * 100 / view->lines : 0);
4326         } else if (match_blame_header("author ", &line)) {
4327                 string_ncopy(commit->author, line, strlen(line));
4329         } else if (match_blame_header("author-time ", &line)) {
4330                 author_time = (time_t) atol(line);
4332         } else if (match_blame_header("author-tz ", &line)) {
4333                 long tz;
4335                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4336                 tz += ('0' - line[2]) * 60 * 60;
4337                 tz += ('0' - line[3]) * 60;
4338                 tz += ('0' - line[4]) * 60;
4340                 if (line[0] == '-')
4341                         tz = -tz;
4343                 author_time -= tz;
4344                 gmtime_r(&author_time, &commit->time);
4346         } else if (match_blame_header("summary ", &line)) {
4347                 string_ncopy(commit->title, line, strlen(line));
4349         } else if (match_blame_header("previous ", &line)) {
4350                 commit->has_previous = TRUE;
4352         } else if (match_blame_header("filename ", &line)) {
4353                 string_ncopy(commit->filename, line, strlen(line));
4354                 commit = NULL;
4355         }
4357         return TRUE;
4360 static bool
4361 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4363         struct blame *blame = line->data;
4364         struct tm *time = NULL;
4365         const char *id = NULL, *author = NULL;
4367         if (blame->commit && *blame->commit->filename) {
4368                 id = blame->commit->id;
4369                 author = blame->commit->author;
4370                 time = &blame->commit->time;
4371         }
4373         if (opt_date && draw_date(view, time))
4374                 return TRUE;
4376         if (opt_author &&
4377             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4378                 return TRUE;
4380         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4381                 return TRUE;
4383         if (draw_lineno(view, lineno))
4384                 return TRUE;
4386         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4387         return TRUE;
4390 static bool
4391 check_blame_commit(struct blame *blame)
4393         if (!blame->commit)
4394                 report("Commit data not loaded yet");
4395         else if (!strcmp(blame->commit->id, NULL_ID))
4396                 report("No commit exist for the selected line");
4397         else
4398                 return TRUE;
4399         return FALSE;
4402 static enum request
4403 blame_request(struct view *view, enum request request, struct line *line)
4405         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4406         struct blame *blame = line->data;
4408         switch (request) {
4409         case REQ_VIEW_BLAME:
4410                 if (check_blame_commit(blame)) {
4411                         string_copy(opt_ref, blame->commit->id);
4412                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4413                 }
4414                 break;
4416         case REQ_PARENT:
4417                 if (check_blame_commit(blame) &&
4418                     select_commit_parent(blame->commit->id, opt_ref))
4419                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4420                 break;
4422         case REQ_ENTER:
4423                 if (!blame->commit) {
4424                         report("No commit loaded yet");
4425                         break;
4426                 }
4428                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4429                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4430                         break;
4432                 if (!strcmp(blame->commit->id, NULL_ID)) {
4433                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4434                         const char *diff_index_argv[] = {
4435                                 "git", "diff-index", "--root", "--patch-with-stat",
4436                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4437                         };
4439                         if (!blame->commit->has_previous) {
4440                                 diff_index_argv[1] = "diff";
4441                                 diff_index_argv[2] = "--no-color";
4442                                 diff_index_argv[6] = "--";
4443                                 diff_index_argv[7] = "/dev/null";
4444                         }
4446                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4447                                 report("Failed to allocate diff command");
4448                                 break;
4449                         }
4450                         flags |= OPEN_PREPARED;
4451                 }
4453                 open_view(view, REQ_VIEW_DIFF, flags);
4454                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4455                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4456                 break;
4458         default:
4459                 return request;
4460         }
4462         return REQ_NONE;
4465 static bool
4466 blame_grep(struct view *view, struct line *line)
4468         struct blame *blame = line->data;
4469         struct blame_commit *commit = blame->commit;
4470         regmatch_t pmatch;
4472 #define MATCH(text, on)                                                 \
4473         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4475         if (commit) {
4476                 char buf[DATE_COLS + 1];
4478                 if (MATCH(commit->title, 1) ||
4479                     MATCH(commit->author, opt_author) ||
4480                     MATCH(commit->id, opt_date))
4481                         return TRUE;
4483                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4484                     MATCH(buf, 1))
4485                         return TRUE;
4486         }
4488         return MATCH(blame->text, 1);
4490 #undef MATCH
4493 static void
4494 blame_select(struct view *view, struct line *line)
4496         struct blame *blame = line->data;
4497         struct blame_commit *commit = blame->commit;
4499         if (!commit)
4500                 return;
4502         if (!strcmp(commit->id, NULL_ID))
4503                 string_ncopy(ref_commit, "HEAD", 4);
4504         else
4505                 string_copy_rev(ref_commit, commit->id);
4508 static struct view_ops blame_ops = {
4509         "line",
4510         NULL,
4511         blame_open,
4512         blame_read,
4513         blame_draw,
4514         blame_request,
4515         blame_grep,
4516         blame_select,
4517 };
4519 /*
4520  * Status backend
4521  */
4523 struct status {
4524         char status;
4525         struct {
4526                 mode_t mode;
4527                 char rev[SIZEOF_REV];
4528                 char name[SIZEOF_STR];
4529         } old;
4530         struct {
4531                 mode_t mode;
4532                 char rev[SIZEOF_REV];
4533                 char name[SIZEOF_STR];
4534         } new;
4535 };
4537 static char status_onbranch[SIZEOF_STR];
4538 static struct status stage_status;
4539 static enum line_type stage_line_type;
4540 static size_t stage_chunks;
4541 static int *stage_chunk;
4543 /* This should work even for the "On branch" line. */
4544 static inline bool
4545 status_has_none(struct view *view, struct line *line)
4547         return line < view->line + view->lines && !line[1].data;
4550 /* Get fields from the diff line:
4551  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4552  */
4553 static inline bool
4554 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4556         const char *old_mode = buf +  1;
4557         const char *new_mode = buf +  8;
4558         const char *old_rev  = buf + 15;
4559         const char *new_rev  = buf + 56;
4560         const char *status   = buf + 97;
4562         if (bufsize < 98 ||
4563             old_mode[-1] != ':' ||
4564             new_mode[-1] != ' ' ||
4565             old_rev[-1]  != ' ' ||
4566             new_rev[-1]  != ' ' ||
4567             status[-1]   != ' ')
4568                 return FALSE;
4570         file->status = *status;
4572         string_copy_rev(file->old.rev, old_rev);
4573         string_copy_rev(file->new.rev, new_rev);
4575         file->old.mode = strtoul(old_mode, NULL, 8);
4576         file->new.mode = strtoul(new_mode, NULL, 8);
4578         file->old.name[0] = file->new.name[0] = 0;
4580         return TRUE;
4583 static bool
4584 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4586         struct status *file = NULL;
4587         struct status *unmerged = NULL;
4588         char *buf;
4589         struct io io = {};
4591         if (!run_io(&io, argv, NULL, IO_RD))
4592                 return FALSE;
4594         add_line_data(view, NULL, type);
4596         while ((buf = io_get(&io, 0, TRUE))) {
4597                 if (!file) {
4598                         file = calloc(1, sizeof(*file));
4599                         if (!file || !add_line_data(view, file, type))
4600                                 goto error_out;
4601                 }
4603                 /* Parse diff info part. */
4604                 if (status) {
4605                         file->status = status;
4606                         if (status == 'A')
4607                                 string_copy(file->old.rev, NULL_ID);
4609                 } else if (!file->status) {
4610                         if (!status_get_diff(file, buf, strlen(buf)))
4611                                 goto error_out;
4613                         buf = io_get(&io, 0, TRUE);
4614                         if (!buf)
4615                                 break;
4617                         /* Collapse all 'M'odified entries that follow a
4618                          * associated 'U'nmerged entry. */
4619                         if (file->status == 'U') {
4620                                 unmerged = file;
4622                         } else if (unmerged) {
4623                                 int collapse = !strcmp(buf, unmerged->new.name);
4625                                 unmerged = NULL;
4626                                 if (collapse) {
4627                                         free(file);
4628                                         file = NULL;
4629                                         view->lines--;
4630                                         continue;
4631                                 }
4632                         }
4633                 }
4635                 /* Grab the old name for rename/copy. */
4636                 if (!*file->old.name &&
4637                     (file->status == 'R' || file->status == 'C')) {
4638                         string_ncopy(file->old.name, buf, strlen(buf));
4640                         buf = io_get(&io, 0, TRUE);
4641                         if (!buf)
4642                                 break;
4643                 }
4645                 /* git-ls-files just delivers a NUL separated list of
4646                  * file names similar to the second half of the
4647                  * git-diff-* output. */
4648                 string_ncopy(file->new.name, buf, strlen(buf));
4649                 if (!*file->old.name)
4650                         string_copy(file->old.name, file->new.name);
4651                 file = NULL;
4652         }
4654         if (io_error(&io)) {
4655 error_out:
4656                 done_io(&io);
4657                 return FALSE;
4658         }
4660         if (!view->line[view->lines - 1].data)
4661                 add_line_data(view, NULL, LINE_STAT_NONE);
4663         done_io(&io);
4664         return TRUE;
4667 /* Don't show unmerged entries in the staged section. */
4668 static const char *status_diff_index_argv[] = {
4669         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4670                              "--cached", "-M", "HEAD", NULL
4671 };
4673 static const char *status_diff_files_argv[] = {
4674         "git", "diff-files", "-z", NULL
4675 };
4677 static const char *status_list_other_argv[] = {
4678         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4679 };
4681 static const char *status_list_no_head_argv[] = {
4682         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4683 };
4685 static const char *update_index_argv[] = {
4686         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4687 };
4689 /* Restore the previous line number to stay in the context or select a
4690  * line with something that can be updated. */
4691 static void
4692 status_restore(struct view *view)
4694         if (view->p_lineno >= view->lines)
4695                 view->p_lineno = view->lines - 1;
4696         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4697                 view->p_lineno++;
4698         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4699                 view->p_lineno--;
4701         /* If the above fails, always skip the "On branch" line. */
4702         if (view->p_lineno < view->lines)
4703                 view->lineno = view->p_lineno;
4704         else
4705                 view->lineno = 1;
4707         if (view->lineno < view->offset)
4708                 view->offset = view->lineno;
4709         else if (view->offset + view->height <= view->lineno)
4710                 view->offset = view->lineno - view->height + 1;
4712         view->p_restore = FALSE;
4715 /* First parse staged info using git-diff-index(1), then parse unstaged
4716  * info using git-diff-files(1), and finally untracked files using
4717  * git-ls-files(1). */
4718 static bool
4719 status_open(struct view *view)
4721         reset_view(view);
4723         add_line_data(view, NULL, LINE_STAT_HEAD);
4724         if (is_initial_commit())
4725                 string_copy(status_onbranch, "Initial commit");
4726         else if (!*opt_head)
4727                 string_copy(status_onbranch, "Not currently on any branch");
4728         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4729                 return FALSE;
4731         run_io_bg(update_index_argv);
4733         if (is_initial_commit()) {
4734                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4735                         return FALSE;
4736         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4737                 return FALSE;
4738         }
4740         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4741             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4742                 return FALSE;
4744         /* Restore the exact position or use the specialized restore
4745          * mode? */
4746         if (!view->p_restore)
4747                 status_restore(view);
4748         return TRUE;
4751 static bool
4752 status_draw(struct view *view, struct line *line, unsigned int lineno)
4754         struct status *status = line->data;
4755         enum line_type type;
4756         const char *text;
4758         if (!status) {
4759                 switch (line->type) {
4760                 case LINE_STAT_STAGED:
4761                         type = LINE_STAT_SECTION;
4762                         text = "Changes to be committed:";
4763                         break;
4765                 case LINE_STAT_UNSTAGED:
4766                         type = LINE_STAT_SECTION;
4767                         text = "Changed but not updated:";
4768                         break;
4770                 case LINE_STAT_UNTRACKED:
4771                         type = LINE_STAT_SECTION;
4772                         text = "Untracked files:";
4773                         break;
4775                 case LINE_STAT_NONE:
4776                         type = LINE_DEFAULT;
4777                         text = "    (no files)";
4778                         break;
4780                 case LINE_STAT_HEAD:
4781                         type = LINE_STAT_HEAD;
4782                         text = status_onbranch;
4783                         break;
4785                 default:
4786                         return FALSE;
4787                 }
4788         } else {
4789                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4791                 buf[0] = status->status;
4792                 if (draw_text(view, line->type, buf, TRUE))
4793                         return TRUE;
4794                 type = LINE_DEFAULT;
4795                 text = status->new.name;
4796         }
4798         draw_text(view, type, text, TRUE);
4799         return TRUE;
4802 static enum request
4803 status_enter(struct view *view, struct line *line)
4805         struct status *status = line->data;
4806         const char *oldpath = status ? status->old.name : NULL;
4807         /* Diffs for unmerged entries are empty when passing the new
4808          * path, so leave it empty. */
4809         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4810         const char *info;
4811         enum open_flags split;
4812         struct view *stage = VIEW(REQ_VIEW_STAGE);
4814         if (line->type == LINE_STAT_NONE ||
4815             (!status && line[1].type == LINE_STAT_NONE)) {
4816                 report("No file to diff");
4817                 return REQ_NONE;
4818         }
4820         switch (line->type) {
4821         case LINE_STAT_STAGED:
4822                 if (is_initial_commit()) {
4823                         const char *no_head_diff_argv[] = {
4824                                 "git", "diff", "--no-color", "--patch-with-stat",
4825                                         "--", "/dev/null", newpath, NULL
4826                         };
4828                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4829                                 return REQ_QUIT;
4830                 } else {
4831                         const char *index_show_argv[] = {
4832                                 "git", "diff-index", "--root", "--patch-with-stat",
4833                                         "-C", "-M", "--cached", "HEAD", "--",
4834                                         oldpath, newpath, NULL
4835                         };
4837                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4838                                 return REQ_QUIT;
4839                 }
4841                 if (status)
4842                         info = "Staged changes to %s";
4843                 else
4844                         info = "Staged changes";
4845                 break;
4847         case LINE_STAT_UNSTAGED:
4848         {
4849                 const char *files_show_argv[] = {
4850                         "git", "diff-files", "--root", "--patch-with-stat",
4851                                 "-C", "-M", "--", oldpath, newpath, NULL
4852                 };
4854                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4855                         return REQ_QUIT;
4856                 if (status)
4857                         info = "Unstaged changes to %s";
4858                 else
4859                         info = "Unstaged changes";
4860                 break;
4861         }
4862         case LINE_STAT_UNTRACKED:
4863                 if (!newpath) {
4864                         report("No file to show");
4865                         return REQ_NONE;
4866                 }
4868                 if (!suffixcmp(status->new.name, -1, "/")) {
4869                         report("Cannot display a directory");
4870                         return REQ_NONE;
4871                 }
4873                 if (!prepare_update_file(stage, newpath))
4874                         return REQ_QUIT;
4875                 info = "Untracked file %s";
4876                 break;
4878         case LINE_STAT_HEAD:
4879                 return REQ_NONE;
4881         default:
4882                 die("line type %d not handled in switch", line->type);
4883         }
4885         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4886         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4887         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4888                 if (status) {
4889                         stage_status = *status;
4890                 } else {
4891                         memset(&stage_status, 0, sizeof(stage_status));
4892                 }
4894                 stage_line_type = line->type;
4895                 stage_chunks = 0;
4896                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4897         }
4899         return REQ_NONE;
4902 static bool
4903 status_exists(struct status *status, enum line_type type)
4905         struct view *view = VIEW(REQ_VIEW_STATUS);
4906         unsigned long lineno;
4908         for (lineno = 0; lineno < view->lines; lineno++) {
4909                 struct line *line = &view->line[lineno];
4910                 struct status *pos = line->data;
4912                 if (line->type != type)
4913                         continue;
4914                 if (!pos && (!status || !status->status) && line[1].data) {
4915                         select_view_line(view, lineno);
4916                         return TRUE;
4917                 }
4918                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4919                         select_view_line(view, lineno);
4920                         return TRUE;
4921                 }
4922         }
4924         return FALSE;
4928 static bool
4929 status_update_prepare(struct io *io, enum line_type type)
4931         const char *staged_argv[] = {
4932                 "git", "update-index", "-z", "--index-info", NULL
4933         };
4934         const char *others_argv[] = {
4935                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4936         };
4938         switch (type) {
4939         case LINE_STAT_STAGED:
4940                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4942         case LINE_STAT_UNSTAGED:
4943                 return run_io(io, others_argv, opt_cdup, IO_WR);
4945         case LINE_STAT_UNTRACKED:
4946                 return run_io(io, others_argv, NULL, IO_WR);
4948         default:
4949                 die("line type %d not handled in switch", type);
4950                 return FALSE;
4951         }
4954 static bool
4955 status_update_write(struct io *io, struct status *status, enum line_type type)
4957         char buf[SIZEOF_STR];
4958         size_t bufsize = 0;
4960         switch (type) {
4961         case LINE_STAT_STAGED:
4962                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4963                                         status->old.mode,
4964                                         status->old.rev,
4965                                         status->old.name, 0))
4966                         return FALSE;
4967                 break;
4969         case LINE_STAT_UNSTAGED:
4970         case LINE_STAT_UNTRACKED:
4971                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4972                         return FALSE;
4973                 break;
4975         default:
4976                 die("line type %d not handled in switch", type);
4977         }
4979         return io_write(io, buf, bufsize);
4982 static bool
4983 status_update_file(struct status *status, enum line_type type)
4985         struct io io = {};
4986         bool result;
4988         if (!status_update_prepare(&io, type))
4989                 return FALSE;
4991         result = status_update_write(&io, status, type);
4992         done_io(&io);
4993         return result;
4996 static bool
4997 status_update_files(struct view *view, struct line *line)
4999         struct io io = {};
5000         bool result = TRUE;
5001         struct line *pos = view->line + view->lines;
5002         int files = 0;
5003         int file, done;
5005         if (!status_update_prepare(&io, line->type))
5006                 return FALSE;
5008         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5009                 files++;
5011         for (file = 0, done = 0; result && file < files; line++, file++) {
5012                 int almost_done = file * 100 / files;
5014                 if (almost_done > done) {
5015                         done = almost_done;
5016                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5017                                       file, files, done);
5018                         update_view_title(view);
5019                 }
5020                 result = status_update_write(&io, line->data, line->type);
5021         }
5023         done_io(&io);
5024         return result;
5027 static bool
5028 status_update(struct view *view)
5030         struct line *line = &view->line[view->lineno];
5032         assert(view->lines);
5034         if (!line->data) {
5035                 /* This should work even for the "On branch" line. */
5036                 if (line < view->line + view->lines && !line[1].data) {
5037                         report("Nothing to update");
5038                         return FALSE;
5039                 }
5041                 if (!status_update_files(view, line + 1)) {
5042                         report("Failed to update file status");
5043                         return FALSE;
5044                 }
5046         } else if (!status_update_file(line->data, line->type)) {
5047                 report("Failed to update file status");
5048                 return FALSE;
5049         }
5051         return TRUE;
5054 static bool
5055 status_revert(struct status *status, enum line_type type, bool has_none)
5057         if (!status || type != LINE_STAT_UNSTAGED) {
5058                 if (type == LINE_STAT_STAGED) {
5059                         report("Cannot revert changes to staged files");
5060                 } else if (type == LINE_STAT_UNTRACKED) {
5061                         report("Cannot revert changes to untracked files");
5062                 } else if (has_none) {
5063                         report("Nothing to revert");
5064                 } else {
5065                         report("Cannot revert changes to multiple files");
5066                 }
5067                 return FALSE;
5069         } else {
5070                 const char *checkout_argv[] = {
5071                         "git", "checkout", "--", status->old.name, NULL
5072                 };
5074                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5075                         return FALSE;
5076                 return run_io_fg(checkout_argv, opt_cdup);
5077         }
5080 static enum request
5081 status_request(struct view *view, enum request request, struct line *line)
5083         struct status *status = line->data;
5085         switch (request) {
5086         case REQ_STATUS_UPDATE:
5087                 if (!status_update(view))
5088                         return REQ_NONE;
5089                 break;
5091         case REQ_STATUS_REVERT:
5092                 if (!status_revert(status, line->type, status_has_none(view, line)))
5093                         return REQ_NONE;
5094                 break;
5096         case REQ_STATUS_MERGE:
5097                 if (!status || status->status != 'U') {
5098                         report("Merging only possible for files with unmerged status ('U').");
5099                         return REQ_NONE;
5100                 }
5101                 open_mergetool(status->new.name);
5102                 break;
5104         case REQ_EDIT:
5105                 if (!status)
5106                         return request;
5107                 if (status->status == 'D') {
5108                         report("File has been deleted.");
5109                         return REQ_NONE;
5110                 }
5112                 open_editor(status->status != '?', status->new.name);
5113                 break;
5115         case REQ_VIEW_BLAME:
5116                 if (status) {
5117                         string_copy(opt_file, status->new.name);
5118                         opt_ref[0] = 0;
5119                 }
5120                 return request;
5122         case REQ_ENTER:
5123                 /* After returning the status view has been split to
5124                  * show the stage view. No further reloading is
5125                  * necessary. */
5126                 status_enter(view, line);
5127                 return REQ_NONE;
5129         case REQ_REFRESH:
5130                 /* Simply reload the view. */
5131                 break;
5133         default:
5134                 return request;
5135         }
5137         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5139         return REQ_NONE;
5142 static void
5143 status_select(struct view *view, struct line *line)
5145         struct status *status = line->data;
5146         char file[SIZEOF_STR] = "all files";
5147         const char *text;
5148         const char *key;
5150         if (status && !string_format(file, "'%s'", status->new.name))
5151                 return;
5153         if (!status && line[1].type == LINE_STAT_NONE)
5154                 line++;
5156         switch (line->type) {
5157         case LINE_STAT_STAGED:
5158                 text = "Press %s to unstage %s for commit";
5159                 break;
5161         case LINE_STAT_UNSTAGED:
5162                 text = "Press %s to stage %s for commit";
5163                 break;
5165         case LINE_STAT_UNTRACKED:
5166                 text = "Press %s to stage %s for addition";
5167                 break;
5169         case LINE_STAT_HEAD:
5170         case LINE_STAT_NONE:
5171                 text = "Nothing to update";
5172                 break;
5174         default:
5175                 die("line type %d not handled in switch", line->type);
5176         }
5178         if (status && status->status == 'U') {
5179                 text = "Press %s to resolve conflict in %s";
5180                 key = get_key(REQ_STATUS_MERGE);
5182         } else {
5183                 key = get_key(REQ_STATUS_UPDATE);
5184         }
5186         string_format(view->ref, text, key, file);
5189 static bool
5190 status_grep(struct view *view, struct line *line)
5192         struct status *status = line->data;
5193         enum { S_STATUS, S_NAME, S_END } state;
5194         char buf[2] = "?";
5195         regmatch_t pmatch;
5197         if (!status)
5198                 return FALSE;
5200         for (state = S_STATUS; state < S_END; state++) {
5201                 const char *text;
5203                 switch (state) {
5204                 case S_NAME:    text = status->new.name;        break;
5205                 case S_STATUS:
5206                         buf[0] = status->status;
5207                         text = buf;
5208                         break;
5210                 default:
5211                         return FALSE;
5212                 }
5214                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5215                         return TRUE;
5216         }
5218         return FALSE;
5221 static struct view_ops status_ops = {
5222         "file",
5223         NULL,
5224         status_open,
5225         NULL,
5226         status_draw,
5227         status_request,
5228         status_grep,
5229         status_select,
5230 };
5233 static bool
5234 stage_diff_write(struct io *io, struct line *line, struct line *end)
5236         while (line < end) {
5237                 if (!io_write(io, line->data, strlen(line->data)) ||
5238                     !io_write(io, "\n", 1))
5239                         return FALSE;
5240                 line++;
5241                 if (line->type == LINE_DIFF_CHUNK ||
5242                     line->type == LINE_DIFF_HEADER)
5243                         break;
5244         }
5246         return TRUE;
5249 static struct line *
5250 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5252         for (; view->line < line; line--)
5253                 if (line->type == type)
5254                         return line;
5256         return NULL;
5259 static bool
5260 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5262         const char *apply_argv[SIZEOF_ARG] = {
5263                 "git", "apply", "--whitespace=nowarn", NULL
5264         };
5265         struct line *diff_hdr;
5266         struct io io = {};
5267         int argc = 3;
5269         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5270         if (!diff_hdr)
5271                 return FALSE;
5273         if (!revert)
5274                 apply_argv[argc++] = "--cached";
5275         if (revert || stage_line_type == LINE_STAT_STAGED)
5276                 apply_argv[argc++] = "-R";
5277         apply_argv[argc++] = "-";
5278         apply_argv[argc++] = NULL;
5279         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5280                 return FALSE;
5282         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5283             !stage_diff_write(&io, chunk, view->line + view->lines))
5284                 chunk = NULL;
5286         done_io(&io);
5287         run_io_bg(update_index_argv);
5289         return chunk ? TRUE : FALSE;
5292 static bool
5293 stage_update(struct view *view, struct line *line)
5295         struct line *chunk = NULL;
5297         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5298                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5300         if (chunk) {
5301                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5302                         report("Failed to apply chunk");
5303                         return FALSE;
5304                 }
5306         } else if (!stage_status.status) {
5307                 view = VIEW(REQ_VIEW_STATUS);
5309                 for (line = view->line; line < view->line + view->lines; line++)
5310                         if (line->type == stage_line_type)
5311                                 break;
5313                 if (!status_update_files(view, line + 1)) {
5314                         report("Failed to update files");
5315                         return FALSE;
5316                 }
5318         } else if (!status_update_file(&stage_status, stage_line_type)) {
5319                 report("Failed to update file");
5320                 return FALSE;
5321         }
5323         return TRUE;
5326 static bool
5327 stage_revert(struct view *view, struct line *line)
5329         struct line *chunk = NULL;
5331         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5332                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5334         if (chunk) {
5335                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5336                         return FALSE;
5338                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5339                         report("Failed to revert chunk");
5340                         return FALSE;
5341                 }
5342                 return TRUE;
5344         } else {
5345                 return status_revert(stage_status.status ? &stage_status : NULL,
5346                                      stage_line_type, FALSE);
5347         }
5351 static void
5352 stage_next(struct view *view, struct line *line)
5354         int i;
5356         if (!stage_chunks) {
5357                 static size_t alloc = 0;
5358                 int *tmp;
5360                 for (line = view->line; line < view->line + view->lines; line++) {
5361                         if (line->type != LINE_DIFF_CHUNK)
5362                                 continue;
5364                         tmp = realloc_items(stage_chunk, &alloc,
5365                                             stage_chunks, sizeof(*tmp));
5366                         if (!tmp) {
5367                                 report("Allocation failure");
5368                                 return;
5369                         }
5371                         stage_chunk = tmp;
5372                         stage_chunk[stage_chunks++] = line - view->line;
5373                 }
5374         }
5376         for (i = 0; i < stage_chunks; i++) {
5377                 if (stage_chunk[i] > view->lineno) {
5378                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5379                         report("Chunk %d of %d", i + 1, stage_chunks);
5380                         return;
5381                 }
5382         }
5384         report("No next chunk found");
5387 static enum request
5388 stage_request(struct view *view, enum request request, struct line *line)
5390         switch (request) {
5391         case REQ_STATUS_UPDATE:
5392                 if (!stage_update(view, line))
5393                         return REQ_NONE;
5394                 break;
5396         case REQ_STATUS_REVERT:
5397                 if (!stage_revert(view, line))
5398                         return REQ_NONE;
5399                 break;
5401         case REQ_STAGE_NEXT:
5402                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5403                         report("File is untracked; press %s to add",
5404                                get_key(REQ_STATUS_UPDATE));
5405                         return REQ_NONE;
5406                 }
5407                 stage_next(view, line);
5408                 return REQ_NONE;
5410         case REQ_EDIT:
5411                 if (!stage_status.new.name[0])
5412                         return request;
5413                 if (stage_status.status == 'D') {
5414                         report("File has been deleted.");
5415                         return REQ_NONE;
5416                 }
5418                 open_editor(stage_status.status != '?', stage_status.new.name);
5419                 break;
5421         case REQ_REFRESH:
5422                 /* Reload everything ... */
5423                 break;
5425         case REQ_VIEW_BLAME:
5426                 if (stage_status.new.name[0]) {
5427                         string_copy(opt_file, stage_status.new.name);
5428                         opt_ref[0] = 0;
5429                 }
5430                 return request;
5432         case REQ_ENTER:
5433                 return pager_request(view, request, line);
5435         default:
5436                 return request;
5437         }
5439         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5440         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5442         /* Check whether the staged entry still exists, and close the
5443          * stage view if it doesn't. */
5444         if (!status_exists(&stage_status, stage_line_type)) {
5445                 status_restore(VIEW(REQ_VIEW_STATUS));
5446                 return REQ_VIEW_CLOSE;
5447         }
5449         if (stage_line_type == LINE_STAT_UNTRACKED) {
5450                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5451                         report("Cannot display a directory");
5452                         return REQ_NONE;
5453                 }
5455                 if (!prepare_update_file(view, stage_status.new.name)) {
5456                         report("Failed to open file: %s", strerror(errno));
5457                         return REQ_NONE;
5458                 }
5459         }
5460         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5462         return REQ_NONE;
5465 static struct view_ops stage_ops = {
5466         "line",
5467         NULL,
5468         NULL,
5469         pager_read,
5470         pager_draw,
5471         stage_request,
5472         pager_grep,
5473         pager_select,
5474 };
5477 /*
5478  * Revision graph
5479  */
5481 struct commit {
5482         char id[SIZEOF_REV];            /* SHA1 ID. */
5483         char title[128];                /* First line of the commit message. */
5484         char author[75];                /* Author of the commit. */
5485         struct tm time;                 /* Date from the author ident. */
5486         struct ref **refs;              /* Repository references. */
5487         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5488         size_t graph_size;              /* The width of the graph array. */
5489         bool has_parents;               /* Rewritten --parents seen. */
5490 };
5492 /* Size of rev graph with no  "padding" columns */
5493 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5495 struct rev_graph {
5496         struct rev_graph *prev, *next, *parents;
5497         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5498         size_t size;
5499         struct commit *commit;
5500         size_t pos;
5501         unsigned int boundary:1;
5502 };
5504 /* Parents of the commit being visualized. */
5505 static struct rev_graph graph_parents[4];
5507 /* The current stack of revisions on the graph. */
5508 static struct rev_graph graph_stacks[4] = {
5509         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5510         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5511         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5512         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5513 };
5515 static inline bool
5516 graph_parent_is_merge(struct rev_graph *graph)
5518         return graph->parents->size > 1;
5521 static inline void
5522 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5524         struct commit *commit = graph->commit;
5526         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5527                 commit->graph[commit->graph_size++] = symbol;
5530 static void
5531 clear_rev_graph(struct rev_graph *graph)
5533         graph->boundary = 0;
5534         graph->size = graph->pos = 0;
5535         graph->commit = NULL;
5536         memset(graph->parents, 0, sizeof(*graph->parents));
5539 static void
5540 done_rev_graph(struct rev_graph *graph)
5542         if (graph_parent_is_merge(graph) &&
5543             graph->pos < graph->size - 1 &&
5544             graph->next->size == graph->size + graph->parents->size - 1) {
5545                 size_t i = graph->pos + graph->parents->size - 1;
5547                 graph->commit->graph_size = i * 2;
5548                 while (i < graph->next->size - 1) {
5549                         append_to_rev_graph(graph, ' ');
5550                         append_to_rev_graph(graph, '\\');
5551                         i++;
5552                 }
5553         }
5555         clear_rev_graph(graph);
5558 static void
5559 push_rev_graph(struct rev_graph *graph, const char *parent)
5561         int i;
5563         /* "Collapse" duplicate parents lines.
5564          *
5565          * FIXME: This needs to also update update the drawn graph but
5566          * for now it just serves as a method for pruning graph lines. */
5567         for (i = 0; i < graph->size; i++)
5568                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5569                         return;
5571         if (graph->size < SIZEOF_REVITEMS) {
5572                 string_copy_rev(graph->rev[graph->size++], parent);
5573         }
5576 static chtype
5577 get_rev_graph_symbol(struct rev_graph *graph)
5579         chtype symbol;
5581         if (graph->boundary)
5582                 symbol = REVGRAPH_BOUND;
5583         else if (graph->parents->size == 0)
5584                 symbol = REVGRAPH_INIT;
5585         else if (graph_parent_is_merge(graph))
5586                 symbol = REVGRAPH_MERGE;
5587         else if (graph->pos >= graph->size)
5588                 symbol = REVGRAPH_BRANCH;
5589         else
5590                 symbol = REVGRAPH_COMMIT;
5592         return symbol;
5595 static void
5596 draw_rev_graph(struct rev_graph *graph)
5598         struct rev_filler {
5599                 chtype separator, line;
5600         };
5601         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5602         static struct rev_filler fillers[] = {
5603                 { ' ',  '|' },
5604                 { '`',  '.' },
5605                 { '\'', ' ' },
5606                 { '/',  ' ' },
5607         };
5608         chtype symbol = get_rev_graph_symbol(graph);
5609         struct rev_filler *filler;
5610         size_t i;
5612         if (opt_line_graphics)
5613                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5615         filler = &fillers[DEFAULT];
5617         for (i = 0; i < graph->pos; i++) {
5618                 append_to_rev_graph(graph, filler->line);
5619                 if (graph_parent_is_merge(graph->prev) &&
5620                     graph->prev->pos == i)
5621                         filler = &fillers[RSHARP];
5623                 append_to_rev_graph(graph, filler->separator);
5624         }
5626         /* Place the symbol for this revision. */
5627         append_to_rev_graph(graph, symbol);
5629         if (graph->prev->size > graph->size)
5630                 filler = &fillers[RDIAG];
5631         else
5632                 filler = &fillers[DEFAULT];
5634         i++;
5636         for (; i < graph->size; i++) {
5637                 append_to_rev_graph(graph, filler->separator);
5638                 append_to_rev_graph(graph, filler->line);
5639                 if (graph_parent_is_merge(graph->prev) &&
5640                     i < graph->prev->pos + graph->parents->size)
5641                         filler = &fillers[RSHARP];
5642                 if (graph->prev->size > graph->size)
5643                         filler = &fillers[LDIAG];
5644         }
5646         if (graph->prev->size > graph->size) {
5647                 append_to_rev_graph(graph, filler->separator);
5648                 if (filler->line != ' ')
5649                         append_to_rev_graph(graph, filler->line);
5650         }
5653 /* Prepare the next rev graph */
5654 static void
5655 prepare_rev_graph(struct rev_graph *graph)
5657         size_t i;
5659         /* First, traverse all lines of revisions up to the active one. */
5660         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5661                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5662                         break;
5664                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5665         }
5667         /* Interleave the new revision parent(s). */
5668         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5669                 push_rev_graph(graph->next, graph->parents->rev[i]);
5671         /* Lastly, put any remaining revisions. */
5672         for (i = graph->pos + 1; i < graph->size; i++)
5673                 push_rev_graph(graph->next, graph->rev[i]);
5676 static void
5677 update_rev_graph(struct view *view, struct rev_graph *graph)
5679         /* If this is the finalizing update ... */
5680         if (graph->commit)
5681                 prepare_rev_graph(graph);
5683         /* Graph visualization needs a one rev look-ahead,
5684          * so the first update doesn't visualize anything. */
5685         if (!graph->prev->commit)
5686                 return;
5688         if (view->lines > 2)
5689                 view->line[view->lines - 3].dirty = 1;
5690         if (view->lines > 1)
5691                 view->line[view->lines - 2].dirty = 1;
5692         draw_rev_graph(graph->prev);
5693         done_rev_graph(graph->prev->prev);
5697 /*
5698  * Main view backend
5699  */
5701 static const char *main_argv[SIZEOF_ARG] = {
5702         "git", "log", "--no-color", "--pretty=raw", "--parents",
5703                       "--topo-order", "%(head)", NULL
5704 };
5706 static bool
5707 main_draw(struct view *view, struct line *line, unsigned int lineno)
5709         struct commit *commit = line->data;
5711         if (!*commit->author)
5712                 return FALSE;
5714         if (opt_date && draw_date(view, &commit->time))
5715                 return TRUE;
5717         if (opt_author &&
5718             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5719                 return TRUE;
5721         if (opt_rev_graph && commit->graph_size &&
5722             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5723                 return TRUE;
5725         if (opt_show_refs && commit->refs) {
5726                 size_t i = 0;
5728                 do {
5729                         enum line_type type;
5731                         if (commit->refs[i]->head)
5732                                 type = LINE_MAIN_HEAD;
5733                         else if (commit->refs[i]->ltag)
5734                                 type = LINE_MAIN_LOCAL_TAG;
5735                         else if (commit->refs[i]->tag)
5736                                 type = LINE_MAIN_TAG;
5737                         else if (commit->refs[i]->tracked)
5738                                 type = LINE_MAIN_TRACKED;
5739                         else if (commit->refs[i]->remote)
5740                                 type = LINE_MAIN_REMOTE;
5741                         else
5742                                 type = LINE_MAIN_REF;
5744                         if (draw_text(view, type, "[", TRUE) ||
5745                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5746                             draw_text(view, type, "]", TRUE))
5747                                 return TRUE;
5749                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5750                                 return TRUE;
5751                 } while (commit->refs[i++]->next);
5752         }
5754         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5755         return TRUE;
5758 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5759 static bool
5760 main_read(struct view *view, char *line)
5762         static struct rev_graph *graph = graph_stacks;
5763         enum line_type type;
5764         struct commit *commit;
5766         if (!line) {
5767                 int i;
5769                 if (!view->lines && !view->parent)
5770                         die("No revisions match the given arguments.");
5771                 if (view->lines > 0) {
5772                         commit = view->line[view->lines - 1].data;
5773                         view->line[view->lines - 1].dirty = 1;
5774                         if (!*commit->author) {
5775                                 view->lines--;
5776                                 free(commit);
5777                                 graph->commit = NULL;
5778                         }
5779                 }
5780                 update_rev_graph(view, graph);
5782                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5783                         clear_rev_graph(&graph_stacks[i]);
5784                 return TRUE;
5785         }
5787         type = get_line_type(line);
5788         if (type == LINE_COMMIT) {
5789                 commit = calloc(1, sizeof(struct commit));
5790                 if (!commit)
5791                         return FALSE;
5793                 line += STRING_SIZE("commit ");
5794                 if (*line == '-') {
5795                         graph->boundary = 1;
5796                         line++;
5797                 }
5799                 string_copy_rev(commit->id, line);
5800                 commit->refs = get_refs(commit->id);
5801                 graph->commit = commit;
5802                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5804                 while ((line = strchr(line, ' '))) {
5805                         line++;
5806                         push_rev_graph(graph->parents, line);
5807                         commit->has_parents = TRUE;
5808                 }
5809                 return TRUE;
5810         }
5812         if (!view->lines)
5813                 return TRUE;
5814         commit = view->line[view->lines - 1].data;
5816         switch (type) {
5817         case LINE_PARENT:
5818                 if (commit->has_parents)
5819                         break;
5820                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5821                 break;
5823         case LINE_AUTHOR:
5824                 parse_author_line(line + STRING_SIZE("author "),
5825                                   commit->author, sizeof(commit->author),
5826                                   &commit->time);
5827                 update_rev_graph(view, graph);
5828                 graph = graph->next;
5829                 break;
5831         default:
5832                 /* Fill in the commit title if it has not already been set. */
5833                 if (commit->title[0])
5834                         break;
5836                 /* Require titles to start with a non-space character at the
5837                  * offset used by git log. */
5838                 if (strncmp(line, "    ", 4))
5839                         break;
5840                 line += 4;
5841                 /* Well, if the title starts with a whitespace character,
5842                  * try to be forgiving.  Otherwise we end up with no title. */
5843                 while (isspace(*line))
5844                         line++;
5845                 if (*line == '\0')
5846                         break;
5847                 /* FIXME: More graceful handling of titles; append "..." to
5848                  * shortened titles, etc. */
5850                 string_ncopy(commit->title, line, strlen(line));
5851                 view->line[view->lines - 1].dirty = 1;
5852         }
5854         return TRUE;
5857 static enum request
5858 main_request(struct view *view, enum request request, struct line *line)
5860         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5862         switch (request) {
5863         case REQ_ENTER:
5864                 open_view(view, REQ_VIEW_DIFF, flags);
5865                 break;
5866         case REQ_REFRESH:
5867                 load_refs();
5868                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5869                 break;
5870         default:
5871                 return request;
5872         }
5874         return REQ_NONE;
5877 static bool
5878 grep_refs(struct ref **refs, regex_t *regex)
5880         regmatch_t pmatch;
5881         size_t i = 0;
5883         if (!refs)
5884                 return FALSE;
5885         do {
5886                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5887                         return TRUE;
5888         } while (refs[i++]->next);
5890         return FALSE;
5893 static bool
5894 main_grep(struct view *view, struct line *line)
5896         struct commit *commit = line->data;
5897         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5898         char buf[DATE_COLS + 1];
5899         regmatch_t pmatch;
5901         for (state = S_TITLE; state < S_END; state++) {
5902                 char *text;
5904                 switch (state) {
5905                 case S_TITLE:   text = commit->title;   break;
5906                 case S_AUTHOR:
5907                         if (!opt_author)
5908                                 continue;
5909                         text = commit->author;
5910                         break;
5911                 case S_DATE:
5912                         if (!opt_date)
5913                                 continue;
5914                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5915                                 continue;
5916                         text = buf;
5917                         break;
5918                 case S_REFS:
5919                         if (!opt_show_refs)
5920                                 continue;
5921                         if (grep_refs(commit->refs, view->regex) == TRUE)
5922                                 return TRUE;
5923                         continue;
5924                 default:
5925                         return FALSE;
5926                 }
5928                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5929                         return TRUE;
5930         }
5932         return FALSE;
5935 static void
5936 main_select(struct view *view, struct line *line)
5938         struct commit *commit = line->data;
5940         string_copy_rev(view->ref, commit->id);
5941         string_copy_rev(ref_commit, view->ref);
5944 static struct view_ops main_ops = {
5945         "commit",
5946         main_argv,
5947         NULL,
5948         main_read,
5949         main_draw,
5950         main_request,
5951         main_grep,
5952         main_select,
5953 };
5956 /*
5957  * Unicode / UTF-8 handling
5958  *
5959  * NOTE: Much of the following code for dealing with unicode is derived from
5960  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5961  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5962  */
5964 /* I've (over)annotated a lot of code snippets because I am not entirely
5965  * confident that the approach taken by this small UTF-8 interface is correct.
5966  * --jonas */
5968 static inline int
5969 unicode_width(unsigned long c)
5971         if (c >= 0x1100 &&
5972            (c <= 0x115f                         /* Hangul Jamo */
5973             || c == 0x2329
5974             || c == 0x232a
5975             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5976                                                 /* CJK ... Yi */
5977             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5978             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5979             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5980             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5981             || (c >= 0xffe0  && c <= 0xffe6)
5982             || (c >= 0x20000 && c <= 0x2fffd)
5983             || (c >= 0x30000 && c <= 0x3fffd)))
5984                 return 2;
5986         if (c == '\t')
5987                 return opt_tab_size;
5989         return 1;
5992 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5993  * Illegal bytes are set one. */
5994 static const unsigned char utf8_bytes[256] = {
5995         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5996         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5997         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5998         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         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,
6002         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,
6003 };
6005 /* Decode UTF-8 multi-byte representation into a unicode character. */
6006 static inline unsigned long
6007 utf8_to_unicode(const char *string, size_t length)
6009         unsigned long unicode;
6011         switch (length) {
6012         case 1:
6013                 unicode  =   string[0];
6014                 break;
6015         case 2:
6016                 unicode  =  (string[0] & 0x1f) << 6;
6017                 unicode +=  (string[1] & 0x3f);
6018                 break;
6019         case 3:
6020                 unicode  =  (string[0] & 0x0f) << 12;
6021                 unicode += ((string[1] & 0x3f) << 6);
6022                 unicode +=  (string[2] & 0x3f);
6023                 break;
6024         case 4:
6025                 unicode  =  (string[0] & 0x0f) << 18;
6026                 unicode += ((string[1] & 0x3f) << 12);
6027                 unicode += ((string[2] & 0x3f) << 6);
6028                 unicode +=  (string[3] & 0x3f);
6029                 break;
6030         case 5:
6031                 unicode  =  (string[0] & 0x0f) << 24;
6032                 unicode += ((string[1] & 0x3f) << 18);
6033                 unicode += ((string[2] & 0x3f) << 12);
6034                 unicode += ((string[3] & 0x3f) << 6);
6035                 unicode +=  (string[4] & 0x3f);
6036                 break;
6037         case 6:
6038                 unicode  =  (string[0] & 0x01) << 30;
6039                 unicode += ((string[1] & 0x3f) << 24);
6040                 unicode += ((string[2] & 0x3f) << 18);
6041                 unicode += ((string[3] & 0x3f) << 12);
6042                 unicode += ((string[4] & 0x3f) << 6);
6043                 unicode +=  (string[5] & 0x3f);
6044                 break;
6045         default:
6046                 die("Invalid unicode length");
6047         }
6049         /* Invalid characters could return the special 0xfffd value but NUL
6050          * should be just as good. */
6051         return unicode > 0xffff ? 0 : unicode;
6054 /* Calculates how much of string can be shown within the given maximum width
6055  * and sets trimmed parameter to non-zero value if all of string could not be
6056  * shown. If the reserve flag is TRUE, it will reserve at least one
6057  * trailing character, which can be useful when drawing a delimiter.
6058  *
6059  * Returns the number of bytes to output from string to satisfy max_width. */
6060 static size_t
6061 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6063         const char *start = string;
6064         const char *end = strchr(string, '\0');
6065         unsigned char last_bytes = 0;
6066         size_t last_ucwidth = 0;
6068         *width = 0;
6069         *trimmed = 0;
6071         while (string < end) {
6072                 int c = *(unsigned char *) string;
6073                 unsigned char bytes = utf8_bytes[c];
6074                 size_t ucwidth;
6075                 unsigned long unicode;
6077                 if (string + bytes > end)
6078                         break;
6080                 /* Change representation to figure out whether
6081                  * it is a single- or double-width character. */
6083                 unicode = utf8_to_unicode(string, bytes);
6084                 /* FIXME: Graceful handling of invalid unicode character. */
6085                 if (!unicode)
6086                         break;
6088                 ucwidth = unicode_width(unicode);
6089                 *width  += ucwidth;
6090                 if (*width > max_width) {
6091                         *trimmed = 1;
6092                         *width -= ucwidth;
6093                         if (reserve && *width == max_width) {
6094                                 string -= last_bytes;
6095                                 *width -= last_ucwidth;
6096                         }
6097                         break;
6098                 }
6100                 string  += bytes;
6101                 last_bytes = bytes;
6102                 last_ucwidth = ucwidth;
6103         }
6105         return string - start;
6109 /*
6110  * Status management
6111  */
6113 /* Whether or not the curses interface has been initialized. */
6114 static bool cursed = FALSE;
6116 /* The status window is used for polling keystrokes. */
6117 static WINDOW *status_win;
6119 static bool status_empty = FALSE;
6121 /* Update status and title window. */
6122 static void
6123 report(const char *msg, ...)
6125         struct view *view = display[current_view];
6127         if (input_mode)
6128                 return;
6130         if (!view) {
6131                 char buf[SIZEOF_STR];
6132                 va_list args;
6134                 va_start(args, msg);
6135                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6136                         buf[sizeof(buf) - 1] = 0;
6137                         buf[sizeof(buf) - 2] = '.';
6138                         buf[sizeof(buf) - 3] = '.';
6139                         buf[sizeof(buf) - 4] = '.';
6140                 }
6141                 va_end(args);
6142                 die("%s", buf);
6143         }
6145         if (!status_empty || *msg) {
6146                 va_list args;
6148                 va_start(args, msg);
6150                 wmove(status_win, 0, 0);
6151                 if (*msg) {
6152                         vwprintw(status_win, msg, args);
6153                         status_empty = FALSE;
6154                 } else {
6155                         status_empty = TRUE;
6156                 }
6157                 wclrtoeol(status_win);
6158                 wrefresh(status_win);
6160                 va_end(args);
6161         }
6163         update_view_title(view);
6164         update_display_cursor(view);
6167 /* Controls when nodelay should be in effect when polling user input. */
6168 static void
6169 set_nonblocking_input(bool loading)
6171         static unsigned int loading_views;
6173         if ((loading == FALSE && loading_views-- == 1) ||
6174             (loading == TRUE  && loading_views++ == 0))
6175                 nodelay(status_win, loading);
6178 static void
6179 init_display(void)
6181         int x, y;
6183         /* Initialize the curses library */
6184         if (isatty(STDIN_FILENO)) {
6185                 cursed = !!initscr();
6186                 opt_tty = stdin;
6187         } else {
6188                 /* Leave stdin and stdout alone when acting as a pager. */
6189                 opt_tty = fopen("/dev/tty", "r+");
6190                 if (!opt_tty)
6191                         die("Failed to open /dev/tty");
6192                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6193         }
6195         if (!cursed)
6196                 die("Failed to initialize curses");
6198         nonl();         /* Tell curses not to do NL->CR/NL on output */
6199         cbreak();       /* Take input chars one at a time, no wait for \n */
6200         noecho();       /* Don't echo input */
6201         leaveok(stdscr, TRUE);
6203         if (has_colors())
6204                 init_colors();
6206         getmaxyx(stdscr, y, x);
6207         status_win = newwin(1, 0, y - 1, 0);
6208         if (!status_win)
6209                 die("Failed to create status window");
6211         /* Enable keyboard mapping */
6212         keypad(status_win, TRUE);
6213         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6215         TABSIZE = opt_tab_size;
6216         if (opt_line_graphics) {
6217                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6218         }
6221 static int
6222 get_input(bool prompting)
6224         struct view *view;
6225         int i, key;
6227         if (prompting)
6228                 input_mode = TRUE;
6230         while (true) {
6231                 foreach_view (view, i)
6232                         update_view(view);
6234                 /* Refresh, accept single keystroke of input */
6235                 key = wgetch(status_win);
6237                 /* wgetch() with nodelay() enabled returns ERR when
6238                  * there's no input. */
6239                 if (key == ERR) {
6240                         doupdate();
6242                 } else if (key == KEY_RESIZE) {
6243                         int height, width;
6245                         getmaxyx(stdscr, height, width);
6247                         /* Resize the status view first so the cursor is
6248                          * placed properly. */
6249                         wresize(status_win, 1, width);
6250                         mvwin(status_win, height - 1, 0);
6251                         wrefresh(status_win);
6252                         resize_display();
6253                         redraw_display(TRUE);
6255                 } else {
6256                         input_mode = FALSE;
6257                         return key;
6258                 }
6259         }
6262 static char *
6263 prompt_input(const char *prompt, input_handler handler, void *data)
6265         enum input_status status = INPUT_OK;
6266         static char buf[SIZEOF_STR];
6267         size_t pos = 0;
6269         buf[pos] = 0;
6271         while (status == INPUT_OK || status == INPUT_SKIP) {
6272                 int key;
6274                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6275                 wclrtoeol(status_win);
6277                 key = get_input(TRUE);
6278                 switch (key) {
6279                 case KEY_RETURN:
6280                 case KEY_ENTER:
6281                 case '\n':
6282                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6283                         break;
6285                 case KEY_BACKSPACE:
6286                         if (pos > 0)
6287                                 buf[--pos] = 0;
6288                         else
6289                                 status = INPUT_CANCEL;
6290                         break;
6292                 case KEY_ESC:
6293                         status = INPUT_CANCEL;
6294                         break;
6296                 default:
6297                         if (pos >= sizeof(buf)) {
6298                                 report("Input string too long");
6299                                 return NULL;
6300                         }
6302                         status = handler(data, buf, key);
6303                         if (status == INPUT_OK)
6304                                 buf[pos++] = (char) key;
6305                 }
6306         }
6308         /* Clear the status window */
6309         status_empty = FALSE;
6310         report("");
6312         if (status == INPUT_CANCEL)
6313                 return NULL;
6315         buf[pos++] = 0;
6317         return buf;
6320 static enum input_status
6321 prompt_yesno_handler(void *data, char *buf, int c)
6323         if (c == 'y' || c == 'Y')
6324                 return INPUT_STOP;
6325         if (c == 'n' || c == 'N')
6326                 return INPUT_CANCEL;
6327         return INPUT_SKIP;
6330 static bool
6331 prompt_yesno(const char *prompt)
6333         char prompt2[SIZEOF_STR];
6335         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6336                 return FALSE;
6338         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6341 static enum input_status
6342 read_prompt_handler(void *data, char *buf, int c)
6344         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6347 static char *
6348 read_prompt(const char *prompt)
6350         return prompt_input(prompt, read_prompt_handler, NULL);
6353 /*
6354  * Repository properties
6355  */
6357 static int
6358 git_properties(const char **argv, const char *separators,
6359                int (*read_property)(char *, size_t, char *, size_t))
6361         struct io io = {};
6363         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6364                 return read_properties(&io, separators, read_property);
6365         return ERR;
6368 static struct ref *refs = NULL;
6369 static size_t refs_alloc = 0;
6370 static size_t refs_size = 0;
6372 /* Id <-> ref store */
6373 static struct ref ***id_refs = NULL;
6374 static size_t id_refs_alloc = 0;
6375 static size_t id_refs_size = 0;
6377 static int
6378 compare_refs(const void *ref1_, const void *ref2_)
6380         const struct ref *ref1 = *(const struct ref **)ref1_;
6381         const struct ref *ref2 = *(const struct ref **)ref2_;
6383         if (ref1->tag != ref2->tag)
6384                 return ref2->tag - ref1->tag;
6385         if (ref1->ltag != ref2->ltag)
6386                 return ref2->ltag - ref2->ltag;
6387         if (ref1->head != ref2->head)
6388                 return ref2->head - ref1->head;
6389         if (ref1->tracked != ref2->tracked)
6390                 return ref2->tracked - ref1->tracked;
6391         if (ref1->remote != ref2->remote)
6392                 return ref2->remote - ref1->remote;
6393         return strcmp(ref1->name, ref2->name);
6396 static struct ref **
6397 get_refs(const char *id)
6399         struct ref ***tmp_id_refs;
6400         struct ref **ref_list = NULL;
6401         size_t ref_list_alloc = 0;
6402         size_t ref_list_size = 0;
6403         size_t i;
6405         for (i = 0; i < id_refs_size; i++)
6406                 if (!strcmp(id, id_refs[i][0]->id))
6407                         return id_refs[i];
6409         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6410                                     sizeof(*id_refs));
6411         if (!tmp_id_refs)
6412                 return NULL;
6414         id_refs = tmp_id_refs;
6416         for (i = 0; i < refs_size; i++) {
6417                 struct ref **tmp;
6419                 if (strcmp(id, refs[i].id))
6420                         continue;
6422                 tmp = realloc_items(ref_list, &ref_list_alloc,
6423                                     ref_list_size + 1, sizeof(*ref_list));
6424                 if (!tmp) {
6425                         if (ref_list)
6426                                 free(ref_list);
6427                         return NULL;
6428                 }
6430                 ref_list = tmp;
6431                 ref_list[ref_list_size] = &refs[i];
6432                 /* XXX: The properties of the commit chains ensures that we can
6433                  * safely modify the shared ref. The repo references will
6434                  * always be similar for the same id. */
6435                 ref_list[ref_list_size]->next = 1;
6437                 ref_list_size++;
6438         }
6440         if (ref_list) {
6441                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6442                 ref_list[ref_list_size - 1]->next = 0;
6443                 id_refs[id_refs_size++] = ref_list;
6444         }
6446         return ref_list;
6449 static int
6450 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6452         struct ref *ref;
6453         bool tag = FALSE;
6454         bool ltag = FALSE;
6455         bool remote = FALSE;
6456         bool tracked = FALSE;
6457         bool check_replace = FALSE;
6458         bool head = FALSE;
6460         if (!prefixcmp(name, "refs/tags/")) {
6461                 if (!suffixcmp(name, namelen, "^{}")) {
6462                         namelen -= 3;
6463                         name[namelen] = 0;
6464                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6465                                 check_replace = TRUE;
6466                 } else {
6467                         ltag = TRUE;
6468                 }
6470                 tag = TRUE;
6471                 namelen -= STRING_SIZE("refs/tags/");
6472                 name    += STRING_SIZE("refs/tags/");
6474         } else if (!prefixcmp(name, "refs/remotes/")) {
6475                 remote = TRUE;
6476                 namelen -= STRING_SIZE("refs/remotes/");
6477                 name    += STRING_SIZE("refs/remotes/");
6478                 tracked  = !strcmp(opt_remote, name);
6480         } else if (!prefixcmp(name, "refs/heads/")) {
6481                 namelen -= STRING_SIZE("refs/heads/");
6482                 name    += STRING_SIZE("refs/heads/");
6483                 head     = !strncmp(opt_head, name, namelen);
6485         } else if (!strcmp(name, "HEAD")) {
6486                 string_ncopy(opt_head_rev, id, idlen);
6487                 return OK;
6488         }
6490         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6491                 /* it's an annotated tag, replace the previous sha1 with the
6492                  * resolved commit id; relies on the fact git-ls-remote lists
6493                  * the commit id of an annotated tag right before the commit id
6494                  * it points to. */
6495                 refs[refs_size - 1].ltag = ltag;
6496                 string_copy_rev(refs[refs_size - 1].id, id);
6498                 return OK;
6499         }
6500         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6501         if (!refs)
6502                 return ERR;
6504         ref = &refs[refs_size++];
6505         ref->name = malloc(namelen + 1);
6506         if (!ref->name)
6507                 return ERR;
6509         strncpy(ref->name, name, namelen);
6510         ref->name[namelen] = 0;
6511         ref->head = head;
6512         ref->tag = tag;
6513         ref->ltag = ltag;
6514         ref->remote = remote;
6515         ref->tracked = tracked;
6516         string_copy_rev(ref->id, id);
6518         return OK;
6521 static int
6522 load_refs(void)
6524         static const char *ls_remote_argv[SIZEOF_ARG] = {
6525                 "git", "ls-remote", ".", NULL
6526         };
6527         static bool init = FALSE;
6529         if (!init) {
6530                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6531                 init = TRUE;
6532         }
6534         if (!*opt_git_dir)
6535                 return OK;
6537         while (refs_size > 0)
6538                 free(refs[--refs_size].name);
6539         while (id_refs_size > 0)
6540                 free(id_refs[--id_refs_size]);
6542         return git_properties(ls_remote_argv, "\t", read_ref);
6545 static int
6546 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6548         if (!strcmp(name, "i18n.commitencoding"))
6549                 string_ncopy(opt_encoding, value, valuelen);
6551         if (!strcmp(name, "core.editor"))
6552                 string_ncopy(opt_editor, value, valuelen);
6554         /* branch.<head>.remote */
6555         if (*opt_head &&
6556             !strncmp(name, "branch.", 7) &&
6557             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6558             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6559                 string_ncopy(opt_remote, value, valuelen);
6561         if (*opt_head && *opt_remote &&
6562             !strncmp(name, "branch.", 7) &&
6563             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6564             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6565                 size_t from = strlen(opt_remote);
6567                 if (!prefixcmp(value, "refs/heads/")) {
6568                         value += STRING_SIZE("refs/heads/");
6569                         valuelen -= STRING_SIZE("refs/heads/");
6570                 }
6572                 if (!string_format_from(opt_remote, &from, "/%s", value))
6573                         opt_remote[0] = 0;
6574         }
6576         return OK;
6579 static int
6580 load_git_config(void)
6582         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6584         return git_properties(config_list_argv, "=", read_repo_config_option);
6587 static int
6588 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6590         if (!opt_git_dir[0]) {
6591                 string_ncopy(opt_git_dir, name, namelen);
6593         } else if (opt_is_inside_work_tree == -1) {
6594                 /* This can be 3 different values depending on the
6595                  * version of git being used. If git-rev-parse does not
6596                  * understand --is-inside-work-tree it will simply echo
6597                  * the option else either "true" or "false" is printed.
6598                  * Default to true for the unknown case. */
6599                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6601         } else if (*name == '.') {
6602                 string_ncopy(opt_cdup, name, namelen);
6604         } else {
6605                 string_ncopy(opt_prefix, name, namelen);
6606         }
6608         return OK;
6611 static int
6612 load_repo_info(void)
6614         const char *head_argv[] = {
6615                 "git", "symbolic-ref", "HEAD", NULL
6616         };
6617         const char *rev_parse_argv[] = {
6618                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6619                         "--show-cdup", "--show-prefix", NULL
6620         };
6622         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6623                 chomp_string(opt_head);
6624                 if (!prefixcmp(opt_head, "refs/heads/")) {
6625                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6627                         memmove(opt_head, offset, strlen(offset) + 1);
6628                 }
6629         }
6631         return git_properties(rev_parse_argv, "=", read_repo_info);
6634 static int
6635 read_properties(struct io *io, const char *separators,
6636                 int (*read_property)(char *, size_t, char *, size_t))
6638         char *name;
6639         int state = OK;
6641         if (!start_io(io))
6642                 return ERR;
6644         while (state == OK && (name = io_get(io, '\n', TRUE))) {
6645                 char *value;
6646                 size_t namelen;
6647                 size_t valuelen;
6649                 name = chomp_string(name);
6650                 namelen = strcspn(name, separators);
6652                 if (name[namelen]) {
6653                         name[namelen] = 0;
6654                         value = chomp_string(name + namelen + 1);
6655                         valuelen = strlen(value);
6657                 } else {
6658                         value = "";
6659                         valuelen = 0;
6660                 }
6662                 state = read_property(name, namelen, value, valuelen);
6663         }
6665         if (state != ERR && io_error(io))
6666                 state = ERR;
6667         done_io(io);
6669         return state;
6673 /*
6674  * Main
6675  */
6677 static void __NORETURN
6678 quit(int sig)
6680         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6681         if (cursed)
6682                 endwin();
6683         exit(0);
6686 static void __NORETURN
6687 die(const char *err, ...)
6689         va_list args;
6691         endwin();
6693         va_start(args, err);
6694         fputs("tig: ", stderr);
6695         vfprintf(stderr, err, args);
6696         fputs("\n", stderr);
6697         va_end(args);
6699         exit(1);
6702 static void
6703 warn(const char *msg, ...)
6705         va_list args;
6707         va_start(args, msg);
6708         fputs("tig warning: ", stderr);
6709         vfprintf(stderr, msg, args);
6710         fputs("\n", stderr);
6711         va_end(args);
6714 int
6715 main(int argc, const char *argv[])
6717         const char **run_argv = NULL;
6718         struct view *view;
6719         enum request request;
6720         size_t i;
6722         signal(SIGINT, quit);
6724         if (setlocale(LC_ALL, "")) {
6725                 char *codeset = nl_langinfo(CODESET);
6727                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6728         }
6730         if (load_repo_info() == ERR)
6731                 die("Failed to load repo info.");
6733         if (load_options() == ERR)
6734                 die("Failed to load user config.");
6736         if (load_git_config() == ERR)
6737                 die("Failed to load repo config.");
6739         request = parse_options(argc, argv, &run_argv);
6740         if (request == REQ_NONE)
6741                 return 0;
6743         /* Require a git repository unless when running in pager mode. */
6744         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6745                 die("Not a git repository");
6747         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6748                 opt_utf8 = FALSE;
6750         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6751                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6752                 if (opt_iconv == ICONV_NONE)
6753                         die("Failed to initialize character set conversion");
6754         }
6756         if (load_refs() == ERR)
6757                 die("Failed to load refs.");
6759         foreach_view (view, i)
6760                 argv_from_env(view->ops->argv, view->cmd_env);
6762         init_display();
6764         if (request == REQ_VIEW_PAGER || run_argv) {
6765                 if (request == REQ_VIEW_PAGER)
6766                         io_open(&VIEW(request)->io, "");
6767                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6768                         die("Failed to format arguments");
6769                 open_view(NULL, request, OPEN_PREPARED);
6770                 request = REQ_NONE;
6771         }
6773         while (view_driver(display[current_view], request)) {
6774                 int key = get_input(FALSE);
6776                 view = display[current_view];
6777                 request = get_keybinding(view->keymap, key);
6779                 /* Some low-level request handling. This keeps access to
6780                  * status_win restricted. */
6781                 switch (request) {
6782                 case REQ_PROMPT:
6783                 {
6784                         char *cmd = read_prompt(":");
6786                         if (cmd) {
6787                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6788                                 const char *argv[SIZEOF_ARG] = { "git" };
6789                                 int argc = 1;
6791                                 /* When running random commands, initially show the
6792                                  * command in the title. However, it maybe later be
6793                                  * overwritten if a commit line is selected. */
6794                                 string_ncopy(next->ref, cmd, strlen(cmd));
6796                                 if (!argv_from_string(argv, &argc, cmd)) {
6797                                         report("Too many arguments");
6798                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6799                                         report("Failed to format command");
6800                                 } else {
6801                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6802                                 }
6803                         }
6805                         request = REQ_NONE;
6806                         break;
6807                 }
6808                 case REQ_SEARCH:
6809                 case REQ_SEARCH_BACK:
6810                 {
6811                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6812                         char *search = read_prompt(prompt);
6814                         if (search)
6815                                 string_ncopy(opt_search, search, strlen(search));
6816                         else
6817                                 request = REQ_NONE;
6818                         break;
6819                 }
6820                 default:
6821                         break;
6822                 }
6823         }
6825         quit(0);
6827         return 0;