Code

bc1dacf40b57071c4990e9885329aee9f0e42b9d
[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
656 io_load(struct io *io, const char *separators,
657         int (*read_property)(char *, size_t, char *, size_t))
659         char *name;
660         int state = OK;
662         if (!start_io(io))
663                 return ERR;
665         while (state == OK && (name = io_get(io, '\n', TRUE))) {
666                 char *value;
667                 size_t namelen;
668                 size_t valuelen;
670                 name = chomp_string(name);
671                 namelen = strcspn(name, separators);
673                 if (name[namelen]) {
674                         name[namelen] = 0;
675                         value = chomp_string(name + namelen + 1);
676                         valuelen = strlen(value);
678                 } else {
679                         value = "";
680                         valuelen = 0;
681                 }
683                 state = read_property(name, namelen, value, valuelen);
684         }
686         if (state != ERR && io_error(io))
687                 state = ERR;
688         done_io(io);
690         return state;
693 static int
694 run_io_load(const char **argv, const char *separators,
695             int (*read_property)(char *, size_t, char *, size_t))
697         struct io io = {};
699         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
700                 ? io_load(&io, separators, read_property) : ERR;
704 /*
705  * User requests
706  */
708 #define REQ_INFO \
709         /* XXX: Keep the view request first and in sync with views[]. */ \
710         REQ_GROUP("View switching") \
711         REQ_(VIEW_MAIN,         "Show main view"), \
712         REQ_(VIEW_DIFF,         "Show diff view"), \
713         REQ_(VIEW_LOG,          "Show log view"), \
714         REQ_(VIEW_TREE,         "Show tree view"), \
715         REQ_(VIEW_BLOB,         "Show blob view"), \
716         REQ_(VIEW_BLAME,        "Show blame view"), \
717         REQ_(VIEW_HELP,         "Show help page"), \
718         REQ_(VIEW_PAGER,        "Show pager view"), \
719         REQ_(VIEW_STATUS,       "Show status view"), \
720         REQ_(VIEW_STAGE,        "Show stage view"), \
721         \
722         REQ_GROUP("View manipulation") \
723         REQ_(ENTER,             "Enter current line and scroll"), \
724         REQ_(NEXT,              "Move to next"), \
725         REQ_(PREVIOUS,          "Move to previous"), \
726         REQ_(PARENT,            "Move to parent"), \
727         REQ_(VIEW_NEXT,         "Move focus to next view"), \
728         REQ_(REFRESH,           "Reload and refresh"), \
729         REQ_(MAXIMIZE,          "Maximize the current view"), \
730         REQ_(VIEW_CLOSE,        "Close the current view"), \
731         REQ_(QUIT,              "Close all views and quit"), \
732         \
733         REQ_GROUP("View specific requests") \
734         REQ_(STATUS_UPDATE,     "Update file status"), \
735         REQ_(STATUS_REVERT,     "Revert file changes"), \
736         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
737         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
738         \
739         REQ_GROUP("Cursor navigation") \
740         REQ_(MOVE_UP,           "Move cursor one line up"), \
741         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
742         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
743         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
744         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
745         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
746         \
747         REQ_GROUP("Scrolling") \
748         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
749         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
750         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
751         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
752         \
753         REQ_GROUP("Searching") \
754         REQ_(SEARCH,            "Search the view"), \
755         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
756         REQ_(FIND_NEXT,         "Find next search match"), \
757         REQ_(FIND_PREV,         "Find previous search match"), \
758         \
759         REQ_GROUP("Option manipulation") \
760         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
761         REQ_(TOGGLE_DATE,       "Toggle date display"), \
762         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
763         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
764         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
765         \
766         REQ_GROUP("Misc") \
767         REQ_(PROMPT,            "Bring up the prompt"), \
768         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
769         REQ_(SHOW_VERSION,      "Show version information"), \
770         REQ_(STOP_LOADING,      "Stop all loading views"), \
771         REQ_(EDIT,              "Open in editor"), \
772         REQ_(NONE,              "Do nothing")
775 /* User action requests. */
776 enum request {
777 #define REQ_GROUP(help)
778 #define REQ_(req, help) REQ_##req
780         /* Offset all requests to avoid conflicts with ncurses getch values. */
781         REQ_OFFSET = KEY_MAX + 1,
782         REQ_INFO
784 #undef  REQ_GROUP
785 #undef  REQ_
786 };
788 struct request_info {
789         enum request request;
790         const char *name;
791         int namelen;
792         const char *help;
793 };
795 static struct request_info req_info[] = {
796 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
797 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
798         REQ_INFO
799 #undef  REQ_GROUP
800 #undef  REQ_
801 };
803 static enum request
804 get_request(const char *name)
806         int namelen = strlen(name);
807         int i;
809         for (i = 0; i < ARRAY_SIZE(req_info); i++)
810                 if (req_info[i].namelen == namelen &&
811                     !string_enum_compare(req_info[i].name, name, namelen))
812                         return req_info[i].request;
814         return REQ_NONE;
818 /*
819  * Options
820  */
822 static const char usage[] =
823 "tig " TIG_VERSION " (" __DATE__ ")\n"
824 "\n"
825 "Usage: tig        [options] [revs] [--] [paths]\n"
826 "   or: tig show   [options] [revs] [--] [paths]\n"
827 "   or: tig blame  [rev] path\n"
828 "   or: tig status\n"
829 "   or: tig <      [git command output]\n"
830 "\n"
831 "Options:\n"
832 "  -v, --version   Show version and exit\n"
833 "  -h, --help      Show help message and exit";
835 /* Option and state variables. */
836 static bool opt_date                    = TRUE;
837 static bool opt_author                  = TRUE;
838 static bool opt_line_number             = FALSE;
839 static bool opt_line_graphics           = TRUE;
840 static bool opt_rev_graph               = FALSE;
841 static bool opt_show_refs               = TRUE;
842 static int opt_num_interval             = NUMBER_INTERVAL;
843 static int opt_tab_size                 = TAB_SIZE;
844 static int opt_author_cols              = AUTHOR_COLS-1;
845 static char opt_path[SIZEOF_STR]        = "";
846 static char opt_file[SIZEOF_STR]        = "";
847 static char opt_ref[SIZEOF_REF]         = "";
848 static char opt_head[SIZEOF_REF]        = "";
849 static char opt_head_rev[SIZEOF_REV]    = "";
850 static char opt_remote[SIZEOF_REF]      = "";
851 static char opt_encoding[20]            = "UTF-8";
852 static bool opt_utf8                    = TRUE;
853 static char opt_codeset[20]             = "UTF-8";
854 static iconv_t opt_iconv                = ICONV_NONE;
855 static char opt_search[SIZEOF_STR]      = "";
856 static char opt_cdup[SIZEOF_STR]        = "";
857 static char opt_prefix[SIZEOF_STR]      = "";
858 static char opt_git_dir[SIZEOF_STR]     = "";
859 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
860 static char opt_editor[SIZEOF_STR]      = "";
861 static FILE *opt_tty                    = NULL;
863 #define is_initial_commit()     (!*opt_head_rev)
864 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
866 static enum request
867 parse_options(int argc, const char *argv[], const char ***run_argv)
869         enum request request = REQ_VIEW_MAIN;
870         const char *subcommand;
871         bool seen_dashdash = FALSE;
872         /* XXX: This is vulnerable to the user overriding options
873          * required for the main view parser. */
874         static const char *custom_argv[SIZEOF_ARG] = {
875                 "git", "log", "--no-color", "--pretty=raw", "--parents",
876                         "--topo-order", NULL
877         };
878         int i, j = 6;
880         if (!isatty(STDIN_FILENO))
881                 return REQ_VIEW_PAGER;
883         if (argc <= 1)
884                 return REQ_VIEW_MAIN;
886         subcommand = argv[1];
887         if (!strcmp(subcommand, "status")) {
888                 if (argc > 2)
889                         warn("ignoring arguments after `%s'", subcommand);
890                 return REQ_VIEW_STATUS;
892         } else if (!strcmp(subcommand, "blame")) {
893                 if (argc <= 2 || argc > 4)
894                         die("invalid number of options to blame\n\n%s", usage);
896                 i = 2;
897                 if (argc == 4) {
898                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
899                         i++;
900                 }
902                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
903                 return REQ_VIEW_BLAME;
905         } else if (!strcmp(subcommand, "show")) {
906                 request = REQ_VIEW_DIFF;
908         } else {
909                 subcommand = NULL;
910         }
912         if (subcommand) {
913                 custom_argv[1] = subcommand;
914                 j = 2;
915         }
917         for (i = 1 + !!subcommand; i < argc; i++) {
918                 const char *opt = argv[i];
920                 if (seen_dashdash || !strcmp(opt, "--")) {
921                         seen_dashdash = TRUE;
923                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
924                         printf("tig version %s\n", TIG_VERSION);
925                         return REQ_NONE;
927                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
928                         printf("%s\n", usage);
929                         return REQ_NONE;
930                 }
932                 custom_argv[j++] = opt;
933                 if (j >= ARRAY_SIZE(custom_argv))
934                         die("command too long");
935         }
937         custom_argv[j] = NULL;
938         *run_argv = custom_argv;
940         return request;
944 /*
945  * Line-oriented content detection.
946  */
948 #define LINE_INFO \
949 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
950 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
951 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
952 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
953 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
954 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
955 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
956 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
957 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
958 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
959 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
960 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
961 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
962 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
963 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
964 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
965 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
966 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
967 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
968 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
969 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
970 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
971 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
972 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
973 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
974 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
975 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
976 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
977 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
978 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
979 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
980 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
981 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
982 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
983 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
984 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
985 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
986 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
987 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
988 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
989 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
990 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
991 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
992 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
993 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
994 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
995 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
996 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
997 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
998 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
999 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1000 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1001 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1002 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1003 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1004 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1006 enum line_type {
1007 #define LINE(type, line, fg, bg, attr) \
1008         LINE_##type
1009         LINE_INFO,
1010         LINE_NONE
1011 #undef  LINE
1012 };
1014 struct line_info {
1015         const char *name;       /* Option name. */
1016         int namelen;            /* Size of option name. */
1017         const char *line;       /* The start of line to match. */
1018         int linelen;            /* Size of string to match. */
1019         int fg, bg, attr;       /* Color and text attributes for the lines. */
1020 };
1022 static struct line_info line_info[] = {
1023 #define LINE(type, line, fg, bg, attr) \
1024         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1025         LINE_INFO
1026 #undef  LINE
1027 };
1029 static enum line_type
1030 get_line_type(const char *line)
1032         int linelen = strlen(line);
1033         enum line_type type;
1035         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1036                 /* Case insensitive search matches Signed-off-by lines better. */
1037                 if (linelen >= line_info[type].linelen &&
1038                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1039                         return type;
1041         return LINE_DEFAULT;
1044 static inline int
1045 get_line_attr(enum line_type type)
1047         assert(type < ARRAY_SIZE(line_info));
1048         return COLOR_PAIR(type) | line_info[type].attr;
1051 static struct line_info *
1052 get_line_info(const char *name)
1054         size_t namelen = strlen(name);
1055         enum line_type type;
1057         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1058                 if (namelen == line_info[type].namelen &&
1059                     !string_enum_compare(line_info[type].name, name, namelen))
1060                         return &line_info[type];
1062         return NULL;
1065 static void
1066 init_colors(void)
1068         int default_bg = line_info[LINE_DEFAULT].bg;
1069         int default_fg = line_info[LINE_DEFAULT].fg;
1070         enum line_type type;
1072         start_color();
1074         if (assume_default_colors(default_fg, default_bg) == ERR) {
1075                 default_bg = COLOR_BLACK;
1076                 default_fg = COLOR_WHITE;
1077         }
1079         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1080                 struct line_info *info = &line_info[type];
1081                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1082                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1084                 init_pair(type, fg, bg);
1085         }
1088 struct line {
1089         enum line_type type;
1091         /* State flags */
1092         unsigned int selected:1;
1093         unsigned int dirty:1;
1094         unsigned int cleareol:1;
1096         void *data;             /* User data */
1097 };
1100 /*
1101  * Keys
1102  */
1104 struct keybinding {
1105         int alias;
1106         enum request request;
1107 };
1109 static struct keybinding default_keybindings[] = {
1110         /* View switching */
1111         { 'm',          REQ_VIEW_MAIN },
1112         { 'd',          REQ_VIEW_DIFF },
1113         { 'l',          REQ_VIEW_LOG },
1114         { 't',          REQ_VIEW_TREE },
1115         { 'f',          REQ_VIEW_BLOB },
1116         { 'B',          REQ_VIEW_BLAME },
1117         { 'p',          REQ_VIEW_PAGER },
1118         { 'h',          REQ_VIEW_HELP },
1119         { 'S',          REQ_VIEW_STATUS },
1120         { 'c',          REQ_VIEW_STAGE },
1122         /* View manipulation */
1123         { 'q',          REQ_VIEW_CLOSE },
1124         { KEY_TAB,      REQ_VIEW_NEXT },
1125         { KEY_RETURN,   REQ_ENTER },
1126         { KEY_UP,       REQ_PREVIOUS },
1127         { KEY_DOWN,     REQ_NEXT },
1128         { 'R',          REQ_REFRESH },
1129         { KEY_F(5),     REQ_REFRESH },
1130         { 'O',          REQ_MAXIMIZE },
1132         /* Cursor navigation */
1133         { 'k',          REQ_MOVE_UP },
1134         { 'j',          REQ_MOVE_DOWN },
1135         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1136         { KEY_END,      REQ_MOVE_LAST_LINE },
1137         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1138         { ' ',          REQ_MOVE_PAGE_DOWN },
1139         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1140         { 'b',          REQ_MOVE_PAGE_UP },
1141         { '-',          REQ_MOVE_PAGE_UP },
1143         /* Scrolling */
1144         { KEY_IC,       REQ_SCROLL_LINE_UP },
1145         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1146         { 'w',          REQ_SCROLL_PAGE_UP },
1147         { 's',          REQ_SCROLL_PAGE_DOWN },
1149         /* Searching */
1150         { '/',          REQ_SEARCH },
1151         { '?',          REQ_SEARCH_BACK },
1152         { 'n',          REQ_FIND_NEXT },
1153         { 'N',          REQ_FIND_PREV },
1155         /* Misc */
1156         { 'Q',          REQ_QUIT },
1157         { 'z',          REQ_STOP_LOADING },
1158         { 'v',          REQ_SHOW_VERSION },
1159         { 'r',          REQ_SCREEN_REDRAW },
1160         { '.',          REQ_TOGGLE_LINENO },
1161         { 'D',          REQ_TOGGLE_DATE },
1162         { 'A',          REQ_TOGGLE_AUTHOR },
1163         { 'g',          REQ_TOGGLE_REV_GRAPH },
1164         { 'F',          REQ_TOGGLE_REFS },
1165         { ':',          REQ_PROMPT },
1166         { 'u',          REQ_STATUS_UPDATE },
1167         { '!',          REQ_STATUS_REVERT },
1168         { 'M',          REQ_STATUS_MERGE },
1169         { '@',          REQ_STAGE_NEXT },
1170         { ',',          REQ_PARENT },
1171         { 'e',          REQ_EDIT },
1172 };
1174 #define KEYMAP_INFO \
1175         KEYMAP_(GENERIC), \
1176         KEYMAP_(MAIN), \
1177         KEYMAP_(DIFF), \
1178         KEYMAP_(LOG), \
1179         KEYMAP_(TREE), \
1180         KEYMAP_(BLOB), \
1181         KEYMAP_(BLAME), \
1182         KEYMAP_(PAGER), \
1183         KEYMAP_(HELP), \
1184         KEYMAP_(STATUS), \
1185         KEYMAP_(STAGE)
1187 enum keymap {
1188 #define KEYMAP_(name) KEYMAP_##name
1189         KEYMAP_INFO
1190 #undef  KEYMAP_
1191 };
1193 static struct int_map keymap_table[] = {
1194 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1195         KEYMAP_INFO
1196 #undef  KEYMAP_
1197 };
1199 #define set_keymap(map, name) \
1200         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1202 struct keybinding_table {
1203         struct keybinding *data;
1204         size_t size;
1205 };
1207 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1209 static void
1210 add_keybinding(enum keymap keymap, enum request request, int key)
1212         struct keybinding_table *table = &keybindings[keymap];
1214         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1215         if (!table->data)
1216                 die("Failed to allocate keybinding");
1217         table->data[table->size].alias = key;
1218         table->data[table->size++].request = request;
1221 /* Looks for a key binding first in the given map, then in the generic map, and
1222  * lastly in the default keybindings. */
1223 static enum request
1224 get_keybinding(enum keymap keymap, int key)
1226         size_t i;
1228         for (i = 0; i < keybindings[keymap].size; i++)
1229                 if (keybindings[keymap].data[i].alias == key)
1230                         return keybindings[keymap].data[i].request;
1232         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1233                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1234                         return keybindings[KEYMAP_GENERIC].data[i].request;
1236         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1237                 if (default_keybindings[i].alias == key)
1238                         return default_keybindings[i].request;
1240         return (enum request) key;
1244 struct key {
1245         const char *name;
1246         int value;
1247 };
1249 static struct key key_table[] = {
1250         { "Enter",      KEY_RETURN },
1251         { "Space",      ' ' },
1252         { "Backspace",  KEY_BACKSPACE },
1253         { "Tab",        KEY_TAB },
1254         { "Escape",     KEY_ESC },
1255         { "Left",       KEY_LEFT },
1256         { "Right",      KEY_RIGHT },
1257         { "Up",         KEY_UP },
1258         { "Down",       KEY_DOWN },
1259         { "Insert",     KEY_IC },
1260         { "Delete",     KEY_DC },
1261         { "Hash",       '#' },
1262         { "Home",       KEY_HOME },
1263         { "End",        KEY_END },
1264         { "PageUp",     KEY_PPAGE },
1265         { "PageDown",   KEY_NPAGE },
1266         { "F1",         KEY_F(1) },
1267         { "F2",         KEY_F(2) },
1268         { "F3",         KEY_F(3) },
1269         { "F4",         KEY_F(4) },
1270         { "F5",         KEY_F(5) },
1271         { "F6",         KEY_F(6) },
1272         { "F7",         KEY_F(7) },
1273         { "F8",         KEY_F(8) },
1274         { "F9",         KEY_F(9) },
1275         { "F10",        KEY_F(10) },
1276         { "F11",        KEY_F(11) },
1277         { "F12",        KEY_F(12) },
1278 };
1280 static int
1281 get_key_value(const char *name)
1283         int i;
1285         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1286                 if (!strcasecmp(key_table[i].name, name))
1287                         return key_table[i].value;
1289         if (strlen(name) == 1 && isprint(*name))
1290                 return (int) *name;
1292         return ERR;
1295 static const char *
1296 get_key_name(int key_value)
1298         static char key_char[] = "'X'";
1299         const char *seq = NULL;
1300         int key;
1302         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1303                 if (key_table[key].value == key_value)
1304                         seq = key_table[key].name;
1306         if (seq == NULL &&
1307             key_value < 127 &&
1308             isprint(key_value)) {
1309                 key_char[1] = (char) key_value;
1310                 seq = key_char;
1311         }
1313         return seq ? seq : "(no key)";
1316 static const char *
1317 get_key(enum request request)
1319         static char buf[BUFSIZ];
1320         size_t pos = 0;
1321         char *sep = "";
1322         int i;
1324         buf[pos] = 0;
1326         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1327                 struct keybinding *keybinding = &default_keybindings[i];
1329                 if (keybinding->request != request)
1330                         continue;
1332                 if (!string_format_from(buf, &pos, "%s%s", sep,
1333                                         get_key_name(keybinding->alias)))
1334                         return "Too many keybindings!";
1335                 sep = ", ";
1336         }
1338         return buf;
1341 struct run_request {
1342         enum keymap keymap;
1343         int key;
1344         const char *argv[SIZEOF_ARG];
1345 };
1347 static struct run_request *run_request;
1348 static size_t run_requests;
1350 static enum request
1351 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1353         struct run_request *req;
1355         if (argc >= ARRAY_SIZE(req->argv) - 1)
1356                 return REQ_NONE;
1358         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1359         if (!req)
1360                 return REQ_NONE;
1362         run_request = req;
1363         req = &run_request[run_requests];
1364         req->keymap = keymap;
1365         req->key = key;
1366         req->argv[0] = NULL;
1368         if (!format_argv(req->argv, argv, FORMAT_NONE))
1369                 return REQ_NONE;
1371         return REQ_NONE + ++run_requests;
1374 static struct run_request *
1375 get_run_request(enum request request)
1377         if (request <= REQ_NONE)
1378                 return NULL;
1379         return &run_request[request - REQ_NONE - 1];
1382 static void
1383 add_builtin_run_requests(void)
1385         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1386         const char *gc[] = { "git", "gc", NULL };
1387         struct {
1388                 enum keymap keymap;
1389                 int key;
1390                 int argc;
1391                 const char **argv;
1392         } reqs[] = {
1393                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1394                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1395         };
1396         int i;
1398         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1399                 enum request req;
1401                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1402                 if (req != REQ_NONE)
1403                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1404         }
1407 /*
1408  * User config file handling.
1409  */
1411 static struct int_map color_map[] = {
1412 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1413         COLOR_MAP(DEFAULT),
1414         COLOR_MAP(BLACK),
1415         COLOR_MAP(BLUE),
1416         COLOR_MAP(CYAN),
1417         COLOR_MAP(GREEN),
1418         COLOR_MAP(MAGENTA),
1419         COLOR_MAP(RED),
1420         COLOR_MAP(WHITE),
1421         COLOR_MAP(YELLOW),
1422 };
1424 #define set_color(color, name) \
1425         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1427 static struct int_map attr_map[] = {
1428 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1429         ATTR_MAP(NORMAL),
1430         ATTR_MAP(BLINK),
1431         ATTR_MAP(BOLD),
1432         ATTR_MAP(DIM),
1433         ATTR_MAP(REVERSE),
1434         ATTR_MAP(STANDOUT),
1435         ATTR_MAP(UNDERLINE),
1436 };
1438 #define set_attribute(attr, name) \
1439         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1441 static int   config_lineno;
1442 static bool  config_errors;
1443 static const char *config_msg;
1445 /* Wants: object fgcolor bgcolor [attr] */
1446 static int
1447 option_color_command(int argc, const char *argv[])
1449         struct line_info *info;
1451         if (argc != 3 && argc != 4) {
1452                 config_msg = "Wrong number of arguments given to color command";
1453                 return ERR;
1454         }
1456         info = get_line_info(argv[0]);
1457         if (!info) {
1458                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1459                         info = get_line_info("delimiter");
1461                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1462                         info = get_line_info("date");
1464                 } else {
1465                         config_msg = "Unknown color name";
1466                         return ERR;
1467                 }
1468         }
1470         if (set_color(&info->fg, argv[1]) == ERR ||
1471             set_color(&info->bg, argv[2]) == ERR) {
1472                 config_msg = "Unknown color";
1473                 return ERR;
1474         }
1476         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1477                 config_msg = "Unknown attribute";
1478                 return ERR;
1479         }
1481         return OK;
1484 static bool parse_bool(const char *s)
1486         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1487                 !strcmp(s, "yes")) ? TRUE : FALSE;
1490 static int
1491 parse_int(const char *s, int default_value, int min, int max)
1493         int value = atoi(s);
1495         return (value < min || value > max) ? default_value : value;
1498 /* Wants: name = value */
1499 static int
1500 option_set_command(int argc, const char *argv[])
1502         if (argc != 3) {
1503                 config_msg = "Wrong number of arguments given to set command";
1504                 return ERR;
1505         }
1507         if (strcmp(argv[1], "=")) {
1508                 config_msg = "No value assigned";
1509                 return ERR;
1510         }
1512         if (!strcmp(argv[0], "show-author")) {
1513                 opt_author = parse_bool(argv[2]);
1514                 return OK;
1515         }
1517         if (!strcmp(argv[0], "show-date")) {
1518                 opt_date = parse_bool(argv[2]);
1519                 return OK;
1520         }
1522         if (!strcmp(argv[0], "show-rev-graph")) {
1523                 opt_rev_graph = parse_bool(argv[2]);
1524                 return OK;
1525         }
1527         if (!strcmp(argv[0], "show-refs")) {
1528                 opt_show_refs = parse_bool(argv[2]);
1529                 return OK;
1530         }
1532         if (!strcmp(argv[0], "show-line-numbers")) {
1533                 opt_line_number = parse_bool(argv[2]);
1534                 return OK;
1535         }
1537         if (!strcmp(argv[0], "line-graphics")) {
1538                 opt_line_graphics = parse_bool(argv[2]);
1539                 return OK;
1540         }
1542         if (!strcmp(argv[0], "line-number-interval")) {
1543                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1544                 return OK;
1545         }
1547         if (!strcmp(argv[0], "author-width")) {
1548                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1549                 return OK;
1550         }
1552         if (!strcmp(argv[0], "tab-size")) {
1553                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1554                 return OK;
1555         }
1557         if (!strcmp(argv[0], "commit-encoding")) {
1558                 const char *arg = argv[2];
1559                 int arglen = strlen(arg);
1561                 switch (arg[0]) {
1562                 case '"':
1563                 case '\'':
1564                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1565                                 config_msg = "Unmatched quotation";
1566                                 return ERR;
1567                         }
1568                         arg += 1; arglen -= 2;
1569                 default:
1570                         string_ncopy(opt_encoding, arg, strlen(arg));
1571                         return OK;
1572                 }
1573         }
1575         config_msg = "Unknown variable name";
1576         return ERR;
1579 /* Wants: mode request key */
1580 static int
1581 option_bind_command(int argc, const char *argv[])
1583         enum request request;
1584         int keymap;
1585         int key;
1587         if (argc < 3) {
1588                 config_msg = "Wrong number of arguments given to bind command";
1589                 return ERR;
1590         }
1592         if (set_keymap(&keymap, argv[0]) == ERR) {
1593                 config_msg = "Unknown key map";
1594                 return ERR;
1595         }
1597         key = get_key_value(argv[1]);
1598         if (key == ERR) {
1599                 config_msg = "Unknown key";
1600                 return ERR;
1601         }
1603         request = get_request(argv[2]);
1604         if (request == REQ_NONE) {
1605                 struct {
1606                         const char *name;
1607                         enum request request;
1608                 } obsolete[] = {
1609                         { "cherry-pick",        REQ_NONE },
1610                         { "screen-resize",      REQ_NONE },
1611                         { "tree-parent",        REQ_PARENT },
1612                 };
1613                 size_t namelen = strlen(argv[2]);
1614                 int i;
1616                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1617                         if (namelen != strlen(obsolete[i].name) ||
1618                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1619                                 continue;
1620                         if (obsolete[i].request != REQ_NONE)
1621                                 add_keybinding(keymap, obsolete[i].request, key);
1622                         config_msg = "Obsolete request name";
1623                         return ERR;
1624                 }
1625         }
1626         if (request == REQ_NONE && *argv[2]++ == '!')
1627                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1628         if (request == REQ_NONE) {
1629                 config_msg = "Unknown request name";
1630                 return ERR;
1631         }
1633         add_keybinding(keymap, request, key);
1635         return OK;
1638 static int
1639 set_option(const char *opt, char *value)
1641         const char *argv[SIZEOF_ARG];
1642         int argc = 0;
1644         if (!argv_from_string(argv, &argc, value)) {
1645                 config_msg = "Too many option arguments";
1646                 return ERR;
1647         }
1649         if (!strcmp(opt, "color"))
1650                 return option_color_command(argc, argv);
1652         if (!strcmp(opt, "set"))
1653                 return option_set_command(argc, argv);
1655         if (!strcmp(opt, "bind"))
1656                 return option_bind_command(argc, argv);
1658         config_msg = "Unknown option command";
1659         return ERR;
1662 static int
1663 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1665         int status = OK;
1667         config_lineno++;
1668         config_msg = "Internal error";
1670         /* Check for comment markers, since read_properties() will
1671          * only ensure opt and value are split at first " \t". */
1672         optlen = strcspn(opt, "#");
1673         if (optlen == 0)
1674                 return OK;
1676         if (opt[optlen] != 0) {
1677                 config_msg = "No option value";
1678                 status = ERR;
1680         }  else {
1681                 /* Look for comment endings in the value. */
1682                 size_t len = strcspn(value, "#");
1684                 if (len < valuelen) {
1685                         valuelen = len;
1686                         value[valuelen] = 0;
1687                 }
1689                 status = set_option(opt, value);
1690         }
1692         if (status == ERR) {
1693                 warn("Error on line %d, near '%.*s': %s",
1694                      config_lineno, (int) optlen, opt, config_msg);
1695                 config_errors = TRUE;
1696         }
1698         /* Always keep going if errors are encountered. */
1699         return OK;
1702 static void
1703 load_option_file(const char *path)
1705         struct io io = {};
1707         /* It's ok that the file doesn't exist. */
1708         if (!io_open(&io, path))
1709                 return;
1711         config_lineno = 0;
1712         config_errors = FALSE;
1714         if (io_load(&io, " \t", read_option) == ERR ||
1715             config_errors == TRUE)
1716                 warn("Errors while loading %s.", path);
1719 static int
1720 load_options(void)
1722         const char *home = getenv("HOME");
1723         const char *tigrc_user = getenv("TIGRC_USER");
1724         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1725         char buf[SIZEOF_STR];
1727         add_builtin_run_requests();
1729         if (!tigrc_system) {
1730                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1731                         return ERR;
1732                 tigrc_system = buf;
1733         }
1734         load_option_file(tigrc_system);
1736         if (!tigrc_user) {
1737                 if (!home || !string_format(buf, "%s/.tigrc", home))
1738                         return ERR;
1739                 tigrc_user = buf;
1740         }
1741         load_option_file(tigrc_user);
1743         return OK;
1747 /*
1748  * The viewer
1749  */
1751 struct view;
1752 struct view_ops;
1754 /* The display array of active views and the index of the current view. */
1755 static struct view *display[2];
1756 static unsigned int current_view;
1758 #define foreach_displayed_view(view, i) \
1759         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1761 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1763 /* Current head and commit ID */
1764 static char ref_blob[SIZEOF_REF]        = "";
1765 static char ref_commit[SIZEOF_REF]      = "HEAD";
1766 static char ref_head[SIZEOF_REF]        = "HEAD";
1768 struct view {
1769         const char *name;       /* View name */
1770         const char *cmd_env;    /* Command line set via environment */
1771         const char *id;         /* Points to either of ref_{head,commit,blob} */
1773         struct view_ops *ops;   /* View operations */
1775         enum keymap keymap;     /* What keymap does this view have */
1776         bool git_dir;           /* Whether the view requires a git directory. */
1778         char ref[SIZEOF_REF];   /* Hovered commit reference */
1779         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1781         int height, width;      /* The width and height of the main window */
1782         WINDOW *win;            /* The main window */
1783         WINDOW *title;          /* The title window living below the main window */
1785         /* Navigation */
1786         unsigned long offset;   /* Offset of the window top */
1787         unsigned long lineno;   /* Current line number */
1788         unsigned long p_offset; /* Previous offset of the window top */
1789         unsigned long p_lineno; /* Previous current line number */
1790         bool p_restore;         /* Should the previous position be restored. */
1792         /* Searching */
1793         char grep[SIZEOF_STR];  /* Search string */
1794         regex_t *regex;         /* Pre-compiled regex */
1796         /* If non-NULL, points to the view that opened this view. If this view
1797          * is closed tig will switch back to the parent view. */
1798         struct view *parent;
1800         /* Buffering */
1801         size_t lines;           /* Total number of lines */
1802         struct line *line;      /* Line index */
1803         size_t line_alloc;      /* Total number of allocated lines */
1804         unsigned int digits;    /* Number of digits in the lines member. */
1806         /* Drawing */
1807         struct line *curline;   /* Line currently being drawn. */
1808         enum line_type curtype; /* Attribute currently used for drawing. */
1809         unsigned long col;      /* Column when drawing. */
1810         bool has_scrolled;      /* View was scrolled. */
1812         /* Loading */
1813         struct io io;
1814         struct io *pipe;
1815         time_t start_time;
1816         time_t update_secs;
1817 };
1819 struct view_ops {
1820         /* What type of content being displayed. Used in the title bar. */
1821         const char *type;
1822         /* Default command arguments. */
1823         const char **argv;
1824         /* Open and reads in all view content. */
1825         bool (*open)(struct view *view);
1826         /* Read one line; updates view->line. */
1827         bool (*read)(struct view *view, char *data);
1828         /* Draw one line; @lineno must be < view->height. */
1829         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1830         /* Depending on view handle a special requests. */
1831         enum request (*request)(struct view *view, enum request request, struct line *line);
1832         /* Search for regex in a line. */
1833         bool (*grep)(struct view *view, struct line *line);
1834         /* Select line */
1835         void (*select)(struct view *view, struct line *line);
1836 };
1838 static struct view_ops blame_ops;
1839 static struct view_ops blob_ops;
1840 static struct view_ops diff_ops;
1841 static struct view_ops help_ops;
1842 static struct view_ops log_ops;
1843 static struct view_ops main_ops;
1844 static struct view_ops pager_ops;
1845 static struct view_ops stage_ops;
1846 static struct view_ops status_ops;
1847 static struct view_ops tree_ops;
1849 #define VIEW_STR(name, env, ref, ops, map, git) \
1850         { name, #env, ref, ops, map, git }
1852 #define VIEW_(id, name, ops, git, ref) \
1853         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1856 static struct view views[] = {
1857         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1858         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1859         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1860         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1861         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1862         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1863         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1864         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1865         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1866         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1867 };
1869 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1870 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1872 #define foreach_view(view, i) \
1873         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1875 #define view_is_displayed(view) \
1876         (view == display[0] || view == display[1])
1879 enum line_graphic {
1880         LINE_GRAPHIC_VLINE
1881 };
1883 static int line_graphics[] = {
1884         /* LINE_GRAPHIC_VLINE: */ '|'
1885 };
1887 static inline void
1888 set_view_attr(struct view *view, enum line_type type)
1890         if (!view->curline->selected && view->curtype != type) {
1891                 wattrset(view->win, get_line_attr(type));
1892                 wchgat(view->win, -1, 0, type, NULL);
1893                 view->curtype = type;
1894         }
1897 static int
1898 draw_chars(struct view *view, enum line_type type, const char *string,
1899            int max_len, bool use_tilde)
1901         int len = 0;
1902         int col = 0;
1903         int trimmed = FALSE;
1905         if (max_len <= 0)
1906                 return 0;
1908         if (opt_utf8) {
1909                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1910         } else {
1911                 col = len = strlen(string);
1912                 if (len > max_len) {
1913                         if (use_tilde) {
1914                                 max_len -= 1;
1915                         }
1916                         col = len = max_len;
1917                         trimmed = TRUE;
1918                 }
1919         }
1921         set_view_attr(view, type);
1922         waddnstr(view->win, string, len);
1923         if (trimmed && use_tilde) {
1924                 set_view_attr(view, LINE_DELIMITER);
1925                 waddch(view->win, '~');
1926                 col++;
1927         }
1929         return col;
1932 static int
1933 draw_space(struct view *view, enum line_type type, int max, int spaces)
1935         static char space[] = "                    ";
1936         int col = 0;
1938         spaces = MIN(max, spaces);
1940         while (spaces > 0) {
1941                 int len = MIN(spaces, sizeof(space) - 1);
1943                 col += draw_chars(view, type, space, spaces, FALSE);
1944                 spaces -= len;
1945         }
1947         return col;
1950 static bool
1951 draw_lineno(struct view *view, unsigned int lineno)
1953         char number[10];
1954         int digits3 = view->digits < 3 ? 3 : view->digits;
1955         int max_number = MIN(digits3, STRING_SIZE(number));
1956         int max = view->width - view->col;
1957         int col;
1959         if (max < max_number)
1960                 max_number = max;
1962         lineno += view->offset + 1;
1963         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1964                 static char fmt[] = "%1ld";
1966                 if (view->digits <= 9)
1967                         fmt[1] = '0' + digits3;
1969                 if (!string_format(number, fmt, lineno))
1970                         number[0] = 0;
1971                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1972         } else {
1973                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1974         }
1976         if (col < max) {
1977                 set_view_attr(view, LINE_DEFAULT);
1978                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1979                 col++;
1980         }
1982         if (col < max)
1983                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1984         view->col += col;
1986         return view->width - view->col <= 0;
1989 static bool
1990 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1992         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1993         return view->width - view->col <= 0;
1996 static bool
1997 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1999         int max = view->width - view->col;
2000         int i;
2002         if (max < size)
2003                 size = max;
2005         set_view_attr(view, type);
2006         /* Using waddch() instead of waddnstr() ensures that
2007          * they'll be rendered correctly for the cursor line. */
2008         for (i = 0; i < size; i++)
2009                 waddch(view->win, graphic[i]);
2011         view->col += size;
2012         if (size < max) {
2013                 waddch(view->win, ' ');
2014                 view->col++;
2015         }
2017         return view->width - view->col <= 0;
2020 static bool
2021 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2023         int max = MIN(view->width - view->col, len);
2024         int col;
2026         if (text)
2027                 col = draw_chars(view, type, text, max - 1, trim);
2028         else
2029                 col = draw_space(view, type, max - 1, max - 1);
2031         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
2032         return view->width - view->col <= 0;
2035 static bool
2036 draw_date(struct view *view, struct tm *time)
2038         char buf[DATE_COLS];
2039         char *date;
2040         int timelen = 0;
2042         if (time)
2043                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2044         date = timelen ? buf : NULL;
2046         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2049 static bool
2050 draw_author(struct view *view, const char *author)
2052         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2054         if (!trim) {
2055                 static char initials[10];
2056                 size_t pos;
2058 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2060                 memset(initials, 0, sizeof(initials));
2061                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2062                         while (is_initial_sep(*author))
2063                                 author++;
2064                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2065                         while (*author && !is_initial_sep(author[1]))
2066                                 author++;
2067                 }
2069                 author = initials;
2070         }
2072         return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2075 static bool
2076 draw_view_line(struct view *view, unsigned int lineno)
2078         struct line *line;
2079         bool selected = (view->offset + lineno == view->lineno);
2081         assert(view_is_displayed(view));
2083         if (view->offset + lineno >= view->lines)
2084                 return FALSE;
2086         line = &view->line[view->offset + lineno];
2088         wmove(view->win, lineno, 0);
2089         if (line->cleareol)
2090                 wclrtoeol(view->win);
2091         view->col = 0;
2092         view->curline = line;
2093         view->curtype = LINE_NONE;
2094         line->selected = FALSE;
2095         line->dirty = line->cleareol = 0;
2097         if (selected) {
2098                 set_view_attr(view, LINE_CURSOR);
2099                 line->selected = TRUE;
2100                 view->ops->select(view, line);
2101         }
2103         return view->ops->draw(view, line, lineno);
2106 static void
2107 redraw_view_dirty(struct view *view)
2109         bool dirty = FALSE;
2110         int lineno;
2112         for (lineno = 0; lineno < view->height; lineno++) {
2113                 if (view->offset + lineno >= view->lines)
2114                         break;
2115                 if (!view->line[view->offset + lineno].dirty)
2116                         continue;
2117                 dirty = TRUE;
2118                 if (!draw_view_line(view, lineno))
2119                         break;
2120         }
2122         if (!dirty)
2123                 return;
2124         wnoutrefresh(view->win);
2127 static void
2128 redraw_view_from(struct view *view, int lineno)
2130         assert(0 <= lineno && lineno < view->height);
2132         for (; lineno < view->height; lineno++) {
2133                 if (!draw_view_line(view, lineno))
2134                         break;
2135         }
2137         wnoutrefresh(view->win);
2140 static void
2141 redraw_view(struct view *view)
2143         werase(view->win);
2144         redraw_view_from(view, 0);
2148 static void
2149 update_view_title(struct view *view)
2151         char buf[SIZEOF_STR];
2152         char state[SIZEOF_STR];
2153         size_t bufpos = 0, statelen = 0;
2155         assert(view_is_displayed(view));
2157         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2158                 unsigned int view_lines = view->offset + view->height;
2159                 unsigned int lines = view->lines
2160                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2161                                    : 0;
2163                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2164                                    view->ops->type,
2165                                    view->lineno + 1,
2166                                    view->lines,
2167                                    lines);
2169         }
2171         if (view->pipe) {
2172                 time_t secs = time(NULL) - view->start_time;
2174                 /* Three git seconds are a long time ... */
2175                 if (secs > 2)
2176                         string_format_from(state, &statelen, " loading %lds", secs);
2177         }
2179         string_format_from(buf, &bufpos, "[%s]", view->name);
2180         if (*view->ref && bufpos < view->width) {
2181                 size_t refsize = strlen(view->ref);
2182                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2184                 if (minsize < view->width)
2185                         refsize = view->width - minsize + 7;
2186                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2187         }
2189         if (statelen && bufpos < view->width) {
2190                 string_format_from(buf, &bufpos, "%s", state);
2191         }
2193         if (view == display[current_view])
2194                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2195         else
2196                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2198         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2199         wclrtoeol(view->title);
2200         wnoutrefresh(view->title);
2203 static void
2204 resize_display(void)
2206         int offset, i;
2207         struct view *base = display[0];
2208         struct view *view = display[1] ? display[1] : display[0];
2210         /* Setup window dimensions */
2212         getmaxyx(stdscr, base->height, base->width);
2214         /* Make room for the status window. */
2215         base->height -= 1;
2217         if (view != base) {
2218                 /* Horizontal split. */
2219                 view->width   = base->width;
2220                 view->height  = SCALE_SPLIT_VIEW(base->height);
2221                 base->height -= view->height;
2223                 /* Make room for the title bar. */
2224                 view->height -= 1;
2225         }
2227         /* Make room for the title bar. */
2228         base->height -= 1;
2230         offset = 0;
2232         foreach_displayed_view (view, i) {
2233                 if (!view->win) {
2234                         view->win = newwin(view->height, 0, offset, 0);
2235                         if (!view->win)
2236                                 die("Failed to create %s view", view->name);
2238                         scrollok(view->win, FALSE);
2240                         view->title = newwin(1, 0, offset + view->height, 0);
2241                         if (!view->title)
2242                                 die("Failed to create title window");
2244                 } else {
2245                         wresize(view->win, view->height, view->width);
2246                         mvwin(view->win,   offset, 0);
2247                         mvwin(view->title, offset + view->height, 0);
2248                 }
2250                 offset += view->height + 1;
2251         }
2254 static void
2255 redraw_display(bool clear)
2257         struct view *view;
2258         int i;
2260         foreach_displayed_view (view, i) {
2261                 if (clear)
2262                         wclear(view->win);
2263                 redraw_view(view);
2264                 update_view_title(view);
2265         }
2268 static void
2269 toggle_view_option(bool *option, const char *help)
2271         *option = !*option;
2272         redraw_display(FALSE);
2273         report("%sabling %s", *option ? "En" : "Dis", help);
2276 /*
2277  * Navigation
2278  */
2280 /* Scrolling backend */
2281 static void
2282 do_scroll_view(struct view *view, int lines)
2284         bool redraw_current_line = FALSE;
2286         /* The rendering expects the new offset. */
2287         view->offset += lines;
2289         assert(0 <= view->offset && view->offset < view->lines);
2290         assert(lines);
2292         /* Move current line into the view. */
2293         if (view->lineno < view->offset) {
2294                 view->lineno = view->offset;
2295                 redraw_current_line = TRUE;
2296         } else if (view->lineno >= view->offset + view->height) {
2297                 view->lineno = view->offset + view->height - 1;
2298                 redraw_current_line = TRUE;
2299         }
2301         assert(view->offset <= view->lineno && view->lineno < view->lines);
2303         /* Redraw the whole screen if scrolling is pointless. */
2304         if (view->height < ABS(lines)) {
2305                 redraw_view(view);
2307         } else {
2308                 int line = lines > 0 ? view->height - lines : 0;
2309                 int end = line + ABS(lines);
2311                 scrollok(view->win, TRUE);
2312                 wscrl(view->win, lines);
2313                 scrollok(view->win, FALSE);
2315                 while (line < end && draw_view_line(view, line))
2316                         line++;
2318                 if (redraw_current_line)
2319                         draw_view_line(view, view->lineno - view->offset);
2320                 wnoutrefresh(view->win);
2321         }
2323         view->has_scrolled = TRUE;
2324         report("");
2327 /* Scroll frontend */
2328 static void
2329 scroll_view(struct view *view, enum request request)
2331         int lines = 1;
2333         assert(view_is_displayed(view));
2335         switch (request) {
2336         case REQ_SCROLL_PAGE_DOWN:
2337                 lines = view->height;
2338         case REQ_SCROLL_LINE_DOWN:
2339                 if (view->offset + lines > view->lines)
2340                         lines = view->lines - view->offset;
2342                 if (lines == 0 || view->offset + view->height >= view->lines) {
2343                         report("Cannot scroll beyond the last line");
2344                         return;
2345                 }
2346                 break;
2348         case REQ_SCROLL_PAGE_UP:
2349                 lines = view->height;
2350         case REQ_SCROLL_LINE_UP:
2351                 if (lines > view->offset)
2352                         lines = view->offset;
2354                 if (lines == 0) {
2355                         report("Cannot scroll beyond the first line");
2356                         return;
2357                 }
2359                 lines = -lines;
2360                 break;
2362         default:
2363                 die("request %d not handled in switch", request);
2364         }
2366         do_scroll_view(view, lines);
2369 /* Cursor moving */
2370 static void
2371 move_view(struct view *view, enum request request)
2373         int scroll_steps = 0;
2374         int steps;
2376         switch (request) {
2377         case REQ_MOVE_FIRST_LINE:
2378                 steps = -view->lineno;
2379                 break;
2381         case REQ_MOVE_LAST_LINE:
2382                 steps = view->lines - view->lineno - 1;
2383                 break;
2385         case REQ_MOVE_PAGE_UP:
2386                 steps = view->height > view->lineno
2387                       ? -view->lineno : -view->height;
2388                 break;
2390         case REQ_MOVE_PAGE_DOWN:
2391                 steps = view->lineno + view->height >= view->lines
2392                       ? view->lines - view->lineno - 1 : view->height;
2393                 break;
2395         case REQ_MOVE_UP:
2396                 steps = -1;
2397                 break;
2399         case REQ_MOVE_DOWN:
2400                 steps = 1;
2401                 break;
2403         default:
2404                 die("request %d not handled in switch", request);
2405         }
2407         if (steps <= 0 && view->lineno == 0) {
2408                 report("Cannot move beyond the first line");
2409                 return;
2411         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2412                 report("Cannot move beyond the last line");
2413                 return;
2414         }
2416         /* Move the current line */
2417         view->lineno += steps;
2418         assert(0 <= view->lineno && view->lineno < view->lines);
2420         /* Check whether the view needs to be scrolled */
2421         if (view->lineno < view->offset ||
2422             view->lineno >= view->offset + view->height) {
2423                 scroll_steps = steps;
2424                 if (steps < 0 && -steps > view->offset) {
2425                         scroll_steps = -view->offset;
2427                 } else if (steps > 0) {
2428                         if (view->lineno == view->lines - 1 &&
2429                             view->lines > view->height) {
2430                                 scroll_steps = view->lines - view->offset - 1;
2431                                 if (scroll_steps >= view->height)
2432                                         scroll_steps -= view->height - 1;
2433                         }
2434                 }
2435         }
2437         if (!view_is_displayed(view)) {
2438                 view->offset += scroll_steps;
2439                 assert(0 <= view->offset && view->offset < view->lines);
2440                 view->ops->select(view, &view->line[view->lineno]);
2441                 return;
2442         }
2444         /* Repaint the old "current" line if we be scrolling */
2445         if (ABS(steps) < view->height)
2446                 draw_view_line(view, view->lineno - steps - view->offset);
2448         if (scroll_steps) {
2449                 do_scroll_view(view, scroll_steps);
2450                 return;
2451         }
2453         /* Draw the current line */
2454         draw_view_line(view, view->lineno - view->offset);
2456         wnoutrefresh(view->win);
2457         report("");
2461 /*
2462  * Searching
2463  */
2465 static void search_view(struct view *view, enum request request);
2467 static void
2468 select_view_line(struct view *view, unsigned long lineno)
2470         if (lineno - view->offset >= view->height) {
2471                 view->offset = lineno;
2472                 view->lineno = lineno;
2473                 if (view_is_displayed(view))
2474                         redraw_view(view);
2476         } else {
2477                 unsigned long old_lineno = view->lineno - view->offset;
2479                 view->lineno = lineno;
2480                 if (view_is_displayed(view)) {
2481                         draw_view_line(view, old_lineno);
2482                         draw_view_line(view, view->lineno - view->offset);
2483                         wnoutrefresh(view->win);
2484                 } else {
2485                         view->ops->select(view, &view->line[view->lineno]);
2486                 }
2487         }
2490 static void
2491 find_next(struct view *view, enum request request)
2493         unsigned long lineno = view->lineno;
2494         int direction;
2496         if (!*view->grep) {
2497                 if (!*opt_search)
2498                         report("No previous search");
2499                 else
2500                         search_view(view, request);
2501                 return;
2502         }
2504         switch (request) {
2505         case REQ_SEARCH:
2506         case REQ_FIND_NEXT:
2507                 direction = 1;
2508                 break;
2510         case REQ_SEARCH_BACK:
2511         case REQ_FIND_PREV:
2512                 direction = -1;
2513                 break;
2515         default:
2516                 return;
2517         }
2519         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2520                 lineno += direction;
2522         /* Note, lineno is unsigned long so will wrap around in which case it
2523          * will become bigger than view->lines. */
2524         for (; lineno < view->lines; lineno += direction) {
2525                 if (view->ops->grep(view, &view->line[lineno])) {
2526                         select_view_line(view, lineno);
2527                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2528                         return;
2529                 }
2530         }
2532         report("No match found for '%s'", view->grep);
2535 static void
2536 search_view(struct view *view, enum request request)
2538         int regex_err;
2540         if (view->regex) {
2541                 regfree(view->regex);
2542                 *view->grep = 0;
2543         } else {
2544                 view->regex = calloc(1, sizeof(*view->regex));
2545                 if (!view->regex)
2546                         return;
2547         }
2549         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2550         if (regex_err != 0) {
2551                 char buf[SIZEOF_STR] = "unknown error";
2553                 regerror(regex_err, view->regex, buf, sizeof(buf));
2554                 report("Search failed: %s", buf);
2555                 return;
2556         }
2558         string_copy(view->grep, opt_search);
2560         find_next(view, request);
2563 /*
2564  * Incremental updating
2565  */
2567 static void
2568 reset_view(struct view *view)
2570         int i;
2572         for (i = 0; i < view->lines; i++)
2573                 free(view->line[i].data);
2574         free(view->line);
2576         view->p_offset = view->offset;
2577         view->p_lineno = view->lineno;
2579         view->line = NULL;
2580         view->offset = 0;
2581         view->lines  = 0;
2582         view->lineno = 0;
2583         view->line_alloc = 0;
2584         view->vid[0] = 0;
2585         view->update_secs = 0;
2588 static void
2589 free_argv(const char *argv[])
2591         int argc;
2593         for (argc = 0; argv[argc]; argc++)
2594                 free((void *) argv[argc]);
2597 static bool
2598 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2600         char buf[SIZEOF_STR];
2601         int argc;
2602         bool noreplace = flags == FORMAT_NONE;
2604         free_argv(dst_argv);
2606         for (argc = 0; src_argv[argc]; argc++) {
2607                 const char *arg = src_argv[argc];
2608                 size_t bufpos = 0;
2610                 while (arg) {
2611                         char *next = strstr(arg, "%(");
2612                         int len = next - arg;
2613                         const char *value;
2615                         if (!next || noreplace) {
2616                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2617                                         noreplace = TRUE;
2618                                 len = strlen(arg);
2619                                 value = "";
2621                         } else if (!prefixcmp(next, "%(directory)")) {
2622                                 value = opt_path;
2624                         } else if (!prefixcmp(next, "%(file)")) {
2625                                 value = opt_file;
2627                         } else if (!prefixcmp(next, "%(ref)")) {
2628                                 value = *opt_ref ? opt_ref : "HEAD";
2630                         } else if (!prefixcmp(next, "%(head)")) {
2631                                 value = ref_head;
2633                         } else if (!prefixcmp(next, "%(commit)")) {
2634                                 value = ref_commit;
2636                         } else if (!prefixcmp(next, "%(blob)")) {
2637                                 value = ref_blob;
2639                         } else {
2640                                 report("Unknown replacement: `%s`", next);
2641                                 return FALSE;
2642                         }
2644                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2645                                 return FALSE;
2647                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2648                 }
2650                 dst_argv[argc] = strdup(buf);
2651                 if (!dst_argv[argc])
2652                         break;
2653         }
2655         dst_argv[argc] = NULL;
2657         return src_argv[argc] == NULL;
2660 static bool
2661 restore_view_position(struct view *view)
2663         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2664                 return FALSE;
2666         /* Changing the view position cancels the restoring. */
2667         /* FIXME: Changing back to the first line is not detected. */
2668         if (view->offset != 0 || view->lineno != 0) {
2669                 view->p_restore = FALSE;
2670                 return FALSE;
2671         }
2673         if (view->p_lineno >= view->lines) {
2674                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2675                 if (view->p_offset >= view->p_lineno) {
2676                         unsigned long half = view->height / 2;
2678                         if (view->p_lineno > half)
2679                                 view->p_offset = view->p_lineno - half;
2680                         else
2681                                 view->p_offset = 0;
2682                 }
2683         }
2685         if (view_is_displayed(view) &&
2686             view->offset != view->p_offset &&
2687             view->lineno != view->p_lineno)
2688                 werase(view->win);
2690         view->offset = view->p_offset;
2691         view->lineno = view->p_lineno;
2692         view->p_restore = FALSE;
2694         return TRUE;
2697 static void
2698 end_update(struct view *view, bool force)
2700         if (!view->pipe)
2701                 return;
2702         while (!view->ops->read(view, NULL))
2703                 if (!force)
2704                         return;
2705         set_nonblocking_input(FALSE);
2706         if (force)
2707                 kill_io(view->pipe);
2708         done_io(view->pipe);
2709         view->pipe = NULL;
2712 static void
2713 setup_update(struct view *view, const char *vid)
2715         set_nonblocking_input(TRUE);
2716         reset_view(view);
2717         string_copy_rev(view->vid, vid);
2718         view->pipe = &view->io;
2719         view->start_time = time(NULL);
2722 static bool
2723 prepare_update(struct view *view, const char *argv[], const char *dir,
2724                enum format_flags flags)
2726         if (view->pipe)
2727                 end_update(view, TRUE);
2728         return init_io_rd(&view->io, argv, dir, flags);
2731 static bool
2732 prepare_update_file(struct view *view, const char *name)
2734         if (view->pipe)
2735                 end_update(view, TRUE);
2736         return io_open(&view->io, name);
2739 static bool
2740 begin_update(struct view *view, bool refresh)
2742         if (view->pipe)
2743                 end_update(view, TRUE);
2745         if (refresh) {
2746                 if (!start_io(&view->io))
2747                         return FALSE;
2749         } else {
2750                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2751                         opt_path[0] = 0;
2753                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2754                         return FALSE;
2756                 /* Put the current ref_* value to the view title ref
2757                  * member. This is needed by the blob view. Most other
2758                  * views sets it automatically after loading because the
2759                  * first line is a commit line. */
2760                 string_copy_rev(view->ref, view->id);
2761         }
2763         setup_update(view, view->id);
2765         return TRUE;
2768 #define ITEM_CHUNK_SIZE 256
2769 static void *
2770 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2772         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2773         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2775         if (mem == NULL || num_chunks != num_chunks_new) {
2776                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2777                 mem = realloc(mem, *size * item_size);
2778         }
2780         return mem;
2783 static struct line *
2784 realloc_lines(struct view *view, size_t line_size)
2786         size_t alloc = view->line_alloc;
2787         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2788                                          sizeof(*view->line));
2790         if (!tmp)
2791                 return NULL;
2793         view->line = tmp;
2794         view->line_alloc = alloc;
2795         return view->line;
2798 static bool
2799 update_view(struct view *view)
2801         char out_buffer[BUFSIZ * 2];
2802         char *line;
2803         /* Clear the view and redraw everything since the tree sorting
2804          * might have rearranged things. */
2805         bool redraw = view->lines == 0;
2806         bool can_read = TRUE;
2808         if (!view->pipe)
2809                 return TRUE;
2811         if (!io_can_read(view->pipe)) {
2812                 if (view->lines == 0) {
2813                         time_t secs = time(NULL) - view->start_time;
2815                         if (secs > 1 && secs > view->update_secs) {
2816                                 if (view->update_secs == 0)
2817                                         redraw_view(view);
2818                                 update_view_title(view);
2819                                 view->update_secs = secs;
2820                         }
2821                 }
2822                 return TRUE;
2823         }
2825         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2826                 if (opt_iconv != ICONV_NONE) {
2827                         ICONV_CONST char *inbuf = line;
2828                         size_t inlen = strlen(line) + 1;
2830                         char *outbuf = out_buffer;
2831                         size_t outlen = sizeof(out_buffer);
2833                         size_t ret;
2835                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2836                         if (ret != (size_t) -1)
2837                                 line = out_buffer;
2838                 }
2840                 if (!view->ops->read(view, line)) {
2841                         report("Allocation failure");
2842                         end_update(view, TRUE);
2843                         return FALSE;
2844                 }
2845         }
2847         {
2848                 unsigned long lines = view->lines;
2849                 int digits;
2851                 for (digits = 0; lines; digits++)
2852                         lines /= 10;
2854                 /* Keep the displayed view in sync with line number scaling. */
2855                 if (digits != view->digits) {
2856                         view->digits = digits;
2857                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2858                                 redraw = TRUE;
2859                 }
2860         }
2862         if (io_error(view->pipe)) {
2863                 report("Failed to read: %s", io_strerror(view->pipe));
2864                 end_update(view, TRUE);
2866         } else if (io_eof(view->pipe)) {
2867                 report("");
2868                 end_update(view, FALSE);
2869         }
2871         if (restore_view_position(view))
2872                 redraw = TRUE;
2874         if (!view_is_displayed(view))
2875                 return TRUE;
2877         if (redraw)
2878                 redraw_view_from(view, 0);
2879         else
2880                 redraw_view_dirty(view);
2882         /* Update the title _after_ the redraw so that if the redraw picks up a
2883          * commit reference in view->ref it'll be available here. */
2884         update_view_title(view);
2885         return TRUE;
2888 static struct line *
2889 add_line_data(struct view *view, void *data, enum line_type type)
2891         struct line *line;
2893         if (!realloc_lines(view, view->lines + 1))
2894                 return NULL;
2896         line = &view->line[view->lines++];
2897         memset(line, 0, sizeof(*line));
2898         line->type = type;
2899         line->data = data;
2900         line->dirty = 1;
2902         return line;
2905 static struct line *
2906 add_line_text(struct view *view, const char *text, enum line_type type)
2908         char *data = text ? strdup(text) : NULL;
2910         return data ? add_line_data(view, data, type) : NULL;
2913 static struct line *
2914 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2916         char buf[SIZEOF_STR];
2917         va_list args;
2919         va_start(args, fmt);
2920         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2921                 buf[0] = 0;
2922         va_end(args);
2924         return buf[0] ? add_line_text(view, buf, type) : NULL;
2927 /*
2928  * View opening
2929  */
2931 enum open_flags {
2932         OPEN_DEFAULT = 0,       /* Use default view switching. */
2933         OPEN_SPLIT = 1,         /* Split current view. */
2934         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2935         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2936         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2937         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2938         OPEN_PREPARED = 32,     /* Open already prepared command. */
2939 };
2941 static void
2942 open_view(struct view *prev, enum request request, enum open_flags flags)
2944         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2945         bool split = !!(flags & OPEN_SPLIT);
2946         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2947         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2948         struct view *view = VIEW(request);
2949         int nviews = displayed_views();
2950         struct view *base_view = display[0];
2952         if (view == prev && nviews == 1 && !reload) {
2953                 report("Already in %s view", view->name);
2954                 return;
2955         }
2957         if (view->git_dir && !opt_git_dir[0]) {
2958                 report("The %s view is disabled in pager view", view->name);
2959                 return;
2960         }
2962         if (split) {
2963                 display[1] = view;
2964                 if (!backgrounded)
2965                         current_view = 1;
2966         } else if (!nomaximize) {
2967                 /* Maximize the current view. */
2968                 memset(display, 0, sizeof(display));
2969                 current_view = 0;
2970                 display[current_view] = view;
2971         }
2973         /* Resize the view when switching between split- and full-screen,
2974          * or when switching between two different full-screen views. */
2975         if (nviews != displayed_views() ||
2976             (nviews == 1 && base_view != display[0]))
2977                 resize_display();
2979         if (view->ops->open) {
2980                 if (view->pipe)
2981                         end_update(view, TRUE);
2982                 if (!view->ops->open(view)) {
2983                         report("Failed to load %s view", view->name);
2984                         return;
2985                 }
2986                 restore_view_position(view);
2988         } else if ((reload || strcmp(view->vid, view->id)) &&
2989                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2990                 report("Failed to load %s view", view->name);
2991                 return;
2992         }
2994         if (split && prev->lineno - prev->offset >= prev->height) {
2995                 /* Take the title line into account. */
2996                 int lines = prev->lineno - prev->offset - prev->height + 1;
2998                 /* Scroll the view that was split if the current line is
2999                  * outside the new limited view. */
3000                 do_scroll_view(prev, lines);
3001         }
3003         if (prev && view != prev) {
3004                 if (split && !backgrounded) {
3005                         /* "Blur" the previous view. */
3006                         update_view_title(prev);
3007                 }
3009                 view->parent = prev;
3010         }
3012         if (view->pipe && view->lines == 0) {
3013                 /* Clear the old view and let the incremental updating refill
3014                  * the screen. */
3015                 werase(view->win);
3016                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3017                 report("");
3018         } else if (view_is_displayed(view)) {
3019                 redraw_view(view);
3020                 report("");
3021         }
3023         /* If the view is backgrounded the above calls to report()
3024          * won't redraw the view title. */
3025         if (backgrounded)
3026                 update_view_title(view);
3029 static void
3030 open_external_viewer(const char *argv[], const char *dir)
3032         def_prog_mode();           /* save current tty modes */
3033         endwin();                  /* restore original tty modes */
3034         run_io_fg(argv, dir);
3035         fprintf(stderr, "Press Enter to continue");
3036         getc(opt_tty);
3037         reset_prog_mode();
3038         redraw_display(TRUE);
3041 static void
3042 open_mergetool(const char *file)
3044         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3046         open_external_viewer(mergetool_argv, opt_cdup);
3049 static void
3050 open_editor(bool from_root, const char *file)
3052         const char *editor_argv[] = { "vi", file, NULL };
3053         const char *editor;
3055         editor = getenv("GIT_EDITOR");
3056         if (!editor && *opt_editor)
3057                 editor = opt_editor;
3058         if (!editor)
3059                 editor = getenv("VISUAL");
3060         if (!editor)
3061                 editor = getenv("EDITOR");
3062         if (!editor)
3063                 editor = "vi";
3065         editor_argv[0] = editor;
3066         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3069 static void
3070 open_run_request(enum request request)
3072         struct run_request *req = get_run_request(request);
3073         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3075         if (!req) {
3076                 report("Unknown run request");
3077                 return;
3078         }
3080         if (format_argv(argv, req->argv, FORMAT_ALL))
3081                 open_external_viewer(argv, NULL);
3082         free_argv(argv);
3085 /*
3086  * User request switch noodle
3087  */
3089 static int
3090 view_driver(struct view *view, enum request request)
3092         int i;
3094         if (request == REQ_NONE) {
3095                 doupdate();
3096                 return TRUE;
3097         }
3099         if (request > REQ_NONE) {
3100                 open_run_request(request);
3101                 /* FIXME: When all views can refresh always do this. */
3102                 if (view == VIEW(REQ_VIEW_STATUS) ||
3103                     view == VIEW(REQ_VIEW_MAIN) ||
3104                     view == VIEW(REQ_VIEW_LOG) ||
3105                     view == VIEW(REQ_VIEW_STAGE))
3106                         request = REQ_REFRESH;
3107                 else
3108                         return TRUE;
3109         }
3111         if (view && view->lines) {
3112                 request = view->ops->request(view, request, &view->line[view->lineno]);
3113                 if (request == REQ_NONE)
3114                         return TRUE;
3115         }
3117         switch (request) {
3118         case REQ_MOVE_UP:
3119         case REQ_MOVE_DOWN:
3120         case REQ_MOVE_PAGE_UP:
3121         case REQ_MOVE_PAGE_DOWN:
3122         case REQ_MOVE_FIRST_LINE:
3123         case REQ_MOVE_LAST_LINE:
3124                 move_view(view, request);
3125                 break;
3127         case REQ_SCROLL_LINE_DOWN:
3128         case REQ_SCROLL_LINE_UP:
3129         case REQ_SCROLL_PAGE_DOWN:
3130         case REQ_SCROLL_PAGE_UP:
3131                 scroll_view(view, request);
3132                 break;
3134         case REQ_VIEW_BLAME:
3135                 if (!opt_file[0]) {
3136                         report("No file chosen, press %s to open tree view",
3137                                get_key(REQ_VIEW_TREE));
3138                         break;
3139                 }
3140                 open_view(view, request, OPEN_DEFAULT);
3141                 break;
3143         case REQ_VIEW_BLOB:
3144                 if (!ref_blob[0]) {
3145                         report("No file chosen, press %s to open tree view",
3146                                get_key(REQ_VIEW_TREE));
3147                         break;
3148                 }
3149                 open_view(view, request, OPEN_DEFAULT);
3150                 break;
3152         case REQ_VIEW_PAGER:
3153                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3154                         report("No pager content, press %s to run command from prompt",
3155                                get_key(REQ_PROMPT));
3156                         break;
3157                 }
3158                 open_view(view, request, OPEN_DEFAULT);
3159                 break;
3161         case REQ_VIEW_STAGE:
3162                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3163                         report("No stage content, press %s to open the status view and choose file",
3164                                get_key(REQ_VIEW_STATUS));
3165                         break;
3166                 }
3167                 open_view(view, request, OPEN_DEFAULT);
3168                 break;
3170         case REQ_VIEW_STATUS:
3171                 if (opt_is_inside_work_tree == FALSE) {
3172                         report("The status view requires a working tree");
3173                         break;
3174                 }
3175                 open_view(view, request, OPEN_DEFAULT);
3176                 break;
3178         case REQ_VIEW_MAIN:
3179         case REQ_VIEW_DIFF:
3180         case REQ_VIEW_LOG:
3181         case REQ_VIEW_TREE:
3182         case REQ_VIEW_HELP:
3183                 open_view(view, request, OPEN_DEFAULT);
3184                 break;
3186         case REQ_NEXT:
3187         case REQ_PREVIOUS:
3188                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3190                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3191                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3192                    (view == VIEW(REQ_VIEW_DIFF) &&
3193                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3194                    (view == VIEW(REQ_VIEW_STAGE) &&
3195                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3196                    (view == VIEW(REQ_VIEW_BLOB) &&
3197                      view->parent == VIEW(REQ_VIEW_TREE))) {
3198                         int line;
3200                         view = view->parent;
3201                         line = view->lineno;
3202                         move_view(view, request);
3203                         if (view_is_displayed(view))
3204                                 update_view_title(view);
3205                         if (line != view->lineno)
3206                                 view->ops->request(view, REQ_ENTER,
3207                                                    &view->line[view->lineno]);
3209                 } else {
3210                         move_view(view, request);
3211                 }
3212                 break;
3214         case REQ_VIEW_NEXT:
3215         {
3216                 int nviews = displayed_views();
3217                 int next_view = (current_view + 1) % nviews;
3219                 if (next_view == current_view) {
3220                         report("Only one view is displayed");
3221                         break;
3222                 }
3224                 current_view = next_view;
3225                 /* Blur out the title of the previous view. */
3226                 update_view_title(view);
3227                 report("");
3228                 break;
3229         }
3230         case REQ_REFRESH:
3231                 report("Refreshing is not yet supported for the %s view", view->name);
3232                 break;
3234         case REQ_MAXIMIZE:
3235                 if (displayed_views() == 2)
3236                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3237                 break;
3239         case REQ_TOGGLE_LINENO:
3240                 toggle_view_option(&opt_line_number, "line numbers");
3241                 break;
3243         case REQ_TOGGLE_DATE:
3244                 toggle_view_option(&opt_date, "date display");
3245                 break;
3247         case REQ_TOGGLE_AUTHOR:
3248                 toggle_view_option(&opt_author, "author display");
3249                 break;
3251         case REQ_TOGGLE_REV_GRAPH:
3252                 toggle_view_option(&opt_rev_graph, "revision graph display");
3253                 break;
3255         case REQ_TOGGLE_REFS:
3256                 toggle_view_option(&opt_show_refs, "reference display");
3257                 break;
3259         case REQ_SEARCH:
3260         case REQ_SEARCH_BACK:
3261                 search_view(view, request);
3262                 break;
3264         case REQ_FIND_NEXT:
3265         case REQ_FIND_PREV:
3266                 find_next(view, request);
3267                 break;
3269         case REQ_STOP_LOADING:
3270                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3271                         view = &views[i];
3272                         if (view->pipe)
3273                                 report("Stopped loading the %s view", view->name),
3274                         end_update(view, TRUE);
3275                 }
3276                 break;
3278         case REQ_SHOW_VERSION:
3279                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3280                 return TRUE;
3282         case REQ_SCREEN_REDRAW:
3283                 redraw_display(TRUE);
3284                 break;
3286         case REQ_EDIT:
3287                 report("Nothing to edit");
3288                 break;
3290         case REQ_ENTER:
3291                 report("Nothing to enter");
3292                 break;
3294         case REQ_VIEW_CLOSE:
3295                 /* XXX: Mark closed views by letting view->parent point to the
3296                  * view itself. Parents to closed view should never be
3297                  * followed. */
3298                 if (view->parent &&
3299                     view->parent->parent != view->parent) {
3300                         memset(display, 0, sizeof(display));
3301                         current_view = 0;
3302                         display[current_view] = view->parent;
3303                         view->parent = view;
3304                         resize_display();
3305                         redraw_display(FALSE);
3306                         report("");
3307                         break;
3308                 }
3309                 /* Fall-through */
3310         case REQ_QUIT:
3311                 return FALSE;
3313         default:
3314                 report("Unknown key, press 'h' for help");
3315                 return TRUE;
3316         }
3318         return TRUE;
3322 /*
3323  * View backend utilities
3324  */
3326 /* Parse author lines where the name may be empty:
3327  *      author  <email@address.tld> 1138474660 +0100
3328  */
3329 static void
3330 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3332         char *nameend = strchr(ident, '<');
3333         char *emailend = strchr(ident, '>');
3335         if (nameend && emailend)
3336                 *nameend = *emailend = 0;
3337         ident = chomp_string(ident);
3338         if (!*ident) {
3339                 if (nameend)
3340                         ident = chomp_string(nameend + 1);
3341                 if (!*ident)
3342                         ident = "Unknown";
3343         }
3345         string_ncopy_do(author, authorsize, ident, strlen(ident));
3347         /* Parse epoch and timezone */
3348         if (emailend && emailend[1] == ' ') {
3349                 char *secs = emailend + 2;
3350                 char *zone = strchr(secs, ' ');
3351                 time_t time = (time_t) atol(secs);
3353                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3354                         long tz;
3356                         zone++;
3357                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3358                         tz += ('0' - zone[2]) * 60 * 60;
3359                         tz += ('0' - zone[3]) * 60;
3360                         tz += ('0' - zone[4]) * 60;
3362                         if (zone[0] == '-')
3363                                 tz = -tz;
3365                         time -= tz;
3366                 }
3368                 gmtime_r(&time, tm);
3369         }
3372 static enum input_status
3373 select_commit_parent_handler(void *data, char *buf, int c)
3375         size_t parents = *(size_t *) data;
3376         int parent = 0;
3378         if (!isdigit(c))
3379                 return INPUT_SKIP;
3381         if (*buf)
3382                 parent = atoi(buf) * 10;
3383         parent += c - '0';
3385         if (parent > parents)
3386                 return INPUT_SKIP;
3387         return INPUT_OK;
3390 static bool
3391 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3393         char buf[SIZEOF_STR * 4];
3394         const char *revlist_argv[] = {
3395                 "git", "rev-list", "-1", "--parents", id, NULL
3396         };
3397         int parents;
3399         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3400             !*chomp_string(buf) ||
3401             (parents = (strlen(buf) / 40) - 1) < 0) {
3402                 report("Failed to get parent information");
3403                 return FALSE;
3405         } else if (parents == 0) {
3406                 report("The selected commit has no parents");
3407                 return FALSE;
3408         }
3410         if (parents > 1) {
3411                 char prompt[SIZEOF_STR];
3412                 char *result;
3414                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3415                         return FALSE;
3416                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3417                 if (!result)
3418                         return FALSE;
3419                 parents = atoi(result);
3420         }
3422         string_copy_rev(rev, &buf[41 * parents]);
3423         return TRUE;
3426 /*
3427  * Pager backend
3428  */
3430 static bool
3431 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3433         char *text = line->data;
3435         if (opt_line_number && draw_lineno(view, lineno))
3436                 return TRUE;
3438         draw_text(view, line->type, text, TRUE);
3439         return TRUE;
3442 static bool
3443 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3445         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3446         char refbuf[SIZEOF_STR];
3447         char *ref = NULL;
3449         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3450                 ref = chomp_string(refbuf);
3452         if (!ref || !*ref)
3453                 return TRUE;
3455         /* This is the only fatal call, since it can "corrupt" the buffer. */
3456         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3457                 return FALSE;
3459         return TRUE;
3462 static void
3463 add_pager_refs(struct view *view, struct line *line)
3465         char buf[SIZEOF_STR];
3466         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3467         struct ref **refs;
3468         size_t bufpos = 0, refpos = 0;
3469         const char *sep = "Refs: ";
3470         bool is_tag = FALSE;
3472         assert(line->type == LINE_COMMIT);
3474         refs = get_refs(commit_id);
3475         if (!refs) {
3476                 if (view == VIEW(REQ_VIEW_DIFF))
3477                         goto try_add_describe_ref;
3478                 return;
3479         }
3481         do {
3482                 struct ref *ref = refs[refpos];
3483                 const char *fmt = ref->tag    ? "%s[%s]" :
3484                                   ref->remote ? "%s<%s>" : "%s%s";
3486                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3487                         return;
3488                 sep = ", ";
3489                 if (ref->tag)
3490                         is_tag = TRUE;
3491         } while (refs[refpos++]->next);
3493         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3494 try_add_describe_ref:
3495                 /* Add <tag>-g<commit_id> "fake" reference. */
3496                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3497                         return;
3498         }
3500         if (bufpos == 0)
3501                 return;
3503         add_line_text(view, buf, LINE_PP_REFS);
3506 static bool
3507 pager_read(struct view *view, char *data)
3509         struct line *line;
3511         if (!data)
3512                 return TRUE;
3514         line = add_line_text(view, data, get_line_type(data));
3515         if (!line)
3516                 return FALSE;
3518         if (line->type == LINE_COMMIT &&
3519             (view == VIEW(REQ_VIEW_DIFF) ||
3520              view == VIEW(REQ_VIEW_LOG)))
3521                 add_pager_refs(view, line);
3523         return TRUE;
3526 static enum request
3527 pager_request(struct view *view, enum request request, struct line *line)
3529         int split = 0;
3531         if (request != REQ_ENTER)
3532                 return request;
3534         if (line->type == LINE_COMMIT &&
3535            (view == VIEW(REQ_VIEW_LOG) ||
3536             view == VIEW(REQ_VIEW_PAGER))) {
3537                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3538                 split = 1;
3539         }
3541         /* Always scroll the view even if it was split. That way
3542          * you can use Enter to scroll through the log view and
3543          * split open each commit diff. */
3544         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3546         /* FIXME: A minor workaround. Scrolling the view will call report("")
3547          * but if we are scrolling a non-current view this won't properly
3548          * update the view title. */
3549         if (split)
3550                 update_view_title(view);
3552         return REQ_NONE;
3555 static bool
3556 pager_grep(struct view *view, struct line *line)
3558         regmatch_t pmatch;
3559         char *text = line->data;
3561         if (!*text)
3562                 return FALSE;
3564         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3565                 return FALSE;
3567         return TRUE;
3570 static void
3571 pager_select(struct view *view, struct line *line)
3573         if (line->type == LINE_COMMIT) {
3574                 char *text = (char *)line->data + STRING_SIZE("commit ");
3576                 if (view != VIEW(REQ_VIEW_PAGER))
3577                         string_copy_rev(view->ref, text);
3578                 string_copy_rev(ref_commit, text);
3579         }
3582 static struct view_ops pager_ops = {
3583         "line",
3584         NULL,
3585         NULL,
3586         pager_read,
3587         pager_draw,
3588         pager_request,
3589         pager_grep,
3590         pager_select,
3591 };
3593 static const char *log_argv[SIZEOF_ARG] = {
3594         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3595 };
3597 static enum request
3598 log_request(struct view *view, enum request request, struct line *line)
3600         switch (request) {
3601         case REQ_REFRESH:
3602                 load_refs();
3603                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3604                 return REQ_NONE;
3605         default:
3606                 return pager_request(view, request, line);
3607         }
3610 static struct view_ops log_ops = {
3611         "line",
3612         log_argv,
3613         NULL,
3614         pager_read,
3615         pager_draw,
3616         log_request,
3617         pager_grep,
3618         pager_select,
3619 };
3621 static const char *diff_argv[SIZEOF_ARG] = {
3622         "git", "show", "--pretty=fuller", "--no-color", "--root",
3623                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3624 };
3626 static struct view_ops diff_ops = {
3627         "line",
3628         diff_argv,
3629         NULL,
3630         pager_read,
3631         pager_draw,
3632         pager_request,
3633         pager_grep,
3634         pager_select,
3635 };
3637 /*
3638  * Help backend
3639  */
3641 static bool
3642 help_open(struct view *view)
3644         char buf[SIZEOF_STR];
3645         size_t bufpos;
3646         int i;
3648         if (view->lines > 0)
3649                 return TRUE;
3651         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3653         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3654                 const char *key;
3656                 if (req_info[i].request == REQ_NONE)
3657                         continue;
3659                 if (!req_info[i].request) {
3660                         add_line_text(view, "", LINE_DEFAULT);
3661                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3662                         continue;
3663                 }
3665                 key = get_key(req_info[i].request);
3666                 if (!*key)
3667                         key = "(no key defined)";
3669                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3670                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3671                         if (buf[bufpos] == '_')
3672                                 buf[bufpos] = '-';
3673                 }
3675                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3676                                 key, buf, req_info[i].help);
3677         }
3679         if (run_requests) {
3680                 add_line_text(view, "", LINE_DEFAULT);
3681                 add_line_text(view, "External commands:", LINE_DEFAULT);
3682         }
3684         for (i = 0; i < run_requests; i++) {
3685                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3686                 const char *key;
3687                 int argc;
3689                 if (!req)
3690                         continue;
3692                 key = get_key_name(req->key);
3693                 if (!*key)
3694                         key = "(no key defined)";
3696                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3697                         if (!string_format_from(buf, &bufpos, "%s%s",
3698                                                 argc ? " " : "", req->argv[argc]))
3699                                 return REQ_NONE;
3701                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3702                                 keymap_table[req->keymap].name, key, buf);
3703         }
3705         return TRUE;
3708 static struct view_ops help_ops = {
3709         "line",
3710         NULL,
3711         help_open,
3712         NULL,
3713         pager_draw,
3714         pager_request,
3715         pager_grep,
3716         pager_select,
3717 };
3720 /*
3721  * Tree backend
3722  */
3724 struct tree_stack_entry {
3725         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3726         unsigned long lineno;           /* Line number to restore */
3727         char *name;                     /* Position of name in opt_path */
3728 };
3730 /* The top of the path stack. */
3731 static struct tree_stack_entry *tree_stack = NULL;
3732 unsigned long tree_lineno = 0;
3734 static void
3735 pop_tree_stack_entry(void)
3737         struct tree_stack_entry *entry = tree_stack;
3739         tree_lineno = entry->lineno;
3740         entry->name[0] = 0;
3741         tree_stack = entry->prev;
3742         free(entry);
3745 static void
3746 push_tree_stack_entry(const char *name, unsigned long lineno)
3748         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3749         size_t pathlen = strlen(opt_path);
3751         if (!entry)
3752                 return;
3754         entry->prev = tree_stack;
3755         entry->name = opt_path + pathlen;
3756         tree_stack = entry;
3758         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3759                 pop_tree_stack_entry();
3760                 return;
3761         }
3763         /* Move the current line to the first tree entry. */
3764         tree_lineno = 1;
3765         entry->lineno = lineno;
3768 /* Parse output from git-ls-tree(1):
3769  *
3770  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3771  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3772  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3773  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3774  */
3776 #define SIZEOF_TREE_ATTR \
3777         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3779 #define SIZEOF_TREE_MODE \
3780         STRING_SIZE("100644 ")
3782 #define TREE_ID_OFFSET \
3783         STRING_SIZE("100644 blob ")
3785 struct tree_entry {
3786         char id[SIZEOF_REV];
3787         mode_t mode;
3788         struct tm time;                 /* Date from the author ident. */
3789         char author[75];                /* Author of the commit. */
3790         char name[1];
3791 };
3793 static const char *
3794 tree_path(struct line *line)
3796         return ((struct tree_entry *) line->data)->name;
3800 static int
3801 tree_compare_entry(struct line *line1, struct line *line2)
3803         if (line1->type != line2->type)
3804                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3805         return strcmp(tree_path(line1), tree_path(line2));
3808 static struct line *
3809 tree_entry(struct view *view, enum line_type type, const char *path,
3810            const char *mode, const char *id)
3812         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3813         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3815         if (!entry || !line) {
3816                 free(entry);
3817                 return NULL;
3818         }
3820         strncpy(entry->name, path, strlen(path));
3821         if (mode)
3822                 entry->mode = strtoul(mode, NULL, 8);
3823         if (id)
3824                 string_copy_rev(entry->id, id);
3826         return line;
3829 static bool
3830 tree_read_date(struct view *view, char *text, bool *read_date)
3832         static char author_name[SIZEOF_STR];
3833         static struct tm author_time;
3835         if (!text && *read_date) {
3836                 *read_date = FALSE;
3837                 return TRUE;
3839         } else if (!text) {
3840                 char *path = *opt_path ? opt_path : ".";
3841                 /* Find next entry to process */
3842                 const char *log_file[] = {
3843                         "git", "log", "--no-color", "--pretty=raw",
3844                                 "--cc", "--raw", view->id, "--", path, NULL
3845                 };
3846                 struct io io = {};
3848                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3849                         report("Failed to load tree data");
3850                         return TRUE;
3851                 }
3853                 done_io(view->pipe);
3854                 view->io = io;
3855                 *read_date = TRUE;
3856                 return FALSE;
3858         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3859                 parse_author_line(text + STRING_SIZE("author "),
3860                                   author_name, sizeof(author_name), &author_time);
3862         } else if (*text == ':') {
3863                 char *pos;
3864                 size_t annotated = 1;
3865                 size_t i;
3867                 pos = strchr(text, '\t');
3868                 if (!pos)
3869                         return TRUE;
3870                 text = pos + 1;
3871                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3872                         text += strlen(opt_prefix);
3873                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3874                         text += strlen(opt_path);
3875                 pos = strchr(text, '/');
3876                 if (pos)
3877                         *pos = 0;
3879                 for (i = 1; i < view->lines; i++) {
3880                         struct line *line = &view->line[i];
3881                         struct tree_entry *entry = line->data;
3883                         annotated += !!*entry->author;
3884                         if (*entry->author || strcmp(entry->name, text))
3885                                 continue;
3887                         string_copy(entry->author, author_name);
3888                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3889                         line->dirty = 1;
3890                         break;
3891                 }
3893                 if (annotated == view->lines)
3894                         kill_io(view->pipe);
3895         }
3896         return TRUE;
3899 static bool
3900 tree_read(struct view *view, char *text)
3902         static bool read_date = FALSE;
3903         struct tree_entry *data;
3904         struct line *entry, *line;
3905         enum line_type type;
3906         size_t textlen = text ? strlen(text) : 0;
3907         char *path = text + SIZEOF_TREE_ATTR;
3909         if (read_date || !text)
3910                 return tree_read_date(view, text, &read_date);
3912         if (textlen <= SIZEOF_TREE_ATTR)
3913                 return FALSE;
3914         if (view->lines == 0 &&
3915             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3916                 return FALSE;
3918         /* Strip the path part ... */
3919         if (*opt_path) {
3920                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3921                 size_t striplen = strlen(opt_path);
3923                 if (pathlen > striplen)
3924                         memmove(path, path + striplen,
3925                                 pathlen - striplen + 1);
3927                 /* Insert "link" to parent directory. */
3928                 if (view->lines == 1 &&
3929                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3930                         return FALSE;
3931         }
3933         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3934         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3935         if (!entry)
3936                 return FALSE;
3937         data = entry->data;
3939         /* Skip "Directory ..." and ".." line. */
3940         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3941                 if (tree_compare_entry(line, entry) <= 0)
3942                         continue;
3944                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3946                 line->data = data;
3947                 line->type = type;
3948                 for (; line <= entry; line++)
3949                         line->dirty = line->cleareol = 1;
3950                 return TRUE;
3951         }
3953         if (tree_lineno > view->lineno) {
3954                 view->lineno = tree_lineno;
3955                 tree_lineno = 0;
3956         }
3958         return TRUE;
3961 static bool
3962 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3964         struct tree_entry *entry = line->data;
3966         if (line->type == LINE_TREE_PARENT) {
3967                 if (draw_text(view, line->type, "Directory path /", TRUE))
3968                         return TRUE;
3969         } else {
3970                 char mode[11] = "-r--r--r--";
3972                 if (S_ISDIR(entry->mode)) {
3973                         mode[3] = mode[6] = mode[9] = 'x';
3974                         mode[0] = 'd';
3975                 }
3976                 if (S_ISLNK(entry->mode))
3977                         mode[0] = 'l';
3978                 if (entry->mode & S_IWUSR)
3979                         mode[2] = 'w';
3980                 if (entry->mode & S_IXUSR)
3981                         mode[3] = 'x';
3982                 if (entry->mode & S_IXGRP)
3983                         mode[6] = 'x';
3984                 if (entry->mode & S_IXOTH)
3985                         mode[9] = 'x';
3986                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3987                         return TRUE;
3989                 if (opt_author && draw_author(view, entry->author))
3990                         return TRUE;
3992                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3993                         return TRUE;
3994         }
3995         if (draw_text(view, line->type, entry->name, TRUE))
3996                 return TRUE;
3997         return TRUE;
4000 static void
4001 open_blob_editor()
4003         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4004         int fd = mkstemp(file);
4006         if (fd == -1)
4007                 report("Failed to create temporary file");
4008         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4009                 report("Failed to save blob data to file");
4010         else
4011                 open_editor(FALSE, file);
4012         if (fd != -1)
4013                 unlink(file);
4016 static enum request
4017 tree_request(struct view *view, enum request request, struct line *line)
4019         enum open_flags flags;
4021         switch (request) {
4022         case REQ_VIEW_BLAME:
4023                 if (line->type != LINE_TREE_FILE) {
4024                         report("Blame only supported for files");
4025                         return REQ_NONE;
4026                 }
4028                 string_copy(opt_ref, view->vid);
4029                 return request;
4031         case REQ_EDIT:
4032                 if (line->type != LINE_TREE_FILE) {
4033                         report("Edit only supported for files");
4034                 } else if (!is_head_commit(view->vid)) {
4035                         open_blob_editor();
4036                 } else {
4037                         open_editor(TRUE, opt_file);
4038                 }
4039                 return REQ_NONE;
4041         case REQ_PARENT:
4042                 if (!*opt_path) {
4043                         /* quit view if at top of tree */
4044                         return REQ_VIEW_CLOSE;
4045                 }
4046                 /* fake 'cd  ..' */
4047                 line = &view->line[1];
4048                 break;
4050         case REQ_ENTER:
4051                 break;
4053         default:
4054                 return request;
4055         }
4057         /* Cleanup the stack if the tree view is at a different tree. */
4058         while (!*opt_path && tree_stack)
4059                 pop_tree_stack_entry();
4061         switch (line->type) {
4062         case LINE_TREE_DIR:
4063                 /* Depending on whether it is a subdir or parent (updir?) link
4064                  * mangle the path buffer. */
4065                 if (line == &view->line[1] && *opt_path) {
4066                         pop_tree_stack_entry();
4068                 } else {
4069                         const char *basename = tree_path(line);
4071                         push_tree_stack_entry(basename, view->lineno);
4072                 }
4074                 /* Trees and subtrees share the same ID, so they are not not
4075                  * unique like blobs. */
4076                 flags = OPEN_RELOAD;
4077                 request = REQ_VIEW_TREE;
4078                 break;
4080         case LINE_TREE_FILE:
4081                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4082                 request = REQ_VIEW_BLOB;
4083                 break;
4085         default:
4086                 return REQ_NONE;
4087         }
4089         open_view(view, request, flags);
4090         if (request == REQ_VIEW_TREE)
4091                 view->lineno = tree_lineno;
4093         return REQ_NONE;
4096 static void
4097 tree_select(struct view *view, struct line *line)
4099         struct tree_entry *entry = line->data;
4101         if (line->type == LINE_TREE_FILE) {
4102                 string_copy_rev(ref_blob, entry->id);
4103                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4105         } else if (line->type != LINE_TREE_DIR) {
4106                 return;
4107         }
4109         string_copy_rev(view->ref, entry->id);
4112 static const char *tree_argv[SIZEOF_ARG] = {
4113         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4114 };
4116 static struct view_ops tree_ops = {
4117         "file",
4118         tree_argv,
4119         NULL,
4120         tree_read,
4121         tree_draw,
4122         tree_request,
4123         pager_grep,
4124         tree_select,
4125 };
4127 static bool
4128 blob_read(struct view *view, char *line)
4130         if (!line)
4131                 return TRUE;
4132         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4135 static enum request
4136 blob_request(struct view *view, enum request request, struct line *line)
4138         switch (request) {
4139         case REQ_EDIT:
4140                 open_blob_editor();
4141                 return REQ_NONE;
4142         default:
4143                 return pager_request(view, request, line);
4144         }
4147 static const char *blob_argv[SIZEOF_ARG] = {
4148         "git", "cat-file", "blob", "%(blob)", NULL
4149 };
4151 static struct view_ops blob_ops = {
4152         "line",
4153         blob_argv,
4154         NULL,
4155         blob_read,
4156         pager_draw,
4157         blob_request,
4158         pager_grep,
4159         pager_select,
4160 };
4162 /*
4163  * Blame backend
4164  *
4165  * Loading the blame view is a two phase job:
4166  *
4167  *  1. File content is read either using opt_file from the
4168  *     filesystem or using git-cat-file.
4169  *  2. Then blame information is incrementally added by
4170  *     reading output from git-blame.
4171  */
4173 static const char *blame_head_argv[] = {
4174         "git", "blame", "--incremental", "--", "%(file)", NULL
4175 };
4177 static const char *blame_ref_argv[] = {
4178         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4179 };
4181 static const char *blame_cat_file_argv[] = {
4182         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4183 };
4185 struct blame_commit {
4186         char id[SIZEOF_REV];            /* SHA1 ID. */
4187         char title[128];                /* First line of the commit message. */
4188         char author[75];                /* Author of the commit. */
4189         struct tm time;                 /* Date from the author ident. */
4190         char filename[128];             /* Name of file. */
4191         bool has_previous;              /* Was a "previous" line detected. */
4192 };
4194 struct blame {
4195         struct blame_commit *commit;
4196         char text[1];
4197 };
4199 static bool
4200 blame_open(struct view *view)
4202         if (*opt_ref || !io_open(&view->io, opt_file)) {
4203                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4204                         return FALSE;
4205         }
4207         setup_update(view, opt_file);
4208         string_format(view->ref, "%s ...", opt_file);
4210         return TRUE;
4213 static struct blame_commit *
4214 get_blame_commit(struct view *view, const char *id)
4216         size_t i;
4218         for (i = 0; i < view->lines; i++) {
4219                 struct blame *blame = view->line[i].data;
4221                 if (!blame->commit)
4222                         continue;
4224                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4225                         return blame->commit;
4226         }
4228         {
4229                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4231                 if (commit)
4232                         string_ncopy(commit->id, id, SIZEOF_REV);
4233                 return commit;
4234         }
4237 static bool
4238 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4240         const char *pos = *posref;
4242         *posref = NULL;
4243         pos = strchr(pos + 1, ' ');
4244         if (!pos || !isdigit(pos[1]))
4245                 return FALSE;
4246         *number = atoi(pos + 1);
4247         if (*number < min || *number > max)
4248                 return FALSE;
4250         *posref = pos;
4251         return TRUE;
4254 static struct blame_commit *
4255 parse_blame_commit(struct view *view, const char *text, int *blamed)
4257         struct blame_commit *commit;
4258         struct blame *blame;
4259         const char *pos = text + SIZEOF_REV - 1;
4260         size_t lineno;
4261         size_t group;
4263         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4264                 return NULL;
4266         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4267             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4268                 return NULL;
4270         commit = get_blame_commit(view, text);
4271         if (!commit)
4272                 return NULL;
4274         *blamed += group;
4275         while (group--) {
4276                 struct line *line = &view->line[lineno + group - 1];
4278                 blame = line->data;
4279                 blame->commit = commit;
4280                 line->dirty = 1;
4281         }
4283         return commit;
4286 static bool
4287 blame_read_file(struct view *view, const char *line, bool *read_file)
4289         if (!line) {
4290                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4291                 struct io io = {};
4293                 if (view->lines == 0 && !view->parent)
4294                         die("No blame exist for %s", view->vid);
4296                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4297                         report("Failed to load blame data");
4298                         return TRUE;
4299                 }
4301                 done_io(view->pipe);
4302                 view->io = io;
4303                 *read_file = FALSE;
4304                 return FALSE;
4306         } else {
4307                 size_t linelen = strlen(line);
4308                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4310                 blame->commit = NULL;
4311                 strncpy(blame->text, line, linelen);
4312                 blame->text[linelen] = 0;
4313                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4314         }
4317 static bool
4318 match_blame_header(const char *name, char **line)
4320         size_t namelen = strlen(name);
4321         bool matched = !strncmp(name, *line, namelen);
4323         if (matched)
4324                 *line += namelen;
4326         return matched;
4329 static bool
4330 blame_read(struct view *view, char *line)
4332         static struct blame_commit *commit = NULL;
4333         static int blamed = 0;
4334         static time_t author_time;
4335         static bool read_file = TRUE;
4337         if (read_file)
4338                 return blame_read_file(view, line, &read_file);
4340         if (!line) {
4341                 /* Reset all! */
4342                 commit = NULL;
4343                 blamed = 0;
4344                 read_file = TRUE;
4345                 string_format(view->ref, "%s", view->vid);
4346                 if (view_is_displayed(view)) {
4347                         update_view_title(view);
4348                         redraw_view_from(view, 0);
4349                 }
4350                 return TRUE;
4351         }
4353         if (!commit) {
4354                 commit = parse_blame_commit(view, line, &blamed);
4355                 string_format(view->ref, "%s %2d%%", view->vid,
4356                               view->lines ? blamed * 100 / view->lines : 0);
4358         } else if (match_blame_header("author ", &line)) {
4359                 string_ncopy(commit->author, line, strlen(line));
4361         } else if (match_blame_header("author-time ", &line)) {
4362                 author_time = (time_t) atol(line);
4364         } else if (match_blame_header("author-tz ", &line)) {
4365                 long tz;
4367                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4368                 tz += ('0' - line[2]) * 60 * 60;
4369                 tz += ('0' - line[3]) * 60;
4370                 tz += ('0' - line[4]) * 60;
4372                 if (line[0] == '-')
4373                         tz = -tz;
4375                 author_time -= tz;
4376                 gmtime_r(&author_time, &commit->time);
4378         } else if (match_blame_header("summary ", &line)) {
4379                 string_ncopy(commit->title, line, strlen(line));
4381         } else if (match_blame_header("previous ", &line)) {
4382                 commit->has_previous = TRUE;
4384         } else if (match_blame_header("filename ", &line)) {
4385                 string_ncopy(commit->filename, line, strlen(line));
4386                 commit = NULL;
4387         }
4389         return TRUE;
4392 static bool
4393 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4395         struct blame *blame = line->data;
4396         struct tm *time = NULL;
4397         const char *id = NULL, *author = NULL;
4399         if (blame->commit && *blame->commit->filename) {
4400                 id = blame->commit->id;
4401                 author = blame->commit->author;
4402                 time = &blame->commit->time;
4403         }
4405         if (opt_date && draw_date(view, time))
4406                 return TRUE;
4408         if (opt_author && draw_author(view, author))
4409                 return TRUE;
4411         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4412                 return TRUE;
4414         if (draw_lineno(view, lineno))
4415                 return TRUE;
4417         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4418         return TRUE;
4421 static bool
4422 check_blame_commit(struct blame *blame)
4424         if (!blame->commit)
4425                 report("Commit data not loaded yet");
4426         else if (!strcmp(blame->commit->id, NULL_ID))
4427                 report("No commit exist for the selected line");
4428         else
4429                 return TRUE;
4430         return FALSE;
4433 static enum request
4434 blame_request(struct view *view, enum request request, struct line *line)
4436         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4437         struct blame *blame = line->data;
4439         switch (request) {
4440         case REQ_VIEW_BLAME:
4441                 if (check_blame_commit(blame)) {
4442                         string_copy(opt_ref, blame->commit->id);
4443                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4444                 }
4445                 break;
4447         case REQ_PARENT:
4448                 if (check_blame_commit(blame) &&
4449                     select_commit_parent(blame->commit->id, opt_ref))
4450                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4451                 break;
4453         case REQ_ENTER:
4454                 if (!blame->commit) {
4455                         report("No commit loaded yet");
4456                         break;
4457                 }
4459                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4460                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4461                         break;
4463                 if (!strcmp(blame->commit->id, NULL_ID)) {
4464                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4465                         const char *diff_index_argv[] = {
4466                                 "git", "diff-index", "--root", "--patch-with-stat",
4467                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4468                         };
4470                         if (!blame->commit->has_previous) {
4471                                 diff_index_argv[1] = "diff";
4472                                 diff_index_argv[2] = "--no-color";
4473                                 diff_index_argv[6] = "--";
4474                                 diff_index_argv[7] = "/dev/null";
4475                         }
4477                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4478                                 report("Failed to allocate diff command");
4479                                 break;
4480                         }
4481                         flags |= OPEN_PREPARED;
4482                 }
4484                 open_view(view, REQ_VIEW_DIFF, flags);
4485                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4486                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4487                 break;
4489         default:
4490                 return request;
4491         }
4493         return REQ_NONE;
4496 static bool
4497 blame_grep(struct view *view, struct line *line)
4499         struct blame *blame = line->data;
4500         struct blame_commit *commit = blame->commit;
4501         regmatch_t pmatch;
4503 #define MATCH(text, on)                                                 \
4504         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4506         if (commit) {
4507                 char buf[DATE_COLS + 1];
4509                 if (MATCH(commit->title, 1) ||
4510                     MATCH(commit->author, opt_author) ||
4511                     MATCH(commit->id, opt_date))
4512                         return TRUE;
4514                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4515                     MATCH(buf, 1))
4516                         return TRUE;
4517         }
4519         return MATCH(blame->text, 1);
4521 #undef MATCH
4524 static void
4525 blame_select(struct view *view, struct line *line)
4527         struct blame *blame = line->data;
4528         struct blame_commit *commit = blame->commit;
4530         if (!commit)
4531                 return;
4533         if (!strcmp(commit->id, NULL_ID))
4534                 string_ncopy(ref_commit, "HEAD", 4);
4535         else
4536                 string_copy_rev(ref_commit, commit->id);
4539 static struct view_ops blame_ops = {
4540         "line",
4541         NULL,
4542         blame_open,
4543         blame_read,
4544         blame_draw,
4545         blame_request,
4546         blame_grep,
4547         blame_select,
4548 };
4550 /*
4551  * Status backend
4552  */
4554 struct status {
4555         char status;
4556         struct {
4557                 mode_t mode;
4558                 char rev[SIZEOF_REV];
4559                 char name[SIZEOF_STR];
4560         } old;
4561         struct {
4562                 mode_t mode;
4563                 char rev[SIZEOF_REV];
4564                 char name[SIZEOF_STR];
4565         } new;
4566 };
4568 static char status_onbranch[SIZEOF_STR];
4569 static struct status stage_status;
4570 static enum line_type stage_line_type;
4571 static size_t stage_chunks;
4572 static int *stage_chunk;
4574 /* This should work even for the "On branch" line. */
4575 static inline bool
4576 status_has_none(struct view *view, struct line *line)
4578         return line < view->line + view->lines && !line[1].data;
4581 /* Get fields from the diff line:
4582  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4583  */
4584 static inline bool
4585 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4587         const char *old_mode = buf +  1;
4588         const char *new_mode = buf +  8;
4589         const char *old_rev  = buf + 15;
4590         const char *new_rev  = buf + 56;
4591         const char *status   = buf + 97;
4593         if (bufsize < 98 ||
4594             old_mode[-1] != ':' ||
4595             new_mode[-1] != ' ' ||
4596             old_rev[-1]  != ' ' ||
4597             new_rev[-1]  != ' ' ||
4598             status[-1]   != ' ')
4599                 return FALSE;
4601         file->status = *status;
4603         string_copy_rev(file->old.rev, old_rev);
4604         string_copy_rev(file->new.rev, new_rev);
4606         file->old.mode = strtoul(old_mode, NULL, 8);
4607         file->new.mode = strtoul(new_mode, NULL, 8);
4609         file->old.name[0] = file->new.name[0] = 0;
4611         return TRUE;
4614 static bool
4615 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4617         struct status *file = NULL;
4618         struct status *unmerged = NULL;
4619         char *buf;
4620         struct io io = {};
4622         if (!run_io(&io, argv, NULL, IO_RD))
4623                 return FALSE;
4625         add_line_data(view, NULL, type);
4627         while ((buf = io_get(&io, 0, TRUE))) {
4628                 if (!file) {
4629                         file = calloc(1, sizeof(*file));
4630                         if (!file || !add_line_data(view, file, type))
4631                                 goto error_out;
4632                 }
4634                 /* Parse diff info part. */
4635                 if (status) {
4636                         file->status = status;
4637                         if (status == 'A')
4638                                 string_copy(file->old.rev, NULL_ID);
4640                 } else if (!file->status) {
4641                         if (!status_get_diff(file, buf, strlen(buf)))
4642                                 goto error_out;
4644                         buf = io_get(&io, 0, TRUE);
4645                         if (!buf)
4646                                 break;
4648                         /* Collapse all 'M'odified entries that follow a
4649                          * associated 'U'nmerged entry. */
4650                         if (file->status == 'U') {
4651                                 unmerged = file;
4653                         } else if (unmerged) {
4654                                 int collapse = !strcmp(buf, unmerged->new.name);
4656                                 unmerged = NULL;
4657                                 if (collapse) {
4658                                         free(file);
4659                                         file = NULL;
4660                                         view->lines--;
4661                                         continue;
4662                                 }
4663                         }
4664                 }
4666                 /* Grab the old name for rename/copy. */
4667                 if (!*file->old.name &&
4668                     (file->status == 'R' || file->status == 'C')) {
4669                         string_ncopy(file->old.name, buf, strlen(buf));
4671                         buf = io_get(&io, 0, TRUE);
4672                         if (!buf)
4673                                 break;
4674                 }
4676                 /* git-ls-files just delivers a NUL separated list of
4677                  * file names similar to the second half of the
4678                  * git-diff-* output. */
4679                 string_ncopy(file->new.name, buf, strlen(buf));
4680                 if (!*file->old.name)
4681                         string_copy(file->old.name, file->new.name);
4682                 file = NULL;
4683         }
4685         if (io_error(&io)) {
4686 error_out:
4687                 done_io(&io);
4688                 return FALSE;
4689         }
4691         if (!view->line[view->lines - 1].data)
4692                 add_line_data(view, NULL, LINE_STAT_NONE);
4694         done_io(&io);
4695         return TRUE;
4698 /* Don't show unmerged entries in the staged section. */
4699 static const char *status_diff_index_argv[] = {
4700         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4701                              "--cached", "-M", "HEAD", NULL
4702 };
4704 static const char *status_diff_files_argv[] = {
4705         "git", "diff-files", "-z", NULL
4706 };
4708 static const char *status_list_other_argv[] = {
4709         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4710 };
4712 static const char *status_list_no_head_argv[] = {
4713         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4714 };
4716 static const char *update_index_argv[] = {
4717         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4718 };
4720 /* Restore the previous line number to stay in the context or select a
4721  * line with something that can be updated. */
4722 static void
4723 status_restore(struct view *view)
4725         if (view->p_lineno >= view->lines)
4726                 view->p_lineno = view->lines - 1;
4727         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4728                 view->p_lineno++;
4729         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4730                 view->p_lineno--;
4732         /* If the above fails, always skip the "On branch" line. */
4733         if (view->p_lineno < view->lines)
4734                 view->lineno = view->p_lineno;
4735         else
4736                 view->lineno = 1;
4738         if (view->lineno < view->offset)
4739                 view->offset = view->lineno;
4740         else if (view->offset + view->height <= view->lineno)
4741                 view->offset = view->lineno - view->height + 1;
4743         view->p_restore = FALSE;
4746 /* First parse staged info using git-diff-index(1), then parse unstaged
4747  * info using git-diff-files(1), and finally untracked files using
4748  * git-ls-files(1). */
4749 static bool
4750 status_open(struct view *view)
4752         reset_view(view);
4754         add_line_data(view, NULL, LINE_STAT_HEAD);
4755         if (is_initial_commit())
4756                 string_copy(status_onbranch, "Initial commit");
4757         else if (!*opt_head)
4758                 string_copy(status_onbranch, "Not currently on any branch");
4759         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4760                 return FALSE;
4762         run_io_bg(update_index_argv);
4764         if (is_initial_commit()) {
4765                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4766                         return FALSE;
4767         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4768                 return FALSE;
4769         }
4771         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4772             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4773                 return FALSE;
4775         /* Restore the exact position or use the specialized restore
4776          * mode? */
4777         if (!view->p_restore)
4778                 status_restore(view);
4779         return TRUE;
4782 static bool
4783 status_draw(struct view *view, struct line *line, unsigned int lineno)
4785         struct status *status = line->data;
4786         enum line_type type;
4787         const char *text;
4789         if (!status) {
4790                 switch (line->type) {
4791                 case LINE_STAT_STAGED:
4792                         type = LINE_STAT_SECTION;
4793                         text = "Changes to be committed:";
4794                         break;
4796                 case LINE_STAT_UNSTAGED:
4797                         type = LINE_STAT_SECTION;
4798                         text = "Changed but not updated:";
4799                         break;
4801                 case LINE_STAT_UNTRACKED:
4802                         type = LINE_STAT_SECTION;
4803                         text = "Untracked files:";
4804                         break;
4806                 case LINE_STAT_NONE:
4807                         type = LINE_DEFAULT;
4808                         text = "    (no files)";
4809                         break;
4811                 case LINE_STAT_HEAD:
4812                         type = LINE_STAT_HEAD;
4813                         text = status_onbranch;
4814                         break;
4816                 default:
4817                         return FALSE;
4818                 }
4819         } else {
4820                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4822                 buf[0] = status->status;
4823                 if (draw_text(view, line->type, buf, TRUE))
4824                         return TRUE;
4825                 type = LINE_DEFAULT;
4826                 text = status->new.name;
4827         }
4829         draw_text(view, type, text, TRUE);
4830         return TRUE;
4833 static enum request
4834 status_enter(struct view *view, struct line *line)
4836         struct status *status = line->data;
4837         const char *oldpath = status ? status->old.name : NULL;
4838         /* Diffs for unmerged entries are empty when passing the new
4839          * path, so leave it empty. */
4840         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4841         const char *info;
4842         enum open_flags split;
4843         struct view *stage = VIEW(REQ_VIEW_STAGE);
4845         if (line->type == LINE_STAT_NONE ||
4846             (!status && line[1].type == LINE_STAT_NONE)) {
4847                 report("No file to diff");
4848                 return REQ_NONE;
4849         }
4851         switch (line->type) {
4852         case LINE_STAT_STAGED:
4853                 if (is_initial_commit()) {
4854                         const char *no_head_diff_argv[] = {
4855                                 "git", "diff", "--no-color", "--patch-with-stat",
4856                                         "--", "/dev/null", newpath, NULL
4857                         };
4859                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4860                                 return REQ_QUIT;
4861                 } else {
4862                         const char *index_show_argv[] = {
4863                                 "git", "diff-index", "--root", "--patch-with-stat",
4864                                         "-C", "-M", "--cached", "HEAD", "--",
4865                                         oldpath, newpath, NULL
4866                         };
4868                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4869                                 return REQ_QUIT;
4870                 }
4872                 if (status)
4873                         info = "Staged changes to %s";
4874                 else
4875                         info = "Staged changes";
4876                 break;
4878         case LINE_STAT_UNSTAGED:
4879         {
4880                 const char *files_show_argv[] = {
4881                         "git", "diff-files", "--root", "--patch-with-stat",
4882                                 "-C", "-M", "--", oldpath, newpath, NULL
4883                 };
4885                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4886                         return REQ_QUIT;
4887                 if (status)
4888                         info = "Unstaged changes to %s";
4889                 else
4890                         info = "Unstaged changes";
4891                 break;
4892         }
4893         case LINE_STAT_UNTRACKED:
4894                 if (!newpath) {
4895                         report("No file to show");
4896                         return REQ_NONE;
4897                 }
4899                 if (!suffixcmp(status->new.name, -1, "/")) {
4900                         report("Cannot display a directory");
4901                         return REQ_NONE;
4902                 }
4904                 if (!prepare_update_file(stage, newpath))
4905                         return REQ_QUIT;
4906                 info = "Untracked file %s";
4907                 break;
4909         case LINE_STAT_HEAD:
4910                 return REQ_NONE;
4912         default:
4913                 die("line type %d not handled in switch", line->type);
4914         }
4916         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4917         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4918         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4919                 if (status) {
4920                         stage_status = *status;
4921                 } else {
4922                         memset(&stage_status, 0, sizeof(stage_status));
4923                 }
4925                 stage_line_type = line->type;
4926                 stage_chunks = 0;
4927                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4928         }
4930         return REQ_NONE;
4933 static bool
4934 status_exists(struct status *status, enum line_type type)
4936         struct view *view = VIEW(REQ_VIEW_STATUS);
4937         unsigned long lineno;
4939         for (lineno = 0; lineno < view->lines; lineno++) {
4940                 struct line *line = &view->line[lineno];
4941                 struct status *pos = line->data;
4943                 if (line->type != type)
4944                         continue;
4945                 if (!pos && (!status || !status->status) && line[1].data) {
4946                         select_view_line(view, lineno);
4947                         return TRUE;
4948                 }
4949                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4950                         select_view_line(view, lineno);
4951                         return TRUE;
4952                 }
4953         }
4955         return FALSE;
4959 static bool
4960 status_update_prepare(struct io *io, enum line_type type)
4962         const char *staged_argv[] = {
4963                 "git", "update-index", "-z", "--index-info", NULL
4964         };
4965         const char *others_argv[] = {
4966                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4967         };
4969         switch (type) {
4970         case LINE_STAT_STAGED:
4971                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4973         case LINE_STAT_UNSTAGED:
4974                 return run_io(io, others_argv, opt_cdup, IO_WR);
4976         case LINE_STAT_UNTRACKED:
4977                 return run_io(io, others_argv, NULL, IO_WR);
4979         default:
4980                 die("line type %d not handled in switch", type);
4981                 return FALSE;
4982         }
4985 static bool
4986 status_update_write(struct io *io, struct status *status, enum line_type type)
4988         char buf[SIZEOF_STR];
4989         size_t bufsize = 0;
4991         switch (type) {
4992         case LINE_STAT_STAGED:
4993                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4994                                         status->old.mode,
4995                                         status->old.rev,
4996                                         status->old.name, 0))
4997                         return FALSE;
4998                 break;
5000         case LINE_STAT_UNSTAGED:
5001         case LINE_STAT_UNTRACKED:
5002                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5003                         return FALSE;
5004                 break;
5006         default:
5007                 die("line type %d not handled in switch", type);
5008         }
5010         return io_write(io, buf, bufsize);
5013 static bool
5014 status_update_file(struct status *status, enum line_type type)
5016         struct io io = {};
5017         bool result;
5019         if (!status_update_prepare(&io, type))
5020                 return FALSE;
5022         result = status_update_write(&io, status, type);
5023         done_io(&io);
5024         return result;
5027 static bool
5028 status_update_files(struct view *view, struct line *line)
5030         struct io io = {};
5031         bool result = TRUE;
5032         struct line *pos = view->line + view->lines;
5033         int files = 0;
5034         int file, done;
5036         if (!status_update_prepare(&io, line->type))
5037                 return FALSE;
5039         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5040                 files++;
5042         for (file = 0, done = 0; result && file < files; line++, file++) {
5043                 int almost_done = file * 100 / files;
5045                 if (almost_done > done) {
5046                         done = almost_done;
5047                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5048                                       file, files, done);
5049                         update_view_title(view);
5050                 }
5051                 result = status_update_write(&io, line->data, line->type);
5052         }
5054         done_io(&io);
5055         return result;
5058 static bool
5059 status_update(struct view *view)
5061         struct line *line = &view->line[view->lineno];
5063         assert(view->lines);
5065         if (!line->data) {
5066                 /* This should work even for the "On branch" line. */
5067                 if (line < view->line + view->lines && !line[1].data) {
5068                         report("Nothing to update");
5069                         return FALSE;
5070                 }
5072                 if (!status_update_files(view, line + 1)) {
5073                         report("Failed to update file status");
5074                         return FALSE;
5075                 }
5077         } else if (!status_update_file(line->data, line->type)) {
5078                 report("Failed to update file status");
5079                 return FALSE;
5080         }
5082         return TRUE;
5085 static bool
5086 status_revert(struct status *status, enum line_type type, bool has_none)
5088         if (!status || type != LINE_STAT_UNSTAGED) {
5089                 if (type == LINE_STAT_STAGED) {
5090                         report("Cannot revert changes to staged files");
5091                 } else if (type == LINE_STAT_UNTRACKED) {
5092                         report("Cannot revert changes to untracked files");
5093                 } else if (has_none) {
5094                         report("Nothing to revert");
5095                 } else {
5096                         report("Cannot revert changes to multiple files");
5097                 }
5098                 return FALSE;
5100         } else {
5101                 const char *checkout_argv[] = {
5102                         "git", "checkout", "--", status->old.name, NULL
5103                 };
5105                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5106                         return FALSE;
5107                 return run_io_fg(checkout_argv, opt_cdup);
5108         }
5111 static enum request
5112 status_request(struct view *view, enum request request, struct line *line)
5114         struct status *status = line->data;
5116         switch (request) {
5117         case REQ_STATUS_UPDATE:
5118                 if (!status_update(view))
5119                         return REQ_NONE;
5120                 break;
5122         case REQ_STATUS_REVERT:
5123                 if (!status_revert(status, line->type, status_has_none(view, line)))
5124                         return REQ_NONE;
5125                 break;
5127         case REQ_STATUS_MERGE:
5128                 if (!status || status->status != 'U') {
5129                         report("Merging only possible for files with unmerged status ('U').");
5130                         return REQ_NONE;
5131                 }
5132                 open_mergetool(status->new.name);
5133                 break;
5135         case REQ_EDIT:
5136                 if (!status)
5137                         return request;
5138                 if (status->status == 'D') {
5139                         report("File has been deleted.");
5140                         return REQ_NONE;
5141                 }
5143                 open_editor(status->status != '?', status->new.name);
5144                 break;
5146         case REQ_VIEW_BLAME:
5147                 if (status) {
5148                         string_copy(opt_file, status->new.name);
5149                         opt_ref[0] = 0;
5150                 }
5151                 return request;
5153         case REQ_ENTER:
5154                 /* After returning the status view has been split to
5155                  * show the stage view. No further reloading is
5156                  * necessary. */
5157                 status_enter(view, line);
5158                 return REQ_NONE;
5160         case REQ_REFRESH:
5161                 /* Simply reload the view. */
5162                 break;
5164         default:
5165                 return request;
5166         }
5168         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5170         return REQ_NONE;
5173 static void
5174 status_select(struct view *view, struct line *line)
5176         struct status *status = line->data;
5177         char file[SIZEOF_STR] = "all files";
5178         const char *text;
5179         const char *key;
5181         if (status && !string_format(file, "'%s'", status->new.name))
5182                 return;
5184         if (!status && line[1].type == LINE_STAT_NONE)
5185                 line++;
5187         switch (line->type) {
5188         case LINE_STAT_STAGED:
5189                 text = "Press %s to unstage %s for commit";
5190                 break;
5192         case LINE_STAT_UNSTAGED:
5193                 text = "Press %s to stage %s for commit";
5194                 break;
5196         case LINE_STAT_UNTRACKED:
5197                 text = "Press %s to stage %s for addition";
5198                 break;
5200         case LINE_STAT_HEAD:
5201         case LINE_STAT_NONE:
5202                 text = "Nothing to update";
5203                 break;
5205         default:
5206                 die("line type %d not handled in switch", line->type);
5207         }
5209         if (status && status->status == 'U') {
5210                 text = "Press %s to resolve conflict in %s";
5211                 key = get_key(REQ_STATUS_MERGE);
5213         } else {
5214                 key = get_key(REQ_STATUS_UPDATE);
5215         }
5217         string_format(view->ref, text, key, file);
5220 static bool
5221 status_grep(struct view *view, struct line *line)
5223         struct status *status = line->data;
5224         enum { S_STATUS, S_NAME, S_END } state;
5225         char buf[2] = "?";
5226         regmatch_t pmatch;
5228         if (!status)
5229                 return FALSE;
5231         for (state = S_STATUS; state < S_END; state++) {
5232                 const char *text;
5234                 switch (state) {
5235                 case S_NAME:    text = status->new.name;        break;
5236                 case S_STATUS:
5237                         buf[0] = status->status;
5238                         text = buf;
5239                         break;
5241                 default:
5242                         return FALSE;
5243                 }
5245                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5246                         return TRUE;
5247         }
5249         return FALSE;
5252 static struct view_ops status_ops = {
5253         "file",
5254         NULL,
5255         status_open,
5256         NULL,
5257         status_draw,
5258         status_request,
5259         status_grep,
5260         status_select,
5261 };
5264 static bool
5265 stage_diff_write(struct io *io, struct line *line, struct line *end)
5267         while (line < end) {
5268                 if (!io_write(io, line->data, strlen(line->data)) ||
5269                     !io_write(io, "\n", 1))
5270                         return FALSE;
5271                 line++;
5272                 if (line->type == LINE_DIFF_CHUNK ||
5273                     line->type == LINE_DIFF_HEADER)
5274                         break;
5275         }
5277         return TRUE;
5280 static struct line *
5281 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5283         for (; view->line < line; line--)
5284                 if (line->type == type)
5285                         return line;
5287         return NULL;
5290 static bool
5291 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5293         const char *apply_argv[SIZEOF_ARG] = {
5294                 "git", "apply", "--whitespace=nowarn", NULL
5295         };
5296         struct line *diff_hdr;
5297         struct io io = {};
5298         int argc = 3;
5300         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5301         if (!diff_hdr)
5302                 return FALSE;
5304         if (!revert)
5305                 apply_argv[argc++] = "--cached";
5306         if (revert || stage_line_type == LINE_STAT_STAGED)
5307                 apply_argv[argc++] = "-R";
5308         apply_argv[argc++] = "-";
5309         apply_argv[argc++] = NULL;
5310         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5311                 return FALSE;
5313         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5314             !stage_diff_write(&io, chunk, view->line + view->lines))
5315                 chunk = NULL;
5317         done_io(&io);
5318         run_io_bg(update_index_argv);
5320         return chunk ? TRUE : FALSE;
5323 static bool
5324 stage_update(struct view *view, struct line *line)
5326         struct line *chunk = NULL;
5328         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5329                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5331         if (chunk) {
5332                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5333                         report("Failed to apply chunk");
5334                         return FALSE;
5335                 }
5337         } else if (!stage_status.status) {
5338                 view = VIEW(REQ_VIEW_STATUS);
5340                 for (line = view->line; line < view->line + view->lines; line++)
5341                         if (line->type == stage_line_type)
5342                                 break;
5344                 if (!status_update_files(view, line + 1)) {
5345                         report("Failed to update files");
5346                         return FALSE;
5347                 }
5349         } else if (!status_update_file(&stage_status, stage_line_type)) {
5350                 report("Failed to update file");
5351                 return FALSE;
5352         }
5354         return TRUE;
5357 static bool
5358 stage_revert(struct view *view, struct line *line)
5360         struct line *chunk = NULL;
5362         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5363                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5365         if (chunk) {
5366                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5367                         return FALSE;
5369                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5370                         report("Failed to revert chunk");
5371                         return FALSE;
5372                 }
5373                 return TRUE;
5375         } else {
5376                 return status_revert(stage_status.status ? &stage_status : NULL,
5377                                      stage_line_type, FALSE);
5378         }
5382 static void
5383 stage_next(struct view *view, struct line *line)
5385         int i;
5387         if (!stage_chunks) {
5388                 static size_t alloc = 0;
5389                 int *tmp;
5391                 for (line = view->line; line < view->line + view->lines; line++) {
5392                         if (line->type != LINE_DIFF_CHUNK)
5393                                 continue;
5395                         tmp = realloc_items(stage_chunk, &alloc,
5396                                             stage_chunks, sizeof(*tmp));
5397                         if (!tmp) {
5398                                 report("Allocation failure");
5399                                 return;
5400                         }
5402                         stage_chunk = tmp;
5403                         stage_chunk[stage_chunks++] = line - view->line;
5404                 }
5405         }
5407         for (i = 0; i < stage_chunks; i++) {
5408                 if (stage_chunk[i] > view->lineno) {
5409                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5410                         report("Chunk %d of %d", i + 1, stage_chunks);
5411                         return;
5412                 }
5413         }
5415         report("No next chunk found");
5418 static enum request
5419 stage_request(struct view *view, enum request request, struct line *line)
5421         switch (request) {
5422         case REQ_STATUS_UPDATE:
5423                 if (!stage_update(view, line))
5424                         return REQ_NONE;
5425                 break;
5427         case REQ_STATUS_REVERT:
5428                 if (!stage_revert(view, line))
5429                         return REQ_NONE;
5430                 break;
5432         case REQ_STAGE_NEXT:
5433                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5434                         report("File is untracked; press %s to add",
5435                                get_key(REQ_STATUS_UPDATE));
5436                         return REQ_NONE;
5437                 }
5438                 stage_next(view, line);
5439                 return REQ_NONE;
5441         case REQ_EDIT:
5442                 if (!stage_status.new.name[0])
5443                         return request;
5444                 if (stage_status.status == 'D') {
5445                         report("File has been deleted.");
5446                         return REQ_NONE;
5447                 }
5449                 open_editor(stage_status.status != '?', stage_status.new.name);
5450                 break;
5452         case REQ_REFRESH:
5453                 /* Reload everything ... */
5454                 break;
5456         case REQ_VIEW_BLAME:
5457                 if (stage_status.new.name[0]) {
5458                         string_copy(opt_file, stage_status.new.name);
5459                         opt_ref[0] = 0;
5460                 }
5461                 return request;
5463         case REQ_ENTER:
5464                 return pager_request(view, request, line);
5466         default:
5467                 return request;
5468         }
5470         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5471         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5473         /* Check whether the staged entry still exists, and close the
5474          * stage view if it doesn't. */
5475         if (!status_exists(&stage_status, stage_line_type)) {
5476                 status_restore(VIEW(REQ_VIEW_STATUS));
5477                 return REQ_VIEW_CLOSE;
5478         }
5480         if (stage_line_type == LINE_STAT_UNTRACKED) {
5481                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5482                         report("Cannot display a directory");
5483                         return REQ_NONE;
5484                 }
5486                 if (!prepare_update_file(view, stage_status.new.name)) {
5487                         report("Failed to open file: %s", strerror(errno));
5488                         return REQ_NONE;
5489                 }
5490         }
5491         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5493         return REQ_NONE;
5496 static struct view_ops stage_ops = {
5497         "line",
5498         NULL,
5499         NULL,
5500         pager_read,
5501         pager_draw,
5502         stage_request,
5503         pager_grep,
5504         pager_select,
5505 };
5508 /*
5509  * Revision graph
5510  */
5512 struct commit {
5513         char id[SIZEOF_REV];            /* SHA1 ID. */
5514         char title[128];                /* First line of the commit message. */
5515         char author[75];                /* Author of the commit. */
5516         struct tm time;                 /* Date from the author ident. */
5517         struct ref **refs;              /* Repository references. */
5518         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5519         size_t graph_size;              /* The width of the graph array. */
5520         bool has_parents;               /* Rewritten --parents seen. */
5521 };
5523 /* Size of rev graph with no  "padding" columns */
5524 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5526 struct rev_graph {
5527         struct rev_graph *prev, *next, *parents;
5528         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5529         size_t size;
5530         struct commit *commit;
5531         size_t pos;
5532         unsigned int boundary:1;
5533 };
5535 /* Parents of the commit being visualized. */
5536 static struct rev_graph graph_parents[4];
5538 /* The current stack of revisions on the graph. */
5539 static struct rev_graph graph_stacks[4] = {
5540         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5541         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5542         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5543         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5544 };
5546 static inline bool
5547 graph_parent_is_merge(struct rev_graph *graph)
5549         return graph->parents->size > 1;
5552 static inline void
5553 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5555         struct commit *commit = graph->commit;
5557         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5558                 commit->graph[commit->graph_size++] = symbol;
5561 static void
5562 clear_rev_graph(struct rev_graph *graph)
5564         graph->boundary = 0;
5565         graph->size = graph->pos = 0;
5566         graph->commit = NULL;
5567         memset(graph->parents, 0, sizeof(*graph->parents));
5570 static void
5571 done_rev_graph(struct rev_graph *graph)
5573         if (graph_parent_is_merge(graph) &&
5574             graph->pos < graph->size - 1 &&
5575             graph->next->size == graph->size + graph->parents->size - 1) {
5576                 size_t i = graph->pos + graph->parents->size - 1;
5578                 graph->commit->graph_size = i * 2;
5579                 while (i < graph->next->size - 1) {
5580                         append_to_rev_graph(graph, ' ');
5581                         append_to_rev_graph(graph, '\\');
5582                         i++;
5583                 }
5584         }
5586         clear_rev_graph(graph);
5589 static void
5590 push_rev_graph(struct rev_graph *graph, const char *parent)
5592         int i;
5594         /* "Collapse" duplicate parents lines.
5595          *
5596          * FIXME: This needs to also update update the drawn graph but
5597          * for now it just serves as a method for pruning graph lines. */
5598         for (i = 0; i < graph->size; i++)
5599                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5600                         return;
5602         if (graph->size < SIZEOF_REVITEMS) {
5603                 string_copy_rev(graph->rev[graph->size++], parent);
5604         }
5607 static chtype
5608 get_rev_graph_symbol(struct rev_graph *graph)
5610         chtype symbol;
5612         if (graph->boundary)
5613                 symbol = REVGRAPH_BOUND;
5614         else if (graph->parents->size == 0)
5615                 symbol = REVGRAPH_INIT;
5616         else if (graph_parent_is_merge(graph))
5617                 symbol = REVGRAPH_MERGE;
5618         else if (graph->pos >= graph->size)
5619                 symbol = REVGRAPH_BRANCH;
5620         else
5621                 symbol = REVGRAPH_COMMIT;
5623         return symbol;
5626 static void
5627 draw_rev_graph(struct rev_graph *graph)
5629         struct rev_filler {
5630                 chtype separator, line;
5631         };
5632         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5633         static struct rev_filler fillers[] = {
5634                 { ' ',  '|' },
5635                 { '`',  '.' },
5636                 { '\'', ' ' },
5637                 { '/',  ' ' },
5638         };
5639         chtype symbol = get_rev_graph_symbol(graph);
5640         struct rev_filler *filler;
5641         size_t i;
5643         if (opt_line_graphics)
5644                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5646         filler = &fillers[DEFAULT];
5648         for (i = 0; i < graph->pos; i++) {
5649                 append_to_rev_graph(graph, filler->line);
5650                 if (graph_parent_is_merge(graph->prev) &&
5651                     graph->prev->pos == i)
5652                         filler = &fillers[RSHARP];
5654                 append_to_rev_graph(graph, filler->separator);
5655         }
5657         /* Place the symbol for this revision. */
5658         append_to_rev_graph(graph, symbol);
5660         if (graph->prev->size > graph->size)
5661                 filler = &fillers[RDIAG];
5662         else
5663                 filler = &fillers[DEFAULT];
5665         i++;
5667         for (; i < graph->size; i++) {
5668                 append_to_rev_graph(graph, filler->separator);
5669                 append_to_rev_graph(graph, filler->line);
5670                 if (graph_parent_is_merge(graph->prev) &&
5671                     i < graph->prev->pos + graph->parents->size)
5672                         filler = &fillers[RSHARP];
5673                 if (graph->prev->size > graph->size)
5674                         filler = &fillers[LDIAG];
5675         }
5677         if (graph->prev->size > graph->size) {
5678                 append_to_rev_graph(graph, filler->separator);
5679                 if (filler->line != ' ')
5680                         append_to_rev_graph(graph, filler->line);
5681         }
5684 /* Prepare the next rev graph */
5685 static void
5686 prepare_rev_graph(struct rev_graph *graph)
5688         size_t i;
5690         /* First, traverse all lines of revisions up to the active one. */
5691         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5692                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5693                         break;
5695                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5696         }
5698         /* Interleave the new revision parent(s). */
5699         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5700                 push_rev_graph(graph->next, graph->parents->rev[i]);
5702         /* Lastly, put any remaining revisions. */
5703         for (i = graph->pos + 1; i < graph->size; i++)
5704                 push_rev_graph(graph->next, graph->rev[i]);
5707 static void
5708 update_rev_graph(struct view *view, struct rev_graph *graph)
5710         /* If this is the finalizing update ... */
5711         if (graph->commit)
5712                 prepare_rev_graph(graph);
5714         /* Graph visualization needs a one rev look-ahead,
5715          * so the first update doesn't visualize anything. */
5716         if (!graph->prev->commit)
5717                 return;
5719         if (view->lines > 2)
5720                 view->line[view->lines - 3].dirty = 1;
5721         if (view->lines > 1)
5722                 view->line[view->lines - 2].dirty = 1;
5723         draw_rev_graph(graph->prev);
5724         done_rev_graph(graph->prev->prev);
5728 /*
5729  * Main view backend
5730  */
5732 static const char *main_argv[SIZEOF_ARG] = {
5733         "git", "log", "--no-color", "--pretty=raw", "--parents",
5734                       "--topo-order", "%(head)", NULL
5735 };
5737 static bool
5738 main_draw(struct view *view, struct line *line, unsigned int lineno)
5740         struct commit *commit = line->data;
5742         if (!*commit->author)
5743                 return FALSE;
5745         if (opt_date && draw_date(view, &commit->time))
5746                 return TRUE;
5748         if (opt_author && draw_author(view, commit->author))
5749                 return TRUE;
5751         if (opt_rev_graph && commit->graph_size &&
5752             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5753                 return TRUE;
5755         if (opt_show_refs && commit->refs) {
5756                 size_t i = 0;
5758                 do {
5759                         enum line_type type;
5761                         if (commit->refs[i]->head)
5762                                 type = LINE_MAIN_HEAD;
5763                         else if (commit->refs[i]->ltag)
5764                                 type = LINE_MAIN_LOCAL_TAG;
5765                         else if (commit->refs[i]->tag)
5766                                 type = LINE_MAIN_TAG;
5767                         else if (commit->refs[i]->tracked)
5768                                 type = LINE_MAIN_TRACKED;
5769                         else if (commit->refs[i]->remote)
5770                                 type = LINE_MAIN_REMOTE;
5771                         else
5772                                 type = LINE_MAIN_REF;
5774                         if (draw_text(view, type, "[", TRUE) ||
5775                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5776                             draw_text(view, type, "]", TRUE))
5777                                 return TRUE;
5779                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5780                                 return TRUE;
5781                 } while (commit->refs[i++]->next);
5782         }
5784         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5785         return TRUE;
5788 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5789 static bool
5790 main_read(struct view *view, char *line)
5792         static struct rev_graph *graph = graph_stacks;
5793         enum line_type type;
5794         struct commit *commit;
5796         if (!line) {
5797                 int i;
5799                 if (!view->lines && !view->parent)
5800                         die("No revisions match the given arguments.");
5801                 if (view->lines > 0) {
5802                         commit = view->line[view->lines - 1].data;
5803                         view->line[view->lines - 1].dirty = 1;
5804                         if (!*commit->author) {
5805                                 view->lines--;
5806                                 free(commit);
5807                                 graph->commit = NULL;
5808                         }
5809                 }
5810                 update_rev_graph(view, graph);
5812                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5813                         clear_rev_graph(&graph_stacks[i]);
5814                 return TRUE;
5815         }
5817         type = get_line_type(line);
5818         if (type == LINE_COMMIT) {
5819                 commit = calloc(1, sizeof(struct commit));
5820                 if (!commit)
5821                         return FALSE;
5823                 line += STRING_SIZE("commit ");
5824                 if (*line == '-') {
5825                         graph->boundary = 1;
5826                         line++;
5827                 }
5829                 string_copy_rev(commit->id, line);
5830                 commit->refs = get_refs(commit->id);
5831                 graph->commit = commit;
5832                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5834                 while ((line = strchr(line, ' '))) {
5835                         line++;
5836                         push_rev_graph(graph->parents, line);
5837                         commit->has_parents = TRUE;
5838                 }
5839                 return TRUE;
5840         }
5842         if (!view->lines)
5843                 return TRUE;
5844         commit = view->line[view->lines - 1].data;
5846         switch (type) {
5847         case LINE_PARENT:
5848                 if (commit->has_parents)
5849                         break;
5850                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5851                 break;
5853         case LINE_AUTHOR:
5854                 parse_author_line(line + STRING_SIZE("author "),
5855                                   commit->author, sizeof(commit->author),
5856                                   &commit->time);
5857                 update_rev_graph(view, graph);
5858                 graph = graph->next;
5859                 break;
5861         default:
5862                 /* Fill in the commit title if it has not already been set. */
5863                 if (commit->title[0])
5864                         break;
5866                 /* Require titles to start with a non-space character at the
5867                  * offset used by git log. */
5868                 if (strncmp(line, "    ", 4))
5869                         break;
5870                 line += 4;
5871                 /* Well, if the title starts with a whitespace character,
5872                  * try to be forgiving.  Otherwise we end up with no title. */
5873                 while (isspace(*line))
5874                         line++;
5875                 if (*line == '\0')
5876                         break;
5877                 /* FIXME: More graceful handling of titles; append "..." to
5878                  * shortened titles, etc. */
5880                 string_ncopy(commit->title, line, strlen(line));
5881                 view->line[view->lines - 1].dirty = 1;
5882         }
5884         return TRUE;
5887 static enum request
5888 main_request(struct view *view, enum request request, struct line *line)
5890         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5892         switch (request) {
5893         case REQ_ENTER:
5894                 open_view(view, REQ_VIEW_DIFF, flags);
5895                 break;
5896         case REQ_REFRESH:
5897                 load_refs();
5898                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5899                 break;
5900         default:
5901                 return request;
5902         }
5904         return REQ_NONE;
5907 static bool
5908 grep_refs(struct ref **refs, regex_t *regex)
5910         regmatch_t pmatch;
5911         size_t i = 0;
5913         if (!refs)
5914                 return FALSE;
5915         do {
5916                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5917                         return TRUE;
5918         } while (refs[i++]->next);
5920         return FALSE;
5923 static bool
5924 main_grep(struct view *view, struct line *line)
5926         struct commit *commit = line->data;
5927         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5928         char buf[DATE_COLS + 1];
5929         regmatch_t pmatch;
5931         for (state = S_TITLE; state < S_END; state++) {
5932                 char *text;
5934                 switch (state) {
5935                 case S_TITLE:   text = commit->title;   break;
5936                 case S_AUTHOR:
5937                         if (!opt_author)
5938                                 continue;
5939                         text = commit->author;
5940                         break;
5941                 case S_DATE:
5942                         if (!opt_date)
5943                                 continue;
5944                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5945                                 continue;
5946                         text = buf;
5947                         break;
5948                 case S_REFS:
5949                         if (!opt_show_refs)
5950                                 continue;
5951                         if (grep_refs(commit->refs, view->regex) == TRUE)
5952                                 return TRUE;
5953                         continue;
5954                 default:
5955                         return FALSE;
5956                 }
5958                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5959                         return TRUE;
5960         }
5962         return FALSE;
5965 static void
5966 main_select(struct view *view, struct line *line)
5968         struct commit *commit = line->data;
5970         string_copy_rev(view->ref, commit->id);
5971         string_copy_rev(ref_commit, view->ref);
5974 static struct view_ops main_ops = {
5975         "commit",
5976         main_argv,
5977         NULL,
5978         main_read,
5979         main_draw,
5980         main_request,
5981         main_grep,
5982         main_select,
5983 };
5986 /*
5987  * Unicode / UTF-8 handling
5988  *
5989  * NOTE: Much of the following code for dealing with unicode is derived from
5990  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5991  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5992  */
5994 /* I've (over)annotated a lot of code snippets because I am not entirely
5995  * confident that the approach taken by this small UTF-8 interface is correct.
5996  * --jonas */
5998 static inline int
5999 unicode_width(unsigned long c)
6001         if (c >= 0x1100 &&
6002            (c <= 0x115f                         /* Hangul Jamo */
6003             || c == 0x2329
6004             || c == 0x232a
6005             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6006                                                 /* CJK ... Yi */
6007             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6008             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6009             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6010             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6011             || (c >= 0xffe0  && c <= 0xffe6)
6012             || (c >= 0x20000 && c <= 0x2fffd)
6013             || (c >= 0x30000 && c <= 0x3fffd)))
6014                 return 2;
6016         if (c == '\t')
6017                 return opt_tab_size;
6019         return 1;
6022 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6023  * Illegal bytes are set one. */
6024 static const unsigned char utf8_bytes[256] = {
6025         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,
6026         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,
6027         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,
6028         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,
6029         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,
6030         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,
6031         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,
6032         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,
6033 };
6035 /* Decode UTF-8 multi-byte representation into a unicode character. */
6036 static inline unsigned long
6037 utf8_to_unicode(const char *string, size_t length)
6039         unsigned long unicode;
6041         switch (length) {
6042         case 1:
6043                 unicode  =   string[0];
6044                 break;
6045         case 2:
6046                 unicode  =  (string[0] & 0x1f) << 6;
6047                 unicode +=  (string[1] & 0x3f);
6048                 break;
6049         case 3:
6050                 unicode  =  (string[0] & 0x0f) << 12;
6051                 unicode += ((string[1] & 0x3f) << 6);
6052                 unicode +=  (string[2] & 0x3f);
6053                 break;
6054         case 4:
6055                 unicode  =  (string[0] & 0x0f) << 18;
6056                 unicode += ((string[1] & 0x3f) << 12);
6057                 unicode += ((string[2] & 0x3f) << 6);
6058                 unicode +=  (string[3] & 0x3f);
6059                 break;
6060         case 5:
6061                 unicode  =  (string[0] & 0x0f) << 24;
6062                 unicode += ((string[1] & 0x3f) << 18);
6063                 unicode += ((string[2] & 0x3f) << 12);
6064                 unicode += ((string[3] & 0x3f) << 6);
6065                 unicode +=  (string[4] & 0x3f);
6066                 break;
6067         case 6:
6068                 unicode  =  (string[0] & 0x01) << 30;
6069                 unicode += ((string[1] & 0x3f) << 24);
6070                 unicode += ((string[2] & 0x3f) << 18);
6071                 unicode += ((string[3] & 0x3f) << 12);
6072                 unicode += ((string[4] & 0x3f) << 6);
6073                 unicode +=  (string[5] & 0x3f);
6074                 break;
6075         default:
6076                 die("Invalid unicode length");
6077         }
6079         /* Invalid characters could return the special 0xfffd value but NUL
6080          * should be just as good. */
6081         return unicode > 0xffff ? 0 : unicode;
6084 /* Calculates how much of string can be shown within the given maximum width
6085  * and sets trimmed parameter to non-zero value if all of string could not be
6086  * shown. If the reserve flag is TRUE, it will reserve at least one
6087  * trailing character, which can be useful when drawing a delimiter.
6088  *
6089  * Returns the number of bytes to output from string to satisfy max_width. */
6090 static size_t
6091 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6093         const char *start = string;
6094         const char *end = strchr(string, '\0');
6095         unsigned char last_bytes = 0;
6096         size_t last_ucwidth = 0;
6098         *width = 0;
6099         *trimmed = 0;
6101         while (string < end) {
6102                 int c = *(unsigned char *) string;
6103                 unsigned char bytes = utf8_bytes[c];
6104                 size_t ucwidth;
6105                 unsigned long unicode;
6107                 if (string + bytes > end)
6108                         break;
6110                 /* Change representation to figure out whether
6111                  * it is a single- or double-width character. */
6113                 unicode = utf8_to_unicode(string, bytes);
6114                 /* FIXME: Graceful handling of invalid unicode character. */
6115                 if (!unicode)
6116                         break;
6118                 ucwidth = unicode_width(unicode);
6119                 *width  += ucwidth;
6120                 if (*width > max_width) {
6121                         *trimmed = 1;
6122                         *width -= ucwidth;
6123                         if (reserve && *width == max_width) {
6124                                 string -= last_bytes;
6125                                 *width -= last_ucwidth;
6126                         }
6127                         break;
6128                 }
6130                 string  += bytes;
6131                 last_bytes = bytes;
6132                 last_ucwidth = ucwidth;
6133         }
6135         return string - start;
6139 /*
6140  * Status management
6141  */
6143 /* Whether or not the curses interface has been initialized. */
6144 static bool cursed = FALSE;
6146 /* Terminal hacks and workarounds. */
6147 static bool use_scroll_redrawwin;
6148 static bool use_scroll_status_wclear;
6150 /* The status window is used for polling keystrokes. */
6151 static WINDOW *status_win;
6153 /* Reading from the prompt? */
6154 static bool input_mode = FALSE;
6156 static bool status_empty = FALSE;
6158 /* Update status and title window. */
6159 static void
6160 report(const char *msg, ...)
6162         struct view *view = display[current_view];
6164         if (input_mode)
6165                 return;
6167         if (!view) {
6168                 char buf[SIZEOF_STR];
6169                 va_list args;
6171                 va_start(args, msg);
6172                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6173                         buf[sizeof(buf) - 1] = 0;
6174                         buf[sizeof(buf) - 2] = '.';
6175                         buf[sizeof(buf) - 3] = '.';
6176                         buf[sizeof(buf) - 4] = '.';
6177                 }
6178                 va_end(args);
6179                 die("%s", buf);
6180         }
6182         if (!status_empty || *msg) {
6183                 va_list args;
6185                 va_start(args, msg);
6187                 wmove(status_win, 0, 0);
6188                 if (view->has_scrolled && use_scroll_status_wclear)
6189                         wclear(status_win);
6190                 if (*msg) {
6191                         vwprintw(status_win, msg, args);
6192                         status_empty = FALSE;
6193                 } else {
6194                         status_empty = TRUE;
6195                 }
6196                 wclrtoeol(status_win);
6197                 wnoutrefresh(status_win);
6199                 va_end(args);
6200         }
6202         update_view_title(view);
6205 /* Controls when nodelay should be in effect when polling user input. */
6206 static void
6207 set_nonblocking_input(bool loading)
6209         static unsigned int loading_views;
6211         if ((loading == FALSE && loading_views-- == 1) ||
6212             (loading == TRUE  && loading_views++ == 0))
6213                 nodelay(status_win, loading);
6216 static void
6217 init_display(void)
6219         const char *term;
6220         int x, y;
6222         /* Initialize the curses library */
6223         if (isatty(STDIN_FILENO)) {
6224                 cursed = !!initscr();
6225                 opt_tty = stdin;
6226         } else {
6227                 /* Leave stdin and stdout alone when acting as a pager. */
6228                 opt_tty = fopen("/dev/tty", "r+");
6229                 if (!opt_tty)
6230                         die("Failed to open /dev/tty");
6231                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6232         }
6234         if (!cursed)
6235                 die("Failed to initialize curses");
6237         nonl();         /* Tell curses not to do NL->CR/NL on output */
6238         cbreak();       /* Take input chars one at a time, no wait for \n */
6239         noecho();       /* Don't echo input */
6240         leaveok(stdscr, FALSE);
6242         if (has_colors())
6243                 init_colors();
6245         getmaxyx(stdscr, y, x);
6246         status_win = newwin(1, 0, y - 1, 0);
6247         if (!status_win)
6248                 die("Failed to create status window");
6250         /* Enable keyboard mapping */
6251         keypad(status_win, TRUE);
6252         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6254         TABSIZE = opt_tab_size;
6255         if (opt_line_graphics) {
6256                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6257         }
6259         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6260         if (term && !strcmp(term, "gnome-terminal")) {
6261                 /* In the gnome-terminal-emulator, the message from
6262                  * scrolling up one line when impossible followed by
6263                  * scrolling down one line causes corruption of the
6264                  * status line. This is fixed by calling wclear. */
6265                 use_scroll_status_wclear = TRUE;
6266                 use_scroll_redrawwin = FALSE;
6268         } else if (term && !strcmp(term, "xrvt-xpm")) {
6269                 /* No problems with full optimizations in xrvt-(unicode)
6270                  * and aterm. */
6271                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6273         } else {
6274                 /* When scrolling in (u)xterm the last line in the
6275                  * scrolling direction will update slowly. */
6276                 use_scroll_redrawwin = TRUE;
6277                 use_scroll_status_wclear = FALSE;
6278         }
6281 static int
6282 get_input(int prompt_position)
6284         struct view *view;
6285         int i, key, cursor_y, cursor_x;
6287         if (prompt_position)
6288                 input_mode = TRUE;
6290         while (TRUE) {
6291                 foreach_view (view, i) {
6292                         update_view(view);
6293                         if (view_is_displayed(view) && view->has_scrolled &&
6294                             use_scroll_redrawwin)
6295                                 redrawwin(view->win);
6296                         view->has_scrolled = FALSE;
6297                 }
6299                 /* Update the cursor position. */
6300                 if (prompt_position) {
6301                         getbegyx(status_win, cursor_y, cursor_x);
6302                         cursor_x = prompt_position;
6303                 } else {
6304                         view = display[current_view];
6305                         getbegyx(view->win, cursor_y, cursor_x);
6306                         cursor_x = view->width - 1;
6307                         cursor_y += view->lineno - view->offset;
6308                 }
6309                 setsyx(cursor_y, cursor_x);
6311                 /* Refresh, accept single keystroke of input */
6312                 doupdate();
6313                 key = wgetch(status_win);
6315                 /* wgetch() with nodelay() enabled returns ERR when
6316                  * there's no input. */
6317                 if (key == ERR) {
6319                 } else if (key == KEY_RESIZE) {
6320                         int height, width;
6322                         getmaxyx(stdscr, height, width);
6324                         wresize(status_win, 1, width);
6325                         mvwin(status_win, height - 1, 0);
6326                         wnoutrefresh(status_win);
6327                         resize_display();
6328                         redraw_display(TRUE);
6330                 } else {
6331                         input_mode = FALSE;
6332                         return key;
6333                 }
6334         }
6337 static char *
6338 prompt_input(const char *prompt, input_handler handler, void *data)
6340         enum input_status status = INPUT_OK;
6341         static char buf[SIZEOF_STR];
6342         size_t pos = 0;
6344         buf[pos] = 0;
6346         while (status == INPUT_OK || status == INPUT_SKIP) {
6347                 int key;
6349                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6350                 wclrtoeol(status_win);
6352                 key = get_input(pos + 1);
6353                 switch (key) {
6354                 case KEY_RETURN:
6355                 case KEY_ENTER:
6356                 case '\n':
6357                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6358                         break;
6360                 case KEY_BACKSPACE:
6361                         if (pos > 0)
6362                                 buf[--pos] = 0;
6363                         else
6364                                 status = INPUT_CANCEL;
6365                         break;
6367                 case KEY_ESC:
6368                         status = INPUT_CANCEL;
6369                         break;
6371                 default:
6372                         if (pos >= sizeof(buf)) {
6373                                 report("Input string too long");
6374                                 return NULL;
6375                         }
6377                         status = handler(data, buf, key);
6378                         if (status == INPUT_OK)
6379                                 buf[pos++] = (char) key;
6380                 }
6381         }
6383         /* Clear the status window */
6384         status_empty = FALSE;
6385         report("");
6387         if (status == INPUT_CANCEL)
6388                 return NULL;
6390         buf[pos++] = 0;
6392         return buf;
6395 static enum input_status
6396 prompt_yesno_handler(void *data, char *buf, int c)
6398         if (c == 'y' || c == 'Y')
6399                 return INPUT_STOP;
6400         if (c == 'n' || c == 'N')
6401                 return INPUT_CANCEL;
6402         return INPUT_SKIP;
6405 static bool
6406 prompt_yesno(const char *prompt)
6408         char prompt2[SIZEOF_STR];
6410         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6411                 return FALSE;
6413         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6416 static enum input_status
6417 read_prompt_handler(void *data, char *buf, int c)
6419         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6422 static char *
6423 read_prompt(const char *prompt)
6425         return prompt_input(prompt, read_prompt_handler, NULL);
6428 /*
6429  * Repository properties
6430  */
6432 static struct ref *refs = NULL;
6433 static size_t refs_alloc = 0;
6434 static size_t refs_size = 0;
6436 /* Id <-> ref store */
6437 static struct ref ***id_refs = NULL;
6438 static size_t id_refs_alloc = 0;
6439 static size_t id_refs_size = 0;
6441 static int
6442 compare_refs(const void *ref1_, const void *ref2_)
6444         const struct ref *ref1 = *(const struct ref **)ref1_;
6445         const struct ref *ref2 = *(const struct ref **)ref2_;
6447         if (ref1->tag != ref2->tag)
6448                 return ref2->tag - ref1->tag;
6449         if (ref1->ltag != ref2->ltag)
6450                 return ref2->ltag - ref2->ltag;
6451         if (ref1->head != ref2->head)
6452                 return ref2->head - ref1->head;
6453         if (ref1->tracked != ref2->tracked)
6454                 return ref2->tracked - ref1->tracked;
6455         if (ref1->remote != ref2->remote)
6456                 return ref2->remote - ref1->remote;
6457         return strcmp(ref1->name, ref2->name);
6460 static struct ref **
6461 get_refs(const char *id)
6463         struct ref ***tmp_id_refs;
6464         struct ref **ref_list = NULL;
6465         size_t ref_list_alloc = 0;
6466         size_t ref_list_size = 0;
6467         size_t i;
6469         for (i = 0; i < id_refs_size; i++)
6470                 if (!strcmp(id, id_refs[i][0]->id))
6471                         return id_refs[i];
6473         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6474                                     sizeof(*id_refs));
6475         if (!tmp_id_refs)
6476                 return NULL;
6478         id_refs = tmp_id_refs;
6480         for (i = 0; i < refs_size; i++) {
6481                 struct ref **tmp;
6483                 if (strcmp(id, refs[i].id))
6484                         continue;
6486                 tmp = realloc_items(ref_list, &ref_list_alloc,
6487                                     ref_list_size + 1, sizeof(*ref_list));
6488                 if (!tmp) {
6489                         if (ref_list)
6490                                 free(ref_list);
6491                         return NULL;
6492                 }
6494                 ref_list = tmp;
6495                 ref_list[ref_list_size] = &refs[i];
6496                 /* XXX: The properties of the commit chains ensures that we can
6497                  * safely modify the shared ref. The repo references will
6498                  * always be similar for the same id. */
6499                 ref_list[ref_list_size]->next = 1;
6501                 ref_list_size++;
6502         }
6504         if (ref_list) {
6505                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6506                 ref_list[ref_list_size - 1]->next = 0;
6507                 id_refs[id_refs_size++] = ref_list;
6508         }
6510         return ref_list;
6513 static int
6514 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6516         struct ref *ref;
6517         bool tag = FALSE;
6518         bool ltag = FALSE;
6519         bool remote = FALSE;
6520         bool tracked = FALSE;
6521         bool check_replace = FALSE;
6522         bool head = FALSE;
6524         if (!prefixcmp(name, "refs/tags/")) {
6525                 if (!suffixcmp(name, namelen, "^{}")) {
6526                         namelen -= 3;
6527                         name[namelen] = 0;
6528                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6529                                 check_replace = TRUE;
6530                 } else {
6531                         ltag = TRUE;
6532                 }
6534                 tag = TRUE;
6535                 namelen -= STRING_SIZE("refs/tags/");
6536                 name    += STRING_SIZE("refs/tags/");
6538         } else if (!prefixcmp(name, "refs/remotes/")) {
6539                 remote = TRUE;
6540                 namelen -= STRING_SIZE("refs/remotes/");
6541                 name    += STRING_SIZE("refs/remotes/");
6542                 tracked  = !strcmp(opt_remote, name);
6544         } else if (!prefixcmp(name, "refs/heads/")) {
6545                 namelen -= STRING_SIZE("refs/heads/");
6546                 name    += STRING_SIZE("refs/heads/");
6547                 head     = !strncmp(opt_head, name, namelen);
6549         } else if (!strcmp(name, "HEAD")) {
6550                 string_ncopy(opt_head_rev, id, idlen);
6551                 return OK;
6552         }
6554         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6555                 /* it's an annotated tag, replace the previous sha1 with the
6556                  * resolved commit id; relies on the fact git-ls-remote lists
6557                  * the commit id of an annotated tag right before the commit id
6558                  * it points to. */
6559                 refs[refs_size - 1].ltag = ltag;
6560                 string_copy_rev(refs[refs_size - 1].id, id);
6562                 return OK;
6563         }
6564         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6565         if (!refs)
6566                 return ERR;
6568         ref = &refs[refs_size++];
6569         ref->name = malloc(namelen + 1);
6570         if (!ref->name)
6571                 return ERR;
6573         strncpy(ref->name, name, namelen);
6574         ref->name[namelen] = 0;
6575         ref->head = head;
6576         ref->tag = tag;
6577         ref->ltag = ltag;
6578         ref->remote = remote;
6579         ref->tracked = tracked;
6580         string_copy_rev(ref->id, id);
6582         return OK;
6585 static int
6586 load_refs(void)
6588         static const char *ls_remote_argv[SIZEOF_ARG] = {
6589                 "git", "ls-remote", ".", NULL
6590         };
6591         static bool init = FALSE;
6593         if (!init) {
6594                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6595                 init = TRUE;
6596         }
6598         if (!*opt_git_dir)
6599                 return OK;
6601         while (refs_size > 0)
6602                 free(refs[--refs_size].name);
6603         while (id_refs_size > 0)
6604                 free(id_refs[--id_refs_size]);
6606         return run_io_load(ls_remote_argv, "\t", read_ref);
6609 static int
6610 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6612         if (!strcmp(name, "i18n.commitencoding"))
6613                 string_ncopy(opt_encoding, value, valuelen);
6615         if (!strcmp(name, "core.editor"))
6616                 string_ncopy(opt_editor, value, valuelen);
6618         /* branch.<head>.remote */
6619         if (*opt_head &&
6620             !strncmp(name, "branch.", 7) &&
6621             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6622             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6623                 string_ncopy(opt_remote, value, valuelen);
6625         if (*opt_head && *opt_remote &&
6626             !strncmp(name, "branch.", 7) &&
6627             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6628             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6629                 size_t from = strlen(opt_remote);
6631                 if (!prefixcmp(value, "refs/heads/")) {
6632                         value += STRING_SIZE("refs/heads/");
6633                         valuelen -= STRING_SIZE("refs/heads/");
6634                 }
6636                 if (!string_format_from(opt_remote, &from, "/%s", value))
6637                         opt_remote[0] = 0;
6638         }
6640         return OK;
6643 static int
6644 load_git_config(void)
6646         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6648         return run_io_load(config_list_argv, "=", read_repo_config_option);
6651 static int
6652 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6654         if (!opt_git_dir[0]) {
6655                 string_ncopy(opt_git_dir, name, namelen);
6657         } else if (opt_is_inside_work_tree == -1) {
6658                 /* This can be 3 different values depending on the
6659                  * version of git being used. If git-rev-parse does not
6660                  * understand --is-inside-work-tree it will simply echo
6661                  * the option else either "true" or "false" is printed.
6662                  * Default to true for the unknown case. */
6663                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6665         } else if (*name == '.') {
6666                 string_ncopy(opt_cdup, name, namelen);
6668         } else {
6669                 string_ncopy(opt_prefix, name, namelen);
6670         }
6672         return OK;
6675 static int
6676 load_repo_info(void)
6678         const char *head_argv[] = {
6679                 "git", "symbolic-ref", "HEAD", NULL
6680         };
6681         const char *rev_parse_argv[] = {
6682                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6683                         "--show-cdup", "--show-prefix", NULL
6684         };
6686         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6687                 chomp_string(opt_head);
6688                 if (!prefixcmp(opt_head, "refs/heads/")) {
6689                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6691                         memmove(opt_head, offset, strlen(offset) + 1);
6692                 }
6693         }
6695         return run_io_load(rev_parse_argv, "=", read_repo_info);
6699 /*
6700  * Main
6701  */
6703 static void __NORETURN
6704 quit(int sig)
6706         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6707         if (cursed)
6708                 endwin();
6709         exit(0);
6712 static void __NORETURN
6713 die(const char *err, ...)
6715         va_list args;
6717         endwin();
6719         va_start(args, err);
6720         fputs("tig: ", stderr);
6721         vfprintf(stderr, err, args);
6722         fputs("\n", stderr);
6723         va_end(args);
6725         exit(1);
6728 static void
6729 warn(const char *msg, ...)
6731         va_list args;
6733         va_start(args, msg);
6734         fputs("tig warning: ", stderr);
6735         vfprintf(stderr, msg, args);
6736         fputs("\n", stderr);
6737         va_end(args);
6740 int
6741 main(int argc, const char *argv[])
6743         const char **run_argv = NULL;
6744         struct view *view;
6745         enum request request;
6746         size_t i;
6748         signal(SIGINT, quit);
6750         if (setlocale(LC_ALL, "")) {
6751                 char *codeset = nl_langinfo(CODESET);
6753                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6754         }
6756         if (load_repo_info() == ERR)
6757                 die("Failed to load repo info.");
6759         if (load_options() == ERR)
6760                 die("Failed to load user config.");
6762         if (load_git_config() == ERR)
6763                 die("Failed to load repo config.");
6765         request = parse_options(argc, argv, &run_argv);
6766         if (request == REQ_NONE)
6767                 return 0;
6769         /* Require a git repository unless when running in pager mode. */
6770         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6771                 die("Not a git repository");
6773         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6774                 opt_utf8 = FALSE;
6776         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6777                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6778                 if (opt_iconv == ICONV_NONE)
6779                         die("Failed to initialize character set conversion");
6780         }
6782         if (load_refs() == ERR)
6783                 die("Failed to load refs.");
6785         foreach_view (view, i)
6786                 argv_from_env(view->ops->argv, view->cmd_env);
6788         init_display();
6790         if (request == REQ_VIEW_PAGER || run_argv) {
6791                 if (request == REQ_VIEW_PAGER)
6792                         io_open(&VIEW(request)->io, "");
6793                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6794                         die("Failed to format arguments");
6795                 open_view(NULL, request, OPEN_PREPARED);
6796                 request = REQ_NONE;
6797         }
6799         while (view_driver(display[current_view], request)) {
6800                 int key = get_input(0);
6802                 view = display[current_view];
6803                 request = get_keybinding(view->keymap, key);
6805                 /* Some low-level request handling. This keeps access to
6806                  * status_win restricted. */
6807                 switch (request) {
6808                 case REQ_PROMPT:
6809                 {
6810                         char *cmd = read_prompt(":");
6812                         if (cmd) {
6813                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6814                                 const char *argv[SIZEOF_ARG] = { "git" };
6815                                 int argc = 1;
6817                                 /* When running random commands, initially show the
6818                                  * command in the title. However, it maybe later be
6819                                  * overwritten if a commit line is selected. */
6820                                 string_ncopy(next->ref, cmd, strlen(cmd));
6822                                 if (!argv_from_string(argv, &argc, cmd)) {
6823                                         report("Too many arguments");
6824                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6825                                         report("Failed to format command");
6826                                 } else {
6827                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6828                                 }
6829                         }
6831                         request = REQ_NONE;
6832                         break;
6833                 }
6834                 case REQ_SEARCH:
6835                 case REQ_SEARCH_BACK:
6836                 {
6837                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6838                         char *search = read_prompt(prompt);
6840                         if (search)
6841                                 string_ncopy(opt_search, search, strlen(search));
6842                         else
6843                                 request = REQ_NONE;
6844                         break;
6845                 }
6846                 default:
6847                         break;
6848                 }
6849         }
6851         quit(0);
6853         return 0;