Code

725ace02579782ab55d4b511cfd89c8c8fc2f19d
[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                 if (view->digits <= 9)
2036                         fmt[1] = '0' + digits3;
2038                 if (string_format(number, fmt, lineno))
2039                         text = number;
2040         }
2041         if (text)
2042                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2043         else
2044                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2045         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2048 static bool
2049 draw_view_line(struct view *view, unsigned int lineno)
2051         struct line *line;
2052         bool selected = (view->offset + lineno == view->lineno);
2054         assert(view_is_displayed(view));
2056         if (view->offset + lineno >= view->lines)
2057                 return FALSE;
2059         line = &view->line[view->offset + lineno];
2061         wmove(view->win, lineno, 0);
2062         if (line->cleareol)
2063                 wclrtoeol(view->win);
2064         view->col = 0;
2065         view->curline = line;
2066         view->curtype = LINE_NONE;
2067         line->selected = FALSE;
2068         line->dirty = line->cleareol = 0;
2070         if (selected) {
2071                 set_view_attr(view, LINE_CURSOR);
2072                 line->selected = TRUE;
2073                 view->ops->select(view, line);
2074         }
2076         return view->ops->draw(view, line, lineno);
2079 static void
2080 redraw_view_dirty(struct view *view)
2082         bool dirty = FALSE;
2083         int lineno;
2085         for (lineno = 0; lineno < view->height; lineno++) {
2086                 if (view->offset + lineno >= view->lines)
2087                         break;
2088                 if (!view->line[view->offset + lineno].dirty)
2089                         continue;
2090                 dirty = TRUE;
2091                 if (!draw_view_line(view, lineno))
2092                         break;
2093         }
2095         if (!dirty)
2096                 return;
2097         wnoutrefresh(view->win);
2100 static void
2101 redraw_view_from(struct view *view, int lineno)
2103         assert(0 <= lineno && lineno < view->height);
2105         for (; lineno < view->height; lineno++) {
2106                 if (!draw_view_line(view, lineno))
2107                         break;
2108         }
2110         wnoutrefresh(view->win);
2113 static void
2114 redraw_view(struct view *view)
2116         werase(view->win);
2117         redraw_view_from(view, 0);
2121 static void
2122 update_view_title(struct view *view)
2124         char buf[SIZEOF_STR];
2125         char state[SIZEOF_STR];
2126         size_t bufpos = 0, statelen = 0;
2128         assert(view_is_displayed(view));
2130         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2131                 unsigned int view_lines = view->offset + view->height;
2132                 unsigned int lines = view->lines
2133                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2134                                    : 0;
2136                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2137                                    view->ops->type,
2138                                    view->lineno + 1,
2139                                    view->lines,
2140                                    lines);
2142         }
2144         if (view->pipe) {
2145                 time_t secs = time(NULL) - view->start_time;
2147                 /* Three git seconds are a long time ... */
2148                 if (secs > 2)
2149                         string_format_from(state, &statelen, " loading %lds", secs);
2150         }
2152         string_format_from(buf, &bufpos, "[%s]", view->name);
2153         if (*view->ref && bufpos < view->width) {
2154                 size_t refsize = strlen(view->ref);
2155                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2157                 if (minsize < view->width)
2158                         refsize = view->width - minsize + 7;
2159                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2160         }
2162         if (statelen && bufpos < view->width) {
2163                 string_format_from(buf, &bufpos, "%s", state);
2164         }
2166         if (view == display[current_view])
2167                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2168         else
2169                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2171         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2172         wclrtoeol(view->title);
2173         wnoutrefresh(view->title);
2176 static void
2177 resize_display(void)
2179         int offset, i;
2180         struct view *base = display[0];
2181         struct view *view = display[1] ? display[1] : display[0];
2183         /* Setup window dimensions */
2185         getmaxyx(stdscr, base->height, base->width);
2187         /* Make room for the status window. */
2188         base->height -= 1;
2190         if (view != base) {
2191                 /* Horizontal split. */
2192                 view->width   = base->width;
2193                 view->height  = SCALE_SPLIT_VIEW(base->height);
2194                 base->height -= view->height;
2196                 /* Make room for the title bar. */
2197                 view->height -= 1;
2198         }
2200         /* Make room for the title bar. */
2201         base->height -= 1;
2203         offset = 0;
2205         foreach_displayed_view (view, i) {
2206                 if (!view->win) {
2207                         view->win = newwin(view->height, 0, offset, 0);
2208                         if (!view->win)
2209                                 die("Failed to create %s view", view->name);
2211                         scrollok(view->win, FALSE);
2213                         view->title = newwin(1, 0, offset + view->height, 0);
2214                         if (!view->title)
2215                                 die("Failed to create title window");
2217                 } else {
2218                         wresize(view->win, view->height, view->width);
2219                         mvwin(view->win,   offset, 0);
2220                         mvwin(view->title, offset + view->height, 0);
2221                 }
2223                 offset += view->height + 1;
2224         }
2227 static void
2228 redraw_display(bool clear)
2230         struct view *view;
2231         int i;
2233         foreach_displayed_view (view, i) {
2234                 if (clear)
2235                         wclear(view->win);
2236                 redraw_view(view);
2237                 update_view_title(view);
2238         }
2241 static void
2242 toggle_view_option(bool *option, const char *help)
2244         *option = !*option;
2245         redraw_display(FALSE);
2246         report("%sabling %s", *option ? "En" : "Dis", help);
2249 static void
2250 maximize_view(struct view *view)
2252         memset(display, 0, sizeof(display));
2253         current_view = 0;
2254         display[current_view] = view;
2255         resize_display();
2256         redraw_display(FALSE);
2257         report("");
2261 /*
2262  * Navigation
2263  */
2265 static bool
2266 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2268         if (lineno >= view->lines)
2269                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2271         if (offset > lineno || offset + view->height <= lineno) {
2272                 unsigned long half = view->height / 2;
2274                 if (lineno > half)
2275                         offset = lineno - half;
2276                 else
2277                         offset = 0;
2278         }
2280         if (offset != view->offset || lineno != view->lineno) {
2281                 view->offset = offset;
2282                 view->lineno = lineno;
2283                 return TRUE;
2284         }
2286         return FALSE;
2289 static int
2290 apply_step(double step, int value)
2292         if (step >= 1)
2293                 return (int) step;
2294         value *= step + 0.01;
2295         return value ? value : 1;
2298 /* Scrolling backend */
2299 static void
2300 do_scroll_view(struct view *view, int lines)
2302         bool redraw_current_line = FALSE;
2304         /* The rendering expects the new offset. */
2305         view->offset += lines;
2307         assert(0 <= view->offset && view->offset < view->lines);
2308         assert(lines);
2310         /* Move current line into the view. */
2311         if (view->lineno < view->offset) {
2312                 view->lineno = view->offset;
2313                 redraw_current_line = TRUE;
2314         } else if (view->lineno >= view->offset + view->height) {
2315                 view->lineno = view->offset + view->height - 1;
2316                 redraw_current_line = TRUE;
2317         }
2319         assert(view->offset <= view->lineno && view->lineno < view->lines);
2321         /* Redraw the whole screen if scrolling is pointless. */
2322         if (view->height < ABS(lines)) {
2323                 redraw_view(view);
2325         } else {
2326                 int line = lines > 0 ? view->height - lines : 0;
2327                 int end = line + ABS(lines);
2329                 scrollok(view->win, TRUE);
2330                 wscrl(view->win, lines);
2331                 scrollok(view->win, FALSE);
2333                 while (line < end && draw_view_line(view, line))
2334                         line++;
2336                 if (redraw_current_line)
2337                         draw_view_line(view, view->lineno - view->offset);
2338                 wnoutrefresh(view->win);
2339         }
2341         view->has_scrolled = TRUE;
2342         report("");
2345 /* Scroll frontend */
2346 static void
2347 scroll_view(struct view *view, enum request request)
2349         int lines = 1;
2351         assert(view_is_displayed(view));
2353         switch (request) {
2354         case REQ_SCROLL_LEFT:
2355                 if (view->yoffset == 0) {
2356                         report("Cannot scroll beyond the first column");
2357                         return;
2358                 }
2359                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2360                         view->yoffset = 0;
2361                 else
2362                         view->yoffset -= apply_step(opt_hscroll, view->width);
2363                 redraw_view_from(view, 0);
2364                 report("");
2365                 return;
2366         case REQ_SCROLL_RIGHT:
2367                 view->yoffset += apply_step(opt_hscroll, view->width);
2368                 redraw_view(view);
2369                 report("");
2370                 return;
2371         case REQ_SCROLL_PAGE_DOWN:
2372                 lines = view->height;
2373         case REQ_SCROLL_LINE_DOWN:
2374                 if (view->offset + lines > view->lines)
2375                         lines = view->lines - view->offset;
2377                 if (lines == 0 || view->offset + view->height >= view->lines) {
2378                         report("Cannot scroll beyond the last line");
2379                         return;
2380                 }
2381                 break;
2383         case REQ_SCROLL_PAGE_UP:
2384                 lines = view->height;
2385         case REQ_SCROLL_LINE_UP:
2386                 if (lines > view->offset)
2387                         lines = view->offset;
2389                 if (lines == 0) {
2390                         report("Cannot scroll beyond the first line");
2391                         return;
2392                 }
2394                 lines = -lines;
2395                 break;
2397         default:
2398                 die("request %d not handled in switch", request);
2399         }
2401         do_scroll_view(view, lines);
2404 /* Cursor moving */
2405 static void
2406 move_view(struct view *view, enum request request)
2408         int scroll_steps = 0;
2409         int steps;
2411         switch (request) {
2412         case REQ_MOVE_FIRST_LINE:
2413                 steps = -view->lineno;
2414                 break;
2416         case REQ_MOVE_LAST_LINE:
2417                 steps = view->lines - view->lineno - 1;
2418                 break;
2420         case REQ_MOVE_PAGE_UP:
2421                 steps = view->height > view->lineno
2422                       ? -view->lineno : -view->height;
2423                 break;
2425         case REQ_MOVE_PAGE_DOWN:
2426                 steps = view->lineno + view->height >= view->lines
2427                       ? view->lines - view->lineno - 1 : view->height;
2428                 break;
2430         case REQ_MOVE_UP:
2431                 steps = -1;
2432                 break;
2434         case REQ_MOVE_DOWN:
2435                 steps = 1;
2436                 break;
2438         default:
2439                 die("request %d not handled in switch", request);
2440         }
2442         if (steps <= 0 && view->lineno == 0) {
2443                 report("Cannot move beyond the first line");
2444                 return;
2446         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2447                 report("Cannot move beyond the last line");
2448                 return;
2449         }
2451         /* Move the current line */
2452         view->lineno += steps;
2453         assert(0 <= view->lineno && view->lineno < view->lines);
2455         /* Check whether the view needs to be scrolled */
2456         if (view->lineno < view->offset ||
2457             view->lineno >= view->offset + view->height) {
2458                 scroll_steps = steps;
2459                 if (steps < 0 && -steps > view->offset) {
2460                         scroll_steps = -view->offset;
2462                 } else if (steps > 0) {
2463                         if (view->lineno == view->lines - 1 &&
2464                             view->lines > view->height) {
2465                                 scroll_steps = view->lines - view->offset - 1;
2466                                 if (scroll_steps >= view->height)
2467                                         scroll_steps -= view->height - 1;
2468                         }
2469                 }
2470         }
2472         if (!view_is_displayed(view)) {
2473                 view->offset += scroll_steps;
2474                 assert(0 <= view->offset && view->offset < view->lines);
2475                 view->ops->select(view, &view->line[view->lineno]);
2476                 return;
2477         }
2479         /* Repaint the old "current" line if we be scrolling */
2480         if (ABS(steps) < view->height)
2481                 draw_view_line(view, view->lineno - steps - view->offset);
2483         if (scroll_steps) {
2484                 do_scroll_view(view, scroll_steps);
2485                 return;
2486         }
2488         /* Draw the current line */
2489         draw_view_line(view, view->lineno - view->offset);
2491         wnoutrefresh(view->win);
2492         report("");
2496 /*
2497  * Searching
2498  */
2500 static void search_view(struct view *view, enum request request);
2502 static void
2503 select_view_line(struct view *view, unsigned long lineno)
2505         unsigned long old_lineno = view->lineno;
2506         unsigned long old_offset = view->offset;
2508         if (goto_view_line(view, view->offset, lineno)) {
2509                 if (view_is_displayed(view)) {
2510                         if (old_offset != view->offset) {
2511                                 redraw_view(view);
2512                         } else {
2513                                 draw_view_line(view, old_lineno - view->offset);
2514                                 draw_view_line(view, view->lineno - view->offset);
2515                                 wnoutrefresh(view->win);
2516                         }
2517                 } else {
2518                         view->ops->select(view, &view->line[view->lineno]);
2519                 }
2520         }
2523 static void
2524 find_next(struct view *view, enum request request)
2526         unsigned long lineno = view->lineno;
2527         int direction;
2529         if (!*view->grep) {
2530                 if (!*opt_search)
2531                         report("No previous search");
2532                 else
2533                         search_view(view, request);
2534                 return;
2535         }
2537         switch (request) {
2538         case REQ_SEARCH:
2539         case REQ_FIND_NEXT:
2540                 direction = 1;
2541                 break;
2543         case REQ_SEARCH_BACK:
2544         case REQ_FIND_PREV:
2545                 direction = -1;
2546                 break;
2548         default:
2549                 return;
2550         }
2552         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2553                 lineno += direction;
2555         /* Note, lineno is unsigned long so will wrap around in which case it
2556          * will become bigger than view->lines. */
2557         for (; lineno < view->lines; lineno += direction) {
2558                 if (view->ops->grep(view, &view->line[lineno])) {
2559                         select_view_line(view, lineno);
2560                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2561                         return;
2562                 }
2563         }
2565         report("No match found for '%s'", view->grep);
2568 static void
2569 search_view(struct view *view, enum request request)
2571         int regex_err;
2573         if (view->regex) {
2574                 regfree(view->regex);
2575                 *view->grep = 0;
2576         } else {
2577                 view->regex = calloc(1, sizeof(*view->regex));
2578                 if (!view->regex)
2579                         return;
2580         }
2582         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2583         if (regex_err != 0) {
2584                 char buf[SIZEOF_STR] = "unknown error";
2586                 regerror(regex_err, view->regex, buf, sizeof(buf));
2587                 report("Search failed: %s", buf);
2588                 return;
2589         }
2591         string_copy(view->grep, opt_search);
2593         find_next(view, request);
2596 /*
2597  * Incremental updating
2598  */
2600 static void
2601 reset_view(struct view *view)
2603         int i;
2605         for (i = 0; i < view->lines; i++)
2606                 free(view->line[i].data);
2607         free(view->line);
2609         view->p_offset = view->offset;
2610         view->p_yoffset = view->yoffset;
2611         view->p_lineno = view->lineno;
2613         view->line = NULL;
2614         view->offset = 0;
2615         view->yoffset = 0;
2616         view->lines  = 0;
2617         view->lineno = 0;
2618         view->line_alloc = 0;
2619         view->vid[0] = 0;
2620         view->update_secs = 0;
2623 static void
2624 free_argv(const char *argv[])
2626         int argc;
2628         for (argc = 0; argv[argc]; argc++)
2629                 free((void *) argv[argc]);
2632 static bool
2633 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2635         char buf[SIZEOF_STR];
2636         int argc;
2637         bool noreplace = flags == FORMAT_NONE;
2639         free_argv(dst_argv);
2641         for (argc = 0; src_argv[argc]; argc++) {
2642                 const char *arg = src_argv[argc];
2643                 size_t bufpos = 0;
2645                 while (arg) {
2646                         char *next = strstr(arg, "%(");
2647                         int len = next - arg;
2648                         const char *value;
2650                         if (!next || noreplace) {
2651                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2652                                         noreplace = TRUE;
2653                                 len = strlen(arg);
2654                                 value = "";
2656                         } else if (!prefixcmp(next, "%(directory)")) {
2657                                 value = opt_path;
2659                         } else if (!prefixcmp(next, "%(file)")) {
2660                                 value = opt_file;
2662                         } else if (!prefixcmp(next, "%(ref)")) {
2663                                 value = *opt_ref ? opt_ref : "HEAD";
2665                         } else if (!prefixcmp(next, "%(head)")) {
2666                                 value = ref_head;
2668                         } else if (!prefixcmp(next, "%(commit)")) {
2669                                 value = ref_commit;
2671                         } else if (!prefixcmp(next, "%(blob)")) {
2672                                 value = ref_blob;
2674                         } else {
2675                                 report("Unknown replacement: `%s`", next);
2676                                 return FALSE;
2677                         }
2679                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2680                                 return FALSE;
2682                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2683                 }
2685                 dst_argv[argc] = strdup(buf);
2686                 if (!dst_argv[argc])
2687                         break;
2688         }
2690         dst_argv[argc] = NULL;
2692         return src_argv[argc] == NULL;
2695 static bool
2696 restore_view_position(struct view *view)
2698         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2699                 return FALSE;
2701         /* Changing the view position cancels the restoring. */
2702         /* FIXME: Changing back to the first line is not detected. */
2703         if (view->offset != 0 || view->lineno != 0) {
2704                 view->p_restore = FALSE;
2705                 return FALSE;
2706         }
2708         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2709             view_is_displayed(view))
2710                 werase(view->win);
2712         view->yoffset = view->p_yoffset;
2713         view->p_restore = FALSE;
2715         return TRUE;
2718 static void
2719 end_update(struct view *view, bool force)
2721         if (!view->pipe)
2722                 return;
2723         while (!view->ops->read(view, NULL))
2724                 if (!force)
2725                         return;
2726         set_nonblocking_input(FALSE);
2727         if (force)
2728                 kill_io(view->pipe);
2729         done_io(view->pipe);
2730         view->pipe = NULL;
2733 static void
2734 setup_update(struct view *view, const char *vid)
2736         set_nonblocking_input(TRUE);
2737         reset_view(view);
2738         string_copy_rev(view->vid, vid);
2739         view->pipe = &view->io;
2740         view->start_time = time(NULL);
2743 static bool
2744 prepare_update(struct view *view, const char *argv[], const char *dir,
2745                enum format_flags flags)
2747         if (view->pipe)
2748                 end_update(view, TRUE);
2749         return init_io_rd(&view->io, argv, dir, flags);
2752 static bool
2753 prepare_update_file(struct view *view, const char *name)
2755         if (view->pipe)
2756                 end_update(view, TRUE);
2757         return io_open(&view->io, name);
2760 static bool
2761 begin_update(struct view *view, bool refresh)
2763         if (view->pipe)
2764                 end_update(view, TRUE);
2766         if (refresh) {
2767                 if (!start_io(&view->io))
2768                         return FALSE;
2770         } else {
2771                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2772                         opt_path[0] = 0;
2774                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2775                         return FALSE;
2777                 /* Put the current ref_* value to the view title ref
2778                  * member. This is needed by the blob view. Most other
2779                  * views sets it automatically after loading because the
2780                  * first line is a commit line. */
2781                 string_copy_rev(view->ref, view->id);
2782         }
2784         setup_update(view, view->id);
2786         return TRUE;
2789 #define ITEM_CHUNK_SIZE 256
2790 static void *
2791 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2793         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2794         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2796         if (mem == NULL || num_chunks != num_chunks_new) {
2797                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2798                 mem = realloc(mem, *size * item_size);
2799         }
2801         return mem;
2804 static struct line *
2805 realloc_lines(struct view *view, size_t line_size)
2807         size_t alloc = view->line_alloc;
2808         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2809                                          sizeof(*view->line));
2811         if (!tmp)
2812                 return NULL;
2814         view->line = tmp;
2815         view->line_alloc = alloc;
2816         return view->line;
2819 static bool
2820 update_view(struct view *view)
2822         char out_buffer[BUFSIZ * 2];
2823         char *line;
2824         /* Clear the view and redraw everything since the tree sorting
2825          * might have rearranged things. */
2826         bool redraw = view->lines == 0;
2827         bool can_read = TRUE;
2829         if (!view->pipe)
2830                 return TRUE;
2832         if (!io_can_read(view->pipe)) {
2833                 if (view->lines == 0 && view_is_displayed(view)) {
2834                         time_t secs = time(NULL) - view->start_time;
2836                         if (secs > 1 && secs > view->update_secs) {
2837                                 if (view->update_secs == 0)
2838                                         redraw_view(view);
2839                                 update_view_title(view);
2840                                 view->update_secs = secs;
2841                         }
2842                 }
2843                 return TRUE;
2844         }
2846         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2847                 if (opt_iconv != ICONV_NONE) {
2848                         ICONV_CONST char *inbuf = line;
2849                         size_t inlen = strlen(line) + 1;
2851                         char *outbuf = out_buffer;
2852                         size_t outlen = sizeof(out_buffer);
2854                         size_t ret;
2856                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2857                         if (ret != (size_t) -1)
2858                                 line = out_buffer;
2859                 }
2861                 if (!view->ops->read(view, line)) {
2862                         report("Allocation failure");
2863                         end_update(view, TRUE);
2864                         return FALSE;
2865                 }
2866         }
2868         {
2869                 unsigned long lines = view->lines;
2870                 int digits;
2872                 for (digits = 0; lines; digits++)
2873                         lines /= 10;
2875                 /* Keep the displayed view in sync with line number scaling. */
2876                 if (digits != view->digits) {
2877                         view->digits = digits;
2878                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2879                                 redraw = TRUE;
2880                 }
2881         }
2883         if (io_error(view->pipe)) {
2884                 report("Failed to read: %s", io_strerror(view->pipe));
2885                 end_update(view, TRUE);
2887         } else if (io_eof(view->pipe)) {
2888                 report("");
2889                 end_update(view, FALSE);
2890         }
2892         if (restore_view_position(view))
2893                 redraw = TRUE;
2895         if (!view_is_displayed(view))
2896                 return TRUE;
2898         if (redraw)
2899                 redraw_view_from(view, 0);
2900         else
2901                 redraw_view_dirty(view);
2903         /* Update the title _after_ the redraw so that if the redraw picks up a
2904          * commit reference in view->ref it'll be available here. */
2905         update_view_title(view);
2906         return TRUE;
2909 static struct line *
2910 add_line_data(struct view *view, void *data, enum line_type type)
2912         struct line *line;
2914         if (!realloc_lines(view, view->lines + 1))
2915                 return NULL;
2917         line = &view->line[view->lines++];
2918         memset(line, 0, sizeof(*line));
2919         line->type = type;
2920         line->data = data;
2921         line->dirty = 1;
2923         return line;
2926 static struct line *
2927 add_line_text(struct view *view, const char *text, enum line_type type)
2929         char *data = text ? strdup(text) : NULL;
2931         return data ? add_line_data(view, data, type) : NULL;
2934 static struct line *
2935 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2937         char buf[SIZEOF_STR];
2938         va_list args;
2940         va_start(args, fmt);
2941         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2942                 buf[0] = 0;
2943         va_end(args);
2945         return buf[0] ? add_line_text(view, buf, type) : NULL;
2948 /*
2949  * View opening
2950  */
2952 enum open_flags {
2953         OPEN_DEFAULT = 0,       /* Use default view switching. */
2954         OPEN_SPLIT = 1,         /* Split current view. */
2955         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2956         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2957         OPEN_PREPARED = 32,     /* Open already prepared command. */
2958 };
2960 static void
2961 open_view(struct view *prev, enum request request, enum open_flags flags)
2963         bool split = !!(flags & OPEN_SPLIT);
2964         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2965         bool nomaximize = !!(flags & OPEN_REFRESH);
2966         struct view *view = VIEW(request);
2967         int nviews = displayed_views();
2968         struct view *base_view = display[0];
2970         if (view == prev && nviews == 1 && !reload) {
2971                 report("Already in %s view", view->name);
2972                 return;
2973         }
2975         if (view->git_dir && !opt_git_dir[0]) {
2976                 report("The %s view is disabled in pager view", view->name);
2977                 return;
2978         }
2980         if (split) {
2981                 display[1] = view;
2982                 current_view = 1;
2983         } else if (!nomaximize) {
2984                 /* Maximize the current view. */
2985                 memset(display, 0, sizeof(display));
2986                 current_view = 0;
2987                 display[current_view] = view;
2988         }
2990         /* Resize the view when switching between split- and full-screen,
2991          * or when switching between two different full-screen views. */
2992         if (nviews != displayed_views() ||
2993             (nviews == 1 && base_view != display[0]))
2994                 resize_display();
2996         if (view->ops->open) {
2997                 if (view->pipe)
2998                         end_update(view, TRUE);
2999                 if (!view->ops->open(view)) {
3000                         report("Failed to load %s view", view->name);
3001                         return;
3002                 }
3003                 restore_view_position(view);
3005         } else if ((reload || strcmp(view->vid, view->id)) &&
3006                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3007                 report("Failed to load %s view", view->name);
3008                 return;
3009         }
3011         if (split && prev->lineno - prev->offset >= prev->height) {
3012                 /* Take the title line into account. */
3013                 int lines = prev->lineno - prev->offset - prev->height + 1;
3015                 /* Scroll the view that was split if the current line is
3016                  * outside the new limited view. */
3017                 do_scroll_view(prev, lines);
3018         }
3020         if (prev && view != prev) {
3021                 if (split) {
3022                         /* "Blur" the previous view. */
3023                         update_view_title(prev);
3024                 }
3026                 view->parent = prev;
3027         }
3029         if (view->pipe && view->lines == 0) {
3030                 /* Clear the old view and let the incremental updating refill
3031                  * the screen. */
3032                 werase(view->win);
3033                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3034                 report("");
3035         } else if (view_is_displayed(view)) {
3036                 redraw_view(view);
3037                 report("");
3038         }
3041 static void
3042 open_external_viewer(const char *argv[], const char *dir)
3044         def_prog_mode();           /* save current tty modes */
3045         endwin();                  /* restore original tty modes */
3046         run_io_fg(argv, dir);
3047         fprintf(stderr, "Press Enter to continue");
3048         getc(opt_tty);
3049         reset_prog_mode();
3050         redraw_display(TRUE);
3053 static void
3054 open_mergetool(const char *file)
3056         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3058         open_external_viewer(mergetool_argv, opt_cdup);
3061 static void
3062 open_editor(bool from_root, const char *file)
3064         const char *editor_argv[] = { "vi", file, NULL };
3065         const char *editor;
3067         editor = getenv("GIT_EDITOR");
3068         if (!editor && *opt_editor)
3069                 editor = opt_editor;
3070         if (!editor)
3071                 editor = getenv("VISUAL");
3072         if (!editor)
3073                 editor = getenv("EDITOR");
3074         if (!editor)
3075                 editor = "vi";
3077         editor_argv[0] = editor;
3078         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3081 static void
3082 open_run_request(enum request request)
3084         struct run_request *req = get_run_request(request);
3085         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3087         if (!req) {
3088                 report("Unknown run request");
3089                 return;
3090         }
3092         if (format_argv(argv, req->argv, FORMAT_ALL))
3093                 open_external_viewer(argv, NULL);
3094         free_argv(argv);
3097 /*
3098  * User request switch noodle
3099  */
3101 static int
3102 view_driver(struct view *view, enum request request)
3104         int i;
3106         if (request == REQ_NONE) {
3107                 doupdate();
3108                 return TRUE;
3109         }
3111         if (request > REQ_NONE) {
3112                 open_run_request(request);
3113                 /* FIXME: When all views can refresh always do this. */
3114                 if (view == VIEW(REQ_VIEW_STATUS) ||
3115                     view == VIEW(REQ_VIEW_MAIN) ||
3116                     view == VIEW(REQ_VIEW_LOG) ||
3117                     view == VIEW(REQ_VIEW_STAGE))
3118                         request = REQ_REFRESH;
3119                 else
3120                         return TRUE;
3121         }
3123         if (view && view->lines) {
3124                 request = view->ops->request(view, request, &view->line[view->lineno]);
3125                 if (request == REQ_NONE)
3126                         return TRUE;
3127         }
3129         switch (request) {
3130         case REQ_MOVE_UP:
3131         case REQ_MOVE_DOWN:
3132         case REQ_MOVE_PAGE_UP:
3133         case REQ_MOVE_PAGE_DOWN:
3134         case REQ_MOVE_FIRST_LINE:
3135         case REQ_MOVE_LAST_LINE:
3136                 move_view(view, request);
3137                 break;
3139         case REQ_SCROLL_LEFT:
3140         case REQ_SCROLL_RIGHT:
3141         case REQ_SCROLL_LINE_DOWN:
3142         case REQ_SCROLL_LINE_UP:
3143         case REQ_SCROLL_PAGE_DOWN:
3144         case REQ_SCROLL_PAGE_UP:
3145                 scroll_view(view, request);
3146                 break;
3148         case REQ_VIEW_BLAME:
3149                 if (!opt_file[0]) {
3150                         report("No file chosen, press %s to open tree view",
3151                                get_key(REQ_VIEW_TREE));
3152                         break;
3153                 }
3154                 open_view(view, request, OPEN_DEFAULT);
3155                 break;
3157         case REQ_VIEW_BLOB:
3158                 if (!ref_blob[0]) {
3159                         report("No file chosen, press %s to open tree view",
3160                                get_key(REQ_VIEW_TREE));
3161                         break;
3162                 }
3163                 open_view(view, request, OPEN_DEFAULT);
3164                 break;
3166         case REQ_VIEW_PAGER:
3167                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3168                         report("No pager content, press %s to run command from prompt",
3169                                get_key(REQ_PROMPT));
3170                         break;
3171                 }
3172                 open_view(view, request, OPEN_DEFAULT);
3173                 break;
3175         case REQ_VIEW_STAGE:
3176                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3177                         report("No stage content, press %s to open the status view and choose file",
3178                                get_key(REQ_VIEW_STATUS));
3179                         break;
3180                 }
3181                 open_view(view, request, OPEN_DEFAULT);
3182                 break;
3184         case REQ_VIEW_STATUS:
3185                 if (opt_is_inside_work_tree == FALSE) {
3186                         report("The status view requires a working tree");
3187                         break;
3188                 }
3189                 open_view(view, request, OPEN_DEFAULT);
3190                 break;
3192         case REQ_VIEW_MAIN:
3193         case REQ_VIEW_DIFF:
3194         case REQ_VIEW_LOG:
3195         case REQ_VIEW_TREE:
3196         case REQ_VIEW_HELP:
3197                 open_view(view, request, OPEN_DEFAULT);
3198                 break;
3200         case REQ_NEXT:
3201         case REQ_PREVIOUS:
3202                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3204                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3205                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3206                    (view == VIEW(REQ_VIEW_DIFF) &&
3207                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3208                    (view == VIEW(REQ_VIEW_STAGE) &&
3209                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3210                    (view == VIEW(REQ_VIEW_BLOB) &&
3211                      view->parent == VIEW(REQ_VIEW_TREE))) {
3212                         int line;
3214                         view = view->parent;
3215                         line = view->lineno;
3216                         move_view(view, request);
3217                         if (view_is_displayed(view))
3218                                 update_view_title(view);
3219                         if (line != view->lineno)
3220                                 view->ops->request(view, REQ_ENTER,
3221                                                    &view->line[view->lineno]);
3223                 } else {
3224                         move_view(view, request);
3225                 }
3226                 break;
3228         case REQ_VIEW_NEXT:
3229         {
3230                 int nviews = displayed_views();
3231                 int next_view = (current_view + 1) % nviews;
3233                 if (next_view == current_view) {
3234                         report("Only one view is displayed");
3235                         break;
3236                 }
3238                 current_view = next_view;
3239                 /* Blur out the title of the previous view. */
3240                 update_view_title(view);
3241                 report("");
3242                 break;
3243         }
3244         case REQ_REFRESH:
3245                 report("Refreshing is not yet supported for the %s view", view->name);
3246                 break;
3248         case REQ_MAXIMIZE:
3249                 if (displayed_views() == 2)
3250                         maximize_view(view);
3251                 break;
3253         case REQ_TOGGLE_LINENO:
3254                 toggle_view_option(&opt_line_number, "line numbers");
3255                 break;
3257         case REQ_TOGGLE_DATE:
3258                 toggle_view_option(&opt_date, "date display");
3259                 break;
3261         case REQ_TOGGLE_AUTHOR:
3262                 toggle_view_option(&opt_author, "author display");
3263                 break;
3265         case REQ_TOGGLE_REV_GRAPH:
3266                 toggle_view_option(&opt_rev_graph, "revision graph display");
3267                 break;
3269         case REQ_TOGGLE_REFS:
3270                 toggle_view_option(&opt_show_refs, "reference display");
3271                 break;
3273         case REQ_SEARCH:
3274         case REQ_SEARCH_BACK:
3275                 search_view(view, request);
3276                 break;
3278         case REQ_FIND_NEXT:
3279         case REQ_FIND_PREV:
3280                 find_next(view, request);
3281                 break;
3283         case REQ_STOP_LOADING:
3284                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3285                         view = &views[i];
3286                         if (view->pipe)
3287                                 report("Stopped loading the %s view", view->name),
3288                         end_update(view, TRUE);
3289                 }
3290                 break;
3292         case REQ_SHOW_VERSION:
3293                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3294                 return TRUE;
3296         case REQ_SCREEN_REDRAW:
3297                 redraw_display(TRUE);
3298                 break;
3300         case REQ_EDIT:
3301                 report("Nothing to edit");
3302                 break;
3304         case REQ_ENTER:
3305                 report("Nothing to enter");
3306                 break;
3308         case REQ_VIEW_CLOSE:
3309                 /* XXX: Mark closed views by letting view->parent point to the
3310                  * view itself. Parents to closed view should never be
3311                  * followed. */
3312                 if (view->parent &&
3313                     view->parent->parent != view->parent) {
3314                         maximize_view(view->parent);
3315                         view->parent = view;
3316                         break;
3317                 }
3318                 /* Fall-through */
3319         case REQ_QUIT:
3320                 return FALSE;
3322         default:
3323                 report("Unknown key, press 'h' for help");
3324                 return TRUE;
3325         }
3327         return TRUE;
3331 /*
3332  * View backend utilities
3333  */
3335 static void
3336 parse_timezone(time_t *time, const char *zone)
3338         long tz;
3340         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3341         tz += ('0' - zone[2]) * 60 * 60;
3342         tz += ('0' - zone[3]) * 60;
3343         tz += ('0' - zone[4]);
3345         if (zone[0] == '-')
3346                 tz = -tz;
3348         *time -= tz;
3351 /* Parse author lines where the name may be empty:
3352  *      author  <email@address.tld> 1138474660 +0100
3353  */
3354 static void
3355 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3357         char *nameend = strchr(ident, '<');
3358         char *emailend = strchr(ident, '>');
3360         if (nameend && emailend)
3361                 *nameend = *emailend = 0;
3362         ident = chomp_string(ident);
3363         if (!*ident) {
3364                 if (nameend)
3365                         ident = chomp_string(nameend + 1);
3366                 if (!*ident)
3367                         ident = "Unknown";
3368         }
3370         string_ncopy_do(author, authorsize, ident, strlen(ident));
3372         /* Parse epoch and timezone */
3373         if (emailend && emailend[1] == ' ') {
3374                 char *secs = emailend + 2;
3375                 char *zone = strchr(secs, ' ');
3376                 time_t time = (time_t) atol(secs);
3378                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3379                         parse_timezone(&time, zone + 1);
3381                 gmtime_r(&time, tm);
3382         }
3385 static enum input_status
3386 select_commit_parent_handler(void *data, char *buf, int c)
3388         size_t parents = *(size_t *) data;
3389         int parent = 0;
3391         if (!isdigit(c))
3392                 return INPUT_SKIP;
3394         if (*buf)
3395                 parent = atoi(buf) * 10;
3396         parent += c - '0';
3398         if (parent > parents)
3399                 return INPUT_SKIP;
3400         return INPUT_OK;
3403 static bool
3404 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3406         char buf[SIZEOF_STR * 4];
3407         const char *revlist_argv[] = {
3408                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3409         };
3410         int parents;
3412         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3413             !*chomp_string(buf) ||
3414             (parents = (strlen(buf) / 40) - 1) < 0) {
3415                 report("Failed to get parent information");
3416                 return FALSE;
3418         } else if (parents == 0) {
3419                 if (path)
3420                         report("Path '%s' does not exist in the parent", path);
3421                 else
3422                         report("The selected commit has no parents");
3423                 return FALSE;
3424         }
3426         if (parents > 1) {
3427                 char prompt[SIZEOF_STR];
3428                 char *result;
3430                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3431                         return FALSE;
3432                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3433                 if (!result)
3434                         return FALSE;
3435                 parents = atoi(result);
3436         }
3438         string_copy_rev(rev, &buf[41 * parents]);
3439         return TRUE;
3442 /*
3443  * Pager backend
3444  */
3446 static bool
3447 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3449         char text[SIZEOF_STR];
3451         if (opt_line_number && draw_lineno(view, lineno))
3452                 return TRUE;
3454         string_expand(text, sizeof(text), line->data, opt_tab_size);
3455         draw_text(view, line->type, text, TRUE);
3456         return TRUE;
3459 static bool
3460 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3462         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3463         char refbuf[SIZEOF_STR];
3464         char *ref = NULL;
3466         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3467                 ref = chomp_string(refbuf);
3469         if (!ref || !*ref)
3470                 return TRUE;
3472         /* This is the only fatal call, since it can "corrupt" the buffer. */
3473         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3474                 return FALSE;
3476         return TRUE;
3479 static void
3480 add_pager_refs(struct view *view, struct line *line)
3482         char buf[SIZEOF_STR];
3483         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3484         struct ref **refs;
3485         size_t bufpos = 0, refpos = 0;
3486         const char *sep = "Refs: ";
3487         bool is_tag = FALSE;
3489         assert(line->type == LINE_COMMIT);
3491         refs = get_refs(commit_id);
3492         if (!refs) {
3493                 if (view == VIEW(REQ_VIEW_DIFF))
3494                         goto try_add_describe_ref;
3495                 return;
3496         }
3498         do {
3499                 struct ref *ref = refs[refpos];
3500                 const char *fmt = ref->tag    ? "%s[%s]" :
3501                                   ref->remote ? "%s<%s>" : "%s%s";
3503                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3504                         return;
3505                 sep = ", ";
3506                 if (ref->tag)
3507                         is_tag = TRUE;
3508         } while (refs[refpos++]->next);
3510         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3511 try_add_describe_ref:
3512                 /* Add <tag>-g<commit_id> "fake" reference. */
3513                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3514                         return;
3515         }
3517         if (bufpos == 0)
3518                 return;
3520         add_line_text(view, buf, LINE_PP_REFS);
3523 static bool
3524 pager_read(struct view *view, char *data)
3526         struct line *line;
3528         if (!data)
3529                 return TRUE;
3531         line = add_line_text(view, data, get_line_type(data));
3532         if (!line)
3533                 return FALSE;
3535         if (line->type == LINE_COMMIT &&
3536             (view == VIEW(REQ_VIEW_DIFF) ||
3537              view == VIEW(REQ_VIEW_LOG)))
3538                 add_pager_refs(view, line);
3540         return TRUE;
3543 static enum request
3544 pager_request(struct view *view, enum request request, struct line *line)
3546         int split = 0;
3548         if (request != REQ_ENTER)
3549                 return request;
3551         if (line->type == LINE_COMMIT &&
3552            (view == VIEW(REQ_VIEW_LOG) ||
3553             view == VIEW(REQ_VIEW_PAGER))) {
3554                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3555                 split = 1;
3556         }
3558         /* Always scroll the view even if it was split. That way
3559          * you can use Enter to scroll through the log view and
3560          * split open each commit diff. */
3561         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3563         /* FIXME: A minor workaround. Scrolling the view will call report("")
3564          * but if we are scrolling a non-current view this won't properly
3565          * update the view title. */
3566         if (split)
3567                 update_view_title(view);
3569         return REQ_NONE;
3572 static bool
3573 pager_grep(struct view *view, struct line *line)
3575         regmatch_t pmatch;
3576         char *text = line->data;
3578         if (!*text)
3579                 return FALSE;
3581         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3582                 return FALSE;
3584         return TRUE;
3587 static void
3588 pager_select(struct view *view, struct line *line)
3590         if (line->type == LINE_COMMIT) {
3591                 char *text = (char *)line->data + STRING_SIZE("commit ");
3593                 if (view != VIEW(REQ_VIEW_PAGER))
3594                         string_copy_rev(view->ref, text);
3595                 string_copy_rev(ref_commit, text);
3596         }
3599 static struct view_ops pager_ops = {
3600         "line",
3601         NULL,
3602         NULL,
3603         pager_read,
3604         pager_draw,
3605         pager_request,
3606         pager_grep,
3607         pager_select,
3608 };
3610 static const char *log_argv[SIZEOF_ARG] = {
3611         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3612 };
3614 static enum request
3615 log_request(struct view *view, enum request request, struct line *line)
3617         switch (request) {
3618         case REQ_REFRESH:
3619                 load_refs();
3620                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3621                 return REQ_NONE;
3622         default:
3623                 return pager_request(view, request, line);
3624         }
3627 static struct view_ops log_ops = {
3628         "line",
3629         log_argv,
3630         NULL,
3631         pager_read,
3632         pager_draw,
3633         log_request,
3634         pager_grep,
3635         pager_select,
3636 };
3638 static const char *diff_argv[SIZEOF_ARG] = {
3639         "git", "show", "--pretty=fuller", "--no-color", "--root",
3640                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3641 };
3643 static struct view_ops diff_ops = {
3644         "line",
3645         diff_argv,
3646         NULL,
3647         pager_read,
3648         pager_draw,
3649         pager_request,
3650         pager_grep,
3651         pager_select,
3652 };
3654 /*
3655  * Help backend
3656  */
3658 static bool
3659 help_open(struct view *view)
3661         char buf[SIZEOF_STR];
3662         size_t bufpos;
3663         int i;
3665         if (view->lines > 0)
3666                 return TRUE;
3668         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3670         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3671                 const char *key;
3673                 if (req_info[i].request == REQ_NONE)
3674                         continue;
3676                 if (!req_info[i].request) {
3677                         add_line_text(view, "", LINE_DEFAULT);
3678                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3679                         continue;
3680                 }
3682                 key = get_key(req_info[i].request);
3683                 if (!*key)
3684                         key = "(no key defined)";
3686                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3687                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3688                         if (buf[bufpos] == '_')
3689                                 buf[bufpos] = '-';
3690                 }
3692                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3693                                 key, buf, req_info[i].help);
3694         }
3696         if (run_requests) {
3697                 add_line_text(view, "", LINE_DEFAULT);
3698                 add_line_text(view, "External commands:", LINE_DEFAULT);
3699         }
3701         for (i = 0; i < run_requests; i++) {
3702                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3703                 const char *key;
3704                 int argc;
3706                 if (!req)
3707                         continue;
3709                 key = get_key_name(req->key);
3710                 if (!*key)
3711                         key = "(no key defined)";
3713                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3714                         if (!string_format_from(buf, &bufpos, "%s%s",
3715                                                 argc ? " " : "", req->argv[argc]))
3716                                 return REQ_NONE;
3718                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3719                                 keymap_table[req->keymap].name, key, buf);
3720         }
3722         return TRUE;
3725 static struct view_ops help_ops = {
3726         "line",
3727         NULL,
3728         help_open,
3729         NULL,
3730         pager_draw,
3731         pager_request,
3732         pager_grep,
3733         pager_select,
3734 };
3737 /*
3738  * Tree backend
3739  */
3741 struct tree_stack_entry {
3742         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3743         unsigned long lineno;           /* Line number to restore */
3744         char *name;                     /* Position of name in opt_path */
3745 };
3747 /* The top of the path stack. */
3748 static struct tree_stack_entry *tree_stack = NULL;
3749 unsigned long tree_lineno = 0;
3751 static void
3752 pop_tree_stack_entry(void)
3754         struct tree_stack_entry *entry = tree_stack;
3756         tree_lineno = entry->lineno;
3757         entry->name[0] = 0;
3758         tree_stack = entry->prev;
3759         free(entry);
3762 static void
3763 push_tree_stack_entry(const char *name, unsigned long lineno)
3765         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3766         size_t pathlen = strlen(opt_path);
3768         if (!entry)
3769                 return;
3771         entry->prev = tree_stack;
3772         entry->name = opt_path + pathlen;
3773         tree_stack = entry;
3775         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3776                 pop_tree_stack_entry();
3777                 return;
3778         }
3780         /* Move the current line to the first tree entry. */
3781         tree_lineno = 1;
3782         entry->lineno = lineno;
3785 /* Parse output from git-ls-tree(1):
3786  *
3787  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3788  */
3790 #define SIZEOF_TREE_ATTR \
3791         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3793 #define SIZEOF_TREE_MODE \
3794         STRING_SIZE("100644 ")
3796 #define TREE_ID_OFFSET \
3797         STRING_SIZE("100644 blob ")
3799 struct tree_entry {
3800         char id[SIZEOF_REV];
3801         mode_t mode;
3802         struct tm time;                 /* Date from the author ident. */
3803         char author[75];                /* Author of the commit. */
3804         char name[1];
3805 };
3807 static const char *
3808 tree_path(struct line *line)
3810         return ((struct tree_entry *) line->data)->name;
3814 static int
3815 tree_compare_entry(struct line *line1, struct line *line2)
3817         if (line1->type != line2->type)
3818                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3819         return strcmp(tree_path(line1), tree_path(line2));
3822 static struct line *
3823 tree_entry(struct view *view, enum line_type type, const char *path,
3824            const char *mode, const char *id)
3826         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3827         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3829         if (!entry || !line) {
3830                 free(entry);
3831                 return NULL;
3832         }
3834         strncpy(entry->name, path, strlen(path));
3835         if (mode)
3836                 entry->mode = strtoul(mode, NULL, 8);
3837         if (id)
3838                 string_copy_rev(entry->id, id);
3840         return line;
3843 static bool
3844 tree_read_date(struct view *view, char *text, bool *read_date)
3846         static char author_name[SIZEOF_STR];
3847         static struct tm author_time;
3849         if (!text && *read_date) {
3850                 *read_date = FALSE;
3851                 return TRUE;
3853         } else if (!text) {
3854                 char *path = *opt_path ? opt_path : ".";
3855                 /* Find next entry to process */
3856                 const char *log_file[] = {
3857                         "git", "log", "--no-color", "--pretty=raw",
3858                                 "--cc", "--raw", view->id, "--", path, NULL
3859                 };
3860                 struct io io = {};
3862                 if (!view->lines) {
3863                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3864                         report("Tree is empty");
3865                         return TRUE;
3866                 }
3868                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3869                         report("Failed to load tree data");
3870                         return TRUE;
3871                 }
3873                 done_io(view->pipe);
3874                 view->io = io;
3875                 *read_date = TRUE;
3876                 return FALSE;
3878         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3879                 parse_author_line(text + STRING_SIZE("author "),
3880                                   author_name, sizeof(author_name), &author_time);
3882         } else if (*text == ':') {
3883                 char *pos;
3884                 size_t annotated = 1;
3885                 size_t i;
3887                 pos = strchr(text, '\t');
3888                 if (!pos)
3889                         return TRUE;
3890                 text = pos + 1;
3891                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3892                         text += strlen(opt_prefix);
3893                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3894                         text += strlen(opt_path);
3895                 pos = strchr(text, '/');
3896                 if (pos)
3897                         *pos = 0;
3899                 for (i = 1; i < view->lines; i++) {
3900                         struct line *line = &view->line[i];
3901                         struct tree_entry *entry = line->data;
3903                         annotated += !!*entry->author;
3904                         if (*entry->author || strcmp(entry->name, text))
3905                                 continue;
3907                         string_copy(entry->author, author_name);
3908                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3909                         line->dirty = 1;
3910                         break;
3911                 }
3913                 if (annotated == view->lines)
3914                         kill_io(view->pipe);
3915         }
3916         return TRUE;
3919 static bool
3920 tree_read(struct view *view, char *text)
3922         static bool read_date = FALSE;
3923         struct tree_entry *data;
3924         struct line *entry, *line;
3925         enum line_type type;
3926         size_t textlen = text ? strlen(text) : 0;
3927         char *path = text + SIZEOF_TREE_ATTR;
3929         if (read_date || !text)
3930                 return tree_read_date(view, text, &read_date);
3932         if (textlen <= SIZEOF_TREE_ATTR)
3933                 return FALSE;
3934         if (view->lines == 0 &&
3935             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3936                 return FALSE;
3938         /* Strip the path part ... */
3939         if (*opt_path) {
3940                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3941                 size_t striplen = strlen(opt_path);
3943                 if (pathlen > striplen)
3944                         memmove(path, path + striplen,
3945                                 pathlen - striplen + 1);
3947                 /* Insert "link" to parent directory. */
3948                 if (view->lines == 1 &&
3949                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3950                         return FALSE;
3951         }
3953         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3954         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3955         if (!entry)
3956                 return FALSE;
3957         data = entry->data;
3959         /* Skip "Directory ..." and ".." line. */
3960         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3961                 if (tree_compare_entry(line, entry) <= 0)
3962                         continue;
3964                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3966                 line->data = data;
3967                 line->type = type;
3968                 for (; line <= entry; line++)
3969                         line->dirty = line->cleareol = 1;
3970                 return TRUE;
3971         }
3973         if (tree_lineno > view->lineno) {
3974                 view->lineno = tree_lineno;
3975                 tree_lineno = 0;
3976         }
3978         return TRUE;
3981 static bool
3982 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3984         struct tree_entry *entry = line->data;
3986         if (line->type == LINE_TREE_HEAD) {
3987                 if (draw_text(view, line->type, "Directory path /", TRUE))
3988                         return TRUE;
3989         } else {
3990                 if (draw_mode(view, entry->mode))
3991                         return TRUE;
3993                 if (opt_author && draw_author(view, entry->author))
3994                         return TRUE;
3996                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3997                         return TRUE;
3998         }
3999         if (draw_text(view, line->type, entry->name, TRUE))
4000                 return TRUE;
4001         return TRUE;
4004 static void
4005 open_blob_editor()
4007         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4008         int fd = mkstemp(file);
4010         if (fd == -1)
4011                 report("Failed to create temporary file");
4012         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4013                 report("Failed to save blob data to file");
4014         else
4015                 open_editor(FALSE, file);
4016         if (fd != -1)
4017                 unlink(file);
4020 static enum request
4021 tree_request(struct view *view, enum request request, struct line *line)
4023         enum open_flags flags;
4025         switch (request) {
4026         case REQ_VIEW_BLAME:
4027                 if (line->type != LINE_TREE_FILE) {
4028                         report("Blame only supported for files");
4029                         return REQ_NONE;
4030                 }
4032                 string_copy(opt_ref, view->vid);
4033                 return request;
4035         case REQ_EDIT:
4036                 if (line->type != LINE_TREE_FILE) {
4037                         report("Edit only supported for files");
4038                 } else if (!is_head_commit(view->vid)) {
4039                         open_blob_editor();
4040                 } else {
4041                         open_editor(TRUE, opt_file);
4042                 }
4043                 return REQ_NONE;
4045         case REQ_PARENT:
4046                 if (!*opt_path) {
4047                         /* quit view if at top of tree */
4048                         return REQ_VIEW_CLOSE;
4049                 }
4050                 /* fake 'cd  ..' */
4051                 line = &view->line[1];
4052                 break;
4054         case REQ_ENTER:
4055                 break;
4057         default:
4058                 return request;
4059         }
4061         /* Cleanup the stack if the tree view is at a different tree. */
4062         while (!*opt_path && tree_stack)
4063                 pop_tree_stack_entry();
4065         switch (line->type) {
4066         case LINE_TREE_DIR:
4067                 /* Depending on whether it is a subdirectory or parent link
4068                  * mangle the path buffer. */
4069                 if (line == &view->line[1] && *opt_path) {
4070                         pop_tree_stack_entry();
4072                 } else {
4073                         const char *basename = tree_path(line);
4075                         push_tree_stack_entry(basename, view->lineno);
4076                 }
4078                 /* Trees and subtrees share the same ID, so they are not not
4079                  * unique like blobs. */
4080                 flags = OPEN_RELOAD;
4081                 request = REQ_VIEW_TREE;
4082                 break;
4084         case LINE_TREE_FILE:
4085                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4086                 request = REQ_VIEW_BLOB;
4087                 break;
4089         default:
4090                 return REQ_NONE;
4091         }
4093         open_view(view, request, flags);
4094         if (request == REQ_VIEW_TREE)
4095                 view->lineno = tree_lineno;
4097         return REQ_NONE;
4100 static void
4101 tree_select(struct view *view, struct line *line)
4103         struct tree_entry *entry = line->data;
4105         if (line->type == LINE_TREE_FILE) {
4106                 string_copy_rev(ref_blob, entry->id);
4107                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4109         } else if (line->type != LINE_TREE_DIR) {
4110                 return;
4111         }
4113         string_copy_rev(view->ref, entry->id);
4116 static const char *tree_argv[SIZEOF_ARG] = {
4117         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4118 };
4120 static struct view_ops tree_ops = {
4121         "file",
4122         tree_argv,
4123         NULL,
4124         tree_read,
4125         tree_draw,
4126         tree_request,
4127         pager_grep,
4128         tree_select,
4129 };
4131 static bool
4132 blob_read(struct view *view, char *line)
4134         if (!line)
4135                 return TRUE;
4136         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4139 static enum request
4140 blob_request(struct view *view, enum request request, struct line *line)
4142         switch (request) {
4143         case REQ_EDIT:
4144                 open_blob_editor();
4145                 return REQ_NONE;
4146         default:
4147                 return pager_request(view, request, line);
4148         }
4151 static const char *blob_argv[SIZEOF_ARG] = {
4152         "git", "cat-file", "blob", "%(blob)", NULL
4153 };
4155 static struct view_ops blob_ops = {
4156         "line",
4157         blob_argv,
4158         NULL,
4159         blob_read,
4160         pager_draw,
4161         blob_request,
4162         pager_grep,
4163         pager_select,
4164 };
4166 /*
4167  * Blame backend
4168  *
4169  * Loading the blame view is a two phase job:
4170  *
4171  *  1. File content is read either using opt_file from the
4172  *     filesystem or using git-cat-file.
4173  *  2. Then blame information is incrementally added by
4174  *     reading output from git-blame.
4175  */
4177 static const char *blame_head_argv[] = {
4178         "git", "blame", "--incremental", "--", "%(file)", NULL
4179 };
4181 static const char *blame_ref_argv[] = {
4182         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4183 };
4185 static const char *blame_cat_file_argv[] = {
4186         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4187 };
4189 struct blame_commit {
4190         char id[SIZEOF_REV];            /* SHA1 ID. */
4191         char title[128];                /* First line of the commit message. */
4192         char author[75];                /* Author of the commit. */
4193         struct tm time;                 /* Date from the author ident. */
4194         char filename[128];             /* Name of file. */
4195         bool has_previous;              /* Was a "previous" line detected. */
4196 };
4198 struct blame {
4199         struct blame_commit *commit;
4200         unsigned long lineno;
4201         char text[1];
4202 };
4204 static bool
4205 blame_open(struct view *view)
4207         if (*opt_ref || !io_open(&view->io, opt_file)) {
4208                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4209                         return FALSE;
4210         }
4212         setup_update(view, opt_file);
4213         string_format(view->ref, "%s ...", opt_file);
4215         return TRUE;
4218 static struct blame_commit *
4219 get_blame_commit(struct view *view, const char *id)
4221         size_t i;
4223         for (i = 0; i < view->lines; i++) {
4224                 struct blame *blame = view->line[i].data;
4226                 if (!blame->commit)
4227                         continue;
4229                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4230                         return blame->commit;
4231         }
4233         {
4234                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4236                 if (commit)
4237                         string_ncopy(commit->id, id, SIZEOF_REV);
4238                 return commit;
4239         }
4242 static bool
4243 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4245         const char *pos = *posref;
4247         *posref = NULL;
4248         pos = strchr(pos + 1, ' ');
4249         if (!pos || !isdigit(pos[1]))
4250                 return FALSE;
4251         *number = atoi(pos + 1);
4252         if (*number < min || *number > max)
4253                 return FALSE;
4255         *posref = pos;
4256         return TRUE;
4259 static struct blame_commit *
4260 parse_blame_commit(struct view *view, const char *text, int *blamed)
4262         struct blame_commit *commit;
4263         struct blame *blame;
4264         const char *pos = text + SIZEOF_REV - 2;
4265         size_t orig_lineno = 0;
4266         size_t lineno;
4267         size_t group;
4269         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4270                 return NULL;
4272         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4273             !parse_number(&pos, &lineno, 1, view->lines) ||
4274             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4275                 return NULL;
4277         commit = get_blame_commit(view, text);
4278         if (!commit)
4279                 return NULL;
4281         *blamed += group;
4282         while (group--) {
4283                 struct line *line = &view->line[lineno + group - 1];
4285                 blame = line->data;
4286                 blame->commit = commit;
4287                 blame->lineno = orig_lineno + group - 1;
4288                 line->dirty = 1;
4289         }
4291         return commit;
4294 static bool
4295 blame_read_file(struct view *view, const char *line, bool *read_file)
4297         if (!line) {
4298                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4299                 struct io io = {};
4301                 if (view->lines == 0 && !view->parent)
4302                         die("No blame exist for %s", view->vid);
4304                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4305                         report("Failed to load blame data");
4306                         return TRUE;
4307                 }
4309                 done_io(view->pipe);
4310                 view->io = io;
4311                 *read_file = FALSE;
4312                 return FALSE;
4314         } else {
4315                 size_t linelen = strlen(line);
4316                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4318                 if (!blame)
4319                         return FALSE;
4321                 blame->commit = NULL;
4322                 strncpy(blame->text, line, linelen);
4323                 blame->text[linelen] = 0;
4324                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4325         }
4328 static bool
4329 match_blame_header(const char *name, char **line)
4331         size_t namelen = strlen(name);
4332         bool matched = !strncmp(name, *line, namelen);
4334         if (matched)
4335                 *line += namelen;
4337         return matched;
4340 static bool
4341 blame_read(struct view *view, char *line)
4343         static struct blame_commit *commit = NULL;
4344         static int blamed = 0;
4345         static time_t author_time;
4346         static bool read_file = TRUE;
4348         if (read_file)
4349                 return blame_read_file(view, line, &read_file);
4351         if (!line) {
4352                 /* Reset all! */
4353                 commit = NULL;
4354                 blamed = 0;
4355                 read_file = TRUE;
4356                 string_format(view->ref, "%s", view->vid);
4357                 if (view_is_displayed(view)) {
4358                         update_view_title(view);
4359                         redraw_view_from(view, 0);
4360                 }
4361                 return TRUE;
4362         }
4364         if (!commit) {
4365                 commit = parse_blame_commit(view, line, &blamed);
4366                 string_format(view->ref, "%s %2d%%", view->vid,
4367                               view->lines ? blamed * 100 / view->lines : 0);
4369         } else if (match_blame_header("author ", &line)) {
4370                 string_ncopy(commit->author, line, strlen(line));
4372         } else if (match_blame_header("author-time ", &line)) {
4373                 author_time = (time_t) atol(line);
4375         } else if (match_blame_header("author-tz ", &line)) {
4376                 parse_timezone(&author_time, line);
4377                 gmtime_r(&author_time, &commit->time);
4379         } else if (match_blame_header("summary ", &line)) {
4380                 string_ncopy(commit->title, line, strlen(line));
4382         } else if (match_blame_header("previous ", &line)) {
4383                 commit->has_previous = TRUE;
4385         } else if (match_blame_header("filename ", &line)) {
4386                 string_ncopy(commit->filename, line, strlen(line));
4387                 commit = NULL;
4388         }
4390         return TRUE;
4393 static bool
4394 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4396         struct blame *blame = line->data;
4397         struct tm *time = NULL;
4398         const char *id = NULL, *author = NULL;
4399         char text[SIZEOF_STR];
4401         if (blame->commit && *blame->commit->filename) {
4402                 id = blame->commit->id;
4403                 author = blame->commit->author;
4404                 time = &blame->commit->time;
4405         }
4407         if (opt_date && draw_date(view, time))
4408                 return TRUE;
4410         if (opt_author && draw_author(view, author))
4411                 return TRUE;
4413         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4414                 return TRUE;
4416         if (draw_lineno(view, lineno))
4417                 return TRUE;
4419         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4420         draw_text(view, LINE_DEFAULT, text, TRUE);
4421         return TRUE;
4424 static bool
4425 check_blame_commit(struct blame *blame, bool check_null_id)
4427         if (!blame->commit)
4428                 report("Commit data not loaded yet");
4429         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4430                 report("No commit exist for the selected line");
4431         else
4432                 return TRUE;
4433         return FALSE;
4436 static void
4437 setup_blame_parent_line(struct view *view, struct blame *blame)
4439         const char *diff_tree_argv[] = {
4440                 "git", "diff-tree", "-U0", blame->commit->id,
4441                         "--", blame->commit->filename, NULL
4442         };
4443         struct io io = {};
4444         int parent_lineno = -1;
4445         int blamed_lineno = -1;
4446         char *line;
4448         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4449                 return;
4451         while ((line = io_get(&io, '\n', TRUE))) {
4452                 if (*line == '@') {
4453                         char *pos = strchr(line, '+');
4455                         parent_lineno = atoi(line + 4);
4456                         if (pos)
4457                                 blamed_lineno = atoi(pos + 1);
4459                 } else if (*line == '+' && parent_lineno != -1) {
4460                         if (blame->lineno == blamed_lineno - 1 &&
4461                             !strcmp(blame->text, line + 1)) {
4462                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4463                                 break;
4464                         }
4465                         blamed_lineno++;
4466                 }
4467         }
4469         done_io(&io);
4472 static enum request
4473 blame_request(struct view *view, enum request request, struct line *line)
4475         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4476         struct blame *blame = line->data;
4478         switch (request) {
4479         case REQ_VIEW_BLAME:
4480                 if (check_blame_commit(blame, TRUE)) {
4481                         string_copy(opt_ref, blame->commit->id);
4482                         string_copy(opt_file, blame->commit->filename);
4483                         if (blame->lineno)
4484                                 view->lineno = blame->lineno;
4485                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4486                 }
4487                 break;
4489         case REQ_PARENT:
4490                 if (check_blame_commit(blame, TRUE) &&
4491                     select_commit_parent(blame->commit->id, opt_ref,
4492                                          blame->commit->filename)) {
4493                         string_copy(opt_file, blame->commit->filename);
4494                         setup_blame_parent_line(view, blame);
4495                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4496                 }
4497                 break;
4499         case REQ_ENTER:
4500                 if (!check_blame_commit(blame, FALSE))
4501                         break;
4503                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4504                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4505                         break;
4507                 if (!strcmp(blame->commit->id, NULL_ID)) {
4508                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4509                         const char *diff_index_argv[] = {
4510                                 "git", "diff-index", "--root", "--patch-with-stat",
4511                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4512                         };
4514                         if (!blame->commit->has_previous) {
4515                                 diff_index_argv[1] = "diff";
4516                                 diff_index_argv[2] = "--no-color";
4517                                 diff_index_argv[6] = "--";
4518                                 diff_index_argv[7] = "/dev/null";
4519                         }
4521                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4522                                 report("Failed to allocate diff command");
4523                                 break;
4524                         }
4525                         flags |= OPEN_PREPARED;
4526                 }
4528                 open_view(view, REQ_VIEW_DIFF, flags);
4529                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4530                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4531                 break;
4533         default:
4534                 return request;
4535         }
4537         return REQ_NONE;
4540 static bool
4541 blame_grep(struct view *view, struct line *line)
4543         struct blame *blame = line->data;
4544         struct blame_commit *commit = blame->commit;
4545         regmatch_t pmatch;
4547 #define MATCH(text, on)                                                 \
4548         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4550         if (commit) {
4551                 char buf[DATE_COLS + 1];
4553                 if (MATCH(commit->title, 1) ||
4554                     MATCH(commit->author, opt_author) ||
4555                     MATCH(commit->id, opt_date))
4556                         return TRUE;
4558                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4559                     MATCH(buf, 1))
4560                         return TRUE;
4561         }
4563         return MATCH(blame->text, 1);
4565 #undef MATCH
4568 static void
4569 blame_select(struct view *view, struct line *line)
4571         struct blame *blame = line->data;
4572         struct blame_commit *commit = blame->commit;
4574         if (!commit)
4575                 return;
4577         if (!strcmp(commit->id, NULL_ID))
4578                 string_ncopy(ref_commit, "HEAD", 4);
4579         else
4580                 string_copy_rev(ref_commit, commit->id);
4583 static struct view_ops blame_ops = {
4584         "line",
4585         NULL,
4586         blame_open,
4587         blame_read,
4588         blame_draw,
4589         blame_request,
4590         blame_grep,
4591         blame_select,
4592 };
4594 /*
4595  * Status backend
4596  */
4598 struct status {
4599         char status;
4600         struct {
4601                 mode_t mode;
4602                 char rev[SIZEOF_REV];
4603                 char name[SIZEOF_STR];
4604         } old;
4605         struct {
4606                 mode_t mode;
4607                 char rev[SIZEOF_REV];
4608                 char name[SIZEOF_STR];
4609         } new;
4610 };
4612 static char status_onbranch[SIZEOF_STR];
4613 static struct status stage_status;
4614 static enum line_type stage_line_type;
4615 static size_t stage_chunks;
4616 static int *stage_chunk;
4618 /* This should work even for the "On branch" line. */
4619 static inline bool
4620 status_has_none(struct view *view, struct line *line)
4622         return line < view->line + view->lines && !line[1].data;
4625 /* Get fields from the diff line:
4626  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4627  */
4628 static inline bool
4629 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4631         const char *old_mode = buf +  1;
4632         const char *new_mode = buf +  8;
4633         const char *old_rev  = buf + 15;
4634         const char *new_rev  = buf + 56;
4635         const char *status   = buf + 97;
4637         if (bufsize < 98 ||
4638             old_mode[-1] != ':' ||
4639             new_mode[-1] != ' ' ||
4640             old_rev[-1]  != ' ' ||
4641             new_rev[-1]  != ' ' ||
4642             status[-1]   != ' ')
4643                 return FALSE;
4645         file->status = *status;
4647         string_copy_rev(file->old.rev, old_rev);
4648         string_copy_rev(file->new.rev, new_rev);
4650         file->old.mode = strtoul(old_mode, NULL, 8);
4651         file->new.mode = strtoul(new_mode, NULL, 8);
4653         file->old.name[0] = file->new.name[0] = 0;
4655         return TRUE;
4658 static bool
4659 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4661         struct status *unmerged = NULL;
4662         char *buf;
4663         struct io io = {};
4665         if (!run_io(&io, argv, NULL, IO_RD))
4666                 return FALSE;
4668         add_line_data(view, NULL, type);
4670         while ((buf = io_get(&io, 0, TRUE))) {
4671                 struct status *file = unmerged;
4673                 if (!file) {
4674                         file = calloc(1, sizeof(*file));
4675                         if (!file || !add_line_data(view, file, type))
4676                                 goto error_out;
4677                 }
4679                 /* Parse diff info part. */
4680                 if (status) {
4681                         file->status = status;
4682                         if (status == 'A')
4683                                 string_copy(file->old.rev, NULL_ID);
4685                 } else if (!file->status || file == unmerged) {
4686                         if (!status_get_diff(file, buf, strlen(buf)))
4687                                 goto error_out;
4689                         buf = io_get(&io, 0, TRUE);
4690                         if (!buf)
4691                                 break;
4693                         /* Collapse all modified entries that follow an
4694                          * associated unmerged entry. */
4695                         if (unmerged == file) {
4696                                 unmerged->status = 'U';
4697                                 unmerged = NULL;
4698                         } else if (file->status == 'U') {
4699                                 unmerged = file;
4700                         }
4701                 }
4703                 /* Grab the old name for rename/copy. */
4704                 if (!*file->old.name &&
4705                     (file->status == 'R' || file->status == 'C')) {
4706                         string_ncopy(file->old.name, buf, strlen(buf));
4708                         buf = io_get(&io, 0, TRUE);
4709                         if (!buf)
4710                                 break;
4711                 }
4713                 /* git-ls-files just delivers a NUL separated list of
4714                  * file names similar to the second half of the
4715                  * git-diff-* output. */
4716                 string_ncopy(file->new.name, buf, strlen(buf));
4717                 if (!*file->old.name)
4718                         string_copy(file->old.name, file->new.name);
4719                 file = NULL;
4720         }
4722         if (io_error(&io)) {
4723 error_out:
4724                 done_io(&io);
4725                 return FALSE;
4726         }
4728         if (!view->line[view->lines - 1].data)
4729                 add_line_data(view, NULL, LINE_STAT_NONE);
4731         done_io(&io);
4732         return TRUE;
4735 /* Don't show unmerged entries in the staged section. */
4736 static const char *status_diff_index_argv[] = {
4737         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4738                              "--cached", "-M", "HEAD", NULL
4739 };
4741 static const char *status_diff_files_argv[] = {
4742         "git", "diff-files", "-z", NULL
4743 };
4745 static const char *status_list_other_argv[] = {
4746         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4747 };
4749 static const char *status_list_no_head_argv[] = {
4750         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4751 };
4753 static const char *update_index_argv[] = {
4754         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4755 };
4757 /* Restore the previous line number to stay in the context or select a
4758  * line with something that can be updated. */
4759 static void
4760 status_restore(struct view *view)
4762         if (view->p_lineno >= view->lines)
4763                 view->p_lineno = view->lines - 1;
4764         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4765                 view->p_lineno++;
4766         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4767                 view->p_lineno--;
4769         /* If the above fails, always skip the "On branch" line. */
4770         if (view->p_lineno < view->lines)
4771                 view->lineno = view->p_lineno;
4772         else
4773                 view->lineno = 1;
4775         if (view->lineno < view->offset)
4776                 view->offset = view->lineno;
4777         else if (view->offset + view->height <= view->lineno)
4778                 view->offset = view->lineno - view->height + 1;
4780         view->p_restore = FALSE;
4783 static void
4784 status_update_onbranch(void)
4786         static const char *paths[][2] = {
4787                 { "rebase-apply/rebasing",      "Rebasing" },
4788                 { "rebase-apply/applying",      "Applying mailbox" },
4789                 { "rebase-apply/",              "Rebasing mailbox" },
4790                 { "rebase-merge/interactive",   "Interactive rebase" },
4791                 { "rebase-merge/",              "Rebase merge" },
4792                 { "MERGE_HEAD",                 "Merging" },
4793                 { "BISECT_LOG",                 "Bisecting" },
4794                 { "HEAD",                       "On branch" },
4795         };
4796         char buf[SIZEOF_STR];
4797         struct stat stat;
4798         int i;
4800         if (is_initial_commit()) {
4801                 string_copy(status_onbranch, "Initial commit");
4802                 return;
4803         }
4805         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4806                 char *head = opt_head;
4808                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4809                     lstat(buf, &stat) < 0)
4810                         continue;
4812                 if (!*opt_head) {
4813                         struct io io = {};
4815                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4816                             io_open(&io, buf) &&
4817                             io_read_buf(&io, buf, sizeof(buf))) {
4818                                 head = chomp_string(buf);
4819                                 if (!prefixcmp(head, "refs/heads/"))
4820                                         head += STRING_SIZE("refs/heads/");
4821                         }
4822                 }
4824                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4825                         string_copy(status_onbranch, opt_head);
4826                 return;
4827         }
4829         string_copy(status_onbranch, "Not currently on any branch");
4832 /* First parse staged info using git-diff-index(1), then parse unstaged
4833  * info using git-diff-files(1), and finally untracked files using
4834  * git-ls-files(1). */
4835 static bool
4836 status_open(struct view *view)
4838         reset_view(view);
4840         add_line_data(view, NULL, LINE_STAT_HEAD);
4841         status_update_onbranch();
4843         run_io_bg(update_index_argv);
4845         if (is_initial_commit()) {
4846                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4847                         return FALSE;
4848         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4849                 return FALSE;
4850         }
4852         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4853             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4854                 return FALSE;
4856         /* Restore the exact position or use the specialized restore
4857          * mode? */
4858         if (!view->p_restore)
4859                 status_restore(view);
4860         return TRUE;
4863 static bool
4864 status_draw(struct view *view, struct line *line, unsigned int lineno)
4866         struct status *status = line->data;
4867         enum line_type type;
4868         const char *text;
4870         if (!status) {
4871                 switch (line->type) {
4872                 case LINE_STAT_STAGED:
4873                         type = LINE_STAT_SECTION;
4874                         text = "Changes to be committed:";
4875                         break;
4877                 case LINE_STAT_UNSTAGED:
4878                         type = LINE_STAT_SECTION;
4879                         text = "Changed but not updated:";
4880                         break;
4882                 case LINE_STAT_UNTRACKED:
4883                         type = LINE_STAT_SECTION;
4884                         text = "Untracked files:";
4885                         break;
4887                 case LINE_STAT_NONE:
4888                         type = LINE_DEFAULT;
4889                         text = "  (no files)";
4890                         break;
4892                 case LINE_STAT_HEAD:
4893                         type = LINE_STAT_HEAD;
4894                         text = status_onbranch;
4895                         break;
4897                 default:
4898                         return FALSE;
4899                 }
4900         } else {
4901                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4903                 buf[0] = status->status;
4904                 if (draw_text(view, line->type, buf, TRUE))
4905                         return TRUE;
4906                 type = LINE_DEFAULT;
4907                 text = status->new.name;
4908         }
4910         draw_text(view, type, text, TRUE);
4911         return TRUE;
4914 static enum request
4915 status_load_error(struct view *view, struct view *stage, const char *path)
4917         if (displayed_views() == 2 || display[current_view] != view)
4918                 maximize_view(view);
4919         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4920         return REQ_NONE;
4923 static enum request
4924 status_enter(struct view *view, struct line *line)
4926         struct status *status = line->data;
4927         const char *oldpath = status ? status->old.name : NULL;
4928         /* Diffs for unmerged entries are empty when passing the new
4929          * path, so leave it empty. */
4930         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4931         const char *info;
4932         enum open_flags split;
4933         struct view *stage = VIEW(REQ_VIEW_STAGE);
4935         if (line->type == LINE_STAT_NONE ||
4936             (!status && line[1].type == LINE_STAT_NONE)) {
4937                 report("No file to diff");
4938                 return REQ_NONE;
4939         }
4941         switch (line->type) {
4942         case LINE_STAT_STAGED:
4943                 if (is_initial_commit()) {
4944                         const char *no_head_diff_argv[] = {
4945                                 "git", "diff", "--no-color", "--patch-with-stat",
4946                                         "--", "/dev/null", newpath, NULL
4947                         };
4949                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4950                                 return status_load_error(view, stage, newpath);
4951                 } else {
4952                         const char *index_show_argv[] = {
4953                                 "git", "diff-index", "--root", "--patch-with-stat",
4954                                         "-C", "-M", "--cached", "HEAD", "--",
4955                                         oldpath, newpath, NULL
4956                         };
4958                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4959                                 return status_load_error(view, stage, newpath);
4960                 }
4962                 if (status)
4963                         info = "Staged changes to %s";
4964                 else
4965                         info = "Staged changes";
4966                 break;
4968         case LINE_STAT_UNSTAGED:
4969         {
4970                 const char *files_show_argv[] = {
4971                         "git", "diff-files", "--root", "--patch-with-stat",
4972                                 "-C", "-M", "--", oldpath, newpath, NULL
4973                 };
4975                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4976                         return status_load_error(view, stage, newpath);
4977                 if (status)
4978                         info = "Unstaged changes to %s";
4979                 else
4980                         info = "Unstaged changes";
4981                 break;
4982         }
4983         case LINE_STAT_UNTRACKED:
4984                 if (!newpath) {
4985                         report("No file to show");
4986                         return REQ_NONE;
4987                 }
4989                 if (!suffixcmp(status->new.name, -1, "/")) {
4990                         report("Cannot display a directory");
4991                         return REQ_NONE;
4992                 }
4994                 if (!prepare_update_file(stage, newpath))
4995                         return status_load_error(view, stage, newpath);
4996                 info = "Untracked file %s";
4997                 break;
4999         case LINE_STAT_HEAD:
5000                 return REQ_NONE;
5002         default:
5003                 die("line type %d not handled in switch", line->type);
5004         }
5006         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5007         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5008         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5009                 if (status) {
5010                         stage_status = *status;
5011                 } else {
5012                         memset(&stage_status, 0, sizeof(stage_status));
5013                 }
5015                 stage_line_type = line->type;
5016                 stage_chunks = 0;
5017                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5018         }
5020         return REQ_NONE;
5023 static bool
5024 status_exists(struct status *status, enum line_type type)
5026         struct view *view = VIEW(REQ_VIEW_STATUS);
5027         unsigned long lineno;
5029         for (lineno = 0; lineno < view->lines; lineno++) {
5030                 struct line *line = &view->line[lineno];
5031                 struct status *pos = line->data;
5033                 if (line->type != type)
5034                         continue;
5035                 if (!pos && (!status || !status->status) && line[1].data) {
5036                         select_view_line(view, lineno);
5037                         return TRUE;
5038                 }
5039                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5040                         select_view_line(view, lineno);
5041                         return TRUE;
5042                 }
5043         }
5045         return FALSE;
5049 static bool
5050 status_update_prepare(struct io *io, enum line_type type)
5052         const char *staged_argv[] = {
5053                 "git", "update-index", "-z", "--index-info", NULL
5054         };
5055         const char *others_argv[] = {
5056                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5057         };
5059         switch (type) {
5060         case LINE_STAT_STAGED:
5061                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5063         case LINE_STAT_UNSTAGED:
5064                 return run_io(io, others_argv, opt_cdup, IO_WR);
5066         case LINE_STAT_UNTRACKED:
5067                 return run_io(io, others_argv, NULL, IO_WR);
5069         default:
5070                 die("line type %d not handled in switch", type);
5071                 return FALSE;
5072         }
5075 static bool
5076 status_update_write(struct io *io, struct status *status, enum line_type type)
5078         char buf[SIZEOF_STR];
5079         size_t bufsize = 0;
5081         switch (type) {
5082         case LINE_STAT_STAGED:
5083                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5084                                         status->old.mode,
5085                                         status->old.rev,
5086                                         status->old.name, 0))
5087                         return FALSE;
5088                 break;
5090         case LINE_STAT_UNSTAGED:
5091         case LINE_STAT_UNTRACKED:
5092                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5093                         return FALSE;
5094                 break;
5096         default:
5097                 die("line type %d not handled in switch", type);
5098         }
5100         return io_write(io, buf, bufsize);
5103 static bool
5104 status_update_file(struct status *status, enum line_type type)
5106         struct io io = {};
5107         bool result;
5109         if (!status_update_prepare(&io, type))
5110                 return FALSE;
5112         result = status_update_write(&io, status, type);
5113         return done_io(&io) && result;
5116 static bool
5117 status_update_files(struct view *view, struct line *line)
5119         char buf[sizeof(view->ref)];
5120         struct io io = {};
5121         bool result = TRUE;
5122         struct line *pos = view->line + view->lines;
5123         int files = 0;
5124         int file, done;
5126         if (!status_update_prepare(&io, line->type))
5127                 return FALSE;
5129         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5130                 files++;
5132         string_copy(buf, view->ref);
5133         for (file = 0, done = 5; result && file < files; line++, file++) {
5134                 int almost_done = file * 100 / files;
5136                 if (almost_done > done) {
5137                         done = almost_done;
5138                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5139                                       file, files, done);
5140                         update_view_title(view);
5141                         doupdate();
5142                 }
5143                 result = status_update_write(&io, line->data, line->type);
5144         }
5145         string_copy(view->ref, buf);
5147         return done_io(&io) && result;
5150 static bool
5151 status_update(struct view *view)
5153         struct line *line = &view->line[view->lineno];
5155         assert(view->lines);
5157         if (!line->data) {
5158                 /* This should work even for the "On branch" line. */
5159                 if (line < view->line + view->lines && !line[1].data) {
5160                         report("Nothing to update");
5161                         return FALSE;
5162                 }
5164                 if (!status_update_files(view, line + 1)) {
5165                         report("Failed to update file status");
5166                         return FALSE;
5167                 }
5169         } else if (!status_update_file(line->data, line->type)) {
5170                 report("Failed to update file status");
5171                 return FALSE;
5172         }
5174         return TRUE;
5177 static bool
5178 status_revert(struct status *status, enum line_type type, bool has_none)
5180         if (!status || type != LINE_STAT_UNSTAGED) {
5181                 if (type == LINE_STAT_STAGED) {
5182                         report("Cannot revert changes to staged files");
5183                 } else if (type == LINE_STAT_UNTRACKED) {
5184                         report("Cannot revert changes to untracked files");
5185                 } else if (has_none) {
5186                         report("Nothing to revert");
5187                 } else {
5188                         report("Cannot revert changes to multiple files");
5189                 }
5190                 return FALSE;
5192         } else {
5193                 char mode[10] = "100644";
5194                 const char *reset_argv[] = {
5195                         "git", "update-index", "--cacheinfo", mode,
5196                                 status->old.rev, status->old.name, NULL
5197                 };
5198                 const char *checkout_argv[] = {
5199                         "git", "checkout", "--", status->old.name, NULL
5200                 };
5202                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5203                         return FALSE;
5204                 string_format(mode, "%o", status->old.mode);
5205                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5206                         run_io_fg(checkout_argv, opt_cdup);
5207         }
5210 static enum request
5211 status_request(struct view *view, enum request request, struct line *line)
5213         struct status *status = line->data;
5215         switch (request) {
5216         case REQ_STATUS_UPDATE:
5217                 if (!status_update(view))
5218                         return REQ_NONE;
5219                 break;
5221         case REQ_STATUS_REVERT:
5222                 if (!status_revert(status, line->type, status_has_none(view, line)))
5223                         return REQ_NONE;
5224                 break;
5226         case REQ_STATUS_MERGE:
5227                 if (!status || status->status != 'U') {
5228                         report("Merging only possible for files with unmerged status ('U').");
5229                         return REQ_NONE;
5230                 }
5231                 open_mergetool(status->new.name);
5232                 break;
5234         case REQ_EDIT:
5235                 if (!status)
5236                         return request;
5237                 if (status->status == 'D') {
5238                         report("File has been deleted.");
5239                         return REQ_NONE;
5240                 }
5242                 open_editor(status->status != '?', status->new.name);
5243                 break;
5245         case REQ_VIEW_BLAME:
5246                 if (status) {
5247                         string_copy(opt_file, status->new.name);
5248                         opt_ref[0] = 0;
5249                 }
5250                 return request;
5252         case REQ_ENTER:
5253                 /* After returning the status view has been split to
5254                  * show the stage view. No further reloading is
5255                  * necessary. */
5256                 return status_enter(view, line);
5258         case REQ_REFRESH:
5259                 /* Simply reload the view. */
5260                 break;
5262         default:
5263                 return request;
5264         }
5266         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5268         return REQ_NONE;
5271 static void
5272 status_select(struct view *view, struct line *line)
5274         struct status *status = line->data;
5275         char file[SIZEOF_STR] = "all files";
5276         const char *text;
5277         const char *key;
5279         if (status && !string_format(file, "'%s'", status->new.name))
5280                 return;
5282         if (!status && line[1].type == LINE_STAT_NONE)
5283                 line++;
5285         switch (line->type) {
5286         case LINE_STAT_STAGED:
5287                 text = "Press %s to unstage %s for commit";
5288                 break;
5290         case LINE_STAT_UNSTAGED:
5291                 text = "Press %s to stage %s for commit";
5292                 break;
5294         case LINE_STAT_UNTRACKED:
5295                 text = "Press %s to stage %s for addition";
5296                 break;
5298         case LINE_STAT_HEAD:
5299         case LINE_STAT_NONE:
5300                 text = "Nothing to update";
5301                 break;
5303         default:
5304                 die("line type %d not handled in switch", line->type);
5305         }
5307         if (status && status->status == 'U') {
5308                 text = "Press %s to resolve conflict in %s";
5309                 key = get_key(REQ_STATUS_MERGE);
5311         } else {
5312                 key = get_key(REQ_STATUS_UPDATE);
5313         }
5315         string_format(view->ref, text, key, file);
5318 static bool
5319 status_grep(struct view *view, struct line *line)
5321         struct status *status = line->data;
5322         enum { S_STATUS, S_NAME, S_END } state;
5323         char buf[2] = "?";
5324         regmatch_t pmatch;
5326         if (!status)
5327                 return FALSE;
5329         for (state = S_STATUS; state < S_END; state++) {
5330                 const char *text;
5332                 switch (state) {
5333                 case S_NAME:    text = status->new.name;        break;
5334                 case S_STATUS:
5335                         buf[0] = status->status;
5336                         text = buf;
5337                         break;
5339                 default:
5340                         return FALSE;
5341                 }
5343                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5344                         return TRUE;
5345         }
5347         return FALSE;
5350 static struct view_ops status_ops = {
5351         "file",
5352         NULL,
5353         status_open,
5354         NULL,
5355         status_draw,
5356         status_request,
5357         status_grep,
5358         status_select,
5359 };
5362 static bool
5363 stage_diff_write(struct io *io, struct line *line, struct line *end)
5365         while (line < end) {
5366                 if (!io_write(io, line->data, strlen(line->data)) ||
5367                     !io_write(io, "\n", 1))
5368                         return FALSE;
5369                 line++;
5370                 if (line->type == LINE_DIFF_CHUNK ||
5371                     line->type == LINE_DIFF_HEADER)
5372                         break;
5373         }
5375         return TRUE;
5378 static struct line *
5379 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5381         for (; view->line < line; line--)
5382                 if (line->type == type)
5383                         return line;
5385         return NULL;
5388 static bool
5389 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5391         const char *apply_argv[SIZEOF_ARG] = {
5392                 "git", "apply", "--whitespace=nowarn", NULL
5393         };
5394         struct line *diff_hdr;
5395         struct io io = {};
5396         int argc = 3;
5398         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5399         if (!diff_hdr)
5400                 return FALSE;
5402         if (!revert)
5403                 apply_argv[argc++] = "--cached";
5404         if (revert || stage_line_type == LINE_STAT_STAGED)
5405                 apply_argv[argc++] = "-R";
5406         apply_argv[argc++] = "-";
5407         apply_argv[argc++] = NULL;
5408         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5409                 return FALSE;
5411         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5412             !stage_diff_write(&io, chunk, view->line + view->lines))
5413                 chunk = NULL;
5415         done_io(&io);
5416         run_io_bg(update_index_argv);
5418         return chunk ? TRUE : FALSE;
5421 static bool
5422 stage_update(struct view *view, struct line *line)
5424         struct line *chunk = NULL;
5426         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5427                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5429         if (chunk) {
5430                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5431                         report("Failed to apply chunk");
5432                         return FALSE;
5433                 }
5435         } else if (!stage_status.status) {
5436                 view = VIEW(REQ_VIEW_STATUS);
5438                 for (line = view->line; line < view->line + view->lines; line++)
5439                         if (line->type == stage_line_type)
5440                                 break;
5442                 if (!status_update_files(view, line + 1)) {
5443                         report("Failed to update files");
5444                         return FALSE;
5445                 }
5447         } else if (!status_update_file(&stage_status, stage_line_type)) {
5448                 report("Failed to update file");
5449                 return FALSE;
5450         }
5452         return TRUE;
5455 static bool
5456 stage_revert(struct view *view, struct line *line)
5458         struct line *chunk = NULL;
5460         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5461                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5463         if (chunk) {
5464                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5465                         return FALSE;
5467                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5468                         report("Failed to revert chunk");
5469                         return FALSE;
5470                 }
5471                 return TRUE;
5473         } else {
5474                 return status_revert(stage_status.status ? &stage_status : NULL,
5475                                      stage_line_type, FALSE);
5476         }
5480 static void
5481 stage_next(struct view *view, struct line *line)
5483         int i;
5485         if (!stage_chunks) {
5486                 static size_t alloc = 0;
5487                 int *tmp;
5489                 for (line = view->line; line < view->line + view->lines; line++) {
5490                         if (line->type != LINE_DIFF_CHUNK)
5491                                 continue;
5493                         tmp = realloc_items(stage_chunk, &alloc,
5494                                             stage_chunks, sizeof(*tmp));
5495                         if (!tmp) {
5496                                 report("Allocation failure");
5497                                 return;
5498                         }
5500                         stage_chunk = tmp;
5501                         stage_chunk[stage_chunks++] = line - view->line;
5502                 }
5503         }
5505         for (i = 0; i < stage_chunks; i++) {
5506                 if (stage_chunk[i] > view->lineno) {
5507                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5508                         report("Chunk %d of %d", i + 1, stage_chunks);
5509                         return;
5510                 }
5511         }
5513         report("No next chunk found");
5516 static enum request
5517 stage_request(struct view *view, enum request request, struct line *line)
5519         switch (request) {
5520         case REQ_STATUS_UPDATE:
5521                 if (!stage_update(view, line))
5522                         return REQ_NONE;
5523                 break;
5525         case REQ_STATUS_REVERT:
5526                 if (!stage_revert(view, line))
5527                         return REQ_NONE;
5528                 break;
5530         case REQ_STAGE_NEXT:
5531                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5532                         report("File is untracked; press %s to add",
5533                                get_key(REQ_STATUS_UPDATE));
5534                         return REQ_NONE;
5535                 }
5536                 stage_next(view, line);
5537                 return REQ_NONE;
5539         case REQ_EDIT:
5540                 if (!stage_status.new.name[0])
5541                         return request;
5542                 if (stage_status.status == 'D') {
5543                         report("File has been deleted.");
5544                         return REQ_NONE;
5545                 }
5547                 open_editor(stage_status.status != '?', stage_status.new.name);
5548                 break;
5550         case REQ_REFRESH:
5551                 /* Reload everything ... */
5552                 break;
5554         case REQ_VIEW_BLAME:
5555                 if (stage_status.new.name[0]) {
5556                         string_copy(opt_file, stage_status.new.name);
5557                         opt_ref[0] = 0;
5558                 }
5559                 return request;
5561         case REQ_ENTER:
5562                 return pager_request(view, request, line);
5564         default:
5565                 return request;
5566         }
5568         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5569         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5571         /* Check whether the staged entry still exists, and close the
5572          * stage view if it doesn't. */
5573         if (!status_exists(&stage_status, stage_line_type)) {
5574                 status_restore(VIEW(REQ_VIEW_STATUS));
5575                 return REQ_VIEW_CLOSE;
5576         }
5578         if (stage_line_type == LINE_STAT_UNTRACKED) {
5579                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5580                         report("Cannot display a directory");
5581                         return REQ_NONE;
5582                 }
5584                 if (!prepare_update_file(view, stage_status.new.name)) {
5585                         report("Failed to open file: %s", strerror(errno));
5586                         return REQ_NONE;
5587                 }
5588         }
5589         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5591         return REQ_NONE;
5594 static struct view_ops stage_ops = {
5595         "line",
5596         NULL,
5597         NULL,
5598         pager_read,
5599         pager_draw,
5600         stage_request,
5601         pager_grep,
5602         pager_select,
5603 };
5606 /*
5607  * Revision graph
5608  */
5610 struct commit {
5611         char id[SIZEOF_REV];            /* SHA1 ID. */
5612         char title[128];                /* First line of the commit message. */
5613         char author[75];                /* Author of the commit. */
5614         struct tm time;                 /* Date from the author ident. */
5615         struct ref **refs;              /* Repository references. */
5616         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5617         size_t graph_size;              /* The width of the graph array. */
5618         bool has_parents;               /* Rewritten --parents seen. */
5619 };
5621 /* Size of rev graph with no  "padding" columns */
5622 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5624 struct rev_graph {
5625         struct rev_graph *prev, *next, *parents;
5626         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5627         size_t size;
5628         struct commit *commit;
5629         size_t pos;
5630         unsigned int boundary:1;
5631 };
5633 /* Parents of the commit being visualized. */
5634 static struct rev_graph graph_parents[4];
5636 /* The current stack of revisions on the graph. */
5637 static struct rev_graph graph_stacks[4] = {
5638         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5639         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5640         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5641         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5642 };
5644 static inline bool
5645 graph_parent_is_merge(struct rev_graph *graph)
5647         return graph->parents->size > 1;
5650 static inline void
5651 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5653         struct commit *commit = graph->commit;
5655         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5656                 commit->graph[commit->graph_size++] = symbol;
5659 static void
5660 clear_rev_graph(struct rev_graph *graph)
5662         graph->boundary = 0;
5663         graph->size = graph->pos = 0;
5664         graph->commit = NULL;
5665         memset(graph->parents, 0, sizeof(*graph->parents));
5668 static void
5669 done_rev_graph(struct rev_graph *graph)
5671         if (graph_parent_is_merge(graph) &&
5672             graph->pos < graph->size - 1 &&
5673             graph->next->size == graph->size + graph->parents->size - 1) {
5674                 size_t i = graph->pos + graph->parents->size - 1;
5676                 graph->commit->graph_size = i * 2;
5677                 while (i < graph->next->size - 1) {
5678                         append_to_rev_graph(graph, ' ');
5679                         append_to_rev_graph(graph, '\\');
5680                         i++;
5681                 }
5682         }
5684         clear_rev_graph(graph);
5687 static void
5688 push_rev_graph(struct rev_graph *graph, const char *parent)
5690         int i;
5692         /* "Collapse" duplicate parents lines.
5693          *
5694          * FIXME: This needs to also update update the drawn graph but
5695          * for now it just serves as a method for pruning graph lines. */
5696         for (i = 0; i < graph->size; i++)
5697                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5698                         return;
5700         if (graph->size < SIZEOF_REVITEMS) {
5701                 string_copy_rev(graph->rev[graph->size++], parent);
5702         }
5705 static chtype
5706 get_rev_graph_symbol(struct rev_graph *graph)
5708         chtype symbol;
5710         if (graph->boundary)
5711                 symbol = REVGRAPH_BOUND;
5712         else if (graph->parents->size == 0)
5713                 symbol = REVGRAPH_INIT;
5714         else if (graph_parent_is_merge(graph))
5715                 symbol = REVGRAPH_MERGE;
5716         else if (graph->pos >= graph->size)
5717                 symbol = REVGRAPH_BRANCH;
5718         else
5719                 symbol = REVGRAPH_COMMIT;
5721         return symbol;
5724 static void
5725 draw_rev_graph(struct rev_graph *graph)
5727         struct rev_filler {
5728                 chtype separator, line;
5729         };
5730         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5731         static struct rev_filler fillers[] = {
5732                 { ' ',  '|' },
5733                 { '`',  '.' },
5734                 { '\'', ' ' },
5735                 { '/',  ' ' },
5736         };
5737         chtype symbol = get_rev_graph_symbol(graph);
5738         struct rev_filler *filler;
5739         size_t i;
5741         if (opt_line_graphics)
5742                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5744         filler = &fillers[DEFAULT];
5746         for (i = 0; i < graph->pos; i++) {
5747                 append_to_rev_graph(graph, filler->line);
5748                 if (graph_parent_is_merge(graph->prev) &&
5749                     graph->prev->pos == i)
5750                         filler = &fillers[RSHARP];
5752                 append_to_rev_graph(graph, filler->separator);
5753         }
5755         /* Place the symbol for this revision. */
5756         append_to_rev_graph(graph, symbol);
5758         if (graph->prev->size > graph->size)
5759                 filler = &fillers[RDIAG];
5760         else
5761                 filler = &fillers[DEFAULT];
5763         i++;
5765         for (; i < graph->size; i++) {
5766                 append_to_rev_graph(graph, filler->separator);
5767                 append_to_rev_graph(graph, filler->line);
5768                 if (graph_parent_is_merge(graph->prev) &&
5769                     i < graph->prev->pos + graph->parents->size)
5770                         filler = &fillers[RSHARP];
5771                 if (graph->prev->size > graph->size)
5772                         filler = &fillers[LDIAG];
5773         }
5775         if (graph->prev->size > graph->size) {
5776                 append_to_rev_graph(graph, filler->separator);
5777                 if (filler->line != ' ')
5778                         append_to_rev_graph(graph, filler->line);
5779         }
5782 /* Prepare the next rev graph */
5783 static void
5784 prepare_rev_graph(struct rev_graph *graph)
5786         size_t i;
5788         /* First, traverse all lines of revisions up to the active one. */
5789         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5790                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5791                         break;
5793                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5794         }
5796         /* Interleave the new revision parent(s). */
5797         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5798                 push_rev_graph(graph->next, graph->parents->rev[i]);
5800         /* Lastly, put any remaining revisions. */
5801         for (i = graph->pos + 1; i < graph->size; i++)
5802                 push_rev_graph(graph->next, graph->rev[i]);
5805 static void
5806 update_rev_graph(struct view *view, struct rev_graph *graph)
5808         /* If this is the finalizing update ... */
5809         if (graph->commit)
5810                 prepare_rev_graph(graph);
5812         /* Graph visualization needs a one rev look-ahead,
5813          * so the first update doesn't visualize anything. */
5814         if (!graph->prev->commit)
5815                 return;
5817         if (view->lines > 2)
5818                 view->line[view->lines - 3].dirty = 1;
5819         if (view->lines > 1)
5820                 view->line[view->lines - 2].dirty = 1;
5821         draw_rev_graph(graph->prev);
5822         done_rev_graph(graph->prev->prev);
5826 /*
5827  * Main view backend
5828  */
5830 static const char *main_argv[SIZEOF_ARG] = {
5831         "git", "log", "--no-color", "--pretty=raw", "--parents",
5832                       "--topo-order", "%(head)", NULL
5833 };
5835 static bool
5836 main_draw(struct view *view, struct line *line, unsigned int lineno)
5838         struct commit *commit = line->data;
5840         if (!*commit->author)
5841                 return FALSE;
5843         if (opt_date && draw_date(view, &commit->time))
5844                 return TRUE;
5846         if (opt_author && draw_author(view, commit->author))
5847                 return TRUE;
5849         if (opt_rev_graph && commit->graph_size &&
5850             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5851                 return TRUE;
5853         if (opt_show_refs && commit->refs) {
5854                 size_t i = 0;
5856                 do {
5857                         enum line_type type;
5859                         if (commit->refs[i]->head)
5860                                 type = LINE_MAIN_HEAD;
5861                         else if (commit->refs[i]->ltag)
5862                                 type = LINE_MAIN_LOCAL_TAG;
5863                         else if (commit->refs[i]->tag)
5864                                 type = LINE_MAIN_TAG;
5865                         else if (commit->refs[i]->tracked)
5866                                 type = LINE_MAIN_TRACKED;
5867                         else if (commit->refs[i]->remote)
5868                                 type = LINE_MAIN_REMOTE;
5869                         else
5870                                 type = LINE_MAIN_REF;
5872                         if (draw_text(view, type, "[", TRUE) ||
5873                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5874                             draw_text(view, type, "]", TRUE))
5875                                 return TRUE;
5877                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5878                                 return TRUE;
5879                 } while (commit->refs[i++]->next);
5880         }
5882         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5883         return TRUE;
5886 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5887 static bool
5888 main_read(struct view *view, char *line)
5890         static struct rev_graph *graph = graph_stacks;
5891         enum line_type type;
5892         struct commit *commit;
5894         if (!line) {
5895                 int i;
5897                 if (!view->lines && !view->parent)
5898                         die("No revisions match the given arguments.");
5899                 if (view->lines > 0) {
5900                         commit = view->line[view->lines - 1].data;
5901                         view->line[view->lines - 1].dirty = 1;
5902                         if (!*commit->author) {
5903                                 view->lines--;
5904                                 free(commit);
5905                                 graph->commit = NULL;
5906                         }
5907                 }
5908                 update_rev_graph(view, graph);
5910                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5911                         clear_rev_graph(&graph_stacks[i]);
5912                 return TRUE;
5913         }
5915         type = get_line_type(line);
5916         if (type == LINE_COMMIT) {
5917                 commit = calloc(1, sizeof(struct commit));
5918                 if (!commit)
5919                         return FALSE;
5921                 line += STRING_SIZE("commit ");
5922                 if (*line == '-') {
5923                         graph->boundary = 1;
5924                         line++;
5925                 }
5927                 string_copy_rev(commit->id, line);
5928                 commit->refs = get_refs(commit->id);
5929                 graph->commit = commit;
5930                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5932                 while ((line = strchr(line, ' '))) {
5933                         line++;
5934                         push_rev_graph(graph->parents, line);
5935                         commit->has_parents = TRUE;
5936                 }
5937                 return TRUE;
5938         }
5940         if (!view->lines)
5941                 return TRUE;
5942         commit = view->line[view->lines - 1].data;
5944         switch (type) {
5945         case LINE_PARENT:
5946                 if (commit->has_parents)
5947                         break;
5948                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5949                 break;
5951         case LINE_AUTHOR:
5952                 parse_author_line(line + STRING_SIZE("author "),
5953                                   commit->author, sizeof(commit->author),
5954                                   &commit->time);
5955                 update_rev_graph(view, graph);
5956                 graph = graph->next;
5957                 break;
5959         default:
5960                 /* Fill in the commit title if it has not already been set. */
5961                 if (commit->title[0])
5962                         break;
5964                 /* Require titles to start with a non-space character at the
5965                  * offset used by git log. */
5966                 if (strncmp(line, "    ", 4))
5967                         break;
5968                 line += 4;
5969                 /* Well, if the title starts with a whitespace character,
5970                  * try to be forgiving.  Otherwise we end up with no title. */
5971                 while (isspace(*line))
5972                         line++;
5973                 if (*line == '\0')
5974                         break;
5975                 /* FIXME: More graceful handling of titles; append "..." to
5976                  * shortened titles, etc. */
5978                 string_expand(commit->title, sizeof(commit->title), line, 1);
5979                 view->line[view->lines - 1].dirty = 1;
5980         }
5982         return TRUE;
5985 static enum request
5986 main_request(struct view *view, enum request request, struct line *line)
5988         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5990         switch (request) {
5991         case REQ_ENTER:
5992                 open_view(view, REQ_VIEW_DIFF, flags);
5993                 break;
5994         case REQ_REFRESH:
5995                 load_refs();
5996                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5997                 break;
5998         default:
5999                 return request;
6000         }
6002         return REQ_NONE;
6005 static bool
6006 grep_refs(struct ref **refs, regex_t *regex)
6008         regmatch_t pmatch;
6009         size_t i = 0;
6011         if (!refs)
6012                 return FALSE;
6013         do {
6014                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6015                         return TRUE;
6016         } while (refs[i++]->next);
6018         return FALSE;
6021 static bool
6022 main_grep(struct view *view, struct line *line)
6024         struct commit *commit = line->data;
6025         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6026         char buf[DATE_COLS + 1];
6027         regmatch_t pmatch;
6029         for (state = S_TITLE; state < S_END; state++) {
6030                 char *text;
6032                 switch (state) {
6033                 case S_TITLE:   text = commit->title;   break;
6034                 case S_AUTHOR:
6035                         if (!opt_author)
6036                                 continue;
6037                         text = commit->author;
6038                         break;
6039                 case S_DATE:
6040                         if (!opt_date)
6041                                 continue;
6042                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6043                                 continue;
6044                         text = buf;
6045                         break;
6046                 case S_REFS:
6047                         if (!opt_show_refs)
6048                                 continue;
6049                         if (grep_refs(commit->refs, view->regex) == TRUE)
6050                                 return TRUE;
6051                         continue;
6052                 default:
6053                         return FALSE;
6054                 }
6056                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6057                         return TRUE;
6058         }
6060         return FALSE;
6063 static void
6064 main_select(struct view *view, struct line *line)
6066         struct commit *commit = line->data;
6068         string_copy_rev(view->ref, commit->id);
6069         string_copy_rev(ref_commit, view->ref);
6072 static struct view_ops main_ops = {
6073         "commit",
6074         main_argv,
6075         NULL,
6076         main_read,
6077         main_draw,
6078         main_request,
6079         main_grep,
6080         main_select,
6081 };
6084 /*
6085  * Unicode / UTF-8 handling
6086  *
6087  * NOTE: Much of the following code for dealing with Unicode is derived from
6088  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6089  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6090  */
6092 static inline int
6093 unicode_width(unsigned long c)
6095         if (c >= 0x1100 &&
6096            (c <= 0x115f                         /* Hangul Jamo */
6097             || c == 0x2329
6098             || c == 0x232a
6099             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6100                                                 /* CJK ... Yi */
6101             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6102             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6103             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6104             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6105             || (c >= 0xffe0  && c <= 0xffe6)
6106             || (c >= 0x20000 && c <= 0x2fffd)
6107             || (c >= 0x30000 && c <= 0x3fffd)))
6108                 return 2;
6110         if (c == '\t')
6111                 return opt_tab_size;
6113         return 1;
6116 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6117  * Illegal bytes are set one. */
6118 static const unsigned char utf8_bytes[256] = {
6119         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,
6120         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,
6121         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,
6122         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,
6123         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,
6124         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,
6125         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,
6126         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,
6127 };
6129 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6130 static inline unsigned long
6131 utf8_to_unicode(const char *string, size_t length)
6133         unsigned long unicode;
6135         switch (length) {
6136         case 1:
6137                 unicode  =   string[0];
6138                 break;
6139         case 2:
6140                 unicode  =  (string[0] & 0x1f) << 6;
6141                 unicode +=  (string[1] & 0x3f);
6142                 break;
6143         case 3:
6144                 unicode  =  (string[0] & 0x0f) << 12;
6145                 unicode += ((string[1] & 0x3f) << 6);
6146                 unicode +=  (string[2] & 0x3f);
6147                 break;
6148         case 4:
6149                 unicode  =  (string[0] & 0x0f) << 18;
6150                 unicode += ((string[1] & 0x3f) << 12);
6151                 unicode += ((string[2] & 0x3f) << 6);
6152                 unicode +=  (string[3] & 0x3f);
6153                 break;
6154         case 5:
6155                 unicode  =  (string[0] & 0x0f) << 24;
6156                 unicode += ((string[1] & 0x3f) << 18);
6157                 unicode += ((string[2] & 0x3f) << 12);
6158                 unicode += ((string[3] & 0x3f) << 6);
6159                 unicode +=  (string[4] & 0x3f);
6160                 break;
6161         case 6:
6162                 unicode  =  (string[0] & 0x01) << 30;
6163                 unicode += ((string[1] & 0x3f) << 24);
6164                 unicode += ((string[2] & 0x3f) << 18);
6165                 unicode += ((string[3] & 0x3f) << 12);
6166                 unicode += ((string[4] & 0x3f) << 6);
6167                 unicode +=  (string[5] & 0x3f);
6168                 break;
6169         default:
6170                 die("Invalid Unicode length");
6171         }
6173         /* Invalid characters could return the special 0xfffd value but NUL
6174          * should be just as good. */
6175         return unicode > 0xffff ? 0 : unicode;
6178 /* Calculates how much of string can be shown within the given maximum width
6179  * and sets trimmed parameter to non-zero value if all of string could not be
6180  * shown. If the reserve flag is TRUE, it will reserve at least one
6181  * trailing character, which can be useful when drawing a delimiter.
6182  *
6183  * Returns the number of bytes to output from string to satisfy max_width. */
6184 static size_t
6185 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6187         const char *string = *start;
6188         const char *end = strchr(string, '\0');
6189         unsigned char last_bytes = 0;
6190         size_t last_ucwidth = 0;
6192         *width = 0;
6193         *trimmed = 0;
6195         while (string < end) {
6196                 int c = *(unsigned char *) string;
6197                 unsigned char bytes = utf8_bytes[c];
6198                 size_t ucwidth;
6199                 unsigned long unicode;
6201                 if (string + bytes > end)
6202                         break;
6204                 /* Change representation to figure out whether
6205                  * it is a single- or double-width character. */
6207                 unicode = utf8_to_unicode(string, bytes);
6208                 /* FIXME: Graceful handling of invalid Unicode character. */
6209                 if (!unicode)
6210                         break;
6212                 ucwidth = unicode_width(unicode);
6213                 if (skip > 0) {
6214                         skip -= ucwidth <= skip ? ucwidth : skip;
6215                         *start += bytes;
6216                 }
6217                 *width  += ucwidth;
6218                 if (*width > max_width) {
6219                         *trimmed = 1;
6220                         *width -= ucwidth;
6221                         if (reserve && *width == max_width) {
6222                                 string -= last_bytes;
6223                                 *width -= last_ucwidth;
6224                         }
6225                         break;
6226                 }
6228                 string  += bytes;
6229                 last_bytes = ucwidth ? bytes : 0;
6230                 last_ucwidth = ucwidth;
6231         }
6233         return string - *start;
6237 /*
6238  * Status management
6239  */
6241 /* Whether or not the curses interface has been initialized. */
6242 static bool cursed = FALSE;
6244 /* Terminal hacks and workarounds. */
6245 static bool use_scroll_redrawwin;
6246 static bool use_scroll_status_wclear;
6248 /* The status window is used for polling keystrokes. */
6249 static WINDOW *status_win;
6251 /* Reading from the prompt? */
6252 static bool input_mode = FALSE;
6254 static bool status_empty = FALSE;
6256 /* Update status and title window. */
6257 static void
6258 report(const char *msg, ...)
6260         struct view *view = display[current_view];
6262         if (input_mode)
6263                 return;
6265         if (!view) {
6266                 char buf[SIZEOF_STR];
6267                 va_list args;
6269                 va_start(args, msg);
6270                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6271                         buf[sizeof(buf) - 1] = 0;
6272                         buf[sizeof(buf) - 2] = '.';
6273                         buf[sizeof(buf) - 3] = '.';
6274                         buf[sizeof(buf) - 4] = '.';
6275                 }
6276                 va_end(args);
6277                 die("%s", buf);
6278         }
6280         if (!status_empty || *msg) {
6281                 va_list args;
6283                 va_start(args, msg);
6285                 wmove(status_win, 0, 0);
6286                 if (view->has_scrolled && use_scroll_status_wclear)
6287                         wclear(status_win);
6288                 if (*msg) {
6289                         vwprintw(status_win, msg, args);
6290                         status_empty = FALSE;
6291                 } else {
6292                         status_empty = TRUE;
6293                 }
6294                 wclrtoeol(status_win);
6295                 wnoutrefresh(status_win);
6297                 va_end(args);
6298         }
6300         update_view_title(view);
6303 /* Controls when nodelay should be in effect when polling user input. */
6304 static void
6305 set_nonblocking_input(bool loading)
6307         static unsigned int loading_views;
6309         if ((loading == FALSE && loading_views-- == 1) ||
6310             (loading == TRUE  && loading_views++ == 0))
6311                 nodelay(status_win, loading);
6314 static void
6315 init_display(void)
6317         const char *term;
6318         int x, y;
6320         /* Initialize the curses library */
6321         if (isatty(STDIN_FILENO)) {
6322                 cursed = !!initscr();
6323                 opt_tty = stdin;
6324         } else {
6325                 /* Leave stdin and stdout alone when acting as a pager. */
6326                 opt_tty = fopen("/dev/tty", "r+");
6327                 if (!opt_tty)
6328                         die("Failed to open /dev/tty");
6329                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6330         }
6332         if (!cursed)
6333                 die("Failed to initialize curses");
6335         nonl();         /* Disable conversion and detect newlines from input. */
6336         cbreak();       /* Take input chars one at a time, no wait for \n */
6337         noecho();       /* Don't echo input */
6338         leaveok(stdscr, FALSE);
6340         if (has_colors())
6341                 init_colors();
6343         getmaxyx(stdscr, y, x);
6344         status_win = newwin(1, 0, y - 1, 0);
6345         if (!status_win)
6346                 die("Failed to create status window");
6348         /* Enable keyboard mapping */
6349         keypad(status_win, TRUE);
6350         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6352         TABSIZE = opt_tab_size;
6353         if (opt_line_graphics) {
6354                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6355         }
6357         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6358         if (term && !strcmp(term, "gnome-terminal")) {
6359                 /* In the gnome-terminal-emulator, the message from
6360                  * scrolling up one line when impossible followed by
6361                  * scrolling down one line causes corruption of the
6362                  * status line. This is fixed by calling wclear. */
6363                 use_scroll_status_wclear = TRUE;
6364                 use_scroll_redrawwin = FALSE;
6366         } else if (term && !strcmp(term, "xrvt-xpm")) {
6367                 /* No problems with full optimizations in xrvt-(unicode)
6368                  * and aterm. */
6369                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6371         } else {
6372                 /* When scrolling in (u)xterm the last line in the
6373                  * scrolling direction will update slowly. */
6374                 use_scroll_redrawwin = TRUE;
6375                 use_scroll_status_wclear = FALSE;
6376         }
6379 static int
6380 get_input(int prompt_position)
6382         struct view *view;
6383         int i, key, cursor_y, cursor_x;
6385         if (prompt_position)
6386                 input_mode = TRUE;
6388         while (TRUE) {
6389                 foreach_view (view, i) {
6390                         update_view(view);
6391                         if (view_is_displayed(view) && view->has_scrolled &&
6392                             use_scroll_redrawwin)
6393                                 redrawwin(view->win);
6394                         view->has_scrolled = FALSE;
6395                 }
6397                 /* Update the cursor position. */
6398                 if (prompt_position) {
6399                         getbegyx(status_win, cursor_y, cursor_x);
6400                         cursor_x = prompt_position;
6401                 } else {
6402                         view = display[current_view];
6403                         getbegyx(view->win, cursor_y, cursor_x);
6404                         cursor_x = view->width - 1;
6405                         cursor_y += view->lineno - view->offset;
6406                 }
6407                 setsyx(cursor_y, cursor_x);
6409                 /* Refresh, accept single keystroke of input */
6410                 doupdate();
6411                 key = wgetch(status_win);
6413                 /* wgetch() with nodelay() enabled returns ERR when
6414                  * there's no input. */
6415                 if (key == ERR) {
6417                 } else if (key == KEY_RESIZE) {
6418                         int height, width;
6420                         getmaxyx(stdscr, height, width);
6422                         wresize(status_win, 1, width);
6423                         mvwin(status_win, height - 1, 0);
6424                         wnoutrefresh(status_win);
6425                         resize_display();
6426                         redraw_display(TRUE);
6428                 } else {
6429                         input_mode = FALSE;
6430                         return key;
6431                 }
6432         }
6435 static char *
6436 prompt_input(const char *prompt, input_handler handler, void *data)
6438         enum input_status status = INPUT_OK;
6439         static char buf[SIZEOF_STR];
6440         size_t pos = 0;
6442         buf[pos] = 0;
6444         while (status == INPUT_OK || status == INPUT_SKIP) {
6445                 int key;
6447                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6448                 wclrtoeol(status_win);
6450                 key = get_input(pos + 1);
6451                 switch (key) {
6452                 case KEY_RETURN:
6453                 case KEY_ENTER:
6454                 case '\n':
6455                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6456                         break;
6458                 case KEY_BACKSPACE:
6459                         if (pos > 0)
6460                                 buf[--pos] = 0;
6461                         else
6462                                 status = INPUT_CANCEL;
6463                         break;
6465                 case KEY_ESC:
6466                         status = INPUT_CANCEL;
6467                         break;
6469                 default:
6470                         if (pos >= sizeof(buf)) {
6471                                 report("Input string too long");
6472                                 return NULL;
6473                         }
6475                         status = handler(data, buf, key);
6476                         if (status == INPUT_OK)
6477                                 buf[pos++] = (char) key;
6478                 }
6479         }
6481         /* Clear the status window */
6482         status_empty = FALSE;
6483         report("");
6485         if (status == INPUT_CANCEL)
6486                 return NULL;
6488         buf[pos++] = 0;
6490         return buf;
6493 static enum input_status
6494 prompt_yesno_handler(void *data, char *buf, int c)
6496         if (c == 'y' || c == 'Y')
6497                 return INPUT_STOP;
6498         if (c == 'n' || c == 'N')
6499                 return INPUT_CANCEL;
6500         return INPUT_SKIP;
6503 static bool
6504 prompt_yesno(const char *prompt)
6506         char prompt2[SIZEOF_STR];
6508         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6509                 return FALSE;
6511         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6514 static enum input_status
6515 read_prompt_handler(void *data, char *buf, int c)
6517         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6520 static char *
6521 read_prompt(const char *prompt)
6523         return prompt_input(prompt, read_prompt_handler, NULL);
6526 /*
6527  * Repository properties
6528  */
6530 static struct ref *refs = NULL;
6531 static size_t refs_alloc = 0;
6532 static size_t refs_size = 0;
6534 /* Id <-> ref store */
6535 static struct ref ***id_refs = NULL;
6536 static size_t id_refs_alloc = 0;
6537 static size_t id_refs_size = 0;
6539 static int
6540 compare_refs(const void *ref1_, const void *ref2_)
6542         const struct ref *ref1 = *(const struct ref **)ref1_;
6543         const struct ref *ref2 = *(const struct ref **)ref2_;
6545         if (ref1->tag != ref2->tag)
6546                 return ref2->tag - ref1->tag;
6547         if (ref1->ltag != ref2->ltag)
6548                 return ref2->ltag - ref2->ltag;
6549         if (ref1->head != ref2->head)
6550                 return ref2->head - ref1->head;
6551         if (ref1->tracked != ref2->tracked)
6552                 return ref2->tracked - ref1->tracked;
6553         if (ref1->remote != ref2->remote)
6554                 return ref2->remote - ref1->remote;
6555         return strcmp(ref1->name, ref2->name);
6558 static struct ref **
6559 get_refs(const char *id)
6561         struct ref ***tmp_id_refs;
6562         struct ref **ref_list = NULL;
6563         size_t ref_list_alloc = 0;
6564         size_t ref_list_size = 0;
6565         size_t i;
6567         for (i = 0; i < id_refs_size; i++)
6568                 if (!strcmp(id, id_refs[i][0]->id))
6569                         return id_refs[i];
6571         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6572                                     sizeof(*id_refs));
6573         if (!tmp_id_refs)
6574                 return NULL;
6576         id_refs = tmp_id_refs;
6578         for (i = 0; i < refs_size; i++) {
6579                 struct ref **tmp;
6581                 if (strcmp(id, refs[i].id))
6582                         continue;
6584                 tmp = realloc_items(ref_list, &ref_list_alloc,
6585                                     ref_list_size + 1, sizeof(*ref_list));
6586                 if (!tmp) {
6587                         if (ref_list)
6588                                 free(ref_list);
6589                         return NULL;
6590                 }
6592                 ref_list = tmp;
6593                 ref_list[ref_list_size] = &refs[i];
6594                 /* XXX: The properties of the commit chains ensures that we can
6595                  * safely modify the shared ref. The repo references will
6596                  * always be similar for the same id. */
6597                 ref_list[ref_list_size]->next = 1;
6599                 ref_list_size++;
6600         }
6602         if (ref_list) {
6603                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6604                 ref_list[ref_list_size - 1]->next = 0;
6605                 id_refs[id_refs_size++] = ref_list;
6606         }
6608         return ref_list;
6611 static int
6612 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6614         struct ref *ref;
6615         bool tag = FALSE;
6616         bool ltag = FALSE;
6617         bool remote = FALSE;
6618         bool tracked = FALSE;
6619         bool check_replace = FALSE;
6620         bool head = FALSE;
6622         if (!prefixcmp(name, "refs/tags/")) {
6623                 if (!suffixcmp(name, namelen, "^{}")) {
6624                         namelen -= 3;
6625                         name[namelen] = 0;
6626                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6627                                 check_replace = TRUE;
6628                 } else {
6629                         ltag = TRUE;
6630                 }
6632                 tag = TRUE;
6633                 namelen -= STRING_SIZE("refs/tags/");
6634                 name    += STRING_SIZE("refs/tags/");
6636         } else if (!prefixcmp(name, "refs/remotes/")) {
6637                 remote = TRUE;
6638                 namelen -= STRING_SIZE("refs/remotes/");
6639                 name    += STRING_SIZE("refs/remotes/");
6640                 tracked  = !strcmp(opt_remote, name);
6642         } else if (!prefixcmp(name, "refs/heads/")) {
6643                 namelen -= STRING_SIZE("refs/heads/");
6644                 name    += STRING_SIZE("refs/heads/");
6645                 head     = !strncmp(opt_head, name, namelen);
6647         } else if (!strcmp(name, "HEAD")) {
6648                 string_ncopy(opt_head_rev, id, idlen);
6649                 return OK;
6650         }
6652         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6653                 /* it's an annotated tag, replace the previous SHA1 with the
6654                  * resolved commit id; relies on the fact git-ls-remote lists
6655                  * the commit id of an annotated tag right before the commit id
6656                  * it points to. */
6657                 refs[refs_size - 1].ltag = ltag;
6658                 string_copy_rev(refs[refs_size - 1].id, id);
6660                 return OK;
6661         }
6662         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6663         if (!refs)
6664                 return ERR;
6666         ref = &refs[refs_size++];
6667         ref->name = malloc(namelen + 1);
6668         if (!ref->name)
6669                 return ERR;
6671         strncpy(ref->name, name, namelen);
6672         ref->name[namelen] = 0;
6673         ref->head = head;
6674         ref->tag = tag;
6675         ref->ltag = ltag;
6676         ref->remote = remote;
6677         ref->tracked = tracked;
6678         string_copy_rev(ref->id, id);
6680         return OK;
6683 static int
6684 load_refs(void)
6686         static const char *ls_remote_argv[SIZEOF_ARG] = {
6687                 "git", "ls-remote", opt_git_dir, NULL
6688         };
6689         static bool init = FALSE;
6691         if (!init) {
6692                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6693                 init = TRUE;
6694         }
6696         if (!*opt_git_dir)
6697                 return OK;
6699         while (refs_size > 0)
6700                 free(refs[--refs_size].name);
6701         while (id_refs_size > 0)
6702                 free(id_refs[--id_refs_size]);
6704         return run_io_load(ls_remote_argv, "\t", read_ref);
6707 static void
6708 set_remote_branch(const char *name, const char *value, size_t valuelen)
6710         if (!strcmp(name, ".remote")) {
6711                 string_ncopy(opt_remote, value, valuelen);
6713         } else if (*opt_remote && !strcmp(name, ".merge")) {
6714                 size_t from = strlen(opt_remote);
6716                 if (!prefixcmp(value, "refs/heads/"))
6717                         value += STRING_SIZE("refs/heads/");
6719                 if (!string_format_from(opt_remote, &from, "/%s", value))
6720                         opt_remote[0] = 0;
6721         }
6724 static void
6725 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6727         const char *argv[SIZEOF_ARG] = { name, "=" };
6728         int argc = 1 + (cmd == option_set_command);
6729         int error = ERR;
6731         if (!argv_from_string(argv, &argc, value))
6732                 config_msg = "Too many option arguments";
6733         else
6734                 error = cmd(argc, argv);
6736         if (error == ERR)
6737                 warn("Option 'tig.%s': %s", name, config_msg);
6740 static bool
6741 set_environment_variable(const char *name, const char *value)
6743         size_t len = strlen(name) + 1 + strlen(value) + 1;
6744         char *env = malloc(len);
6746         if (env &&
6747             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6748             putenv(env) == 0)
6749                 return TRUE;
6750         free(env);
6751         return FALSE;
6754 static void
6755 set_work_tree(const char *value)
6757         char cwd[SIZEOF_STR];
6759         if (!getcwd(cwd, sizeof(cwd)))
6760                 die("Failed to get cwd path: %s", strerror(errno));
6761         if (chdir(opt_git_dir) < 0)
6762                 die("Failed to chdir(%s): %s", strerror(errno));
6763         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6764                 die("Failed to get git path: %s", strerror(errno));
6765         if (chdir(cwd) < 0)
6766                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6767         if (chdir(value) < 0)
6768                 die("Failed to chdir(%s): %s", value, strerror(errno));
6769         if (!getcwd(cwd, sizeof(cwd)))
6770                 die("Failed to get cwd path: %s", strerror(errno));
6771         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6772                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6773         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6774                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6775         opt_is_inside_work_tree = TRUE;
6778 static int
6779 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6781         if (!strcmp(name, "i18n.commitencoding"))
6782                 string_ncopy(opt_encoding, value, valuelen);
6784         else if (!strcmp(name, "core.editor"))
6785                 string_ncopy(opt_editor, value, valuelen);
6787         else if (!strcmp(name, "core.worktree"))
6788                 set_work_tree(value);
6790         else if (!prefixcmp(name, "tig.color."))
6791                 set_repo_config_option(name + 10, value, option_color_command);
6793         else if (!prefixcmp(name, "tig.bind."))
6794                 set_repo_config_option(name + 9, value, option_bind_command);
6796         else if (!prefixcmp(name, "tig."))
6797                 set_repo_config_option(name + 4, value, option_set_command);
6799         else if (*opt_head && !prefixcmp(name, "branch.") &&
6800                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6801                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6803         return OK;
6806 static int
6807 load_git_config(void)
6809         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6811         return run_io_load(config_list_argv, "=", read_repo_config_option);
6814 static int
6815 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6817         if (!opt_git_dir[0]) {
6818                 string_ncopy(opt_git_dir, name, namelen);
6820         } else if (opt_is_inside_work_tree == -1) {
6821                 /* This can be 3 different values depending on the
6822                  * version of git being used. If git-rev-parse does not
6823                  * understand --is-inside-work-tree it will simply echo
6824                  * the option else either "true" or "false" is printed.
6825                  * Default to true for the unknown case. */
6826                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6828         } else if (*name == '.') {
6829                 string_ncopy(opt_cdup, name, namelen);
6831         } else {
6832                 string_ncopy(opt_prefix, name, namelen);
6833         }
6835         return OK;
6838 static int
6839 load_repo_info(void)
6841         const char *head_argv[] = {
6842                 "git", "symbolic-ref", "HEAD", NULL
6843         };
6844         const char *rev_parse_argv[] = {
6845                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6846                         "--show-cdup", "--show-prefix", NULL
6847         };
6849         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6850                 chomp_string(opt_head);
6851                 if (!prefixcmp(opt_head, "refs/heads/")) {
6852                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6854                         memmove(opt_head, offset, strlen(offset) + 1);
6855                 }
6856         }
6858         return run_io_load(rev_parse_argv, "=", read_repo_info);
6862 /*
6863  * Main
6864  */
6866 static const char usage[] =
6867 "tig " TIG_VERSION " (" __DATE__ ")\n"
6868 "\n"
6869 "Usage: tig        [options] [revs] [--] [paths]\n"
6870 "   or: tig show   [options] [revs] [--] [paths]\n"
6871 "   or: tig blame  [rev] path\n"
6872 "   or: tig status\n"
6873 "   or: tig <      [git command output]\n"
6874 "\n"
6875 "Options:\n"
6876 "  -v, --version   Show version and exit\n"
6877 "  -h, --help      Show help message and exit";
6879 static void __NORETURN
6880 quit(int sig)
6882         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6883         if (cursed)
6884                 endwin();
6885         exit(0);
6888 static void __NORETURN
6889 die(const char *err, ...)
6891         va_list args;
6893         endwin();
6895         va_start(args, err);
6896         fputs("tig: ", stderr);
6897         vfprintf(stderr, err, args);
6898         fputs("\n", stderr);
6899         va_end(args);
6901         exit(1);
6904 static void
6905 warn(const char *msg, ...)
6907         va_list args;
6909         va_start(args, msg);
6910         fputs("tig warning: ", stderr);
6911         vfprintf(stderr, msg, args);
6912         fputs("\n", stderr);
6913         va_end(args);
6916 static enum request
6917 parse_options(int argc, const char *argv[])
6919         enum request request = REQ_VIEW_MAIN;
6920         const char *subcommand;
6921         bool seen_dashdash = FALSE;
6922         /* XXX: This is vulnerable to the user overriding options
6923          * required for the main view parser. */
6924         const char *custom_argv[SIZEOF_ARG] = {
6925                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6926                         "--topo-order", NULL
6927         };
6928         int i, j = 6;
6930         if (!isatty(STDIN_FILENO)) {
6931                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6932                 return REQ_VIEW_PAGER;
6933         }
6935         if (argc <= 1)
6936                 return REQ_NONE;
6938         subcommand = argv[1];
6939         if (!strcmp(subcommand, "status")) {
6940                 if (argc > 2)
6941                         warn("ignoring arguments after `%s'", subcommand);
6942                 return REQ_VIEW_STATUS;
6944         } else if (!strcmp(subcommand, "blame")) {
6945                 if (argc <= 2 || argc > 4)
6946                         die("invalid number of options to blame\n\n%s", usage);
6948                 i = 2;
6949                 if (argc == 4) {
6950                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6951                         i++;
6952                 }
6954                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6955                 return REQ_VIEW_BLAME;
6957         } else if (!strcmp(subcommand, "show")) {
6958                 request = REQ_VIEW_DIFF;
6960         } else {
6961                 subcommand = NULL;
6962         }
6964         if (subcommand) {
6965                 custom_argv[1] = subcommand;
6966                 j = 2;
6967         }
6969         for (i = 1 + !!subcommand; i < argc; i++) {
6970                 const char *opt = argv[i];
6972                 if (seen_dashdash || !strcmp(opt, "--")) {
6973                         seen_dashdash = TRUE;
6975                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6976                         printf("tig version %s\n", TIG_VERSION);
6977                         quit(0);
6979                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6980                         printf("%s\n", usage);
6981                         quit(0);
6982                 }
6984                 custom_argv[j++] = opt;
6985                 if (j >= ARRAY_SIZE(custom_argv))
6986                         die("command too long");
6987         }
6989         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6990                 die("Failed to format arguments"); 
6992         return request;
6995 int
6996 main(int argc, const char *argv[])
6998         enum request request = parse_options(argc, argv);
6999         struct view *view;
7000         size_t i;
7002         signal(SIGINT, quit);
7003         signal(SIGPIPE, SIG_IGN);
7005         if (setlocale(LC_ALL, "")) {
7006                 char *codeset = nl_langinfo(CODESET);
7008                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7009         }
7011         if (load_repo_info() == ERR)
7012                 die("Failed to load repo info.");
7014         if (load_options() == ERR)
7015                 die("Failed to load user config.");
7017         if (load_git_config() == ERR)
7018                 die("Failed to load repo config.");
7020         /* Require a git repository unless when running in pager mode. */
7021         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7022                 die("Not a git repository");
7024         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7025                 opt_utf8 = FALSE;
7027         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7028                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7029                 if (opt_iconv == ICONV_NONE)
7030                         die("Failed to initialize character set conversion");
7031         }
7033         if (load_refs() == ERR)
7034                 die("Failed to load refs.");
7036         foreach_view (view, i)
7037                 argv_from_env(view->ops->argv, view->cmd_env);
7039         init_display();
7041         if (request != REQ_NONE)
7042                 open_view(NULL, request, OPEN_PREPARED);
7043         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7045         while (view_driver(display[current_view], request)) {
7046                 int key = get_input(0);
7048                 view = display[current_view];
7049                 request = get_keybinding(view->keymap, key);
7051                 /* Some low-level request handling. This keeps access to
7052                  * status_win restricted. */
7053                 switch (request) {
7054                 case REQ_PROMPT:
7055                 {
7056                         char *cmd = read_prompt(":");
7058                         if (cmd && isdigit(*cmd)) {
7059                                 int lineno = view->lineno + 1;
7061                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7062                                         select_view_line(view, lineno - 1);
7063                                         report("");
7064                                 } else {
7065                                         report("Unable to parse '%s' as a line number", cmd);
7066                                 }
7068                         } else if (cmd) {
7069                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7070                                 const char *argv[SIZEOF_ARG] = { "git" };
7071                                 int argc = 1;
7073                                 /* When running random commands, initially show the
7074                                  * command in the title. However, it maybe later be
7075                                  * overwritten if a commit line is selected. */
7076                                 string_ncopy(next->ref, cmd, strlen(cmd));
7078                                 if (!argv_from_string(argv, &argc, cmd)) {
7079                                         report("Too many arguments");
7080                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7081                                         report("Failed to format command");
7082                                 } else {
7083                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7084                                 }
7085                         }
7087                         request = REQ_NONE;
7088                         break;
7089                 }
7090                 case REQ_SEARCH:
7091                 case REQ_SEARCH_BACK:
7092                 {
7093                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7094                         char *search = read_prompt(prompt);
7096                         if (search)
7097                                 string_ncopy(opt_search, search, strlen(search));
7098                         else if (*opt_search)
7099                                 request = request == REQ_SEARCH ?
7100                                         REQ_FIND_NEXT :
7101                                         REQ_FIND_PREV;
7102                         else
7103                                 request = REQ_NONE;
7104                         break;
7105                 }
7106                 default:
7107                         break;
7108                 }
7109         }
7111         quit(0);
7113         return 0;