Code

bd9a7a4bf0852cd23413adf32c5eb3282beaadde
[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
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 struct int_map {
152         const char *name;
153         int namelen;
154         int value;
155 };
157 static int
158 set_from_int_map(struct int_map *map, size_t map_size,
159                  int *value, const char *name, int namelen)
162         int i;
164         for (i = 0; i < map_size; i++)
165                 if (namelen == map[i].namelen &&
166                     !strncasecmp(name, map[i].name, namelen)) {
167                         *value = map[i].value;
168                         return OK;
169                 }
171         return ERR;
174 enum input_status {
175         INPUT_OK,
176         INPUT_SKIP,
177         INPUT_STOP,
178         INPUT_CANCEL
179 };
181 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
183 static char *prompt_input(const char *prompt, input_handler handler, void *data);
184 static bool prompt_yesno(const char *prompt);
186 /*
187  * String helpers
188  */
190 static inline void
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193         if (srclen > dstlen - 1)
194                 srclen = dstlen - 1;
196         strncpy(dst, src, srclen);
197         dst[srclen] = 0;
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206         string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
214 static size_t
215 string_expand_length(const char *line, int tabsize)
217         size_t size, pos;
219         for (pos = 0; line[pos]; pos++) {
220                 if (line[pos] == '\t' && tabsize > 0)
221                         size += tabsize - (size % tabsize);
222                 else
223                         size++;
224         }
225         return size;
228 static void
229 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
231         size_t size, pos;
233         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
234                 if (src[pos] == '\t') {
235                         size_t expanded = tabsize - (size % tabsize);
237                         if (expanded + size >= dstlen - 1)
238                                 expanded = dstlen - size - 1;
239                         memcpy(dst + size, "        ", expanded);
240                         size += expanded;
241                 } else {
242                         dst[size++] = src[pos];
243                 }
244         }
246         dst[size] = 0;
249 static char *
250 chomp_string(char *name)
252         int namelen;
254         while (isspace(*name))
255                 name++;
257         namelen = strlen(name) - 1;
258         while (namelen > 0 && isspace(name[namelen]))
259                 name[namelen--] = 0;
261         return name;
264 static bool
265 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
267         va_list args;
268         size_t pos = bufpos ? *bufpos : 0;
270         va_start(args, fmt);
271         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
272         va_end(args);
274         if (bufpos)
275                 *bufpos = pos;
277         return pos >= bufsize ? FALSE : TRUE;
280 #define string_format(buf, fmt, args...) \
281         string_nformat(buf, sizeof(buf), NULL, fmt, args)
283 #define string_format_from(buf, from, fmt, args...) \
284         string_nformat(buf, sizeof(buf), from, fmt, args)
286 static int
287 string_enum_compare(const char *str1, const char *str2, int len)
289         size_t i;
291 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
293         /* Diff-Header == DIFF_HEADER */
294         for (i = 0; i < len; i++) {
295                 if (toupper(str1[i]) == toupper(str2[i]))
296                         continue;
298                 if (string_enum_sep(str1[i]) &&
299                     string_enum_sep(str2[i]))
300                         continue;
302                 return str1[i] - str2[i];
303         }
305         return 0;
308 #define prefixcmp(str1, str2) \
309         strncmp(str1, str2, STRING_SIZE(str2))
311 static inline int
312 suffixcmp(const char *str, int slen, const char *suffix)
314         size_t len = slen >= 0 ? slen : strlen(str);
315         size_t suffixlen = strlen(suffix);
317         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
321 static bool
322 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
324         int valuelen;
326         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
327                 bool advance = cmd[valuelen] != 0;
329                 cmd[valuelen] = 0;
330                 argv[(*argc)++] = chomp_string(cmd);
331                 cmd = chomp_string(cmd + valuelen + advance);
332         }
334         if (*argc < SIZEOF_ARG)
335                 argv[*argc] = NULL;
336         return *argc < SIZEOF_ARG;
339 static void
340 argv_from_env(const char **argv, const char *name)
342         char *env = argv ? getenv(name) : NULL;
343         int argc = 0;
345         if (env && *env)
346                 env = strdup(env);
347         if (env && !argv_from_string(argv, &argc, env))
348                 die("Too many arguments in the `%s` environment variable", name);
352 /*
353  * Executing external commands.
354  */
356 enum io_type {
357         IO_FD,                  /* File descriptor based IO. */
358         IO_BG,                  /* Execute command in the background. */
359         IO_FG,                  /* Execute command with same std{in,out,err}. */
360         IO_RD,                  /* Read only fork+exec IO. */
361         IO_WR,                  /* Write only fork+exec IO. */
362         IO_AP,                  /* Append fork+exec output to file. */
363 };
365 struct io {
366         enum io_type type;      /* The requested type of pipe. */
367         const char *dir;        /* Directory from which to execute. */
368         pid_t pid;              /* Pipe for reading or writing. */
369         int pipe;               /* Pipe end for reading or writing. */
370         int error;              /* Error status. */
371         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
372         char *buf;              /* Read buffer. */
373         size_t bufalloc;        /* Allocated buffer size. */
374         size_t bufsize;         /* Buffer content size. */
375         char *bufpos;           /* Current buffer position. */
376         unsigned int eof:1;     /* Has end of file been reached. */
377 };
379 static void
380 reset_io(struct io *io)
382         io->pipe = -1;
383         io->pid = 0;
384         io->buf = io->bufpos = NULL;
385         io->bufalloc = io->bufsize = 0;
386         io->error = 0;
387         io->eof = 0;
390 static void
391 init_io(struct io *io, const char *dir, enum io_type type)
393         reset_io(io);
394         io->type = type;
395         io->dir = dir;
398 static bool
399 init_io_rd(struct io *io, const char *argv[], const char *dir,
400                 enum format_flags flags)
402         init_io(io, dir, IO_RD);
403         return format_argv(io->argv, argv, flags);
406 static bool
407 io_open(struct io *io, const char *name)
409         init_io(io, NULL, IO_FD);
410         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
411         return io->pipe != -1;
414 static bool
415 kill_io(struct io *io)
417         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
420 static bool
421 done_io(struct io *io)
423         pid_t pid = io->pid;
425         if (io->pipe != -1)
426                 close(io->pipe);
427         free(io->buf);
428         reset_io(io);
430         while (pid > 0) {
431                 int status;
432                 pid_t waiting = waitpid(pid, &status, 0);
434                 if (waiting < 0) {
435                         if (errno == EINTR)
436                                 continue;
437                         report("waitpid failed (%s)", strerror(errno));
438                         return FALSE;
439                 }
441                 return waiting == pid &&
442                        !WIFSIGNALED(status) &&
443                        WIFEXITED(status) &&
444                        !WEXITSTATUS(status);
445         }
447         return TRUE;
450 static bool
451 start_io(struct io *io)
453         int pipefds[2] = { -1, -1 };
455         if (io->type == IO_FD)
456                 return TRUE;
458         if ((io->type == IO_RD || io->type == IO_WR) &&
459             pipe(pipefds) < 0)
460                 return FALSE;
461         else if (io->type == IO_AP)
462                 pipefds[1] = io->pipe;
464         if ((io->pid = fork())) {
465                 if (pipefds[!(io->type == IO_WR)] != -1)
466                         close(pipefds[!(io->type == IO_WR)]);
467                 if (io->pid != -1) {
468                         io->pipe = pipefds[!!(io->type == IO_WR)];
469                         return TRUE;
470                 }
472         } else {
473                 if (io->type != IO_FG) {
474                         int devnull = open("/dev/null", O_RDWR);
475                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
476                         int writefd = (io->type == IO_RD || io->type == IO_AP)
477                                                         ? pipefds[1] : devnull;
479                         dup2(readfd,  STDIN_FILENO);
480                         dup2(writefd, STDOUT_FILENO);
481                         dup2(devnull, STDERR_FILENO);
483                         close(devnull);
484                         if (pipefds[0] != -1)
485                                 close(pipefds[0]);
486                         if (pipefds[1] != -1)
487                                 close(pipefds[1]);
488                 }
490                 if (io->dir && *io->dir && chdir(io->dir) == -1)
491                         die("Failed to change directory: %s", strerror(errno));
493                 execvp(io->argv[0], (char *const*) io->argv);
494                 die("Failed to execute program: %s", strerror(errno));
495         }
497         if (pipefds[!!(io->type == IO_WR)] != -1)
498                 close(pipefds[!!(io->type == IO_WR)]);
499         return FALSE;
502 static bool
503 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
505         init_io(io, dir, type);
506         if (!format_argv(io->argv, argv, FORMAT_NONE))
507                 return FALSE;
508         return start_io(io);
511 static int
512 run_io_do(struct io *io)
514         return start_io(io) && done_io(io);
517 static int
518 run_io_bg(const char **argv)
520         struct io io = {};
522         init_io(&io, NULL, IO_BG);
523         if (!format_argv(io.argv, argv, FORMAT_NONE))
524                 return FALSE;
525         return run_io_do(&io);
528 static bool
529 run_io_fg(const char **argv, const char *dir)
531         struct io io = {};
533         init_io(&io, dir, IO_FG);
534         if (!format_argv(io.argv, argv, FORMAT_NONE))
535                 return FALSE;
536         return run_io_do(&io);
539 static bool
540 run_io_append(const char **argv, enum format_flags flags, int fd)
542         struct io io = {};
544         init_io(&io, NULL, IO_AP);
545         io.pipe = fd;
546         if (format_argv(io.argv, argv, flags))
547                 return run_io_do(&io);
548         close(fd);
549         return FALSE;
552 static bool
553 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
555         return init_io_rd(io, argv, NULL, flags) && start_io(io);
558 static bool
559 io_eof(struct io *io)
561         return io->eof;
564 static int
565 io_error(struct io *io)
567         return io->error;
570 static bool
571 io_strerror(struct io *io)
573         return strerror(io->error);
576 static bool
577 io_can_read(struct io *io)
579         struct timeval tv = { 0, 500 };
580         fd_set fds;
582         FD_ZERO(&fds);
583         FD_SET(io->pipe, &fds);
585         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
588 static ssize_t
589 io_read(struct io *io, void *buf, size_t bufsize)
591         do {
592                 ssize_t readsize = read(io->pipe, buf, bufsize);
594                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
595                         continue;
596                 else if (readsize == -1)
597                         io->error = errno;
598                 else if (readsize == 0)
599                         io->eof = 1;
600                 return readsize;
601         } while (1);
604 static char *
605 io_get(struct io *io, int c, bool can_read)
607         char *eol;
608         ssize_t readsize;
610         if (!io->buf) {
611                 io->buf = io->bufpos = malloc(BUFSIZ);
612                 if (!io->buf)
613                         return NULL;
614                 io->bufalloc = BUFSIZ;
615                 io->bufsize = 0;
616         }
618         while (TRUE) {
619                 if (io->bufsize > 0) {
620                         eol = memchr(io->bufpos, c, io->bufsize);
621                         if (eol) {
622                                 char *line = io->bufpos;
624                                 *eol = 0;
625                                 io->bufpos = eol + 1;
626                                 io->bufsize -= io->bufpos - line;
627                                 return line;
628                         }
629                 }
631                 if (io_eof(io)) {
632                         if (io->bufsize) {
633                                 io->bufpos[io->bufsize] = 0;
634                                 io->bufsize = 0;
635                                 return io->bufpos;
636                         }
637                         return NULL;
638                 }
640                 if (!can_read)
641                         return NULL;
643                 if (io->bufsize > 0 && io->bufpos > io->buf)
644                         memmove(io->buf, io->bufpos, io->bufsize);
646                 io->bufpos = io->buf;
647                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
648                 if (io_error(io))
649                         return NULL;
650                 io->bufsize += readsize;
651         }
654 static bool
655 io_write(struct io *io, const void *buf, size_t bufsize)
657         size_t written = 0;
659         while (!io_error(io) && written < bufsize) {
660                 ssize_t size;
662                 size = write(io->pipe, buf + written, bufsize - written);
663                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
664                         continue;
665                 else if (size == -1)
666                         io->error = errno;
667                 else
668                         written += size;
669         }
671         return written == bufsize;
674 static bool
675 run_io_buf(const char **argv, char buf[], size_t bufsize)
677         struct io io = {};
678         bool error;
680         if (!run_io_rd(&io, argv, FORMAT_NONE))
681                 return FALSE;
683         io.buf = io.bufpos = buf;
684         io.bufalloc = bufsize;
685         error = !io_get(&io, '\n', TRUE) && io_error(&io);
686         io.buf = NULL;
688         return done_io(&io) || error;
691 static int
692 io_load(struct io *io, const char *separators,
693         int (*read_property)(char *, size_t, char *, size_t))
695         char *name;
696         int state = OK;
698         if (!start_io(io))
699                 return ERR;
701         while (state == OK && (name = io_get(io, '\n', TRUE))) {
702                 char *value;
703                 size_t namelen;
704                 size_t valuelen;
706                 name = chomp_string(name);
707                 namelen = strcspn(name, separators);
709                 if (name[namelen]) {
710                         name[namelen] = 0;
711                         value = chomp_string(name + namelen + 1);
712                         valuelen = strlen(value);
714                 } else {
715                         value = "";
716                         valuelen = 0;
717                 }
719                 state = read_property(name, namelen, value, valuelen);
720         }
722         if (state != ERR && io_error(io))
723                 state = ERR;
724         done_io(io);
726         return state;
729 static int
730 run_io_load(const char **argv, const char *separators,
731             int (*read_property)(char *, size_t, char *, size_t))
733         struct io io = {};
735         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
736                 ? io_load(&io, separators, read_property) : ERR;
740 /*
741  * User requests
742  */
744 #define REQ_INFO \
745         /* XXX: Keep the view request first and in sync with views[]. */ \
746         REQ_GROUP("View switching") \
747         REQ_(VIEW_MAIN,         "Show main view"), \
748         REQ_(VIEW_DIFF,         "Show diff view"), \
749         REQ_(VIEW_LOG,          "Show log view"), \
750         REQ_(VIEW_TREE,         "Show tree view"), \
751         REQ_(VIEW_BLOB,         "Show blob view"), \
752         REQ_(VIEW_BLAME,        "Show blame view"), \
753         REQ_(VIEW_HELP,         "Show help page"), \
754         REQ_(VIEW_PAGER,        "Show pager view"), \
755         REQ_(VIEW_STATUS,       "Show status view"), \
756         REQ_(VIEW_STAGE,        "Show stage view"), \
757         \
758         REQ_GROUP("View manipulation") \
759         REQ_(ENTER,             "Enter current line and scroll"), \
760         REQ_(NEXT,              "Move to next"), \
761         REQ_(PREVIOUS,          "Move to previous"), \
762         REQ_(PARENT,            "Move to parent"), \
763         REQ_(VIEW_NEXT,         "Move focus to next view"), \
764         REQ_(REFRESH,           "Reload and refresh"), \
765         REQ_(MAXIMIZE,          "Maximize the current view"), \
766         REQ_(VIEW_CLOSE,        "Close the current view"), \
767         REQ_(QUIT,              "Close all views and quit"), \
768         \
769         REQ_GROUP("View specific requests") \
770         REQ_(STATUS_UPDATE,     "Update file status"), \
771         REQ_(STATUS_REVERT,     "Revert file changes"), \
772         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
773         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
774         \
775         REQ_GROUP("Cursor navigation") \
776         REQ_(MOVE_UP,           "Move cursor one line up"), \
777         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
778         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
779         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
780         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
781         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
782         \
783         REQ_GROUP("Scrolling") \
784         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
785         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
786         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
787         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
788         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
789         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
790         \
791         REQ_GROUP("Searching") \
792         REQ_(SEARCH,            "Search the view"), \
793         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
794         REQ_(FIND_NEXT,         "Find next search match"), \
795         REQ_(FIND_PREV,         "Find previous search match"), \
796         \
797         REQ_GROUP("Option manipulation") \
798         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
799         REQ_(TOGGLE_DATE,       "Toggle date display"), \
800         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
801         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
802         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
803         \
804         REQ_GROUP("Misc") \
805         REQ_(PROMPT,            "Bring up the prompt"), \
806         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
807         REQ_(SHOW_VERSION,      "Show version information"), \
808         REQ_(STOP_LOADING,      "Stop all loading views"), \
809         REQ_(EDIT,              "Open in editor"), \
810         REQ_(NONE,              "Do nothing")
813 /* User action requests. */
814 enum request {
815 #define REQ_GROUP(help)
816 #define REQ_(req, help) REQ_##req
818         /* Offset all requests to avoid conflicts with ncurses getch values. */
819         REQ_OFFSET = KEY_MAX + 1,
820         REQ_INFO
822 #undef  REQ_GROUP
823 #undef  REQ_
824 };
826 struct request_info {
827         enum request request;
828         const char *name;
829         int namelen;
830         const char *help;
831 };
833 static struct request_info req_info[] = {
834 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
835 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
836         REQ_INFO
837 #undef  REQ_GROUP
838 #undef  REQ_
839 };
841 static enum request
842 get_request(const char *name)
844         int namelen = strlen(name);
845         int i;
847         for (i = 0; i < ARRAY_SIZE(req_info); i++)
848                 if (req_info[i].namelen == namelen &&
849                     !string_enum_compare(req_info[i].name, name, namelen))
850                         return req_info[i].request;
852         return REQ_NONE;
856 /*
857  * Options
858  */
860 /* Option and state variables. */
861 static bool opt_date                    = TRUE;
862 static bool opt_author                  = TRUE;
863 static bool opt_line_number             = FALSE;
864 static bool opt_line_graphics           = TRUE;
865 static bool opt_rev_graph               = FALSE;
866 static bool opt_show_refs               = TRUE;
867 static int opt_num_interval             = NUMBER_INTERVAL;
868 static int opt_tab_size                 = TAB_SIZE;
869 static int opt_author_cols              = AUTHOR_COLS-1;
870 static char opt_path[SIZEOF_STR]        = "";
871 static char opt_file[SIZEOF_STR]        = "";
872 static char opt_ref[SIZEOF_REF]         = "";
873 static char opt_head[SIZEOF_REF]        = "";
874 static char opt_head_rev[SIZEOF_REV]    = "";
875 static char opt_remote[SIZEOF_REF]      = "";
876 static char opt_encoding[20]            = "UTF-8";
877 static bool opt_utf8                    = TRUE;
878 static char opt_codeset[20]             = "UTF-8";
879 static iconv_t opt_iconv                = ICONV_NONE;
880 static char opt_search[SIZEOF_STR]      = "";
881 static char opt_cdup[SIZEOF_STR]        = "";
882 static char opt_prefix[SIZEOF_STR]      = "";
883 static char opt_git_dir[SIZEOF_STR]     = "";
884 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
885 static char opt_editor[SIZEOF_STR]      = "";
886 static FILE *opt_tty                    = NULL;
888 #define is_initial_commit()     (!*opt_head_rev)
889 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
892 /*
893  * Line-oriented content detection.
894  */
896 #define LINE_INFO \
897 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
898 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
899 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
900 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
901 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
902 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
907 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
908 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
909 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
910 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
911 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
912 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
913 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
915 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
916 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
917 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
918 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
919 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
920 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
921 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
922 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
923 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
924 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
926 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
927 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
928 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
929 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
930 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
931 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
932 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
933 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
934 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
935 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
936 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
937 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
938 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
939 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
940 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
941 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
942 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
943 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
944 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
945 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
946 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
947 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
948 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
949 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
950 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
951 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
953 enum line_type {
954 #define LINE(type, line, fg, bg, attr) \
955         LINE_##type
956         LINE_INFO,
957         LINE_NONE
958 #undef  LINE
959 };
961 struct line_info {
962         const char *name;       /* Option name. */
963         int namelen;            /* Size of option name. */
964         const char *line;       /* The start of line to match. */
965         int linelen;            /* Size of string to match. */
966         int fg, bg, attr;       /* Color and text attributes for the lines. */
967 };
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
972         LINE_INFO
973 #undef  LINE
974 };
976 static enum line_type
977 get_line_type(const char *line)
979         int linelen = strlen(line);
980         enum line_type type;
982         for (type = 0; type < ARRAY_SIZE(line_info); type++)
983                 /* Case insensitive search matches Signed-off-by lines better. */
984                 if (linelen >= line_info[type].linelen &&
985                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
986                         return type;
988         return LINE_DEFAULT;
991 static inline int
992 get_line_attr(enum line_type type)
994         assert(type < ARRAY_SIZE(line_info));
995         return COLOR_PAIR(type) | line_info[type].attr;
998 static struct line_info *
999 get_line_info(const char *name)
1001         size_t namelen = strlen(name);
1002         enum line_type type;
1004         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005                 if (namelen == line_info[type].namelen &&
1006                     !string_enum_compare(line_info[type].name, name, namelen))
1007                         return &line_info[type];
1009         return NULL;
1012 static void
1013 init_colors(void)
1015         int default_bg = line_info[LINE_DEFAULT].bg;
1016         int default_fg = line_info[LINE_DEFAULT].fg;
1017         enum line_type type;
1019         start_color();
1021         if (assume_default_colors(default_fg, default_bg) == ERR) {
1022                 default_bg = COLOR_BLACK;
1023                 default_fg = COLOR_WHITE;
1024         }
1026         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027                 struct line_info *info = &line_info[type];
1028                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031                 init_pair(type, fg, bg);
1032         }
1035 struct line {
1036         enum line_type type;
1038         /* State flags */
1039         unsigned int selected:1;
1040         unsigned int dirty:1;
1041         unsigned int cleareol:1;
1043         void *data;             /* User data */
1044 };
1047 /*
1048  * Keys
1049  */
1051 struct keybinding {
1052         int alias;
1053         enum request request;
1054 };
1056 static struct keybinding default_keybindings[] = {
1057         /* View switching */
1058         { 'm',          REQ_VIEW_MAIN },
1059         { 'd',          REQ_VIEW_DIFF },
1060         { 'l',          REQ_VIEW_LOG },
1061         { 't',          REQ_VIEW_TREE },
1062         { 'f',          REQ_VIEW_BLOB },
1063         { 'B',          REQ_VIEW_BLAME },
1064         { 'p',          REQ_VIEW_PAGER },
1065         { 'h',          REQ_VIEW_HELP },
1066         { 'S',          REQ_VIEW_STATUS },
1067         { 'c',          REQ_VIEW_STAGE },
1069         /* View manipulation */
1070         { 'q',          REQ_VIEW_CLOSE },
1071         { KEY_TAB,      REQ_VIEW_NEXT },
1072         { KEY_RETURN,   REQ_ENTER },
1073         { KEY_UP,       REQ_PREVIOUS },
1074         { KEY_DOWN,     REQ_NEXT },
1075         { 'R',          REQ_REFRESH },
1076         { KEY_F(5),     REQ_REFRESH },
1077         { 'O',          REQ_MAXIMIZE },
1079         /* Cursor navigation */
1080         { 'k',          REQ_MOVE_UP },
1081         { 'j',          REQ_MOVE_DOWN },
1082         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1083         { KEY_END,      REQ_MOVE_LAST_LINE },
1084         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1085         { ' ',          REQ_MOVE_PAGE_DOWN },
1086         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1087         { 'b',          REQ_MOVE_PAGE_UP },
1088         { '-',          REQ_MOVE_PAGE_UP },
1090         /* Scrolling */
1091         { KEY_LEFT,     REQ_SCROLL_LEFT },
1092         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1093         { KEY_IC,       REQ_SCROLL_LINE_UP },
1094         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1095         { 'w',          REQ_SCROLL_PAGE_UP },
1096         { 's',          REQ_SCROLL_PAGE_DOWN },
1098         /* Searching */
1099         { '/',          REQ_SEARCH },
1100         { '?',          REQ_SEARCH_BACK },
1101         { 'n',          REQ_FIND_NEXT },
1102         { 'N',          REQ_FIND_PREV },
1104         /* Misc */
1105         { 'Q',          REQ_QUIT },
1106         { 'z',          REQ_STOP_LOADING },
1107         { 'v',          REQ_SHOW_VERSION },
1108         { 'r',          REQ_SCREEN_REDRAW },
1109         { '.',          REQ_TOGGLE_LINENO },
1110         { 'D',          REQ_TOGGLE_DATE },
1111         { 'A',          REQ_TOGGLE_AUTHOR },
1112         { 'g',          REQ_TOGGLE_REV_GRAPH },
1113         { 'F',          REQ_TOGGLE_REFS },
1114         { ':',          REQ_PROMPT },
1115         { 'u',          REQ_STATUS_UPDATE },
1116         { '!',          REQ_STATUS_REVERT },
1117         { 'M',          REQ_STATUS_MERGE },
1118         { '@',          REQ_STAGE_NEXT },
1119         { ',',          REQ_PARENT },
1120         { 'e',          REQ_EDIT },
1121 };
1123 #define KEYMAP_INFO \
1124         KEYMAP_(GENERIC), \
1125         KEYMAP_(MAIN), \
1126         KEYMAP_(DIFF), \
1127         KEYMAP_(LOG), \
1128         KEYMAP_(TREE), \
1129         KEYMAP_(BLOB), \
1130         KEYMAP_(BLAME), \
1131         KEYMAP_(PAGER), \
1132         KEYMAP_(HELP), \
1133         KEYMAP_(STATUS), \
1134         KEYMAP_(STAGE)
1136 enum keymap {
1137 #define KEYMAP_(name) KEYMAP_##name
1138         KEYMAP_INFO
1139 #undef  KEYMAP_
1140 };
1142 static struct int_map keymap_table[] = {
1143 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1144         KEYMAP_INFO
1145 #undef  KEYMAP_
1146 };
1148 #define set_keymap(map, name) \
1149         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1151 struct keybinding_table {
1152         struct keybinding *data;
1153         size_t size;
1154 };
1156 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1158 static void
1159 add_keybinding(enum keymap keymap, enum request request, int key)
1161         struct keybinding_table *table = &keybindings[keymap];
1163         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1164         if (!table->data)
1165                 die("Failed to allocate keybinding");
1166         table->data[table->size].alias = key;
1167         table->data[table->size++].request = request;
1170 /* Looks for a key binding first in the given map, then in the generic map, and
1171  * lastly in the default keybindings. */
1172 static enum request
1173 get_keybinding(enum keymap keymap, int key)
1175         size_t i;
1177         for (i = 0; i < keybindings[keymap].size; i++)
1178                 if (keybindings[keymap].data[i].alias == key)
1179                         return keybindings[keymap].data[i].request;
1181         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1182                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1183                         return keybindings[KEYMAP_GENERIC].data[i].request;
1185         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1186                 if (default_keybindings[i].alias == key)
1187                         return default_keybindings[i].request;
1189         return (enum request) key;
1193 struct key {
1194         const char *name;
1195         int value;
1196 };
1198 static struct key key_table[] = {
1199         { "Enter",      KEY_RETURN },
1200         { "Space",      ' ' },
1201         { "Backspace",  KEY_BACKSPACE },
1202         { "Tab",        KEY_TAB },
1203         { "Escape",     KEY_ESC },
1204         { "Left",       KEY_LEFT },
1205         { "Right",      KEY_RIGHT },
1206         { "Up",         KEY_UP },
1207         { "Down",       KEY_DOWN },
1208         { "Insert",     KEY_IC },
1209         { "Delete",     KEY_DC },
1210         { "Hash",       '#' },
1211         { "Home",       KEY_HOME },
1212         { "End",        KEY_END },
1213         { "PageUp",     KEY_PPAGE },
1214         { "PageDown",   KEY_NPAGE },
1215         { "F1",         KEY_F(1) },
1216         { "F2",         KEY_F(2) },
1217         { "F3",         KEY_F(3) },
1218         { "F4",         KEY_F(4) },
1219         { "F5",         KEY_F(5) },
1220         { "F6",         KEY_F(6) },
1221         { "F7",         KEY_F(7) },
1222         { "F8",         KEY_F(8) },
1223         { "F9",         KEY_F(9) },
1224         { "F10",        KEY_F(10) },
1225         { "F11",        KEY_F(11) },
1226         { "F12",        KEY_F(12) },
1227 };
1229 static int
1230 get_key_value(const char *name)
1232         int i;
1234         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1235                 if (!strcasecmp(key_table[i].name, name))
1236                         return key_table[i].value;
1238         if (strlen(name) == 1 && isprint(*name))
1239                 return (int) *name;
1241         return ERR;
1244 static const char *
1245 get_key_name(int key_value)
1247         static char key_char[] = "'X'";
1248         const char *seq = NULL;
1249         int key;
1251         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1252                 if (key_table[key].value == key_value)
1253                         seq = key_table[key].name;
1255         if (seq == NULL &&
1256             key_value < 127 &&
1257             isprint(key_value)) {
1258                 key_char[1] = (char) key_value;
1259                 seq = key_char;
1260         }
1262         return seq ? seq : "(no key)";
1265 static const char *
1266 get_key(enum request request)
1268         static char buf[BUFSIZ];
1269         size_t pos = 0;
1270         char *sep = "";
1271         int i;
1273         buf[pos] = 0;
1275         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1276                 struct keybinding *keybinding = &default_keybindings[i];
1278                 if (keybinding->request != request)
1279                         continue;
1281                 if (!string_format_from(buf, &pos, "%s%s", sep,
1282                                         get_key_name(keybinding->alias)))
1283                         return "Too many keybindings!";
1284                 sep = ", ";
1285         }
1287         return buf;
1290 struct run_request {
1291         enum keymap keymap;
1292         int key;
1293         const char *argv[SIZEOF_ARG];
1294 };
1296 static struct run_request *run_request;
1297 static size_t run_requests;
1299 static enum request
1300 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1302         struct run_request *req;
1304         if (argc >= ARRAY_SIZE(req->argv) - 1)
1305                 return REQ_NONE;
1307         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1308         if (!req)
1309                 return REQ_NONE;
1311         run_request = req;
1312         req = &run_request[run_requests];
1313         req->keymap = keymap;
1314         req->key = key;
1315         req->argv[0] = NULL;
1317         if (!format_argv(req->argv, argv, FORMAT_NONE))
1318                 return REQ_NONE;
1320         return REQ_NONE + ++run_requests;
1323 static struct run_request *
1324 get_run_request(enum request request)
1326         if (request <= REQ_NONE)
1327                 return NULL;
1328         return &run_request[request - REQ_NONE - 1];
1331 static void
1332 add_builtin_run_requests(void)
1334         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1335         const char *gc[] = { "git", "gc", NULL };
1336         struct {
1337                 enum keymap keymap;
1338                 int key;
1339                 int argc;
1340                 const char **argv;
1341         } reqs[] = {
1342                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1343                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1344         };
1345         int i;
1347         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1348                 enum request req;
1350                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1351                 if (req != REQ_NONE)
1352                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1353         }
1356 /*
1357  * User config file handling.
1358  */
1360 static struct int_map color_map[] = {
1361 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1362         COLOR_MAP(DEFAULT),
1363         COLOR_MAP(BLACK),
1364         COLOR_MAP(BLUE),
1365         COLOR_MAP(CYAN),
1366         COLOR_MAP(GREEN),
1367         COLOR_MAP(MAGENTA),
1368         COLOR_MAP(RED),
1369         COLOR_MAP(WHITE),
1370         COLOR_MAP(YELLOW),
1371 };
1373 #define set_color(color, name) \
1374         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1376 static struct int_map attr_map[] = {
1377 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1378         ATTR_MAP(NORMAL),
1379         ATTR_MAP(BLINK),
1380         ATTR_MAP(BOLD),
1381         ATTR_MAP(DIM),
1382         ATTR_MAP(REVERSE),
1383         ATTR_MAP(STANDOUT),
1384         ATTR_MAP(UNDERLINE),
1385 };
1387 #define set_attribute(attr, name) \
1388         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1390 static int   config_lineno;
1391 static bool  config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attr] */
1395 static int
1396 option_color_command(int argc, const char *argv[])
1398         struct line_info *info;
1400         if (argc != 3 && argc != 4) {
1401                 config_msg = "Wrong number of arguments given to color command";
1402                 return ERR;
1403         }
1405         info = get_line_info(argv[0]);
1406         if (!info) {
1407                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408                         info = get_line_info("delimiter");
1410                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411                         info = get_line_info("date");
1413                 } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
1414                         info = get_line_info("author");
1416                 } else {
1417                         config_msg = "Unknown color name";
1418                         return ERR;
1419                 }
1420         }
1422         if (set_color(&info->fg, argv[1]) == ERR ||
1423             set_color(&info->bg, argv[2]) == ERR) {
1424                 config_msg = "Unknown color";
1425                 return ERR;
1426         }
1428         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1429                 config_msg = "Unknown attribute";
1430                 return ERR;
1431         }
1433         return OK;
1436 static bool parse_bool(const char *s)
1438         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1439                 !strcmp(s, "yes")) ? TRUE : FALSE;
1442 static int
1443 parse_int(const char *s, int default_value, int min, int max)
1445         int value = atoi(s);
1447         return (value < min || value > max) ? default_value : value;
1450 /* Wants: name = value */
1451 static int
1452 option_set_command(int argc, const char *argv[])
1454         if (argc != 3) {
1455                 config_msg = "Wrong number of arguments given to set command";
1456                 return ERR;
1457         }
1459         if (strcmp(argv[1], "=")) {
1460                 config_msg = "No value assigned";
1461                 return ERR;
1462         }
1464         if (!strcmp(argv[0], "show-author")) {
1465                 opt_author = parse_bool(argv[2]);
1466                 return OK;
1467         }
1469         if (!strcmp(argv[0], "show-date")) {
1470                 opt_date = parse_bool(argv[2]);
1471                 return OK;
1472         }
1474         if (!strcmp(argv[0], "show-rev-graph")) {
1475                 opt_rev_graph = parse_bool(argv[2]);
1476                 return OK;
1477         }
1479         if (!strcmp(argv[0], "show-refs")) {
1480                 opt_show_refs = parse_bool(argv[2]);
1481                 return OK;
1482         }
1484         if (!strcmp(argv[0], "show-line-numbers")) {
1485                 opt_line_number = parse_bool(argv[2]);
1486                 return OK;
1487         }
1489         if (!strcmp(argv[0], "line-graphics")) {
1490                 opt_line_graphics = parse_bool(argv[2]);
1491                 return OK;
1492         }
1494         if (!strcmp(argv[0], "line-number-interval")) {
1495                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1496                 return OK;
1497         }
1499         if (!strcmp(argv[0], "author-width")) {
1500                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1501                 return OK;
1502         }
1504         if (!strcmp(argv[0], "tab-size")) {
1505                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1506                 return OK;
1507         }
1509         if (!strcmp(argv[0], "commit-encoding")) {
1510                 const char *arg = argv[2];
1511                 int arglen = strlen(arg);
1513                 switch (arg[0]) {
1514                 case '"':
1515                 case '\'':
1516                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1517                                 config_msg = "Unmatched quotation";
1518                                 return ERR;
1519                         }
1520                         arg += 1; arglen -= 2;
1521                 default:
1522                         string_ncopy(opt_encoding, arg, strlen(arg));
1523                         return OK;
1524                 }
1525         }
1527         config_msg = "Unknown variable name";
1528         return ERR;
1531 /* Wants: mode request key */
1532 static int
1533 option_bind_command(int argc, const char *argv[])
1535         enum request request;
1536         int keymap;
1537         int key;
1539         if (argc < 3) {
1540                 config_msg = "Wrong number of arguments given to bind command";
1541                 return ERR;
1542         }
1544         if (set_keymap(&keymap, argv[0]) == ERR) {
1545                 config_msg = "Unknown key map";
1546                 return ERR;
1547         }
1549         key = get_key_value(argv[1]);
1550         if (key == ERR) {
1551                 config_msg = "Unknown key";
1552                 return ERR;
1553         }
1555         request = get_request(argv[2]);
1556         if (request == REQ_NONE) {
1557                 struct {
1558                         const char *name;
1559                         enum request request;
1560                 } obsolete[] = {
1561                         { "cherry-pick",        REQ_NONE },
1562                         { "screen-resize",      REQ_NONE },
1563                         { "tree-parent",        REQ_PARENT },
1564                 };
1565                 size_t namelen = strlen(argv[2]);
1566                 int i;
1568                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1569                         if (namelen != strlen(obsolete[i].name) ||
1570                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1571                                 continue;
1572                         if (obsolete[i].request != REQ_NONE)
1573                                 add_keybinding(keymap, obsolete[i].request, key);
1574                         config_msg = "Obsolete request name";
1575                         return ERR;
1576                 }
1577         }
1578         if (request == REQ_NONE && *argv[2]++ == '!')
1579                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1580         if (request == REQ_NONE) {
1581                 config_msg = "Unknown request name";
1582                 return ERR;
1583         }
1585         add_keybinding(keymap, request, key);
1587         return OK;
1590 static int
1591 set_option(const char *opt, char *value)
1593         const char *argv[SIZEOF_ARG];
1594         int argc = 0;
1596         if (!argv_from_string(argv, &argc, value)) {
1597                 config_msg = "Too many option arguments";
1598                 return ERR;
1599         }
1601         if (!strcmp(opt, "color"))
1602                 return option_color_command(argc, argv);
1604         if (!strcmp(opt, "set"))
1605                 return option_set_command(argc, argv);
1607         if (!strcmp(opt, "bind"))
1608                 return option_bind_command(argc, argv);
1610         config_msg = "Unknown option command";
1611         return ERR;
1614 static int
1615 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1617         int status = OK;
1619         config_lineno++;
1620         config_msg = "Internal error";
1622         /* Check for comment markers, since read_properties() will
1623          * only ensure opt and value are split at first " \t". */
1624         optlen = strcspn(opt, "#");
1625         if (optlen == 0)
1626                 return OK;
1628         if (opt[optlen] != 0) {
1629                 config_msg = "No option value";
1630                 status = ERR;
1632         }  else {
1633                 /* Look for comment endings in the value. */
1634                 size_t len = strcspn(value, "#");
1636                 if (len < valuelen) {
1637                         valuelen = len;
1638                         value[valuelen] = 0;
1639                 }
1641                 status = set_option(opt, value);
1642         }
1644         if (status == ERR) {
1645                 warn("Error on line %d, near '%.*s': %s",
1646                      config_lineno, (int) optlen, opt, config_msg);
1647                 config_errors = TRUE;
1648         }
1650         /* Always keep going if errors are encountered. */
1651         return OK;
1654 static void
1655 load_option_file(const char *path)
1657         struct io io = {};
1659         /* It's ok that the file doesn't exist. */
1660         if (!io_open(&io, path))
1661                 return;
1663         config_lineno = 0;
1664         config_errors = FALSE;
1666         if (io_load(&io, " \t", read_option) == ERR ||
1667             config_errors == TRUE)
1668                 warn("Errors while loading %s.", path);
1671 static int
1672 load_options(void)
1674         const char *home = getenv("HOME");
1675         const char *tigrc_user = getenv("TIGRC_USER");
1676         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1677         char buf[SIZEOF_STR];
1679         add_builtin_run_requests();
1681         if (!tigrc_system) {
1682                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1683                         return ERR;
1684                 tigrc_system = buf;
1685         }
1686         load_option_file(tigrc_system);
1688         if (!tigrc_user) {
1689                 if (!home || !string_format(buf, "%s/.tigrc", home))
1690                         return ERR;
1691                 tigrc_user = buf;
1692         }
1693         load_option_file(tigrc_user);
1695         return OK;
1699 /*
1700  * The viewer
1701  */
1703 struct view;
1704 struct view_ops;
1706 /* The display array of active views and the index of the current view. */
1707 static struct view *display[2];
1708 static unsigned int current_view;
1710 #define foreach_displayed_view(view, i) \
1711         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1713 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1715 /* Current head and commit ID */
1716 static char ref_blob[SIZEOF_REF]        = "";
1717 static char ref_commit[SIZEOF_REF]      = "HEAD";
1718 static char ref_head[SIZEOF_REF]        = "HEAD";
1720 struct view {
1721         const char *name;       /* View name */
1722         const char *cmd_env;    /* Command line set via environment */
1723         const char *id;         /* Points to either of ref_{head,commit,blob} */
1725         struct view_ops *ops;   /* View operations */
1727         enum keymap keymap;     /* What keymap does this view have */
1728         bool git_dir;           /* Whether the view requires a git directory. */
1730         char ref[SIZEOF_REF];   /* Hovered commit reference */
1731         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1733         int height, width;      /* The width and height of the main window */
1734         WINDOW *win;            /* The main window */
1735         WINDOW *title;          /* The title window living below the main window */
1737         /* Navigation */
1738         unsigned long offset;   /* Offset of the window top */
1739         unsigned long yoffset;  /* Offset from the window side. */
1740         unsigned long lineno;   /* Current line number */
1741         unsigned long p_offset; /* Previous offset of the window top */
1742         unsigned long p_yoffset;/* Previous offset from the window side */
1743         unsigned long p_lineno; /* Previous current line number */
1744         bool p_restore;         /* Should the previous position be restored. */
1746         /* Searching */
1747         char grep[SIZEOF_STR];  /* Search string */
1748         regex_t *regex;         /* Pre-compiled regex */
1750         /* If non-NULL, points to the view that opened this view. If this view
1751          * is closed tig will switch back to the parent view. */
1752         struct view *parent;
1754         /* Buffering */
1755         size_t lines;           /* Total number of lines */
1756         struct line *line;      /* Line index */
1757         size_t line_alloc;      /* Total number of allocated lines */
1758         unsigned int digits;    /* Number of digits in the lines member. */
1760         /* Drawing */
1761         struct line *curline;   /* Line currently being drawn. */
1762         enum line_type curtype; /* Attribute currently used for drawing. */
1763         unsigned long col;      /* Column when drawing. */
1764         bool has_scrolled;      /* View was scrolled. */
1765         bool can_hscroll;       /* View can be scrolled horizontally. */
1767         /* Loading */
1768         struct io io;
1769         struct io *pipe;
1770         time_t start_time;
1771         time_t update_secs;
1772 };
1774 struct view_ops {
1775         /* What type of content being displayed. Used in the title bar. */
1776         const char *type;
1777         /* Default command arguments. */
1778         const char **argv;
1779         /* Open and reads in all view content. */
1780         bool (*open)(struct view *view);
1781         /* Read one line; updates view->line. */
1782         bool (*read)(struct view *view, char *data);
1783         /* Draw one line; @lineno must be < view->height. */
1784         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1785         /* Depending on view handle a special requests. */
1786         enum request (*request)(struct view *view, enum request request, struct line *line);
1787         /* Search for regex in a line. */
1788         bool (*grep)(struct view *view, struct line *line);
1789         /* Select line */
1790         void (*select)(struct view *view, struct line *line);
1791 };
1793 static struct view_ops blame_ops;
1794 static struct view_ops blob_ops;
1795 static struct view_ops diff_ops;
1796 static struct view_ops help_ops;
1797 static struct view_ops log_ops;
1798 static struct view_ops main_ops;
1799 static struct view_ops pager_ops;
1800 static struct view_ops stage_ops;
1801 static struct view_ops status_ops;
1802 static struct view_ops tree_ops;
1804 #define VIEW_STR(name, env, ref, ops, map, git) \
1805         { name, #env, ref, ops, map, git }
1807 #define VIEW_(id, name, ops, git, ref) \
1808         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1811 static struct view views[] = {
1812         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1813         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1814         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1815         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1816         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1817         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1818         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1819         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1820         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1821         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1822 };
1824 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1825 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1827 #define foreach_view(view, i) \
1828         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1830 #define view_is_displayed(view) \
1831         (view == display[0] || view == display[1])
1834 enum line_graphic {
1835         LINE_GRAPHIC_VLINE
1836 };
1838 static int line_graphics[] = {
1839         /* LINE_GRAPHIC_VLINE: */ '|'
1840 };
1842 static inline void
1843 set_view_attr(struct view *view, enum line_type type)
1845         if (!view->curline->selected && view->curtype != type) {
1846                 wattrset(view->win, get_line_attr(type));
1847                 wchgat(view->win, -1, 0, type, NULL);
1848                 view->curtype = type;
1849         }
1852 static int
1853 draw_chars(struct view *view, enum line_type type, const char *string,
1854            int max_len, bool use_tilde)
1856         int len = 0;
1857         int col = 0;
1858         int trimmed = FALSE;
1859         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1861         if (max_len <= 0)
1862                 return 0;
1864         if (opt_utf8) {
1865                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1866         } else {
1867                 col = len = strlen(string);
1868                 if (len > max_len) {
1869                         if (use_tilde) {
1870                                 max_len -= 1;
1871                         }
1872                         col = len = max_len;
1873                         trimmed = TRUE;
1874                 }
1875         }
1877         set_view_attr(view, type);
1878         if (len > 0)
1879                 waddnstr(view->win, string, len);
1880         if (trimmed && use_tilde) {
1881                 set_view_attr(view, LINE_DELIMITER);
1882                 waddch(view->win, '~');
1883                 col++;
1884         }
1886         if (view->col + col >= view->width + view->yoffset)
1887                 view->can_hscroll = TRUE;
1889         return col;
1892 static int
1893 draw_space(struct view *view, enum line_type type, int max, int spaces)
1895         static char space[] = "                    ";
1896         int col = 0;
1898         spaces = MIN(max, spaces);
1900         while (spaces > 0) {
1901                 int len = MIN(spaces, sizeof(space) - 1);
1903                 col += draw_chars(view, type, space, spaces, FALSE);
1904                 spaces -= len;
1905         }
1907         return col;
1910 static bool
1911 draw_lineno(struct view *view, unsigned int lineno)
1913         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1914         char number[10];
1915         int digits3 = view->digits < 3 ? 3 : view->digits;
1916         int max_number = MIN(digits3, STRING_SIZE(number));
1917         int max = view->width - view->col;
1918         int col;
1920         if (max < max_number)
1921                 max_number = max;
1923         lineno += view->offset + 1;
1924         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1925                 static char fmt[] = "%1ld";
1927                 if (view->digits <= 9)
1928                         fmt[1] = '0' + digits3;
1930                 if (!string_format(number, fmt, lineno))
1931                         number[0] = 0;
1932                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1933         } else {
1934                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1935         }
1937         if (col < max && skip <= col) {
1938                 set_view_attr(view, LINE_DEFAULT);
1939                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1940         }
1941         col++;
1943         view->col += col;
1944         if (col < max && skip <= col)
1945                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1946         view->col++;
1948         return view->width + view->yoffset <= view->col;
1951 static bool
1952 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1954         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1955         return view->width - view->col <= 0;
1958 static bool
1959 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1961         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1962         int max = view->width - view->col;
1963         int i;
1965         if (max < size)
1966                 size = max;
1968         set_view_attr(view, type);
1969         /* Using waddch() instead of waddnstr() ensures that
1970          * they'll be rendered correctly for the cursor line. */
1971         for (i = skip; i < size; i++)
1972                 waddch(view->win, graphic[i]);
1974         view->col += size;
1975         if (size < max && skip <= size)
1976                 waddch(view->win, ' ');
1977         view->col++;
1979         return view->width - view->col <= 0;
1982 static bool
1983 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1985         int max = MIN(view->width - view->col, len);
1986         int col;
1988         if (text)
1989                 col = draw_chars(view, type, text, max - 1, trim);
1990         else
1991                 col = draw_space(view, type, max - 1, max - 1);
1993         view->col += col;
1994         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1995         return view->width + view->yoffset <= view->col;
1998 static bool
1999 draw_date(struct view *view, struct tm *time)
2001         char buf[DATE_COLS];
2002         char *date;
2003         int timelen = 0;
2005         if (time)
2006                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2007         date = timelen ? buf : NULL;
2009         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2012 static bool
2013 draw_author(struct view *view, const char *author)
2015         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2017         if (!trim) {
2018                 static char initials[10];
2019                 size_t pos;
2021 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2023                 memset(initials, 0, sizeof(initials));
2024                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2025                         while (is_initial_sep(*author))
2026                                 author++;
2027                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2028                         while (*author && !is_initial_sep(author[1]))
2029                                 author++;
2030                 }
2032                 author = initials;
2033         }
2035         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2038 static bool
2039 draw_view_line(struct view *view, unsigned int lineno)
2041         struct line *line;
2042         bool selected = (view->offset + lineno == view->lineno);
2044         assert(view_is_displayed(view));
2046         if (view->offset + lineno >= view->lines)
2047                 return FALSE;
2049         line = &view->line[view->offset + lineno];
2051         wmove(view->win, lineno, 0);
2052         if (line->cleareol)
2053                 wclrtoeol(view->win);
2054         view->col = 0;
2055         view->curline = line;
2056         view->curtype = LINE_NONE;
2057         line->selected = FALSE;
2058         line->dirty = line->cleareol = 0;
2060         if (selected) {
2061                 set_view_attr(view, LINE_CURSOR);
2062                 line->selected = TRUE;
2063                 view->ops->select(view, line);
2064         }
2066         return view->ops->draw(view, line, lineno);
2069 static void
2070 redraw_view_dirty(struct view *view)
2072         bool dirty = FALSE;
2073         int lineno;
2075         for (lineno = 0; lineno < view->height; lineno++) {
2076                 if (view->offset + lineno >= view->lines)
2077                         break;
2078                 if (!view->line[view->offset + lineno].dirty)
2079                         continue;
2080                 dirty = TRUE;
2081                 if (!draw_view_line(view, lineno))
2082                         break;
2083         }
2085         if (!dirty)
2086                 return;
2087         wnoutrefresh(view->win);
2090 static void
2091 redraw_view_from(struct view *view, int lineno)
2093         assert(0 <= lineno && lineno < view->height);
2095         if (lineno == 0)
2096                 view->can_hscroll = FALSE;
2098         for (; lineno < view->height; lineno++) {
2099                 if (!draw_view_line(view, lineno))
2100                         break;
2101         }
2103         wnoutrefresh(view->win);
2106 static void
2107 redraw_view(struct view *view)
2109         werase(view->win);
2110         redraw_view_from(view, 0);
2114 static void
2115 update_view_title(struct view *view)
2117         char buf[SIZEOF_STR];
2118         char state[SIZEOF_STR];
2119         size_t bufpos = 0, statelen = 0;
2121         assert(view_is_displayed(view));
2123         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2124                 unsigned int view_lines = view->offset + view->height;
2125                 unsigned int lines = view->lines
2126                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2127                                    : 0;
2129                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2130                                    view->ops->type,
2131                                    view->lineno + 1,
2132                                    view->lines,
2133                                    lines);
2135         }
2137         if (view->pipe) {
2138                 time_t secs = time(NULL) - view->start_time;
2140                 /* Three git seconds are a long time ... */
2141                 if (secs > 2)
2142                         string_format_from(state, &statelen, " loading %lds", secs);
2143         }
2145         string_format_from(buf, &bufpos, "[%s]", view->name);
2146         if (*view->ref && bufpos < view->width) {
2147                 size_t refsize = strlen(view->ref);
2148                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2150                 if (minsize < view->width)
2151                         refsize = view->width - minsize + 7;
2152                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2153         }
2155         if (statelen && bufpos < view->width) {
2156                 string_format_from(buf, &bufpos, "%s", state);
2157         }
2159         if (view == display[current_view])
2160                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2161         else
2162                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2164         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2165         wclrtoeol(view->title);
2166         wnoutrefresh(view->title);
2169 static void
2170 resize_display(void)
2172         int offset, i;
2173         struct view *base = display[0];
2174         struct view *view = display[1] ? display[1] : display[0];
2176         /* Setup window dimensions */
2178         getmaxyx(stdscr, base->height, base->width);
2180         /* Make room for the status window. */
2181         base->height -= 1;
2183         if (view != base) {
2184                 /* Horizontal split. */
2185                 view->width   = base->width;
2186                 view->height  = SCALE_SPLIT_VIEW(base->height);
2187                 base->height -= view->height;
2189                 /* Make room for the title bar. */
2190                 view->height -= 1;
2191         }
2193         /* Make room for the title bar. */
2194         base->height -= 1;
2196         offset = 0;
2198         foreach_displayed_view (view, i) {
2199                 if (!view->win) {
2200                         view->win = newwin(view->height, 0, offset, 0);
2201                         if (!view->win)
2202                                 die("Failed to create %s view", view->name);
2204                         scrollok(view->win, FALSE);
2206                         view->title = newwin(1, 0, offset + view->height, 0);
2207                         if (!view->title)
2208                                 die("Failed to create title window");
2210                 } else {
2211                         wresize(view->win, view->height, view->width);
2212                         mvwin(view->win,   offset, 0);
2213                         mvwin(view->title, offset + view->height, 0);
2214                 }
2216                 offset += view->height + 1;
2217         }
2220 static void
2221 redraw_display(bool clear)
2223         struct view *view;
2224         int i;
2226         foreach_displayed_view (view, i) {
2227                 if (clear)
2228                         wclear(view->win);
2229                 redraw_view(view);
2230                 update_view_title(view);
2231         }
2234 static void
2235 toggle_view_option(bool *option, const char *help)
2237         *option = !*option;
2238         redraw_display(FALSE);
2239         report("%sabling %s", *option ? "En" : "Dis", help);
2242 /*
2243  * Navigation
2244  */
2246 /* Scrolling backend */
2247 static void
2248 do_scroll_view(struct view *view, int lines)
2250         bool redraw_current_line = FALSE;
2252         /* The rendering expects the new offset. */
2253         view->offset += lines;
2255         assert(0 <= view->offset && view->offset < view->lines);
2256         assert(lines);
2258         /* Move current line into the view. */
2259         if (view->lineno < view->offset) {
2260                 view->lineno = view->offset;
2261                 redraw_current_line = TRUE;
2262         } else if (view->lineno >= view->offset + view->height) {
2263                 view->lineno = view->offset + view->height - 1;
2264                 redraw_current_line = TRUE;
2265         }
2267         assert(view->offset <= view->lineno && view->lineno < view->lines);
2269         /* Redraw the whole screen if scrolling is pointless. */
2270         if (view->height < ABS(lines)) {
2271                 redraw_view(view);
2273         } else {
2274                 int line = lines > 0 ? view->height - lines : 0;
2275                 int end = line + ABS(lines);
2277                 scrollok(view->win, TRUE);
2278                 wscrl(view->win, lines);
2279                 scrollok(view->win, FALSE);
2281                 while (line < end && draw_view_line(view, line))
2282                         line++;
2284                 if (redraw_current_line)
2285                         draw_view_line(view, view->lineno - view->offset);
2286                 wnoutrefresh(view->win);
2287         }
2289         view->has_scrolled = TRUE;
2290         report("");
2293 /* Scroll frontend */
2294 static void
2295 scroll_view(struct view *view, enum request request)
2297         int lines = 1;
2299         assert(view_is_displayed(view));
2301         switch (request) {
2302         case REQ_SCROLL_LEFT:
2303                 if (view->yoffset == 0) {
2304                         report("Cannot scroll beyond the first column");
2305                         return;
2306                 }
2307                 if (view->yoffset <= SCROLL_INTERVAL)
2308                         view->yoffset = 0;
2309                 else
2310                         view->yoffset -= SCROLL_INTERVAL;
2311                 redraw_view_from(view, 0);
2312                 report("");
2313                 return;
2314         case REQ_SCROLL_RIGHT:
2315                 if (!view->can_hscroll) {
2316                         report("Cannot scroll beyond the last column");
2317                         return;
2318                 }
2319                 view->yoffset += SCROLL_INTERVAL;
2320                 redraw_view(view);
2321                 report("");
2322                 return;
2323         case REQ_SCROLL_PAGE_DOWN:
2324                 lines = view->height;
2325         case REQ_SCROLL_LINE_DOWN:
2326                 if (view->offset + lines > view->lines)
2327                         lines = view->lines - view->offset;
2329                 if (lines == 0 || view->offset + view->height >= view->lines) {
2330                         report("Cannot scroll beyond the last line");
2331                         return;
2332                 }
2333                 break;
2335         case REQ_SCROLL_PAGE_UP:
2336                 lines = view->height;
2337         case REQ_SCROLL_LINE_UP:
2338                 if (lines > view->offset)
2339                         lines = view->offset;
2341                 if (lines == 0) {
2342                         report("Cannot scroll beyond the first line");
2343                         return;
2344                 }
2346                 lines = -lines;
2347                 break;
2349         default:
2350                 die("request %d not handled in switch", request);
2351         }
2353         do_scroll_view(view, lines);
2356 /* Cursor moving */
2357 static void
2358 move_view(struct view *view, enum request request)
2360         int scroll_steps = 0;
2361         int steps;
2363         switch (request) {
2364         case REQ_MOVE_FIRST_LINE:
2365                 steps = -view->lineno;
2366                 break;
2368         case REQ_MOVE_LAST_LINE:
2369                 steps = view->lines - view->lineno - 1;
2370                 break;
2372         case REQ_MOVE_PAGE_UP:
2373                 steps = view->height > view->lineno
2374                       ? -view->lineno : -view->height;
2375                 break;
2377         case REQ_MOVE_PAGE_DOWN:
2378                 steps = view->lineno + view->height >= view->lines
2379                       ? view->lines - view->lineno - 1 : view->height;
2380                 break;
2382         case REQ_MOVE_UP:
2383                 steps = -1;
2384                 break;
2386         case REQ_MOVE_DOWN:
2387                 steps = 1;
2388                 break;
2390         default:
2391                 die("request %d not handled in switch", request);
2392         }
2394         if (steps <= 0 && view->lineno == 0) {
2395                 report("Cannot move beyond the first line");
2396                 return;
2398         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2399                 report("Cannot move beyond the last line");
2400                 return;
2401         }
2403         /* Move the current line */
2404         view->lineno += steps;
2405         assert(0 <= view->lineno && view->lineno < view->lines);
2407         /* Check whether the view needs to be scrolled */
2408         if (view->lineno < view->offset ||
2409             view->lineno >= view->offset + view->height) {
2410                 scroll_steps = steps;
2411                 if (steps < 0 && -steps > view->offset) {
2412                         scroll_steps = -view->offset;
2414                 } else if (steps > 0) {
2415                         if (view->lineno == view->lines - 1 &&
2416                             view->lines > view->height) {
2417                                 scroll_steps = view->lines - view->offset - 1;
2418                                 if (scroll_steps >= view->height)
2419                                         scroll_steps -= view->height - 1;
2420                         }
2421                 }
2422         }
2424         if (!view_is_displayed(view)) {
2425                 view->offset += scroll_steps;
2426                 assert(0 <= view->offset && view->offset < view->lines);
2427                 view->ops->select(view, &view->line[view->lineno]);
2428                 return;
2429         }
2431         /* Repaint the old "current" line if we be scrolling */
2432         if (ABS(steps) < view->height)
2433                 draw_view_line(view, view->lineno - steps - view->offset);
2435         if (scroll_steps) {
2436                 do_scroll_view(view, scroll_steps);
2437                 return;
2438         }
2440         /* Draw the current line */
2441         draw_view_line(view, view->lineno - view->offset);
2443         wnoutrefresh(view->win);
2444         report("");
2448 /*
2449  * Searching
2450  */
2452 static void search_view(struct view *view, enum request request);
2454 static void
2455 select_view_line(struct view *view, unsigned long lineno)
2457         if (lineno - view->offset >= view->height) {
2458                 view->offset = lineno;
2459                 view->lineno = lineno;
2460                 if (view_is_displayed(view))
2461                         redraw_view(view);
2463         } else {
2464                 unsigned long old_lineno = view->lineno - view->offset;
2466                 view->lineno = lineno;
2467                 if (view_is_displayed(view)) {
2468                         draw_view_line(view, old_lineno);
2469                         draw_view_line(view, view->lineno - view->offset);
2470                         wnoutrefresh(view->win);
2471                 } else {
2472                         view->ops->select(view, &view->line[view->lineno]);
2473                 }
2474         }
2477 static void
2478 find_next(struct view *view, enum request request)
2480         unsigned long lineno = view->lineno;
2481         int direction;
2483         if (!*view->grep) {
2484                 if (!*opt_search)
2485                         report("No previous search");
2486                 else
2487                         search_view(view, request);
2488                 return;
2489         }
2491         switch (request) {
2492         case REQ_SEARCH:
2493         case REQ_FIND_NEXT:
2494                 direction = 1;
2495                 break;
2497         case REQ_SEARCH_BACK:
2498         case REQ_FIND_PREV:
2499                 direction = -1;
2500                 break;
2502         default:
2503                 return;
2504         }
2506         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2507                 lineno += direction;
2509         /* Note, lineno is unsigned long so will wrap around in which case it
2510          * will become bigger than view->lines. */
2511         for (; lineno < view->lines; lineno += direction) {
2512                 if (view->ops->grep(view, &view->line[lineno])) {
2513                         select_view_line(view, lineno);
2514                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2515                         return;
2516                 }
2517         }
2519         report("No match found for '%s'", view->grep);
2522 static void
2523 search_view(struct view *view, enum request request)
2525         int regex_err;
2527         if (view->regex) {
2528                 regfree(view->regex);
2529                 *view->grep = 0;
2530         } else {
2531                 view->regex = calloc(1, sizeof(*view->regex));
2532                 if (!view->regex)
2533                         return;
2534         }
2536         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2537         if (regex_err != 0) {
2538                 char buf[SIZEOF_STR] = "unknown error";
2540                 regerror(regex_err, view->regex, buf, sizeof(buf));
2541                 report("Search failed: %s", buf);
2542                 return;
2543         }
2545         string_copy(view->grep, opt_search);
2547         find_next(view, request);
2550 /*
2551  * Incremental updating
2552  */
2554 static void
2555 reset_view(struct view *view)
2557         int i;
2559         for (i = 0; i < view->lines; i++)
2560                 free(view->line[i].data);
2561         free(view->line);
2563         view->p_offset = view->offset;
2564         view->p_yoffset = view->yoffset;
2565         view->p_lineno = view->lineno;
2567         view->line = NULL;
2568         view->offset = 0;
2569         view->yoffset = 0;
2570         view->lines  = 0;
2571         view->lineno = 0;
2572         view->line_alloc = 0;
2573         view->vid[0] = 0;
2574         view->update_secs = 0;
2577 static void
2578 free_argv(const char *argv[])
2580         int argc;
2582         for (argc = 0; argv[argc]; argc++)
2583                 free((void *) argv[argc]);
2586 static bool
2587 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2589         char buf[SIZEOF_STR];
2590         int argc;
2591         bool noreplace = flags == FORMAT_NONE;
2593         free_argv(dst_argv);
2595         for (argc = 0; src_argv[argc]; argc++) {
2596                 const char *arg = src_argv[argc];
2597                 size_t bufpos = 0;
2599                 while (arg) {
2600                         char *next = strstr(arg, "%(");
2601                         int len = next - arg;
2602                         const char *value;
2604                         if (!next || noreplace) {
2605                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2606                                         noreplace = TRUE;
2607                                 len = strlen(arg);
2608                                 value = "";
2610                         } else if (!prefixcmp(next, "%(directory)")) {
2611                                 value = opt_path;
2613                         } else if (!prefixcmp(next, "%(file)")) {
2614                                 value = opt_file;
2616                         } else if (!prefixcmp(next, "%(ref)")) {
2617                                 value = *opt_ref ? opt_ref : "HEAD";
2619                         } else if (!prefixcmp(next, "%(head)")) {
2620                                 value = ref_head;
2622                         } else if (!prefixcmp(next, "%(commit)")) {
2623                                 value = ref_commit;
2625                         } else if (!prefixcmp(next, "%(blob)")) {
2626                                 value = ref_blob;
2628                         } else {
2629                                 report("Unknown replacement: `%s`", next);
2630                                 return FALSE;
2631                         }
2633                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2634                                 return FALSE;
2636                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2637                 }
2639                 dst_argv[argc] = strdup(buf);
2640                 if (!dst_argv[argc])
2641                         break;
2642         }
2644         dst_argv[argc] = NULL;
2646         return src_argv[argc] == NULL;
2649 static bool
2650 restore_view_position(struct view *view)
2652         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2653                 return FALSE;
2655         /* Changing the view position cancels the restoring. */
2656         /* FIXME: Changing back to the first line is not detected. */
2657         if (view->offset != 0 || view->lineno != 0) {
2658                 view->p_restore = FALSE;
2659                 return FALSE;
2660         }
2662         if (view->p_lineno >= view->lines) {
2663                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2664                 if (view->p_offset >= view->p_lineno) {
2665                         unsigned long half = view->height / 2;
2667                         if (view->p_lineno > half)
2668                                 view->p_offset = view->p_lineno - half;
2669                         else
2670                                 view->p_offset = 0;
2671                 }
2672         }
2674         if (view_is_displayed(view) &&
2675             view->offset != view->p_offset &&
2676             view->lineno != view->p_lineno)
2677                 werase(view->win);
2679         view->offset = view->p_offset;
2680         view->yoffset = view->p_yoffset;
2681         view->lineno = view->p_lineno;
2682         view->p_restore = FALSE;
2684         return TRUE;
2687 static void
2688 end_update(struct view *view, bool force)
2690         if (!view->pipe)
2691                 return;
2692         while (!view->ops->read(view, NULL))
2693                 if (!force)
2694                         return;
2695         set_nonblocking_input(FALSE);
2696         if (force)
2697                 kill_io(view->pipe);
2698         done_io(view->pipe);
2699         view->pipe = NULL;
2702 static void
2703 setup_update(struct view *view, const char *vid)
2705         set_nonblocking_input(TRUE);
2706         reset_view(view);
2707         string_copy_rev(view->vid, vid);
2708         view->pipe = &view->io;
2709         view->start_time = time(NULL);
2712 static bool
2713 prepare_update(struct view *view, const char *argv[], const char *dir,
2714                enum format_flags flags)
2716         if (view->pipe)
2717                 end_update(view, TRUE);
2718         return init_io_rd(&view->io, argv, dir, flags);
2721 static bool
2722 prepare_update_file(struct view *view, const char *name)
2724         if (view->pipe)
2725                 end_update(view, TRUE);
2726         return io_open(&view->io, name);
2729 static bool
2730 begin_update(struct view *view, bool refresh)
2732         if (view->pipe)
2733                 end_update(view, TRUE);
2735         if (refresh) {
2736                 if (!start_io(&view->io))
2737                         return FALSE;
2739         } else {
2740                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2741                         opt_path[0] = 0;
2743                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2744                         return FALSE;
2746                 /* Put the current ref_* value to the view title ref
2747                  * member. This is needed by the blob view. Most other
2748                  * views sets it automatically after loading because the
2749                  * first line is a commit line. */
2750                 string_copy_rev(view->ref, view->id);
2751         }
2753         setup_update(view, view->id);
2755         return TRUE;
2758 #define ITEM_CHUNK_SIZE 256
2759 static void *
2760 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2762         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2763         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2765         if (mem == NULL || num_chunks != num_chunks_new) {
2766                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2767                 mem = realloc(mem, *size * item_size);
2768         }
2770         return mem;
2773 static struct line *
2774 realloc_lines(struct view *view, size_t line_size)
2776         size_t alloc = view->line_alloc;
2777         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2778                                          sizeof(*view->line));
2780         if (!tmp)
2781                 return NULL;
2783         view->line = tmp;
2784         view->line_alloc = alloc;
2785         return view->line;
2788 static bool
2789 update_view(struct view *view)
2791         char out_buffer[BUFSIZ * 2];
2792         char *line;
2793         /* Clear the view and redraw everything since the tree sorting
2794          * might have rearranged things. */
2795         bool redraw = view->lines == 0;
2796         bool can_read = TRUE;
2798         if (!view->pipe)
2799                 return TRUE;
2801         if (!io_can_read(view->pipe)) {
2802                 if (view->lines == 0) {
2803                         time_t secs = time(NULL) - view->start_time;
2805                         if (secs > 1 && secs > view->update_secs) {
2806                                 if (view->update_secs == 0)
2807                                         redraw_view(view);
2808                                 update_view_title(view);
2809                                 view->update_secs = secs;
2810                         }
2811                 }
2812                 return TRUE;
2813         }
2815         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2816                 if (opt_iconv != ICONV_NONE) {
2817                         ICONV_CONST char *inbuf = line;
2818                         size_t inlen = strlen(line) + 1;
2820                         char *outbuf = out_buffer;
2821                         size_t outlen = sizeof(out_buffer);
2823                         size_t ret;
2825                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2826                         if (ret != (size_t) -1)
2827                                 line = out_buffer;
2828                 }
2830                 if (!view->ops->read(view, line)) {
2831                         report("Allocation failure");
2832                         end_update(view, TRUE);
2833                         return FALSE;
2834                 }
2835         }
2837         {
2838                 unsigned long lines = view->lines;
2839                 int digits;
2841                 for (digits = 0; lines; digits++)
2842                         lines /= 10;
2844                 /* Keep the displayed view in sync with line number scaling. */
2845                 if (digits != view->digits) {
2846                         view->digits = digits;
2847                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2848                                 redraw = TRUE;
2849                 }
2850         }
2852         if (io_error(view->pipe)) {
2853                 report("Failed to read: %s", io_strerror(view->pipe));
2854                 end_update(view, TRUE);
2856         } else if (io_eof(view->pipe)) {
2857                 report("");
2858                 end_update(view, FALSE);
2859         }
2861         if (restore_view_position(view))
2862                 redraw = TRUE;
2864         if (!view_is_displayed(view))
2865                 return TRUE;
2867         if (redraw)
2868                 redraw_view_from(view, 0);
2869         else
2870                 redraw_view_dirty(view);
2872         /* Update the title _after_ the redraw so that if the redraw picks up a
2873          * commit reference in view->ref it'll be available here. */
2874         update_view_title(view);
2875         return TRUE;
2878 static struct line *
2879 add_line_data(struct view *view, void *data, enum line_type type)
2881         struct line *line;
2883         if (!realloc_lines(view, view->lines + 1))
2884                 return NULL;
2886         line = &view->line[view->lines++];
2887         memset(line, 0, sizeof(*line));
2888         line->type = type;
2889         line->data = data;
2890         line->dirty = 1;
2892         return line;
2895 static struct line *
2896 add_line_text(struct view *view, const char *text, enum line_type type)
2898         char *data = text ? strdup(text) : NULL;
2900         return data ? add_line_data(view, data, type) : NULL;
2903 static struct line *
2904 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2906         char buf[SIZEOF_STR];
2907         va_list args;
2909         va_start(args, fmt);
2910         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2911                 buf[0] = 0;
2912         va_end(args);
2914         return buf[0] ? add_line_text(view, buf, type) : NULL;
2917 /*
2918  * View opening
2919  */
2921 enum open_flags {
2922         OPEN_DEFAULT = 0,       /* Use default view switching. */
2923         OPEN_SPLIT = 1,         /* Split current view. */
2924         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2925         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2926         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2927         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2928         OPEN_PREPARED = 32,     /* Open already prepared command. */
2929 };
2931 static void
2932 open_view(struct view *prev, enum request request, enum open_flags flags)
2934         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2935         bool split = !!(flags & OPEN_SPLIT);
2936         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2937         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2938         struct view *view = VIEW(request);
2939         int nviews = displayed_views();
2940         struct view *base_view = display[0];
2942         if (view == prev && nviews == 1 && !reload) {
2943                 report("Already in %s view", view->name);
2944                 return;
2945         }
2947         if (view->git_dir && !opt_git_dir[0]) {
2948                 report("The %s view is disabled in pager view", view->name);
2949                 return;
2950         }
2952         if (split) {
2953                 display[1] = view;
2954                 if (!backgrounded)
2955                         current_view = 1;
2956         } else if (!nomaximize) {
2957                 /* Maximize the current view. */
2958                 memset(display, 0, sizeof(display));
2959                 current_view = 0;
2960                 display[current_view] = view;
2961         }
2963         /* Resize the view when switching between split- and full-screen,
2964          * or when switching between two different full-screen views. */
2965         if (nviews != displayed_views() ||
2966             (nviews == 1 && base_view != display[0]))
2967                 resize_display();
2969         if (view->ops->open) {
2970                 if (view->pipe)
2971                         end_update(view, TRUE);
2972                 if (!view->ops->open(view)) {
2973                         report("Failed to load %s view", view->name);
2974                         return;
2975                 }
2976                 restore_view_position(view);
2978         } else if ((reload || strcmp(view->vid, view->id)) &&
2979                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2980                 report("Failed to load %s view", view->name);
2981                 return;
2982         }
2984         if (split && prev->lineno - prev->offset >= prev->height) {
2985                 /* Take the title line into account. */
2986                 int lines = prev->lineno - prev->offset - prev->height + 1;
2988                 /* Scroll the view that was split if the current line is
2989                  * outside the new limited view. */
2990                 do_scroll_view(prev, lines);
2991         }
2993         if (prev && view != prev) {
2994                 if (split && !backgrounded) {
2995                         /* "Blur" the previous view. */
2996                         update_view_title(prev);
2997                 }
2999                 view->parent = prev;
3000         }
3002         if (view->pipe && view->lines == 0) {
3003                 /* Clear the old view and let the incremental updating refill
3004                  * the screen. */
3005                 werase(view->win);
3006                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3007                 report("");
3008         } else if (view_is_displayed(view)) {
3009                 redraw_view(view);
3010                 report("");
3011         }
3013         /* If the view is backgrounded the above calls to report()
3014          * won't redraw the view title. */
3015         if (backgrounded)
3016                 update_view_title(view);
3019 static void
3020 open_external_viewer(const char *argv[], const char *dir)
3022         def_prog_mode();           /* save current tty modes */
3023         endwin();                  /* restore original tty modes */
3024         run_io_fg(argv, dir);
3025         fprintf(stderr, "Press Enter to continue");
3026         getc(opt_tty);
3027         reset_prog_mode();
3028         redraw_display(TRUE);
3031 static void
3032 open_mergetool(const char *file)
3034         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3036         open_external_viewer(mergetool_argv, opt_cdup);
3039 static void
3040 open_editor(bool from_root, const char *file)
3042         const char *editor_argv[] = { "vi", file, NULL };
3043         const char *editor;
3045         editor = getenv("GIT_EDITOR");
3046         if (!editor && *opt_editor)
3047                 editor = opt_editor;
3048         if (!editor)
3049                 editor = getenv("VISUAL");
3050         if (!editor)
3051                 editor = getenv("EDITOR");
3052         if (!editor)
3053                 editor = "vi";
3055         editor_argv[0] = editor;
3056         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3059 static void
3060 open_run_request(enum request request)
3062         struct run_request *req = get_run_request(request);
3063         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3065         if (!req) {
3066                 report("Unknown run request");
3067                 return;
3068         }
3070         if (format_argv(argv, req->argv, FORMAT_ALL))
3071                 open_external_viewer(argv, NULL);
3072         free_argv(argv);
3075 /*
3076  * User request switch noodle
3077  */
3079 static int
3080 view_driver(struct view *view, enum request request)
3082         int i;
3084         if (request == REQ_NONE) {
3085                 doupdate();
3086                 return TRUE;
3087         }
3089         if (request > REQ_NONE) {
3090                 open_run_request(request);
3091                 /* FIXME: When all views can refresh always do this. */
3092                 if (view == VIEW(REQ_VIEW_STATUS) ||
3093                     view == VIEW(REQ_VIEW_MAIN) ||
3094                     view == VIEW(REQ_VIEW_LOG) ||
3095                     view == VIEW(REQ_VIEW_STAGE))
3096                         request = REQ_REFRESH;
3097                 else
3098                         return TRUE;
3099         }
3101         if (view && view->lines) {
3102                 request = view->ops->request(view, request, &view->line[view->lineno]);
3103                 if (request == REQ_NONE)
3104                         return TRUE;
3105         }
3107         switch (request) {
3108         case REQ_MOVE_UP:
3109         case REQ_MOVE_DOWN:
3110         case REQ_MOVE_PAGE_UP:
3111         case REQ_MOVE_PAGE_DOWN:
3112         case REQ_MOVE_FIRST_LINE:
3113         case REQ_MOVE_LAST_LINE:
3114                 move_view(view, request);
3115                 break;
3117         case REQ_SCROLL_LEFT:
3118         case REQ_SCROLL_RIGHT:
3119         case REQ_SCROLL_LINE_DOWN:
3120         case REQ_SCROLL_LINE_UP:
3121         case REQ_SCROLL_PAGE_DOWN:
3122         case REQ_SCROLL_PAGE_UP:
3123                 scroll_view(view, request);
3124                 break;
3126         case REQ_VIEW_BLAME:
3127                 if (!opt_file[0]) {
3128                         report("No file chosen, press %s to open tree view",
3129                                get_key(REQ_VIEW_TREE));
3130                         break;
3131                 }
3132                 open_view(view, request, OPEN_DEFAULT);
3133                 break;
3135         case REQ_VIEW_BLOB:
3136                 if (!ref_blob[0]) {
3137                         report("No file chosen, press %s to open tree view",
3138                                get_key(REQ_VIEW_TREE));
3139                         break;
3140                 }
3141                 open_view(view, request, OPEN_DEFAULT);
3142                 break;
3144         case REQ_VIEW_PAGER:
3145                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3146                         report("No pager content, press %s to run command from prompt",
3147                                get_key(REQ_PROMPT));
3148                         break;
3149                 }
3150                 open_view(view, request, OPEN_DEFAULT);
3151                 break;
3153         case REQ_VIEW_STAGE:
3154                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3155                         report("No stage content, press %s to open the status view and choose file",
3156                                get_key(REQ_VIEW_STATUS));
3157                         break;
3158                 }
3159                 open_view(view, request, OPEN_DEFAULT);
3160                 break;
3162         case REQ_VIEW_STATUS:
3163                 if (opt_is_inside_work_tree == FALSE) {
3164                         report("The status view requires a working tree");
3165                         break;
3166                 }
3167                 open_view(view, request, OPEN_DEFAULT);
3168                 break;
3170         case REQ_VIEW_MAIN:
3171         case REQ_VIEW_DIFF:
3172         case REQ_VIEW_LOG:
3173         case REQ_VIEW_TREE:
3174         case REQ_VIEW_HELP:
3175                 open_view(view, request, OPEN_DEFAULT);
3176                 break;
3178         case REQ_NEXT:
3179         case REQ_PREVIOUS:
3180                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3182                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3183                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3184                    (view == VIEW(REQ_VIEW_DIFF) &&
3185                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3186                    (view == VIEW(REQ_VIEW_STAGE) &&
3187                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3188                    (view == VIEW(REQ_VIEW_BLOB) &&
3189                      view->parent == VIEW(REQ_VIEW_TREE))) {
3190                         int line;
3192                         view = view->parent;
3193                         line = view->lineno;
3194                         move_view(view, request);
3195                         if (view_is_displayed(view))
3196                                 update_view_title(view);
3197                         if (line != view->lineno)
3198                                 view->ops->request(view, REQ_ENTER,
3199                                                    &view->line[view->lineno]);
3201                 } else {
3202                         move_view(view, request);
3203                 }
3204                 break;
3206         case REQ_VIEW_NEXT:
3207         {
3208                 int nviews = displayed_views();
3209                 int next_view = (current_view + 1) % nviews;
3211                 if (next_view == current_view) {
3212                         report("Only one view is displayed");
3213                         break;
3214                 }
3216                 current_view = next_view;
3217                 /* Blur out the title of the previous view. */
3218                 update_view_title(view);
3219                 report("");
3220                 break;
3221         }
3222         case REQ_REFRESH:
3223                 report("Refreshing is not yet supported for the %s view", view->name);
3224                 break;
3226         case REQ_MAXIMIZE:
3227                 if (displayed_views() == 2)
3228                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3229                 break;
3231         case REQ_TOGGLE_LINENO:
3232                 toggle_view_option(&opt_line_number, "line numbers");
3233                 break;
3235         case REQ_TOGGLE_DATE:
3236                 toggle_view_option(&opt_date, "date display");
3237                 break;
3239         case REQ_TOGGLE_AUTHOR:
3240                 toggle_view_option(&opt_author, "author display");
3241                 break;
3243         case REQ_TOGGLE_REV_GRAPH:
3244                 toggle_view_option(&opt_rev_graph, "revision graph display");
3245                 break;
3247         case REQ_TOGGLE_REFS:
3248                 toggle_view_option(&opt_show_refs, "reference display");
3249                 break;
3251         case REQ_SEARCH:
3252         case REQ_SEARCH_BACK:
3253                 search_view(view, request);
3254                 break;
3256         case REQ_FIND_NEXT:
3257         case REQ_FIND_PREV:
3258                 find_next(view, request);
3259                 break;
3261         case REQ_STOP_LOADING:
3262                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3263                         view = &views[i];
3264                         if (view->pipe)
3265                                 report("Stopped loading the %s view", view->name),
3266                         end_update(view, TRUE);
3267                 }
3268                 break;
3270         case REQ_SHOW_VERSION:
3271                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3272                 return TRUE;
3274         case REQ_SCREEN_REDRAW:
3275                 redraw_display(TRUE);
3276                 break;
3278         case REQ_EDIT:
3279                 report("Nothing to edit");
3280                 break;
3282         case REQ_ENTER:
3283                 report("Nothing to enter");
3284                 break;
3286         case REQ_VIEW_CLOSE:
3287                 /* XXX: Mark closed views by letting view->parent point to the
3288                  * view itself. Parents to closed view should never be
3289                  * followed. */
3290                 if (view->parent &&
3291                     view->parent->parent != view->parent) {
3292                         memset(display, 0, sizeof(display));
3293                         current_view = 0;
3294                         display[current_view] = view->parent;
3295                         view->parent = view;
3296                         resize_display();
3297                         redraw_display(FALSE);
3298                         report("");
3299                         break;
3300                 }
3301                 /* Fall-through */
3302         case REQ_QUIT:
3303                 return FALSE;
3305         default:
3306                 report("Unknown key, press 'h' for help");
3307                 return TRUE;
3308         }
3310         return TRUE;
3314 /*
3315  * View backend utilities
3316  */
3318 /* Parse author lines where the name may be empty:
3319  *      author  <email@address.tld> 1138474660 +0100
3320  */
3321 static void
3322 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3324         char *nameend = strchr(ident, '<');
3325         char *emailend = strchr(ident, '>');
3327         if (nameend && emailend)
3328                 *nameend = *emailend = 0;
3329         ident = chomp_string(ident);
3330         if (!*ident) {
3331                 if (nameend)
3332                         ident = chomp_string(nameend + 1);
3333                 if (!*ident)
3334                         ident = "Unknown";
3335         }
3337         string_ncopy_do(author, authorsize, ident, strlen(ident));
3339         /* Parse epoch and timezone */
3340         if (emailend && emailend[1] == ' ') {
3341                 char *secs = emailend + 2;
3342                 char *zone = strchr(secs, ' ');
3343                 time_t time = (time_t) atol(secs);
3345                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3346                         long tz;
3348                         zone++;
3349                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3350                         tz += ('0' - zone[2]) * 60 * 60;
3351                         tz += ('0' - zone[3]) * 60;
3352                         tz += ('0' - zone[4]) * 60;
3354                         if (zone[0] == '-')
3355                                 tz = -tz;
3357                         time -= tz;
3358                 }
3360                 gmtime_r(&time, tm);
3361         }
3364 static enum input_status
3365 select_commit_parent_handler(void *data, char *buf, int c)
3367         size_t parents = *(size_t *) data;
3368         int parent = 0;
3370         if (!isdigit(c))
3371                 return INPUT_SKIP;
3373         if (*buf)
3374                 parent = atoi(buf) * 10;
3375         parent += c - '0';
3377         if (parent > parents)
3378                 return INPUT_SKIP;
3379         return INPUT_OK;
3382 static bool
3383 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3385         char buf[SIZEOF_STR * 4];
3386         const char *revlist_argv[] = {
3387                 "git", "rev-list", "-1", "--parents", id, NULL
3388         };
3389         int parents;
3391         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3392             !*chomp_string(buf) ||
3393             (parents = (strlen(buf) / 40) - 1) < 0) {
3394                 report("Failed to get parent information");
3395                 return FALSE;
3397         } else if (parents == 0) {
3398                 report("The selected commit has no parents");
3399                 return FALSE;
3400         }
3402         if (parents > 1) {
3403                 char prompt[SIZEOF_STR];
3404                 char *result;
3406                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3407                         return FALSE;
3408                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3409                 if (!result)
3410                         return FALSE;
3411                 parents = atoi(result);
3412         }
3414         string_copy_rev(rev, &buf[41 * parents]);
3415         return TRUE;
3418 /*
3419  * Pager backend
3420  */
3422 static bool
3423 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3425         char text[SIZEOF_STR];
3427         if (opt_line_number && draw_lineno(view, lineno))
3428                 return TRUE;
3430         string_expand(text, sizeof(text), line->data, opt_tab_size);
3431         draw_text(view, line->type, text, TRUE);
3432         return TRUE;
3435 static bool
3436 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3438         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3439         char refbuf[SIZEOF_STR];
3440         char *ref = NULL;
3442         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3443                 ref = chomp_string(refbuf);
3445         if (!ref || !*ref)
3446                 return TRUE;
3448         /* This is the only fatal call, since it can "corrupt" the buffer. */
3449         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3450                 return FALSE;
3452         return TRUE;
3455 static void
3456 add_pager_refs(struct view *view, struct line *line)
3458         char buf[SIZEOF_STR];
3459         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3460         struct ref **refs;
3461         size_t bufpos = 0, refpos = 0;
3462         const char *sep = "Refs: ";
3463         bool is_tag = FALSE;
3465         assert(line->type == LINE_COMMIT);
3467         refs = get_refs(commit_id);
3468         if (!refs) {
3469                 if (view == VIEW(REQ_VIEW_DIFF))
3470                         goto try_add_describe_ref;
3471                 return;
3472         }
3474         do {
3475                 struct ref *ref = refs[refpos];
3476                 const char *fmt = ref->tag    ? "%s[%s]" :
3477                                   ref->remote ? "%s<%s>" : "%s%s";
3479                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3480                         return;
3481                 sep = ", ";
3482                 if (ref->tag)
3483                         is_tag = TRUE;
3484         } while (refs[refpos++]->next);
3486         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3487 try_add_describe_ref:
3488                 /* Add <tag>-g<commit_id> "fake" reference. */
3489                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3490                         return;
3491         }
3493         if (bufpos == 0)
3494                 return;
3496         add_line_text(view, buf, LINE_PP_REFS);
3499 static bool
3500 pager_read(struct view *view, char *data)
3502         struct line *line;
3504         if (!data)
3505                 return TRUE;
3507         line = add_line_text(view, data, get_line_type(data));
3508         if (!line)
3509                 return FALSE;
3511         if (line->type == LINE_COMMIT &&
3512             (view == VIEW(REQ_VIEW_DIFF) ||
3513              view == VIEW(REQ_VIEW_LOG)))
3514                 add_pager_refs(view, line);
3516         return TRUE;
3519 static enum request
3520 pager_request(struct view *view, enum request request, struct line *line)
3522         int split = 0;
3524         if (request != REQ_ENTER)
3525                 return request;
3527         if (line->type == LINE_COMMIT &&
3528            (view == VIEW(REQ_VIEW_LOG) ||
3529             view == VIEW(REQ_VIEW_PAGER))) {
3530                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3531                 split = 1;
3532         }
3534         /* Always scroll the view even if it was split. That way
3535          * you can use Enter to scroll through the log view and
3536          * split open each commit diff. */
3537         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3539         /* FIXME: A minor workaround. Scrolling the view will call report("")
3540          * but if we are scrolling a non-current view this won't properly
3541          * update the view title. */
3542         if (split)
3543                 update_view_title(view);
3545         return REQ_NONE;
3548 static bool
3549 pager_grep(struct view *view, struct line *line)
3551         regmatch_t pmatch;
3552         char *text = line->data;
3554         if (!*text)
3555                 return FALSE;
3557         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3558                 return FALSE;
3560         return TRUE;
3563 static void
3564 pager_select(struct view *view, struct line *line)
3566         if (line->type == LINE_COMMIT) {
3567                 char *text = (char *)line->data + STRING_SIZE("commit ");
3569                 if (view != VIEW(REQ_VIEW_PAGER))
3570                         string_copy_rev(view->ref, text);
3571                 string_copy_rev(ref_commit, text);
3572         }
3575 static struct view_ops pager_ops = {
3576         "line",
3577         NULL,
3578         NULL,
3579         pager_read,
3580         pager_draw,
3581         pager_request,
3582         pager_grep,
3583         pager_select,
3584 };
3586 static const char *log_argv[SIZEOF_ARG] = {
3587         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3588 };
3590 static enum request
3591 log_request(struct view *view, enum request request, struct line *line)
3593         switch (request) {
3594         case REQ_REFRESH:
3595                 load_refs();
3596                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3597                 return REQ_NONE;
3598         default:
3599                 return pager_request(view, request, line);
3600         }
3603 static struct view_ops log_ops = {
3604         "line",
3605         log_argv,
3606         NULL,
3607         pager_read,
3608         pager_draw,
3609         log_request,
3610         pager_grep,
3611         pager_select,
3612 };
3614 static const char *diff_argv[SIZEOF_ARG] = {
3615         "git", "show", "--pretty=fuller", "--no-color", "--root",
3616                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3617 };
3619 static struct view_ops diff_ops = {
3620         "line",
3621         diff_argv,
3622         NULL,
3623         pager_read,
3624         pager_draw,
3625         pager_request,
3626         pager_grep,
3627         pager_select,
3628 };
3630 /*
3631  * Help backend
3632  */
3634 static bool
3635 help_open(struct view *view)
3637         char buf[SIZEOF_STR];
3638         size_t bufpos;
3639         int i;
3641         if (view->lines > 0)
3642                 return TRUE;
3644         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3646         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3647                 const char *key;
3649                 if (req_info[i].request == REQ_NONE)
3650                         continue;
3652                 if (!req_info[i].request) {
3653                         add_line_text(view, "", LINE_DEFAULT);
3654                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3655                         continue;
3656                 }
3658                 key = get_key(req_info[i].request);
3659                 if (!*key)
3660                         key = "(no key defined)";
3662                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3663                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3664                         if (buf[bufpos] == '_')
3665                                 buf[bufpos] = '-';
3666                 }
3668                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3669                                 key, buf, req_info[i].help);
3670         }
3672         if (run_requests) {
3673                 add_line_text(view, "", LINE_DEFAULT);
3674                 add_line_text(view, "External commands:", LINE_DEFAULT);
3675         }
3677         for (i = 0; i < run_requests; i++) {
3678                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3679                 const char *key;
3680                 int argc;
3682                 if (!req)
3683                         continue;
3685                 key = get_key_name(req->key);
3686                 if (!*key)
3687                         key = "(no key defined)";
3689                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3690                         if (!string_format_from(buf, &bufpos, "%s%s",
3691                                                 argc ? " " : "", req->argv[argc]))
3692                                 return REQ_NONE;
3694                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3695                                 keymap_table[req->keymap].name, key, buf);
3696         }
3698         return TRUE;
3701 static struct view_ops help_ops = {
3702         "line",
3703         NULL,
3704         help_open,
3705         NULL,
3706         pager_draw,
3707         pager_request,
3708         pager_grep,
3709         pager_select,
3710 };
3713 /*
3714  * Tree backend
3715  */
3717 struct tree_stack_entry {
3718         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3719         unsigned long lineno;           /* Line number to restore */
3720         char *name;                     /* Position of name in opt_path */
3721 };
3723 /* The top of the path stack. */
3724 static struct tree_stack_entry *tree_stack = NULL;
3725 unsigned long tree_lineno = 0;
3727 static void
3728 pop_tree_stack_entry(void)
3730         struct tree_stack_entry *entry = tree_stack;
3732         tree_lineno = entry->lineno;
3733         entry->name[0] = 0;
3734         tree_stack = entry->prev;
3735         free(entry);
3738 static void
3739 push_tree_stack_entry(const char *name, unsigned long lineno)
3741         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3742         size_t pathlen = strlen(opt_path);
3744         if (!entry)
3745                 return;
3747         entry->prev = tree_stack;
3748         entry->name = opt_path + pathlen;
3749         tree_stack = entry;
3751         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3752                 pop_tree_stack_entry();
3753                 return;
3754         }
3756         /* Move the current line to the first tree entry. */
3757         tree_lineno = 1;
3758         entry->lineno = lineno;
3761 /* Parse output from git-ls-tree(1):
3762  *
3763  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3764  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3765  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3766  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3767  */
3769 #define SIZEOF_TREE_ATTR \
3770         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3772 #define SIZEOF_TREE_MODE \
3773         STRING_SIZE("100644 ")
3775 #define TREE_ID_OFFSET \
3776         STRING_SIZE("100644 blob ")
3778 struct tree_entry {
3779         char id[SIZEOF_REV];
3780         mode_t mode;
3781         struct tm time;                 /* Date from the author ident. */
3782         char author[75];                /* Author of the commit. */
3783         char name[1];
3784 };
3786 static const char *
3787 tree_path(struct line *line)
3789         return ((struct tree_entry *) line->data)->name;
3793 static int
3794 tree_compare_entry(struct line *line1, struct line *line2)
3796         if (line1->type != line2->type)
3797                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3798         return strcmp(tree_path(line1), tree_path(line2));
3801 static struct line *
3802 tree_entry(struct view *view, enum line_type type, const char *path,
3803            const char *mode, const char *id)
3805         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3806         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3808         if (!entry || !line) {
3809                 free(entry);
3810                 return NULL;
3811         }
3813         strncpy(entry->name, path, strlen(path));
3814         if (mode)
3815                 entry->mode = strtoul(mode, NULL, 8);
3816         if (id)
3817                 string_copy_rev(entry->id, id);
3819         return line;
3822 static bool
3823 tree_read_date(struct view *view, char *text, bool *read_date)
3825         static char author_name[SIZEOF_STR];
3826         static struct tm author_time;
3828         if (!text && *read_date) {
3829                 *read_date = FALSE;
3830                 return TRUE;
3832         } else if (!text) {
3833                 char *path = *opt_path ? opt_path : ".";
3834                 /* Find next entry to process */
3835                 const char *log_file[] = {
3836                         "git", "log", "--no-color", "--pretty=raw",
3837                                 "--cc", "--raw", view->id, "--", path, NULL
3838                 };
3839                 struct io io = {};
3841                 if (!view->lines) {
3842                         tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL);
3843                         report("Tree is empty");
3844                         return TRUE;
3845                 }
3847                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3848                         report("Failed to load tree data");
3849                         return TRUE;
3850                 }
3852                 done_io(view->pipe);
3853                 view->io = io;
3854                 *read_date = TRUE;
3855                 return FALSE;
3857         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3858                 parse_author_line(text + STRING_SIZE("author "),
3859                                   author_name, sizeof(author_name), &author_time);
3861         } else if (*text == ':') {
3862                 char *pos;
3863                 size_t annotated = 1;
3864                 size_t i;
3866                 pos = strchr(text, '\t');
3867                 if (!pos)
3868                         return TRUE;
3869                 text = pos + 1;
3870                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3871                         text += strlen(opt_prefix);
3872                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3873                         text += strlen(opt_path);
3874                 pos = strchr(text, '/');
3875                 if (pos)
3876                         *pos = 0;
3878                 for (i = 1; i < view->lines; i++) {
3879                         struct line *line = &view->line[i];
3880                         struct tree_entry *entry = line->data;
3882                         annotated += !!*entry->author;
3883                         if (*entry->author || strcmp(entry->name, text))
3884                                 continue;
3886                         string_copy(entry->author, author_name);
3887                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3888                         line->dirty = 1;
3889                         break;
3890                 }
3892                 if (annotated == view->lines)
3893                         kill_io(view->pipe);
3894         }
3895         return TRUE;
3898 static bool
3899 tree_read(struct view *view, char *text)
3901         static bool read_date = FALSE;
3902         struct tree_entry *data;
3903         struct line *entry, *line;
3904         enum line_type type;
3905         size_t textlen = text ? strlen(text) : 0;
3906         char *path = text + SIZEOF_TREE_ATTR;
3908         if (read_date || !text)
3909                 return tree_read_date(view, text, &read_date);
3911         if (textlen <= SIZEOF_TREE_ATTR)
3912                 return FALSE;
3913         if (view->lines == 0 &&
3914             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3915                 return FALSE;
3917         /* Strip the path part ... */
3918         if (*opt_path) {
3919                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3920                 size_t striplen = strlen(opt_path);
3922                 if (pathlen > striplen)
3923                         memmove(path, path + striplen,
3924                                 pathlen - striplen + 1);
3926                 /* Insert "link" to parent directory. */
3927                 if (view->lines == 1 &&
3928                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3929                         return FALSE;
3930         }
3932         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3933         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3934         if (!entry)
3935                 return FALSE;
3936         data = entry->data;
3938         /* Skip "Directory ..." and ".." line. */
3939         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3940                 if (tree_compare_entry(line, entry) <= 0)
3941                         continue;
3943                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3945                 line->data = data;
3946                 line->type = type;
3947                 for (; line <= entry; line++)
3948                         line->dirty = line->cleareol = 1;
3949                 return TRUE;
3950         }
3952         if (tree_lineno > view->lineno) {
3953                 view->lineno = tree_lineno;
3954                 tree_lineno = 0;
3955         }
3957         return TRUE;
3960 static bool
3961 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3963         struct tree_entry *entry = line->data;
3965         if (line->type == LINE_TREE_PARENT) {
3966                 if (draw_text(view, line->type, "Directory path /", TRUE))
3967                         return TRUE;
3968         } else {
3969                 char mode[11] = "-r--r--r--";
3971                 if (S_ISDIR(entry->mode)) {
3972                         mode[3] = mode[6] = mode[9] = 'x';
3973                         mode[0] = 'd';
3974                 }
3975                 if (S_ISLNK(entry->mode))
3976                         mode[0] = 'l';
3977                 if (entry->mode & S_IWUSR)
3978                         mode[2] = 'w';
3979                 if (entry->mode & S_IXUSR)
3980                         mode[3] = 'x';
3981                 if (entry->mode & S_IXGRP)
3982                         mode[6] = 'x';
3983                 if (entry->mode & S_IXOTH)
3984                         mode[9] = 'x';
3985                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3986                         return TRUE;
3988                 if (opt_author && draw_author(view, entry->author))
3989                         return TRUE;
3991                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3992                         return TRUE;
3993         }
3994         if (draw_text(view, line->type, entry->name, TRUE))
3995                 return TRUE;
3996         return TRUE;
3999 static void
4000 open_blob_editor()
4002         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4003         int fd = mkstemp(file);
4005         if (fd == -1)
4006                 report("Failed to create temporary file");
4007         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4008                 report("Failed to save blob data to file");
4009         else
4010                 open_editor(FALSE, file);
4011         if (fd != -1)
4012                 unlink(file);
4015 static enum request
4016 tree_request(struct view *view, enum request request, struct line *line)
4018         enum open_flags flags;
4020         switch (request) {
4021         case REQ_VIEW_BLAME:
4022                 if (line->type != LINE_TREE_FILE) {
4023                         report("Blame only supported for files");
4024                         return REQ_NONE;
4025                 }
4027                 string_copy(opt_ref, view->vid);
4028                 return request;
4030         case REQ_EDIT:
4031                 if (line->type != LINE_TREE_FILE) {
4032                         report("Edit only supported for files");
4033                 } else if (!is_head_commit(view->vid)) {
4034                         open_blob_editor();
4035                 } else {
4036                         open_editor(TRUE, opt_file);
4037                 }
4038                 return REQ_NONE;
4040         case REQ_PARENT:
4041                 if (!*opt_path) {
4042                         /* quit view if at top of tree */
4043                         return REQ_VIEW_CLOSE;
4044                 }
4045                 /* fake 'cd  ..' */
4046                 line = &view->line[1];
4047                 break;
4049         case REQ_ENTER:
4050                 break;
4052         default:
4053                 return request;
4054         }
4056         /* Cleanup the stack if the tree view is at a different tree. */
4057         while (!*opt_path && tree_stack)
4058                 pop_tree_stack_entry();
4060         switch (line->type) {
4061         case LINE_TREE_DIR:
4062                 /* Depending on whether it is a subdir or parent (updir?) link
4063                  * mangle the path buffer. */
4064                 if (line == &view->line[1] && *opt_path) {
4065                         pop_tree_stack_entry();
4067                 } else {
4068                         const char *basename = tree_path(line);
4070                         push_tree_stack_entry(basename, view->lineno);
4071                 }
4073                 /* Trees and subtrees share the same ID, so they are not not
4074                  * unique like blobs. */
4075                 flags = OPEN_RELOAD;
4076                 request = REQ_VIEW_TREE;
4077                 break;
4079         case LINE_TREE_FILE:
4080                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4081                 request = REQ_VIEW_BLOB;
4082                 break;
4084         default:
4085                 return REQ_NONE;
4086         }
4088         open_view(view, request, flags);
4089         if (request == REQ_VIEW_TREE)
4090                 view->lineno = tree_lineno;
4092         return REQ_NONE;
4095 static void
4096 tree_select(struct view *view, struct line *line)
4098         struct tree_entry *entry = line->data;
4100         if (line->type == LINE_TREE_FILE) {
4101                 string_copy_rev(ref_blob, entry->id);
4102                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4104         } else if (line->type != LINE_TREE_DIR) {
4105                 return;
4106         }
4108         string_copy_rev(view->ref, entry->id);
4111 static const char *tree_argv[SIZEOF_ARG] = {
4112         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4113 };
4115 static struct view_ops tree_ops = {
4116         "file",
4117         tree_argv,
4118         NULL,
4119         tree_read,
4120         tree_draw,
4121         tree_request,
4122         pager_grep,
4123         tree_select,
4124 };
4126 static bool
4127 blob_read(struct view *view, char *line)
4129         if (!line)
4130                 return TRUE;
4131         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4134 static enum request
4135 blob_request(struct view *view, enum request request, struct line *line)
4137         switch (request) {
4138         case REQ_EDIT:
4139                 open_blob_editor();
4140                 return REQ_NONE;
4141         default:
4142                 return pager_request(view, request, line);
4143         }
4146 static const char *blob_argv[SIZEOF_ARG] = {
4147         "git", "cat-file", "blob", "%(blob)", NULL
4148 };
4150 static struct view_ops blob_ops = {
4151         "line",
4152         blob_argv,
4153         NULL,
4154         blob_read,
4155         pager_draw,
4156         blob_request,
4157         pager_grep,
4158         pager_select,
4159 };
4161 /*
4162  * Blame backend
4163  *
4164  * Loading the blame view is a two phase job:
4165  *
4166  *  1. File content is read either using opt_file from the
4167  *     filesystem or using git-cat-file.
4168  *  2. Then blame information is incrementally added by
4169  *     reading output from git-blame.
4170  */
4172 static const char *blame_head_argv[] = {
4173         "git", "blame", "--incremental", "--", "%(file)", NULL
4174 };
4176 static const char *blame_ref_argv[] = {
4177         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4178 };
4180 static const char *blame_cat_file_argv[] = {
4181         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4182 };
4184 struct blame_commit {
4185         char id[SIZEOF_REV];            /* SHA1 ID. */
4186         char title[128];                /* First line of the commit message. */
4187         char author[75];                /* Author of the commit. */
4188         struct tm time;                 /* Date from the author ident. */
4189         char filename[128];             /* Name of file. */
4190         bool has_previous;              /* Was a "previous" line detected. */
4191 };
4193 struct blame {
4194         struct blame_commit *commit;
4195         char text[1];
4196 };
4198 static bool
4199 blame_open(struct view *view)
4201         if (*opt_ref || !io_open(&view->io, opt_file)) {
4202                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4203                         return FALSE;
4204         }
4206         setup_update(view, opt_file);
4207         string_format(view->ref, "%s ...", opt_file);
4209         return TRUE;
4212 static struct blame_commit *
4213 get_blame_commit(struct view *view, const char *id)
4215         size_t i;
4217         for (i = 0; i < view->lines; i++) {
4218                 struct blame *blame = view->line[i].data;
4220                 if (!blame->commit)
4221                         continue;
4223                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4224                         return blame->commit;
4225         }
4227         {
4228                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4230                 if (commit)
4231                         string_ncopy(commit->id, id, SIZEOF_REV);
4232                 return commit;
4233         }
4236 static bool
4237 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4239         const char *pos = *posref;
4241         *posref = NULL;
4242         pos = strchr(pos + 1, ' ');
4243         if (!pos || !isdigit(pos[1]))
4244                 return FALSE;
4245         *number = atoi(pos + 1);
4246         if (*number < min || *number > max)
4247                 return FALSE;
4249         *posref = pos;
4250         return TRUE;
4253 static struct blame_commit *
4254 parse_blame_commit(struct view *view, const char *text, int *blamed)
4256         struct blame_commit *commit;
4257         struct blame *blame;
4258         const char *pos = text + SIZEOF_REV - 1;
4259         size_t lineno;
4260         size_t group;
4262         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4263                 return NULL;
4265         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4266             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4267                 return NULL;
4269         commit = get_blame_commit(view, text);
4270         if (!commit)
4271                 return NULL;
4273         *blamed += group;
4274         while (group--) {
4275                 struct line *line = &view->line[lineno + group - 1];
4277                 blame = line->data;
4278                 blame->commit = commit;
4279                 line->dirty = 1;
4280         }
4282         return commit;
4285 static bool
4286 blame_read_file(struct view *view, const char *line, bool *read_file)
4288         if (!line) {
4289                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4290                 struct io io = {};
4292                 if (view->lines == 0 && !view->parent)
4293                         die("No blame exist for %s", view->vid);
4295                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4296                         report("Failed to load blame data");
4297                         return TRUE;
4298                 }
4300                 done_io(view->pipe);
4301                 view->io = io;
4302                 *read_file = FALSE;
4303                 return FALSE;
4305         } else {
4306                 size_t linelen = string_expand_length(line, opt_tab_size);
4307                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4309                 if (!blame)
4310                         return FALSE;
4312                 blame->commit = NULL;
4313                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4314                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4315         }
4318 static bool
4319 match_blame_header(const char *name, char **line)
4321         size_t namelen = strlen(name);
4322         bool matched = !strncmp(name, *line, namelen);
4324         if (matched)
4325                 *line += namelen;
4327         return matched;
4330 static bool
4331 blame_read(struct view *view, char *line)
4333         static struct blame_commit *commit = NULL;
4334         static int blamed = 0;
4335         static time_t author_time;
4336         static bool read_file = TRUE;
4338         if (read_file)
4339                 return blame_read_file(view, line, &read_file);
4341         if (!line) {
4342                 /* Reset all! */
4343                 commit = NULL;
4344                 blamed = 0;
4345                 read_file = TRUE;
4346                 string_format(view->ref, "%s", view->vid);
4347                 if (view_is_displayed(view)) {
4348                         update_view_title(view);
4349                         redraw_view_from(view, 0);
4350                 }
4351                 return TRUE;
4352         }
4354         if (!commit) {
4355                 commit = parse_blame_commit(view, line, &blamed);
4356                 string_format(view->ref, "%s %2d%%", view->vid,
4357                               view->lines ? blamed * 100 / view->lines : 0);
4359         } else if (match_blame_header("author ", &line)) {
4360                 string_ncopy(commit->author, line, strlen(line));
4362         } else if (match_blame_header("author-time ", &line)) {
4363                 author_time = (time_t) atol(line);
4365         } else if (match_blame_header("author-tz ", &line)) {
4366                 long tz;
4368                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4369                 tz += ('0' - line[2]) * 60 * 60;
4370                 tz += ('0' - line[3]) * 60;
4371                 tz += ('0' - line[4]) * 60;
4373                 if (line[0] == '-')
4374                         tz = -tz;
4376                 author_time -= tz;
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;
4400         if (blame->commit && *blame->commit->filename) {
4401                 id = blame->commit->id;
4402                 author = blame->commit->author;
4403                 time = &blame->commit->time;
4404         }
4406         if (opt_date && draw_date(view, time))
4407                 return TRUE;
4409         if (opt_author && draw_author(view, author))
4410                 return TRUE;
4412         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4413                 return TRUE;
4415         if (draw_lineno(view, lineno))
4416                 return TRUE;
4418         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4419         return TRUE;
4422 static bool
4423 check_blame_commit(struct blame *blame)
4425         if (!blame->commit)
4426                 report("Commit data not loaded yet");
4427         else if (!strcmp(blame->commit->id, NULL_ID))
4428                 report("No commit exist for the selected line");
4429         else
4430                 return TRUE;
4431         return FALSE;
4434 static enum request
4435 blame_request(struct view *view, enum request request, struct line *line)
4437         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4438         struct blame *blame = line->data;
4440         switch (request) {
4441         case REQ_VIEW_BLAME:
4442                 if (check_blame_commit(blame)) {
4443                         string_copy(opt_ref, blame->commit->id);
4444                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4445                 }
4446                 break;
4448         case REQ_PARENT:
4449                 if (check_blame_commit(blame) &&
4450                     select_commit_parent(blame->commit->id, opt_ref))
4451                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4452                 break;
4454         case REQ_ENTER:
4455                 if (!blame->commit) {
4456                         report("No commit loaded yet");
4457                         break;
4458                 }
4460                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4461                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4462                         break;
4464                 if (!strcmp(blame->commit->id, NULL_ID)) {
4465                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4466                         const char *diff_index_argv[] = {
4467                                 "git", "diff-index", "--root", "--patch-with-stat",
4468                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4469                         };
4471                         if (!blame->commit->has_previous) {
4472                                 diff_index_argv[1] = "diff";
4473                                 diff_index_argv[2] = "--no-color";
4474                                 diff_index_argv[6] = "--";
4475                                 diff_index_argv[7] = "/dev/null";
4476                         }
4478                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4479                                 report("Failed to allocate diff command");
4480                                 break;
4481                         }
4482                         flags |= OPEN_PREPARED;
4483                 }
4485                 open_view(view, REQ_VIEW_DIFF, flags);
4486                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4487                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4488                 break;
4490         default:
4491                 return request;
4492         }
4494         return REQ_NONE;
4497 static bool
4498 blame_grep(struct view *view, struct line *line)
4500         struct blame *blame = line->data;
4501         struct blame_commit *commit = blame->commit;
4502         regmatch_t pmatch;
4504 #define MATCH(text, on)                                                 \
4505         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4507         if (commit) {
4508                 char buf[DATE_COLS + 1];
4510                 if (MATCH(commit->title, 1) ||
4511                     MATCH(commit->author, opt_author) ||
4512                     MATCH(commit->id, opt_date))
4513                         return TRUE;
4515                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4516                     MATCH(buf, 1))
4517                         return TRUE;
4518         }
4520         return MATCH(blame->text, 1);
4522 #undef MATCH
4525 static void
4526 blame_select(struct view *view, struct line *line)
4528         struct blame *blame = line->data;
4529         struct blame_commit *commit = blame->commit;
4531         if (!commit)
4532                 return;
4534         if (!strcmp(commit->id, NULL_ID))
4535                 string_ncopy(ref_commit, "HEAD", 4);
4536         else
4537                 string_copy_rev(ref_commit, commit->id);
4540 static struct view_ops blame_ops = {
4541         "line",
4542         NULL,
4543         blame_open,
4544         blame_read,
4545         blame_draw,
4546         blame_request,
4547         blame_grep,
4548         blame_select,
4549 };
4551 /*
4552  * Status backend
4553  */
4555 struct status {
4556         char status;
4557         struct {
4558                 mode_t mode;
4559                 char rev[SIZEOF_REV];
4560                 char name[SIZEOF_STR];
4561         } old;
4562         struct {
4563                 mode_t mode;
4564                 char rev[SIZEOF_REV];
4565                 char name[SIZEOF_STR];
4566         } new;
4567 };
4569 static char status_onbranch[SIZEOF_STR];
4570 static struct status stage_status;
4571 static enum line_type stage_line_type;
4572 static size_t stage_chunks;
4573 static int *stage_chunk;
4575 /* This should work even for the "On branch" line. */
4576 static inline bool
4577 status_has_none(struct view *view, struct line *line)
4579         return line < view->line + view->lines && !line[1].data;
4582 /* Get fields from the diff line:
4583  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4584  */
4585 static inline bool
4586 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4588         const char *old_mode = buf +  1;
4589         const char *new_mode = buf +  8;
4590         const char *old_rev  = buf + 15;
4591         const char *new_rev  = buf + 56;
4592         const char *status   = buf + 97;
4594         if (bufsize < 98 ||
4595             old_mode[-1] != ':' ||
4596             new_mode[-1] != ' ' ||
4597             old_rev[-1]  != ' ' ||
4598             new_rev[-1]  != ' ' ||
4599             status[-1]   != ' ')
4600                 return FALSE;
4602         file->status = *status;
4604         string_copy_rev(file->old.rev, old_rev);
4605         string_copy_rev(file->new.rev, new_rev);
4607         file->old.mode = strtoul(old_mode, NULL, 8);
4608         file->new.mode = strtoul(new_mode, NULL, 8);
4610         file->old.name[0] = file->new.name[0] = 0;
4612         return TRUE;
4615 static bool
4616 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4618         struct status *unmerged = NULL;
4619         char *buf;
4620         struct io io = {};
4622         if (!run_io(&io, argv, NULL, IO_RD))
4623                 return FALSE;
4625         add_line_data(view, NULL, type);
4627         while ((buf = io_get(&io, 0, TRUE))) {
4628                 struct status *file = unmerged;
4630                 if (!file) {
4631                         file = calloc(1, sizeof(*file));
4632                         if (!file || !add_line_data(view, file, type))
4633                                 goto error_out;
4634                 }
4636                 /* Parse diff info part. */
4637                 if (status) {
4638                         file->status = status;
4639                         if (status == 'A')
4640                                 string_copy(file->old.rev, NULL_ID);
4642                 } else if (!file->status || file == unmerged) {
4643                         if (!status_get_diff(file, buf, strlen(buf)))
4644                                 goto error_out;
4646                         buf = io_get(&io, 0, TRUE);
4647                         if (!buf)
4648                                 break;
4650                         /* Collapse all 'M'odified entries that follow a
4651                          * associated 'U'nmerged entry. */
4652                         if (unmerged == file) {
4653                                 unmerged->status = 'U';
4654                                 unmerged = NULL;
4655                         } else if (file->status == 'U') {
4656                                 unmerged = file;
4657                         }
4658                 }
4660                 /* Grab the old name for rename/copy. */
4661                 if (!*file->old.name &&
4662                     (file->status == 'R' || file->status == 'C')) {
4663                         string_ncopy(file->old.name, buf, strlen(buf));
4665                         buf = io_get(&io, 0, TRUE);
4666                         if (!buf)
4667                                 break;
4668                 }
4670                 /* git-ls-files just delivers a NUL separated list of
4671                  * file names similar to the second half of the
4672                  * git-diff-* output. */
4673                 string_ncopy(file->new.name, buf, strlen(buf));
4674                 if (!*file->old.name)
4675                         string_copy(file->old.name, file->new.name);
4676                 file = NULL;
4677         }
4679         if (io_error(&io)) {
4680 error_out:
4681                 done_io(&io);
4682                 return FALSE;
4683         }
4685         if (!view->line[view->lines - 1].data)
4686                 add_line_data(view, NULL, LINE_STAT_NONE);
4688         done_io(&io);
4689         return TRUE;
4692 /* Don't show unmerged entries in the staged section. */
4693 static const char *status_diff_index_argv[] = {
4694         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4695                              "--cached", "-M", "HEAD", NULL
4696 };
4698 static const char *status_diff_files_argv[] = {
4699         "git", "diff-files", "-z", NULL
4700 };
4702 static const char *status_list_other_argv[] = {
4703         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4704 };
4706 static const char *status_list_no_head_argv[] = {
4707         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4708 };
4710 static const char *update_index_argv[] = {
4711         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4712 };
4714 /* Restore the previous line number to stay in the context or select a
4715  * line with something that can be updated. */
4716 static void
4717 status_restore(struct view *view)
4719         if (view->p_lineno >= view->lines)
4720                 view->p_lineno = view->lines - 1;
4721         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4722                 view->p_lineno++;
4723         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4724                 view->p_lineno--;
4726         /* If the above fails, always skip the "On branch" line. */
4727         if (view->p_lineno < view->lines)
4728                 view->lineno = view->p_lineno;
4729         else
4730                 view->lineno = 1;
4732         if (view->lineno < view->offset)
4733                 view->offset = view->lineno;
4734         else if (view->offset + view->height <= view->lineno)
4735                 view->offset = view->lineno - view->height + 1;
4737         view->p_restore = FALSE;
4740 /* First parse staged info using git-diff-index(1), then parse unstaged
4741  * info using git-diff-files(1), and finally untracked files using
4742  * git-ls-files(1). */
4743 static bool
4744 status_open(struct view *view)
4746         reset_view(view);
4748         add_line_data(view, NULL, LINE_STAT_HEAD);
4749         if (is_initial_commit())
4750                 string_copy(status_onbranch, "Initial commit");
4751         else if (!*opt_head)
4752                 string_copy(status_onbranch, "Not currently on any branch");
4753         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4754                 return FALSE;
4756         run_io_bg(update_index_argv);
4758         if (is_initial_commit()) {
4759                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4760                         return FALSE;
4761         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4762                 return FALSE;
4763         }
4765         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4766             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4767                 return FALSE;
4769         /* Restore the exact position or use the specialized restore
4770          * mode? */
4771         if (!view->p_restore)
4772                 status_restore(view);
4773         return TRUE;
4776 static bool
4777 status_draw(struct view *view, struct line *line, unsigned int lineno)
4779         struct status *status = line->data;
4780         enum line_type type;
4781         const char *text;
4783         if (!status) {
4784                 switch (line->type) {
4785                 case LINE_STAT_STAGED:
4786                         type = LINE_STAT_SECTION;
4787                         text = "Changes to be committed:";
4788                         break;
4790                 case LINE_STAT_UNSTAGED:
4791                         type = LINE_STAT_SECTION;
4792                         text = "Changed but not updated:";
4793                         break;
4795                 case LINE_STAT_UNTRACKED:
4796                         type = LINE_STAT_SECTION;
4797                         text = "Untracked files:";
4798                         break;
4800                 case LINE_STAT_NONE:
4801                         type = LINE_DEFAULT;
4802                         text = "  (no files)";
4803                         break;
4805                 case LINE_STAT_HEAD:
4806                         type = LINE_STAT_HEAD;
4807                         text = status_onbranch;
4808                         break;
4810                 default:
4811                         return FALSE;
4812                 }
4813         } else {
4814                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4816                 buf[0] = status->status;
4817                 if (draw_text(view, line->type, buf, TRUE))
4818                         return TRUE;
4819                 type = LINE_DEFAULT;
4820                 text = status->new.name;
4821         }
4823         draw_text(view, type, text, TRUE);
4824         return TRUE;
4827 static enum request
4828 status_enter(struct view *view, struct line *line)
4830         struct status *status = line->data;
4831         const char *oldpath = status ? status->old.name : NULL;
4832         /* Diffs for unmerged entries are empty when passing the new
4833          * path, so leave it empty. */
4834         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4835         const char *info;
4836         enum open_flags split;
4837         struct view *stage = VIEW(REQ_VIEW_STAGE);
4839         if (line->type == LINE_STAT_NONE ||
4840             (!status && line[1].type == LINE_STAT_NONE)) {
4841                 report("No file to diff");
4842                 return REQ_NONE;
4843         }
4845         switch (line->type) {
4846         case LINE_STAT_STAGED:
4847                 if (is_initial_commit()) {
4848                         const char *no_head_diff_argv[] = {
4849                                 "git", "diff", "--no-color", "--patch-with-stat",
4850                                         "--", "/dev/null", newpath, NULL
4851                         };
4853                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4854                                 return REQ_QUIT;
4855                 } else {
4856                         const char *index_show_argv[] = {
4857                                 "git", "diff-index", "--root", "--patch-with-stat",
4858                                         "-C", "-M", "--cached", "HEAD", "--",
4859                                         oldpath, newpath, NULL
4860                         };
4862                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4863                                 return REQ_QUIT;
4864                 }
4866                 if (status)
4867                         info = "Staged changes to %s";
4868                 else
4869                         info = "Staged changes";
4870                 break;
4872         case LINE_STAT_UNSTAGED:
4873         {
4874                 const char *files_show_argv[] = {
4875                         "git", "diff-files", "--root", "--patch-with-stat",
4876                                 "-C", "-M", "--", oldpath, newpath, NULL
4877                 };
4879                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4880                         return REQ_QUIT;
4881                 if (status)
4882                         info = "Unstaged changes to %s";
4883                 else
4884                         info = "Unstaged changes";
4885                 break;
4886         }
4887         case LINE_STAT_UNTRACKED:
4888                 if (!newpath) {
4889                         report("No file to show");
4890                         return REQ_NONE;
4891                 }
4893                 if (!suffixcmp(status->new.name, -1, "/")) {
4894                         report("Cannot display a directory");
4895                         return REQ_NONE;
4896                 }
4898                 if (!prepare_update_file(stage, newpath))
4899                         return REQ_QUIT;
4900                 info = "Untracked file %s";
4901                 break;
4903         case LINE_STAT_HEAD:
4904                 return REQ_NONE;
4906         default:
4907                 die("line type %d not handled in switch", line->type);
4908         }
4910         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4911         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4912         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4913                 if (status) {
4914                         stage_status = *status;
4915                 } else {
4916                         memset(&stage_status, 0, sizeof(stage_status));
4917                 }
4919                 stage_line_type = line->type;
4920                 stage_chunks = 0;
4921                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4922         }
4924         return REQ_NONE;
4927 static bool
4928 status_exists(struct status *status, enum line_type type)
4930         struct view *view = VIEW(REQ_VIEW_STATUS);
4931         unsigned long lineno;
4933         for (lineno = 0; lineno < view->lines; lineno++) {
4934                 struct line *line = &view->line[lineno];
4935                 struct status *pos = line->data;
4937                 if (line->type != type)
4938                         continue;
4939                 if (!pos && (!status || !status->status) && line[1].data) {
4940                         select_view_line(view, lineno);
4941                         return TRUE;
4942                 }
4943                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4944                         select_view_line(view, lineno);
4945                         return TRUE;
4946                 }
4947         }
4949         return FALSE;
4953 static bool
4954 status_update_prepare(struct io *io, enum line_type type)
4956         const char *staged_argv[] = {
4957                 "git", "update-index", "-z", "--index-info", NULL
4958         };
4959         const char *others_argv[] = {
4960                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4961         };
4963         switch (type) {
4964         case LINE_STAT_STAGED:
4965                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4967         case LINE_STAT_UNSTAGED:
4968                 return run_io(io, others_argv, opt_cdup, IO_WR);
4970         case LINE_STAT_UNTRACKED:
4971                 return run_io(io, others_argv, NULL, IO_WR);
4973         default:
4974                 die("line type %d not handled in switch", type);
4975                 return FALSE;
4976         }
4979 static bool
4980 status_update_write(struct io *io, struct status *status, enum line_type type)
4982         char buf[SIZEOF_STR];
4983         size_t bufsize = 0;
4985         switch (type) {
4986         case LINE_STAT_STAGED:
4987                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4988                                         status->old.mode,
4989                                         status->old.rev,
4990                                         status->old.name, 0))
4991                         return FALSE;
4992                 break;
4994         case LINE_STAT_UNSTAGED:
4995         case LINE_STAT_UNTRACKED:
4996                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4997                         return FALSE;
4998                 break;
5000         default:
5001                 die("line type %d not handled in switch", type);
5002         }
5004         return io_write(io, buf, bufsize);
5007 static bool
5008 status_update_file(struct status *status, enum line_type type)
5010         struct io io = {};
5011         bool result;
5013         if (!status_update_prepare(&io, type))
5014                 return FALSE;
5016         result = status_update_write(&io, status, type);
5017         done_io(&io);
5018         return result;
5021 static bool
5022 status_update_files(struct view *view, struct line *line)
5024         struct io io = {};
5025         bool result = TRUE;
5026         struct line *pos = view->line + view->lines;
5027         int files = 0;
5028         int file, done;
5030         if (!status_update_prepare(&io, line->type))
5031                 return FALSE;
5033         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5034                 files++;
5036         for (file = 0, done = 0; result && file < files; line++, file++) {
5037                 int almost_done = file * 100 / files;
5039                 if (almost_done > done) {
5040                         done = almost_done;
5041                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5042                                       file, files, done);
5043                         update_view_title(view);
5044                 }
5045                 result = status_update_write(&io, line->data, line->type);
5046         }
5048         done_io(&io);
5049         return result;
5052 static bool
5053 status_update(struct view *view)
5055         struct line *line = &view->line[view->lineno];
5057         assert(view->lines);
5059         if (!line->data) {
5060                 /* This should work even for the "On branch" line. */
5061                 if (line < view->line + view->lines && !line[1].data) {
5062                         report("Nothing to update");
5063                         return FALSE;
5064                 }
5066                 if (!status_update_files(view, line + 1)) {
5067                         report("Failed to update file status");
5068                         return FALSE;
5069                 }
5071         } else if (!status_update_file(line->data, line->type)) {
5072                 report("Failed to update file status");
5073                 return FALSE;
5074         }
5076         return TRUE;
5079 static bool
5080 status_revert(struct status *status, enum line_type type, bool has_none)
5082         if (!status || type != LINE_STAT_UNSTAGED) {
5083                 if (type == LINE_STAT_STAGED) {
5084                         report("Cannot revert changes to staged files");
5085                 } else if (type == LINE_STAT_UNTRACKED) {
5086                         report("Cannot revert changes to untracked files");
5087                 } else if (has_none) {
5088                         report("Nothing to revert");
5089                 } else {
5090                         report("Cannot revert changes to multiple files");
5091                 }
5092                 return FALSE;
5094         } else {
5095                 char mode[10] = "100644";
5096                 const char *reset_argv[] = {
5097                         "git", "update-index", "--cacheinfo", mode,
5098                                 status->old.rev, status->old.name, NULL
5099                 };
5100                 const char *checkout_argv[] = {
5101                         "git", "checkout", "--", status->old.name, NULL
5102                 };
5104                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5105                         return FALSE;
5106                 string_format(mode, "%o", status->old.mode);
5107                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5108                         run_io_fg(checkout_argv, opt_cdup);
5109         }
5112 static enum request
5113 status_request(struct view *view, enum request request, struct line *line)
5115         struct status *status = line->data;
5117         switch (request) {
5118         case REQ_STATUS_UPDATE:
5119                 if (!status_update(view))
5120                         return REQ_NONE;
5121                 break;
5123         case REQ_STATUS_REVERT:
5124                 if (!status_revert(status, line->type, status_has_none(view, line)))
5125                         return REQ_NONE;
5126                 break;
5128         case REQ_STATUS_MERGE:
5129                 if (!status || status->status != 'U') {
5130                         report("Merging only possible for files with unmerged status ('U').");
5131                         return REQ_NONE;
5132                 }
5133                 open_mergetool(status->new.name);
5134                 break;
5136         case REQ_EDIT:
5137                 if (!status)
5138                         return request;
5139                 if (status->status == 'D') {
5140                         report("File has been deleted.");
5141                         return REQ_NONE;
5142                 }
5144                 open_editor(status->status != '?', status->new.name);
5145                 break;
5147         case REQ_VIEW_BLAME:
5148                 if (status) {
5149                         string_copy(opt_file, status->new.name);
5150                         opt_ref[0] = 0;
5151                 }
5152                 return request;
5154         case REQ_ENTER:
5155                 /* After returning the status view has been split to
5156                  * show the stage view. No further reloading is
5157                  * necessary. */
5158                 status_enter(view, line);
5159                 return REQ_NONE;
5161         case REQ_REFRESH:
5162                 /* Simply reload the view. */
5163                 break;
5165         default:
5166                 return request;
5167         }
5169         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5171         return REQ_NONE;
5174 static void
5175 status_select(struct view *view, struct line *line)
5177         struct status *status = line->data;
5178         char file[SIZEOF_STR] = "all files";
5179         const char *text;
5180         const char *key;
5182         if (status && !string_format(file, "'%s'", status->new.name))
5183                 return;
5185         if (!status && line[1].type == LINE_STAT_NONE)
5186                 line++;
5188         switch (line->type) {
5189         case LINE_STAT_STAGED:
5190                 text = "Press %s to unstage %s for commit";
5191                 break;
5193         case LINE_STAT_UNSTAGED:
5194                 text = "Press %s to stage %s for commit";
5195                 break;
5197         case LINE_STAT_UNTRACKED:
5198                 text = "Press %s to stage %s for addition";
5199                 break;
5201         case LINE_STAT_HEAD:
5202         case LINE_STAT_NONE:
5203                 text = "Nothing to update";
5204                 break;
5206         default:
5207                 die("line type %d not handled in switch", line->type);
5208         }
5210         if (status && status->status == 'U') {
5211                 text = "Press %s to resolve conflict in %s";
5212                 key = get_key(REQ_STATUS_MERGE);
5214         } else {
5215                 key = get_key(REQ_STATUS_UPDATE);
5216         }
5218         string_format(view->ref, text, key, file);
5221 static bool
5222 status_grep(struct view *view, struct line *line)
5224         struct status *status = line->data;
5225         enum { S_STATUS, S_NAME, S_END } state;
5226         char buf[2] = "?";
5227         regmatch_t pmatch;
5229         if (!status)
5230                 return FALSE;
5232         for (state = S_STATUS; state < S_END; state++) {
5233                 const char *text;
5235                 switch (state) {
5236                 case S_NAME:    text = status->new.name;        break;
5237                 case S_STATUS:
5238                         buf[0] = status->status;
5239                         text = buf;
5240                         break;
5242                 default:
5243                         return FALSE;
5244                 }
5246                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5247                         return TRUE;
5248         }
5250         return FALSE;
5253 static struct view_ops status_ops = {
5254         "file",
5255         NULL,
5256         status_open,
5257         NULL,
5258         status_draw,
5259         status_request,
5260         status_grep,
5261         status_select,
5262 };
5265 static bool
5266 stage_diff_write(struct io *io, struct line *line, struct line *end)
5268         while (line < end) {
5269                 if (!io_write(io, line->data, strlen(line->data)) ||
5270                     !io_write(io, "\n", 1))
5271                         return FALSE;
5272                 line++;
5273                 if (line->type == LINE_DIFF_CHUNK ||
5274                     line->type == LINE_DIFF_HEADER)
5275                         break;
5276         }
5278         return TRUE;
5281 static struct line *
5282 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5284         for (; view->line < line; line--)
5285                 if (line->type == type)
5286                         return line;
5288         return NULL;
5291 static bool
5292 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5294         const char *apply_argv[SIZEOF_ARG] = {
5295                 "git", "apply", "--whitespace=nowarn", NULL
5296         };
5297         struct line *diff_hdr;
5298         struct io io = {};
5299         int argc = 3;
5301         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5302         if (!diff_hdr)
5303                 return FALSE;
5305         if (!revert)
5306                 apply_argv[argc++] = "--cached";
5307         if (revert || stage_line_type == LINE_STAT_STAGED)
5308                 apply_argv[argc++] = "-R";
5309         apply_argv[argc++] = "-";
5310         apply_argv[argc++] = NULL;
5311         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5312                 return FALSE;
5314         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5315             !stage_diff_write(&io, chunk, view->line + view->lines))
5316                 chunk = NULL;
5318         done_io(&io);
5319         run_io_bg(update_index_argv);
5321         return chunk ? TRUE : FALSE;
5324 static bool
5325 stage_update(struct view *view, struct line *line)
5327         struct line *chunk = NULL;
5329         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5330                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5332         if (chunk) {
5333                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5334                         report("Failed to apply chunk");
5335                         return FALSE;
5336                 }
5338         } else if (!stage_status.status) {
5339                 view = VIEW(REQ_VIEW_STATUS);
5341                 for (line = view->line; line < view->line + view->lines; line++)
5342                         if (line->type == stage_line_type)
5343                                 break;
5345                 if (!status_update_files(view, line + 1)) {
5346                         report("Failed to update files");
5347                         return FALSE;
5348                 }
5350         } else if (!status_update_file(&stage_status, stage_line_type)) {
5351                 report("Failed to update file");
5352                 return FALSE;
5353         }
5355         return TRUE;
5358 static bool
5359 stage_revert(struct view *view, struct line *line)
5361         struct line *chunk = NULL;
5363         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5364                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5366         if (chunk) {
5367                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5368                         return FALSE;
5370                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5371                         report("Failed to revert chunk");
5372                         return FALSE;
5373                 }
5374                 return TRUE;
5376         } else {
5377                 return status_revert(stage_status.status ? &stage_status : NULL,
5378                                      stage_line_type, FALSE);
5379         }
5383 static void
5384 stage_next(struct view *view, struct line *line)
5386         int i;
5388         if (!stage_chunks) {
5389                 static size_t alloc = 0;
5390                 int *tmp;
5392                 for (line = view->line; line < view->line + view->lines; line++) {
5393                         if (line->type != LINE_DIFF_CHUNK)
5394                                 continue;
5396                         tmp = realloc_items(stage_chunk, &alloc,
5397                                             stage_chunks, sizeof(*tmp));
5398                         if (!tmp) {
5399                                 report("Allocation failure");
5400                                 return;
5401                         }
5403                         stage_chunk = tmp;
5404                         stage_chunk[stage_chunks++] = line - view->line;
5405                 }
5406         }
5408         for (i = 0; i < stage_chunks; i++) {
5409                 if (stage_chunk[i] > view->lineno) {
5410                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5411                         report("Chunk %d of %d", i + 1, stage_chunks);
5412                         return;
5413                 }
5414         }
5416         report("No next chunk found");
5419 static enum request
5420 stage_request(struct view *view, enum request request, struct line *line)
5422         switch (request) {
5423         case REQ_STATUS_UPDATE:
5424                 if (!stage_update(view, line))
5425                         return REQ_NONE;
5426                 break;
5428         case REQ_STATUS_REVERT:
5429                 if (!stage_revert(view, line))
5430                         return REQ_NONE;
5431                 break;
5433         case REQ_STAGE_NEXT:
5434                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5435                         report("File is untracked; press %s to add",
5436                                get_key(REQ_STATUS_UPDATE));
5437                         return REQ_NONE;
5438                 }
5439                 stage_next(view, line);
5440                 return REQ_NONE;
5442         case REQ_EDIT:
5443                 if (!stage_status.new.name[0])
5444                         return request;
5445                 if (stage_status.status == 'D') {
5446                         report("File has been deleted.");
5447                         return REQ_NONE;
5448                 }
5450                 open_editor(stage_status.status != '?', stage_status.new.name);
5451                 break;
5453         case REQ_REFRESH:
5454                 /* Reload everything ... */
5455                 break;
5457         case REQ_VIEW_BLAME:
5458                 if (stage_status.new.name[0]) {
5459                         string_copy(opt_file, stage_status.new.name);
5460                         opt_ref[0] = 0;
5461                 }
5462                 return request;
5464         case REQ_ENTER:
5465                 return pager_request(view, request, line);
5467         default:
5468                 return request;
5469         }
5471         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5472         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5474         /* Check whether the staged entry still exists, and close the
5475          * stage view if it doesn't. */
5476         if (!status_exists(&stage_status, stage_line_type)) {
5477                 status_restore(VIEW(REQ_VIEW_STATUS));
5478                 return REQ_VIEW_CLOSE;
5479         }
5481         if (stage_line_type == LINE_STAT_UNTRACKED) {
5482                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5483                         report("Cannot display a directory");
5484                         return REQ_NONE;
5485                 }
5487                 if (!prepare_update_file(view, stage_status.new.name)) {
5488                         report("Failed to open file: %s", strerror(errno));
5489                         return REQ_NONE;
5490                 }
5491         }
5492         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5494         return REQ_NONE;
5497 static struct view_ops stage_ops = {
5498         "line",
5499         NULL,
5500         NULL,
5501         pager_read,
5502         pager_draw,
5503         stage_request,
5504         pager_grep,
5505         pager_select,
5506 };
5509 /*
5510  * Revision graph
5511  */
5513 struct commit {
5514         char id[SIZEOF_REV];            /* SHA1 ID. */
5515         char title[128];                /* First line of the commit message. */
5516         char author[75];                /* Author of the commit. */
5517         struct tm time;                 /* Date from the author ident. */
5518         struct ref **refs;              /* Repository references. */
5519         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5520         size_t graph_size;              /* The width of the graph array. */
5521         bool has_parents;               /* Rewritten --parents seen. */
5522 };
5524 /* Size of rev graph with no  "padding" columns */
5525 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5527 struct rev_graph {
5528         struct rev_graph *prev, *next, *parents;
5529         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5530         size_t size;
5531         struct commit *commit;
5532         size_t pos;
5533         unsigned int boundary:1;
5534 };
5536 /* Parents of the commit being visualized. */
5537 static struct rev_graph graph_parents[4];
5539 /* The current stack of revisions on the graph. */
5540 static struct rev_graph graph_stacks[4] = {
5541         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5542         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5543         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5544         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5545 };
5547 static inline bool
5548 graph_parent_is_merge(struct rev_graph *graph)
5550         return graph->parents->size > 1;
5553 static inline void
5554 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5556         struct commit *commit = graph->commit;
5558         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5559                 commit->graph[commit->graph_size++] = symbol;
5562 static void
5563 clear_rev_graph(struct rev_graph *graph)
5565         graph->boundary = 0;
5566         graph->size = graph->pos = 0;
5567         graph->commit = NULL;
5568         memset(graph->parents, 0, sizeof(*graph->parents));
5571 static void
5572 done_rev_graph(struct rev_graph *graph)
5574         if (graph_parent_is_merge(graph) &&
5575             graph->pos < graph->size - 1 &&
5576             graph->next->size == graph->size + graph->parents->size - 1) {
5577                 size_t i = graph->pos + graph->parents->size - 1;
5579                 graph->commit->graph_size = i * 2;
5580                 while (i < graph->next->size - 1) {
5581                         append_to_rev_graph(graph, ' ');
5582                         append_to_rev_graph(graph, '\\');
5583                         i++;
5584                 }
5585         }
5587         clear_rev_graph(graph);
5590 static void
5591 push_rev_graph(struct rev_graph *graph, const char *parent)
5593         int i;
5595         /* "Collapse" duplicate parents lines.
5596          *
5597          * FIXME: This needs to also update update the drawn graph but
5598          * for now it just serves as a method for pruning graph lines. */
5599         for (i = 0; i < graph->size; i++)
5600                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5601                         return;
5603         if (graph->size < SIZEOF_REVITEMS) {
5604                 string_copy_rev(graph->rev[graph->size++], parent);
5605         }
5608 static chtype
5609 get_rev_graph_symbol(struct rev_graph *graph)
5611         chtype symbol;
5613         if (graph->boundary)
5614                 symbol = REVGRAPH_BOUND;
5615         else if (graph->parents->size == 0)
5616                 symbol = REVGRAPH_INIT;
5617         else if (graph_parent_is_merge(graph))
5618                 symbol = REVGRAPH_MERGE;
5619         else if (graph->pos >= graph->size)
5620                 symbol = REVGRAPH_BRANCH;
5621         else
5622                 symbol = REVGRAPH_COMMIT;
5624         return symbol;
5627 static void
5628 draw_rev_graph(struct rev_graph *graph)
5630         struct rev_filler {
5631                 chtype separator, line;
5632         };
5633         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5634         static struct rev_filler fillers[] = {
5635                 { ' ',  '|' },
5636                 { '`',  '.' },
5637                 { '\'', ' ' },
5638                 { '/',  ' ' },
5639         };
5640         chtype symbol = get_rev_graph_symbol(graph);
5641         struct rev_filler *filler;
5642         size_t i;
5644         if (opt_line_graphics)
5645                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5647         filler = &fillers[DEFAULT];
5649         for (i = 0; i < graph->pos; i++) {
5650                 append_to_rev_graph(graph, filler->line);
5651                 if (graph_parent_is_merge(graph->prev) &&
5652                     graph->prev->pos == i)
5653                         filler = &fillers[RSHARP];
5655                 append_to_rev_graph(graph, filler->separator);
5656         }
5658         /* Place the symbol for this revision. */
5659         append_to_rev_graph(graph, symbol);
5661         if (graph->prev->size > graph->size)
5662                 filler = &fillers[RDIAG];
5663         else
5664                 filler = &fillers[DEFAULT];
5666         i++;
5668         for (; i < graph->size; i++) {
5669                 append_to_rev_graph(graph, filler->separator);
5670                 append_to_rev_graph(graph, filler->line);
5671                 if (graph_parent_is_merge(graph->prev) &&
5672                     i < graph->prev->pos + graph->parents->size)
5673                         filler = &fillers[RSHARP];
5674                 if (graph->prev->size > graph->size)
5675                         filler = &fillers[LDIAG];
5676         }
5678         if (graph->prev->size > graph->size) {
5679                 append_to_rev_graph(graph, filler->separator);
5680                 if (filler->line != ' ')
5681                         append_to_rev_graph(graph, filler->line);
5682         }
5685 /* Prepare the next rev graph */
5686 static void
5687 prepare_rev_graph(struct rev_graph *graph)
5689         size_t i;
5691         /* First, traverse all lines of revisions up to the active one. */
5692         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5693                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5694                         break;
5696                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5697         }
5699         /* Interleave the new revision parent(s). */
5700         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5701                 push_rev_graph(graph->next, graph->parents->rev[i]);
5703         /* Lastly, put any remaining revisions. */
5704         for (i = graph->pos + 1; i < graph->size; i++)
5705                 push_rev_graph(graph->next, graph->rev[i]);
5708 static void
5709 update_rev_graph(struct view *view, struct rev_graph *graph)
5711         /* If this is the finalizing update ... */
5712         if (graph->commit)
5713                 prepare_rev_graph(graph);
5715         /* Graph visualization needs a one rev look-ahead,
5716          * so the first update doesn't visualize anything. */
5717         if (!graph->prev->commit)
5718                 return;
5720         if (view->lines > 2)
5721                 view->line[view->lines - 3].dirty = 1;
5722         if (view->lines > 1)
5723                 view->line[view->lines - 2].dirty = 1;
5724         draw_rev_graph(graph->prev);
5725         done_rev_graph(graph->prev->prev);
5729 /*
5730  * Main view backend
5731  */
5733 static const char *main_argv[SIZEOF_ARG] = {
5734         "git", "log", "--no-color", "--pretty=raw", "--parents",
5735                       "--topo-order", "%(head)", NULL
5736 };
5738 static bool
5739 main_draw(struct view *view, struct line *line, unsigned int lineno)
5741         struct commit *commit = line->data;
5743         if (!*commit->author)
5744                 return FALSE;
5746         if (opt_date && draw_date(view, &commit->time))
5747                 return TRUE;
5749         if (opt_author && draw_author(view, commit->author))
5750                 return TRUE;
5752         if (opt_rev_graph && commit->graph_size &&
5753             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5754                 return TRUE;
5756         if (opt_show_refs && commit->refs) {
5757                 size_t i = 0;
5759                 do {
5760                         enum line_type type;
5762                         if (commit->refs[i]->head)
5763                                 type = LINE_MAIN_HEAD;
5764                         else if (commit->refs[i]->ltag)
5765                                 type = LINE_MAIN_LOCAL_TAG;
5766                         else if (commit->refs[i]->tag)
5767                                 type = LINE_MAIN_TAG;
5768                         else if (commit->refs[i]->tracked)
5769                                 type = LINE_MAIN_TRACKED;
5770                         else if (commit->refs[i]->remote)
5771                                 type = LINE_MAIN_REMOTE;
5772                         else
5773                                 type = LINE_MAIN_REF;
5775                         if (draw_text(view, type, "[", TRUE) ||
5776                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5777                             draw_text(view, type, "]", TRUE))
5778                                 return TRUE;
5780                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5781                                 return TRUE;
5782                 } while (commit->refs[i++]->next);
5783         }
5785         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5786         return TRUE;
5789 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5790 static bool
5791 main_read(struct view *view, char *line)
5793         static struct rev_graph *graph = graph_stacks;
5794         enum line_type type;
5795         struct commit *commit;
5797         if (!line) {
5798                 int i;
5800                 if (!view->lines && !view->parent)
5801                         die("No revisions match the given arguments.");
5802                 if (view->lines > 0) {
5803                         commit = view->line[view->lines - 1].data;
5804                         view->line[view->lines - 1].dirty = 1;
5805                         if (!*commit->author) {
5806                                 view->lines--;
5807                                 free(commit);
5808                                 graph->commit = NULL;
5809                         }
5810                 }
5811                 update_rev_graph(view, graph);
5813                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5814                         clear_rev_graph(&graph_stacks[i]);
5815                 return TRUE;
5816         }
5818         type = get_line_type(line);
5819         if (type == LINE_COMMIT) {
5820                 commit = calloc(1, sizeof(struct commit));
5821                 if (!commit)
5822                         return FALSE;
5824                 line += STRING_SIZE("commit ");
5825                 if (*line == '-') {
5826                         graph->boundary = 1;
5827                         line++;
5828                 }
5830                 string_copy_rev(commit->id, line);
5831                 commit->refs = get_refs(commit->id);
5832                 graph->commit = commit;
5833                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5835                 while ((line = strchr(line, ' '))) {
5836                         line++;
5837                         push_rev_graph(graph->parents, line);
5838                         commit->has_parents = TRUE;
5839                 }
5840                 return TRUE;
5841         }
5843         if (!view->lines)
5844                 return TRUE;
5845         commit = view->line[view->lines - 1].data;
5847         switch (type) {
5848         case LINE_PARENT:
5849                 if (commit->has_parents)
5850                         break;
5851                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5852                 break;
5854         case LINE_AUTHOR:
5855                 parse_author_line(line + STRING_SIZE("author "),
5856                                   commit->author, sizeof(commit->author),
5857                                   &commit->time);
5858                 update_rev_graph(view, graph);
5859                 graph = graph->next;
5860                 break;
5862         default:
5863                 /* Fill in the commit title if it has not already been set. */
5864                 if (commit->title[0])
5865                         break;
5867                 /* Require titles to start with a non-space character at the
5868                  * offset used by git log. */
5869                 if (strncmp(line, "    ", 4))
5870                         break;
5871                 line += 4;
5872                 /* Well, if the title starts with a whitespace character,
5873                  * try to be forgiving.  Otherwise we end up with no title. */
5874                 while (isspace(*line))
5875                         line++;
5876                 if (*line == '\0')
5877                         break;
5878                 /* FIXME: More graceful handling of titles; append "..." to
5879                  * shortened titles, etc. */
5881                 string_expand(commit->title, sizeof(commit->title), line, 1);
5882                 view->line[view->lines - 1].dirty = 1;
5883         }
5885         return TRUE;
5888 static enum request
5889 main_request(struct view *view, enum request request, struct line *line)
5891         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5893         switch (request) {
5894         case REQ_ENTER:
5895                 open_view(view, REQ_VIEW_DIFF, flags);
5896                 break;
5897         case REQ_REFRESH:
5898                 load_refs();
5899                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5900                 break;
5901         default:
5902                 return request;
5903         }
5905         return REQ_NONE;
5908 static bool
5909 grep_refs(struct ref **refs, regex_t *regex)
5911         regmatch_t pmatch;
5912         size_t i = 0;
5914         if (!refs)
5915                 return FALSE;
5916         do {
5917                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5918                         return TRUE;
5919         } while (refs[i++]->next);
5921         return FALSE;
5924 static bool
5925 main_grep(struct view *view, struct line *line)
5927         struct commit *commit = line->data;
5928         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5929         char buf[DATE_COLS + 1];
5930         regmatch_t pmatch;
5932         for (state = S_TITLE; state < S_END; state++) {
5933                 char *text;
5935                 switch (state) {
5936                 case S_TITLE:   text = commit->title;   break;
5937                 case S_AUTHOR:
5938                         if (!opt_author)
5939                                 continue;
5940                         text = commit->author;
5941                         break;
5942                 case S_DATE:
5943                         if (!opt_date)
5944                                 continue;
5945                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5946                                 continue;
5947                         text = buf;
5948                         break;
5949                 case S_REFS:
5950                         if (!opt_show_refs)
5951                                 continue;
5952                         if (grep_refs(commit->refs, view->regex) == TRUE)
5953                                 return TRUE;
5954                         continue;
5955                 default:
5956                         return FALSE;
5957                 }
5959                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5960                         return TRUE;
5961         }
5963         return FALSE;
5966 static void
5967 main_select(struct view *view, struct line *line)
5969         struct commit *commit = line->data;
5971         string_copy_rev(view->ref, commit->id);
5972         string_copy_rev(ref_commit, view->ref);
5975 static struct view_ops main_ops = {
5976         "commit",
5977         main_argv,
5978         NULL,
5979         main_read,
5980         main_draw,
5981         main_request,
5982         main_grep,
5983         main_select,
5984 };
5987 /*
5988  * Unicode / UTF-8 handling
5989  *
5990  * NOTE: Much of the following code for dealing with unicode is derived from
5991  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5992  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5993  */
5995 /* I've (over)annotated a lot of code snippets because I am not entirely
5996  * confident that the approach taken by this small UTF-8 interface is correct.
5997  * --jonas */
5999 static inline int
6000 unicode_width(unsigned long c)
6002         if (c >= 0x1100 &&
6003            (c <= 0x115f                         /* Hangul Jamo */
6004             || c == 0x2329
6005             || c == 0x232a
6006             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6007                                                 /* CJK ... Yi */
6008             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6009             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6010             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6011             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6012             || (c >= 0xffe0  && c <= 0xffe6)
6013             || (c >= 0x20000 && c <= 0x2fffd)
6014             || (c >= 0x30000 && c <= 0x3fffd)))
6015                 return 2;
6017         if (c == '\t')
6018                 return opt_tab_size;
6020         return 1;
6023 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6024  * Illegal bytes are set one. */
6025 static const unsigned char utf8_bytes[256] = {
6026         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6027         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6028         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6029         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6030         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6031         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,
6032         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,
6033         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,
6034 };
6036 /* Decode UTF-8 multi-byte representation into a unicode character. */
6037 static inline unsigned long
6038 utf8_to_unicode(const char *string, size_t length)
6040         unsigned long unicode;
6042         switch (length) {
6043         case 1:
6044                 unicode  =   string[0];
6045                 break;
6046         case 2:
6047                 unicode  =  (string[0] & 0x1f) << 6;
6048                 unicode +=  (string[1] & 0x3f);
6049                 break;
6050         case 3:
6051                 unicode  =  (string[0] & 0x0f) << 12;
6052                 unicode += ((string[1] & 0x3f) << 6);
6053                 unicode +=  (string[2] & 0x3f);
6054                 break;
6055         case 4:
6056                 unicode  =  (string[0] & 0x0f) << 18;
6057                 unicode += ((string[1] & 0x3f) << 12);
6058                 unicode += ((string[2] & 0x3f) << 6);
6059                 unicode +=  (string[3] & 0x3f);
6060                 break;
6061         case 5:
6062                 unicode  =  (string[0] & 0x0f) << 24;
6063                 unicode += ((string[1] & 0x3f) << 18);
6064                 unicode += ((string[2] & 0x3f) << 12);
6065                 unicode += ((string[3] & 0x3f) << 6);
6066                 unicode +=  (string[4] & 0x3f);
6067                 break;
6068         case 6:
6069                 unicode  =  (string[0] & 0x01) << 30;
6070                 unicode += ((string[1] & 0x3f) << 24);
6071                 unicode += ((string[2] & 0x3f) << 18);
6072                 unicode += ((string[3] & 0x3f) << 12);
6073                 unicode += ((string[4] & 0x3f) << 6);
6074                 unicode +=  (string[5] & 0x3f);
6075                 break;
6076         default:
6077                 die("Invalid unicode length");
6078         }
6080         /* Invalid characters could return the special 0xfffd value but NUL
6081          * should be just as good. */
6082         return unicode > 0xffff ? 0 : unicode;
6085 /* Calculates how much of string can be shown within the given maximum width
6086  * and sets trimmed parameter to non-zero value if all of string could not be
6087  * shown. If the reserve flag is TRUE, it will reserve at least one
6088  * trailing character, which can be useful when drawing a delimiter.
6089  *
6090  * Returns the number of bytes to output from string to satisfy max_width. */
6091 static size_t
6092 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6094         const char *string = *start;
6095         const char *end = strchr(string, '\0');
6096         unsigned char last_bytes = 0;
6097         size_t last_ucwidth = 0;
6099         *width = 0;
6100         *trimmed = 0;
6102         while (string < end) {
6103                 int c = *(unsigned char *) string;
6104                 unsigned char bytes = utf8_bytes[c];
6105                 size_t ucwidth;
6106                 unsigned long unicode;
6108                 if (string + bytes > end)
6109                         break;
6111                 /* Change representation to figure out whether
6112                  * it is a single- or double-width character. */
6114                 unicode = utf8_to_unicode(string, bytes);
6115                 /* FIXME: Graceful handling of invalid unicode character. */
6116                 if (!unicode)
6117                         break;
6119                 ucwidth = unicode_width(unicode);
6120                 if (skip > 0) {
6121                         skip -= ucwidth <= skip ? ucwidth : skip;
6122                         *start += bytes;
6123                 }
6124                 *width  += ucwidth;
6125                 if (*width > max_width) {
6126                         *trimmed = 1;
6127                         *width -= ucwidth;
6128                         if (reserve && *width == max_width) {
6129                                 string -= last_bytes;
6130                                 *width -= last_ucwidth;
6131                         }
6132                         break;
6133                 }
6135                 string  += bytes;
6136                 last_bytes = ucwidth ? bytes : 0;
6137                 last_ucwidth = ucwidth;
6138         }
6140         return string - *start;
6144 /*
6145  * Status management
6146  */
6148 /* Whether or not the curses interface has been initialized. */
6149 static bool cursed = FALSE;
6151 /* Terminal hacks and workarounds. */
6152 static bool use_scroll_redrawwin;
6153 static bool use_scroll_status_wclear;
6155 /* The status window is used for polling keystrokes. */
6156 static WINDOW *status_win;
6158 /* Reading from the prompt? */
6159 static bool input_mode = FALSE;
6161 static bool status_empty = FALSE;
6163 /* Update status and title window. */
6164 static void
6165 report(const char *msg, ...)
6167         struct view *view = display[current_view];
6169         if (input_mode)
6170                 return;
6172         if (!view) {
6173                 char buf[SIZEOF_STR];
6174                 va_list args;
6176                 va_start(args, msg);
6177                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6178                         buf[sizeof(buf) - 1] = 0;
6179                         buf[sizeof(buf) - 2] = '.';
6180                         buf[sizeof(buf) - 3] = '.';
6181                         buf[sizeof(buf) - 4] = '.';
6182                 }
6183                 va_end(args);
6184                 die("%s", buf);
6185         }
6187         if (!status_empty || *msg) {
6188                 va_list args;
6190                 va_start(args, msg);
6192                 wmove(status_win, 0, 0);
6193                 if (view->has_scrolled && use_scroll_status_wclear)
6194                         wclear(status_win);
6195                 if (*msg) {
6196                         vwprintw(status_win, msg, args);
6197                         status_empty = FALSE;
6198                 } else {
6199                         status_empty = TRUE;
6200                 }
6201                 wclrtoeol(status_win);
6202                 wnoutrefresh(status_win);
6204                 va_end(args);
6205         }
6207         update_view_title(view);
6210 /* Controls when nodelay should be in effect when polling user input. */
6211 static void
6212 set_nonblocking_input(bool loading)
6214         static unsigned int loading_views;
6216         if ((loading == FALSE && loading_views-- == 1) ||
6217             (loading == TRUE  && loading_views++ == 0))
6218                 nodelay(status_win, loading);
6221 static void
6222 init_display(void)
6224         const char *term;
6225         int x, y;
6227         /* Initialize the curses library */
6228         if (isatty(STDIN_FILENO)) {
6229                 cursed = !!initscr();
6230                 opt_tty = stdin;
6231         } else {
6232                 /* Leave stdin and stdout alone when acting as a pager. */
6233                 opt_tty = fopen("/dev/tty", "r+");
6234                 if (!opt_tty)
6235                         die("Failed to open /dev/tty");
6236                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6237         }
6239         if (!cursed)
6240                 die("Failed to initialize curses");
6242         nonl();         /* Tell curses not to do NL->CR/NL on output */
6243         cbreak();       /* Take input chars one at a time, no wait for \n */
6244         noecho();       /* Don't echo input */
6245         leaveok(stdscr, FALSE);
6247         if (has_colors())
6248                 init_colors();
6250         getmaxyx(stdscr, y, x);
6251         status_win = newwin(1, 0, y - 1, 0);
6252         if (!status_win)
6253                 die("Failed to create status window");
6255         /* Enable keyboard mapping */
6256         keypad(status_win, TRUE);
6257         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6259         TABSIZE = opt_tab_size;
6260         if (opt_line_graphics) {
6261                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6262         }
6264         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6265         if (term && !strcmp(term, "gnome-terminal")) {
6266                 /* In the gnome-terminal-emulator, the message from
6267                  * scrolling up one line when impossible followed by
6268                  * scrolling down one line causes corruption of the
6269                  * status line. This is fixed by calling wclear. */
6270                 use_scroll_status_wclear = TRUE;
6271                 use_scroll_redrawwin = FALSE;
6273         } else if (term && !strcmp(term, "xrvt-xpm")) {
6274                 /* No problems with full optimizations in xrvt-(unicode)
6275                  * and aterm. */
6276                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6278         } else {
6279                 /* When scrolling in (u)xterm the last line in the
6280                  * scrolling direction will update slowly. */
6281                 use_scroll_redrawwin = TRUE;
6282                 use_scroll_status_wclear = FALSE;
6283         }
6286 static int
6287 get_input(int prompt_position)
6289         struct view *view;
6290         int i, key, cursor_y, cursor_x;
6292         if (prompt_position)
6293                 input_mode = TRUE;
6295         while (TRUE) {
6296                 foreach_view (view, i) {
6297                         update_view(view);
6298                         if (view_is_displayed(view) && view->has_scrolled &&
6299                             use_scroll_redrawwin)
6300                                 redrawwin(view->win);
6301                         view->has_scrolled = FALSE;
6302                 }
6304                 /* Update the cursor position. */
6305                 if (prompt_position) {
6306                         getbegyx(status_win, cursor_y, cursor_x);
6307                         cursor_x = prompt_position;
6308                 } else {
6309                         view = display[current_view];
6310                         getbegyx(view->win, cursor_y, cursor_x);
6311                         cursor_x = view->width - 1;
6312                         cursor_y += view->lineno - view->offset;
6313                 }
6314                 setsyx(cursor_y, cursor_x);
6316                 /* Refresh, accept single keystroke of input */
6317                 doupdate();
6318                 key = wgetch(status_win);
6320                 /* wgetch() with nodelay() enabled returns ERR when
6321                  * there's no input. */
6322                 if (key == ERR) {
6324                 } else if (key == KEY_RESIZE) {
6325                         int height, width;
6327                         getmaxyx(stdscr, height, width);
6329                         wresize(status_win, 1, width);
6330                         mvwin(status_win, height - 1, 0);
6331                         wnoutrefresh(status_win);
6332                         resize_display();
6333                         redraw_display(TRUE);
6335                 } else {
6336                         input_mode = FALSE;
6337                         return key;
6338                 }
6339         }
6342 static char *
6343 prompt_input(const char *prompt, input_handler handler, void *data)
6345         enum input_status status = INPUT_OK;
6346         static char buf[SIZEOF_STR];
6347         size_t pos = 0;
6349         buf[pos] = 0;
6351         while (status == INPUT_OK || status == INPUT_SKIP) {
6352                 int key;
6354                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6355                 wclrtoeol(status_win);
6357                 key = get_input(pos + 1);
6358                 switch (key) {
6359                 case KEY_RETURN:
6360                 case KEY_ENTER:
6361                 case '\n':
6362                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6363                         break;
6365                 case KEY_BACKSPACE:
6366                         if (pos > 0)
6367                                 buf[--pos] = 0;
6368                         else
6369                                 status = INPUT_CANCEL;
6370                         break;
6372                 case KEY_ESC:
6373                         status = INPUT_CANCEL;
6374                         break;
6376                 default:
6377                         if (pos >= sizeof(buf)) {
6378                                 report("Input string too long");
6379                                 return NULL;
6380                         }
6382                         status = handler(data, buf, key);
6383                         if (status == INPUT_OK)
6384                                 buf[pos++] = (char) key;
6385                 }
6386         }
6388         /* Clear the status window */
6389         status_empty = FALSE;
6390         report("");
6392         if (status == INPUT_CANCEL)
6393                 return NULL;
6395         buf[pos++] = 0;
6397         return buf;
6400 static enum input_status
6401 prompt_yesno_handler(void *data, char *buf, int c)
6403         if (c == 'y' || c == 'Y')
6404                 return INPUT_STOP;
6405         if (c == 'n' || c == 'N')
6406                 return INPUT_CANCEL;
6407         return INPUT_SKIP;
6410 static bool
6411 prompt_yesno(const char *prompt)
6413         char prompt2[SIZEOF_STR];
6415         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6416                 return FALSE;
6418         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6421 static enum input_status
6422 read_prompt_handler(void *data, char *buf, int c)
6424         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6427 static char *
6428 read_prompt(const char *prompt)
6430         return prompt_input(prompt, read_prompt_handler, NULL);
6433 /*
6434  * Repository properties
6435  */
6437 static struct ref *refs = NULL;
6438 static size_t refs_alloc = 0;
6439 static size_t refs_size = 0;
6441 /* Id <-> ref store */
6442 static struct ref ***id_refs = NULL;
6443 static size_t id_refs_alloc = 0;
6444 static size_t id_refs_size = 0;
6446 static int
6447 compare_refs(const void *ref1_, const void *ref2_)
6449         const struct ref *ref1 = *(const struct ref **)ref1_;
6450         const struct ref *ref2 = *(const struct ref **)ref2_;
6452         if (ref1->tag != ref2->tag)
6453                 return ref2->tag - ref1->tag;
6454         if (ref1->ltag != ref2->ltag)
6455                 return ref2->ltag - ref2->ltag;
6456         if (ref1->head != ref2->head)
6457                 return ref2->head - ref1->head;
6458         if (ref1->tracked != ref2->tracked)
6459                 return ref2->tracked - ref1->tracked;
6460         if (ref1->remote != ref2->remote)
6461                 return ref2->remote - ref1->remote;
6462         return strcmp(ref1->name, ref2->name);
6465 static struct ref **
6466 get_refs(const char *id)
6468         struct ref ***tmp_id_refs;
6469         struct ref **ref_list = NULL;
6470         size_t ref_list_alloc = 0;
6471         size_t ref_list_size = 0;
6472         size_t i;
6474         for (i = 0; i < id_refs_size; i++)
6475                 if (!strcmp(id, id_refs[i][0]->id))
6476                         return id_refs[i];
6478         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6479                                     sizeof(*id_refs));
6480         if (!tmp_id_refs)
6481                 return NULL;
6483         id_refs = tmp_id_refs;
6485         for (i = 0; i < refs_size; i++) {
6486                 struct ref **tmp;
6488                 if (strcmp(id, refs[i].id))
6489                         continue;
6491                 tmp = realloc_items(ref_list, &ref_list_alloc,
6492                                     ref_list_size + 1, sizeof(*ref_list));
6493                 if (!tmp) {
6494                         if (ref_list)
6495                                 free(ref_list);
6496                         return NULL;
6497                 }
6499                 ref_list = tmp;
6500                 ref_list[ref_list_size] = &refs[i];
6501                 /* XXX: The properties of the commit chains ensures that we can
6502                  * safely modify the shared ref. The repo references will
6503                  * always be similar for the same id. */
6504                 ref_list[ref_list_size]->next = 1;
6506                 ref_list_size++;
6507         }
6509         if (ref_list) {
6510                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6511                 ref_list[ref_list_size - 1]->next = 0;
6512                 id_refs[id_refs_size++] = ref_list;
6513         }
6515         return ref_list;
6518 static int
6519 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6521         struct ref *ref;
6522         bool tag = FALSE;
6523         bool ltag = FALSE;
6524         bool remote = FALSE;
6525         bool tracked = FALSE;
6526         bool check_replace = FALSE;
6527         bool head = FALSE;
6529         if (!prefixcmp(name, "refs/tags/")) {
6530                 if (!suffixcmp(name, namelen, "^{}")) {
6531                         namelen -= 3;
6532                         name[namelen] = 0;
6533                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6534                                 check_replace = TRUE;
6535                 } else {
6536                         ltag = TRUE;
6537                 }
6539                 tag = TRUE;
6540                 namelen -= STRING_SIZE("refs/tags/");
6541                 name    += STRING_SIZE("refs/tags/");
6543         } else if (!prefixcmp(name, "refs/remotes/")) {
6544                 remote = TRUE;
6545                 namelen -= STRING_SIZE("refs/remotes/");
6546                 name    += STRING_SIZE("refs/remotes/");
6547                 tracked  = !strcmp(opt_remote, name);
6549         } else if (!prefixcmp(name, "refs/heads/")) {
6550                 namelen -= STRING_SIZE("refs/heads/");
6551                 name    += STRING_SIZE("refs/heads/");
6552                 head     = !strncmp(opt_head, name, namelen);
6554         } else if (!strcmp(name, "HEAD")) {
6555                 string_ncopy(opt_head_rev, id, idlen);
6556                 return OK;
6557         }
6559         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6560                 /* it's an annotated tag, replace the previous sha1 with the
6561                  * resolved commit id; relies on the fact git-ls-remote lists
6562                  * the commit id of an annotated tag right before the commit id
6563                  * it points to. */
6564                 refs[refs_size - 1].ltag = ltag;
6565                 string_copy_rev(refs[refs_size - 1].id, id);
6567                 return OK;
6568         }
6569         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6570         if (!refs)
6571                 return ERR;
6573         ref = &refs[refs_size++];
6574         ref->name = malloc(namelen + 1);
6575         if (!ref->name)
6576                 return ERR;
6578         strncpy(ref->name, name, namelen);
6579         ref->name[namelen] = 0;
6580         ref->head = head;
6581         ref->tag = tag;
6582         ref->ltag = ltag;
6583         ref->remote = remote;
6584         ref->tracked = tracked;
6585         string_copy_rev(ref->id, id);
6587         return OK;
6590 static int
6591 load_refs(void)
6593         static const char *ls_remote_argv[SIZEOF_ARG] = {
6594                 "git", "ls-remote", ".", NULL
6595         };
6596         static bool init = FALSE;
6598         if (!init) {
6599                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6600                 init = TRUE;
6601         }
6603         if (!*opt_git_dir)
6604                 return OK;
6606         while (refs_size > 0)
6607                 free(refs[--refs_size].name);
6608         while (id_refs_size > 0)
6609                 free(id_refs[--id_refs_size]);
6611         return run_io_load(ls_remote_argv, "\t", read_ref);
6614 static int
6615 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6617         if (!strcmp(name, "i18n.commitencoding"))
6618                 string_ncopy(opt_encoding, value, valuelen);
6620         if (!strcmp(name, "core.editor"))
6621                 string_ncopy(opt_editor, value, valuelen);
6623         /* branch.<head>.remote */
6624         if (*opt_head &&
6625             !strncmp(name, "branch.", 7) &&
6626             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6627             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6628                 string_ncopy(opt_remote, value, valuelen);
6630         if (*opt_head && *opt_remote &&
6631             !strncmp(name, "branch.", 7) &&
6632             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6633             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6634                 size_t from = strlen(opt_remote);
6636                 if (!prefixcmp(value, "refs/heads/")) {
6637                         value += STRING_SIZE("refs/heads/");
6638                         valuelen -= STRING_SIZE("refs/heads/");
6639                 }
6641                 if (!string_format_from(opt_remote, &from, "/%s", value))
6642                         opt_remote[0] = 0;
6643         }
6645         return OK;
6648 static int
6649 load_git_config(void)
6651         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6653         return run_io_load(config_list_argv, "=", read_repo_config_option);
6656 static int
6657 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6659         if (!opt_git_dir[0]) {
6660                 string_ncopy(opt_git_dir, name, namelen);
6662         } else if (opt_is_inside_work_tree == -1) {
6663                 /* This can be 3 different values depending on the
6664                  * version of git being used. If git-rev-parse does not
6665                  * understand --is-inside-work-tree it will simply echo
6666                  * the option else either "true" or "false" is printed.
6667                  * Default to true for the unknown case. */
6668                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6670         } else if (*name == '.') {
6671                 string_ncopy(opt_cdup, name, namelen);
6673         } else {
6674                 string_ncopy(opt_prefix, name, namelen);
6675         }
6677         return OK;
6680 static int
6681 load_repo_info(void)
6683         const char *head_argv[] = {
6684                 "git", "symbolic-ref", "HEAD", NULL
6685         };
6686         const char *rev_parse_argv[] = {
6687                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6688                         "--show-cdup", "--show-prefix", NULL
6689         };
6691         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6692                 chomp_string(opt_head);
6693                 if (!prefixcmp(opt_head, "refs/heads/")) {
6694                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6696                         memmove(opt_head, offset, strlen(offset) + 1);
6697                 }
6698         }
6700         return run_io_load(rev_parse_argv, "=", read_repo_info);
6704 /*
6705  * Main
6706  */
6708 static const char usage[] =
6709 "tig " TIG_VERSION " (" __DATE__ ")\n"
6710 "\n"
6711 "Usage: tig        [options] [revs] [--] [paths]\n"
6712 "   or: tig show   [options] [revs] [--] [paths]\n"
6713 "   or: tig blame  [rev] path\n"
6714 "   or: tig status\n"
6715 "   or: tig <      [git command output]\n"
6716 "\n"
6717 "Options:\n"
6718 "  -v, --version   Show version and exit\n"
6719 "  -h, --help      Show help message and exit";
6721 static void __NORETURN
6722 quit(int sig)
6724         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6725         if (cursed)
6726                 endwin();
6727         exit(0);
6730 static void __NORETURN
6731 die(const char *err, ...)
6733         va_list args;
6735         endwin();
6737         va_start(args, err);
6738         fputs("tig: ", stderr);
6739         vfprintf(stderr, err, args);
6740         fputs("\n", stderr);
6741         va_end(args);
6743         exit(1);
6746 static void
6747 warn(const char *msg, ...)
6749         va_list args;
6751         va_start(args, msg);
6752         fputs("tig warning: ", stderr);
6753         vfprintf(stderr, msg, args);
6754         fputs("\n", stderr);
6755         va_end(args);
6758 static enum request
6759 parse_options(int argc, const char *argv[])
6761         enum request request = REQ_VIEW_MAIN;
6762         const char *subcommand;
6763         bool seen_dashdash = FALSE;
6764         /* XXX: This is vulnerable to the user overriding options
6765          * required for the main view parser. */
6766         const char *custom_argv[SIZEOF_ARG] = {
6767                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6768                         "--topo-order", NULL
6769         };
6770         int i, j = 6;
6772         if (!isatty(STDIN_FILENO)) {
6773                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6774                 return REQ_VIEW_PAGER;
6775         }
6777         if (argc <= 1)
6778                 return REQ_NONE;
6780         subcommand = argv[1];
6781         if (!strcmp(subcommand, "status")) {
6782                 if (argc > 2)
6783                         warn("ignoring arguments after `%s'", subcommand);
6784                 return REQ_VIEW_STATUS;
6786         } else if (!strcmp(subcommand, "blame")) {
6787                 if (argc <= 2 || argc > 4)
6788                         die("invalid number of options to blame\n\n%s", usage);
6790                 i = 2;
6791                 if (argc == 4) {
6792                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6793                         i++;
6794                 }
6796                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6797                 return REQ_VIEW_BLAME;
6799         } else if (!strcmp(subcommand, "show")) {
6800                 request = REQ_VIEW_DIFF;
6802         } else {
6803                 subcommand = NULL;
6804         }
6806         if (subcommand) {
6807                 custom_argv[1] = subcommand;
6808                 j = 2;
6809         }
6811         for (i = 1 + !!subcommand; i < argc; i++) {
6812                 const char *opt = argv[i];
6814                 if (seen_dashdash || !strcmp(opt, "--")) {
6815                         seen_dashdash = TRUE;
6817                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6818                         printf("tig version %s\n", TIG_VERSION);
6819                         quit(0);
6821                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6822                         printf("%s\n", usage);
6823                         quit(0);
6824                 }
6826                 custom_argv[j++] = opt;
6827                 if (j >= ARRAY_SIZE(custom_argv))
6828                         die("command too long");
6829         }
6831         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6832                 die("Failed to format arguments"); 
6834         return request;
6837 int
6838 main(int argc, const char *argv[])
6840         enum request request = parse_options(argc, argv);
6841         struct view *view;
6842         size_t i;
6844         signal(SIGINT, quit);
6846         if (setlocale(LC_ALL, "")) {
6847                 char *codeset = nl_langinfo(CODESET);
6849                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6850         }
6852         if (load_repo_info() == ERR)
6853                 die("Failed to load repo info.");
6855         if (load_options() == ERR)
6856                 die("Failed to load user config.");
6858         if (load_git_config() == ERR)
6859                 die("Failed to load repo config.");
6861         /* Require a git repository unless when running in pager mode. */
6862         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6863                 die("Not a git repository");
6865         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6866                 opt_utf8 = FALSE;
6868         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6869                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6870                 if (opt_iconv == ICONV_NONE)
6871                         die("Failed to initialize character set conversion");
6872         }
6874         if (load_refs() == ERR)
6875                 die("Failed to load refs.");
6877         foreach_view (view, i)
6878                 argv_from_env(view->ops->argv, view->cmd_env);
6880         init_display();
6882         if (request != REQ_NONE)
6883                 open_view(NULL, request, OPEN_PREPARED);
6884         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6886         while (view_driver(display[current_view], request)) {
6887                 int key = get_input(0);
6889                 view = display[current_view];
6890                 request = get_keybinding(view->keymap, key);
6892                 /* Some low-level request handling. This keeps access to
6893                  * status_win restricted. */
6894                 switch (request) {
6895                 case REQ_PROMPT:
6896                 {
6897                         char *cmd = read_prompt(":");
6899                         if (cmd) {
6900                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6901                                 const char *argv[SIZEOF_ARG] = { "git" };
6902                                 int argc = 1;
6904                                 /* When running random commands, initially show the
6905                                  * command in the title. However, it maybe later be
6906                                  * overwritten if a commit line is selected. */
6907                                 string_ncopy(next->ref, cmd, strlen(cmd));
6909                                 if (!argv_from_string(argv, &argc, cmd)) {
6910                                         report("Too many arguments");
6911                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6912                                         report("Failed to format command");
6913                                 } else {
6914                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6915                                 }
6916                         }
6918                         request = REQ_NONE;
6919                         break;
6920                 }
6921                 case REQ_SEARCH:
6922                 case REQ_SEARCH_BACK:
6923                 {
6924                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6925                         char *search = read_prompt(prompt);
6927                         if (search)
6928                                 string_ncopy(opt_search, search, strlen(search));
6929                         else if (*opt_search)
6930                                 request = request == REQ_SEARCH ?
6931                                         REQ_FIND_NEXT :
6932                                         REQ_FIND_PREV;
6933                         else
6934                                 request = REQ_NONE;
6935                         break;
6936                 }
6937                 default:
6938                         break;
6939                 }
6940         }
6942         quit(0);
6944         return 0;