Code

tigrc(5): document the tree view colors
[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(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
931 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
932 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
933 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
934 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
935 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
936 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
937 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
938 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
939 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
940 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
941 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
942 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
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_mode(struct view *view, mode_t mode)
2041         static const char dir_mode[]    = "drwxr-xr-x";
2042         static const char link_mode[]   = "lrwxrwxrwx";
2043         static const char exe_mode[]    = "-rwxr-xr-x";
2044         static const char file_mode[]   = "-rw-r--r--";
2045         const char *str;
2047         if (S_ISDIR(mode))
2048                 str = dir_mode;
2049         else if (S_ISLNK(mode))
2050                 str = link_mode;
2051         else if (mode & S_IXUSR)
2052                 str = exe_mode;
2053         else
2054                 str = file_mode;
2056         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2059 static bool
2060 draw_view_line(struct view *view, unsigned int lineno)
2062         struct line *line;
2063         bool selected = (view->offset + lineno == view->lineno);
2065         assert(view_is_displayed(view));
2067         if (view->offset + lineno >= view->lines)
2068                 return FALSE;
2070         line = &view->line[view->offset + lineno];
2072         wmove(view->win, lineno, 0);
2073         if (line->cleareol)
2074                 wclrtoeol(view->win);
2075         view->col = 0;
2076         view->curline = line;
2077         view->curtype = LINE_NONE;
2078         line->selected = FALSE;
2079         line->dirty = line->cleareol = 0;
2081         if (selected) {
2082                 set_view_attr(view, LINE_CURSOR);
2083                 line->selected = TRUE;
2084                 view->ops->select(view, line);
2085         }
2087         return view->ops->draw(view, line, lineno);
2090 static void
2091 redraw_view_dirty(struct view *view)
2093         bool dirty = FALSE;
2094         int lineno;
2096         for (lineno = 0; lineno < view->height; lineno++) {
2097                 if (view->offset + lineno >= view->lines)
2098                         break;
2099                 if (!view->line[view->offset + lineno].dirty)
2100                         continue;
2101                 dirty = TRUE;
2102                 if (!draw_view_line(view, lineno))
2103                         break;
2104         }
2106         if (!dirty)
2107                 return;
2108         wnoutrefresh(view->win);
2111 static void
2112 redraw_view_from(struct view *view, int lineno)
2114         assert(0 <= lineno && lineno < view->height);
2116         if (lineno == 0)
2117                 view->can_hscroll = FALSE;
2119         for (; lineno < view->height; lineno++) {
2120                 if (!draw_view_line(view, lineno))
2121                         break;
2122         }
2124         wnoutrefresh(view->win);
2127 static void
2128 redraw_view(struct view *view)
2130         werase(view->win);
2131         redraw_view_from(view, 0);
2135 static void
2136 update_view_title(struct view *view)
2138         char buf[SIZEOF_STR];
2139         char state[SIZEOF_STR];
2140         size_t bufpos = 0, statelen = 0;
2142         assert(view_is_displayed(view));
2144         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2145                 unsigned int view_lines = view->offset + view->height;
2146                 unsigned int lines = view->lines
2147                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2148                                    : 0;
2150                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2151                                    view->ops->type,
2152                                    view->lineno + 1,
2153                                    view->lines,
2154                                    lines);
2156         }
2158         if (view->pipe) {
2159                 time_t secs = time(NULL) - view->start_time;
2161                 /* Three git seconds are a long time ... */
2162                 if (secs > 2)
2163                         string_format_from(state, &statelen, " loading %lds", secs);
2164         }
2166         string_format_from(buf, &bufpos, "[%s]", view->name);
2167         if (*view->ref && bufpos < view->width) {
2168                 size_t refsize = strlen(view->ref);
2169                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2171                 if (minsize < view->width)
2172                         refsize = view->width - minsize + 7;
2173                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2174         }
2176         if (statelen && bufpos < view->width) {
2177                 string_format_from(buf, &bufpos, "%s", state);
2178         }
2180         if (view == display[current_view])
2181                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2182         else
2183                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2185         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2186         wclrtoeol(view->title);
2187         wnoutrefresh(view->title);
2190 static void
2191 resize_display(void)
2193         int offset, i;
2194         struct view *base = display[0];
2195         struct view *view = display[1] ? display[1] : display[0];
2197         /* Setup window dimensions */
2199         getmaxyx(stdscr, base->height, base->width);
2201         /* Make room for the status window. */
2202         base->height -= 1;
2204         if (view != base) {
2205                 /* Horizontal split. */
2206                 view->width   = base->width;
2207                 view->height  = SCALE_SPLIT_VIEW(base->height);
2208                 base->height -= view->height;
2210                 /* Make room for the title bar. */
2211                 view->height -= 1;
2212         }
2214         /* Make room for the title bar. */
2215         base->height -= 1;
2217         offset = 0;
2219         foreach_displayed_view (view, i) {
2220                 if (!view->win) {
2221                         view->win = newwin(view->height, 0, offset, 0);
2222                         if (!view->win)
2223                                 die("Failed to create %s view", view->name);
2225                         scrollok(view->win, FALSE);
2227                         view->title = newwin(1, 0, offset + view->height, 0);
2228                         if (!view->title)
2229                                 die("Failed to create title window");
2231                 } else {
2232                         wresize(view->win, view->height, view->width);
2233                         mvwin(view->win,   offset, 0);
2234                         mvwin(view->title, offset + view->height, 0);
2235                 }
2237                 offset += view->height + 1;
2238         }
2241 static void
2242 redraw_display(bool clear)
2244         struct view *view;
2245         int i;
2247         foreach_displayed_view (view, i) {
2248                 if (clear)
2249                         wclear(view->win);
2250                 redraw_view(view);
2251                 update_view_title(view);
2252         }
2255 static void
2256 toggle_view_option(bool *option, const char *help)
2258         *option = !*option;
2259         redraw_display(FALSE);
2260         report("%sabling %s", *option ? "En" : "Dis", help);
2263 /*
2264  * Navigation
2265  */
2267 /* Scrolling backend */
2268 static void
2269 do_scroll_view(struct view *view, int lines)
2271         bool redraw_current_line = FALSE;
2273         /* The rendering expects the new offset. */
2274         view->offset += lines;
2276         assert(0 <= view->offset && view->offset < view->lines);
2277         assert(lines);
2279         /* Move current line into the view. */
2280         if (view->lineno < view->offset) {
2281                 view->lineno = view->offset;
2282                 redraw_current_line = TRUE;
2283         } else if (view->lineno >= view->offset + view->height) {
2284                 view->lineno = view->offset + view->height - 1;
2285                 redraw_current_line = TRUE;
2286         }
2288         assert(view->offset <= view->lineno && view->lineno < view->lines);
2290         /* Redraw the whole screen if scrolling is pointless. */
2291         if (view->height < ABS(lines)) {
2292                 redraw_view(view);
2294         } else {
2295                 int line = lines > 0 ? view->height - lines : 0;
2296                 int end = line + ABS(lines);
2298                 scrollok(view->win, TRUE);
2299                 wscrl(view->win, lines);
2300                 scrollok(view->win, FALSE);
2302                 while (line < end && draw_view_line(view, line))
2303                         line++;
2305                 if (redraw_current_line)
2306                         draw_view_line(view, view->lineno - view->offset);
2307                 wnoutrefresh(view->win);
2308         }
2310         view->has_scrolled = TRUE;
2311         report("");
2314 /* Scroll frontend */
2315 static void
2316 scroll_view(struct view *view, enum request request)
2318         int lines = 1;
2320         assert(view_is_displayed(view));
2322         switch (request) {
2323         case REQ_SCROLL_LEFT:
2324                 if (view->yoffset == 0) {
2325                         report("Cannot scroll beyond the first column");
2326                         return;
2327                 }
2328                 if (view->yoffset <= SCROLL_INTERVAL)
2329                         view->yoffset = 0;
2330                 else
2331                         view->yoffset -= SCROLL_INTERVAL;
2332                 redraw_view_from(view, 0);
2333                 report("");
2334                 return;
2335         case REQ_SCROLL_RIGHT:
2336                 if (!view->can_hscroll) {
2337                         report("Cannot scroll beyond the last column");
2338                         return;
2339                 }
2340                 view->yoffset += SCROLL_INTERVAL;
2341                 redraw_view(view);
2342                 report("");
2343                 return;
2344         case REQ_SCROLL_PAGE_DOWN:
2345                 lines = view->height;
2346         case REQ_SCROLL_LINE_DOWN:
2347                 if (view->offset + lines > view->lines)
2348                         lines = view->lines - view->offset;
2350                 if (lines == 0 || view->offset + view->height >= view->lines) {
2351                         report("Cannot scroll beyond the last line");
2352                         return;
2353                 }
2354                 break;
2356         case REQ_SCROLL_PAGE_UP:
2357                 lines = view->height;
2358         case REQ_SCROLL_LINE_UP:
2359                 if (lines > view->offset)
2360                         lines = view->offset;
2362                 if (lines == 0) {
2363                         report("Cannot scroll beyond the first line");
2364                         return;
2365                 }
2367                 lines = -lines;
2368                 break;
2370         default:
2371                 die("request %d not handled in switch", request);
2372         }
2374         do_scroll_view(view, lines);
2377 /* Cursor moving */
2378 static void
2379 move_view(struct view *view, enum request request)
2381         int scroll_steps = 0;
2382         int steps;
2384         switch (request) {
2385         case REQ_MOVE_FIRST_LINE:
2386                 steps = -view->lineno;
2387                 break;
2389         case REQ_MOVE_LAST_LINE:
2390                 steps = view->lines - view->lineno - 1;
2391                 break;
2393         case REQ_MOVE_PAGE_UP:
2394                 steps = view->height > view->lineno
2395                       ? -view->lineno : -view->height;
2396                 break;
2398         case REQ_MOVE_PAGE_DOWN:
2399                 steps = view->lineno + view->height >= view->lines
2400                       ? view->lines - view->lineno - 1 : view->height;
2401                 break;
2403         case REQ_MOVE_UP:
2404                 steps = -1;
2405                 break;
2407         case REQ_MOVE_DOWN:
2408                 steps = 1;
2409                 break;
2411         default:
2412                 die("request %d not handled in switch", request);
2413         }
2415         if (steps <= 0 && view->lineno == 0) {
2416                 report("Cannot move beyond the first line");
2417                 return;
2419         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2420                 report("Cannot move beyond the last line");
2421                 return;
2422         }
2424         /* Move the current line */
2425         view->lineno += steps;
2426         assert(0 <= view->lineno && view->lineno < view->lines);
2428         /* Check whether the view needs to be scrolled */
2429         if (view->lineno < view->offset ||
2430             view->lineno >= view->offset + view->height) {
2431                 scroll_steps = steps;
2432                 if (steps < 0 && -steps > view->offset) {
2433                         scroll_steps = -view->offset;
2435                 } else if (steps > 0) {
2436                         if (view->lineno == view->lines - 1 &&
2437                             view->lines > view->height) {
2438                                 scroll_steps = view->lines - view->offset - 1;
2439                                 if (scroll_steps >= view->height)
2440                                         scroll_steps -= view->height - 1;
2441                         }
2442                 }
2443         }
2445         if (!view_is_displayed(view)) {
2446                 view->offset += scroll_steps;
2447                 assert(0 <= view->offset && view->offset < view->lines);
2448                 view->ops->select(view, &view->line[view->lineno]);
2449                 return;
2450         }
2452         /* Repaint the old "current" line if we be scrolling */
2453         if (ABS(steps) < view->height)
2454                 draw_view_line(view, view->lineno - steps - view->offset);
2456         if (scroll_steps) {
2457                 do_scroll_view(view, scroll_steps);
2458                 return;
2459         }
2461         /* Draw the current line */
2462         draw_view_line(view, view->lineno - view->offset);
2464         wnoutrefresh(view->win);
2465         report("");
2469 /*
2470  * Searching
2471  */
2473 static void search_view(struct view *view, enum request request);
2475 static void
2476 select_view_line(struct view *view, unsigned long lineno)
2478         if (lineno - view->offset >= view->height) {
2479                 view->offset = lineno;
2480                 view->lineno = lineno;
2481                 if (view_is_displayed(view))
2482                         redraw_view(view);
2484         } else {
2485                 unsigned long old_lineno = view->lineno - view->offset;
2487                 view->lineno = lineno;
2488                 if (view_is_displayed(view)) {
2489                         draw_view_line(view, old_lineno);
2490                         draw_view_line(view, view->lineno - view->offset);
2491                         wnoutrefresh(view->win);
2492                 } else {
2493                         view->ops->select(view, &view->line[view->lineno]);
2494                 }
2495         }
2498 static void
2499 find_next(struct view *view, enum request request)
2501         unsigned long lineno = view->lineno;
2502         int direction;
2504         if (!*view->grep) {
2505                 if (!*opt_search)
2506                         report("No previous search");
2507                 else
2508                         search_view(view, request);
2509                 return;
2510         }
2512         switch (request) {
2513         case REQ_SEARCH:
2514         case REQ_FIND_NEXT:
2515                 direction = 1;
2516                 break;
2518         case REQ_SEARCH_BACK:
2519         case REQ_FIND_PREV:
2520                 direction = -1;
2521                 break;
2523         default:
2524                 return;
2525         }
2527         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2528                 lineno += direction;
2530         /* Note, lineno is unsigned long so will wrap around in which case it
2531          * will become bigger than view->lines. */
2532         for (; lineno < view->lines; lineno += direction) {
2533                 if (view->ops->grep(view, &view->line[lineno])) {
2534                         select_view_line(view, lineno);
2535                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2536                         return;
2537                 }
2538         }
2540         report("No match found for '%s'", view->grep);
2543 static void
2544 search_view(struct view *view, enum request request)
2546         int regex_err;
2548         if (view->regex) {
2549                 regfree(view->regex);
2550                 *view->grep = 0;
2551         } else {
2552                 view->regex = calloc(1, sizeof(*view->regex));
2553                 if (!view->regex)
2554                         return;
2555         }
2557         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2558         if (regex_err != 0) {
2559                 char buf[SIZEOF_STR] = "unknown error";
2561                 regerror(regex_err, view->regex, buf, sizeof(buf));
2562                 report("Search failed: %s", buf);
2563                 return;
2564         }
2566         string_copy(view->grep, opt_search);
2568         find_next(view, request);
2571 /*
2572  * Incremental updating
2573  */
2575 static void
2576 reset_view(struct view *view)
2578         int i;
2580         for (i = 0; i < view->lines; i++)
2581                 free(view->line[i].data);
2582         free(view->line);
2584         view->p_offset = view->offset;
2585         view->p_yoffset = view->yoffset;
2586         view->p_lineno = view->lineno;
2588         view->line = NULL;
2589         view->offset = 0;
2590         view->yoffset = 0;
2591         view->lines  = 0;
2592         view->lineno = 0;
2593         view->line_alloc = 0;
2594         view->vid[0] = 0;
2595         view->update_secs = 0;
2598 static void
2599 free_argv(const char *argv[])
2601         int argc;
2603         for (argc = 0; argv[argc]; argc++)
2604                 free((void *) argv[argc]);
2607 static bool
2608 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2610         char buf[SIZEOF_STR];
2611         int argc;
2612         bool noreplace = flags == FORMAT_NONE;
2614         free_argv(dst_argv);
2616         for (argc = 0; src_argv[argc]; argc++) {
2617                 const char *arg = src_argv[argc];
2618                 size_t bufpos = 0;
2620                 while (arg) {
2621                         char *next = strstr(arg, "%(");
2622                         int len = next - arg;
2623                         const char *value;
2625                         if (!next || noreplace) {
2626                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2627                                         noreplace = TRUE;
2628                                 len = strlen(arg);
2629                                 value = "";
2631                         } else if (!prefixcmp(next, "%(directory)")) {
2632                                 value = opt_path;
2634                         } else if (!prefixcmp(next, "%(file)")) {
2635                                 value = opt_file;
2637                         } else if (!prefixcmp(next, "%(ref)")) {
2638                                 value = *opt_ref ? opt_ref : "HEAD";
2640                         } else if (!prefixcmp(next, "%(head)")) {
2641                                 value = ref_head;
2643                         } else if (!prefixcmp(next, "%(commit)")) {
2644                                 value = ref_commit;
2646                         } else if (!prefixcmp(next, "%(blob)")) {
2647                                 value = ref_blob;
2649                         } else {
2650                                 report("Unknown replacement: `%s`", next);
2651                                 return FALSE;
2652                         }
2654                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2655                                 return FALSE;
2657                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2658                 }
2660                 dst_argv[argc] = strdup(buf);
2661                 if (!dst_argv[argc])
2662                         break;
2663         }
2665         dst_argv[argc] = NULL;
2667         return src_argv[argc] == NULL;
2670 static bool
2671 restore_view_position(struct view *view)
2673         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2674                 return FALSE;
2676         /* Changing the view position cancels the restoring. */
2677         /* FIXME: Changing back to the first line is not detected. */
2678         if (view->offset != 0 || view->lineno != 0) {
2679                 view->p_restore = FALSE;
2680                 return FALSE;
2681         }
2683         if (view->p_lineno >= view->lines) {
2684                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2685                 if (view->p_offset >= view->p_lineno) {
2686                         unsigned long half = view->height / 2;
2688                         if (view->p_lineno > half)
2689                                 view->p_offset = view->p_lineno - half;
2690                         else
2691                                 view->p_offset = 0;
2692                 }
2693         }
2695         if (view_is_displayed(view) &&
2696             view->offset != view->p_offset &&
2697             view->lineno != view->p_lineno)
2698                 werase(view->win);
2700         view->offset = view->p_offset;
2701         view->yoffset = view->p_yoffset;
2702         view->lineno = view->p_lineno;
2703         view->p_restore = FALSE;
2705         return TRUE;
2708 static void
2709 end_update(struct view *view, bool force)
2711         if (!view->pipe)
2712                 return;
2713         while (!view->ops->read(view, NULL))
2714                 if (!force)
2715                         return;
2716         set_nonblocking_input(FALSE);
2717         if (force)
2718                 kill_io(view->pipe);
2719         done_io(view->pipe);
2720         view->pipe = NULL;
2723 static void
2724 setup_update(struct view *view, const char *vid)
2726         set_nonblocking_input(TRUE);
2727         reset_view(view);
2728         string_copy_rev(view->vid, vid);
2729         view->pipe = &view->io;
2730         view->start_time = time(NULL);
2733 static bool
2734 prepare_update(struct view *view, const char *argv[], const char *dir,
2735                enum format_flags flags)
2737         if (view->pipe)
2738                 end_update(view, TRUE);
2739         return init_io_rd(&view->io, argv, dir, flags);
2742 static bool
2743 prepare_update_file(struct view *view, const char *name)
2745         if (view->pipe)
2746                 end_update(view, TRUE);
2747         return io_open(&view->io, name);
2750 static bool
2751 begin_update(struct view *view, bool refresh)
2753         if (view->pipe)
2754                 end_update(view, TRUE);
2756         if (refresh) {
2757                 if (!start_io(&view->io))
2758                         return FALSE;
2760         } else {
2761                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2762                         opt_path[0] = 0;
2764                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2765                         return FALSE;
2767                 /* Put the current ref_* value to the view title ref
2768                  * member. This is needed by the blob view. Most other
2769                  * views sets it automatically after loading because the
2770                  * first line is a commit line. */
2771                 string_copy_rev(view->ref, view->id);
2772         }
2774         setup_update(view, view->id);
2776         return TRUE;
2779 #define ITEM_CHUNK_SIZE 256
2780 static void *
2781 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2783         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2784         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2786         if (mem == NULL || num_chunks != num_chunks_new) {
2787                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2788                 mem = realloc(mem, *size * item_size);
2789         }
2791         return mem;
2794 static struct line *
2795 realloc_lines(struct view *view, size_t line_size)
2797         size_t alloc = view->line_alloc;
2798         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2799                                          sizeof(*view->line));
2801         if (!tmp)
2802                 return NULL;
2804         view->line = tmp;
2805         view->line_alloc = alloc;
2806         return view->line;
2809 static bool
2810 update_view(struct view *view)
2812         char out_buffer[BUFSIZ * 2];
2813         char *line;
2814         /* Clear the view and redraw everything since the tree sorting
2815          * might have rearranged things. */
2816         bool redraw = view->lines == 0;
2817         bool can_read = TRUE;
2819         if (!view->pipe)
2820                 return TRUE;
2822         if (!io_can_read(view->pipe)) {
2823                 if (view->lines == 0) {
2824                         time_t secs = time(NULL) - view->start_time;
2826                         if (secs > 1 && secs > view->update_secs) {
2827                                 if (view->update_secs == 0)
2828                                         redraw_view(view);
2829                                 update_view_title(view);
2830                                 view->update_secs = secs;
2831                         }
2832                 }
2833                 return TRUE;
2834         }
2836         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2837                 if (opt_iconv != ICONV_NONE) {
2838                         ICONV_CONST char *inbuf = line;
2839                         size_t inlen = strlen(line) + 1;
2841                         char *outbuf = out_buffer;
2842                         size_t outlen = sizeof(out_buffer);
2844                         size_t ret;
2846                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2847                         if (ret != (size_t) -1)
2848                                 line = out_buffer;
2849                 }
2851                 if (!view->ops->read(view, line)) {
2852                         report("Allocation failure");
2853                         end_update(view, TRUE);
2854                         return FALSE;
2855                 }
2856         }
2858         {
2859                 unsigned long lines = view->lines;
2860                 int digits;
2862                 for (digits = 0; lines; digits++)
2863                         lines /= 10;
2865                 /* Keep the displayed view in sync with line number scaling. */
2866                 if (digits != view->digits) {
2867                         view->digits = digits;
2868                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2869                                 redraw = TRUE;
2870                 }
2871         }
2873         if (io_error(view->pipe)) {
2874                 report("Failed to read: %s", io_strerror(view->pipe));
2875                 end_update(view, TRUE);
2877         } else if (io_eof(view->pipe)) {
2878                 report("");
2879                 end_update(view, FALSE);
2880         }
2882         if (restore_view_position(view))
2883                 redraw = TRUE;
2885         if (!view_is_displayed(view))
2886                 return TRUE;
2888         if (redraw)
2889                 redraw_view_from(view, 0);
2890         else
2891                 redraw_view_dirty(view);
2893         /* Update the title _after_ the redraw so that if the redraw picks up a
2894          * commit reference in view->ref it'll be available here. */
2895         update_view_title(view);
2896         return TRUE;
2899 static struct line *
2900 add_line_data(struct view *view, void *data, enum line_type type)
2902         struct line *line;
2904         if (!realloc_lines(view, view->lines + 1))
2905                 return NULL;
2907         line = &view->line[view->lines++];
2908         memset(line, 0, sizeof(*line));
2909         line->type = type;
2910         line->data = data;
2911         line->dirty = 1;
2913         return line;
2916 static struct line *
2917 add_line_text(struct view *view, const char *text, enum line_type type)
2919         char *data = text ? strdup(text) : NULL;
2921         return data ? add_line_data(view, data, type) : NULL;
2924 static struct line *
2925 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2927         char buf[SIZEOF_STR];
2928         va_list args;
2930         va_start(args, fmt);
2931         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2932                 buf[0] = 0;
2933         va_end(args);
2935         return buf[0] ? add_line_text(view, buf, type) : NULL;
2938 /*
2939  * View opening
2940  */
2942 enum open_flags {
2943         OPEN_DEFAULT = 0,       /* Use default view switching. */
2944         OPEN_SPLIT = 1,         /* Split current view. */
2945         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2946         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2947         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2948         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2949         OPEN_PREPARED = 32,     /* Open already prepared command. */
2950 };
2952 static void
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2955         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2956         bool split = !!(flags & OPEN_SPLIT);
2957         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2958         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2959         struct view *view = VIEW(request);
2960         int nviews = displayed_views();
2961         struct view *base_view = display[0];
2963         if (view == prev && nviews == 1 && !reload) {
2964                 report("Already in %s view", view->name);
2965                 return;
2966         }
2968         if (view->git_dir && !opt_git_dir[0]) {
2969                 report("The %s view is disabled in pager view", view->name);
2970                 return;
2971         }
2973         if (split) {
2974                 display[1] = view;
2975                 if (!backgrounded)
2976                         current_view = 1;
2977         } else if (!nomaximize) {
2978                 /* Maximize the current view. */
2979                 memset(display, 0, sizeof(display));
2980                 current_view = 0;
2981                 display[current_view] = view;
2982         }
2984         /* Resize the view when switching between split- and full-screen,
2985          * or when switching between two different full-screen views. */
2986         if (nviews != displayed_views() ||
2987             (nviews == 1 && base_view != display[0]))
2988                 resize_display();
2990         if (view->ops->open) {
2991                 if (view->pipe)
2992                         end_update(view, TRUE);
2993                 if (!view->ops->open(view)) {
2994                         report("Failed to load %s view", view->name);
2995                         return;
2996                 }
2997                 restore_view_position(view);
2999         } else if ((reload || strcmp(view->vid, view->id)) &&
3000                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3001                 report("Failed to load %s view", view->name);
3002                 return;
3003         }
3005         if (split && prev->lineno - prev->offset >= prev->height) {
3006                 /* Take the title line into account. */
3007                 int lines = prev->lineno - prev->offset - prev->height + 1;
3009                 /* Scroll the view that was split if the current line is
3010                  * outside the new limited view. */
3011                 do_scroll_view(prev, lines);
3012         }
3014         if (prev && view != prev) {
3015                 if (split && !backgrounded) {
3016                         /* "Blur" the previous view. */
3017                         update_view_title(prev);
3018                 }
3020                 view->parent = prev;
3021         }
3023         if (view->pipe && view->lines == 0) {
3024                 /* Clear the old view and let the incremental updating refill
3025                  * the screen. */
3026                 werase(view->win);
3027                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3028                 report("");
3029         } else if (view_is_displayed(view)) {
3030                 redraw_view(view);
3031                 report("");
3032         }
3034         /* If the view is backgrounded the above calls to report()
3035          * won't redraw the view title. */
3036         if (backgrounded)
3037                 update_view_title(view);
3040 static void
3041 open_external_viewer(const char *argv[], const char *dir)
3043         def_prog_mode();           /* save current tty modes */
3044         endwin();                  /* restore original tty modes */
3045         run_io_fg(argv, dir);
3046         fprintf(stderr, "Press Enter to continue");
3047         getc(opt_tty);
3048         reset_prog_mode();
3049         redraw_display(TRUE);
3052 static void
3053 open_mergetool(const char *file)
3055         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3057         open_external_viewer(mergetool_argv, opt_cdup);
3060 static void
3061 open_editor(bool from_root, const char *file)
3063         const char *editor_argv[] = { "vi", file, NULL };
3064         const char *editor;
3066         editor = getenv("GIT_EDITOR");
3067         if (!editor && *opt_editor)
3068                 editor = opt_editor;
3069         if (!editor)
3070                 editor = getenv("VISUAL");
3071         if (!editor)
3072                 editor = getenv("EDITOR");
3073         if (!editor)
3074                 editor = "vi";
3076         editor_argv[0] = editor;
3077         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3080 static void
3081 open_run_request(enum request request)
3083         struct run_request *req = get_run_request(request);
3084         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3086         if (!req) {
3087                 report("Unknown run request");
3088                 return;
3089         }
3091         if (format_argv(argv, req->argv, FORMAT_ALL))
3092                 open_external_viewer(argv, NULL);
3093         free_argv(argv);
3096 /*
3097  * User request switch noodle
3098  */
3100 static int
3101 view_driver(struct view *view, enum request request)
3103         int i;
3105         if (request == REQ_NONE) {
3106                 doupdate();
3107                 return TRUE;
3108         }
3110         if (request > REQ_NONE) {
3111                 open_run_request(request);
3112                 /* FIXME: When all views can refresh always do this. */
3113                 if (view == VIEW(REQ_VIEW_STATUS) ||
3114                     view == VIEW(REQ_VIEW_MAIN) ||
3115                     view == VIEW(REQ_VIEW_LOG) ||
3116                     view == VIEW(REQ_VIEW_STAGE))
3117                         request = REQ_REFRESH;
3118                 else
3119                         return TRUE;
3120         }
3122         if (view && view->lines) {
3123                 request = view->ops->request(view, request, &view->line[view->lineno]);
3124                 if (request == REQ_NONE)
3125                         return TRUE;
3126         }
3128         switch (request) {
3129         case REQ_MOVE_UP:
3130         case REQ_MOVE_DOWN:
3131         case REQ_MOVE_PAGE_UP:
3132         case REQ_MOVE_PAGE_DOWN:
3133         case REQ_MOVE_FIRST_LINE:
3134         case REQ_MOVE_LAST_LINE:
3135                 move_view(view, request);
3136                 break;
3138         case REQ_SCROLL_LEFT:
3139         case REQ_SCROLL_RIGHT:
3140         case REQ_SCROLL_LINE_DOWN:
3141         case REQ_SCROLL_LINE_UP:
3142         case REQ_SCROLL_PAGE_DOWN:
3143         case REQ_SCROLL_PAGE_UP:
3144                 scroll_view(view, request);
3145                 break;
3147         case REQ_VIEW_BLAME:
3148                 if (!opt_file[0]) {
3149                         report("No file chosen, press %s to open tree view",
3150                                get_key(REQ_VIEW_TREE));
3151                         break;
3152                 }
3153                 open_view(view, request, OPEN_DEFAULT);
3154                 break;
3156         case REQ_VIEW_BLOB:
3157                 if (!ref_blob[0]) {
3158                         report("No file chosen, press %s to open tree view",
3159                                get_key(REQ_VIEW_TREE));
3160                         break;
3161                 }
3162                 open_view(view, request, OPEN_DEFAULT);
3163                 break;
3165         case REQ_VIEW_PAGER:
3166                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3167                         report("No pager content, press %s to run command from prompt",
3168                                get_key(REQ_PROMPT));
3169                         break;
3170                 }
3171                 open_view(view, request, OPEN_DEFAULT);
3172                 break;
3174         case REQ_VIEW_STAGE:
3175                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3176                         report("No stage content, press %s to open the status view and choose file",
3177                                get_key(REQ_VIEW_STATUS));
3178                         break;
3179                 }
3180                 open_view(view, request, OPEN_DEFAULT);
3181                 break;
3183         case REQ_VIEW_STATUS:
3184                 if (opt_is_inside_work_tree == FALSE) {
3185                         report("The status view requires a working tree");
3186                         break;
3187                 }
3188                 open_view(view, request, OPEN_DEFAULT);
3189                 break;
3191         case REQ_VIEW_MAIN:
3192         case REQ_VIEW_DIFF:
3193         case REQ_VIEW_LOG:
3194         case REQ_VIEW_TREE:
3195         case REQ_VIEW_HELP:
3196                 open_view(view, request, OPEN_DEFAULT);
3197                 break;
3199         case REQ_NEXT:
3200         case REQ_PREVIOUS:
3201                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3203                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3204                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3205                    (view == VIEW(REQ_VIEW_DIFF) &&
3206                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3207                    (view == VIEW(REQ_VIEW_STAGE) &&
3208                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3209                    (view == VIEW(REQ_VIEW_BLOB) &&
3210                      view->parent == VIEW(REQ_VIEW_TREE))) {
3211                         int line;
3213                         view = view->parent;
3214                         line = view->lineno;
3215                         move_view(view, request);
3216                         if (view_is_displayed(view))
3217                                 update_view_title(view);
3218                         if (line != view->lineno)
3219                                 view->ops->request(view, REQ_ENTER,
3220                                                    &view->line[view->lineno]);
3222                 } else {
3223                         move_view(view, request);
3224                 }
3225                 break;
3227         case REQ_VIEW_NEXT:
3228         {
3229                 int nviews = displayed_views();
3230                 int next_view = (current_view + 1) % nviews;
3232                 if (next_view == current_view) {
3233                         report("Only one view is displayed");
3234                         break;
3235                 }
3237                 current_view = next_view;
3238                 /* Blur out the title of the previous view. */
3239                 update_view_title(view);
3240                 report("");
3241                 break;
3242         }
3243         case REQ_REFRESH:
3244                 report("Refreshing is not yet supported for the %s view", view->name);
3245                 break;
3247         case REQ_MAXIMIZE:
3248                 if (displayed_views() == 2)
3249                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3250                 break;
3252         case REQ_TOGGLE_LINENO:
3253                 toggle_view_option(&opt_line_number, "line numbers");
3254                 break;
3256         case REQ_TOGGLE_DATE:
3257                 toggle_view_option(&opt_date, "date display");
3258                 break;
3260         case REQ_TOGGLE_AUTHOR:
3261                 toggle_view_option(&opt_author, "author display");
3262                 break;
3264         case REQ_TOGGLE_REV_GRAPH:
3265                 toggle_view_option(&opt_rev_graph, "revision graph display");
3266                 break;
3268         case REQ_TOGGLE_REFS:
3269                 toggle_view_option(&opt_show_refs, "reference display");
3270                 break;
3272         case REQ_SEARCH:
3273         case REQ_SEARCH_BACK:
3274                 search_view(view, request);
3275                 break;
3277         case REQ_FIND_NEXT:
3278         case REQ_FIND_PREV:
3279                 find_next(view, request);
3280                 break;
3282         case REQ_STOP_LOADING:
3283                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3284                         view = &views[i];
3285                         if (view->pipe)
3286                                 report("Stopped loading the %s view", view->name),
3287                         end_update(view, TRUE);
3288                 }
3289                 break;
3291         case REQ_SHOW_VERSION:
3292                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3293                 return TRUE;
3295         case REQ_SCREEN_REDRAW:
3296                 redraw_display(TRUE);
3297                 break;
3299         case REQ_EDIT:
3300                 report("Nothing to edit");
3301                 break;
3303         case REQ_ENTER:
3304                 report("Nothing to enter");
3305                 break;
3307         case REQ_VIEW_CLOSE:
3308                 /* XXX: Mark closed views by letting view->parent point to the
3309                  * view itself. Parents to closed view should never be
3310                  * followed. */
3311                 if (view->parent &&
3312                     view->parent->parent != view->parent) {
3313                         memset(display, 0, sizeof(display));
3314                         current_view = 0;
3315                         display[current_view] = view->parent;
3316                         view->parent = view;
3317                         resize_display();
3318                         redraw_display(FALSE);
3319                         report("");
3320                         break;
3321                 }
3322                 /* Fall-through */
3323         case REQ_QUIT:
3324                 return FALSE;
3326         default:
3327                 report("Unknown key, press 'h' for help");
3328                 return TRUE;
3329         }
3331         return TRUE;
3335 /*
3336  * View backend utilities
3337  */
3339 /* Parse author lines where the name may be empty:
3340  *      author  <email@address.tld> 1138474660 +0100
3341  */
3342 static void
3343 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3345         char *nameend = strchr(ident, '<');
3346         char *emailend = strchr(ident, '>');
3348         if (nameend && emailend)
3349                 *nameend = *emailend = 0;
3350         ident = chomp_string(ident);
3351         if (!*ident) {
3352                 if (nameend)
3353                         ident = chomp_string(nameend + 1);
3354                 if (!*ident)
3355                         ident = "Unknown";
3356         }
3358         string_ncopy_do(author, authorsize, ident, strlen(ident));
3360         /* Parse epoch and timezone */
3361         if (emailend && emailend[1] == ' ') {
3362                 char *secs = emailend + 2;
3363                 char *zone = strchr(secs, ' ');
3364                 time_t time = (time_t) atol(secs);
3366                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3367                         long tz;
3369                         zone++;
3370                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3371                         tz += ('0' - zone[2]) * 60 * 60;
3372                         tz += ('0' - zone[3]) * 60;
3373                         tz += ('0' - zone[4]) * 60;
3375                         if (zone[0] == '-')
3376                                 tz = -tz;
3378                         time -= tz;
3379                 }
3381                 gmtime_r(&time, tm);
3382         }
3385 static enum input_status
3386 select_commit_parent_handler(void *data, char *buf, int c)
3388         size_t parents = *(size_t *) data;
3389         int parent = 0;
3391         if (!isdigit(c))
3392                 return INPUT_SKIP;
3394         if (*buf)
3395                 parent = atoi(buf) * 10;
3396         parent += c - '0';
3398         if (parent > parents)
3399                 return INPUT_SKIP;
3400         return INPUT_OK;
3403 static bool
3404 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3406         char buf[SIZEOF_STR * 4];
3407         const char *revlist_argv[] = {
3408                 "git", "rev-list", "-1", "--parents", id, NULL
3409         };
3410         int parents;
3412         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3413             !*chomp_string(buf) ||
3414             (parents = (strlen(buf) / 40) - 1) < 0) {
3415                 report("Failed to get parent information");
3416                 return FALSE;
3418         } else if (parents == 0) {
3419                 report("The selected commit has no parents");
3420                 return FALSE;
3421         }
3423         if (parents > 1) {
3424                 char prompt[SIZEOF_STR];
3425                 char *result;
3427                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3428                         return FALSE;
3429                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3430                 if (!result)
3431                         return FALSE;
3432                 parents = atoi(result);
3433         }
3435         string_copy_rev(rev, &buf[41 * parents]);
3436         return TRUE;
3439 /*
3440  * Pager backend
3441  */
3443 static bool
3444 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3446         char text[SIZEOF_STR];
3448         if (opt_line_number && draw_lineno(view, lineno))
3449                 return TRUE;
3451         string_expand(text, sizeof(text), line->data, opt_tab_size);
3452         draw_text(view, line->type, text, TRUE);
3453         return TRUE;
3456 static bool
3457 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3459         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3460         char refbuf[SIZEOF_STR];
3461         char *ref = NULL;
3463         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3464                 ref = chomp_string(refbuf);
3466         if (!ref || !*ref)
3467                 return TRUE;
3469         /* This is the only fatal call, since it can "corrupt" the buffer. */
3470         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3471                 return FALSE;
3473         return TRUE;
3476 static void
3477 add_pager_refs(struct view *view, struct line *line)
3479         char buf[SIZEOF_STR];
3480         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3481         struct ref **refs;
3482         size_t bufpos = 0, refpos = 0;
3483         const char *sep = "Refs: ";
3484         bool is_tag = FALSE;
3486         assert(line->type == LINE_COMMIT);
3488         refs = get_refs(commit_id);
3489         if (!refs) {
3490                 if (view == VIEW(REQ_VIEW_DIFF))
3491                         goto try_add_describe_ref;
3492                 return;
3493         }
3495         do {
3496                 struct ref *ref = refs[refpos];
3497                 const char *fmt = ref->tag    ? "%s[%s]" :
3498                                   ref->remote ? "%s<%s>" : "%s%s";
3500                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3501                         return;
3502                 sep = ", ";
3503                 if (ref->tag)
3504                         is_tag = TRUE;
3505         } while (refs[refpos++]->next);
3507         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3508 try_add_describe_ref:
3509                 /* Add <tag>-g<commit_id> "fake" reference. */
3510                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3511                         return;
3512         }
3514         if (bufpos == 0)
3515                 return;
3517         add_line_text(view, buf, LINE_PP_REFS);
3520 static bool
3521 pager_read(struct view *view, char *data)
3523         struct line *line;
3525         if (!data)
3526                 return TRUE;
3528         line = add_line_text(view, data, get_line_type(data));
3529         if (!line)
3530                 return FALSE;
3532         if (line->type == LINE_COMMIT &&
3533             (view == VIEW(REQ_VIEW_DIFF) ||
3534              view == VIEW(REQ_VIEW_LOG)))
3535                 add_pager_refs(view, line);
3537         return TRUE;
3540 static enum request
3541 pager_request(struct view *view, enum request request, struct line *line)
3543         int split = 0;
3545         if (request != REQ_ENTER)
3546                 return request;
3548         if (line->type == LINE_COMMIT &&
3549            (view == VIEW(REQ_VIEW_LOG) ||
3550             view == VIEW(REQ_VIEW_PAGER))) {
3551                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3552                 split = 1;
3553         }
3555         /* Always scroll the view even if it was split. That way
3556          * you can use Enter to scroll through the log view and
3557          * split open each commit diff. */
3558         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3560         /* FIXME: A minor workaround. Scrolling the view will call report("")
3561          * but if we are scrolling a non-current view this won't properly
3562          * update the view title. */
3563         if (split)
3564                 update_view_title(view);
3566         return REQ_NONE;
3569 static bool
3570 pager_grep(struct view *view, struct line *line)
3572         regmatch_t pmatch;
3573         char *text = line->data;
3575         if (!*text)
3576                 return FALSE;
3578         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3579                 return FALSE;
3581         return TRUE;
3584 static void
3585 pager_select(struct view *view, struct line *line)
3587         if (line->type == LINE_COMMIT) {
3588                 char *text = (char *)line->data + STRING_SIZE("commit ");
3590                 if (view != VIEW(REQ_VIEW_PAGER))
3591                         string_copy_rev(view->ref, text);
3592                 string_copy_rev(ref_commit, text);
3593         }
3596 static struct view_ops pager_ops = {
3597         "line",
3598         NULL,
3599         NULL,
3600         pager_read,
3601         pager_draw,
3602         pager_request,
3603         pager_grep,
3604         pager_select,
3605 };
3607 static const char *log_argv[SIZEOF_ARG] = {
3608         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3609 };
3611 static enum request
3612 log_request(struct view *view, enum request request, struct line *line)
3614         switch (request) {
3615         case REQ_REFRESH:
3616                 load_refs();
3617                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3618                 return REQ_NONE;
3619         default:
3620                 return pager_request(view, request, line);
3621         }
3624 static struct view_ops log_ops = {
3625         "line",
3626         log_argv,
3627         NULL,
3628         pager_read,
3629         pager_draw,
3630         log_request,
3631         pager_grep,
3632         pager_select,
3633 };
3635 static const char *diff_argv[SIZEOF_ARG] = {
3636         "git", "show", "--pretty=fuller", "--no-color", "--root",
3637                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3638 };
3640 static struct view_ops diff_ops = {
3641         "line",
3642         diff_argv,
3643         NULL,
3644         pager_read,
3645         pager_draw,
3646         pager_request,
3647         pager_grep,
3648         pager_select,
3649 };
3651 /*
3652  * Help backend
3653  */
3655 static bool
3656 help_open(struct view *view)
3658         char buf[SIZEOF_STR];
3659         size_t bufpos;
3660         int i;
3662         if (view->lines > 0)
3663                 return TRUE;
3665         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3667         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3668                 const char *key;
3670                 if (req_info[i].request == REQ_NONE)
3671                         continue;
3673                 if (!req_info[i].request) {
3674                         add_line_text(view, "", LINE_DEFAULT);
3675                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3676                         continue;
3677                 }
3679                 key = get_key(req_info[i].request);
3680                 if (!*key)
3681                         key = "(no key defined)";
3683                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3684                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3685                         if (buf[bufpos] == '_')
3686                                 buf[bufpos] = '-';
3687                 }
3689                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3690                                 key, buf, req_info[i].help);
3691         }
3693         if (run_requests) {
3694                 add_line_text(view, "", LINE_DEFAULT);
3695                 add_line_text(view, "External commands:", LINE_DEFAULT);
3696         }
3698         for (i = 0; i < run_requests; i++) {
3699                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3700                 const char *key;
3701                 int argc;
3703                 if (!req)
3704                         continue;
3706                 key = get_key_name(req->key);
3707                 if (!*key)
3708                         key = "(no key defined)";
3710                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3711                         if (!string_format_from(buf, &bufpos, "%s%s",
3712                                                 argc ? " " : "", req->argv[argc]))
3713                                 return REQ_NONE;
3715                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3716                                 keymap_table[req->keymap].name, key, buf);
3717         }
3719         return TRUE;
3722 static struct view_ops help_ops = {
3723         "line",
3724         NULL,
3725         help_open,
3726         NULL,
3727         pager_draw,
3728         pager_request,
3729         pager_grep,
3730         pager_select,
3731 };
3734 /*
3735  * Tree backend
3736  */
3738 struct tree_stack_entry {
3739         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3740         unsigned long lineno;           /* Line number to restore */
3741         char *name;                     /* Position of name in opt_path */
3742 };
3744 /* The top of the path stack. */
3745 static struct tree_stack_entry *tree_stack = NULL;
3746 unsigned long tree_lineno = 0;
3748 static void
3749 pop_tree_stack_entry(void)
3751         struct tree_stack_entry *entry = tree_stack;
3753         tree_lineno = entry->lineno;
3754         entry->name[0] = 0;
3755         tree_stack = entry->prev;
3756         free(entry);
3759 static void
3760 push_tree_stack_entry(const char *name, unsigned long lineno)
3762         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3763         size_t pathlen = strlen(opt_path);
3765         if (!entry)
3766                 return;
3768         entry->prev = tree_stack;
3769         entry->name = opt_path + pathlen;
3770         tree_stack = entry;
3772         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3773                 pop_tree_stack_entry();
3774                 return;
3775         }
3777         /* Move the current line to the first tree entry. */
3778         tree_lineno = 1;
3779         entry->lineno = lineno;
3782 /* Parse output from git-ls-tree(1):
3783  *
3784  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3785  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3786  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3787  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3788  */
3790 #define SIZEOF_TREE_ATTR \
3791         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3793 #define SIZEOF_TREE_MODE \
3794         STRING_SIZE("100644 ")
3796 #define TREE_ID_OFFSET \
3797         STRING_SIZE("100644 blob ")
3799 struct tree_entry {
3800         char id[SIZEOF_REV];
3801         mode_t mode;
3802         struct tm time;                 /* Date from the author ident. */
3803         char author[75];                /* Author of the commit. */
3804         char name[1];
3805 };
3807 static const char *
3808 tree_path(struct line *line)
3810         return ((struct tree_entry *) line->data)->name;
3814 static int
3815 tree_compare_entry(struct line *line1, struct line *line2)
3817         if (line1->type != line2->type)
3818                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3819         return strcmp(tree_path(line1), tree_path(line2));
3822 static struct line *
3823 tree_entry(struct view *view, enum line_type type, const char *path,
3824            const char *mode, const char *id)
3826         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3827         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3829         if (!entry || !line) {
3830                 free(entry);
3831                 return NULL;
3832         }
3834         strncpy(entry->name, path, strlen(path));
3835         if (mode)
3836                 entry->mode = strtoul(mode, NULL, 8);
3837         if (id)
3838                 string_copy_rev(entry->id, id);
3840         return line;
3843 static bool
3844 tree_read_date(struct view *view, char *text, bool *read_date)
3846         static char author_name[SIZEOF_STR];
3847         static struct tm author_time;
3849         if (!text && *read_date) {
3850                 *read_date = FALSE;
3851                 return TRUE;
3853         } else if (!text) {
3854                 char *path = *opt_path ? opt_path : ".";
3855                 /* Find next entry to process */
3856                 const char *log_file[] = {
3857                         "git", "log", "--no-color", "--pretty=raw",
3858                                 "--cc", "--raw", view->id, "--", path, NULL
3859                 };
3860                 struct io io = {};
3862                 if (!view->lines) {
3863                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3864                         report("Tree is empty");
3865                         return TRUE;
3866                 }
3868                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3869                         report("Failed to load tree data");
3870                         return TRUE;
3871                 }
3873                 done_io(view->pipe);
3874                 view->io = io;
3875                 *read_date = TRUE;
3876                 return FALSE;
3878         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3879                 parse_author_line(text + STRING_SIZE("author "),
3880                                   author_name, sizeof(author_name), &author_time);
3882         } else if (*text == ':') {
3883                 char *pos;
3884                 size_t annotated = 1;
3885                 size_t i;
3887                 pos = strchr(text, '\t');
3888                 if (!pos)
3889                         return TRUE;
3890                 text = pos + 1;
3891                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3892                         text += strlen(opt_prefix);
3893                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3894                         text += strlen(opt_path);
3895                 pos = strchr(text, '/');
3896                 if (pos)
3897                         *pos = 0;
3899                 for (i = 1; i < view->lines; i++) {
3900                         struct line *line = &view->line[i];
3901                         struct tree_entry *entry = line->data;
3903                         annotated += !!*entry->author;
3904                         if (*entry->author || strcmp(entry->name, text))
3905                                 continue;
3907                         string_copy(entry->author, author_name);
3908                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3909                         line->dirty = 1;
3910                         break;
3911                 }
3913                 if (annotated == view->lines)
3914                         kill_io(view->pipe);
3915         }
3916         return TRUE;
3919 static bool
3920 tree_read(struct view *view, char *text)
3922         static bool read_date = FALSE;
3923         struct tree_entry *data;
3924         struct line *entry, *line;
3925         enum line_type type;
3926         size_t textlen = text ? strlen(text) : 0;
3927         char *path = text + SIZEOF_TREE_ATTR;
3929         if (read_date || !text)
3930                 return tree_read_date(view, text, &read_date);
3932         if (textlen <= SIZEOF_TREE_ATTR)
3933                 return FALSE;
3934         if (view->lines == 0 &&
3935             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3936                 return FALSE;
3938         /* Strip the path part ... */
3939         if (*opt_path) {
3940                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3941                 size_t striplen = strlen(opt_path);
3943                 if (pathlen > striplen)
3944                         memmove(path, path + striplen,
3945                                 pathlen - striplen + 1);
3947                 /* Insert "link" to parent directory. */
3948                 if (view->lines == 1 &&
3949                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3950                         return FALSE;
3951         }
3953         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3954         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3955         if (!entry)
3956                 return FALSE;
3957         data = entry->data;
3959         /* Skip "Directory ..." and ".." line. */
3960         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3961                 if (tree_compare_entry(line, entry) <= 0)
3962                         continue;
3964                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3966                 line->data = data;
3967                 line->type = type;
3968                 for (; line <= entry; line++)
3969                         line->dirty = line->cleareol = 1;
3970                 return TRUE;
3971         }
3973         if (tree_lineno > view->lineno) {
3974                 view->lineno = tree_lineno;
3975                 tree_lineno = 0;
3976         }
3978         return TRUE;
3981 static bool
3982 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3984         struct tree_entry *entry = line->data;
3986         if (line->type == LINE_TREE_HEAD) {
3987                 if (draw_text(view, line->type, "Directory path /", TRUE))
3988                         return TRUE;
3989         } else {
3990                 if (draw_mode(view, entry->mode))
3991                         return TRUE;
3993                 if (opt_author && draw_author(view, entry->author))
3994                         return TRUE;
3996                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3997                         return TRUE;
3998         }
3999         if (draw_text(view, line->type, entry->name, TRUE))
4000                 return TRUE;
4001         return TRUE;
4004 static void
4005 open_blob_editor()
4007         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4008         int fd = mkstemp(file);
4010         if (fd == -1)
4011                 report("Failed to create temporary file");
4012         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4013                 report("Failed to save blob data to file");
4014         else
4015                 open_editor(FALSE, file);
4016         if (fd != -1)
4017                 unlink(file);
4020 static enum request
4021 tree_request(struct view *view, enum request request, struct line *line)
4023         enum open_flags flags;
4025         switch (request) {
4026         case REQ_VIEW_BLAME:
4027                 if (line->type != LINE_TREE_FILE) {
4028                         report("Blame only supported for files");
4029                         return REQ_NONE;
4030                 }
4032                 string_copy(opt_ref, view->vid);
4033                 return request;
4035         case REQ_EDIT:
4036                 if (line->type != LINE_TREE_FILE) {
4037                         report("Edit only supported for files");
4038                 } else if (!is_head_commit(view->vid)) {
4039                         open_blob_editor();
4040                 } else {
4041                         open_editor(TRUE, opt_file);
4042                 }
4043                 return REQ_NONE;
4045         case REQ_PARENT:
4046                 if (!*opt_path) {
4047                         /* quit view if at top of tree */
4048                         return REQ_VIEW_CLOSE;
4049                 }
4050                 /* fake 'cd  ..' */
4051                 line = &view->line[1];
4052                 break;
4054         case REQ_ENTER:
4055                 break;
4057         default:
4058                 return request;
4059         }
4061         /* Cleanup the stack if the tree view is at a different tree. */
4062         while (!*opt_path && tree_stack)
4063                 pop_tree_stack_entry();
4065         switch (line->type) {
4066         case LINE_TREE_DIR:
4067                 /* Depending on whether it is a subdir or parent (updir?) link
4068                  * mangle the path buffer. */
4069                 if (line == &view->line[1] && *opt_path) {
4070                         pop_tree_stack_entry();
4072                 } else {
4073                         const char *basename = tree_path(line);
4075                         push_tree_stack_entry(basename, view->lineno);
4076                 }
4078                 /* Trees and subtrees share the same ID, so they are not not
4079                  * unique like blobs. */
4080                 flags = OPEN_RELOAD;
4081                 request = REQ_VIEW_TREE;
4082                 break;
4084         case LINE_TREE_FILE:
4085                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4086                 request = REQ_VIEW_BLOB;
4087                 break;
4089         default:
4090                 return REQ_NONE;
4091         }
4093         open_view(view, request, flags);
4094         if (request == REQ_VIEW_TREE)
4095                 view->lineno = tree_lineno;
4097         return REQ_NONE;
4100 static void
4101 tree_select(struct view *view, struct line *line)
4103         struct tree_entry *entry = line->data;
4105         if (line->type == LINE_TREE_FILE) {
4106                 string_copy_rev(ref_blob, entry->id);
4107                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4109         } else if (line->type != LINE_TREE_DIR) {
4110                 return;
4111         }
4113         string_copy_rev(view->ref, entry->id);
4116 static const char *tree_argv[SIZEOF_ARG] = {
4117         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4118 };
4120 static struct view_ops tree_ops = {
4121         "file",
4122         tree_argv,
4123         NULL,
4124         tree_read,
4125         tree_draw,
4126         tree_request,
4127         pager_grep,
4128         tree_select,
4129 };
4131 static bool
4132 blob_read(struct view *view, char *line)
4134         if (!line)
4135                 return TRUE;
4136         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4139 static enum request
4140 blob_request(struct view *view, enum request request, struct line *line)
4142         switch (request) {
4143         case REQ_EDIT:
4144                 open_blob_editor();
4145                 return REQ_NONE;
4146         default:
4147                 return pager_request(view, request, line);
4148         }
4151 static const char *blob_argv[SIZEOF_ARG] = {
4152         "git", "cat-file", "blob", "%(blob)", NULL
4153 };
4155 static struct view_ops blob_ops = {
4156         "line",
4157         blob_argv,
4158         NULL,
4159         blob_read,
4160         pager_draw,
4161         blob_request,
4162         pager_grep,
4163         pager_select,
4164 };
4166 /*
4167  * Blame backend
4168  *
4169  * Loading the blame view is a two phase job:
4170  *
4171  *  1. File content is read either using opt_file from the
4172  *     filesystem or using git-cat-file.
4173  *  2. Then blame information is incrementally added by
4174  *     reading output from git-blame.
4175  */
4177 static const char *blame_head_argv[] = {
4178         "git", "blame", "--incremental", "--", "%(file)", NULL
4179 };
4181 static const char *blame_ref_argv[] = {
4182         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4183 };
4185 static const char *blame_cat_file_argv[] = {
4186         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4187 };
4189 struct blame_commit {
4190         char id[SIZEOF_REV];            /* SHA1 ID. */
4191         char title[128];                /* First line of the commit message. */
4192         char author[75];                /* Author of the commit. */
4193         struct tm time;                 /* Date from the author ident. */
4194         char filename[128];             /* Name of file. */
4195         bool has_previous;              /* Was a "previous" line detected. */
4196 };
4198 struct blame {
4199         struct blame_commit *commit;
4200         char text[1];
4201 };
4203 static bool
4204 blame_open(struct view *view)
4206         if (*opt_ref || !io_open(&view->io, opt_file)) {
4207                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4208                         return FALSE;
4209         }
4211         setup_update(view, opt_file);
4212         string_format(view->ref, "%s ...", opt_file);
4214         return TRUE;
4217 static struct blame_commit *
4218 get_blame_commit(struct view *view, const char *id)
4220         size_t i;
4222         for (i = 0; i < view->lines; i++) {
4223                 struct blame *blame = view->line[i].data;
4225                 if (!blame->commit)
4226                         continue;
4228                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4229                         return blame->commit;
4230         }
4232         {
4233                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4235                 if (commit)
4236                         string_ncopy(commit->id, id, SIZEOF_REV);
4237                 return commit;
4238         }
4241 static bool
4242 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4244         const char *pos = *posref;
4246         *posref = NULL;
4247         pos = strchr(pos + 1, ' ');
4248         if (!pos || !isdigit(pos[1]))
4249                 return FALSE;
4250         *number = atoi(pos + 1);
4251         if (*number < min || *number > max)
4252                 return FALSE;
4254         *posref = pos;
4255         return TRUE;
4258 static struct blame_commit *
4259 parse_blame_commit(struct view *view, const char *text, int *blamed)
4261         struct blame_commit *commit;
4262         struct blame *blame;
4263         const char *pos = text + SIZEOF_REV - 1;
4264         size_t lineno;
4265         size_t group;
4267         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4268                 return NULL;
4270         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4271             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4272                 return NULL;
4274         commit = get_blame_commit(view, text);
4275         if (!commit)
4276                 return NULL;
4278         *blamed += group;
4279         while (group--) {
4280                 struct line *line = &view->line[lineno + group - 1];
4282                 blame = line->data;
4283                 blame->commit = commit;
4284                 line->dirty = 1;
4285         }
4287         return commit;
4290 static bool
4291 blame_read_file(struct view *view, const char *line, bool *read_file)
4293         if (!line) {
4294                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4295                 struct io io = {};
4297                 if (view->lines == 0 && !view->parent)
4298                         die("No blame exist for %s", view->vid);
4300                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4301                         report("Failed to load blame data");
4302                         return TRUE;
4303                 }
4305                 done_io(view->pipe);
4306                 view->io = io;
4307                 *read_file = FALSE;
4308                 return FALSE;
4310         } else {
4311                 size_t linelen = string_expand_length(line, opt_tab_size);
4312                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4314                 if (!blame)
4315                         return FALSE;
4317                 blame->commit = NULL;
4318                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4319                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4320         }
4323 static bool
4324 match_blame_header(const char *name, char **line)
4326         size_t namelen = strlen(name);
4327         bool matched = !strncmp(name, *line, namelen);
4329         if (matched)
4330                 *line += namelen;
4332         return matched;
4335 static bool
4336 blame_read(struct view *view, char *line)
4338         static struct blame_commit *commit = NULL;
4339         static int blamed = 0;
4340         static time_t author_time;
4341         static bool read_file = TRUE;
4343         if (read_file)
4344                 return blame_read_file(view, line, &read_file);
4346         if (!line) {
4347                 /* Reset all! */
4348                 commit = NULL;
4349                 blamed = 0;
4350                 read_file = TRUE;
4351                 string_format(view->ref, "%s", view->vid);
4352                 if (view_is_displayed(view)) {
4353                         update_view_title(view);
4354                         redraw_view_from(view, 0);
4355                 }
4356                 return TRUE;
4357         }
4359         if (!commit) {
4360                 commit = parse_blame_commit(view, line, &blamed);
4361                 string_format(view->ref, "%s %2d%%", view->vid,
4362                               view->lines ? blamed * 100 / view->lines : 0);
4364         } else if (match_blame_header("author ", &line)) {
4365                 string_ncopy(commit->author, line, strlen(line));
4367         } else if (match_blame_header("author-time ", &line)) {
4368                 author_time = (time_t) atol(line);
4370         } else if (match_blame_header("author-tz ", &line)) {
4371                 long tz;
4373                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4374                 tz += ('0' - line[2]) * 60 * 60;
4375                 tz += ('0' - line[3]) * 60;
4376                 tz += ('0' - line[4]) * 60;
4378                 if (line[0] == '-')
4379                         tz = -tz;
4381                 author_time -= tz;
4382                 gmtime_r(&author_time, &commit->time);
4384         } else if (match_blame_header("summary ", &line)) {
4385                 string_ncopy(commit->title, line, strlen(line));
4387         } else if (match_blame_header("previous ", &line)) {
4388                 commit->has_previous = TRUE;
4390         } else if (match_blame_header("filename ", &line)) {
4391                 string_ncopy(commit->filename, line, strlen(line));
4392                 commit = NULL;
4393         }
4395         return TRUE;
4398 static bool
4399 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4401         struct blame *blame = line->data;
4402         struct tm *time = NULL;
4403         const char *id = NULL, *author = NULL;
4405         if (blame->commit && *blame->commit->filename) {
4406                 id = blame->commit->id;
4407                 author = blame->commit->author;
4408                 time = &blame->commit->time;
4409         }
4411         if (opt_date && draw_date(view, time))
4412                 return TRUE;
4414         if (opt_author && draw_author(view, author))
4415                 return TRUE;
4417         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4418                 return TRUE;
4420         if (draw_lineno(view, lineno))
4421                 return TRUE;
4423         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4424         return TRUE;
4427 static bool
4428 check_blame_commit(struct blame *blame)
4430         if (!blame->commit)
4431                 report("Commit data not loaded yet");
4432         else if (!strcmp(blame->commit->id, NULL_ID))
4433                 report("No commit exist for the selected line");
4434         else
4435                 return TRUE;
4436         return FALSE;
4439 static enum request
4440 blame_request(struct view *view, enum request request, struct line *line)
4442         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4443         struct blame *blame = line->data;
4445         switch (request) {
4446         case REQ_VIEW_BLAME:
4447                 if (check_blame_commit(blame)) {
4448                         string_copy(opt_ref, blame->commit->id);
4449                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4450                 }
4451                 break;
4453         case REQ_PARENT:
4454                 if (check_blame_commit(blame) &&
4455                     select_commit_parent(blame->commit->id, opt_ref))
4456                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4457                 break;
4459         case REQ_ENTER:
4460                 if (!blame->commit) {
4461                         report("No commit loaded yet");
4462                         break;
4463                 }
4465                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4466                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4467                         break;
4469                 if (!strcmp(blame->commit->id, NULL_ID)) {
4470                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4471                         const char *diff_index_argv[] = {
4472                                 "git", "diff-index", "--root", "--patch-with-stat",
4473                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4474                         };
4476                         if (!blame->commit->has_previous) {
4477                                 diff_index_argv[1] = "diff";
4478                                 diff_index_argv[2] = "--no-color";
4479                                 diff_index_argv[6] = "--";
4480                                 diff_index_argv[7] = "/dev/null";
4481                         }
4483                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4484                                 report("Failed to allocate diff command");
4485                                 break;
4486                         }
4487                         flags |= OPEN_PREPARED;
4488                 }
4490                 open_view(view, REQ_VIEW_DIFF, flags);
4491                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4492                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4493                 break;
4495         default:
4496                 return request;
4497         }
4499         return REQ_NONE;
4502 static bool
4503 blame_grep(struct view *view, struct line *line)
4505         struct blame *blame = line->data;
4506         struct blame_commit *commit = blame->commit;
4507         regmatch_t pmatch;
4509 #define MATCH(text, on)                                                 \
4510         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4512         if (commit) {
4513                 char buf[DATE_COLS + 1];
4515                 if (MATCH(commit->title, 1) ||
4516                     MATCH(commit->author, opt_author) ||
4517                     MATCH(commit->id, opt_date))
4518                         return TRUE;
4520                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4521                     MATCH(buf, 1))
4522                         return TRUE;
4523         }
4525         return MATCH(blame->text, 1);
4527 #undef MATCH
4530 static void
4531 blame_select(struct view *view, struct line *line)
4533         struct blame *blame = line->data;
4534         struct blame_commit *commit = blame->commit;
4536         if (!commit)
4537                 return;
4539         if (!strcmp(commit->id, NULL_ID))
4540                 string_ncopy(ref_commit, "HEAD", 4);
4541         else
4542                 string_copy_rev(ref_commit, commit->id);
4545 static struct view_ops blame_ops = {
4546         "line",
4547         NULL,
4548         blame_open,
4549         blame_read,
4550         blame_draw,
4551         blame_request,
4552         blame_grep,
4553         blame_select,
4554 };
4556 /*
4557  * Status backend
4558  */
4560 struct status {
4561         char status;
4562         struct {
4563                 mode_t mode;
4564                 char rev[SIZEOF_REV];
4565                 char name[SIZEOF_STR];
4566         } old;
4567         struct {
4568                 mode_t mode;
4569                 char rev[SIZEOF_REV];
4570                 char name[SIZEOF_STR];
4571         } new;
4572 };
4574 static char status_onbranch[SIZEOF_STR];
4575 static struct status stage_status;
4576 static enum line_type stage_line_type;
4577 static size_t stage_chunks;
4578 static int *stage_chunk;
4580 /* This should work even for the "On branch" line. */
4581 static inline bool
4582 status_has_none(struct view *view, struct line *line)
4584         return line < view->line + view->lines && !line[1].data;
4587 /* Get fields from the diff line:
4588  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4589  */
4590 static inline bool
4591 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4593         const char *old_mode = buf +  1;
4594         const char *new_mode = buf +  8;
4595         const char *old_rev  = buf + 15;
4596         const char *new_rev  = buf + 56;
4597         const char *status   = buf + 97;
4599         if (bufsize < 98 ||
4600             old_mode[-1] != ':' ||
4601             new_mode[-1] != ' ' ||
4602             old_rev[-1]  != ' ' ||
4603             new_rev[-1]  != ' ' ||
4604             status[-1]   != ' ')
4605                 return FALSE;
4607         file->status = *status;
4609         string_copy_rev(file->old.rev, old_rev);
4610         string_copy_rev(file->new.rev, new_rev);
4612         file->old.mode = strtoul(old_mode, NULL, 8);
4613         file->new.mode = strtoul(new_mode, NULL, 8);
4615         file->old.name[0] = file->new.name[0] = 0;
4617         return TRUE;
4620 static bool
4621 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4623         struct status *unmerged = NULL;
4624         char *buf;
4625         struct io io = {};
4627         if (!run_io(&io, argv, NULL, IO_RD))
4628                 return FALSE;
4630         add_line_data(view, NULL, type);
4632         while ((buf = io_get(&io, 0, TRUE))) {
4633                 struct status *file = unmerged;
4635                 if (!file) {
4636                         file = calloc(1, sizeof(*file));
4637                         if (!file || !add_line_data(view, file, type))
4638                                 goto error_out;
4639                 }
4641                 /* Parse diff info part. */
4642                 if (status) {
4643                         file->status = status;
4644                         if (status == 'A')
4645                                 string_copy(file->old.rev, NULL_ID);
4647                 } else if (!file->status || file == unmerged) {
4648                         if (!status_get_diff(file, buf, strlen(buf)))
4649                                 goto error_out;
4651                         buf = io_get(&io, 0, TRUE);
4652                         if (!buf)
4653                                 break;
4655                         /* Collapse all 'M'odified entries that follow a
4656                          * associated 'U'nmerged entry. */
4657                         if (unmerged == file) {
4658                                 unmerged->status = 'U';
4659                                 unmerged = NULL;
4660                         } else if (file->status == 'U') {
4661                                 unmerged = file;
4662                         }
4663                 }
4665                 /* Grab the old name for rename/copy. */
4666                 if (!*file->old.name &&
4667                     (file->status == 'R' || file->status == 'C')) {
4668                         string_ncopy(file->old.name, buf, strlen(buf));
4670                         buf = io_get(&io, 0, TRUE);
4671                         if (!buf)
4672                                 break;
4673                 }
4675                 /* git-ls-files just delivers a NUL separated list of
4676                  * file names similar to the second half of the
4677                  * git-diff-* output. */
4678                 string_ncopy(file->new.name, buf, strlen(buf));
4679                 if (!*file->old.name)
4680                         string_copy(file->old.name, file->new.name);
4681                 file = NULL;
4682         }
4684         if (io_error(&io)) {
4685 error_out:
4686                 done_io(&io);
4687                 return FALSE;
4688         }
4690         if (!view->line[view->lines - 1].data)
4691                 add_line_data(view, NULL, LINE_STAT_NONE);
4693         done_io(&io);
4694         return TRUE;
4697 /* Don't show unmerged entries in the staged section. */
4698 static const char *status_diff_index_argv[] = {
4699         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4700                              "--cached", "-M", "HEAD", NULL
4701 };
4703 static const char *status_diff_files_argv[] = {
4704         "git", "diff-files", "-z", NULL
4705 };
4707 static const char *status_list_other_argv[] = {
4708         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4709 };
4711 static const char *status_list_no_head_argv[] = {
4712         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4713 };
4715 static const char *update_index_argv[] = {
4716         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4717 };
4719 /* Restore the previous line number to stay in the context or select a
4720  * line with something that can be updated. */
4721 static void
4722 status_restore(struct view *view)
4724         if (view->p_lineno >= view->lines)
4725                 view->p_lineno = view->lines - 1;
4726         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4727                 view->p_lineno++;
4728         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4729                 view->p_lineno--;
4731         /* If the above fails, always skip the "On branch" line. */
4732         if (view->p_lineno < view->lines)
4733                 view->lineno = view->p_lineno;
4734         else
4735                 view->lineno = 1;
4737         if (view->lineno < view->offset)
4738                 view->offset = view->lineno;
4739         else if (view->offset + view->height <= view->lineno)
4740                 view->offset = view->lineno - view->height + 1;
4742         view->p_restore = FALSE;
4745 /* First parse staged info using git-diff-index(1), then parse unstaged
4746  * info using git-diff-files(1), and finally untracked files using
4747  * git-ls-files(1). */
4748 static bool
4749 status_open(struct view *view)
4751         reset_view(view);
4753         add_line_data(view, NULL, LINE_STAT_HEAD);
4754         if (is_initial_commit())
4755                 string_copy(status_onbranch, "Initial commit");
4756         else if (!*opt_head)
4757                 string_copy(status_onbranch, "Not currently on any branch");
4758         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4759                 return FALSE;
4761         run_io_bg(update_index_argv);
4763         if (is_initial_commit()) {
4764                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4765                         return FALSE;
4766         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4767                 return FALSE;
4768         }
4770         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4771             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4772                 return FALSE;
4774         /* Restore the exact position or use the specialized restore
4775          * mode? */
4776         if (!view->p_restore)
4777                 status_restore(view);
4778         return TRUE;
4781 static bool
4782 status_draw(struct view *view, struct line *line, unsigned int lineno)
4784         struct status *status = line->data;
4785         enum line_type type;
4786         const char *text;
4788         if (!status) {
4789                 switch (line->type) {
4790                 case LINE_STAT_STAGED:
4791                         type = LINE_STAT_SECTION;
4792                         text = "Changes to be committed:";
4793                         break;
4795                 case LINE_STAT_UNSTAGED:
4796                         type = LINE_STAT_SECTION;
4797                         text = "Changed but not updated:";
4798                         break;
4800                 case LINE_STAT_UNTRACKED:
4801                         type = LINE_STAT_SECTION;
4802                         text = "Untracked files:";
4803                         break;
4805                 case LINE_STAT_NONE:
4806                         type = LINE_DEFAULT;
4807                         text = "  (no files)";
4808                         break;
4810                 case LINE_STAT_HEAD:
4811                         type = LINE_STAT_HEAD;
4812                         text = status_onbranch;
4813                         break;
4815                 default:
4816                         return FALSE;
4817                 }
4818         } else {
4819                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4821                 buf[0] = status->status;
4822                 if (draw_text(view, line->type, buf, TRUE))
4823                         return TRUE;
4824                 type = LINE_DEFAULT;
4825                 text = status->new.name;
4826         }
4828         draw_text(view, type, text, TRUE);
4829         return TRUE;
4832 static enum request
4833 status_enter(struct view *view, struct line *line)
4835         struct status *status = line->data;
4836         const char *oldpath = status ? status->old.name : NULL;
4837         /* Diffs for unmerged entries are empty when passing the new
4838          * path, so leave it empty. */
4839         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4840         const char *info;
4841         enum open_flags split;
4842         struct view *stage = VIEW(REQ_VIEW_STAGE);
4844         if (line->type == LINE_STAT_NONE ||
4845             (!status && line[1].type == LINE_STAT_NONE)) {
4846                 report("No file to diff");
4847                 return REQ_NONE;
4848         }
4850         switch (line->type) {
4851         case LINE_STAT_STAGED:
4852                 if (is_initial_commit()) {
4853                         const char *no_head_diff_argv[] = {
4854                                 "git", "diff", "--no-color", "--patch-with-stat",
4855                                         "--", "/dev/null", newpath, NULL
4856                         };
4858                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4859                                 return REQ_QUIT;
4860                 } else {
4861                         const char *index_show_argv[] = {
4862                                 "git", "diff-index", "--root", "--patch-with-stat",
4863                                         "-C", "-M", "--cached", "HEAD", "--",
4864                                         oldpath, newpath, NULL
4865                         };
4867                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4868                                 return REQ_QUIT;
4869                 }
4871                 if (status)
4872                         info = "Staged changes to %s";
4873                 else
4874                         info = "Staged changes";
4875                 break;
4877         case LINE_STAT_UNSTAGED:
4878         {
4879                 const char *files_show_argv[] = {
4880                         "git", "diff-files", "--root", "--patch-with-stat",
4881                                 "-C", "-M", "--", oldpath, newpath, NULL
4882                 };
4884                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4885                         return REQ_QUIT;
4886                 if (status)
4887                         info = "Unstaged changes to %s";
4888                 else
4889                         info = "Unstaged changes";
4890                 break;
4891         }
4892         case LINE_STAT_UNTRACKED:
4893                 if (!newpath) {
4894                         report("No file to show");
4895                         return REQ_NONE;
4896                 }
4898                 if (!suffixcmp(status->new.name, -1, "/")) {
4899                         report("Cannot display a directory");
4900                         return REQ_NONE;
4901                 }
4903                 if (!prepare_update_file(stage, newpath))
4904                         return REQ_QUIT;
4905                 info = "Untracked file %s";
4906                 break;
4908         case LINE_STAT_HEAD:
4909                 return REQ_NONE;
4911         default:
4912                 die("line type %d not handled in switch", line->type);
4913         }
4915         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4916         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4917         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4918                 if (status) {
4919                         stage_status = *status;
4920                 } else {
4921                         memset(&stage_status, 0, sizeof(stage_status));
4922                 }
4924                 stage_line_type = line->type;
4925                 stage_chunks = 0;
4926                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4927         }
4929         return REQ_NONE;
4932 static bool
4933 status_exists(struct status *status, enum line_type type)
4935         struct view *view = VIEW(REQ_VIEW_STATUS);
4936         unsigned long lineno;
4938         for (lineno = 0; lineno < view->lines; lineno++) {
4939                 struct line *line = &view->line[lineno];
4940                 struct status *pos = line->data;
4942                 if (line->type != type)
4943                         continue;
4944                 if (!pos && (!status || !status->status) && line[1].data) {
4945                         select_view_line(view, lineno);
4946                         return TRUE;
4947                 }
4948                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4949                         select_view_line(view, lineno);
4950                         return TRUE;
4951                 }
4952         }
4954         return FALSE;
4958 static bool
4959 status_update_prepare(struct io *io, enum line_type type)
4961         const char *staged_argv[] = {
4962                 "git", "update-index", "-z", "--index-info", NULL
4963         };
4964         const char *others_argv[] = {
4965                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4966         };
4968         switch (type) {
4969         case LINE_STAT_STAGED:
4970                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4972         case LINE_STAT_UNSTAGED:
4973                 return run_io(io, others_argv, opt_cdup, IO_WR);
4975         case LINE_STAT_UNTRACKED:
4976                 return run_io(io, others_argv, NULL, IO_WR);
4978         default:
4979                 die("line type %d not handled in switch", type);
4980                 return FALSE;
4981         }
4984 static bool
4985 status_update_write(struct io *io, struct status *status, enum line_type type)
4987         char buf[SIZEOF_STR];
4988         size_t bufsize = 0;
4990         switch (type) {
4991         case LINE_STAT_STAGED:
4992                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4993                                         status->old.mode,
4994                                         status->old.rev,
4995                                         status->old.name, 0))
4996                         return FALSE;
4997                 break;
4999         case LINE_STAT_UNSTAGED:
5000         case LINE_STAT_UNTRACKED:
5001                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5002                         return FALSE;
5003                 break;
5005         default:
5006                 die("line type %d not handled in switch", type);
5007         }
5009         return io_write(io, buf, bufsize);
5012 static bool
5013 status_update_file(struct status *status, enum line_type type)
5015         struct io io = {};
5016         bool result;
5018         if (!status_update_prepare(&io, type))
5019                 return FALSE;
5021         result = status_update_write(&io, status, type);
5022         done_io(&io);
5023         return result;
5026 static bool
5027 status_update_files(struct view *view, struct line *line)
5029         struct io io = {};
5030         bool result = TRUE;
5031         struct line *pos = view->line + view->lines;
5032         int files = 0;
5033         int file, done;
5035         if (!status_update_prepare(&io, line->type))
5036                 return FALSE;
5038         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5039                 files++;
5041         for (file = 0, done = 0; result && file < files; line++, file++) {
5042                 int almost_done = file * 100 / files;
5044                 if (almost_done > done) {
5045                         done = almost_done;
5046                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5047                                       file, files, done);
5048                         update_view_title(view);
5049                 }
5050                 result = status_update_write(&io, line->data, line->type);
5051         }
5053         done_io(&io);
5054         return result;
5057 static bool
5058 status_update(struct view *view)
5060         struct line *line = &view->line[view->lineno];
5062         assert(view->lines);
5064         if (!line->data) {
5065                 /* This should work even for the "On branch" line. */
5066                 if (line < view->line + view->lines && !line[1].data) {
5067                         report("Nothing to update");
5068                         return FALSE;
5069                 }
5071                 if (!status_update_files(view, line + 1)) {
5072                         report("Failed to update file status");
5073                         return FALSE;
5074                 }
5076         } else if (!status_update_file(line->data, line->type)) {
5077                 report("Failed to update file status");
5078                 return FALSE;
5079         }
5081         return TRUE;
5084 static bool
5085 status_revert(struct status *status, enum line_type type, bool has_none)
5087         if (!status || type != LINE_STAT_UNSTAGED) {
5088                 if (type == LINE_STAT_STAGED) {
5089                         report("Cannot revert changes to staged files");
5090                 } else if (type == LINE_STAT_UNTRACKED) {
5091                         report("Cannot revert changes to untracked files");
5092                 } else if (has_none) {
5093                         report("Nothing to revert");
5094                 } else {
5095                         report("Cannot revert changes to multiple files");
5096                 }
5097                 return FALSE;
5099         } else {
5100                 char mode[10] = "100644";
5101                 const char *reset_argv[] = {
5102                         "git", "update-index", "--cacheinfo", mode,
5103                                 status->old.rev, status->old.name, NULL
5104                 };
5105                 const char *checkout_argv[] = {
5106                         "git", "checkout", "--", status->old.name, NULL
5107                 };
5109                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5110                         return FALSE;
5111                 string_format(mode, "%o", status->old.mode);
5112                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5113                         run_io_fg(checkout_argv, opt_cdup);
5114         }
5117 static enum request
5118 status_request(struct view *view, enum request request, struct line *line)
5120         struct status *status = line->data;
5122         switch (request) {
5123         case REQ_STATUS_UPDATE:
5124                 if (!status_update(view))
5125                         return REQ_NONE;
5126                 break;
5128         case REQ_STATUS_REVERT:
5129                 if (!status_revert(status, line->type, status_has_none(view, line)))
5130                         return REQ_NONE;
5131                 break;
5133         case REQ_STATUS_MERGE:
5134                 if (!status || status->status != 'U') {
5135                         report("Merging only possible for files with unmerged status ('U').");
5136                         return REQ_NONE;
5137                 }
5138                 open_mergetool(status->new.name);
5139                 break;
5141         case REQ_EDIT:
5142                 if (!status)
5143                         return request;
5144                 if (status->status == 'D') {
5145                         report("File has been deleted.");
5146                         return REQ_NONE;
5147                 }
5149                 open_editor(status->status != '?', status->new.name);
5150                 break;
5152         case REQ_VIEW_BLAME:
5153                 if (status) {
5154                         string_copy(opt_file, status->new.name);
5155                         opt_ref[0] = 0;
5156                 }
5157                 return request;
5159         case REQ_ENTER:
5160                 /* After returning the status view has been split to
5161                  * show the stage view. No further reloading is
5162                  * necessary. */
5163                 status_enter(view, line);
5164                 return REQ_NONE;
5166         case REQ_REFRESH:
5167                 /* Simply reload the view. */
5168                 break;
5170         default:
5171                 return request;
5172         }
5174         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5176         return REQ_NONE;
5179 static void
5180 status_select(struct view *view, struct line *line)
5182         struct status *status = line->data;
5183         char file[SIZEOF_STR] = "all files";
5184         const char *text;
5185         const char *key;
5187         if (status && !string_format(file, "'%s'", status->new.name))
5188                 return;
5190         if (!status && line[1].type == LINE_STAT_NONE)
5191                 line++;
5193         switch (line->type) {
5194         case LINE_STAT_STAGED:
5195                 text = "Press %s to unstage %s for commit";
5196                 break;
5198         case LINE_STAT_UNSTAGED:
5199                 text = "Press %s to stage %s for commit";
5200                 break;
5202         case LINE_STAT_UNTRACKED:
5203                 text = "Press %s to stage %s for addition";
5204                 break;
5206         case LINE_STAT_HEAD:
5207         case LINE_STAT_NONE:
5208                 text = "Nothing to update";
5209                 break;
5211         default:
5212                 die("line type %d not handled in switch", line->type);
5213         }
5215         if (status && status->status == 'U') {
5216                 text = "Press %s to resolve conflict in %s";
5217                 key = get_key(REQ_STATUS_MERGE);
5219         } else {
5220                 key = get_key(REQ_STATUS_UPDATE);
5221         }
5223         string_format(view->ref, text, key, file);
5226 static bool
5227 status_grep(struct view *view, struct line *line)
5229         struct status *status = line->data;
5230         enum { S_STATUS, S_NAME, S_END } state;
5231         char buf[2] = "?";
5232         regmatch_t pmatch;
5234         if (!status)
5235                 return FALSE;
5237         for (state = S_STATUS; state < S_END; state++) {
5238                 const char *text;
5240                 switch (state) {
5241                 case S_NAME:    text = status->new.name;        break;
5242                 case S_STATUS:
5243                         buf[0] = status->status;
5244                         text = buf;
5245                         break;
5247                 default:
5248                         return FALSE;
5249                 }
5251                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5252                         return TRUE;
5253         }
5255         return FALSE;
5258 static struct view_ops status_ops = {
5259         "file",
5260         NULL,
5261         status_open,
5262         NULL,
5263         status_draw,
5264         status_request,
5265         status_grep,
5266         status_select,
5267 };
5270 static bool
5271 stage_diff_write(struct io *io, struct line *line, struct line *end)
5273         while (line < end) {
5274                 if (!io_write(io, line->data, strlen(line->data)) ||
5275                     !io_write(io, "\n", 1))
5276                         return FALSE;
5277                 line++;
5278                 if (line->type == LINE_DIFF_CHUNK ||
5279                     line->type == LINE_DIFF_HEADER)
5280                         break;
5281         }
5283         return TRUE;
5286 static struct line *
5287 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5289         for (; view->line < line; line--)
5290                 if (line->type == type)
5291                         return line;
5293         return NULL;
5296 static bool
5297 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5299         const char *apply_argv[SIZEOF_ARG] = {
5300                 "git", "apply", "--whitespace=nowarn", NULL
5301         };
5302         struct line *diff_hdr;
5303         struct io io = {};
5304         int argc = 3;
5306         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5307         if (!diff_hdr)
5308                 return FALSE;
5310         if (!revert)
5311                 apply_argv[argc++] = "--cached";
5312         if (revert || stage_line_type == LINE_STAT_STAGED)
5313                 apply_argv[argc++] = "-R";
5314         apply_argv[argc++] = "-";
5315         apply_argv[argc++] = NULL;
5316         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5317                 return FALSE;
5319         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5320             !stage_diff_write(&io, chunk, view->line + view->lines))
5321                 chunk = NULL;
5323         done_io(&io);
5324         run_io_bg(update_index_argv);
5326         return chunk ? TRUE : FALSE;
5329 static bool
5330 stage_update(struct view *view, struct line *line)
5332         struct line *chunk = NULL;
5334         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5335                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5337         if (chunk) {
5338                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5339                         report("Failed to apply chunk");
5340                         return FALSE;
5341                 }
5343         } else if (!stage_status.status) {
5344                 view = VIEW(REQ_VIEW_STATUS);
5346                 for (line = view->line; line < view->line + view->lines; line++)
5347                         if (line->type == stage_line_type)
5348                                 break;
5350                 if (!status_update_files(view, line + 1)) {
5351                         report("Failed to update files");
5352                         return FALSE;
5353                 }
5355         } else if (!status_update_file(&stage_status, stage_line_type)) {
5356                 report("Failed to update file");
5357                 return FALSE;
5358         }
5360         return TRUE;
5363 static bool
5364 stage_revert(struct view *view, struct line *line)
5366         struct line *chunk = NULL;
5368         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5369                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5371         if (chunk) {
5372                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5373                         return FALSE;
5375                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5376                         report("Failed to revert chunk");
5377                         return FALSE;
5378                 }
5379                 return TRUE;
5381         } else {
5382                 return status_revert(stage_status.status ? &stage_status : NULL,
5383                                      stage_line_type, FALSE);
5384         }
5388 static void
5389 stage_next(struct view *view, struct line *line)
5391         int i;
5393         if (!stage_chunks) {
5394                 static size_t alloc = 0;
5395                 int *tmp;
5397                 for (line = view->line; line < view->line + view->lines; line++) {
5398                         if (line->type != LINE_DIFF_CHUNK)
5399                                 continue;
5401                         tmp = realloc_items(stage_chunk, &alloc,
5402                                             stage_chunks, sizeof(*tmp));
5403                         if (!tmp) {
5404                                 report("Allocation failure");
5405                                 return;
5406                         }
5408                         stage_chunk = tmp;
5409                         stage_chunk[stage_chunks++] = line - view->line;
5410                 }
5411         }
5413         for (i = 0; i < stage_chunks; i++) {
5414                 if (stage_chunk[i] > view->lineno) {
5415                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5416                         report("Chunk %d of %d", i + 1, stage_chunks);
5417                         return;
5418                 }
5419         }
5421         report("No next chunk found");
5424 static enum request
5425 stage_request(struct view *view, enum request request, struct line *line)
5427         switch (request) {
5428         case REQ_STATUS_UPDATE:
5429                 if (!stage_update(view, line))
5430                         return REQ_NONE;
5431                 break;
5433         case REQ_STATUS_REVERT:
5434                 if (!stage_revert(view, line))
5435                         return REQ_NONE;
5436                 break;
5438         case REQ_STAGE_NEXT:
5439                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5440                         report("File is untracked; press %s to add",
5441                                get_key(REQ_STATUS_UPDATE));
5442                         return REQ_NONE;
5443                 }
5444                 stage_next(view, line);
5445                 return REQ_NONE;
5447         case REQ_EDIT:
5448                 if (!stage_status.new.name[0])
5449                         return request;
5450                 if (stage_status.status == 'D') {
5451                         report("File has been deleted.");
5452                         return REQ_NONE;
5453                 }
5455                 open_editor(stage_status.status != '?', stage_status.new.name);
5456                 break;
5458         case REQ_REFRESH:
5459                 /* Reload everything ... */
5460                 break;
5462         case REQ_VIEW_BLAME:
5463                 if (stage_status.new.name[0]) {
5464                         string_copy(opt_file, stage_status.new.name);
5465                         opt_ref[0] = 0;
5466                 }
5467                 return request;
5469         case REQ_ENTER:
5470                 return pager_request(view, request, line);
5472         default:
5473                 return request;
5474         }
5476         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5477         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5479         /* Check whether the staged entry still exists, and close the
5480          * stage view if it doesn't. */
5481         if (!status_exists(&stage_status, stage_line_type)) {
5482                 status_restore(VIEW(REQ_VIEW_STATUS));
5483                 return REQ_VIEW_CLOSE;
5484         }
5486         if (stage_line_type == LINE_STAT_UNTRACKED) {
5487                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5488                         report("Cannot display a directory");
5489                         return REQ_NONE;
5490                 }
5492                 if (!prepare_update_file(view, stage_status.new.name)) {
5493                         report("Failed to open file: %s", strerror(errno));
5494                         return REQ_NONE;
5495                 }
5496         }
5497         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5499         return REQ_NONE;
5502 static struct view_ops stage_ops = {
5503         "line",
5504         NULL,
5505         NULL,
5506         pager_read,
5507         pager_draw,
5508         stage_request,
5509         pager_grep,
5510         pager_select,
5511 };
5514 /*
5515  * Revision graph
5516  */
5518 struct commit {
5519         char id[SIZEOF_REV];            /* SHA1 ID. */
5520         char title[128];                /* First line of the commit message. */
5521         char author[75];                /* Author of the commit. */
5522         struct tm time;                 /* Date from the author ident. */
5523         struct ref **refs;              /* Repository references. */
5524         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5525         size_t graph_size;              /* The width of the graph array. */
5526         bool has_parents;               /* Rewritten --parents seen. */
5527 };
5529 /* Size of rev graph with no  "padding" columns */
5530 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5532 struct rev_graph {
5533         struct rev_graph *prev, *next, *parents;
5534         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5535         size_t size;
5536         struct commit *commit;
5537         size_t pos;
5538         unsigned int boundary:1;
5539 };
5541 /* Parents of the commit being visualized. */
5542 static struct rev_graph graph_parents[4];
5544 /* The current stack of revisions on the graph. */
5545 static struct rev_graph graph_stacks[4] = {
5546         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5547         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5548         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5549         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5550 };
5552 static inline bool
5553 graph_parent_is_merge(struct rev_graph *graph)
5555         return graph->parents->size > 1;
5558 static inline void
5559 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5561         struct commit *commit = graph->commit;
5563         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5564                 commit->graph[commit->graph_size++] = symbol;
5567 static void
5568 clear_rev_graph(struct rev_graph *graph)
5570         graph->boundary = 0;
5571         graph->size = graph->pos = 0;
5572         graph->commit = NULL;
5573         memset(graph->parents, 0, sizeof(*graph->parents));
5576 static void
5577 done_rev_graph(struct rev_graph *graph)
5579         if (graph_parent_is_merge(graph) &&
5580             graph->pos < graph->size - 1 &&
5581             graph->next->size == graph->size + graph->parents->size - 1) {
5582                 size_t i = graph->pos + graph->parents->size - 1;
5584                 graph->commit->graph_size = i * 2;
5585                 while (i < graph->next->size - 1) {
5586                         append_to_rev_graph(graph, ' ');
5587                         append_to_rev_graph(graph, '\\');
5588                         i++;
5589                 }
5590         }
5592         clear_rev_graph(graph);
5595 static void
5596 push_rev_graph(struct rev_graph *graph, const char *parent)
5598         int i;
5600         /* "Collapse" duplicate parents lines.
5601          *
5602          * FIXME: This needs to also update update the drawn graph but
5603          * for now it just serves as a method for pruning graph lines. */
5604         for (i = 0; i < graph->size; i++)
5605                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5606                         return;
5608         if (graph->size < SIZEOF_REVITEMS) {
5609                 string_copy_rev(graph->rev[graph->size++], parent);
5610         }
5613 static chtype
5614 get_rev_graph_symbol(struct rev_graph *graph)
5616         chtype symbol;
5618         if (graph->boundary)
5619                 symbol = REVGRAPH_BOUND;
5620         else if (graph->parents->size == 0)
5621                 symbol = REVGRAPH_INIT;
5622         else if (graph_parent_is_merge(graph))
5623                 symbol = REVGRAPH_MERGE;
5624         else if (graph->pos >= graph->size)
5625                 symbol = REVGRAPH_BRANCH;
5626         else
5627                 symbol = REVGRAPH_COMMIT;
5629         return symbol;
5632 static void
5633 draw_rev_graph(struct rev_graph *graph)
5635         struct rev_filler {
5636                 chtype separator, line;
5637         };
5638         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5639         static struct rev_filler fillers[] = {
5640                 { ' ',  '|' },
5641                 { '`',  '.' },
5642                 { '\'', ' ' },
5643                 { '/',  ' ' },
5644         };
5645         chtype symbol = get_rev_graph_symbol(graph);
5646         struct rev_filler *filler;
5647         size_t i;
5649         if (opt_line_graphics)
5650                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5652         filler = &fillers[DEFAULT];
5654         for (i = 0; i < graph->pos; i++) {
5655                 append_to_rev_graph(graph, filler->line);
5656                 if (graph_parent_is_merge(graph->prev) &&
5657                     graph->prev->pos == i)
5658                         filler = &fillers[RSHARP];
5660                 append_to_rev_graph(graph, filler->separator);
5661         }
5663         /* Place the symbol for this revision. */
5664         append_to_rev_graph(graph, symbol);
5666         if (graph->prev->size > graph->size)
5667                 filler = &fillers[RDIAG];
5668         else
5669                 filler = &fillers[DEFAULT];
5671         i++;
5673         for (; i < graph->size; i++) {
5674                 append_to_rev_graph(graph, filler->separator);
5675                 append_to_rev_graph(graph, filler->line);
5676                 if (graph_parent_is_merge(graph->prev) &&
5677                     i < graph->prev->pos + graph->parents->size)
5678                         filler = &fillers[RSHARP];
5679                 if (graph->prev->size > graph->size)
5680                         filler = &fillers[LDIAG];
5681         }
5683         if (graph->prev->size > graph->size) {
5684                 append_to_rev_graph(graph, filler->separator);
5685                 if (filler->line != ' ')
5686                         append_to_rev_graph(graph, filler->line);
5687         }
5690 /* Prepare the next rev graph */
5691 static void
5692 prepare_rev_graph(struct rev_graph *graph)
5694         size_t i;
5696         /* First, traverse all lines of revisions up to the active one. */
5697         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5698                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5699                         break;
5701                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5702         }
5704         /* Interleave the new revision parent(s). */
5705         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5706                 push_rev_graph(graph->next, graph->parents->rev[i]);
5708         /* Lastly, put any remaining revisions. */
5709         for (i = graph->pos + 1; i < graph->size; i++)
5710                 push_rev_graph(graph->next, graph->rev[i]);
5713 static void
5714 update_rev_graph(struct view *view, struct rev_graph *graph)
5716         /* If this is the finalizing update ... */
5717         if (graph->commit)
5718                 prepare_rev_graph(graph);
5720         /* Graph visualization needs a one rev look-ahead,
5721          * so the first update doesn't visualize anything. */
5722         if (!graph->prev->commit)
5723                 return;
5725         if (view->lines > 2)
5726                 view->line[view->lines - 3].dirty = 1;
5727         if (view->lines > 1)
5728                 view->line[view->lines - 2].dirty = 1;
5729         draw_rev_graph(graph->prev);
5730         done_rev_graph(graph->prev->prev);
5734 /*
5735  * Main view backend
5736  */
5738 static const char *main_argv[SIZEOF_ARG] = {
5739         "git", "log", "--no-color", "--pretty=raw", "--parents",
5740                       "--topo-order", "%(head)", NULL
5741 };
5743 static bool
5744 main_draw(struct view *view, struct line *line, unsigned int lineno)
5746         struct commit *commit = line->data;
5748         if (!*commit->author)
5749                 return FALSE;
5751         if (opt_date && draw_date(view, &commit->time))
5752                 return TRUE;
5754         if (opt_author && draw_author(view, commit->author))
5755                 return TRUE;
5757         if (opt_rev_graph && commit->graph_size &&
5758             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5759                 return TRUE;
5761         if (opt_show_refs && commit->refs) {
5762                 size_t i = 0;
5764                 do {
5765                         enum line_type type;
5767                         if (commit->refs[i]->head)
5768                                 type = LINE_MAIN_HEAD;
5769                         else if (commit->refs[i]->ltag)
5770                                 type = LINE_MAIN_LOCAL_TAG;
5771                         else if (commit->refs[i]->tag)
5772                                 type = LINE_MAIN_TAG;
5773                         else if (commit->refs[i]->tracked)
5774                                 type = LINE_MAIN_TRACKED;
5775                         else if (commit->refs[i]->remote)
5776                                 type = LINE_MAIN_REMOTE;
5777                         else
5778                                 type = LINE_MAIN_REF;
5780                         if (draw_text(view, type, "[", TRUE) ||
5781                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5782                             draw_text(view, type, "]", TRUE))
5783                                 return TRUE;
5785                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5786                                 return TRUE;
5787                 } while (commit->refs[i++]->next);
5788         }
5790         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5791         return TRUE;
5794 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5795 static bool
5796 main_read(struct view *view, char *line)
5798         static struct rev_graph *graph = graph_stacks;
5799         enum line_type type;
5800         struct commit *commit;
5802         if (!line) {
5803                 int i;
5805                 if (!view->lines && !view->parent)
5806                         die("No revisions match the given arguments.");
5807                 if (view->lines > 0) {
5808                         commit = view->line[view->lines - 1].data;
5809                         view->line[view->lines - 1].dirty = 1;
5810                         if (!*commit->author) {
5811                                 view->lines--;
5812                                 free(commit);
5813                                 graph->commit = NULL;
5814                         }
5815                 }
5816                 update_rev_graph(view, graph);
5818                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5819                         clear_rev_graph(&graph_stacks[i]);
5820                 return TRUE;
5821         }
5823         type = get_line_type(line);
5824         if (type == LINE_COMMIT) {
5825                 commit = calloc(1, sizeof(struct commit));
5826                 if (!commit)
5827                         return FALSE;
5829                 line += STRING_SIZE("commit ");
5830                 if (*line == '-') {
5831                         graph->boundary = 1;
5832                         line++;
5833                 }
5835                 string_copy_rev(commit->id, line);
5836                 commit->refs = get_refs(commit->id);
5837                 graph->commit = commit;
5838                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5840                 while ((line = strchr(line, ' '))) {
5841                         line++;
5842                         push_rev_graph(graph->parents, line);
5843                         commit->has_parents = TRUE;
5844                 }
5845                 return TRUE;
5846         }
5848         if (!view->lines)
5849                 return TRUE;
5850         commit = view->line[view->lines - 1].data;
5852         switch (type) {
5853         case LINE_PARENT:
5854                 if (commit->has_parents)
5855                         break;
5856                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5857                 break;
5859         case LINE_AUTHOR:
5860                 parse_author_line(line + STRING_SIZE("author "),
5861                                   commit->author, sizeof(commit->author),
5862                                   &commit->time);
5863                 update_rev_graph(view, graph);
5864                 graph = graph->next;
5865                 break;
5867         default:
5868                 /* Fill in the commit title if it has not already been set. */
5869                 if (commit->title[0])
5870                         break;
5872                 /* Require titles to start with a non-space character at the
5873                  * offset used by git log. */
5874                 if (strncmp(line, "    ", 4))
5875                         break;
5876                 line += 4;
5877                 /* Well, if the title starts with a whitespace character,
5878                  * try to be forgiving.  Otherwise we end up with no title. */
5879                 while (isspace(*line))
5880                         line++;
5881                 if (*line == '\0')
5882                         break;
5883                 /* FIXME: More graceful handling of titles; append "..." to
5884                  * shortened titles, etc. */
5886                 string_expand(commit->title, sizeof(commit->title), line, 1);
5887                 view->line[view->lines - 1].dirty = 1;
5888         }
5890         return TRUE;
5893 static enum request
5894 main_request(struct view *view, enum request request, struct line *line)
5896         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5898         switch (request) {
5899         case REQ_ENTER:
5900                 open_view(view, REQ_VIEW_DIFF, flags);
5901                 break;
5902         case REQ_REFRESH:
5903                 load_refs();
5904                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5905                 break;
5906         default:
5907                 return request;
5908         }
5910         return REQ_NONE;
5913 static bool
5914 grep_refs(struct ref **refs, regex_t *regex)
5916         regmatch_t pmatch;
5917         size_t i = 0;
5919         if (!refs)
5920                 return FALSE;
5921         do {
5922                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5923                         return TRUE;
5924         } while (refs[i++]->next);
5926         return FALSE;
5929 static bool
5930 main_grep(struct view *view, struct line *line)
5932         struct commit *commit = line->data;
5933         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5934         char buf[DATE_COLS + 1];
5935         regmatch_t pmatch;
5937         for (state = S_TITLE; state < S_END; state++) {
5938                 char *text;
5940                 switch (state) {
5941                 case S_TITLE:   text = commit->title;   break;
5942                 case S_AUTHOR:
5943                         if (!opt_author)
5944                                 continue;
5945                         text = commit->author;
5946                         break;
5947                 case S_DATE:
5948                         if (!opt_date)
5949                                 continue;
5950                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5951                                 continue;
5952                         text = buf;
5953                         break;
5954                 case S_REFS:
5955                         if (!opt_show_refs)
5956                                 continue;
5957                         if (grep_refs(commit->refs, view->regex) == TRUE)
5958                                 return TRUE;
5959                         continue;
5960                 default:
5961                         return FALSE;
5962                 }
5964                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5965                         return TRUE;
5966         }
5968         return FALSE;
5971 static void
5972 main_select(struct view *view, struct line *line)
5974         struct commit *commit = line->data;
5976         string_copy_rev(view->ref, commit->id);
5977         string_copy_rev(ref_commit, view->ref);
5980 static struct view_ops main_ops = {
5981         "commit",
5982         main_argv,
5983         NULL,
5984         main_read,
5985         main_draw,
5986         main_request,
5987         main_grep,
5988         main_select,
5989 };
5992 /*
5993  * Unicode / UTF-8 handling
5994  *
5995  * NOTE: Much of the following code for dealing with unicode is derived from
5996  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5997  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5998  */
6000 /* I've (over)annotated a lot of code snippets because I am not entirely
6001  * confident that the approach taken by this small UTF-8 interface is correct.
6002  * --jonas */
6004 static inline int
6005 unicode_width(unsigned long c)
6007         if (c >= 0x1100 &&
6008            (c <= 0x115f                         /* Hangul Jamo */
6009             || c == 0x2329
6010             || c == 0x232a
6011             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6012                                                 /* CJK ... Yi */
6013             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6014             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6015             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6016             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6017             || (c >= 0xffe0  && c <= 0xffe6)
6018             || (c >= 0x20000 && c <= 0x2fffd)
6019             || (c >= 0x30000 && c <= 0x3fffd)))
6020                 return 2;
6022         if (c == '\t')
6023                 return opt_tab_size;
6025         return 1;
6028 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6029  * Illegal bytes are set one. */
6030 static const unsigned char utf8_bytes[256] = {
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         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,
6033         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,
6034         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,
6035         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,
6036         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,
6037         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,
6038         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,
6039 };
6041 /* Decode UTF-8 multi-byte representation into a unicode character. */
6042 static inline unsigned long
6043 utf8_to_unicode(const char *string, size_t length)
6045         unsigned long unicode;
6047         switch (length) {
6048         case 1:
6049                 unicode  =   string[0];
6050                 break;
6051         case 2:
6052                 unicode  =  (string[0] & 0x1f) << 6;
6053                 unicode +=  (string[1] & 0x3f);
6054                 break;
6055         case 3:
6056                 unicode  =  (string[0] & 0x0f) << 12;
6057                 unicode += ((string[1] & 0x3f) << 6);
6058                 unicode +=  (string[2] & 0x3f);
6059                 break;
6060         case 4:
6061                 unicode  =  (string[0] & 0x0f) << 18;
6062                 unicode += ((string[1] & 0x3f) << 12);
6063                 unicode += ((string[2] & 0x3f) << 6);
6064                 unicode +=  (string[3] & 0x3f);
6065                 break;
6066         case 5:
6067                 unicode  =  (string[0] & 0x0f) << 24;
6068                 unicode += ((string[1] & 0x3f) << 18);
6069                 unicode += ((string[2] & 0x3f) << 12);
6070                 unicode += ((string[3] & 0x3f) << 6);
6071                 unicode +=  (string[4] & 0x3f);
6072                 break;
6073         case 6:
6074                 unicode  =  (string[0] & 0x01) << 30;
6075                 unicode += ((string[1] & 0x3f) << 24);
6076                 unicode += ((string[2] & 0x3f) << 18);
6077                 unicode += ((string[3] & 0x3f) << 12);
6078                 unicode += ((string[4] & 0x3f) << 6);
6079                 unicode +=  (string[5] & 0x3f);
6080                 break;
6081         default:
6082                 die("Invalid unicode length");
6083         }
6085         /* Invalid characters could return the special 0xfffd value but NUL
6086          * should be just as good. */
6087         return unicode > 0xffff ? 0 : unicode;
6090 /* Calculates how much of string can be shown within the given maximum width
6091  * and sets trimmed parameter to non-zero value if all of string could not be
6092  * shown. If the reserve flag is TRUE, it will reserve at least one
6093  * trailing character, which can be useful when drawing a delimiter.
6094  *
6095  * Returns the number of bytes to output from string to satisfy max_width. */
6096 static size_t
6097 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6099         const char *string = *start;
6100         const char *end = strchr(string, '\0');
6101         unsigned char last_bytes = 0;
6102         size_t last_ucwidth = 0;
6104         *width = 0;
6105         *trimmed = 0;
6107         while (string < end) {
6108                 int c = *(unsigned char *) string;
6109                 unsigned char bytes = utf8_bytes[c];
6110                 size_t ucwidth;
6111                 unsigned long unicode;
6113                 if (string + bytes > end)
6114                         break;
6116                 /* Change representation to figure out whether
6117                  * it is a single- or double-width character. */
6119                 unicode = utf8_to_unicode(string, bytes);
6120                 /* FIXME: Graceful handling of invalid unicode character. */
6121                 if (!unicode)
6122                         break;
6124                 ucwidth = unicode_width(unicode);
6125                 if (skip > 0) {
6126                         skip -= ucwidth <= skip ? ucwidth : skip;
6127                         *start += bytes;
6128                 }
6129                 *width  += ucwidth;
6130                 if (*width > max_width) {
6131                         *trimmed = 1;
6132                         *width -= ucwidth;
6133                         if (reserve && *width == max_width) {
6134                                 string -= last_bytes;
6135                                 *width -= last_ucwidth;
6136                         }
6137                         break;
6138                 }
6140                 string  += bytes;
6141                 last_bytes = ucwidth ? bytes : 0;
6142                 last_ucwidth = ucwidth;
6143         }
6145         return string - *start;
6149 /*
6150  * Status management
6151  */
6153 /* Whether or not the curses interface has been initialized. */
6154 static bool cursed = FALSE;
6156 /* Terminal hacks and workarounds. */
6157 static bool use_scroll_redrawwin;
6158 static bool use_scroll_status_wclear;
6160 /* The status window is used for polling keystrokes. */
6161 static WINDOW *status_win;
6163 /* Reading from the prompt? */
6164 static bool input_mode = FALSE;
6166 static bool status_empty = FALSE;
6168 /* Update status and title window. */
6169 static void
6170 report(const char *msg, ...)
6172         struct view *view = display[current_view];
6174         if (input_mode)
6175                 return;
6177         if (!view) {
6178                 char buf[SIZEOF_STR];
6179                 va_list args;
6181                 va_start(args, msg);
6182                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6183                         buf[sizeof(buf) - 1] = 0;
6184                         buf[sizeof(buf) - 2] = '.';
6185                         buf[sizeof(buf) - 3] = '.';
6186                         buf[sizeof(buf) - 4] = '.';
6187                 }
6188                 va_end(args);
6189                 die("%s", buf);
6190         }
6192         if (!status_empty || *msg) {
6193                 va_list args;
6195                 va_start(args, msg);
6197                 wmove(status_win, 0, 0);
6198                 if (view->has_scrolled && use_scroll_status_wclear)
6199                         wclear(status_win);
6200                 if (*msg) {
6201                         vwprintw(status_win, msg, args);
6202                         status_empty = FALSE;
6203                 } else {
6204                         status_empty = TRUE;
6205                 }
6206                 wclrtoeol(status_win);
6207                 wnoutrefresh(status_win);
6209                 va_end(args);
6210         }
6212         update_view_title(view);
6215 /* Controls when nodelay should be in effect when polling user input. */
6216 static void
6217 set_nonblocking_input(bool loading)
6219         static unsigned int loading_views;
6221         if ((loading == FALSE && loading_views-- == 1) ||
6222             (loading == TRUE  && loading_views++ == 0))
6223                 nodelay(status_win, loading);
6226 static void
6227 init_display(void)
6229         const char *term;
6230         int x, y;
6232         /* Initialize the curses library */
6233         if (isatty(STDIN_FILENO)) {
6234                 cursed = !!initscr();
6235                 opt_tty = stdin;
6236         } else {
6237                 /* Leave stdin and stdout alone when acting as a pager. */
6238                 opt_tty = fopen("/dev/tty", "r+");
6239                 if (!opt_tty)
6240                         die("Failed to open /dev/tty");
6241                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6242         }
6244         if (!cursed)
6245                 die("Failed to initialize curses");
6247         nonl();         /* Tell curses not to do NL->CR/NL on output */
6248         cbreak();       /* Take input chars one at a time, no wait for \n */
6249         noecho();       /* Don't echo input */
6250         leaveok(stdscr, FALSE);
6252         if (has_colors())
6253                 init_colors();
6255         getmaxyx(stdscr, y, x);
6256         status_win = newwin(1, 0, y - 1, 0);
6257         if (!status_win)
6258                 die("Failed to create status window");
6260         /* Enable keyboard mapping */
6261         keypad(status_win, TRUE);
6262         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6264         TABSIZE = opt_tab_size;
6265         if (opt_line_graphics) {
6266                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6267         }
6269         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6270         if (term && !strcmp(term, "gnome-terminal")) {
6271                 /* In the gnome-terminal-emulator, the message from
6272                  * scrolling up one line when impossible followed by
6273                  * scrolling down one line causes corruption of the
6274                  * status line. This is fixed by calling wclear. */
6275                 use_scroll_status_wclear = TRUE;
6276                 use_scroll_redrawwin = FALSE;
6278         } else if (term && !strcmp(term, "xrvt-xpm")) {
6279                 /* No problems with full optimizations in xrvt-(unicode)
6280                  * and aterm. */
6281                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6283         } else {
6284                 /* When scrolling in (u)xterm the last line in the
6285                  * scrolling direction will update slowly. */
6286                 use_scroll_redrawwin = TRUE;
6287                 use_scroll_status_wclear = FALSE;
6288         }
6291 static int
6292 get_input(int prompt_position)
6294         struct view *view;
6295         int i, key, cursor_y, cursor_x;
6297         if (prompt_position)
6298                 input_mode = TRUE;
6300         while (TRUE) {
6301                 foreach_view (view, i) {
6302                         update_view(view);
6303                         if (view_is_displayed(view) && view->has_scrolled &&
6304                             use_scroll_redrawwin)
6305                                 redrawwin(view->win);
6306                         view->has_scrolled = FALSE;
6307                 }
6309                 /* Update the cursor position. */
6310                 if (prompt_position) {
6311                         getbegyx(status_win, cursor_y, cursor_x);
6312                         cursor_x = prompt_position;
6313                 } else {
6314                         view = display[current_view];
6315                         getbegyx(view->win, cursor_y, cursor_x);
6316                         cursor_x = view->width - 1;
6317                         cursor_y += view->lineno - view->offset;
6318                 }
6319                 setsyx(cursor_y, cursor_x);
6321                 /* Refresh, accept single keystroke of input */
6322                 doupdate();
6323                 key = wgetch(status_win);
6325                 /* wgetch() with nodelay() enabled returns ERR when
6326                  * there's no input. */
6327                 if (key == ERR) {
6329                 } else if (key == KEY_RESIZE) {
6330                         int height, width;
6332                         getmaxyx(stdscr, height, width);
6334                         wresize(status_win, 1, width);
6335                         mvwin(status_win, height - 1, 0);
6336                         wnoutrefresh(status_win);
6337                         resize_display();
6338                         redraw_display(TRUE);
6340                 } else {
6341                         input_mode = FALSE;
6342                         return key;
6343                 }
6344         }
6347 static char *
6348 prompt_input(const char *prompt, input_handler handler, void *data)
6350         enum input_status status = INPUT_OK;
6351         static char buf[SIZEOF_STR];
6352         size_t pos = 0;
6354         buf[pos] = 0;
6356         while (status == INPUT_OK || status == INPUT_SKIP) {
6357                 int key;
6359                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6360                 wclrtoeol(status_win);
6362                 key = get_input(pos + 1);
6363                 switch (key) {
6364                 case KEY_RETURN:
6365                 case KEY_ENTER:
6366                 case '\n':
6367                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6368                         break;
6370                 case KEY_BACKSPACE:
6371                         if (pos > 0)
6372                                 buf[--pos] = 0;
6373                         else
6374                                 status = INPUT_CANCEL;
6375                         break;
6377                 case KEY_ESC:
6378                         status = INPUT_CANCEL;
6379                         break;
6381                 default:
6382                         if (pos >= sizeof(buf)) {
6383                                 report("Input string too long");
6384                                 return NULL;
6385                         }
6387                         status = handler(data, buf, key);
6388                         if (status == INPUT_OK)
6389                                 buf[pos++] = (char) key;
6390                 }
6391         }
6393         /* Clear the status window */
6394         status_empty = FALSE;
6395         report("");
6397         if (status == INPUT_CANCEL)
6398                 return NULL;
6400         buf[pos++] = 0;
6402         return buf;
6405 static enum input_status
6406 prompt_yesno_handler(void *data, char *buf, int c)
6408         if (c == 'y' || c == 'Y')
6409                 return INPUT_STOP;
6410         if (c == 'n' || c == 'N')
6411                 return INPUT_CANCEL;
6412         return INPUT_SKIP;
6415 static bool
6416 prompt_yesno(const char *prompt)
6418         char prompt2[SIZEOF_STR];
6420         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6421                 return FALSE;
6423         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6426 static enum input_status
6427 read_prompt_handler(void *data, char *buf, int c)
6429         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6432 static char *
6433 read_prompt(const char *prompt)
6435         return prompt_input(prompt, read_prompt_handler, NULL);
6438 /*
6439  * Repository properties
6440  */
6442 static struct ref *refs = NULL;
6443 static size_t refs_alloc = 0;
6444 static size_t refs_size = 0;
6446 /* Id <-> ref store */
6447 static struct ref ***id_refs = NULL;
6448 static size_t id_refs_alloc = 0;
6449 static size_t id_refs_size = 0;
6451 static int
6452 compare_refs(const void *ref1_, const void *ref2_)
6454         const struct ref *ref1 = *(const struct ref **)ref1_;
6455         const struct ref *ref2 = *(const struct ref **)ref2_;
6457         if (ref1->tag != ref2->tag)
6458                 return ref2->tag - ref1->tag;
6459         if (ref1->ltag != ref2->ltag)
6460                 return ref2->ltag - ref2->ltag;
6461         if (ref1->head != ref2->head)
6462                 return ref2->head - ref1->head;
6463         if (ref1->tracked != ref2->tracked)
6464                 return ref2->tracked - ref1->tracked;
6465         if (ref1->remote != ref2->remote)
6466                 return ref2->remote - ref1->remote;
6467         return strcmp(ref1->name, ref2->name);
6470 static struct ref **
6471 get_refs(const char *id)
6473         struct ref ***tmp_id_refs;
6474         struct ref **ref_list = NULL;
6475         size_t ref_list_alloc = 0;
6476         size_t ref_list_size = 0;
6477         size_t i;
6479         for (i = 0; i < id_refs_size; i++)
6480                 if (!strcmp(id, id_refs[i][0]->id))
6481                         return id_refs[i];
6483         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6484                                     sizeof(*id_refs));
6485         if (!tmp_id_refs)
6486                 return NULL;
6488         id_refs = tmp_id_refs;
6490         for (i = 0; i < refs_size; i++) {
6491                 struct ref **tmp;
6493                 if (strcmp(id, refs[i].id))
6494                         continue;
6496                 tmp = realloc_items(ref_list, &ref_list_alloc,
6497                                     ref_list_size + 1, sizeof(*ref_list));
6498                 if (!tmp) {
6499                         if (ref_list)
6500                                 free(ref_list);
6501                         return NULL;
6502                 }
6504                 ref_list = tmp;
6505                 ref_list[ref_list_size] = &refs[i];
6506                 /* XXX: The properties of the commit chains ensures that we can
6507                  * safely modify the shared ref. The repo references will
6508                  * always be similar for the same id. */
6509                 ref_list[ref_list_size]->next = 1;
6511                 ref_list_size++;
6512         }
6514         if (ref_list) {
6515                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6516                 ref_list[ref_list_size - 1]->next = 0;
6517                 id_refs[id_refs_size++] = ref_list;
6518         }
6520         return ref_list;
6523 static int
6524 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6526         struct ref *ref;
6527         bool tag = FALSE;
6528         bool ltag = FALSE;
6529         bool remote = FALSE;
6530         bool tracked = FALSE;
6531         bool check_replace = FALSE;
6532         bool head = FALSE;
6534         if (!prefixcmp(name, "refs/tags/")) {
6535                 if (!suffixcmp(name, namelen, "^{}")) {
6536                         namelen -= 3;
6537                         name[namelen] = 0;
6538                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6539                                 check_replace = TRUE;
6540                 } else {
6541                         ltag = TRUE;
6542                 }
6544                 tag = TRUE;
6545                 namelen -= STRING_SIZE("refs/tags/");
6546                 name    += STRING_SIZE("refs/tags/");
6548         } else if (!prefixcmp(name, "refs/remotes/")) {
6549                 remote = TRUE;
6550                 namelen -= STRING_SIZE("refs/remotes/");
6551                 name    += STRING_SIZE("refs/remotes/");
6552                 tracked  = !strcmp(opt_remote, name);
6554         } else if (!prefixcmp(name, "refs/heads/")) {
6555                 namelen -= STRING_SIZE("refs/heads/");
6556                 name    += STRING_SIZE("refs/heads/");
6557                 head     = !strncmp(opt_head, name, namelen);
6559         } else if (!strcmp(name, "HEAD")) {
6560                 string_ncopy(opt_head_rev, id, idlen);
6561                 return OK;
6562         }
6564         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6565                 /* it's an annotated tag, replace the previous sha1 with the
6566                  * resolved commit id; relies on the fact git-ls-remote lists
6567                  * the commit id of an annotated tag right before the commit id
6568                  * it points to. */
6569                 refs[refs_size - 1].ltag = ltag;
6570                 string_copy_rev(refs[refs_size - 1].id, id);
6572                 return OK;
6573         }
6574         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6575         if (!refs)
6576                 return ERR;
6578         ref = &refs[refs_size++];
6579         ref->name = malloc(namelen + 1);
6580         if (!ref->name)
6581                 return ERR;
6583         strncpy(ref->name, name, namelen);
6584         ref->name[namelen] = 0;
6585         ref->head = head;
6586         ref->tag = tag;
6587         ref->ltag = ltag;
6588         ref->remote = remote;
6589         ref->tracked = tracked;
6590         string_copy_rev(ref->id, id);
6592         return OK;
6595 static int
6596 load_refs(void)
6598         static const char *ls_remote_argv[SIZEOF_ARG] = {
6599                 "git", "ls-remote", ".", NULL
6600         };
6601         static bool init = FALSE;
6603         if (!init) {
6604                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6605                 init = TRUE;
6606         }
6608         if (!*opt_git_dir)
6609                 return OK;
6611         while (refs_size > 0)
6612                 free(refs[--refs_size].name);
6613         while (id_refs_size > 0)
6614                 free(id_refs[--id_refs_size]);
6616         return run_io_load(ls_remote_argv, "\t", read_ref);
6619 static int
6620 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6622         if (!strcmp(name, "i18n.commitencoding"))
6623                 string_ncopy(opt_encoding, value, valuelen);
6625         if (!strcmp(name, "core.editor"))
6626                 string_ncopy(opt_editor, value, valuelen);
6628         /* branch.<head>.remote */
6629         if (*opt_head &&
6630             !strncmp(name, "branch.", 7) &&
6631             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6632             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6633                 string_ncopy(opt_remote, value, valuelen);
6635         if (*opt_head && *opt_remote &&
6636             !strncmp(name, "branch.", 7) &&
6637             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6638             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6639                 size_t from = strlen(opt_remote);
6641                 if (!prefixcmp(value, "refs/heads/")) {
6642                         value += STRING_SIZE("refs/heads/");
6643                         valuelen -= STRING_SIZE("refs/heads/");
6644                 }
6646                 if (!string_format_from(opt_remote, &from, "/%s", value))
6647                         opt_remote[0] = 0;
6648         }
6650         return OK;
6653 static int
6654 load_git_config(void)
6656         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6658         return run_io_load(config_list_argv, "=", read_repo_config_option);
6661 static int
6662 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6664         if (!opt_git_dir[0]) {
6665                 string_ncopy(opt_git_dir, name, namelen);
6667         } else if (opt_is_inside_work_tree == -1) {
6668                 /* This can be 3 different values depending on the
6669                  * version of git being used. If git-rev-parse does not
6670                  * understand --is-inside-work-tree it will simply echo
6671                  * the option else either "true" or "false" is printed.
6672                  * Default to true for the unknown case. */
6673                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6675         } else if (*name == '.') {
6676                 string_ncopy(opt_cdup, name, namelen);
6678         } else {
6679                 string_ncopy(opt_prefix, name, namelen);
6680         }
6682         return OK;
6685 static int
6686 load_repo_info(void)
6688         const char *head_argv[] = {
6689                 "git", "symbolic-ref", "HEAD", NULL
6690         };
6691         const char *rev_parse_argv[] = {
6692                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6693                         "--show-cdup", "--show-prefix", NULL
6694         };
6696         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6697                 chomp_string(opt_head);
6698                 if (!prefixcmp(opt_head, "refs/heads/")) {
6699                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6701                         memmove(opt_head, offset, strlen(offset) + 1);
6702                 }
6703         }
6705         return run_io_load(rev_parse_argv, "=", read_repo_info);
6709 /*
6710  * Main
6711  */
6713 static const char usage[] =
6714 "tig " TIG_VERSION " (" __DATE__ ")\n"
6715 "\n"
6716 "Usage: tig        [options] [revs] [--] [paths]\n"
6717 "   or: tig show   [options] [revs] [--] [paths]\n"
6718 "   or: tig blame  [rev] path\n"
6719 "   or: tig status\n"
6720 "   or: tig <      [git command output]\n"
6721 "\n"
6722 "Options:\n"
6723 "  -v, --version   Show version and exit\n"
6724 "  -h, --help      Show help message and exit";
6726 static void __NORETURN
6727 quit(int sig)
6729         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6730         if (cursed)
6731                 endwin();
6732         exit(0);
6735 static void __NORETURN
6736 die(const char *err, ...)
6738         va_list args;
6740         endwin();
6742         va_start(args, err);
6743         fputs("tig: ", stderr);
6744         vfprintf(stderr, err, args);
6745         fputs("\n", stderr);
6746         va_end(args);
6748         exit(1);
6751 static void
6752 warn(const char *msg, ...)
6754         va_list args;
6756         va_start(args, msg);
6757         fputs("tig warning: ", stderr);
6758         vfprintf(stderr, msg, args);
6759         fputs("\n", stderr);
6760         va_end(args);
6763 static enum request
6764 parse_options(int argc, const char *argv[])
6766         enum request request = REQ_VIEW_MAIN;
6767         const char *subcommand;
6768         bool seen_dashdash = FALSE;
6769         /* XXX: This is vulnerable to the user overriding options
6770          * required for the main view parser. */
6771         const char *custom_argv[SIZEOF_ARG] = {
6772                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6773                         "--topo-order", NULL
6774         };
6775         int i, j = 6;
6777         if (!isatty(STDIN_FILENO)) {
6778                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6779                 return REQ_VIEW_PAGER;
6780         }
6782         if (argc <= 1)
6783                 return REQ_NONE;
6785         subcommand = argv[1];
6786         if (!strcmp(subcommand, "status")) {
6787                 if (argc > 2)
6788                         warn("ignoring arguments after `%s'", subcommand);
6789                 return REQ_VIEW_STATUS;
6791         } else if (!strcmp(subcommand, "blame")) {
6792                 if (argc <= 2 || argc > 4)
6793                         die("invalid number of options to blame\n\n%s", usage);
6795                 i = 2;
6796                 if (argc == 4) {
6797                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6798                         i++;
6799                 }
6801                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6802                 return REQ_VIEW_BLAME;
6804         } else if (!strcmp(subcommand, "show")) {
6805                 request = REQ_VIEW_DIFF;
6807         } else {
6808                 subcommand = NULL;
6809         }
6811         if (subcommand) {
6812                 custom_argv[1] = subcommand;
6813                 j = 2;
6814         }
6816         for (i = 1 + !!subcommand; i < argc; i++) {
6817                 const char *opt = argv[i];
6819                 if (seen_dashdash || !strcmp(opt, "--")) {
6820                         seen_dashdash = TRUE;
6822                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6823                         printf("tig version %s\n", TIG_VERSION);
6824                         quit(0);
6826                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6827                         printf("%s\n", usage);
6828                         quit(0);
6829                 }
6831                 custom_argv[j++] = opt;
6832                 if (j >= ARRAY_SIZE(custom_argv))
6833                         die("command too long");
6834         }
6836         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6837                 die("Failed to format arguments"); 
6839         return request;
6842 int
6843 main(int argc, const char *argv[])
6845         enum request request = parse_options(argc, argv);
6846         struct view *view;
6847         size_t i;
6849         signal(SIGINT, quit);
6851         if (setlocale(LC_ALL, "")) {
6852                 char *codeset = nl_langinfo(CODESET);
6854                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6855         }
6857         if (load_repo_info() == ERR)
6858                 die("Failed to load repo info.");
6860         if (load_options() == ERR)
6861                 die("Failed to load user config.");
6863         if (load_git_config() == ERR)
6864                 die("Failed to load repo config.");
6866         /* Require a git repository unless when running in pager mode. */
6867         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6868                 die("Not a git repository");
6870         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6871                 opt_utf8 = FALSE;
6873         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6874                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6875                 if (opt_iconv == ICONV_NONE)
6876                         die("Failed to initialize character set conversion");
6877         }
6879         if (load_refs() == ERR)
6880                 die("Failed to load refs.");
6882         foreach_view (view, i)
6883                 argv_from_env(view->ops->argv, view->cmd_env);
6885         init_display();
6887         if (request != REQ_NONE)
6888                 open_view(NULL, request, OPEN_PREPARED);
6889         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6891         while (view_driver(display[current_view], request)) {
6892                 int key = get_input(0);
6894                 view = display[current_view];
6895                 request = get_keybinding(view->keymap, key);
6897                 /* Some low-level request handling. This keeps access to
6898                  * status_win restricted. */
6899                 switch (request) {
6900                 case REQ_PROMPT:
6901                 {
6902                         char *cmd = read_prompt(":");
6904                         if (cmd) {
6905                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6906                                 const char *argv[SIZEOF_ARG] = { "git" };
6907                                 int argc = 1;
6909                                 /* When running random commands, initially show the
6910                                  * command in the title. However, it maybe later be
6911                                  * overwritten if a commit line is selected. */
6912                                 string_ncopy(next->ref, cmd, strlen(cmd));
6914                                 if (!argv_from_string(argv, &argc, cmd)) {
6915                                         report("Too many arguments");
6916                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6917                                         report("Failed to format command");
6918                                 } else {
6919                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6920                                 }
6921                         }
6923                         request = REQ_NONE;
6924                         break;
6925                 }
6926                 case REQ_SEARCH:
6927                 case REQ_SEARCH_BACK:
6928                 {
6929                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6930                         char *search = read_prompt(prompt);
6932                         if (search)
6933                                 string_ncopy(opt_search, search, strlen(search));
6934                         else if (*opt_search)
6935                                 request = request == REQ_SEARCH ?
6936                                         REQ_FIND_NEXT :
6937                                         REQ_FIND_PREV;
6938                         else
6939                                 request = REQ_NONE;
6940                         break;
6941                 }
6942                 default:
6943                         break;
6944                 }
6945         }
6947         quit(0);
6949         return 0;