Code

Add small cache for author names to reduce memory foot-print
[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 int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
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 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB         '\t'
127 #define KEY_RETURN      '\r'
128 #define KEY_ESC         27
131 struct ref {
132         char *name;             /* Ref name; tag or head names are shortened. */
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         unsigned int head:1;    /* Is it the current HEAD? */
135         unsigned int tag:1;     /* Is it a tag? */
136         unsigned int ltag:1;    /* If so, is the tag local? */
137         unsigned int remote:1;  /* Is it a remote ref? */
138         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
144 enum format_flags {
145         FORMAT_ALL,             /* Perform replacement in all arguments. */
146         FORMAT_DASH,            /* Perform replacement up until "--". */
147         FORMAT_NONE             /* No replacement should be performed. */
148 };
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153         INPUT_OK,
154         INPUT_SKIP,
155         INPUT_STOP,
156         INPUT_CANCEL
157 };
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
164 /*
165  * String helpers
166  */
168 static inline void
169 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
171         if (srclen > dstlen - 1)
172                 srclen = dstlen - 1;
174         strncpy(dst, src, srclen);
175         dst[srclen] = 0;
178 /* Shorthands for safely copying into a fixed buffer. */
180 #define string_copy(dst, src) \
181         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
183 #define string_ncopy(dst, src, srclen) \
184         string_ncopy_do(dst, sizeof(dst), src, srclen)
186 #define string_copy_rev(dst, src) \
187         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
189 #define string_add(dst, from, src) \
190         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
192 static void
193 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
195         size_t size, pos;
197         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
198                 if (src[pos] == '\t') {
199                         size_t expanded = tabsize - (size % tabsize);
201                         if (expanded + size >= dstlen - 1)
202                                 expanded = dstlen - size - 1;
203                         memcpy(dst + size, "        ", expanded);
204                         size += expanded;
205                 } else {
206                         dst[size++] = src[pos];
207                 }
208         }
210         dst[size] = 0;
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 struct enum_map {
273         const char *name;
274         int namelen;
275         int value;
276 };
278 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
280 static bool
281 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
283         size_t namelen = strlen(name);
284         int i;
286         for (i = 0; i < map_size; i++)
287                 if (namelen == map[i].namelen &&
288                     !string_enum_compare(name, map[i].name, namelen)) {
289                         *value = map[i].value;
290                         return TRUE;
291                 }
293         return FALSE;
296 #define map_enum(attr, map, name) \
297         map_enum_do(map, ARRAY_SIZE(map), attr, name)
299 #define prefixcmp(str1, str2) \
300         strncmp(str1, str2, STRING_SIZE(str2))
302 static inline int
303 suffixcmp(const char *str, int slen, const char *suffix)
305         size_t len = slen >= 0 ? slen : strlen(str);
306         size_t suffixlen = strlen(suffix);
308         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
312 static bool
313 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
315         int valuelen;
317         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
318                 bool advance = cmd[valuelen] != 0;
320                 cmd[valuelen] = 0;
321                 argv[(*argc)++] = chomp_string(cmd);
322                 cmd = chomp_string(cmd + valuelen + advance);
323         }
325         if (*argc < SIZEOF_ARG)
326                 argv[*argc] = NULL;
327         return *argc < SIZEOF_ARG;
330 static void
331 argv_from_env(const char **argv, const char *name)
333         char *env = argv ? getenv(name) : NULL;
334         int argc = 0;
336         if (env && *env)
337                 env = strdup(env);
338         if (env && !argv_from_string(argv, &argc, env))
339                 die("Too many arguments in the `%s` environment variable", name);
343 /*
344  * Executing external commands.
345  */
347 enum io_type {
348         IO_FD,                  /* File descriptor based IO. */
349         IO_BG,                  /* Execute command in the background. */
350         IO_FG,                  /* Execute command with same std{in,out,err}. */
351         IO_RD,                  /* Read only fork+exec IO. */
352         IO_WR,                  /* Write only fork+exec IO. */
353         IO_AP,                  /* Append fork+exec output to file. */
354 };
356 struct io {
357         enum io_type type;      /* The requested type of pipe. */
358         const char *dir;        /* Directory from which to execute. */
359         pid_t pid;              /* Pipe for reading or writing. */
360         int pipe;               /* Pipe end for reading or writing. */
361         int error;              /* Error status. */
362         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
363         char *buf;              /* Read buffer. */
364         size_t bufalloc;        /* Allocated buffer size. */
365         size_t bufsize;         /* Buffer content size. */
366         char *bufpos;           /* Current buffer position. */
367         unsigned int eof:1;     /* Has end of file been reached. */
368 };
370 static void
371 reset_io(struct io *io)
373         io->pipe = -1;
374         io->pid = 0;
375         io->buf = io->bufpos = NULL;
376         io->bufalloc = io->bufsize = 0;
377         io->error = 0;
378         io->eof = 0;
381 static void
382 init_io(struct io *io, const char *dir, enum io_type type)
384         reset_io(io);
385         io->type = type;
386         io->dir = dir;
389 static bool
390 init_io_rd(struct io *io, const char *argv[], const char *dir,
391                 enum format_flags flags)
393         init_io(io, dir, IO_RD);
394         return format_argv(io->argv, argv, flags);
397 static bool
398 io_open(struct io *io, const char *name)
400         init_io(io, NULL, IO_FD);
401         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
402         if (io->pipe == -1)
403                 io->error = errno;
404         return io->pipe != -1;
407 static bool
408 kill_io(struct io *io)
410         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
413 static bool
414 done_io(struct io *io)
416         pid_t pid = io->pid;
418         if (io->pipe != -1)
419                 close(io->pipe);
420         free(io->buf);
421         reset_io(io);
423         while (pid > 0) {
424                 int status;
425                 pid_t waiting = waitpid(pid, &status, 0);
427                 if (waiting < 0) {
428                         if (errno == EINTR)
429                                 continue;
430                         report("waitpid failed (%s)", strerror(errno));
431                         return FALSE;
432                 }
434                 return waiting == pid &&
435                        !WIFSIGNALED(status) &&
436                        WIFEXITED(status) &&
437                        !WEXITSTATUS(status);
438         }
440         return TRUE;
443 static bool
444 start_io(struct io *io)
446         int pipefds[2] = { -1, -1 };
448         if (io->type == IO_FD)
449                 return TRUE;
451         if ((io->type == IO_RD || io->type == IO_WR) &&
452             pipe(pipefds) < 0)
453                 return FALSE;
454         else if (io->type == IO_AP)
455                 pipefds[1] = io->pipe;
457         if ((io->pid = fork())) {
458                 if (pipefds[!(io->type == IO_WR)] != -1)
459                         close(pipefds[!(io->type == IO_WR)]);
460                 if (io->pid != -1) {
461                         io->pipe = pipefds[!!(io->type == IO_WR)];
462                         return TRUE;
463                 }
465         } else {
466                 if (io->type != IO_FG) {
467                         int devnull = open("/dev/null", O_RDWR);
468                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
469                         int writefd = (io->type == IO_RD || io->type == IO_AP)
470                                                         ? pipefds[1] : devnull;
472                         dup2(readfd,  STDIN_FILENO);
473                         dup2(writefd, STDOUT_FILENO);
474                         dup2(devnull, STDERR_FILENO);
476                         close(devnull);
477                         if (pipefds[0] != -1)
478                                 close(pipefds[0]);
479                         if (pipefds[1] != -1)
480                                 close(pipefds[1]);
481                 }
483                 if (io->dir && *io->dir && chdir(io->dir) == -1)
484                         die("Failed to change directory: %s", strerror(errno));
486                 execvp(io->argv[0], (char *const*) io->argv);
487                 die("Failed to execute program: %s", strerror(errno));
488         }
490         if (pipefds[!!(io->type == IO_WR)] != -1)
491                 close(pipefds[!!(io->type == IO_WR)]);
492         return FALSE;
495 static bool
496 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
498         init_io(io, dir, type);
499         if (!format_argv(io->argv, argv, FORMAT_NONE))
500                 return FALSE;
501         return start_io(io);
504 static int
505 run_io_do(struct io *io)
507         return start_io(io) && done_io(io);
510 static int
511 run_io_bg(const char **argv)
513         struct io io = {};
515         init_io(&io, NULL, IO_BG);
516         if (!format_argv(io.argv, argv, FORMAT_NONE))
517                 return FALSE;
518         return run_io_do(&io);
521 static bool
522 run_io_fg(const char **argv, const char *dir)
524         struct io io = {};
526         init_io(&io, dir, IO_FG);
527         if (!format_argv(io.argv, argv, FORMAT_NONE))
528                 return FALSE;
529         return run_io_do(&io);
532 static bool
533 run_io_append(const char **argv, enum format_flags flags, int fd)
535         struct io io = {};
537         init_io(&io, NULL, IO_AP);
538         io.pipe = fd;
539         if (format_argv(io.argv, argv, flags))
540                 return run_io_do(&io);
541         close(fd);
542         return FALSE;
545 static bool
546 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
548         return init_io_rd(io, argv, NULL, flags) && start_io(io);
551 static bool
552 io_eof(struct io *io)
554         return io->eof;
557 static int
558 io_error(struct io *io)
560         return io->error;
563 static char *
564 io_strerror(struct io *io)
566         return strerror(io->error);
569 static bool
570 io_can_read(struct io *io)
572         struct timeval tv = { 0, 500 };
573         fd_set fds;
575         FD_ZERO(&fds);
576         FD_SET(io->pipe, &fds);
578         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
581 static ssize_t
582 io_read(struct io *io, void *buf, size_t bufsize)
584         do {
585                 ssize_t readsize = read(io->pipe, buf, bufsize);
587                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
588                         continue;
589                 else if (readsize == -1)
590                         io->error = errno;
591                 else if (readsize == 0)
592                         io->eof = 1;
593                 return readsize;
594         } while (1);
597 static char *
598 io_get(struct io *io, int c, bool can_read)
600         char *eol;
601         ssize_t readsize;
603         if (!io->buf) {
604                 io->buf = io->bufpos = malloc(BUFSIZ);
605                 if (!io->buf)
606                         return NULL;
607                 io->bufalloc = BUFSIZ;
608                 io->bufsize = 0;
609         }
611         while (TRUE) {
612                 if (io->bufsize > 0) {
613                         eol = memchr(io->bufpos, c, io->bufsize);
614                         if (eol) {
615                                 char *line = io->bufpos;
617                                 *eol = 0;
618                                 io->bufpos = eol + 1;
619                                 io->bufsize -= io->bufpos - line;
620                                 return line;
621                         }
622                 }
624                 if (io_eof(io)) {
625                         if (io->bufsize) {
626                                 io->bufpos[io->bufsize] = 0;
627                                 io->bufsize = 0;
628                                 return io->bufpos;
629                         }
630                         return NULL;
631                 }
633                 if (!can_read)
634                         return NULL;
636                 if (io->bufsize > 0 && io->bufpos > io->buf)
637                         memmove(io->buf, io->bufpos, io->bufsize);
639                 io->bufpos = io->buf;
640                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
641                 if (io_error(io))
642                         return NULL;
643                 io->bufsize += readsize;
644         }
647 static bool
648 io_write(struct io *io, const void *buf, size_t bufsize)
650         size_t written = 0;
652         while (!io_error(io) && written < bufsize) {
653                 ssize_t size;
655                 size = write(io->pipe, buf + written, bufsize - written);
656                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
657                         continue;
658                 else if (size == -1)
659                         io->error = errno;
660                 else
661                         written += size;
662         }
664         return written == bufsize;
667 static bool
668 io_read_buf(struct io *io, char buf[], size_t bufsize)
670         bool error;
672         io->buf = io->bufpos = buf;
673         io->bufalloc = bufsize;
674         error = !io_get(io, '\n', TRUE) && io_error(io);
675         io->buf = NULL;
677         return done_io(io) || error;
680 static bool
681 run_io_buf(const char **argv, char buf[], size_t bufsize)
683         struct io io = {};
685         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
688 static int
689 io_load(struct io *io, const char *separators,
690         int (*read_property)(char *, size_t, char *, size_t))
692         char *name;
693         int state = OK;
695         if (!start_io(io))
696                 return ERR;
698         while (state == OK && (name = io_get(io, '\n', TRUE))) {
699                 char *value;
700                 size_t namelen;
701                 size_t valuelen;
703                 name = chomp_string(name);
704                 namelen = strcspn(name, separators);
706                 if (name[namelen]) {
707                         name[namelen] = 0;
708                         value = chomp_string(name + namelen + 1);
709                         valuelen = strlen(value);
711                 } else {
712                         value = "";
713                         valuelen = 0;
714                 }
716                 state = read_property(name, namelen, value, valuelen);
717         }
719         if (state != ERR && io_error(io))
720                 state = ERR;
721         done_io(io);
723         return state;
726 static int
727 run_io_load(const char **argv, const char *separators,
728             int (*read_property)(char *, size_t, char *, size_t))
730         struct io io = {};
732         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
733                 ? io_load(&io, separators, read_property) : ERR;
737 /*
738  * User requests
739  */
741 #define REQ_INFO \
742         /* XXX: Keep the view request first and in sync with views[]. */ \
743         REQ_GROUP("View switching") \
744         REQ_(VIEW_MAIN,         "Show main view"), \
745         REQ_(VIEW_DIFF,         "Show diff view"), \
746         REQ_(VIEW_LOG,          "Show log view"), \
747         REQ_(VIEW_TREE,         "Show tree view"), \
748         REQ_(VIEW_BLOB,         "Show blob view"), \
749         REQ_(VIEW_BLAME,        "Show blame view"), \
750         REQ_(VIEW_HELP,         "Show help page"), \
751         REQ_(VIEW_PAGER,        "Show pager view"), \
752         REQ_(VIEW_STATUS,       "Show status view"), \
753         REQ_(VIEW_STAGE,        "Show stage view"), \
754         \
755         REQ_GROUP("View manipulation") \
756         REQ_(ENTER,             "Enter current line and scroll"), \
757         REQ_(NEXT,              "Move to next"), \
758         REQ_(PREVIOUS,          "Move to previous"), \
759         REQ_(PARENT,            "Move to parent"), \
760         REQ_(VIEW_NEXT,         "Move focus to next view"), \
761         REQ_(REFRESH,           "Reload and refresh"), \
762         REQ_(MAXIMIZE,          "Maximize the current view"), \
763         REQ_(VIEW_CLOSE,        "Close the current view"), \
764         REQ_(QUIT,              "Close all views and quit"), \
765         \
766         REQ_GROUP("View specific requests") \
767         REQ_(STATUS_UPDATE,     "Update file status"), \
768         REQ_(STATUS_REVERT,     "Revert file changes"), \
769         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
770         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
771         \
772         REQ_GROUP("Cursor navigation") \
773         REQ_(MOVE_UP,           "Move cursor one line up"), \
774         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
775         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
776         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
777         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
778         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
779         \
780         REQ_GROUP("Scrolling") \
781         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
782         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
783         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
784         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
785         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
786         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
787         \
788         REQ_GROUP("Searching") \
789         REQ_(SEARCH,            "Search the view"), \
790         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
791         REQ_(FIND_NEXT,         "Find next search match"), \
792         REQ_(FIND_PREV,         "Find previous search match"), \
793         \
794         REQ_GROUP("Option manipulation") \
795         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
796         REQ_(TOGGLE_DATE,       "Toggle date display"), \
797         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
798         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
799         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
800         \
801         REQ_GROUP("Misc") \
802         REQ_(PROMPT,            "Bring up the prompt"), \
803         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
804         REQ_(SHOW_VERSION,      "Show version information"), \
805         REQ_(STOP_LOADING,      "Stop all loading views"), \
806         REQ_(EDIT,              "Open in editor"), \
807         REQ_(NONE,              "Do nothing")
810 /* User action requests. */
811 enum request {
812 #define REQ_GROUP(help)
813 #define REQ_(req, help) REQ_##req
815         /* Offset all requests to avoid conflicts with ncurses getch values. */
816         REQ_OFFSET = KEY_MAX + 1,
817         REQ_INFO
819 #undef  REQ_GROUP
820 #undef  REQ_
821 };
823 struct request_info {
824         enum request request;
825         const char *name;
826         int namelen;
827         const char *help;
828 };
830 static const struct request_info req_info[] = {
831 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
832 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
833         REQ_INFO
834 #undef  REQ_GROUP
835 #undef  REQ_
836 };
838 static enum request
839 get_request(const char *name)
841         int namelen = strlen(name);
842         int i;
844         for (i = 0; i < ARRAY_SIZE(req_info); i++)
845                 if (req_info[i].namelen == namelen &&
846                     !string_enum_compare(req_info[i].name, name, namelen))
847                         return req_info[i].request;
849         return REQ_NONE;
853 /*
854  * Options
855  */
857 /* Option and state variables. */
858 static bool opt_date                    = TRUE;
859 static bool opt_author                  = TRUE;
860 static bool opt_line_number             = FALSE;
861 static bool opt_line_graphics           = TRUE;
862 static bool opt_rev_graph               = FALSE;
863 static bool opt_show_refs               = TRUE;
864 static int opt_num_interval             = NUMBER_INTERVAL;
865 static double opt_hscroll               = 0.50;
866 static int opt_tab_size                 = TAB_SIZE;
867 static int opt_author_cols              = AUTHOR_COLS-1;
868 static char opt_path[SIZEOF_STR]        = "";
869 static char opt_file[SIZEOF_STR]        = "";
870 static char opt_ref[SIZEOF_REF]         = "";
871 static char opt_head[SIZEOF_REF]        = "";
872 static char opt_head_rev[SIZEOF_REV]    = "";
873 static char opt_remote[SIZEOF_REF]      = "";
874 static char opt_encoding[20]            = "UTF-8";
875 static bool opt_utf8                    = TRUE;
876 static char opt_codeset[20]             = "UTF-8";
877 static iconv_t opt_iconv                = ICONV_NONE;
878 static char opt_search[SIZEOF_STR]      = "";
879 static char opt_cdup[SIZEOF_STR]        = "";
880 static char opt_prefix[SIZEOF_STR]      = "";
881 static char opt_git_dir[SIZEOF_STR]     = "";
882 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
883 static char opt_editor[SIZEOF_STR]      = "";
884 static FILE *opt_tty                    = NULL;
886 #define is_initial_commit()     (!*opt_head_rev)
887 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
890 /*
891  * Line-oriented content detection.
892  */
894 #define LINE_INFO \
895 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
896 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
897 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
898 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
899 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
900 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
901 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
902 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
909 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
910 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
911 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
912 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
913 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
914 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
915 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
916 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
917 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
918 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
919 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
920 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
921 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
922 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
923 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
924 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
925 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
926 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
927 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
928 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
929 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
930 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
931 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
932 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
933 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
934 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
935 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
936 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
937 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
938 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
939 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
940 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
941 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
942 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
943 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
944 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
945 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
946 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
947 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
948 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
949 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
951 enum line_type {
952 #define LINE(type, line, fg, bg, attr) \
953         LINE_##type
954         LINE_INFO,
955         LINE_NONE
956 #undef  LINE
957 };
959 struct line_info {
960         const char *name;       /* Option name. */
961         int namelen;            /* Size of option name. */
962         const char *line;       /* The start of line to match. */
963         int linelen;            /* Size of string to match. */
964         int fg, bg, attr;       /* Color and text attributes for the lines. */
965 };
967 static struct line_info line_info[] = {
968 #define LINE(type, line, fg, bg, attr) \
969         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
970         LINE_INFO
971 #undef  LINE
972 };
974 static enum line_type
975 get_line_type(const char *line)
977         int linelen = strlen(line);
978         enum line_type type;
980         for (type = 0; type < ARRAY_SIZE(line_info); type++)
981                 /* Case insensitive search matches Signed-off-by lines better. */
982                 if (linelen >= line_info[type].linelen &&
983                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
984                         return type;
986         return LINE_DEFAULT;
989 static inline int
990 get_line_attr(enum line_type type)
992         assert(type < ARRAY_SIZE(line_info));
993         return COLOR_PAIR(type) | line_info[type].attr;
996 static struct line_info *
997 get_line_info(const char *name)
999         size_t namelen = strlen(name);
1000         enum line_type type;
1002         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1003                 if (namelen == line_info[type].namelen &&
1004                     !string_enum_compare(line_info[type].name, name, namelen))
1005                         return &line_info[type];
1007         return NULL;
1010 static void
1011 init_colors(void)
1013         int default_bg = line_info[LINE_DEFAULT].bg;
1014         int default_fg = line_info[LINE_DEFAULT].fg;
1015         enum line_type type;
1017         start_color();
1019         if (assume_default_colors(default_fg, default_bg) == ERR) {
1020                 default_bg = COLOR_BLACK;
1021                 default_fg = COLOR_WHITE;
1022         }
1024         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1025                 struct line_info *info = &line_info[type];
1026                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1027                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1029                 init_pair(type, fg, bg);
1030         }
1033 struct line {
1034         enum line_type type;
1036         /* State flags */
1037         unsigned int selected:1;
1038         unsigned int dirty:1;
1039         unsigned int cleareol:1;
1041         void *data;             /* User data */
1042 };
1045 /*
1046  * Keys
1047  */
1049 struct keybinding {
1050         int alias;
1051         enum request request;
1052 };
1054 static const struct keybinding default_keybindings[] = {
1055         /* View switching */
1056         { 'm',          REQ_VIEW_MAIN },
1057         { 'd',          REQ_VIEW_DIFF },
1058         { 'l',          REQ_VIEW_LOG },
1059         { 't',          REQ_VIEW_TREE },
1060         { 'f',          REQ_VIEW_BLOB },
1061         { 'B',          REQ_VIEW_BLAME },
1062         { 'p',          REQ_VIEW_PAGER },
1063         { 'h',          REQ_VIEW_HELP },
1064         { 'S',          REQ_VIEW_STATUS },
1065         { 'c',          REQ_VIEW_STAGE },
1067         /* View manipulation */
1068         { 'q',          REQ_VIEW_CLOSE },
1069         { KEY_TAB,      REQ_VIEW_NEXT },
1070         { KEY_RETURN,   REQ_ENTER },
1071         { KEY_UP,       REQ_PREVIOUS },
1072         { KEY_DOWN,     REQ_NEXT },
1073         { 'R',          REQ_REFRESH },
1074         { KEY_F(5),     REQ_REFRESH },
1075         { 'O',          REQ_MAXIMIZE },
1077         /* Cursor navigation */
1078         { 'k',          REQ_MOVE_UP },
1079         { 'j',          REQ_MOVE_DOWN },
1080         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1081         { KEY_END,      REQ_MOVE_LAST_LINE },
1082         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1083         { ' ',          REQ_MOVE_PAGE_DOWN },
1084         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1085         { 'b',          REQ_MOVE_PAGE_UP },
1086         { '-',          REQ_MOVE_PAGE_UP },
1088         /* Scrolling */
1089         { KEY_LEFT,     REQ_SCROLL_LEFT },
1090         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1091         { KEY_IC,       REQ_SCROLL_LINE_UP },
1092         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1093         { 'w',          REQ_SCROLL_PAGE_UP },
1094         { 's',          REQ_SCROLL_PAGE_DOWN },
1096         /* Searching */
1097         { '/',          REQ_SEARCH },
1098         { '?',          REQ_SEARCH_BACK },
1099         { 'n',          REQ_FIND_NEXT },
1100         { 'N',          REQ_FIND_PREV },
1102         /* Misc */
1103         { 'Q',          REQ_QUIT },
1104         { 'z',          REQ_STOP_LOADING },
1105         { 'v',          REQ_SHOW_VERSION },
1106         { 'r',          REQ_SCREEN_REDRAW },
1107         { '.',          REQ_TOGGLE_LINENO },
1108         { 'D',          REQ_TOGGLE_DATE },
1109         { 'A',          REQ_TOGGLE_AUTHOR },
1110         { 'g',          REQ_TOGGLE_REV_GRAPH },
1111         { 'F',          REQ_TOGGLE_REFS },
1112         { ':',          REQ_PROMPT },
1113         { 'u',          REQ_STATUS_UPDATE },
1114         { '!',          REQ_STATUS_REVERT },
1115         { 'M',          REQ_STATUS_MERGE },
1116         { '@',          REQ_STAGE_NEXT },
1117         { ',',          REQ_PARENT },
1118         { 'e',          REQ_EDIT },
1119 };
1121 #define KEYMAP_INFO \
1122         KEYMAP_(GENERIC), \
1123         KEYMAP_(MAIN), \
1124         KEYMAP_(DIFF), \
1125         KEYMAP_(LOG), \
1126         KEYMAP_(TREE), \
1127         KEYMAP_(BLOB), \
1128         KEYMAP_(BLAME), \
1129         KEYMAP_(PAGER), \
1130         KEYMAP_(HELP), \
1131         KEYMAP_(STATUS), \
1132         KEYMAP_(STAGE)
1134 enum keymap {
1135 #define KEYMAP_(name) KEYMAP_##name
1136         KEYMAP_INFO
1137 #undef  KEYMAP_
1138 };
1140 static const struct enum_map keymap_table[] = {
1141 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1142         KEYMAP_INFO
1143 #undef  KEYMAP_
1144 };
1146 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1148 struct keybinding_table {
1149         struct keybinding *data;
1150         size_t size;
1151 };
1153 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1155 static void
1156 add_keybinding(enum keymap keymap, enum request request, int key)
1158         struct keybinding_table *table = &keybindings[keymap];
1160         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1161         if (!table->data)
1162                 die("Failed to allocate keybinding");
1163         table->data[table->size].alias = key;
1164         table->data[table->size++].request = request;
1167 /* Looks for a key binding first in the given map, then in the generic map, and
1168  * lastly in the default keybindings. */
1169 static enum request
1170 get_keybinding(enum keymap keymap, int key)
1172         size_t i;
1174         for (i = 0; i < keybindings[keymap].size; i++)
1175                 if (keybindings[keymap].data[i].alias == key)
1176                         return keybindings[keymap].data[i].request;
1178         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1179                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1180                         return keybindings[KEYMAP_GENERIC].data[i].request;
1182         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1183                 if (default_keybindings[i].alias == key)
1184                         return default_keybindings[i].request;
1186         return (enum request) key;
1190 struct key {
1191         const char *name;
1192         int value;
1193 };
1195 static const struct key key_table[] = {
1196         { "Enter",      KEY_RETURN },
1197         { "Space",      ' ' },
1198         { "Backspace",  KEY_BACKSPACE },
1199         { "Tab",        KEY_TAB },
1200         { "Escape",     KEY_ESC },
1201         { "Left",       KEY_LEFT },
1202         { "Right",      KEY_RIGHT },
1203         { "Up",         KEY_UP },
1204         { "Down",       KEY_DOWN },
1205         { "Insert",     KEY_IC },
1206         { "Delete",     KEY_DC },
1207         { "Hash",       '#' },
1208         { "Home",       KEY_HOME },
1209         { "End",        KEY_END },
1210         { "PageUp",     KEY_PPAGE },
1211         { "PageDown",   KEY_NPAGE },
1212         { "F1",         KEY_F(1) },
1213         { "F2",         KEY_F(2) },
1214         { "F3",         KEY_F(3) },
1215         { "F4",         KEY_F(4) },
1216         { "F5",         KEY_F(5) },
1217         { "F6",         KEY_F(6) },
1218         { "F7",         KEY_F(7) },
1219         { "F8",         KEY_F(8) },
1220         { "F9",         KEY_F(9) },
1221         { "F10",        KEY_F(10) },
1222         { "F11",        KEY_F(11) },
1223         { "F12",        KEY_F(12) },
1224 };
1226 static int
1227 get_key_value(const char *name)
1229         int i;
1231         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1232                 if (!strcasecmp(key_table[i].name, name))
1233                         return key_table[i].value;
1235         if (strlen(name) == 1 && isprint(*name))
1236                 return (int) *name;
1238         return ERR;
1241 static const char *
1242 get_key_name(int key_value)
1244         static char key_char[] = "'X'";
1245         const char *seq = NULL;
1246         int key;
1248         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1249                 if (key_table[key].value == key_value)
1250                         seq = key_table[key].name;
1252         if (seq == NULL &&
1253             key_value < 127 &&
1254             isprint(key_value)) {
1255                 key_char[1] = (char) key_value;
1256                 seq = key_char;
1257         }
1259         return seq ? seq : "(no key)";
1262 static const char *
1263 get_key(enum request request)
1265         static char buf[BUFSIZ];
1266         size_t pos = 0;
1267         char *sep = "";
1268         int i;
1270         buf[pos] = 0;
1272         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1273                 const struct keybinding *keybinding = &default_keybindings[i];
1275                 if (keybinding->request != request)
1276                         continue;
1278                 if (!string_format_from(buf, &pos, "%s%s", sep,
1279                                         get_key_name(keybinding->alias)))
1280                         return "Too many keybindings!";
1281                 sep = ", ";
1282         }
1284         return buf;
1287 struct run_request {
1288         enum keymap keymap;
1289         int key;
1290         const char *argv[SIZEOF_ARG];
1291 };
1293 static struct run_request *run_request;
1294 static size_t run_requests;
1296 static enum request
1297 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1299         struct run_request *req;
1301         if (argc >= ARRAY_SIZE(req->argv) - 1)
1302                 return REQ_NONE;
1304         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1305         if (!req)
1306                 return REQ_NONE;
1308         run_request = req;
1309         req = &run_request[run_requests];
1310         req->keymap = keymap;
1311         req->key = key;
1312         req->argv[0] = NULL;
1314         if (!format_argv(req->argv, argv, FORMAT_NONE))
1315                 return REQ_NONE;
1317         return REQ_NONE + ++run_requests;
1320 static struct run_request *
1321 get_run_request(enum request request)
1323         if (request <= REQ_NONE)
1324                 return NULL;
1325         return &run_request[request - REQ_NONE - 1];
1328 static void
1329 add_builtin_run_requests(void)
1331         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1332         const char *gc[] = { "git", "gc", NULL };
1333         struct {
1334                 enum keymap keymap;
1335                 int key;
1336                 int argc;
1337                 const char **argv;
1338         } reqs[] = {
1339                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1340                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1341         };
1342         int i;
1344         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1345                 enum request req;
1347                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1348                 if (req != REQ_NONE)
1349                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1350         }
1353 /*
1354  * User config file handling.
1355  */
1357 static int   config_lineno;
1358 static bool  config_errors;
1359 static const char *config_msg;
1361 static const struct enum_map color_map[] = {
1362 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1363         COLOR_MAP(DEFAULT),
1364         COLOR_MAP(BLACK),
1365         COLOR_MAP(BLUE),
1366         COLOR_MAP(CYAN),
1367         COLOR_MAP(GREEN),
1368         COLOR_MAP(MAGENTA),
1369         COLOR_MAP(RED),
1370         COLOR_MAP(WHITE),
1371         COLOR_MAP(YELLOW),
1372 };
1374 static const struct enum_map attr_map[] = {
1375 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1376         ATTR_MAP(NORMAL),
1377         ATTR_MAP(BLINK),
1378         ATTR_MAP(BOLD),
1379         ATTR_MAP(DIM),
1380         ATTR_MAP(REVERSE),
1381         ATTR_MAP(STANDOUT),
1382         ATTR_MAP(UNDERLINE),
1383 };
1385 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1387 static int parse_step(double *opt, const char *arg)
1389         *opt = atoi(arg);
1390         if (!strchr(arg, '%'))
1391                 return OK;
1393         /* "Shift down" so 100% and 1 does not conflict. */
1394         *opt = (*opt - 1) / 100;
1395         if (*opt >= 1.0) {
1396                 *opt = 0.99;
1397                 config_msg = "Step value larger than 100%";
1398                 return ERR;
1399         }
1400         if (*opt < 0.0) {
1401                 *opt = 1;
1402                 config_msg = "Invalid step value";
1403                 return ERR;
1404         }
1405         return OK;
1408 static int
1409 parse_int(int *opt, const char *arg, int min, int max)
1411         int value = atoi(arg);
1413         if (min <= value && value <= max) {
1414                 *opt = value;
1415                 return OK;
1416         }
1418         config_msg = "Integer value out of bound";
1419         return ERR;
1422 static bool
1423 set_color(int *color, const char *name)
1425         if (map_enum(color, color_map, name))
1426                 return TRUE;
1427         if (!prefixcmp(name, "color"))
1428                 return parse_int(color, name + 5, 0, 255) == OK;
1429         return FALSE;
1432 /* Wants: object fgcolor bgcolor [attribute] */
1433 static int
1434 option_color_command(int argc, const char *argv[])
1436         struct line_info *info;
1438         if (argc != 3 && argc != 4) {
1439                 config_msg = "Wrong number of arguments given to color command";
1440                 return ERR;
1441         }
1443         info = get_line_info(argv[0]);
1444         if (!info) {
1445                 static const struct enum_map obsolete[] = {
1446                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1447                         ENUM_MAP("main-date",   LINE_DATE),
1448                         ENUM_MAP("main-author", LINE_AUTHOR),
1449                 };
1450                 int index;
1452                 if (!map_enum(&index, obsolete, argv[0])) {
1453                         config_msg = "Unknown color name";
1454                         return ERR;
1455                 }
1456                 info = &line_info[index];
1457         }
1459         if (!set_color(&info->fg, argv[1]) ||
1460             !set_color(&info->bg, argv[2])) {
1461                 config_msg = "Unknown color";
1462                 return ERR;
1463         }
1465         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1466                 config_msg = "Unknown attribute";
1467                 return ERR;
1468         }
1470         return OK;
1473 static int parse_bool(bool *opt, const char *arg)
1475         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1476                 ? TRUE : FALSE;
1477         return OK;
1480 static int
1481 parse_string(char *opt, const char *arg, size_t optsize)
1483         int arglen = strlen(arg);
1485         switch (arg[0]) {
1486         case '\"':
1487         case '\'':
1488                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1489                         config_msg = "Unmatched quotation";
1490                         return ERR;
1491                 }
1492                 arg += 1; arglen -= 2;
1493         default:
1494                 string_ncopy_do(opt, optsize, arg, arglen);
1495                 return OK;
1496         }
1499 /* Wants: name = value */
1500 static int
1501 option_set_command(int argc, const char *argv[])
1503         if (argc != 3) {
1504                 config_msg = "Wrong number of arguments given to set command";
1505                 return ERR;
1506         }
1508         if (strcmp(argv[1], "=")) {
1509                 config_msg = "No value assigned";
1510                 return ERR;
1511         }
1513         if (!strcmp(argv[0], "show-author"))
1514                 return parse_bool(&opt_author, argv[2]);
1516         if (!strcmp(argv[0], "show-date"))
1517                 return parse_bool(&opt_date, argv[2]);
1519         if (!strcmp(argv[0], "show-rev-graph"))
1520                 return parse_bool(&opt_rev_graph, argv[2]);
1522         if (!strcmp(argv[0], "show-refs"))
1523                 return parse_bool(&opt_show_refs, argv[2]);
1525         if (!strcmp(argv[0], "show-line-numbers"))
1526                 return parse_bool(&opt_line_number, argv[2]);
1528         if (!strcmp(argv[0], "line-graphics"))
1529                 return parse_bool(&opt_line_graphics, argv[2]);
1531         if (!strcmp(argv[0], "line-number-interval"))
1532                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1534         if (!strcmp(argv[0], "author-width"))
1535                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1537         if (!strcmp(argv[0], "horizontal-scroll"))
1538                 return parse_step(&opt_hscroll, argv[2]);
1540         if (!strcmp(argv[0], "tab-size"))
1541                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1543         if (!strcmp(argv[0], "commit-encoding"))
1544                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1546         config_msg = "Unknown variable name";
1547         return ERR;
1550 /* Wants: mode request key */
1551 static int
1552 option_bind_command(int argc, const char *argv[])
1554         enum request request;
1555         int keymap;
1556         int key;
1558         if (argc < 3) {
1559                 config_msg = "Wrong number of arguments given to bind command";
1560                 return ERR;
1561         }
1563         if (set_keymap(&keymap, argv[0]) == ERR) {
1564                 config_msg = "Unknown key map";
1565                 return ERR;
1566         }
1568         key = get_key_value(argv[1]);
1569         if (key == ERR) {
1570                 config_msg = "Unknown key";
1571                 return ERR;
1572         }
1574         request = get_request(argv[2]);
1575         if (request == REQ_NONE) {
1576                 static const struct enum_map obsolete[] = {
1577                         ENUM_MAP("cherry-pick",         REQ_NONE),
1578                         ENUM_MAP("screen-resize",       REQ_NONE),
1579                         ENUM_MAP("tree-parent",         REQ_PARENT),
1580                 };
1581                 int alias;
1583                 if (map_enum(&alias, obsolete, argv[2])) {
1584                         if (alias != REQ_NONE)
1585                                 add_keybinding(keymap, alias, key);
1586                         config_msg = "Obsolete request name";
1587                         return ERR;
1588                 }
1589         }
1590         if (request == REQ_NONE && *argv[2]++ == '!')
1591                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1592         if (request == REQ_NONE) {
1593                 config_msg = "Unknown request name";
1594                 return ERR;
1595         }
1597         add_keybinding(keymap, request, key);
1599         return OK;
1602 static int
1603 set_option(const char *opt, char *value)
1605         const char *argv[SIZEOF_ARG];
1606         int argc = 0;
1608         if (!argv_from_string(argv, &argc, value)) {
1609                 config_msg = "Too many option arguments";
1610                 return ERR;
1611         }
1613         if (!strcmp(opt, "color"))
1614                 return option_color_command(argc, argv);
1616         if (!strcmp(opt, "set"))
1617                 return option_set_command(argc, argv);
1619         if (!strcmp(opt, "bind"))
1620                 return option_bind_command(argc, argv);
1622         config_msg = "Unknown option command";
1623         return ERR;
1626 static int
1627 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1629         int status = OK;
1631         config_lineno++;
1632         config_msg = "Internal error";
1634         /* Check for comment markers, since read_properties() will
1635          * only ensure opt and value are split at first " \t". */
1636         optlen = strcspn(opt, "#");
1637         if (optlen == 0)
1638                 return OK;
1640         if (opt[optlen] != 0) {
1641                 config_msg = "No option value";
1642                 status = ERR;
1644         }  else {
1645                 /* Look for comment endings in the value. */
1646                 size_t len = strcspn(value, "#");
1648                 if (len < valuelen) {
1649                         valuelen = len;
1650                         value[valuelen] = 0;
1651                 }
1653                 status = set_option(opt, value);
1654         }
1656         if (status == ERR) {
1657                 warn("Error on line %d, near '%.*s': %s",
1658                      config_lineno, (int) optlen, opt, config_msg);
1659                 config_errors = TRUE;
1660         }
1662         /* Always keep going if errors are encountered. */
1663         return OK;
1666 static void
1667 load_option_file(const char *path)
1669         struct io io = {};
1671         /* It's OK that the file doesn't exist. */
1672         if (!io_open(&io, path))
1673                 return;
1675         config_lineno = 0;
1676         config_errors = FALSE;
1678         if (io_load(&io, " \t", read_option) == ERR ||
1679             config_errors == TRUE)
1680                 warn("Errors while loading %s.", path);
1683 static int
1684 load_options(void)
1686         const char *home = getenv("HOME");
1687         const char *tigrc_user = getenv("TIGRC_USER");
1688         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1689         char buf[SIZEOF_STR];
1691         add_builtin_run_requests();
1693         if (!tigrc_system)
1694                 tigrc_system = SYSCONFDIR "/tigrc";
1695         load_option_file(tigrc_system);
1697         if (!tigrc_user) {
1698                 if (!home || !string_format(buf, "%s/.tigrc", home))
1699                         return ERR;
1700                 tigrc_user = buf;
1701         }
1702         load_option_file(tigrc_user);
1704         return OK;
1708 /*
1709  * The viewer
1710  */
1712 struct view;
1713 struct view_ops;
1715 /* The display array of active views and the index of the current view. */
1716 static struct view *display[2];
1717 static unsigned int current_view;
1719 #define foreach_displayed_view(view, i) \
1720         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1722 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1724 /* Current head and commit ID */
1725 static char ref_blob[SIZEOF_REF]        = "";
1726 static char ref_commit[SIZEOF_REF]      = "HEAD";
1727 static char ref_head[SIZEOF_REF]        = "HEAD";
1729 struct view {
1730         const char *name;       /* View name */
1731         const char *cmd_env;    /* Command line set via environment */
1732         const char *id;         /* Points to either of ref_{head,commit,blob} */
1734         struct view_ops *ops;   /* View operations */
1736         enum keymap keymap;     /* What keymap does this view have */
1737         bool git_dir;           /* Whether the view requires a git directory. */
1739         char ref[SIZEOF_REF];   /* Hovered commit reference */
1740         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1742         int height, width;      /* The width and height of the main window */
1743         WINDOW *win;            /* The main window */
1744         WINDOW *title;          /* The title window living below the main window */
1746         /* Navigation */
1747         unsigned long offset;   /* Offset of the window top */
1748         unsigned long yoffset;  /* Offset from the window side. */
1749         unsigned long lineno;   /* Current line number */
1750         unsigned long p_offset; /* Previous offset of the window top */
1751         unsigned long p_yoffset;/* Previous offset from the window side */
1752         unsigned long p_lineno; /* Previous current line number */
1753         bool p_restore;         /* Should the previous position be restored. */
1755         /* Searching */
1756         char grep[SIZEOF_STR];  /* Search string */
1757         regex_t *regex;         /* Pre-compiled regexp */
1759         /* If non-NULL, points to the view that opened this view. If this view
1760          * is closed tig will switch back to the parent view. */
1761         struct view *parent;
1763         /* Buffering */
1764         size_t lines;           /* Total number of lines */
1765         struct line *line;      /* Line index */
1766         size_t line_alloc;      /* Total number of allocated lines */
1767         unsigned int digits;    /* Number of digits in the lines member. */
1769         /* Drawing */
1770         struct line *curline;   /* Line currently being drawn. */
1771         enum line_type curtype; /* Attribute currently used for drawing. */
1772         unsigned long col;      /* Column when drawing. */
1773         bool has_scrolled;      /* View was scrolled. */
1775         /* Loading */
1776         struct io io;
1777         struct io *pipe;
1778         time_t start_time;
1779         time_t update_secs;
1780 };
1782 struct view_ops {
1783         /* What type of content being displayed. Used in the title bar. */
1784         const char *type;
1785         /* Default command arguments. */
1786         const char **argv;
1787         /* Open and reads in all view content. */
1788         bool (*open)(struct view *view);
1789         /* Read one line; updates view->line. */
1790         bool (*read)(struct view *view, char *data);
1791         /* Draw one line; @lineno must be < view->height. */
1792         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1793         /* Depending on view handle a special requests. */
1794         enum request (*request)(struct view *view, enum request request, struct line *line);
1795         /* Search for regexp in a line. */
1796         bool (*grep)(struct view *view, struct line *line);
1797         /* Select line */
1798         void (*select)(struct view *view, struct line *line);
1799 };
1801 static struct view_ops blame_ops;
1802 static struct view_ops blob_ops;
1803 static struct view_ops diff_ops;
1804 static struct view_ops help_ops;
1805 static struct view_ops log_ops;
1806 static struct view_ops main_ops;
1807 static struct view_ops pager_ops;
1808 static struct view_ops stage_ops;
1809 static struct view_ops status_ops;
1810 static struct view_ops tree_ops;
1812 #define VIEW_STR(name, env, ref, ops, map, git) \
1813         { name, #env, ref, ops, map, git }
1815 #define VIEW_(id, name, ops, git, ref) \
1816         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1819 static struct view views[] = {
1820         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1821         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1822         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1823         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1824         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1825         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1826         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1827         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1828         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1829         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1830 };
1832 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1833 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1835 #define foreach_view(view, i) \
1836         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1838 #define view_is_displayed(view) \
1839         (view == display[0] || view == display[1])
1842 enum line_graphic {
1843         LINE_GRAPHIC_VLINE
1844 };
1846 static chtype line_graphics[] = {
1847         /* LINE_GRAPHIC_VLINE: */ '|'
1848 };
1850 static inline void
1851 set_view_attr(struct view *view, enum line_type type)
1853         if (!view->curline->selected && view->curtype != type) {
1854                 wattrset(view->win, get_line_attr(type));
1855                 wchgat(view->win, -1, 0, type, NULL);
1856                 view->curtype = type;
1857         }
1860 static int
1861 draw_chars(struct view *view, enum line_type type, const char *string,
1862            int max_len, bool use_tilde)
1864         int len = 0;
1865         int col = 0;
1866         int trimmed = FALSE;
1867         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1869         if (max_len <= 0)
1870                 return 0;
1872         if (opt_utf8) {
1873                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1874         } else {
1875                 col = len = strlen(string);
1876                 if (len > max_len) {
1877                         if (use_tilde) {
1878                                 max_len -= 1;
1879                         }
1880                         col = len = max_len;
1881                         trimmed = TRUE;
1882                 }
1883         }
1885         set_view_attr(view, type);
1886         if (len > 0)
1887                 waddnstr(view->win, string, len);
1888         if (trimmed && use_tilde) {
1889                 set_view_attr(view, LINE_DELIMITER);
1890                 waddch(view->win, '~');
1891                 col++;
1892         }
1894         return col;
1897 static int
1898 draw_space(struct view *view, enum line_type type, int max, int spaces)
1900         static char space[] = "                    ";
1901         int col = 0;
1903         spaces = MIN(max, spaces);
1905         while (spaces > 0) {
1906                 int len = MIN(spaces, sizeof(space) - 1);
1908                 col += draw_chars(view, type, space, len, FALSE);
1909                 spaces -= len;
1910         }
1912         return col;
1915 static bool
1916 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1918         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1919         return view->width + view->yoffset <= view->col;
1922 static bool
1923 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1925         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1926         int max = view->width + view->yoffset - view->col;
1927         int i;
1929         if (max < size)
1930                 size = max;
1932         set_view_attr(view, type);
1933         /* Using waddch() instead of waddnstr() ensures that
1934          * they'll be rendered correctly for the cursor line. */
1935         for (i = skip; i < size; i++)
1936                 waddch(view->win, graphic[i]);
1938         view->col += size;
1939         if (size < max && skip <= size)
1940                 waddch(view->win, ' ');
1941         view->col++;
1943         return view->width + view->yoffset <= view->col;
1946 static bool
1947 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1949         int max = MIN(view->width + view->yoffset - view->col, len);
1950         int col;
1952         if (text)
1953                 col = draw_chars(view, type, text, max - 1, trim);
1954         else
1955                 col = draw_space(view, type, max - 1, max - 1);
1957         view->col += col;
1958         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1959         return view->width + view->yoffset <= view->col;
1962 static bool
1963 draw_date(struct view *view, struct tm *time)
1965         char buf[DATE_COLS];
1966         char *date;
1967         int timelen = 0;
1969         if (time)
1970                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1971         date = timelen ? buf : NULL;
1973         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1976 static bool
1977 draw_author(struct view *view, const char *author)
1979         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1981         if (!trim) {
1982                 static char initials[10];
1983                 size_t pos;
1985 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
1987                 memset(initials, 0, sizeof(initials));
1988                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
1989                         while (is_initial_sep(*author))
1990                                 author++;
1991                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
1992                         while (*author && !is_initial_sep(author[1]))
1993                                 author++;
1994                 }
1996                 author = initials;
1997         }
1999         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2002 static bool
2003 draw_mode(struct view *view, mode_t mode)
2005         const char *str;
2007         if (S_ISDIR(mode))
2008                 str = "drwxr-xr-x";
2009         else if (S_ISLNK(mode))
2010                 str = "lrwxrwxrwx";
2011         else if (S_ISGITLINK(mode))
2012                 str = "m---------";
2013         else if (S_ISREG(mode) && mode & S_IXUSR)
2014                 str = "-rwxr-xr-x";
2015         else if (S_ISREG(mode))
2016                 str = "-rw-r--r--";
2017         else
2018                 str = "----------";
2020         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2023 static bool
2024 draw_lineno(struct view *view, unsigned int lineno)
2026         char number[10];
2027         int digits3 = view->digits < 3 ? 3 : view->digits;
2028         int max = MIN(view->width + view->yoffset - view->col, digits3);
2029         char *text = NULL;
2031         lineno += view->offset + 1;
2032         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2033                 static char fmt[] = "%1ld";
2035                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2036                 if (string_format(number, fmt, lineno))
2037                         text = number;
2038         }
2039         if (text)
2040                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2041         else
2042                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2043         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2046 static bool
2047 draw_view_line(struct view *view, unsigned int lineno)
2049         struct line *line;
2050         bool selected = (view->offset + lineno == view->lineno);
2052         assert(view_is_displayed(view));
2054         if (view->offset + lineno >= view->lines)
2055                 return FALSE;
2057         line = &view->line[view->offset + lineno];
2059         wmove(view->win, lineno, 0);
2060         if (line->cleareol)
2061                 wclrtoeol(view->win);
2062         view->col = 0;
2063         view->curline = line;
2064         view->curtype = LINE_NONE;
2065         line->selected = FALSE;
2066         line->dirty = line->cleareol = 0;
2068         if (selected) {
2069                 set_view_attr(view, LINE_CURSOR);
2070                 line->selected = TRUE;
2071                 view->ops->select(view, line);
2072         }
2074         return view->ops->draw(view, line, lineno);
2077 static void
2078 redraw_view_dirty(struct view *view)
2080         bool dirty = FALSE;
2081         int lineno;
2083         for (lineno = 0; lineno < view->height; lineno++) {
2084                 if (view->offset + lineno >= view->lines)
2085                         break;
2086                 if (!view->line[view->offset + lineno].dirty)
2087                         continue;
2088                 dirty = TRUE;
2089                 if (!draw_view_line(view, lineno))
2090                         break;
2091         }
2093         if (!dirty)
2094                 return;
2095         wnoutrefresh(view->win);
2098 static void
2099 redraw_view_from(struct view *view, int lineno)
2101         assert(0 <= lineno && lineno < view->height);
2103         for (; lineno < view->height; lineno++) {
2104                 if (!draw_view_line(view, lineno))
2105                         break;
2106         }
2108         wnoutrefresh(view->win);
2111 static void
2112 redraw_view(struct view *view)
2114         werase(view->win);
2115         redraw_view_from(view, 0);
2119 static void
2120 update_view_title(struct view *view)
2122         char buf[SIZEOF_STR];
2123         char state[SIZEOF_STR];
2124         size_t bufpos = 0, statelen = 0;
2126         assert(view_is_displayed(view));
2128         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2129                 unsigned int view_lines = view->offset + view->height;
2130                 unsigned int lines = view->lines
2131                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2132                                    : 0;
2134                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2135                                    view->ops->type,
2136                                    view->lineno + 1,
2137                                    view->lines,
2138                                    lines);
2140         }
2142         if (view->pipe) {
2143                 time_t secs = time(NULL) - view->start_time;
2145                 /* Three git seconds are a long time ... */
2146                 if (secs > 2)
2147                         string_format_from(state, &statelen, " loading %lds", secs);
2148         }
2150         string_format_from(buf, &bufpos, "[%s]", view->name);
2151         if (*view->ref && bufpos < view->width) {
2152                 size_t refsize = strlen(view->ref);
2153                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2155                 if (minsize < view->width)
2156                         refsize = view->width - minsize + 7;
2157                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2158         }
2160         if (statelen && bufpos < view->width) {
2161                 string_format_from(buf, &bufpos, "%s", state);
2162         }
2164         if (view == display[current_view])
2165                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2166         else
2167                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2169         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2170         wclrtoeol(view->title);
2171         wnoutrefresh(view->title);
2174 static void
2175 resize_display(void)
2177         int offset, i;
2178         struct view *base = display[0];
2179         struct view *view = display[1] ? display[1] : display[0];
2181         /* Setup window dimensions */
2183         getmaxyx(stdscr, base->height, base->width);
2185         /* Make room for the status window. */
2186         base->height -= 1;
2188         if (view != base) {
2189                 /* Horizontal split. */
2190                 view->width   = base->width;
2191                 view->height  = SCALE_SPLIT_VIEW(base->height);
2192                 base->height -= view->height;
2194                 /* Make room for the title bar. */
2195                 view->height -= 1;
2196         }
2198         /* Make room for the title bar. */
2199         base->height -= 1;
2201         offset = 0;
2203         foreach_displayed_view (view, i) {
2204                 if (!view->win) {
2205                         view->win = newwin(view->height, 0, offset, 0);
2206                         if (!view->win)
2207                                 die("Failed to create %s view", view->name);
2209                         scrollok(view->win, FALSE);
2211                         view->title = newwin(1, 0, offset + view->height, 0);
2212                         if (!view->title)
2213                                 die("Failed to create title window");
2215                 } else {
2216                         wresize(view->win, view->height, view->width);
2217                         mvwin(view->win,   offset, 0);
2218                         mvwin(view->title, offset + view->height, 0);
2219                 }
2221                 offset += view->height + 1;
2222         }
2225 static void
2226 redraw_display(bool clear)
2228         struct view *view;
2229         int i;
2231         foreach_displayed_view (view, i) {
2232                 if (clear)
2233                         wclear(view->win);
2234                 redraw_view(view);
2235                 update_view_title(view);
2236         }
2239 static void
2240 toggle_view_option(bool *option, const char *help)
2242         *option = !*option;
2243         redraw_display(FALSE);
2244         report("%sabling %s", *option ? "En" : "Dis", help);
2247 static void
2248 maximize_view(struct view *view)
2250         memset(display, 0, sizeof(display));
2251         current_view = 0;
2252         display[current_view] = view;
2253         resize_display();
2254         redraw_display(FALSE);
2255         report("");
2259 /*
2260  * Navigation
2261  */
2263 static bool
2264 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2266         if (lineno >= view->lines)
2267                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2269         if (offset > lineno || offset + view->height <= lineno) {
2270                 unsigned long half = view->height / 2;
2272                 if (lineno > half)
2273                         offset = lineno - half;
2274                 else
2275                         offset = 0;
2276         }
2278         if (offset != view->offset || lineno != view->lineno) {
2279                 view->offset = offset;
2280                 view->lineno = lineno;
2281                 return TRUE;
2282         }
2284         return FALSE;
2287 static int
2288 apply_step(double step, int value)
2290         if (step >= 1)
2291                 return (int) step;
2292         value *= step + 0.01;
2293         return value ? value : 1;
2296 /* Scrolling backend */
2297 static void
2298 do_scroll_view(struct view *view, int lines)
2300         bool redraw_current_line = FALSE;
2302         /* The rendering expects the new offset. */
2303         view->offset += lines;
2305         assert(0 <= view->offset && view->offset < view->lines);
2306         assert(lines);
2308         /* Move current line into the view. */
2309         if (view->lineno < view->offset) {
2310                 view->lineno = view->offset;
2311                 redraw_current_line = TRUE;
2312         } else if (view->lineno >= view->offset + view->height) {
2313                 view->lineno = view->offset + view->height - 1;
2314                 redraw_current_line = TRUE;
2315         }
2317         assert(view->offset <= view->lineno && view->lineno < view->lines);
2319         /* Redraw the whole screen if scrolling is pointless. */
2320         if (view->height < ABS(lines)) {
2321                 redraw_view(view);
2323         } else {
2324                 int line = lines > 0 ? view->height - lines : 0;
2325                 int end = line + ABS(lines);
2327                 scrollok(view->win, TRUE);
2328                 wscrl(view->win, lines);
2329                 scrollok(view->win, FALSE);
2331                 while (line < end && draw_view_line(view, line))
2332                         line++;
2334                 if (redraw_current_line)
2335                         draw_view_line(view, view->lineno - view->offset);
2336                 wnoutrefresh(view->win);
2337         }
2339         view->has_scrolled = TRUE;
2340         report("");
2343 /* Scroll frontend */
2344 static void
2345 scroll_view(struct view *view, enum request request)
2347         int lines = 1;
2349         assert(view_is_displayed(view));
2351         switch (request) {
2352         case REQ_SCROLL_LEFT:
2353                 if (view->yoffset == 0) {
2354                         report("Cannot scroll beyond the first column");
2355                         return;
2356                 }
2357                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2358                         view->yoffset = 0;
2359                 else
2360                         view->yoffset -= apply_step(opt_hscroll, view->width);
2361                 redraw_view_from(view, 0);
2362                 report("");
2363                 return;
2364         case REQ_SCROLL_RIGHT:
2365                 view->yoffset += apply_step(opt_hscroll, view->width);
2366                 redraw_view(view);
2367                 report("");
2368                 return;
2369         case REQ_SCROLL_PAGE_DOWN:
2370                 lines = view->height;
2371         case REQ_SCROLL_LINE_DOWN:
2372                 if (view->offset + lines > view->lines)
2373                         lines = view->lines - view->offset;
2375                 if (lines == 0 || view->offset + view->height >= view->lines) {
2376                         report("Cannot scroll beyond the last line");
2377                         return;
2378                 }
2379                 break;
2381         case REQ_SCROLL_PAGE_UP:
2382                 lines = view->height;
2383         case REQ_SCROLL_LINE_UP:
2384                 if (lines > view->offset)
2385                         lines = view->offset;
2387                 if (lines == 0) {
2388                         report("Cannot scroll beyond the first line");
2389                         return;
2390                 }
2392                 lines = -lines;
2393                 break;
2395         default:
2396                 die("request %d not handled in switch", request);
2397         }
2399         do_scroll_view(view, lines);
2402 /* Cursor moving */
2403 static void
2404 move_view(struct view *view, enum request request)
2406         int scroll_steps = 0;
2407         int steps;
2409         switch (request) {
2410         case REQ_MOVE_FIRST_LINE:
2411                 steps = -view->lineno;
2412                 break;
2414         case REQ_MOVE_LAST_LINE:
2415                 steps = view->lines - view->lineno - 1;
2416                 break;
2418         case REQ_MOVE_PAGE_UP:
2419                 steps = view->height > view->lineno
2420                       ? -view->lineno : -view->height;
2421                 break;
2423         case REQ_MOVE_PAGE_DOWN:
2424                 steps = view->lineno + view->height >= view->lines
2425                       ? view->lines - view->lineno - 1 : view->height;
2426                 break;
2428         case REQ_MOVE_UP:
2429                 steps = -1;
2430                 break;
2432         case REQ_MOVE_DOWN:
2433                 steps = 1;
2434                 break;
2436         default:
2437                 die("request %d not handled in switch", request);
2438         }
2440         if (steps <= 0 && view->lineno == 0) {
2441                 report("Cannot move beyond the first line");
2442                 return;
2444         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2445                 report("Cannot move beyond the last line");
2446                 return;
2447         }
2449         /* Move the current line */
2450         view->lineno += steps;
2451         assert(0 <= view->lineno && view->lineno < view->lines);
2453         /* Check whether the view needs to be scrolled */
2454         if (view->lineno < view->offset ||
2455             view->lineno >= view->offset + view->height) {
2456                 scroll_steps = steps;
2457                 if (steps < 0 && -steps > view->offset) {
2458                         scroll_steps = -view->offset;
2460                 } else if (steps > 0) {
2461                         if (view->lineno == view->lines - 1 &&
2462                             view->lines > view->height) {
2463                                 scroll_steps = view->lines - view->offset - 1;
2464                                 if (scroll_steps >= view->height)
2465                                         scroll_steps -= view->height - 1;
2466                         }
2467                 }
2468         }
2470         if (!view_is_displayed(view)) {
2471                 view->offset += scroll_steps;
2472                 assert(0 <= view->offset && view->offset < view->lines);
2473                 view->ops->select(view, &view->line[view->lineno]);
2474                 return;
2475         }
2477         /* Repaint the old "current" line if we be scrolling */
2478         if (ABS(steps) < view->height)
2479                 draw_view_line(view, view->lineno - steps - view->offset);
2481         if (scroll_steps) {
2482                 do_scroll_view(view, scroll_steps);
2483                 return;
2484         }
2486         /* Draw the current line */
2487         draw_view_line(view, view->lineno - view->offset);
2489         wnoutrefresh(view->win);
2490         report("");
2494 /*
2495  * Searching
2496  */
2498 static void search_view(struct view *view, enum request request);
2500 static void
2501 select_view_line(struct view *view, unsigned long lineno)
2503         unsigned long old_lineno = view->lineno;
2504         unsigned long old_offset = view->offset;
2506         if (goto_view_line(view, view->offset, lineno)) {
2507                 if (view_is_displayed(view)) {
2508                         if (old_offset != view->offset) {
2509                                 redraw_view(view);
2510                         } else {
2511                                 draw_view_line(view, old_lineno - view->offset);
2512                                 draw_view_line(view, view->lineno - view->offset);
2513                                 wnoutrefresh(view->win);
2514                         }
2515                 } else {
2516                         view->ops->select(view, &view->line[view->lineno]);
2517                 }
2518         }
2521 static void
2522 find_next(struct view *view, enum request request)
2524         unsigned long lineno = view->lineno;
2525         int direction;
2527         if (!*view->grep) {
2528                 if (!*opt_search)
2529                         report("No previous search");
2530                 else
2531                         search_view(view, request);
2532                 return;
2533         }
2535         switch (request) {
2536         case REQ_SEARCH:
2537         case REQ_FIND_NEXT:
2538                 direction = 1;
2539                 break;
2541         case REQ_SEARCH_BACK:
2542         case REQ_FIND_PREV:
2543                 direction = -1;
2544                 break;
2546         default:
2547                 return;
2548         }
2550         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2551                 lineno += direction;
2553         /* Note, lineno is unsigned long so will wrap around in which case it
2554          * will become bigger than view->lines. */
2555         for (; lineno < view->lines; lineno += direction) {
2556                 if (view->ops->grep(view, &view->line[lineno])) {
2557                         select_view_line(view, lineno);
2558                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2559                         return;
2560                 }
2561         }
2563         report("No match found for '%s'", view->grep);
2566 static void
2567 search_view(struct view *view, enum request request)
2569         int regex_err;
2571         if (view->regex) {
2572                 regfree(view->regex);
2573                 *view->grep = 0;
2574         } else {
2575                 view->regex = calloc(1, sizeof(*view->regex));
2576                 if (!view->regex)
2577                         return;
2578         }
2580         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2581         if (regex_err != 0) {
2582                 char buf[SIZEOF_STR] = "unknown error";
2584                 regerror(regex_err, view->regex, buf, sizeof(buf));
2585                 report("Search failed: %s", buf);
2586                 return;
2587         }
2589         string_copy(view->grep, opt_search);
2591         find_next(view, request);
2594 /*
2595  * Incremental updating
2596  */
2598 static void
2599 reset_view(struct view *view)
2601         int i;
2603         for (i = 0; i < view->lines; i++)
2604                 free(view->line[i].data);
2605         free(view->line);
2607         view->p_offset = view->offset;
2608         view->p_yoffset = view->yoffset;
2609         view->p_lineno = view->lineno;
2611         view->line = NULL;
2612         view->offset = 0;
2613         view->yoffset = 0;
2614         view->lines  = 0;
2615         view->lineno = 0;
2616         view->line_alloc = 0;
2617         view->vid[0] = 0;
2618         view->update_secs = 0;
2621 static void
2622 free_argv(const char *argv[])
2624         int argc;
2626         for (argc = 0; argv[argc]; argc++)
2627                 free((void *) argv[argc]);
2630 static bool
2631 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2633         char buf[SIZEOF_STR];
2634         int argc;
2635         bool noreplace = flags == FORMAT_NONE;
2637         free_argv(dst_argv);
2639         for (argc = 0; src_argv[argc]; argc++) {
2640                 const char *arg = src_argv[argc];
2641                 size_t bufpos = 0;
2643                 while (arg) {
2644                         char *next = strstr(arg, "%(");
2645                         int len = next - arg;
2646                         const char *value;
2648                         if (!next || noreplace) {
2649                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2650                                         noreplace = TRUE;
2651                                 len = strlen(arg);
2652                                 value = "";
2654                         } else if (!prefixcmp(next, "%(directory)")) {
2655                                 value = opt_path;
2657                         } else if (!prefixcmp(next, "%(file)")) {
2658                                 value = opt_file;
2660                         } else if (!prefixcmp(next, "%(ref)")) {
2661                                 value = *opt_ref ? opt_ref : "HEAD";
2663                         } else if (!prefixcmp(next, "%(head)")) {
2664                                 value = ref_head;
2666                         } else if (!prefixcmp(next, "%(commit)")) {
2667                                 value = ref_commit;
2669                         } else if (!prefixcmp(next, "%(blob)")) {
2670                                 value = ref_blob;
2672                         } else {
2673                                 report("Unknown replacement: `%s`", next);
2674                                 return FALSE;
2675                         }
2677                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2678                                 return FALSE;
2680                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2681                 }
2683                 dst_argv[argc] = strdup(buf);
2684                 if (!dst_argv[argc])
2685                         break;
2686         }
2688         dst_argv[argc] = NULL;
2690         return src_argv[argc] == NULL;
2693 static bool
2694 restore_view_position(struct view *view)
2696         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2697                 return FALSE;
2699         /* Changing the view position cancels the restoring. */
2700         /* FIXME: Changing back to the first line is not detected. */
2701         if (view->offset != 0 || view->lineno != 0) {
2702                 view->p_restore = FALSE;
2703                 return FALSE;
2704         }
2706         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2707             view_is_displayed(view))
2708                 werase(view->win);
2710         view->yoffset = view->p_yoffset;
2711         view->p_restore = FALSE;
2713         return TRUE;
2716 static void
2717 end_update(struct view *view, bool force)
2719         if (!view->pipe)
2720                 return;
2721         while (!view->ops->read(view, NULL))
2722                 if (!force)
2723                         return;
2724         set_nonblocking_input(FALSE);
2725         if (force)
2726                 kill_io(view->pipe);
2727         done_io(view->pipe);
2728         view->pipe = NULL;
2731 static void
2732 setup_update(struct view *view, const char *vid)
2734         set_nonblocking_input(TRUE);
2735         reset_view(view);
2736         string_copy_rev(view->vid, vid);
2737         view->pipe = &view->io;
2738         view->start_time = time(NULL);
2741 static bool
2742 prepare_update(struct view *view, const char *argv[], const char *dir,
2743                enum format_flags flags)
2745         if (view->pipe)
2746                 end_update(view, TRUE);
2747         return init_io_rd(&view->io, argv, dir, flags);
2750 static bool
2751 prepare_update_file(struct view *view, const char *name)
2753         if (view->pipe)
2754                 end_update(view, TRUE);
2755         return io_open(&view->io, name);
2758 static bool
2759 begin_update(struct view *view, bool refresh)
2761         if (view->pipe)
2762                 end_update(view, TRUE);
2764         if (refresh) {
2765                 if (!start_io(&view->io))
2766                         return FALSE;
2768         } else {
2769                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2770                         opt_path[0] = 0;
2772                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2773                         return FALSE;
2775                 /* Put the current ref_* value to the view title ref
2776                  * member. This is needed by the blob view. Most other
2777                  * views sets it automatically after loading because the
2778                  * first line is a commit line. */
2779                 string_copy_rev(view->ref, view->id);
2780         }
2782         setup_update(view, view->id);
2784         return TRUE;
2787 #define ITEM_CHUNK_SIZE 256
2788 static void *
2789 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2791         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2792         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2794         if (mem == NULL || num_chunks != num_chunks_new) {
2795                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2796                 mem = realloc(mem, *size * item_size);
2797         }
2799         return mem;
2802 static struct line *
2803 realloc_lines(struct view *view, size_t line_size)
2805         size_t alloc = view->line_alloc;
2806         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2807                                          sizeof(*view->line));
2809         if (!tmp)
2810                 return NULL;
2812         view->line = tmp;
2813         view->line_alloc = alloc;
2814         return view->line;
2817 static bool
2818 update_view(struct view *view)
2820         char out_buffer[BUFSIZ * 2];
2821         char *line;
2822         /* Clear the view and redraw everything since the tree sorting
2823          * might have rearranged things. */
2824         bool redraw = view->lines == 0;
2825         bool can_read = TRUE;
2827         if (!view->pipe)
2828                 return TRUE;
2830         if (!io_can_read(view->pipe)) {
2831                 if (view->lines == 0 && view_is_displayed(view)) {
2832                         time_t secs = time(NULL) - view->start_time;
2834                         if (secs > 1 && secs > view->update_secs) {
2835                                 if (view->update_secs == 0)
2836                                         redraw_view(view);
2837                                 update_view_title(view);
2838                                 view->update_secs = secs;
2839                         }
2840                 }
2841                 return TRUE;
2842         }
2844         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2845                 if (opt_iconv != ICONV_NONE) {
2846                         ICONV_CONST char *inbuf = line;
2847                         size_t inlen = strlen(line) + 1;
2849                         char *outbuf = out_buffer;
2850                         size_t outlen = sizeof(out_buffer);
2852                         size_t ret;
2854                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2855                         if (ret != (size_t) -1)
2856                                 line = out_buffer;
2857                 }
2859                 if (!view->ops->read(view, line)) {
2860                         report("Allocation failure");
2861                         end_update(view, TRUE);
2862                         return FALSE;
2863                 }
2864         }
2866         {
2867                 unsigned long lines = view->lines;
2868                 int digits;
2870                 for (digits = 0; lines; digits++)
2871                         lines /= 10;
2873                 /* Keep the displayed view in sync with line number scaling. */
2874                 if (digits != view->digits) {
2875                         view->digits = digits;
2876                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2877                                 redraw = TRUE;
2878                 }
2879         }
2881         if (io_error(view->pipe)) {
2882                 report("Failed to read: %s", io_strerror(view->pipe));
2883                 end_update(view, TRUE);
2885         } else if (io_eof(view->pipe)) {
2886                 report("");
2887                 end_update(view, FALSE);
2888         }
2890         if (restore_view_position(view))
2891                 redraw = TRUE;
2893         if (!view_is_displayed(view))
2894                 return TRUE;
2896         if (redraw)
2897                 redraw_view_from(view, 0);
2898         else
2899                 redraw_view_dirty(view);
2901         /* Update the title _after_ the redraw so that if the redraw picks up a
2902          * commit reference in view->ref it'll be available here. */
2903         update_view_title(view);
2904         return TRUE;
2907 static struct line *
2908 add_line_data(struct view *view, void *data, enum line_type type)
2910         struct line *line;
2912         if (!realloc_lines(view, view->lines + 1))
2913                 return NULL;
2915         line = &view->line[view->lines++];
2916         memset(line, 0, sizeof(*line));
2917         line->type = type;
2918         line->data = data;
2919         line->dirty = 1;
2921         return line;
2924 static struct line *
2925 add_line_text(struct view *view, const char *text, enum line_type type)
2927         char *data = text ? strdup(text) : NULL;
2929         return data ? add_line_data(view, data, type) : NULL;
2932 static struct line *
2933 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2935         char buf[SIZEOF_STR];
2936         va_list args;
2938         va_start(args, fmt);
2939         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2940                 buf[0] = 0;
2941         va_end(args);
2943         return buf[0] ? add_line_text(view, buf, type) : NULL;
2946 /*
2947  * View opening
2948  */
2950 enum open_flags {
2951         OPEN_DEFAULT = 0,       /* Use default view switching. */
2952         OPEN_SPLIT = 1,         /* Split current view. */
2953         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2954         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2955         OPEN_PREPARED = 32,     /* Open already prepared command. */
2956 };
2958 static void
2959 open_view(struct view *prev, enum request request, enum open_flags flags)
2961         bool split = !!(flags & OPEN_SPLIT);
2962         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2963         bool nomaximize = !!(flags & OPEN_REFRESH);
2964         struct view *view = VIEW(request);
2965         int nviews = displayed_views();
2966         struct view *base_view = display[0];
2968         if (view == prev && nviews == 1 && !reload) {
2969                 report("Already in %s view", view->name);
2970                 return;
2971         }
2973         if (view->git_dir && !opt_git_dir[0]) {
2974                 report("The %s view is disabled in pager view", view->name);
2975                 return;
2976         }
2978         if (split) {
2979                 display[1] = view;
2980                 current_view = 1;
2981         } else if (!nomaximize) {
2982                 /* Maximize the current view. */
2983                 memset(display, 0, sizeof(display));
2984                 current_view = 0;
2985                 display[current_view] = view;
2986         }
2988         /* Resize the view when switching between split- and full-screen,
2989          * or when switching between two different full-screen views. */
2990         if (nviews != displayed_views() ||
2991             (nviews == 1 && base_view != display[0]))
2992                 resize_display();
2994         if (view->ops->open) {
2995                 if (view->pipe)
2996                         end_update(view, TRUE);
2997                 if (!view->ops->open(view)) {
2998                         report("Failed to load %s view", view->name);
2999                         return;
3000                 }
3001                 restore_view_position(view);
3003         } else if ((reload || strcmp(view->vid, view->id)) &&
3004                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3005                 report("Failed to load %s view", view->name);
3006                 return;
3007         }
3009         if (split && prev->lineno - prev->offset >= prev->height) {
3010                 /* Take the title line into account. */
3011                 int lines = prev->lineno - prev->offset - prev->height + 1;
3013                 /* Scroll the view that was split if the current line is
3014                  * outside the new limited view. */
3015                 do_scroll_view(prev, lines);
3016         }
3018         if (prev && view != prev) {
3019                 if (split) {
3020                         /* "Blur" the previous view. */
3021                         update_view_title(prev);
3022                 }
3024                 view->parent = prev;
3025         }
3027         if (view->pipe && view->lines == 0) {
3028                 /* Clear the old view and let the incremental updating refill
3029                  * the screen. */
3030                 werase(view->win);
3031                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3032                 report("");
3033         } else if (view_is_displayed(view)) {
3034                 redraw_view(view);
3035                 report("");
3036         }
3039 static void
3040 open_external_viewer(const char *argv[], const char *dir)
3042         def_prog_mode();           /* save current tty modes */
3043         endwin();                  /* restore original tty modes */
3044         run_io_fg(argv, dir);
3045         fprintf(stderr, "Press Enter to continue");
3046         getc(opt_tty);
3047         reset_prog_mode();
3048         redraw_display(TRUE);
3051 static void
3052 open_mergetool(const char *file)
3054         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3056         open_external_viewer(mergetool_argv, opt_cdup);
3059 static void
3060 open_editor(bool from_root, const char *file)
3062         const char *editor_argv[] = { "vi", file, NULL };
3063         const char *editor;
3065         editor = getenv("GIT_EDITOR");
3066         if (!editor && *opt_editor)
3067                 editor = opt_editor;
3068         if (!editor)
3069                 editor = getenv("VISUAL");
3070         if (!editor)
3071                 editor = getenv("EDITOR");
3072         if (!editor)
3073                 editor = "vi";
3075         editor_argv[0] = editor;
3076         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3079 static void
3080 open_run_request(enum request request)
3082         struct run_request *req = get_run_request(request);
3083         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3085         if (!req) {
3086                 report("Unknown run request");
3087                 return;
3088         }
3090         if (format_argv(argv, req->argv, FORMAT_ALL))
3091                 open_external_viewer(argv, NULL);
3092         free_argv(argv);
3095 /*
3096  * User request switch noodle
3097  */
3099 static int
3100 view_driver(struct view *view, enum request request)
3102         int i;
3104         if (request == REQ_NONE)
3105                 return TRUE;
3107         if (request > REQ_NONE) {
3108                 open_run_request(request);
3109                 /* FIXME: When all views can refresh always do this. */
3110                 if (view == VIEW(REQ_VIEW_STATUS) ||
3111                     view == VIEW(REQ_VIEW_MAIN) ||
3112                     view == VIEW(REQ_VIEW_LOG) ||
3113                     view == VIEW(REQ_VIEW_STAGE))
3114                         request = REQ_REFRESH;
3115                 else
3116                         return TRUE;
3117         }
3119         if (view && view->lines) {
3120                 request = view->ops->request(view, request, &view->line[view->lineno]);
3121                 if (request == REQ_NONE)
3122                         return TRUE;
3123         }
3125         switch (request) {
3126         case REQ_MOVE_UP:
3127         case REQ_MOVE_DOWN:
3128         case REQ_MOVE_PAGE_UP:
3129         case REQ_MOVE_PAGE_DOWN:
3130         case REQ_MOVE_FIRST_LINE:
3131         case REQ_MOVE_LAST_LINE:
3132                 move_view(view, request);
3133                 break;
3135         case REQ_SCROLL_LEFT:
3136         case REQ_SCROLL_RIGHT:
3137         case REQ_SCROLL_LINE_DOWN:
3138         case REQ_SCROLL_LINE_UP:
3139         case REQ_SCROLL_PAGE_DOWN:
3140         case REQ_SCROLL_PAGE_UP:
3141                 scroll_view(view, request);
3142                 break;
3144         case REQ_VIEW_BLAME:
3145                 if (!opt_file[0]) {
3146                         report("No file chosen, press %s to open tree view",
3147                                get_key(REQ_VIEW_TREE));
3148                         break;
3149                 }
3150                 open_view(view, request, OPEN_DEFAULT);
3151                 break;
3153         case REQ_VIEW_BLOB:
3154                 if (!ref_blob[0]) {
3155                         report("No file chosen, press %s to open tree view",
3156                                get_key(REQ_VIEW_TREE));
3157                         break;
3158                 }
3159                 open_view(view, request, OPEN_DEFAULT);
3160                 break;
3162         case REQ_VIEW_PAGER:
3163                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3164                         report("No pager content, press %s to run command from prompt",
3165                                get_key(REQ_PROMPT));
3166                         break;
3167                 }
3168                 open_view(view, request, OPEN_DEFAULT);
3169                 break;
3171         case REQ_VIEW_STAGE:
3172                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3173                         report("No stage content, press %s to open the status view and choose file",
3174                                get_key(REQ_VIEW_STATUS));
3175                         break;
3176                 }
3177                 open_view(view, request, OPEN_DEFAULT);
3178                 break;
3180         case REQ_VIEW_STATUS:
3181                 if (opt_is_inside_work_tree == FALSE) {
3182                         report("The status view requires a working tree");
3183                         break;
3184                 }
3185                 open_view(view, request, OPEN_DEFAULT);
3186                 break;
3188         case REQ_VIEW_MAIN:
3189         case REQ_VIEW_DIFF:
3190         case REQ_VIEW_LOG:
3191         case REQ_VIEW_TREE:
3192         case REQ_VIEW_HELP:
3193                 open_view(view, request, OPEN_DEFAULT);
3194                 break;
3196         case REQ_NEXT:
3197         case REQ_PREVIOUS:
3198                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3200                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3201                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3202                    (view == VIEW(REQ_VIEW_DIFF) &&
3203                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3204                    (view == VIEW(REQ_VIEW_STAGE) &&
3205                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3206                    (view == VIEW(REQ_VIEW_BLOB) &&
3207                      view->parent == VIEW(REQ_VIEW_TREE))) {
3208                         int line;
3210                         view = view->parent;
3211                         line = view->lineno;
3212                         move_view(view, request);
3213                         if (view_is_displayed(view))
3214                                 update_view_title(view);
3215                         if (line != view->lineno)
3216                                 view->ops->request(view, REQ_ENTER,
3217                                                    &view->line[view->lineno]);
3219                 } else {
3220                         move_view(view, request);
3221                 }
3222                 break;
3224         case REQ_VIEW_NEXT:
3225         {
3226                 int nviews = displayed_views();
3227                 int next_view = (current_view + 1) % nviews;
3229                 if (next_view == current_view) {
3230                         report("Only one view is displayed");
3231                         break;
3232                 }
3234                 current_view = next_view;
3235                 /* Blur out the title of the previous view. */
3236                 update_view_title(view);
3237                 report("");
3238                 break;
3239         }
3240         case REQ_REFRESH:
3241                 report("Refreshing is not yet supported for the %s view", view->name);
3242                 break;
3244         case REQ_MAXIMIZE:
3245                 if (displayed_views() == 2)
3246                         maximize_view(view);
3247                 break;
3249         case REQ_TOGGLE_LINENO:
3250                 toggle_view_option(&opt_line_number, "line numbers");
3251                 break;
3253         case REQ_TOGGLE_DATE:
3254                 toggle_view_option(&opt_date, "date display");
3255                 break;
3257         case REQ_TOGGLE_AUTHOR:
3258                 toggle_view_option(&opt_author, "author display");
3259                 break;
3261         case REQ_TOGGLE_REV_GRAPH:
3262                 toggle_view_option(&opt_rev_graph, "revision graph display");
3263                 break;
3265         case REQ_TOGGLE_REFS:
3266                 toggle_view_option(&opt_show_refs, "reference display");
3267                 break;
3269         case REQ_SEARCH:
3270         case REQ_SEARCH_BACK:
3271                 search_view(view, request);
3272                 break;
3274         case REQ_FIND_NEXT:
3275         case REQ_FIND_PREV:
3276                 find_next(view, request);
3277                 break;
3279         case REQ_STOP_LOADING:
3280                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3281                         view = &views[i];
3282                         if (view->pipe)
3283                                 report("Stopped loading the %s view", view->name),
3284                         end_update(view, TRUE);
3285                 }
3286                 break;
3288         case REQ_SHOW_VERSION:
3289                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3290                 return TRUE;
3292         case REQ_SCREEN_REDRAW:
3293                 redraw_display(TRUE);
3294                 break;
3296         case REQ_EDIT:
3297                 report("Nothing to edit");
3298                 break;
3300         case REQ_ENTER:
3301                 report("Nothing to enter");
3302                 break;
3304         case REQ_VIEW_CLOSE:
3305                 /* XXX: Mark closed views by letting view->parent point to the
3306                  * view itself. Parents to closed view should never be
3307                  * followed. */
3308                 if (view->parent &&
3309                     view->parent->parent != view->parent) {
3310                         maximize_view(view->parent);
3311                         view->parent = view;
3312                         break;
3313                 }
3314                 /* Fall-through */
3315         case REQ_QUIT:
3316                 return FALSE;
3318         default:
3319                 report("Unknown key, press 'h' for help");
3320                 return TRUE;
3321         }
3323         return TRUE;
3327 /*
3328  * View backend utilities
3329  */
3331 /* Small author cache to reduce memory consumption. It uses binary
3332  * search to lookup or find place to position new entries. No entries
3333  * are ever freed. */
3334 static const char *
3335 get_author(const char *name)
3337         static const char **authors;
3338         static size_t authors_alloc;
3339         static size_t authors_size;
3340         const char **tmp;
3341         int from = 0, to = authors_size - 1;
3343         while (from <= to) {
3344                 size_t pos = (to + from) / 2;
3345                 int cmp = strcmp(name, authors[pos]);
3347                 if (!cmp)
3348                         return authors[pos];
3350                 if (cmp < 0)
3351                         to = pos - 1;
3352                 else
3353                         from = pos + 1;
3354         }
3356         tmp = realloc_items(authors, &authors_alloc, authors_size + 1, sizeof(*authors));
3357         if (!tmp)
3358                 return NULL;
3359         name = strdup(name);
3360         if (!name)
3361                 return NULL;
3363         authors = tmp;
3364         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3365         authors[from] = name;
3366         authors_size++;
3368         return name;
3371 static void
3372 parse_timezone(time_t *time, const char *zone)
3374         long tz;
3376         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3377         tz += ('0' - zone[2]) * 60 * 60;
3378         tz += ('0' - zone[3]) * 60;
3379         tz += ('0' - zone[4]);
3381         if (zone[0] == '-')
3382                 tz = -tz;
3384         *time -= tz;
3387 /* Parse author lines where the name may be empty:
3388  *      author  <email@address.tld> 1138474660 +0100
3389  */
3390 static void
3391 parse_author_line(char *ident, const char **author, struct tm *tm)
3393         char *nameend = strchr(ident, '<');
3394         char *emailend = strchr(ident, '>');
3396         if (nameend && emailend)
3397                 *nameend = *emailend = 0;
3398         ident = chomp_string(ident);
3399         if (!*ident) {
3400                 if (nameend)
3401                         ident = chomp_string(nameend + 1);
3402                 if (!*ident)
3403                         ident = "Unknown";
3404         }
3406         *author = get_author(ident);
3408         /* Parse epoch and timezone */
3409         if (emailend && emailend[1] == ' ') {
3410                 char *secs = emailend + 2;
3411                 char *zone = strchr(secs, ' ');
3412                 time_t time = (time_t) atol(secs);
3414                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3415                         parse_timezone(&time, zone + 1);
3417                 gmtime_r(&time, tm);
3418         }
3421 static enum input_status
3422 select_commit_parent_handler(void *data, char *buf, int c)
3424         size_t parents = *(size_t *) data;
3425         int parent = 0;
3427         if (!isdigit(c))
3428                 return INPUT_SKIP;
3430         if (*buf)
3431                 parent = atoi(buf) * 10;
3432         parent += c - '0';
3434         if (parent > parents)
3435                 return INPUT_SKIP;
3436         return INPUT_OK;
3439 static bool
3440 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3442         char buf[SIZEOF_STR * 4];
3443         const char *revlist_argv[] = {
3444                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3445         };
3446         int parents;
3448         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3449             !*chomp_string(buf) ||
3450             (parents = (strlen(buf) / 40) - 1) < 0) {
3451                 report("Failed to get parent information");
3452                 return FALSE;
3454         } else if (parents == 0) {
3455                 if (path)
3456                         report("Path '%s' does not exist in the parent", path);
3457                 else
3458                         report("The selected commit has no parents");
3459                 return FALSE;
3460         }
3462         if (parents > 1) {
3463                 char prompt[SIZEOF_STR];
3464                 char *result;
3466                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3467                         return FALSE;
3468                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3469                 if (!result)
3470                         return FALSE;
3471                 parents = atoi(result);
3472         }
3474         string_copy_rev(rev, &buf[41 * parents]);
3475         return TRUE;
3478 /*
3479  * Pager backend
3480  */
3482 static bool
3483 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3485         char text[SIZEOF_STR];
3487         if (opt_line_number && draw_lineno(view, lineno))
3488                 return TRUE;
3490         string_expand(text, sizeof(text), line->data, opt_tab_size);
3491         draw_text(view, line->type, text, TRUE);
3492         return TRUE;
3495 static bool
3496 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3498         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3499         char refbuf[SIZEOF_STR];
3500         char *ref = NULL;
3502         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3503                 ref = chomp_string(refbuf);
3505         if (!ref || !*ref)
3506                 return TRUE;
3508         /* This is the only fatal call, since it can "corrupt" the buffer. */
3509         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3510                 return FALSE;
3512         return TRUE;
3515 static void
3516 add_pager_refs(struct view *view, struct line *line)
3518         char buf[SIZEOF_STR];
3519         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3520         struct ref **refs;
3521         size_t bufpos = 0, refpos = 0;
3522         const char *sep = "Refs: ";
3523         bool is_tag = FALSE;
3525         assert(line->type == LINE_COMMIT);
3527         refs = get_refs(commit_id);
3528         if (!refs) {
3529                 if (view == VIEW(REQ_VIEW_DIFF))
3530                         goto try_add_describe_ref;
3531                 return;
3532         }
3534         do {
3535                 struct ref *ref = refs[refpos];
3536                 const char *fmt = ref->tag    ? "%s[%s]" :
3537                                   ref->remote ? "%s<%s>" : "%s%s";
3539                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3540                         return;
3541                 sep = ", ";
3542                 if (ref->tag)
3543                         is_tag = TRUE;
3544         } while (refs[refpos++]->next);
3546         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3547 try_add_describe_ref:
3548                 /* Add <tag>-g<commit_id> "fake" reference. */
3549                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3550                         return;
3551         }
3553         if (bufpos == 0)
3554                 return;
3556         add_line_text(view, buf, LINE_PP_REFS);
3559 static bool
3560 pager_read(struct view *view, char *data)
3562         struct line *line;
3564         if (!data)
3565                 return TRUE;
3567         line = add_line_text(view, data, get_line_type(data));
3568         if (!line)
3569                 return FALSE;
3571         if (line->type == LINE_COMMIT &&
3572             (view == VIEW(REQ_VIEW_DIFF) ||
3573              view == VIEW(REQ_VIEW_LOG)))
3574                 add_pager_refs(view, line);
3576         return TRUE;
3579 static enum request
3580 pager_request(struct view *view, enum request request, struct line *line)
3582         int split = 0;
3584         if (request != REQ_ENTER)
3585                 return request;
3587         if (line->type == LINE_COMMIT &&
3588            (view == VIEW(REQ_VIEW_LOG) ||
3589             view == VIEW(REQ_VIEW_PAGER))) {
3590                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3591                 split = 1;
3592         }
3594         /* Always scroll the view even if it was split. That way
3595          * you can use Enter to scroll through the log view and
3596          * split open each commit diff. */
3597         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3599         /* FIXME: A minor workaround. Scrolling the view will call report("")
3600          * but if we are scrolling a non-current view this won't properly
3601          * update the view title. */
3602         if (split)
3603                 update_view_title(view);
3605         return REQ_NONE;
3608 static bool
3609 pager_grep(struct view *view, struct line *line)
3611         regmatch_t pmatch;
3612         char *text = line->data;
3614         if (!*text)
3615                 return FALSE;
3617         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3618                 return FALSE;
3620         return TRUE;
3623 static void
3624 pager_select(struct view *view, struct line *line)
3626         if (line->type == LINE_COMMIT) {
3627                 char *text = (char *)line->data + STRING_SIZE("commit ");
3629                 if (view != VIEW(REQ_VIEW_PAGER))
3630                         string_copy_rev(view->ref, text);
3631                 string_copy_rev(ref_commit, text);
3632         }
3635 static struct view_ops pager_ops = {
3636         "line",
3637         NULL,
3638         NULL,
3639         pager_read,
3640         pager_draw,
3641         pager_request,
3642         pager_grep,
3643         pager_select,
3644 };
3646 static const char *log_argv[SIZEOF_ARG] = {
3647         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3648 };
3650 static enum request
3651 log_request(struct view *view, enum request request, struct line *line)
3653         switch (request) {
3654         case REQ_REFRESH:
3655                 load_refs();
3656                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3657                 return REQ_NONE;
3658         default:
3659                 return pager_request(view, request, line);
3660         }
3663 static struct view_ops log_ops = {
3664         "line",
3665         log_argv,
3666         NULL,
3667         pager_read,
3668         pager_draw,
3669         log_request,
3670         pager_grep,
3671         pager_select,
3672 };
3674 static const char *diff_argv[SIZEOF_ARG] = {
3675         "git", "show", "--pretty=fuller", "--no-color", "--root",
3676                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3677 };
3679 static struct view_ops diff_ops = {
3680         "line",
3681         diff_argv,
3682         NULL,
3683         pager_read,
3684         pager_draw,
3685         pager_request,
3686         pager_grep,
3687         pager_select,
3688 };
3690 /*
3691  * Help backend
3692  */
3694 static bool
3695 help_open(struct view *view)
3697         char buf[SIZEOF_STR];
3698         size_t bufpos;
3699         int i;
3701         if (view->lines > 0)
3702                 return TRUE;
3704         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3706         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3707                 const char *key;
3709                 if (req_info[i].request == REQ_NONE)
3710                         continue;
3712                 if (!req_info[i].request) {
3713                         add_line_text(view, "", LINE_DEFAULT);
3714                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3715                         continue;
3716                 }
3718                 key = get_key(req_info[i].request);
3719                 if (!*key)
3720                         key = "(no key defined)";
3722                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3723                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3724                         if (buf[bufpos] == '_')
3725                                 buf[bufpos] = '-';
3726                 }
3728                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3729                                 key, buf, req_info[i].help);
3730         }
3732         if (run_requests) {
3733                 add_line_text(view, "", LINE_DEFAULT);
3734                 add_line_text(view, "External commands:", LINE_DEFAULT);
3735         }
3737         for (i = 0; i < run_requests; i++) {
3738                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3739                 const char *key;
3740                 int argc;
3742                 if (!req)
3743                         continue;
3745                 key = get_key_name(req->key);
3746                 if (!*key)
3747                         key = "(no key defined)";
3749                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3750                         if (!string_format_from(buf, &bufpos, "%s%s",
3751                                                 argc ? " " : "", req->argv[argc]))
3752                                 return REQ_NONE;
3754                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3755                                 keymap_table[req->keymap].name, key, buf);
3756         }
3758         return TRUE;
3761 static struct view_ops help_ops = {
3762         "line",
3763         NULL,
3764         help_open,
3765         NULL,
3766         pager_draw,
3767         pager_request,
3768         pager_grep,
3769         pager_select,
3770 };
3773 /*
3774  * Tree backend
3775  */
3777 struct tree_stack_entry {
3778         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3779         unsigned long lineno;           /* Line number to restore */
3780         char *name;                     /* Position of name in opt_path */
3781 };
3783 /* The top of the path stack. */
3784 static struct tree_stack_entry *tree_stack = NULL;
3785 unsigned long tree_lineno = 0;
3787 static void
3788 pop_tree_stack_entry(void)
3790         struct tree_stack_entry *entry = tree_stack;
3792         tree_lineno = entry->lineno;
3793         entry->name[0] = 0;
3794         tree_stack = entry->prev;
3795         free(entry);
3798 static void
3799 push_tree_stack_entry(const char *name, unsigned long lineno)
3801         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3802         size_t pathlen = strlen(opt_path);
3804         if (!entry)
3805                 return;
3807         entry->prev = tree_stack;
3808         entry->name = opt_path + pathlen;
3809         tree_stack = entry;
3811         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3812                 pop_tree_stack_entry();
3813                 return;
3814         }
3816         /* Move the current line to the first tree entry. */
3817         tree_lineno = 1;
3818         entry->lineno = lineno;
3821 /* Parse output from git-ls-tree(1):
3822  *
3823  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3824  */
3826 #define SIZEOF_TREE_ATTR \
3827         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3829 #define SIZEOF_TREE_MODE \
3830         STRING_SIZE("100644 ")
3832 #define TREE_ID_OFFSET \
3833         STRING_SIZE("100644 blob ")
3835 struct tree_entry {
3836         char id[SIZEOF_REV];
3837         mode_t mode;
3838         struct tm time;                 /* Date from the author ident. */
3839         const char *author;             /* Author of the commit. */
3840         char name[1];
3841 };
3843 static const char *
3844 tree_path(struct line *line)
3846         return ((struct tree_entry *) line->data)->name;
3850 static int
3851 tree_compare_entry(struct line *line1, struct line *line2)
3853         if (line1->type != line2->type)
3854                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3855         return strcmp(tree_path(line1), tree_path(line2));
3858 static struct line *
3859 tree_entry(struct view *view, enum line_type type, const char *path,
3860            const char *mode, const char *id)
3862         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3863         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3865         if (!entry || !line) {
3866                 free(entry);
3867                 return NULL;
3868         }
3870         strncpy(entry->name, path, strlen(path));
3871         if (mode)
3872                 entry->mode = strtoul(mode, NULL, 8);
3873         if (id)
3874                 string_copy_rev(entry->id, id);
3876         return line;
3879 static bool
3880 tree_read_date(struct view *view, char *text, bool *read_date)
3882         static const char *author_name;
3883         static struct tm author_time;
3885         if (!text && *read_date) {
3886                 *read_date = FALSE;
3887                 return TRUE;
3889         } else if (!text) {
3890                 char *path = *opt_path ? opt_path : ".";
3891                 /* Find next entry to process */
3892                 const char *log_file[] = {
3893                         "git", "log", "--no-color", "--pretty=raw",
3894                                 "--cc", "--raw", view->id, "--", path, NULL
3895                 };
3896                 struct io io = {};
3898                 if (!view->lines) {
3899                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3900                         report("Tree is empty");
3901                         return TRUE;
3902                 }
3904                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3905                         report("Failed to load tree data");
3906                         return TRUE;
3907                 }
3909                 done_io(view->pipe);
3910                 view->io = io;
3911                 *read_date = TRUE;
3912                 return FALSE;
3914         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3915                 parse_author_line(text + STRING_SIZE("author "),
3916                                   &author_name, &author_time);
3918         } else if (*text == ':') {
3919                 char *pos;
3920                 size_t annotated = 1;
3921                 size_t i;
3923                 pos = strchr(text, '\t');
3924                 if (!pos)
3925                         return TRUE;
3926                 text = pos + 1;
3927                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3928                         text += strlen(opt_prefix);
3929                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3930                         text += strlen(opt_path);
3931                 pos = strchr(text, '/');
3932                 if (pos)
3933                         *pos = 0;
3935                 for (i = 1; i < view->lines; i++) {
3936                         struct line *line = &view->line[i];
3937                         struct tree_entry *entry = line->data;
3939                         annotated += !!entry->author;
3940                         if (entry->author || strcmp(entry->name, text))
3941                                 continue;
3943                         entry->author = author_name;
3944                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3945                         line->dirty = 1;
3946                         break;
3947                 }
3949                 if (annotated == view->lines)
3950                         kill_io(view->pipe);
3951         }
3952         return TRUE;
3955 static bool
3956 tree_read(struct view *view, char *text)
3958         static bool read_date = FALSE;
3959         struct tree_entry *data;
3960         struct line *entry, *line;
3961         enum line_type type;
3962         size_t textlen = text ? strlen(text) : 0;
3963         char *path = text + SIZEOF_TREE_ATTR;
3965         if (read_date || !text)
3966                 return tree_read_date(view, text, &read_date);
3968         if (textlen <= SIZEOF_TREE_ATTR)
3969                 return FALSE;
3970         if (view->lines == 0 &&
3971             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3972                 return FALSE;
3974         /* Strip the path part ... */
3975         if (*opt_path) {
3976                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3977                 size_t striplen = strlen(opt_path);
3979                 if (pathlen > striplen)
3980                         memmove(path, path + striplen,
3981                                 pathlen - striplen + 1);
3983                 /* Insert "link" to parent directory. */
3984                 if (view->lines == 1 &&
3985                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3986                         return FALSE;
3987         }
3989         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3990         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3991         if (!entry)
3992                 return FALSE;
3993         data = entry->data;
3995         /* Skip "Directory ..." and ".." line. */
3996         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3997                 if (tree_compare_entry(line, entry) <= 0)
3998                         continue;
4000                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4002                 line->data = data;
4003                 line->type = type;
4004                 for (; line <= entry; line++)
4005                         line->dirty = line->cleareol = 1;
4006                 return TRUE;
4007         }
4009         if (tree_lineno > view->lineno) {
4010                 view->lineno = tree_lineno;
4011                 tree_lineno = 0;
4012         }
4014         return TRUE;
4017 static bool
4018 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4020         struct tree_entry *entry = line->data;
4022         if (line->type == LINE_TREE_HEAD) {
4023                 if (draw_text(view, line->type, "Directory path /", TRUE))
4024                         return TRUE;
4025         } else {
4026                 if (draw_mode(view, entry->mode))
4027                         return TRUE;
4029                 if (opt_author && draw_author(view, entry->author))
4030                         return TRUE;
4032                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4033                         return TRUE;
4034         }
4035         if (draw_text(view, line->type, entry->name, TRUE))
4036                 return TRUE;
4037         return TRUE;
4040 static void
4041 open_blob_editor()
4043         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4044         int fd = mkstemp(file);
4046         if (fd == -1)
4047                 report("Failed to create temporary file");
4048         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4049                 report("Failed to save blob data to file");
4050         else
4051                 open_editor(FALSE, file);
4052         if (fd != -1)
4053                 unlink(file);
4056 static enum request
4057 tree_request(struct view *view, enum request request, struct line *line)
4059         enum open_flags flags;
4061         switch (request) {
4062         case REQ_VIEW_BLAME:
4063                 if (line->type != LINE_TREE_FILE) {
4064                         report("Blame only supported for files");
4065                         return REQ_NONE;
4066                 }
4068                 string_copy(opt_ref, view->vid);
4069                 return request;
4071         case REQ_EDIT:
4072                 if (line->type != LINE_TREE_FILE) {
4073                         report("Edit only supported for files");
4074                 } else if (!is_head_commit(view->vid)) {
4075                         open_blob_editor();
4076                 } else {
4077                         open_editor(TRUE, opt_file);
4078                 }
4079                 return REQ_NONE;
4081         case REQ_PARENT:
4082                 if (!*opt_path) {
4083                         /* quit view if at top of tree */
4084                         return REQ_VIEW_CLOSE;
4085                 }
4086                 /* fake 'cd  ..' */
4087                 line = &view->line[1];
4088                 break;
4090         case REQ_ENTER:
4091                 break;
4093         default:
4094                 return request;
4095         }
4097         /* Cleanup the stack if the tree view is at a different tree. */
4098         while (!*opt_path && tree_stack)
4099                 pop_tree_stack_entry();
4101         switch (line->type) {
4102         case LINE_TREE_DIR:
4103                 /* Depending on whether it is a subdirectory or parent link
4104                  * mangle the path buffer. */
4105                 if (line == &view->line[1] && *opt_path) {
4106                         pop_tree_stack_entry();
4108                 } else {
4109                         const char *basename = tree_path(line);
4111                         push_tree_stack_entry(basename, view->lineno);
4112                 }
4114                 /* Trees and subtrees share the same ID, so they are not not
4115                  * unique like blobs. */
4116                 flags = OPEN_RELOAD;
4117                 request = REQ_VIEW_TREE;
4118                 break;
4120         case LINE_TREE_FILE:
4121                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4122                 request = REQ_VIEW_BLOB;
4123                 break;
4125         default:
4126                 return REQ_NONE;
4127         }
4129         open_view(view, request, flags);
4130         if (request == REQ_VIEW_TREE)
4131                 view->lineno = tree_lineno;
4133         return REQ_NONE;
4136 static void
4137 tree_select(struct view *view, struct line *line)
4139         struct tree_entry *entry = line->data;
4141         if (line->type == LINE_TREE_FILE) {
4142                 string_copy_rev(ref_blob, entry->id);
4143                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4145         } else if (line->type != LINE_TREE_DIR) {
4146                 return;
4147         }
4149         string_copy_rev(view->ref, entry->id);
4152 static const char *tree_argv[SIZEOF_ARG] = {
4153         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4154 };
4156 static struct view_ops tree_ops = {
4157         "file",
4158         tree_argv,
4159         NULL,
4160         tree_read,
4161         tree_draw,
4162         tree_request,
4163         pager_grep,
4164         tree_select,
4165 };
4167 static bool
4168 blob_read(struct view *view, char *line)
4170         if (!line)
4171                 return TRUE;
4172         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4175 static enum request
4176 blob_request(struct view *view, enum request request, struct line *line)
4178         switch (request) {
4179         case REQ_EDIT:
4180                 open_blob_editor();
4181                 return REQ_NONE;
4182         default:
4183                 return pager_request(view, request, line);
4184         }
4187 static const char *blob_argv[SIZEOF_ARG] = {
4188         "git", "cat-file", "blob", "%(blob)", NULL
4189 };
4191 static struct view_ops blob_ops = {
4192         "line",
4193         blob_argv,
4194         NULL,
4195         blob_read,
4196         pager_draw,
4197         blob_request,
4198         pager_grep,
4199         pager_select,
4200 };
4202 /*
4203  * Blame backend
4204  *
4205  * Loading the blame view is a two phase job:
4206  *
4207  *  1. File content is read either using opt_file from the
4208  *     filesystem or using git-cat-file.
4209  *  2. Then blame information is incrementally added by
4210  *     reading output from git-blame.
4211  */
4213 static const char *blame_head_argv[] = {
4214         "git", "blame", "--incremental", "--", "%(file)", NULL
4215 };
4217 static const char *blame_ref_argv[] = {
4218         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4219 };
4221 static const char *blame_cat_file_argv[] = {
4222         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4223 };
4225 struct blame_commit {
4226         char id[SIZEOF_REV];            /* SHA1 ID. */
4227         char title[128];                /* First line of the commit message. */
4228         const char *author;             /* Author of the commit. */
4229         struct tm time;                 /* Date from the author ident. */
4230         char filename[128];             /* Name of file. */
4231         bool has_previous;              /* Was a "previous" line detected. */
4232 };
4234 struct blame {
4235         struct blame_commit *commit;
4236         unsigned long lineno;
4237         char text[1];
4238 };
4240 static bool
4241 blame_open(struct view *view)
4243         if (*opt_ref || !io_open(&view->io, opt_file)) {
4244                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4245                         return FALSE;
4246         }
4248         setup_update(view, opt_file);
4249         string_format(view->ref, "%s ...", opt_file);
4251         return TRUE;
4254 static struct blame_commit *
4255 get_blame_commit(struct view *view, const char *id)
4257         size_t i;
4259         for (i = 0; i < view->lines; i++) {
4260                 struct blame *blame = view->line[i].data;
4262                 if (!blame->commit)
4263                         continue;
4265                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4266                         return blame->commit;
4267         }
4269         {
4270                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4272                 if (commit)
4273                         string_ncopy(commit->id, id, SIZEOF_REV);
4274                 return commit;
4275         }
4278 static bool
4279 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4281         const char *pos = *posref;
4283         *posref = NULL;
4284         pos = strchr(pos + 1, ' ');
4285         if (!pos || !isdigit(pos[1]))
4286                 return FALSE;
4287         *number = atoi(pos + 1);
4288         if (*number < min || *number > max)
4289                 return FALSE;
4291         *posref = pos;
4292         return TRUE;
4295 static struct blame_commit *
4296 parse_blame_commit(struct view *view, const char *text, int *blamed)
4298         struct blame_commit *commit;
4299         struct blame *blame;
4300         const char *pos = text + SIZEOF_REV - 2;
4301         size_t orig_lineno = 0;
4302         size_t lineno;
4303         size_t group;
4305         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4306                 return NULL;
4308         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4309             !parse_number(&pos, &lineno, 1, view->lines) ||
4310             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4311                 return NULL;
4313         commit = get_blame_commit(view, text);
4314         if (!commit)
4315                 return NULL;
4317         *blamed += group;
4318         while (group--) {
4319                 struct line *line = &view->line[lineno + group - 1];
4321                 blame = line->data;
4322                 blame->commit = commit;
4323                 blame->lineno = orig_lineno + group - 1;
4324                 line->dirty = 1;
4325         }
4327         return commit;
4330 static bool
4331 blame_read_file(struct view *view, const char *line, bool *read_file)
4333         if (!line) {
4334                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4335                 struct io io = {};
4337                 if (view->lines == 0 && !view->parent)
4338                         die("No blame exist for %s", view->vid);
4340                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4341                         report("Failed to load blame data");
4342                         return TRUE;
4343                 }
4345                 done_io(view->pipe);
4346                 view->io = io;
4347                 *read_file = FALSE;
4348                 return FALSE;
4350         } else {
4351                 size_t linelen = strlen(line);
4352                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4354                 if (!blame)
4355                         return FALSE;
4357                 blame->commit = NULL;
4358                 strncpy(blame->text, line, linelen);
4359                 blame->text[linelen] = 0;
4360                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4361         }
4364 static bool
4365 match_blame_header(const char *name, char **line)
4367         size_t namelen = strlen(name);
4368         bool matched = !strncmp(name, *line, namelen);
4370         if (matched)
4371                 *line += namelen;
4373         return matched;
4376 static bool
4377 blame_read(struct view *view, char *line)
4379         static struct blame_commit *commit = NULL;
4380         static int blamed = 0;
4381         static time_t author_time;
4382         static bool read_file = TRUE;
4384         if (read_file)
4385                 return blame_read_file(view, line, &read_file);
4387         if (!line) {
4388                 /* Reset all! */
4389                 commit = NULL;
4390                 blamed = 0;
4391                 read_file = TRUE;
4392                 string_format(view->ref, "%s", view->vid);
4393                 if (view_is_displayed(view)) {
4394                         update_view_title(view);
4395                         redraw_view_from(view, 0);
4396                 }
4397                 return TRUE;
4398         }
4400         if (!commit) {
4401                 commit = parse_blame_commit(view, line, &blamed);
4402                 string_format(view->ref, "%s %2d%%", view->vid,
4403                               view->lines ? blamed * 100 / view->lines : 0);
4405         } else if (match_blame_header("author ", &line)) {
4406                 commit->author = get_author(line);
4408         } else if (match_blame_header("author-time ", &line)) {
4409                 author_time = (time_t) atol(line);
4411         } else if (match_blame_header("author-tz ", &line)) {
4412                 parse_timezone(&author_time, line);
4413                 gmtime_r(&author_time, &commit->time);
4415         } else if (match_blame_header("summary ", &line)) {
4416                 string_ncopy(commit->title, line, strlen(line));
4418         } else if (match_blame_header("previous ", &line)) {
4419                 commit->has_previous = TRUE;
4421         } else if (match_blame_header("filename ", &line)) {
4422                 string_ncopy(commit->filename, line, strlen(line));
4423                 commit = NULL;
4424         }
4426         return TRUE;
4429 static bool
4430 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4432         struct blame *blame = line->data;
4433         struct tm *time = NULL;
4434         const char *id = NULL, *author = NULL;
4435         char text[SIZEOF_STR];
4437         if (blame->commit && *blame->commit->filename) {
4438                 id = blame->commit->id;
4439                 author = blame->commit->author;
4440                 time = &blame->commit->time;
4441         }
4443         if (opt_date && draw_date(view, time))
4444                 return TRUE;
4446         if (opt_author && draw_author(view, author))
4447                 return TRUE;
4449         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4450                 return TRUE;
4452         if (draw_lineno(view, lineno))
4453                 return TRUE;
4455         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4456         draw_text(view, LINE_DEFAULT, text, TRUE);
4457         return TRUE;
4460 static bool
4461 check_blame_commit(struct blame *blame, bool check_null_id)
4463         if (!blame->commit)
4464                 report("Commit data not loaded yet");
4465         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4466                 report("No commit exist for the selected line");
4467         else
4468                 return TRUE;
4469         return FALSE;
4472 static void
4473 setup_blame_parent_line(struct view *view, struct blame *blame)
4475         const char *diff_tree_argv[] = {
4476                 "git", "diff-tree", "-U0", blame->commit->id,
4477                         "--", blame->commit->filename, NULL
4478         };
4479         struct io io = {};
4480         int parent_lineno = -1;
4481         int blamed_lineno = -1;
4482         char *line;
4484         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4485                 return;
4487         while ((line = io_get(&io, '\n', TRUE))) {
4488                 if (*line == '@') {
4489                         char *pos = strchr(line, '+');
4491                         parent_lineno = atoi(line + 4);
4492                         if (pos)
4493                                 blamed_lineno = atoi(pos + 1);
4495                 } else if (*line == '+' && parent_lineno != -1) {
4496                         if (blame->lineno == blamed_lineno - 1 &&
4497                             !strcmp(blame->text, line + 1)) {
4498                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4499                                 break;
4500                         }
4501                         blamed_lineno++;
4502                 }
4503         }
4505         done_io(&io);
4508 static enum request
4509 blame_request(struct view *view, enum request request, struct line *line)
4511         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4512         struct blame *blame = line->data;
4514         switch (request) {
4515         case REQ_VIEW_BLAME:
4516                 if (check_blame_commit(blame, TRUE)) {
4517                         string_copy(opt_ref, blame->commit->id);
4518                         string_copy(opt_file, blame->commit->filename);
4519                         if (blame->lineno)
4520                                 view->lineno = blame->lineno;
4521                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4522                 }
4523                 break;
4525         case REQ_PARENT:
4526                 if (check_blame_commit(blame, TRUE) &&
4527                     select_commit_parent(blame->commit->id, opt_ref,
4528                                          blame->commit->filename)) {
4529                         string_copy(opt_file, blame->commit->filename);
4530                         setup_blame_parent_line(view, blame);
4531                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4532                 }
4533                 break;
4535         case REQ_ENTER:
4536                 if (!check_blame_commit(blame, FALSE))
4537                         break;
4539                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4540                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4541                         break;
4543                 if (!strcmp(blame->commit->id, NULL_ID)) {
4544                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4545                         const char *diff_index_argv[] = {
4546                                 "git", "diff-index", "--root", "--patch-with-stat",
4547                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4548                         };
4550                         if (!blame->commit->has_previous) {
4551                                 diff_index_argv[1] = "diff";
4552                                 diff_index_argv[2] = "--no-color";
4553                                 diff_index_argv[6] = "--";
4554                                 diff_index_argv[7] = "/dev/null";
4555                         }
4557                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4558                                 report("Failed to allocate diff command");
4559                                 break;
4560                         }
4561                         flags |= OPEN_PREPARED;
4562                 }
4564                 open_view(view, REQ_VIEW_DIFF, flags);
4565                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4566                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4567                 break;
4569         default:
4570                 return request;
4571         }
4573         return REQ_NONE;
4576 static bool
4577 blame_grep(struct view *view, struct line *line)
4579         struct blame *blame = line->data;
4580         struct blame_commit *commit = blame->commit;
4581         regmatch_t pmatch;
4583 #define MATCH(text, on)                                                 \
4584         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4586         if (commit) {
4587                 char buf[DATE_COLS + 1];
4589                 if (MATCH(commit->title, 1) ||
4590                     MATCH(commit->author, opt_author) ||
4591                     MATCH(commit->id, opt_date))
4592                         return TRUE;
4594                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4595                     MATCH(buf, 1))
4596                         return TRUE;
4597         }
4599         return MATCH(blame->text, 1);
4601 #undef MATCH
4604 static void
4605 blame_select(struct view *view, struct line *line)
4607         struct blame *blame = line->data;
4608         struct blame_commit *commit = blame->commit;
4610         if (!commit)
4611                 return;
4613         if (!strcmp(commit->id, NULL_ID))
4614                 string_ncopy(ref_commit, "HEAD", 4);
4615         else
4616                 string_copy_rev(ref_commit, commit->id);
4619 static struct view_ops blame_ops = {
4620         "line",
4621         NULL,
4622         blame_open,
4623         blame_read,
4624         blame_draw,
4625         blame_request,
4626         blame_grep,
4627         blame_select,
4628 };
4630 /*
4631  * Status backend
4632  */
4634 struct status {
4635         char status;
4636         struct {
4637                 mode_t mode;
4638                 char rev[SIZEOF_REV];
4639                 char name[SIZEOF_STR];
4640         } old;
4641         struct {
4642                 mode_t mode;
4643                 char rev[SIZEOF_REV];
4644                 char name[SIZEOF_STR];
4645         } new;
4646 };
4648 static char status_onbranch[SIZEOF_STR];
4649 static struct status stage_status;
4650 static enum line_type stage_line_type;
4651 static size_t stage_chunks;
4652 static int *stage_chunk;
4654 /* This should work even for the "On branch" line. */
4655 static inline bool
4656 status_has_none(struct view *view, struct line *line)
4658         return line < view->line + view->lines && !line[1].data;
4661 /* Get fields from the diff line:
4662  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4663  */
4664 static inline bool
4665 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4667         const char *old_mode = buf +  1;
4668         const char *new_mode = buf +  8;
4669         const char *old_rev  = buf + 15;
4670         const char *new_rev  = buf + 56;
4671         const char *status   = buf + 97;
4673         if (bufsize < 98 ||
4674             old_mode[-1] != ':' ||
4675             new_mode[-1] != ' ' ||
4676             old_rev[-1]  != ' ' ||
4677             new_rev[-1]  != ' ' ||
4678             status[-1]   != ' ')
4679                 return FALSE;
4681         file->status = *status;
4683         string_copy_rev(file->old.rev, old_rev);
4684         string_copy_rev(file->new.rev, new_rev);
4686         file->old.mode = strtoul(old_mode, NULL, 8);
4687         file->new.mode = strtoul(new_mode, NULL, 8);
4689         file->old.name[0] = file->new.name[0] = 0;
4691         return TRUE;
4694 static bool
4695 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4697         struct status *unmerged = NULL;
4698         char *buf;
4699         struct io io = {};
4701         if (!run_io(&io, argv, NULL, IO_RD))
4702                 return FALSE;
4704         add_line_data(view, NULL, type);
4706         while ((buf = io_get(&io, 0, TRUE))) {
4707                 struct status *file = unmerged;
4709                 if (!file) {
4710                         file = calloc(1, sizeof(*file));
4711                         if (!file || !add_line_data(view, file, type))
4712                                 goto error_out;
4713                 }
4715                 /* Parse diff info part. */
4716                 if (status) {
4717                         file->status = status;
4718                         if (status == 'A')
4719                                 string_copy(file->old.rev, NULL_ID);
4721                 } else if (!file->status || file == unmerged) {
4722                         if (!status_get_diff(file, buf, strlen(buf)))
4723                                 goto error_out;
4725                         buf = io_get(&io, 0, TRUE);
4726                         if (!buf)
4727                                 break;
4729                         /* Collapse all modified entries that follow an
4730                          * associated unmerged entry. */
4731                         if (unmerged == file) {
4732                                 unmerged->status = 'U';
4733                                 unmerged = NULL;
4734                         } else if (file->status == 'U') {
4735                                 unmerged = file;
4736                         }
4737                 }
4739                 /* Grab the old name for rename/copy. */
4740                 if (!*file->old.name &&
4741                     (file->status == 'R' || file->status == 'C')) {
4742                         string_ncopy(file->old.name, buf, strlen(buf));
4744                         buf = io_get(&io, 0, TRUE);
4745                         if (!buf)
4746                                 break;
4747                 }
4749                 /* git-ls-files just delivers a NUL separated list of
4750                  * file names similar to the second half of the
4751                  * git-diff-* output. */
4752                 string_ncopy(file->new.name, buf, strlen(buf));
4753                 if (!*file->old.name)
4754                         string_copy(file->old.name, file->new.name);
4755                 file = NULL;
4756         }
4758         if (io_error(&io)) {
4759 error_out:
4760                 done_io(&io);
4761                 return FALSE;
4762         }
4764         if (!view->line[view->lines - 1].data)
4765                 add_line_data(view, NULL, LINE_STAT_NONE);
4767         done_io(&io);
4768         return TRUE;
4771 /* Don't show unmerged entries in the staged section. */
4772 static const char *status_diff_index_argv[] = {
4773         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4774                              "--cached", "-M", "HEAD", NULL
4775 };
4777 static const char *status_diff_files_argv[] = {
4778         "git", "diff-files", "-z", NULL
4779 };
4781 static const char *status_list_other_argv[] = {
4782         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4783 };
4785 static const char *status_list_no_head_argv[] = {
4786         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4787 };
4789 static const char *update_index_argv[] = {
4790         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4791 };
4793 /* Restore the previous line number to stay in the context or select a
4794  * line with something that can be updated. */
4795 static void
4796 status_restore(struct view *view)
4798         if (view->p_lineno >= view->lines)
4799                 view->p_lineno = view->lines - 1;
4800         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4801                 view->p_lineno++;
4802         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4803                 view->p_lineno--;
4805         /* If the above fails, always skip the "On branch" line. */
4806         if (view->p_lineno < view->lines)
4807                 view->lineno = view->p_lineno;
4808         else
4809                 view->lineno = 1;
4811         if (view->lineno < view->offset)
4812                 view->offset = view->lineno;
4813         else if (view->offset + view->height <= view->lineno)
4814                 view->offset = view->lineno - view->height + 1;
4816         view->p_restore = FALSE;
4819 static void
4820 status_update_onbranch(void)
4822         static const char *paths[][2] = {
4823                 { "rebase-apply/rebasing",      "Rebasing" },
4824                 { "rebase-apply/applying",      "Applying mailbox" },
4825                 { "rebase-apply/",              "Rebasing mailbox" },
4826                 { "rebase-merge/interactive",   "Interactive rebase" },
4827                 { "rebase-merge/",              "Rebase merge" },
4828                 { "MERGE_HEAD",                 "Merging" },
4829                 { "BISECT_LOG",                 "Bisecting" },
4830                 { "HEAD",                       "On branch" },
4831         };
4832         char buf[SIZEOF_STR];
4833         struct stat stat;
4834         int i;
4836         if (is_initial_commit()) {
4837                 string_copy(status_onbranch, "Initial commit");
4838                 return;
4839         }
4841         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4842                 char *head = opt_head;
4844                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4845                     lstat(buf, &stat) < 0)
4846                         continue;
4848                 if (!*opt_head) {
4849                         struct io io = {};
4851                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4852                             io_open(&io, buf) &&
4853                             io_read_buf(&io, buf, sizeof(buf))) {
4854                                 head = chomp_string(buf);
4855                                 if (!prefixcmp(head, "refs/heads/"))
4856                                         head += STRING_SIZE("refs/heads/");
4857                         }
4858                 }
4860                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4861                         string_copy(status_onbranch, opt_head);
4862                 return;
4863         }
4865         string_copy(status_onbranch, "Not currently on any branch");
4868 /* First parse staged info using git-diff-index(1), then parse unstaged
4869  * info using git-diff-files(1), and finally untracked files using
4870  * git-ls-files(1). */
4871 static bool
4872 status_open(struct view *view)
4874         reset_view(view);
4876         add_line_data(view, NULL, LINE_STAT_HEAD);
4877         status_update_onbranch();
4879         run_io_bg(update_index_argv);
4881         if (is_initial_commit()) {
4882                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4883                         return FALSE;
4884         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4885                 return FALSE;
4886         }
4888         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4889             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4890                 return FALSE;
4892         /* Restore the exact position or use the specialized restore
4893          * mode? */
4894         if (!view->p_restore)
4895                 status_restore(view);
4896         return TRUE;
4899 static bool
4900 status_draw(struct view *view, struct line *line, unsigned int lineno)
4902         struct status *status = line->data;
4903         enum line_type type;
4904         const char *text;
4906         if (!status) {
4907                 switch (line->type) {
4908                 case LINE_STAT_STAGED:
4909                         type = LINE_STAT_SECTION;
4910                         text = "Changes to be committed:";
4911                         break;
4913                 case LINE_STAT_UNSTAGED:
4914                         type = LINE_STAT_SECTION;
4915                         text = "Changed but not updated:";
4916                         break;
4918                 case LINE_STAT_UNTRACKED:
4919                         type = LINE_STAT_SECTION;
4920                         text = "Untracked files:";
4921                         break;
4923                 case LINE_STAT_NONE:
4924                         type = LINE_DEFAULT;
4925                         text = "  (no files)";
4926                         break;
4928                 case LINE_STAT_HEAD:
4929                         type = LINE_STAT_HEAD;
4930                         text = status_onbranch;
4931                         break;
4933                 default:
4934                         return FALSE;
4935                 }
4936         } else {
4937                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4939                 buf[0] = status->status;
4940                 if (draw_text(view, line->type, buf, TRUE))
4941                         return TRUE;
4942                 type = LINE_DEFAULT;
4943                 text = status->new.name;
4944         }
4946         draw_text(view, type, text, TRUE);
4947         return TRUE;
4950 static enum request
4951 status_load_error(struct view *view, struct view *stage, const char *path)
4953         if (displayed_views() == 2 || display[current_view] != view)
4954                 maximize_view(view);
4955         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4956         return REQ_NONE;
4959 static enum request
4960 status_enter(struct view *view, struct line *line)
4962         struct status *status = line->data;
4963         const char *oldpath = status ? status->old.name : NULL;
4964         /* Diffs for unmerged entries are empty when passing the new
4965          * path, so leave it empty. */
4966         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4967         const char *info;
4968         enum open_flags split;
4969         struct view *stage = VIEW(REQ_VIEW_STAGE);
4971         if (line->type == LINE_STAT_NONE ||
4972             (!status && line[1].type == LINE_STAT_NONE)) {
4973                 report("No file to diff");
4974                 return REQ_NONE;
4975         }
4977         switch (line->type) {
4978         case LINE_STAT_STAGED:
4979                 if (is_initial_commit()) {
4980                         const char *no_head_diff_argv[] = {
4981                                 "git", "diff", "--no-color", "--patch-with-stat",
4982                                         "--", "/dev/null", newpath, NULL
4983                         };
4985                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4986                                 return status_load_error(view, stage, newpath);
4987                 } else {
4988                         const char *index_show_argv[] = {
4989                                 "git", "diff-index", "--root", "--patch-with-stat",
4990                                         "-C", "-M", "--cached", "HEAD", "--",
4991                                         oldpath, newpath, NULL
4992                         };
4994                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4995                                 return status_load_error(view, stage, newpath);
4996                 }
4998                 if (status)
4999                         info = "Staged changes to %s";
5000                 else
5001                         info = "Staged changes";
5002                 break;
5004         case LINE_STAT_UNSTAGED:
5005         {
5006                 const char *files_show_argv[] = {
5007                         "git", "diff-files", "--root", "--patch-with-stat",
5008                                 "-C", "-M", "--", oldpath, newpath, NULL
5009                 };
5011                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5012                         return status_load_error(view, stage, newpath);
5013                 if (status)
5014                         info = "Unstaged changes to %s";
5015                 else
5016                         info = "Unstaged changes";
5017                 break;
5018         }
5019         case LINE_STAT_UNTRACKED:
5020                 if (!newpath) {
5021                         report("No file to show");
5022                         return REQ_NONE;
5023                 }
5025                 if (!suffixcmp(status->new.name, -1, "/")) {
5026                         report("Cannot display a directory");
5027                         return REQ_NONE;
5028                 }
5030                 if (!prepare_update_file(stage, newpath))
5031                         return status_load_error(view, stage, newpath);
5032                 info = "Untracked file %s";
5033                 break;
5035         case LINE_STAT_HEAD:
5036                 return REQ_NONE;
5038         default:
5039                 die("line type %d not handled in switch", line->type);
5040         }
5042         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5043         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5044         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5045                 if (status) {
5046                         stage_status = *status;
5047                 } else {
5048                         memset(&stage_status, 0, sizeof(stage_status));
5049                 }
5051                 stage_line_type = line->type;
5052                 stage_chunks = 0;
5053                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5054         }
5056         return REQ_NONE;
5059 static bool
5060 status_exists(struct status *status, enum line_type type)
5062         struct view *view = VIEW(REQ_VIEW_STATUS);
5063         unsigned long lineno;
5065         for (lineno = 0; lineno < view->lines; lineno++) {
5066                 struct line *line = &view->line[lineno];
5067                 struct status *pos = line->data;
5069                 if (line->type != type)
5070                         continue;
5071                 if (!pos && (!status || !status->status) && line[1].data) {
5072                         select_view_line(view, lineno);
5073                         return TRUE;
5074                 }
5075                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5076                         select_view_line(view, lineno);
5077                         return TRUE;
5078                 }
5079         }
5081         return FALSE;
5085 static bool
5086 status_update_prepare(struct io *io, enum line_type type)
5088         const char *staged_argv[] = {
5089                 "git", "update-index", "-z", "--index-info", NULL
5090         };
5091         const char *others_argv[] = {
5092                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5093         };
5095         switch (type) {
5096         case LINE_STAT_STAGED:
5097                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5099         case LINE_STAT_UNSTAGED:
5100                 return run_io(io, others_argv, opt_cdup, IO_WR);
5102         case LINE_STAT_UNTRACKED:
5103                 return run_io(io, others_argv, NULL, IO_WR);
5105         default:
5106                 die("line type %d not handled in switch", type);
5107                 return FALSE;
5108         }
5111 static bool
5112 status_update_write(struct io *io, struct status *status, enum line_type type)
5114         char buf[SIZEOF_STR];
5115         size_t bufsize = 0;
5117         switch (type) {
5118         case LINE_STAT_STAGED:
5119                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5120                                         status->old.mode,
5121                                         status->old.rev,
5122                                         status->old.name, 0))
5123                         return FALSE;
5124                 break;
5126         case LINE_STAT_UNSTAGED:
5127         case LINE_STAT_UNTRACKED:
5128                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5129                         return FALSE;
5130                 break;
5132         default:
5133                 die("line type %d not handled in switch", type);
5134         }
5136         return io_write(io, buf, bufsize);
5139 static bool
5140 status_update_file(struct status *status, enum line_type type)
5142         struct io io = {};
5143         bool result;
5145         if (!status_update_prepare(&io, type))
5146                 return FALSE;
5148         result = status_update_write(&io, status, type);
5149         return done_io(&io) && result;
5152 static bool
5153 status_update_files(struct view *view, struct line *line)
5155         char buf[sizeof(view->ref)];
5156         struct io io = {};
5157         bool result = TRUE;
5158         struct line *pos = view->line + view->lines;
5159         int files = 0;
5160         int file, done;
5161         int cursor_y, cursor_x;
5163         if (!status_update_prepare(&io, line->type))
5164                 return FALSE;
5166         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5167                 files++;
5169         string_copy(buf, view->ref);
5170         getsyx(cursor_y, cursor_x);
5171         for (file = 0, done = 5; result && file < files; line++, file++) {
5172                 int almost_done = file * 100 / files;
5174                 if (almost_done > done) {
5175                         done = almost_done;
5176                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5177                                       file, files, done);
5178                         update_view_title(view);
5179                         setsyx(cursor_y, cursor_x);
5180                         doupdate();
5181                 }
5182                 result = status_update_write(&io, line->data, line->type);
5183         }
5184         string_copy(view->ref, buf);
5186         return done_io(&io) && result;
5189 static bool
5190 status_update(struct view *view)
5192         struct line *line = &view->line[view->lineno];
5194         assert(view->lines);
5196         if (!line->data) {
5197                 /* This should work even for the "On branch" line. */
5198                 if (line < view->line + view->lines && !line[1].data) {
5199                         report("Nothing to update");
5200                         return FALSE;
5201                 }
5203                 if (!status_update_files(view, line + 1)) {
5204                         report("Failed to update file status");
5205                         return FALSE;
5206                 }
5208         } else if (!status_update_file(line->data, line->type)) {
5209                 report("Failed to update file status");
5210                 return FALSE;
5211         }
5213         return TRUE;
5216 static bool
5217 status_revert(struct status *status, enum line_type type, bool has_none)
5219         if (!status || type != LINE_STAT_UNSTAGED) {
5220                 if (type == LINE_STAT_STAGED) {
5221                         report("Cannot revert changes to staged files");
5222                 } else if (type == LINE_STAT_UNTRACKED) {
5223                         report("Cannot revert changes to untracked files");
5224                 } else if (has_none) {
5225                         report("Nothing to revert");
5226                 } else {
5227                         report("Cannot revert changes to multiple files");
5228                 }
5229                 return FALSE;
5231         } else {
5232                 char mode[10] = "100644";
5233                 const char *reset_argv[] = {
5234                         "git", "update-index", "--cacheinfo", mode,
5235                                 status->old.rev, status->old.name, NULL
5236                 };
5237                 const char *checkout_argv[] = {
5238                         "git", "checkout", "--", status->old.name, NULL
5239                 };
5241                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5242                         return FALSE;
5243                 string_format(mode, "%o", status->old.mode);
5244                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5245                         run_io_fg(checkout_argv, opt_cdup);
5246         }
5249 static enum request
5250 status_request(struct view *view, enum request request, struct line *line)
5252         struct status *status = line->data;
5254         switch (request) {
5255         case REQ_STATUS_UPDATE:
5256                 if (!status_update(view))
5257                         return REQ_NONE;
5258                 break;
5260         case REQ_STATUS_REVERT:
5261                 if (!status_revert(status, line->type, status_has_none(view, line)))
5262                         return REQ_NONE;
5263                 break;
5265         case REQ_STATUS_MERGE:
5266                 if (!status || status->status != 'U') {
5267                         report("Merging only possible for files with unmerged status ('U').");
5268                         return REQ_NONE;
5269                 }
5270                 open_mergetool(status->new.name);
5271                 break;
5273         case REQ_EDIT:
5274                 if (!status)
5275                         return request;
5276                 if (status->status == 'D') {
5277                         report("File has been deleted.");
5278                         return REQ_NONE;
5279                 }
5281                 open_editor(status->status != '?', status->new.name);
5282                 break;
5284         case REQ_VIEW_BLAME:
5285                 if (status) {
5286                         string_copy(opt_file, status->new.name);
5287                         opt_ref[0] = 0;
5288                 }
5289                 return request;
5291         case REQ_ENTER:
5292                 /* After returning the status view has been split to
5293                  * show the stage view. No further reloading is
5294                  * necessary. */
5295                 return status_enter(view, line);
5297         case REQ_REFRESH:
5298                 /* Simply reload the view. */
5299                 break;
5301         default:
5302                 return request;
5303         }
5305         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5307         return REQ_NONE;
5310 static void
5311 status_select(struct view *view, struct line *line)
5313         struct status *status = line->data;
5314         char file[SIZEOF_STR] = "all files";
5315         const char *text;
5316         const char *key;
5318         if (status && !string_format(file, "'%s'", status->new.name))
5319                 return;
5321         if (!status && line[1].type == LINE_STAT_NONE)
5322                 line++;
5324         switch (line->type) {
5325         case LINE_STAT_STAGED:
5326                 text = "Press %s to unstage %s for commit";
5327                 break;
5329         case LINE_STAT_UNSTAGED:
5330                 text = "Press %s to stage %s for commit";
5331                 break;
5333         case LINE_STAT_UNTRACKED:
5334                 text = "Press %s to stage %s for addition";
5335                 break;
5337         case LINE_STAT_HEAD:
5338         case LINE_STAT_NONE:
5339                 text = "Nothing to update";
5340                 break;
5342         default:
5343                 die("line type %d not handled in switch", line->type);
5344         }
5346         if (status && status->status == 'U') {
5347                 text = "Press %s to resolve conflict in %s";
5348                 key = get_key(REQ_STATUS_MERGE);
5350         } else {
5351                 key = get_key(REQ_STATUS_UPDATE);
5352         }
5354         string_format(view->ref, text, key, file);
5357 static bool
5358 status_grep(struct view *view, struct line *line)
5360         struct status *status = line->data;
5361         enum { S_STATUS, S_NAME, S_END } state;
5362         char buf[2] = "?";
5363         regmatch_t pmatch;
5365         if (!status)
5366                 return FALSE;
5368         for (state = S_STATUS; state < S_END; state++) {
5369                 const char *text;
5371                 switch (state) {
5372                 case S_NAME:    text = status->new.name;        break;
5373                 case S_STATUS:
5374                         buf[0] = status->status;
5375                         text = buf;
5376                         break;
5378                 default:
5379                         return FALSE;
5380                 }
5382                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5383                         return TRUE;
5384         }
5386         return FALSE;
5389 static struct view_ops status_ops = {
5390         "file",
5391         NULL,
5392         status_open,
5393         NULL,
5394         status_draw,
5395         status_request,
5396         status_grep,
5397         status_select,
5398 };
5401 static bool
5402 stage_diff_write(struct io *io, struct line *line, struct line *end)
5404         while (line < end) {
5405                 if (!io_write(io, line->data, strlen(line->data)) ||
5406                     !io_write(io, "\n", 1))
5407                         return FALSE;
5408                 line++;
5409                 if (line->type == LINE_DIFF_CHUNK ||
5410                     line->type == LINE_DIFF_HEADER)
5411                         break;
5412         }
5414         return TRUE;
5417 static struct line *
5418 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5420         for (; view->line < line; line--)
5421                 if (line->type == type)
5422                         return line;
5424         return NULL;
5427 static bool
5428 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5430         const char *apply_argv[SIZEOF_ARG] = {
5431                 "git", "apply", "--whitespace=nowarn", NULL
5432         };
5433         struct line *diff_hdr;
5434         struct io io = {};
5435         int argc = 3;
5437         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5438         if (!diff_hdr)
5439                 return FALSE;
5441         if (!revert)
5442                 apply_argv[argc++] = "--cached";
5443         if (revert || stage_line_type == LINE_STAT_STAGED)
5444                 apply_argv[argc++] = "-R";
5445         apply_argv[argc++] = "-";
5446         apply_argv[argc++] = NULL;
5447         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5448                 return FALSE;
5450         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5451             !stage_diff_write(&io, chunk, view->line + view->lines))
5452                 chunk = NULL;
5454         done_io(&io);
5455         run_io_bg(update_index_argv);
5457         return chunk ? TRUE : FALSE;
5460 static bool
5461 stage_update(struct view *view, struct line *line)
5463         struct line *chunk = NULL;
5465         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5466                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5468         if (chunk) {
5469                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5470                         report("Failed to apply chunk");
5471                         return FALSE;
5472                 }
5474         } else if (!stage_status.status) {
5475                 view = VIEW(REQ_VIEW_STATUS);
5477                 for (line = view->line; line < view->line + view->lines; line++)
5478                         if (line->type == stage_line_type)
5479                                 break;
5481                 if (!status_update_files(view, line + 1)) {
5482                         report("Failed to update files");
5483                         return FALSE;
5484                 }
5486         } else if (!status_update_file(&stage_status, stage_line_type)) {
5487                 report("Failed to update file");
5488                 return FALSE;
5489         }
5491         return TRUE;
5494 static bool
5495 stage_revert(struct view *view, struct line *line)
5497         struct line *chunk = NULL;
5499         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5500                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5502         if (chunk) {
5503                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5504                         return FALSE;
5506                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5507                         report("Failed to revert chunk");
5508                         return FALSE;
5509                 }
5510                 return TRUE;
5512         } else {
5513                 return status_revert(stage_status.status ? &stage_status : NULL,
5514                                      stage_line_type, FALSE);
5515         }
5519 static void
5520 stage_next(struct view *view, struct line *line)
5522         int i;
5524         if (!stage_chunks) {
5525                 static size_t alloc = 0;
5526                 int *tmp;
5528                 for (line = view->line; line < view->line + view->lines; line++) {
5529                         if (line->type != LINE_DIFF_CHUNK)
5530                                 continue;
5532                         tmp = realloc_items(stage_chunk, &alloc,
5533                                             stage_chunks, sizeof(*tmp));
5534                         if (!tmp) {
5535                                 report("Allocation failure");
5536                                 return;
5537                         }
5539                         stage_chunk = tmp;
5540                         stage_chunk[stage_chunks++] = line - view->line;
5541                 }
5542         }
5544         for (i = 0; i < stage_chunks; i++) {
5545                 if (stage_chunk[i] > view->lineno) {
5546                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5547                         report("Chunk %d of %d", i + 1, stage_chunks);
5548                         return;
5549                 }
5550         }
5552         report("No next chunk found");
5555 static enum request
5556 stage_request(struct view *view, enum request request, struct line *line)
5558         switch (request) {
5559         case REQ_STATUS_UPDATE:
5560                 if (!stage_update(view, line))
5561                         return REQ_NONE;
5562                 break;
5564         case REQ_STATUS_REVERT:
5565                 if (!stage_revert(view, line))
5566                         return REQ_NONE;
5567                 break;
5569         case REQ_STAGE_NEXT:
5570                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5571                         report("File is untracked; press %s to add",
5572                                get_key(REQ_STATUS_UPDATE));
5573                         return REQ_NONE;
5574                 }
5575                 stage_next(view, line);
5576                 return REQ_NONE;
5578         case REQ_EDIT:
5579                 if (!stage_status.new.name[0])
5580                         return request;
5581                 if (stage_status.status == 'D') {
5582                         report("File has been deleted.");
5583                         return REQ_NONE;
5584                 }
5586                 open_editor(stage_status.status != '?', stage_status.new.name);
5587                 break;
5589         case REQ_REFRESH:
5590                 /* Reload everything ... */
5591                 break;
5593         case REQ_VIEW_BLAME:
5594                 if (stage_status.new.name[0]) {
5595                         string_copy(opt_file, stage_status.new.name);
5596                         opt_ref[0] = 0;
5597                 }
5598                 return request;
5600         case REQ_ENTER:
5601                 return pager_request(view, request, line);
5603         default:
5604                 return request;
5605         }
5607         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5608         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5610         /* Check whether the staged entry still exists, and close the
5611          * stage view if it doesn't. */
5612         if (!status_exists(&stage_status, stage_line_type)) {
5613                 status_restore(VIEW(REQ_VIEW_STATUS));
5614                 return REQ_VIEW_CLOSE;
5615         }
5617         if (stage_line_type == LINE_STAT_UNTRACKED) {
5618                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5619                         report("Cannot display a directory");
5620                         return REQ_NONE;
5621                 }
5623                 if (!prepare_update_file(view, stage_status.new.name)) {
5624                         report("Failed to open file: %s", strerror(errno));
5625                         return REQ_NONE;
5626                 }
5627         }
5628         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5630         return REQ_NONE;
5633 static struct view_ops stage_ops = {
5634         "line",
5635         NULL,
5636         NULL,
5637         pager_read,
5638         pager_draw,
5639         stage_request,
5640         pager_grep,
5641         pager_select,
5642 };
5645 /*
5646  * Revision graph
5647  */
5649 struct commit {
5650         char id[SIZEOF_REV];            /* SHA1 ID. */
5651         char title[128];                /* First line of the commit message. */
5652         const char *author;             /* Author of the commit. */
5653         struct tm time;                 /* Date from the author ident. */
5654         struct ref **refs;              /* Repository references. */
5655         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5656         size_t graph_size;              /* The width of the graph array. */
5657         bool has_parents;               /* Rewritten --parents seen. */
5658 };
5660 /* Size of rev graph with no  "padding" columns */
5661 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5663 struct rev_graph {
5664         struct rev_graph *prev, *next, *parents;
5665         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5666         size_t size;
5667         struct commit *commit;
5668         size_t pos;
5669         unsigned int boundary:1;
5670 };
5672 /* Parents of the commit being visualized. */
5673 static struct rev_graph graph_parents[4];
5675 /* The current stack of revisions on the graph. */
5676 static struct rev_graph graph_stacks[4] = {
5677         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5678         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5679         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5680         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5681 };
5683 static inline bool
5684 graph_parent_is_merge(struct rev_graph *graph)
5686         return graph->parents->size > 1;
5689 static inline void
5690 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5692         struct commit *commit = graph->commit;
5694         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5695                 commit->graph[commit->graph_size++] = symbol;
5698 static void
5699 clear_rev_graph(struct rev_graph *graph)
5701         graph->boundary = 0;
5702         graph->size = graph->pos = 0;
5703         graph->commit = NULL;
5704         memset(graph->parents, 0, sizeof(*graph->parents));
5707 static void
5708 done_rev_graph(struct rev_graph *graph)
5710         if (graph_parent_is_merge(graph) &&
5711             graph->pos < graph->size - 1 &&
5712             graph->next->size == graph->size + graph->parents->size - 1) {
5713                 size_t i = graph->pos + graph->parents->size - 1;
5715                 graph->commit->graph_size = i * 2;
5716                 while (i < graph->next->size - 1) {
5717                         append_to_rev_graph(graph, ' ');
5718                         append_to_rev_graph(graph, '\\');
5719                         i++;
5720                 }
5721         }
5723         clear_rev_graph(graph);
5726 static void
5727 push_rev_graph(struct rev_graph *graph, const char *parent)
5729         int i;
5731         /* "Collapse" duplicate parents lines.
5732          *
5733          * FIXME: This needs to also update update the drawn graph but
5734          * for now it just serves as a method for pruning graph lines. */
5735         for (i = 0; i < graph->size; i++)
5736                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5737                         return;
5739         if (graph->size < SIZEOF_REVITEMS) {
5740                 string_copy_rev(graph->rev[graph->size++], parent);
5741         }
5744 static chtype
5745 get_rev_graph_symbol(struct rev_graph *graph)
5747         chtype symbol;
5749         if (graph->boundary)
5750                 symbol = REVGRAPH_BOUND;
5751         else if (graph->parents->size == 0)
5752                 symbol = REVGRAPH_INIT;
5753         else if (graph_parent_is_merge(graph))
5754                 symbol = REVGRAPH_MERGE;
5755         else if (graph->pos >= graph->size)
5756                 symbol = REVGRAPH_BRANCH;
5757         else
5758                 symbol = REVGRAPH_COMMIT;
5760         return symbol;
5763 static void
5764 draw_rev_graph(struct rev_graph *graph)
5766         struct rev_filler {
5767                 chtype separator, line;
5768         };
5769         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5770         static struct rev_filler fillers[] = {
5771                 { ' ',  '|' },
5772                 { '`',  '.' },
5773                 { '\'', ' ' },
5774                 { '/',  ' ' },
5775         };
5776         chtype symbol = get_rev_graph_symbol(graph);
5777         struct rev_filler *filler;
5778         size_t i;
5780         if (opt_line_graphics)
5781                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5783         filler = &fillers[DEFAULT];
5785         for (i = 0; i < graph->pos; i++) {
5786                 append_to_rev_graph(graph, filler->line);
5787                 if (graph_parent_is_merge(graph->prev) &&
5788                     graph->prev->pos == i)
5789                         filler = &fillers[RSHARP];
5791                 append_to_rev_graph(graph, filler->separator);
5792         }
5794         /* Place the symbol for this revision. */
5795         append_to_rev_graph(graph, symbol);
5797         if (graph->prev->size > graph->size)
5798                 filler = &fillers[RDIAG];
5799         else
5800                 filler = &fillers[DEFAULT];
5802         i++;
5804         for (; i < graph->size; i++) {
5805                 append_to_rev_graph(graph, filler->separator);
5806                 append_to_rev_graph(graph, filler->line);
5807                 if (graph_parent_is_merge(graph->prev) &&
5808                     i < graph->prev->pos + graph->parents->size)
5809                         filler = &fillers[RSHARP];
5810                 if (graph->prev->size > graph->size)
5811                         filler = &fillers[LDIAG];
5812         }
5814         if (graph->prev->size > graph->size) {
5815                 append_to_rev_graph(graph, filler->separator);
5816                 if (filler->line != ' ')
5817                         append_to_rev_graph(graph, filler->line);
5818         }
5821 /* Prepare the next rev graph */
5822 static void
5823 prepare_rev_graph(struct rev_graph *graph)
5825         size_t i;
5827         /* First, traverse all lines of revisions up to the active one. */
5828         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5829                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5830                         break;
5832                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5833         }
5835         /* Interleave the new revision parent(s). */
5836         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5837                 push_rev_graph(graph->next, graph->parents->rev[i]);
5839         /* Lastly, put any remaining revisions. */
5840         for (i = graph->pos + 1; i < graph->size; i++)
5841                 push_rev_graph(graph->next, graph->rev[i]);
5844 static void
5845 update_rev_graph(struct view *view, struct rev_graph *graph)
5847         /* If this is the finalizing update ... */
5848         if (graph->commit)
5849                 prepare_rev_graph(graph);
5851         /* Graph visualization needs a one rev look-ahead,
5852          * so the first update doesn't visualize anything. */
5853         if (!graph->prev->commit)
5854                 return;
5856         if (view->lines > 2)
5857                 view->line[view->lines - 3].dirty = 1;
5858         if (view->lines > 1)
5859                 view->line[view->lines - 2].dirty = 1;
5860         draw_rev_graph(graph->prev);
5861         done_rev_graph(graph->prev->prev);
5865 /*
5866  * Main view backend
5867  */
5869 static const char *main_argv[SIZEOF_ARG] = {
5870         "git", "log", "--no-color", "--pretty=raw", "--parents",
5871                       "--topo-order", "%(head)", NULL
5872 };
5874 static bool
5875 main_draw(struct view *view, struct line *line, unsigned int lineno)
5877         struct commit *commit = line->data;
5879         if (!commit->author)
5880                 return FALSE;
5882         if (opt_date && draw_date(view, &commit->time))
5883                 return TRUE;
5885         if (opt_author && draw_author(view, commit->author))
5886                 return TRUE;
5888         if (opt_rev_graph && commit->graph_size &&
5889             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5890                 return TRUE;
5892         if (opt_show_refs && commit->refs) {
5893                 size_t i = 0;
5895                 do {
5896                         enum line_type type;
5898                         if (commit->refs[i]->head)
5899                                 type = LINE_MAIN_HEAD;
5900                         else if (commit->refs[i]->ltag)
5901                                 type = LINE_MAIN_LOCAL_TAG;
5902                         else if (commit->refs[i]->tag)
5903                                 type = LINE_MAIN_TAG;
5904                         else if (commit->refs[i]->tracked)
5905                                 type = LINE_MAIN_TRACKED;
5906                         else if (commit->refs[i]->remote)
5907                                 type = LINE_MAIN_REMOTE;
5908                         else
5909                                 type = LINE_MAIN_REF;
5911                         if (draw_text(view, type, "[", TRUE) ||
5912                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5913                             draw_text(view, type, "]", TRUE))
5914                                 return TRUE;
5916                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5917                                 return TRUE;
5918                 } while (commit->refs[i++]->next);
5919         }
5921         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5922         return TRUE;
5925 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5926 static bool
5927 main_read(struct view *view, char *line)
5929         static struct rev_graph *graph = graph_stacks;
5930         enum line_type type;
5931         struct commit *commit;
5933         if (!line) {
5934                 int i;
5936                 if (!view->lines && !view->parent)
5937                         die("No revisions match the given arguments.");
5938                 if (view->lines > 0) {
5939                         commit = view->line[view->lines - 1].data;
5940                         view->line[view->lines - 1].dirty = 1;
5941                         if (!commit->author) {
5942                                 view->lines--;
5943                                 free(commit);
5944                                 graph->commit = NULL;
5945                         }
5946                 }
5947                 update_rev_graph(view, graph);
5949                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5950                         clear_rev_graph(&graph_stacks[i]);
5951                 return TRUE;
5952         }
5954         type = get_line_type(line);
5955         if (type == LINE_COMMIT) {
5956                 commit = calloc(1, sizeof(struct commit));
5957                 if (!commit)
5958                         return FALSE;
5960                 line += STRING_SIZE("commit ");
5961                 if (*line == '-') {
5962                         graph->boundary = 1;
5963                         line++;
5964                 }
5966                 string_copy_rev(commit->id, line);
5967                 commit->refs = get_refs(commit->id);
5968                 graph->commit = commit;
5969                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5971                 while ((line = strchr(line, ' '))) {
5972                         line++;
5973                         push_rev_graph(graph->parents, line);
5974                         commit->has_parents = TRUE;
5975                 }
5976                 return TRUE;
5977         }
5979         if (!view->lines)
5980                 return TRUE;
5981         commit = view->line[view->lines - 1].data;
5983         switch (type) {
5984         case LINE_PARENT:
5985                 if (commit->has_parents)
5986                         break;
5987                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5988                 break;
5990         case LINE_AUTHOR:
5991                 parse_author_line(line + STRING_SIZE("author "),
5992                                   &commit->author, &commit->time);
5993                 update_rev_graph(view, graph);
5994                 graph = graph->next;
5995                 break;
5997         default:
5998                 /* Fill in the commit title if it has not already been set. */
5999                 if (commit->title[0])
6000                         break;
6002                 /* Require titles to start with a non-space character at the
6003                  * offset used by git log. */
6004                 if (strncmp(line, "    ", 4))
6005                         break;
6006                 line += 4;
6007                 /* Well, if the title starts with a whitespace character,
6008                  * try to be forgiving.  Otherwise we end up with no title. */
6009                 while (isspace(*line))
6010                         line++;
6011                 if (*line == '\0')
6012                         break;
6013                 /* FIXME: More graceful handling of titles; append "..." to
6014                  * shortened titles, etc. */
6016                 string_expand(commit->title, sizeof(commit->title), line, 1);
6017                 view->line[view->lines - 1].dirty = 1;
6018         }
6020         return TRUE;
6023 static enum request
6024 main_request(struct view *view, enum request request, struct line *line)
6026         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6028         switch (request) {
6029         case REQ_ENTER:
6030                 open_view(view, REQ_VIEW_DIFF, flags);
6031                 break;
6032         case REQ_REFRESH:
6033                 load_refs();
6034                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6035                 break;
6036         default:
6037                 return request;
6038         }
6040         return REQ_NONE;
6043 static bool
6044 grep_refs(struct ref **refs, regex_t *regex)
6046         regmatch_t pmatch;
6047         size_t i = 0;
6049         if (!refs)
6050                 return FALSE;
6051         do {
6052                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6053                         return TRUE;
6054         } while (refs[i++]->next);
6056         return FALSE;
6059 static bool
6060 main_grep(struct view *view, struct line *line)
6062         struct commit *commit = line->data;
6063         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6064         char buf[DATE_COLS + 1];
6065         regmatch_t pmatch;
6067         for (state = S_TITLE; state < S_END; state++) {
6068                 const char *text;
6070                 switch (state) {
6071                 case S_TITLE:   text = commit->title;   break;
6072                 case S_AUTHOR:
6073                         if (!opt_author)
6074                                 continue;
6075                         text = commit->author;
6076                         break;
6077                 case S_DATE:
6078                         if (!opt_date)
6079                                 continue;
6080                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6081                                 continue;
6082                         text = buf;
6083                         break;
6084                 case S_REFS:
6085                         if (!opt_show_refs)
6086                                 continue;
6087                         if (grep_refs(commit->refs, view->regex) == TRUE)
6088                                 return TRUE;
6089                         continue;
6090                 default:
6091                         return FALSE;
6092                 }
6094                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6095                         return TRUE;
6096         }
6098         return FALSE;
6101 static void
6102 main_select(struct view *view, struct line *line)
6104         struct commit *commit = line->data;
6106         string_copy_rev(view->ref, commit->id);
6107         string_copy_rev(ref_commit, view->ref);
6110 static struct view_ops main_ops = {
6111         "commit",
6112         main_argv,
6113         NULL,
6114         main_read,
6115         main_draw,
6116         main_request,
6117         main_grep,
6118         main_select,
6119 };
6122 /*
6123  * Unicode / UTF-8 handling
6124  *
6125  * NOTE: Much of the following code for dealing with Unicode is derived from
6126  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6127  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6128  */
6130 static inline int
6131 unicode_width(unsigned long c)
6133         if (c >= 0x1100 &&
6134            (c <= 0x115f                         /* Hangul Jamo */
6135             || c == 0x2329
6136             || c == 0x232a
6137             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6138                                                 /* CJK ... Yi */
6139             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6140             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6141             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6142             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6143             || (c >= 0xffe0  && c <= 0xffe6)
6144             || (c >= 0x20000 && c <= 0x2fffd)
6145             || (c >= 0x30000 && c <= 0x3fffd)))
6146                 return 2;
6148         if (c == '\t')
6149                 return opt_tab_size;
6151         return 1;
6154 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6155  * Illegal bytes are set one. */
6156 static const unsigned char utf8_bytes[256] = {
6157         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,
6158         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,
6159         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,
6160         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,
6161         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,
6162         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,
6163         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,
6164         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,
6165 };
6167 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6168 static inline unsigned long
6169 utf8_to_unicode(const char *string, size_t length)
6171         unsigned long unicode;
6173         switch (length) {
6174         case 1:
6175                 unicode  =   string[0];
6176                 break;
6177         case 2:
6178                 unicode  =  (string[0] & 0x1f) << 6;
6179                 unicode +=  (string[1] & 0x3f);
6180                 break;
6181         case 3:
6182                 unicode  =  (string[0] & 0x0f) << 12;
6183                 unicode += ((string[1] & 0x3f) << 6);
6184                 unicode +=  (string[2] & 0x3f);
6185                 break;
6186         case 4:
6187                 unicode  =  (string[0] & 0x0f) << 18;
6188                 unicode += ((string[1] & 0x3f) << 12);
6189                 unicode += ((string[2] & 0x3f) << 6);
6190                 unicode +=  (string[3] & 0x3f);
6191                 break;
6192         case 5:
6193                 unicode  =  (string[0] & 0x0f) << 24;
6194                 unicode += ((string[1] & 0x3f) << 18);
6195                 unicode += ((string[2] & 0x3f) << 12);
6196                 unicode += ((string[3] & 0x3f) << 6);
6197                 unicode +=  (string[4] & 0x3f);
6198                 break;
6199         case 6:
6200                 unicode  =  (string[0] & 0x01) << 30;
6201                 unicode += ((string[1] & 0x3f) << 24);
6202                 unicode += ((string[2] & 0x3f) << 18);
6203                 unicode += ((string[3] & 0x3f) << 12);
6204                 unicode += ((string[4] & 0x3f) << 6);
6205                 unicode +=  (string[5] & 0x3f);
6206                 break;
6207         default:
6208                 die("Invalid Unicode length");
6209         }
6211         /* Invalid characters could return the special 0xfffd value but NUL
6212          * should be just as good. */
6213         return unicode > 0xffff ? 0 : unicode;
6216 /* Calculates how much of string can be shown within the given maximum width
6217  * and sets trimmed parameter to non-zero value if all of string could not be
6218  * shown. If the reserve flag is TRUE, it will reserve at least one
6219  * trailing character, which can be useful when drawing a delimiter.
6220  *
6221  * Returns the number of bytes to output from string to satisfy max_width. */
6222 static size_t
6223 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6225         const char *string = *start;
6226         const char *end = strchr(string, '\0');
6227         unsigned char last_bytes = 0;
6228         size_t last_ucwidth = 0;
6230         *width = 0;
6231         *trimmed = 0;
6233         while (string < end) {
6234                 int c = *(unsigned char *) string;
6235                 unsigned char bytes = utf8_bytes[c];
6236                 size_t ucwidth;
6237                 unsigned long unicode;
6239                 if (string + bytes > end)
6240                         break;
6242                 /* Change representation to figure out whether
6243                  * it is a single- or double-width character. */
6245                 unicode = utf8_to_unicode(string, bytes);
6246                 /* FIXME: Graceful handling of invalid Unicode character. */
6247                 if (!unicode)
6248                         break;
6250                 ucwidth = unicode_width(unicode);
6251                 if (skip > 0) {
6252                         skip -= ucwidth <= skip ? ucwidth : skip;
6253                         *start += bytes;
6254                 }
6255                 *width  += ucwidth;
6256                 if (*width > max_width) {
6257                         *trimmed = 1;
6258                         *width -= ucwidth;
6259                         if (reserve && *width == max_width) {
6260                                 string -= last_bytes;
6261                                 *width -= last_ucwidth;
6262                         }
6263                         break;
6264                 }
6266                 string  += bytes;
6267                 last_bytes = ucwidth ? bytes : 0;
6268                 last_ucwidth = ucwidth;
6269         }
6271         return string - *start;
6275 /*
6276  * Status management
6277  */
6279 /* Whether or not the curses interface has been initialized. */
6280 static bool cursed = FALSE;
6282 /* Terminal hacks and workarounds. */
6283 static bool use_scroll_redrawwin;
6284 static bool use_scroll_status_wclear;
6286 /* The status window is used for polling keystrokes. */
6287 static WINDOW *status_win;
6289 /* Reading from the prompt? */
6290 static bool input_mode = FALSE;
6292 static bool status_empty = FALSE;
6294 /* Update status and title window. */
6295 static void
6296 report(const char *msg, ...)
6298         struct view *view = display[current_view];
6300         if (input_mode)
6301                 return;
6303         if (!view) {
6304                 char buf[SIZEOF_STR];
6305                 va_list args;
6307                 va_start(args, msg);
6308                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6309                         buf[sizeof(buf) - 1] = 0;
6310                         buf[sizeof(buf) - 2] = '.';
6311                         buf[sizeof(buf) - 3] = '.';
6312                         buf[sizeof(buf) - 4] = '.';
6313                 }
6314                 va_end(args);
6315                 die("%s", buf);
6316         }
6318         if (!status_empty || *msg) {
6319                 va_list args;
6321                 va_start(args, msg);
6323                 wmove(status_win, 0, 0);
6324                 if (view->has_scrolled && use_scroll_status_wclear)
6325                         wclear(status_win);
6326                 if (*msg) {
6327                         vwprintw(status_win, msg, args);
6328                         status_empty = FALSE;
6329                 } else {
6330                         status_empty = TRUE;
6331                 }
6332                 wclrtoeol(status_win);
6333                 wnoutrefresh(status_win);
6335                 va_end(args);
6336         }
6338         update_view_title(view);
6341 /* Controls when nodelay should be in effect when polling user input. */
6342 static void
6343 set_nonblocking_input(bool loading)
6345         static unsigned int loading_views;
6347         if ((loading == FALSE && loading_views-- == 1) ||
6348             (loading == TRUE  && loading_views++ == 0))
6349                 nodelay(status_win, loading);
6352 static void
6353 init_display(void)
6355         const char *term;
6356         int x, y;
6358         /* Initialize the curses library */
6359         if (isatty(STDIN_FILENO)) {
6360                 cursed = !!initscr();
6361                 opt_tty = stdin;
6362         } else {
6363                 /* Leave stdin and stdout alone when acting as a pager. */
6364                 opt_tty = fopen("/dev/tty", "r+");
6365                 if (!opt_tty)
6366                         die("Failed to open /dev/tty");
6367                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6368         }
6370         if (!cursed)
6371                 die("Failed to initialize curses");
6373         nonl();         /* Disable conversion and detect newlines from input. */
6374         cbreak();       /* Take input chars one at a time, no wait for \n */
6375         noecho();       /* Don't echo input */
6376         leaveok(stdscr, FALSE);
6378         if (has_colors())
6379                 init_colors();
6381         getmaxyx(stdscr, y, x);
6382         status_win = newwin(1, 0, y - 1, 0);
6383         if (!status_win)
6384                 die("Failed to create status window");
6386         /* Enable keyboard mapping */
6387         keypad(status_win, TRUE);
6388         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6390         TABSIZE = opt_tab_size;
6391         if (opt_line_graphics) {
6392                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6393         }
6395         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6396         if (term && !strcmp(term, "gnome-terminal")) {
6397                 /* In the gnome-terminal-emulator, the message from
6398                  * scrolling up one line when impossible followed by
6399                  * scrolling down one line causes corruption of the
6400                  * status line. This is fixed by calling wclear. */
6401                 use_scroll_status_wclear = TRUE;
6402                 use_scroll_redrawwin = FALSE;
6404         } else if (term && !strcmp(term, "xrvt-xpm")) {
6405                 /* No problems with full optimizations in xrvt-(unicode)
6406                  * and aterm. */
6407                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6409         } else {
6410                 /* When scrolling in (u)xterm the last line in the
6411                  * scrolling direction will update slowly. */
6412                 use_scroll_redrawwin = TRUE;
6413                 use_scroll_status_wclear = FALSE;
6414         }
6417 static int
6418 get_input(int prompt_position)
6420         struct view *view;
6421         int i, key, cursor_y, cursor_x;
6423         if (prompt_position)
6424                 input_mode = TRUE;
6426         while (TRUE) {
6427                 foreach_view (view, i) {
6428                         update_view(view);
6429                         if (view_is_displayed(view) && view->has_scrolled &&
6430                             use_scroll_redrawwin)
6431                                 redrawwin(view->win);
6432                         view->has_scrolled = FALSE;
6433                 }
6435                 /* Update the cursor position. */
6436                 if (prompt_position) {
6437                         getbegyx(status_win, cursor_y, cursor_x);
6438                         cursor_x = prompt_position;
6439                 } else {
6440                         view = display[current_view];
6441                         getbegyx(view->win, cursor_y, cursor_x);
6442                         cursor_x = view->width - 1;
6443                         cursor_y += view->lineno - view->offset;
6444                 }
6445                 setsyx(cursor_y, cursor_x);
6447                 /* Refresh, accept single keystroke of input */
6448                 doupdate();
6449                 key = wgetch(status_win);
6451                 /* wgetch() with nodelay() enabled returns ERR when
6452                  * there's no input. */
6453                 if (key == ERR) {
6455                 } else if (key == KEY_RESIZE) {
6456                         int height, width;
6458                         getmaxyx(stdscr, height, width);
6460                         wresize(status_win, 1, width);
6461                         mvwin(status_win, height - 1, 0);
6462                         wnoutrefresh(status_win);
6463                         resize_display();
6464                         redraw_display(TRUE);
6466                 } else {
6467                         input_mode = FALSE;
6468                         return key;
6469                 }
6470         }
6473 static char *
6474 prompt_input(const char *prompt, input_handler handler, void *data)
6476         enum input_status status = INPUT_OK;
6477         static char buf[SIZEOF_STR];
6478         size_t pos = 0;
6480         buf[pos] = 0;
6482         while (status == INPUT_OK || status == INPUT_SKIP) {
6483                 int key;
6485                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6486                 wclrtoeol(status_win);
6488                 key = get_input(pos + 1);
6489                 switch (key) {
6490                 case KEY_RETURN:
6491                 case KEY_ENTER:
6492                 case '\n':
6493                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6494                         break;
6496                 case KEY_BACKSPACE:
6497                         if (pos > 0)
6498                                 buf[--pos] = 0;
6499                         else
6500                                 status = INPUT_CANCEL;
6501                         break;
6503                 case KEY_ESC:
6504                         status = INPUT_CANCEL;
6505                         break;
6507                 default:
6508                         if (pos >= sizeof(buf)) {
6509                                 report("Input string too long");
6510                                 return NULL;
6511                         }
6513                         status = handler(data, buf, key);
6514                         if (status == INPUT_OK)
6515                                 buf[pos++] = (char) key;
6516                 }
6517         }
6519         /* Clear the status window */
6520         status_empty = FALSE;
6521         report("");
6523         if (status == INPUT_CANCEL)
6524                 return NULL;
6526         buf[pos++] = 0;
6528         return buf;
6531 static enum input_status
6532 prompt_yesno_handler(void *data, char *buf, int c)
6534         if (c == 'y' || c == 'Y')
6535                 return INPUT_STOP;
6536         if (c == 'n' || c == 'N')
6537                 return INPUT_CANCEL;
6538         return INPUT_SKIP;
6541 static bool
6542 prompt_yesno(const char *prompt)
6544         char prompt2[SIZEOF_STR];
6546         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6547                 return FALSE;
6549         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6552 static enum input_status
6553 read_prompt_handler(void *data, char *buf, int c)
6555         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6558 static char *
6559 read_prompt(const char *prompt)
6561         return prompt_input(prompt, read_prompt_handler, NULL);
6564 /*
6565  * Repository properties
6566  */
6568 static struct ref *refs = NULL;
6569 static size_t refs_alloc = 0;
6570 static size_t refs_size = 0;
6572 /* Id <-> ref store */
6573 static struct ref ***id_refs = NULL;
6574 static size_t id_refs_alloc = 0;
6575 static size_t id_refs_size = 0;
6577 static int
6578 compare_refs(const void *ref1_, const void *ref2_)
6580         const struct ref *ref1 = *(const struct ref **)ref1_;
6581         const struct ref *ref2 = *(const struct ref **)ref2_;
6583         if (ref1->tag != ref2->tag)
6584                 return ref2->tag - ref1->tag;
6585         if (ref1->ltag != ref2->ltag)
6586                 return ref2->ltag - ref2->ltag;
6587         if (ref1->head != ref2->head)
6588                 return ref2->head - ref1->head;
6589         if (ref1->tracked != ref2->tracked)
6590                 return ref2->tracked - ref1->tracked;
6591         if (ref1->remote != ref2->remote)
6592                 return ref2->remote - ref1->remote;
6593         return strcmp(ref1->name, ref2->name);
6596 static struct ref **
6597 get_refs(const char *id)
6599         struct ref ***tmp_id_refs;
6600         struct ref **ref_list = NULL;
6601         size_t ref_list_alloc = 0;
6602         size_t ref_list_size = 0;
6603         size_t i;
6605         for (i = 0; i < id_refs_size; i++)
6606                 if (!strcmp(id, id_refs[i][0]->id))
6607                         return id_refs[i];
6609         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6610                                     sizeof(*id_refs));
6611         if (!tmp_id_refs)
6612                 return NULL;
6614         id_refs = tmp_id_refs;
6616         for (i = 0; i < refs_size; i++) {
6617                 struct ref **tmp;
6619                 if (strcmp(id, refs[i].id))
6620                         continue;
6622                 tmp = realloc_items(ref_list, &ref_list_alloc,
6623                                     ref_list_size + 1, sizeof(*ref_list));
6624                 if (!tmp) {
6625                         if (ref_list)
6626                                 free(ref_list);
6627                         return NULL;
6628                 }
6630                 ref_list = tmp;
6631                 ref_list[ref_list_size] = &refs[i];
6632                 /* XXX: The properties of the commit chains ensures that we can
6633                  * safely modify the shared ref. The repo references will
6634                  * always be similar for the same id. */
6635                 ref_list[ref_list_size]->next = 1;
6637                 ref_list_size++;
6638         }
6640         if (ref_list) {
6641                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6642                 ref_list[ref_list_size - 1]->next = 0;
6643                 id_refs[id_refs_size++] = ref_list;
6644         }
6646         return ref_list;
6649 static int
6650 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6652         struct ref *ref;
6653         bool tag = FALSE;
6654         bool ltag = FALSE;
6655         bool remote = FALSE;
6656         bool tracked = FALSE;
6657         bool check_replace = FALSE;
6658         bool head = FALSE;
6660         if (!prefixcmp(name, "refs/tags/")) {
6661                 if (!suffixcmp(name, namelen, "^{}")) {
6662                         namelen -= 3;
6663                         name[namelen] = 0;
6664                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6665                                 check_replace = TRUE;
6666                 } else {
6667                         ltag = TRUE;
6668                 }
6670                 tag = TRUE;
6671                 namelen -= STRING_SIZE("refs/tags/");
6672                 name    += STRING_SIZE("refs/tags/");
6674         } else if (!prefixcmp(name, "refs/remotes/")) {
6675                 remote = TRUE;
6676                 namelen -= STRING_SIZE("refs/remotes/");
6677                 name    += STRING_SIZE("refs/remotes/");
6678                 tracked  = !strcmp(opt_remote, name);
6680         } else if (!prefixcmp(name, "refs/heads/")) {
6681                 namelen -= STRING_SIZE("refs/heads/");
6682                 name    += STRING_SIZE("refs/heads/");
6683                 head     = !strncmp(opt_head, name, namelen);
6685         } else if (!strcmp(name, "HEAD")) {
6686                 string_ncopy(opt_head_rev, id, idlen);
6687                 return OK;
6688         }
6690         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6691                 /* it's an annotated tag, replace the previous SHA1 with the
6692                  * resolved commit id; relies on the fact git-ls-remote lists
6693                  * the commit id of an annotated tag right before the commit id
6694                  * it points to. */
6695                 refs[refs_size - 1].ltag = ltag;
6696                 string_copy_rev(refs[refs_size - 1].id, id);
6698                 return OK;
6699         }
6700         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6701         if (!refs)
6702                 return ERR;
6704         ref = &refs[refs_size++];
6705         ref->name = malloc(namelen + 1);
6706         if (!ref->name)
6707                 return ERR;
6709         strncpy(ref->name, name, namelen);
6710         ref->name[namelen] = 0;
6711         ref->head = head;
6712         ref->tag = tag;
6713         ref->ltag = ltag;
6714         ref->remote = remote;
6715         ref->tracked = tracked;
6716         string_copy_rev(ref->id, id);
6718         return OK;
6721 static int
6722 load_refs(void)
6724         static const char *ls_remote_argv[SIZEOF_ARG] = {
6725                 "git", "ls-remote", opt_git_dir, NULL
6726         };
6727         static bool init = FALSE;
6729         if (!init) {
6730                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6731                 init = TRUE;
6732         }
6734         if (!*opt_git_dir)
6735                 return OK;
6737         while (refs_size > 0)
6738                 free(refs[--refs_size].name);
6739         while (id_refs_size > 0)
6740                 free(id_refs[--id_refs_size]);
6742         return run_io_load(ls_remote_argv, "\t", read_ref);
6745 static void
6746 set_remote_branch(const char *name, const char *value, size_t valuelen)
6748         if (!strcmp(name, ".remote")) {
6749                 string_ncopy(opt_remote, value, valuelen);
6751         } else if (*opt_remote && !strcmp(name, ".merge")) {
6752                 size_t from = strlen(opt_remote);
6754                 if (!prefixcmp(value, "refs/heads/"))
6755                         value += STRING_SIZE("refs/heads/");
6757                 if (!string_format_from(opt_remote, &from, "/%s", value))
6758                         opt_remote[0] = 0;
6759         }
6762 static void
6763 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6765         const char *argv[SIZEOF_ARG] = { name, "=" };
6766         int argc = 1 + (cmd == option_set_command);
6767         int error = ERR;
6769         if (!argv_from_string(argv, &argc, value))
6770                 config_msg = "Too many option arguments";
6771         else
6772                 error = cmd(argc, argv);
6774         if (error == ERR)
6775                 warn("Option 'tig.%s': %s", name, config_msg);
6778 static bool
6779 set_environment_variable(const char *name, const char *value)
6781         size_t len = strlen(name) + 1 + strlen(value) + 1;
6782         char *env = malloc(len);
6784         if (env &&
6785             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6786             putenv(env) == 0)
6787                 return TRUE;
6788         free(env);
6789         return FALSE;
6792 static void
6793 set_work_tree(const char *value)
6795         char cwd[SIZEOF_STR];
6797         if (!getcwd(cwd, sizeof(cwd)))
6798                 die("Failed to get cwd path: %s", strerror(errno));
6799         if (chdir(opt_git_dir) < 0)
6800                 die("Failed to chdir(%s): %s", strerror(errno));
6801         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6802                 die("Failed to get git path: %s", strerror(errno));
6803         if (chdir(cwd) < 0)
6804                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6805         if (chdir(value) < 0)
6806                 die("Failed to chdir(%s): %s", value, strerror(errno));
6807         if (!getcwd(cwd, sizeof(cwd)))
6808                 die("Failed to get cwd path: %s", strerror(errno));
6809         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6810                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6811         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6812                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6813         opt_is_inside_work_tree = TRUE;
6816 static int
6817 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6819         if (!strcmp(name, "i18n.commitencoding"))
6820                 string_ncopy(opt_encoding, value, valuelen);
6822         else if (!strcmp(name, "core.editor"))
6823                 string_ncopy(opt_editor, value, valuelen);
6825         else if (!strcmp(name, "core.worktree"))
6826                 set_work_tree(value);
6828         else if (!prefixcmp(name, "tig.color."))
6829                 set_repo_config_option(name + 10, value, option_color_command);
6831         else if (!prefixcmp(name, "tig.bind."))
6832                 set_repo_config_option(name + 9, value, option_bind_command);
6834         else if (!prefixcmp(name, "tig."))
6835                 set_repo_config_option(name + 4, value, option_set_command);
6837         else if (*opt_head && !prefixcmp(name, "branch.") &&
6838                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6839                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6841         return OK;
6844 static int
6845 load_git_config(void)
6847         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6849         return run_io_load(config_list_argv, "=", read_repo_config_option);
6852 static int
6853 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6855         if (!opt_git_dir[0]) {
6856                 string_ncopy(opt_git_dir, name, namelen);
6858         } else if (opt_is_inside_work_tree == -1) {
6859                 /* This can be 3 different values depending on the
6860                  * version of git being used. If git-rev-parse does not
6861                  * understand --is-inside-work-tree it will simply echo
6862                  * the option else either "true" or "false" is printed.
6863                  * Default to true for the unknown case. */
6864                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6866         } else if (*name == '.') {
6867                 string_ncopy(opt_cdup, name, namelen);
6869         } else {
6870                 string_ncopy(opt_prefix, name, namelen);
6871         }
6873         return OK;
6876 static int
6877 load_repo_info(void)
6879         const char *head_argv[] = {
6880                 "git", "symbolic-ref", "HEAD", NULL
6881         };
6882         const char *rev_parse_argv[] = {
6883                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6884                         "--show-cdup", "--show-prefix", NULL
6885         };
6887         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6888                 chomp_string(opt_head);
6889                 if (!prefixcmp(opt_head, "refs/heads/")) {
6890                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6892                         memmove(opt_head, offset, strlen(offset) + 1);
6893                 }
6894         }
6896         return run_io_load(rev_parse_argv, "=", read_repo_info);
6900 /*
6901  * Main
6902  */
6904 static const char usage[] =
6905 "tig " TIG_VERSION " (" __DATE__ ")\n"
6906 "\n"
6907 "Usage: tig        [options] [revs] [--] [paths]\n"
6908 "   or: tig show   [options] [revs] [--] [paths]\n"
6909 "   or: tig blame  [rev] path\n"
6910 "   or: tig status\n"
6911 "   or: tig <      [git command output]\n"
6912 "\n"
6913 "Options:\n"
6914 "  -v, --version   Show version and exit\n"
6915 "  -h, --help      Show help message and exit";
6917 static void __NORETURN
6918 quit(int sig)
6920         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6921         if (cursed)
6922                 endwin();
6923         exit(0);
6926 static void __NORETURN
6927 die(const char *err, ...)
6929         va_list args;
6931         endwin();
6933         va_start(args, err);
6934         fputs("tig: ", stderr);
6935         vfprintf(stderr, err, args);
6936         fputs("\n", stderr);
6937         va_end(args);
6939         exit(1);
6942 static void
6943 warn(const char *msg, ...)
6945         va_list args;
6947         va_start(args, msg);
6948         fputs("tig warning: ", stderr);
6949         vfprintf(stderr, msg, args);
6950         fputs("\n", stderr);
6951         va_end(args);
6954 static enum request
6955 parse_options(int argc, const char *argv[])
6957         enum request request = REQ_VIEW_MAIN;
6958         const char *subcommand;
6959         bool seen_dashdash = FALSE;
6960         /* XXX: This is vulnerable to the user overriding options
6961          * required for the main view parser. */
6962         const char *custom_argv[SIZEOF_ARG] = {
6963                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6964                         "--topo-order", NULL
6965         };
6966         int i, j = 6;
6968         if (!isatty(STDIN_FILENO)) {
6969                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6970                 return REQ_VIEW_PAGER;
6971         }
6973         if (argc <= 1)
6974                 return REQ_NONE;
6976         subcommand = argv[1];
6977         if (!strcmp(subcommand, "status")) {
6978                 if (argc > 2)
6979                         warn("ignoring arguments after `%s'", subcommand);
6980                 return REQ_VIEW_STATUS;
6982         } else if (!strcmp(subcommand, "blame")) {
6983                 if (argc <= 2 || argc > 4)
6984                         die("invalid number of options to blame\n\n%s", usage);
6986                 i = 2;
6987                 if (argc == 4) {
6988                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6989                         i++;
6990                 }
6992                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6993                 return REQ_VIEW_BLAME;
6995         } else if (!strcmp(subcommand, "show")) {
6996                 request = REQ_VIEW_DIFF;
6998         } else {
6999                 subcommand = NULL;
7000         }
7002         if (subcommand) {
7003                 custom_argv[1] = subcommand;
7004                 j = 2;
7005         }
7007         for (i = 1 + !!subcommand; i < argc; i++) {
7008                 const char *opt = argv[i];
7010                 if (seen_dashdash || !strcmp(opt, "--")) {
7011                         seen_dashdash = TRUE;
7013                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7014                         printf("tig version %s\n", TIG_VERSION);
7015                         quit(0);
7017                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7018                         printf("%s\n", usage);
7019                         quit(0);
7020                 }
7022                 custom_argv[j++] = opt;
7023                 if (j >= ARRAY_SIZE(custom_argv))
7024                         die("command too long");
7025         }
7027         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
7028                 die("Failed to format arguments"); 
7030         return request;
7033 int
7034 main(int argc, const char *argv[])
7036         enum request request = parse_options(argc, argv);
7037         struct view *view;
7038         size_t i;
7040         signal(SIGINT, quit);
7041         signal(SIGPIPE, SIG_IGN);
7043         if (setlocale(LC_ALL, "")) {
7044                 char *codeset = nl_langinfo(CODESET);
7046                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7047         }
7049         if (load_repo_info() == ERR)
7050                 die("Failed to load repo info.");
7052         if (load_options() == ERR)
7053                 die("Failed to load user config.");
7055         if (load_git_config() == ERR)
7056                 die("Failed to load repo config.");
7058         /* Require a git repository unless when running in pager mode. */
7059         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7060                 die("Not a git repository");
7062         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7063                 opt_utf8 = FALSE;
7065         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7066                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7067                 if (opt_iconv == ICONV_NONE)
7068                         die("Failed to initialize character set conversion");
7069         }
7071         if (load_refs() == ERR)
7072                 die("Failed to load refs.");
7074         foreach_view (view, i)
7075                 argv_from_env(view->ops->argv, view->cmd_env);
7077         init_display();
7079         if (request != REQ_NONE)
7080                 open_view(NULL, request, OPEN_PREPARED);
7081         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7083         while (view_driver(display[current_view], request)) {
7084                 int key = get_input(0);
7086                 view = display[current_view];
7087                 request = get_keybinding(view->keymap, key);
7089                 /* Some low-level request handling. This keeps access to
7090                  * status_win restricted. */
7091                 switch (request) {
7092                 case REQ_PROMPT:
7093                 {
7094                         char *cmd = read_prompt(":");
7096                         if (cmd && isdigit(*cmd)) {
7097                                 int lineno = view->lineno + 1;
7099                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7100                                         select_view_line(view, lineno - 1);
7101                                         report("");
7102                                 } else {
7103                                         report("Unable to parse '%s' as a line number", cmd);
7104                                 }
7106                         } else if (cmd) {
7107                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7108                                 const char *argv[SIZEOF_ARG] = { "git" };
7109                                 int argc = 1;
7111                                 /* When running random commands, initially show the
7112                                  * command in the title. However, it maybe later be
7113                                  * overwritten if a commit line is selected. */
7114                                 string_ncopy(next->ref, cmd, strlen(cmd));
7116                                 if (!argv_from_string(argv, &argc, cmd)) {
7117                                         report("Too many arguments");
7118                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7119                                         report("Failed to format command");
7120                                 } else {
7121                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7122                                 }
7123                         }
7125                         request = REQ_NONE;
7126                         break;
7127                 }
7128                 case REQ_SEARCH:
7129                 case REQ_SEARCH_BACK:
7130                 {
7131                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7132                         char *search = read_prompt(prompt);
7134                         if (search)
7135                                 string_ncopy(opt_search, search, strlen(search));
7136                         else if (*opt_search)
7137                                 request = request == REQ_SEARCH ?
7138                                         REQ_FIND_NEXT :
7139                                         REQ_FIND_PREV;
7140                         else
7141                                 request = REQ_NONE;
7142                         break;
7143                 }
7144                 default:
7145                         break;
7146                 }
7147         }
7149         quit(0);
7151         return 0;