Code

Status view: report failures to update a file
[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 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 /*
164  * String helpers
165  */
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170         if (srclen > dstlen - 1)
171                 srclen = dstlen - 1;
173         strncpy(dst, src, srclen);
174         dst[srclen] = 0;
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183         string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static void
192 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
194         size_t size, pos;
196         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
197                 if (src[pos] == '\t') {
198                         size_t expanded = tabsize - (size % tabsize);
200                         if (expanded + size >= dstlen - 1)
201                                 expanded = dstlen - size - 1;
202                         memcpy(dst + size, "        ", expanded);
203                         size += expanded;
204                 } else {
205                         dst[size++] = src[pos];
206                 }
207         }
209         dst[size] = 0;
212 static char *
213 chomp_string(char *name)
215         int namelen;
217         while (isspace(*name))
218                 name++;
220         namelen = strlen(name) - 1;
221         while (namelen > 0 && isspace(name[namelen]))
222                 name[namelen--] = 0;
224         return name;
227 static bool
228 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
230         va_list args;
231         size_t pos = bufpos ? *bufpos : 0;
233         va_start(args, fmt);
234         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
235         va_end(args);
237         if (bufpos)
238                 *bufpos = pos;
240         return pos >= bufsize ? FALSE : TRUE;
243 #define string_format(buf, fmt, args...) \
244         string_nformat(buf, sizeof(buf), NULL, fmt, args)
246 #define string_format_from(buf, from, fmt, args...) \
247         string_nformat(buf, sizeof(buf), from, fmt, args)
249 static int
250 string_enum_compare(const char *str1, const char *str2, int len)
252         size_t i;
254 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
256         /* Diff-Header == DIFF_HEADER */
257         for (i = 0; i < len; i++) {
258                 if (toupper(str1[i]) == toupper(str2[i]))
259                         continue;
261                 if (string_enum_sep(str1[i]) &&
262                     string_enum_sep(str2[i]))
263                         continue;
265                 return str1[i] - str2[i];
266         }
268         return 0;
271 struct enum_map {
272         const char *name;
273         int namelen;
274         int value;
275 };
277 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
279 static bool
280 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
282         size_t namelen = strlen(name);
283         int i;
285         for (i = 0; i < map_size; i++)
286                 if (namelen == map[i].namelen &&
287                     !string_enum_compare(name, map[i].name, namelen)) {
288                         *value = map[i].value;
289                         return TRUE;
290                 }
292         return FALSE;
295 #define map_enum(attr, map, name) \
296         map_enum_do(map, ARRAY_SIZE(map), attr, name)
298 #define prefixcmp(str1, str2) \
299         strncmp(str1, str2, STRING_SIZE(str2))
301 static inline int
302 suffixcmp(const char *str, int slen, const char *suffix)
304         size_t len = slen >= 0 ? slen : strlen(str);
305         size_t suffixlen = strlen(suffix);
307         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
311 static bool
312 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
314         int valuelen;
316         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
317                 bool advance = cmd[valuelen] != 0;
319                 cmd[valuelen] = 0;
320                 argv[(*argc)++] = chomp_string(cmd);
321                 cmd = chomp_string(cmd + valuelen + advance);
322         }
324         if (*argc < SIZEOF_ARG)
325                 argv[*argc] = NULL;
326         return *argc < SIZEOF_ARG;
329 static void
330 argv_from_env(const char **argv, const char *name)
332         char *env = argv ? getenv(name) : NULL;
333         int argc = 0;
335         if (env && *env)
336                 env = strdup(env);
337         if (env && !argv_from_string(argv, &argc, env))
338                 die("Too many arguments in the `%s` environment variable", name);
342 /*
343  * Executing external commands.
344  */
346 enum io_type {
347         IO_FD,                  /* File descriptor based IO. */
348         IO_BG,                  /* Execute command in the background. */
349         IO_FG,                  /* Execute command with same std{in,out,err}. */
350         IO_RD,                  /* Read only fork+exec IO. */
351         IO_WR,                  /* Write only fork+exec IO. */
352         IO_AP,                  /* Append fork+exec output to file. */
353 };
355 struct io {
356         enum io_type type;      /* The requested type of pipe. */
357         const char *dir;        /* Directory from which to execute. */
358         pid_t pid;              /* Pipe for reading or writing. */
359         int pipe;               /* Pipe end for reading or writing. */
360         int error;              /* Error status. */
361         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
362         char *buf;              /* Read buffer. */
363         size_t bufalloc;        /* Allocated buffer size. */
364         size_t bufsize;         /* Buffer content size. */
365         char *bufpos;           /* Current buffer position. */
366         unsigned int eof:1;     /* Has end of file been reached. */
367 };
369 static void
370 reset_io(struct io *io)
372         io->pipe = -1;
373         io->pid = 0;
374         io->buf = io->bufpos = NULL;
375         io->bufalloc = io->bufsize = 0;
376         io->error = 0;
377         io->eof = 0;
380 static void
381 init_io(struct io *io, const char *dir, enum io_type type)
383         reset_io(io);
384         io->type = type;
385         io->dir = dir;
388 static bool
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390                 enum format_flags flags)
392         init_io(io, dir, IO_RD);
393         return format_argv(io->argv, argv, flags);
396 static bool
397 io_open(struct io *io, const char *name)
399         init_io(io, NULL, IO_FD);
400         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
401         if (io->pipe == -1)
402                 io->error = errno;
403         return io->pipe != -1;
406 static bool
407 kill_io(struct io *io)
409         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
412 static bool
413 done_io(struct io *io)
415         pid_t pid = io->pid;
417         if (io->pipe != -1)
418                 close(io->pipe);
419         free(io->buf);
420         reset_io(io);
422         while (pid > 0) {
423                 int status;
424                 pid_t waiting = waitpid(pid, &status, 0);
426                 if (waiting < 0) {
427                         if (errno == EINTR)
428                                 continue;
429                         report("waitpid failed (%s)", strerror(errno));
430                         return FALSE;
431                 }
433                 return waiting == pid &&
434                        !WIFSIGNALED(status) &&
435                        WIFEXITED(status) &&
436                        !WEXITSTATUS(status);
437         }
439         return TRUE;
442 static bool
443 start_io(struct io *io)
445         int pipefds[2] = { -1, -1 };
447         if (io->type == IO_FD)
448                 return TRUE;
450         if ((io->type == IO_RD || io->type == IO_WR) &&
451             pipe(pipefds) < 0)
452                 return FALSE;
453         else if (io->type == IO_AP)
454                 pipefds[1] = io->pipe;
456         if ((io->pid = fork())) {
457                 if (pipefds[!(io->type == IO_WR)] != -1)
458                         close(pipefds[!(io->type == IO_WR)]);
459                 if (io->pid != -1) {
460                         io->pipe = pipefds[!!(io->type == IO_WR)];
461                         return TRUE;
462                 }
464         } else {
465                 if (io->type != IO_FG) {
466                         int devnull = open("/dev/null", O_RDWR);
467                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
468                         int writefd = (io->type == IO_RD || io->type == IO_AP)
469                                                         ? pipefds[1] : devnull;
471                         dup2(readfd,  STDIN_FILENO);
472                         dup2(writefd, STDOUT_FILENO);
473                         dup2(devnull, STDERR_FILENO);
475                         close(devnull);
476                         if (pipefds[0] != -1)
477                                 close(pipefds[0]);
478                         if (pipefds[1] != -1)
479                                 close(pipefds[1]);
480                 }
482                 if (io->dir && *io->dir && chdir(io->dir) == -1)
483                         die("Failed to change directory: %s", strerror(errno));
485                 execvp(io->argv[0], (char *const*) io->argv);
486                 die("Failed to execute program: %s", strerror(errno));
487         }
489         if (pipefds[!!(io->type == IO_WR)] != -1)
490                 close(pipefds[!!(io->type == IO_WR)]);
491         return FALSE;
494 static bool
495 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
497         init_io(io, dir, type);
498         if (!format_argv(io->argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return start_io(io);
503 static int
504 run_io_do(struct io *io)
506         return start_io(io) && done_io(io);
509 static int
510 run_io_bg(const char **argv)
512         struct io io = {};
514         init_io(&io, NULL, IO_BG);
515         if (!format_argv(io.argv, argv, FORMAT_NONE))
516                 return FALSE;
517         return run_io_do(&io);
520 static bool
521 run_io_fg(const char **argv, const char *dir)
523         struct io io = {};
525         init_io(&io, dir, IO_FG);
526         if (!format_argv(io.argv, argv, FORMAT_NONE))
527                 return FALSE;
528         return run_io_do(&io);
531 static bool
532 run_io_append(const char **argv, enum format_flags flags, int fd)
534         struct io io = {};
536         init_io(&io, NULL, IO_AP);
537         io.pipe = fd;
538         if (format_argv(io.argv, argv, flags))
539                 return run_io_do(&io);
540         close(fd);
541         return FALSE;
544 static bool
545 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
547         return init_io_rd(io, argv, NULL, flags) && start_io(io);
550 static bool
551 io_eof(struct io *io)
553         return io->eof;
556 static int
557 io_error(struct io *io)
559         return io->error;
562 static char *
563 io_strerror(struct io *io)
565         return strerror(io->error);
568 static bool
569 io_can_read(struct io *io)
571         struct timeval tv = { 0, 500 };
572         fd_set fds;
574         FD_ZERO(&fds);
575         FD_SET(io->pipe, &fds);
577         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
580 static ssize_t
581 io_read(struct io *io, void *buf, size_t bufsize)
583         do {
584                 ssize_t readsize = read(io->pipe, buf, bufsize);
586                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
587                         continue;
588                 else if (readsize == -1)
589                         io->error = errno;
590                 else if (readsize == 0)
591                         io->eof = 1;
592                 return readsize;
593         } while (1);
596 static char *
597 io_get(struct io *io, int c, bool can_read)
599         char *eol;
600         ssize_t readsize;
602         if (!io->buf) {
603                 io->buf = io->bufpos = malloc(BUFSIZ);
604                 if (!io->buf)
605                         return NULL;
606                 io->bufalloc = BUFSIZ;
607                 io->bufsize = 0;
608         }
610         while (TRUE) {
611                 if (io->bufsize > 0) {
612                         eol = memchr(io->bufpos, c, io->bufsize);
613                         if (eol) {
614                                 char *line = io->bufpos;
616                                 *eol = 0;
617                                 io->bufpos = eol + 1;
618                                 io->bufsize -= io->bufpos - line;
619                                 return line;
620                         }
621                 }
623                 if (io_eof(io)) {
624                         if (io->bufsize) {
625                                 io->bufpos[io->bufsize] = 0;
626                                 io->bufsize = 0;
627                                 return io->bufpos;
628                         }
629                         return NULL;
630                 }
632                 if (!can_read)
633                         return NULL;
635                 if (io->bufsize > 0 && io->bufpos > io->buf)
636                         memmove(io->buf, io->bufpos, io->bufsize);
638                 io->bufpos = io->buf;
639                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
640                 if (io_error(io))
641                         return NULL;
642                 io->bufsize += readsize;
643         }
646 static bool
647 io_write(struct io *io, const void *buf, size_t bufsize)
649         size_t written = 0;
651         while (!io_error(io) && written < bufsize) {
652                 ssize_t size;
654                 size = write(io->pipe, buf + written, bufsize - written);
655                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
656                         continue;
657                 else if (size == -1)
658                         io->error = errno;
659                 else
660                         written += size;
661         }
663         return written == bufsize;
666 static bool
667 io_read_buf(struct io *io, char buf[], size_t bufsize)
669         bool error;
671         io->buf = io->bufpos = buf;
672         io->bufalloc = bufsize;
673         error = !io_get(io, '\n', TRUE) && io_error(io);
674         io->buf = NULL;
676         return done_io(io) || error;
679 static bool
680 run_io_buf(const char **argv, char buf[], size_t bufsize)
682         struct io io = {};
684         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
687 static int
688 io_load(struct io *io, const char *separators,
689         int (*read_property)(char *, size_t, char *, size_t))
691         char *name;
692         int state = OK;
694         if (!start_io(io))
695                 return ERR;
697         while (state == OK && (name = io_get(io, '\n', TRUE))) {
698                 char *value;
699                 size_t namelen;
700                 size_t valuelen;
702                 name = chomp_string(name);
703                 namelen = strcspn(name, separators);
705                 if (name[namelen]) {
706                         name[namelen] = 0;
707                         value = chomp_string(name + namelen + 1);
708                         valuelen = strlen(value);
710                 } else {
711                         value = "";
712                         valuelen = 0;
713                 }
715                 state = read_property(name, namelen, value, valuelen);
716         }
718         if (state != ERR && io_error(io))
719                 state = ERR;
720         done_io(io);
722         return state;
725 static int
726 run_io_load(const char **argv, const char *separators,
727             int (*read_property)(char *, size_t, char *, size_t))
729         struct io io = {};
731         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
732                 ? io_load(&io, separators, read_property) : ERR;
736 /*
737  * User requests
738  */
740 #define REQ_INFO \
741         /* XXX: Keep the view request first and in sync with views[]. */ \
742         REQ_GROUP("View switching") \
743         REQ_(VIEW_MAIN,         "Show main view"), \
744         REQ_(VIEW_DIFF,         "Show diff view"), \
745         REQ_(VIEW_LOG,          "Show log view"), \
746         REQ_(VIEW_TREE,         "Show tree view"), \
747         REQ_(VIEW_BLOB,         "Show blob view"), \
748         REQ_(VIEW_BLAME,        "Show blame view"), \
749         REQ_(VIEW_HELP,         "Show help page"), \
750         REQ_(VIEW_PAGER,        "Show pager view"), \
751         REQ_(VIEW_STATUS,       "Show status view"), \
752         REQ_(VIEW_STAGE,        "Show stage view"), \
753         \
754         REQ_GROUP("View manipulation") \
755         REQ_(ENTER,             "Enter current line and scroll"), \
756         REQ_(NEXT,              "Move to next"), \
757         REQ_(PREVIOUS,          "Move to previous"), \
758         REQ_(PARENT,            "Move to parent"), \
759         REQ_(VIEW_NEXT,         "Move focus to next view"), \
760         REQ_(REFRESH,           "Reload and refresh"), \
761         REQ_(MAXIMIZE,          "Maximize the current view"), \
762         REQ_(VIEW_CLOSE,        "Close the current view"), \
763         REQ_(QUIT,              "Close all views and quit"), \
764         \
765         REQ_GROUP("View specific requests") \
766         REQ_(STATUS_UPDATE,     "Update file status"), \
767         REQ_(STATUS_REVERT,     "Revert file changes"), \
768         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
769         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
770         \
771         REQ_GROUP("Cursor navigation") \
772         REQ_(MOVE_UP,           "Move cursor one line up"), \
773         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
774         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
775         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
776         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
777         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
778         \
779         REQ_GROUP("Scrolling") \
780         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
781         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
782         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
783         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
784         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
785         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
786         \
787         REQ_GROUP("Searching") \
788         REQ_(SEARCH,            "Search the view"), \
789         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
790         REQ_(FIND_NEXT,         "Find next search match"), \
791         REQ_(FIND_PREV,         "Find previous search match"), \
792         \
793         REQ_GROUP("Option manipulation") \
794         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
795         REQ_(TOGGLE_DATE,       "Toggle date display"), \
796         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
797         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
798         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
799         \
800         REQ_GROUP("Misc") \
801         REQ_(PROMPT,            "Bring up the prompt"), \
802         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
803         REQ_(SHOW_VERSION,      "Show version information"), \
804         REQ_(STOP_LOADING,      "Stop all loading views"), \
805         REQ_(EDIT,              "Open in editor"), \
806         REQ_(NONE,              "Do nothing")
809 /* User action requests. */
810 enum request {
811 #define REQ_GROUP(help)
812 #define REQ_(req, help) REQ_##req
814         /* Offset all requests to avoid conflicts with ncurses getch values. */
815         REQ_OFFSET = KEY_MAX + 1,
816         REQ_INFO
818 #undef  REQ_GROUP
819 #undef  REQ_
820 };
822 struct request_info {
823         enum request request;
824         const char *name;
825         int namelen;
826         const char *help;
827 };
829 static const struct request_info req_info[] = {
830 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
831 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
832         REQ_INFO
833 #undef  REQ_GROUP
834 #undef  REQ_
835 };
837 static enum request
838 get_request(const char *name)
840         int namelen = strlen(name);
841         int i;
843         for (i = 0; i < ARRAY_SIZE(req_info); i++)
844                 if (req_info[i].namelen == namelen &&
845                     !string_enum_compare(req_info[i].name, name, namelen))
846                         return req_info[i].request;
848         return REQ_NONE;
852 /*
853  * Options
854  */
856 /* Option and state variables. */
857 static bool opt_date                    = TRUE;
858 static bool opt_author                  = TRUE;
859 static bool opt_line_number             = FALSE;
860 static bool opt_line_graphics           = TRUE;
861 static bool opt_rev_graph               = FALSE;
862 static bool opt_show_refs               = TRUE;
863 static int opt_num_interval             = NUMBER_INTERVAL;
864 static int opt_tab_size                 = TAB_SIZE;
865 static int opt_author_cols              = AUTHOR_COLS-1;
866 static char opt_path[SIZEOF_STR]        = "";
867 static char opt_file[SIZEOF_STR]        = "";
868 static char opt_ref[SIZEOF_REF]         = "";
869 static char opt_head[SIZEOF_REF]        = "";
870 static char opt_head_rev[SIZEOF_REV]    = "";
871 static char opt_remote[SIZEOF_REF]      = "";
872 static char opt_encoding[20]            = "UTF-8";
873 static bool opt_utf8                    = TRUE;
874 static char opt_codeset[20]             = "UTF-8";
875 static iconv_t opt_iconv                = ICONV_NONE;
876 static char opt_search[SIZEOF_STR]      = "";
877 static char opt_cdup[SIZEOF_STR]        = "";
878 static char opt_prefix[SIZEOF_STR]      = "";
879 static char opt_git_dir[SIZEOF_STR]     = "";
880 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
881 static char opt_editor[SIZEOF_STR]      = "";
882 static FILE *opt_tty                    = NULL;
884 #define is_initial_commit()     (!*opt_head_rev)
885 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
888 /*
889  * Line-oriented content detection.
890  */
892 #define LINE_INFO \
893 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
894 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
895 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
896 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
897 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
898 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
899 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
900 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
901 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
902 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
905 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
906 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
907 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
908 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
909 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
910 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
911 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
912 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
913 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
914 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
915 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
916 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
917 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
918 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
919 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
920 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
921 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
922 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
923 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
924 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
925 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
926 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
927 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
928 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
929 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
930 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
931 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
932 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
933 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
934 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
935 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
936 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
937 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
938 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
939 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
940 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
941 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
942 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
943 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
944 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
945 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
946 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
947 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
949 enum line_type {
950 #define LINE(type, line, fg, bg, attr) \
951         LINE_##type
952         LINE_INFO,
953         LINE_NONE
954 #undef  LINE
955 };
957 struct line_info {
958         const char *name;       /* Option name. */
959         int namelen;            /* Size of option name. */
960         const char *line;       /* The start of line to match. */
961         int linelen;            /* Size of string to match. */
962         int fg, bg, attr;       /* Color and text attributes for the lines. */
963 };
965 static struct line_info line_info[] = {
966 #define LINE(type, line, fg, bg, attr) \
967         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
968         LINE_INFO
969 #undef  LINE
970 };
972 static enum line_type
973 get_line_type(const char *line)
975         int linelen = strlen(line);
976         enum line_type type;
978         for (type = 0; type < ARRAY_SIZE(line_info); type++)
979                 /* Case insensitive search matches Signed-off-by lines better. */
980                 if (linelen >= line_info[type].linelen &&
981                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
982                         return type;
984         return LINE_DEFAULT;
987 static inline int
988 get_line_attr(enum line_type type)
990         assert(type < ARRAY_SIZE(line_info));
991         return COLOR_PAIR(type) | line_info[type].attr;
994 static struct line_info *
995 get_line_info(const char *name)
997         size_t namelen = strlen(name);
998         enum line_type type;
1000         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1001                 if (namelen == line_info[type].namelen &&
1002                     !string_enum_compare(line_info[type].name, name, namelen))
1003                         return &line_info[type];
1005         return NULL;
1008 static void
1009 init_colors(void)
1011         int default_bg = line_info[LINE_DEFAULT].bg;
1012         int default_fg = line_info[LINE_DEFAULT].fg;
1013         enum line_type type;
1015         start_color();
1017         if (assume_default_colors(default_fg, default_bg) == ERR) {
1018                 default_bg = COLOR_BLACK;
1019                 default_fg = COLOR_WHITE;
1020         }
1022         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1023                 struct line_info *info = &line_info[type];
1024                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1025                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1027                 init_pair(type, fg, bg);
1028         }
1031 struct line {
1032         enum line_type type;
1034         /* State flags */
1035         unsigned int selected:1;
1036         unsigned int dirty:1;
1037         unsigned int cleareol:1;
1039         void *data;             /* User data */
1040 };
1043 /*
1044  * Keys
1045  */
1047 struct keybinding {
1048         int alias;
1049         enum request request;
1050 };
1052 static const struct keybinding default_keybindings[] = {
1053         /* View switching */
1054         { 'm',          REQ_VIEW_MAIN },
1055         { 'd',          REQ_VIEW_DIFF },
1056         { 'l',          REQ_VIEW_LOG },
1057         { 't',          REQ_VIEW_TREE },
1058         { 'f',          REQ_VIEW_BLOB },
1059         { 'B',          REQ_VIEW_BLAME },
1060         { 'p',          REQ_VIEW_PAGER },
1061         { 'h',          REQ_VIEW_HELP },
1062         { 'S',          REQ_VIEW_STATUS },
1063         { 'c',          REQ_VIEW_STAGE },
1065         /* View manipulation */
1066         { 'q',          REQ_VIEW_CLOSE },
1067         { KEY_TAB,      REQ_VIEW_NEXT },
1068         { KEY_RETURN,   REQ_ENTER },
1069         { KEY_UP,       REQ_PREVIOUS },
1070         { KEY_DOWN,     REQ_NEXT },
1071         { 'R',          REQ_REFRESH },
1072         { KEY_F(5),     REQ_REFRESH },
1073         { 'O',          REQ_MAXIMIZE },
1075         /* Cursor navigation */
1076         { 'k',          REQ_MOVE_UP },
1077         { 'j',          REQ_MOVE_DOWN },
1078         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1079         { KEY_END,      REQ_MOVE_LAST_LINE },
1080         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1081         { ' ',          REQ_MOVE_PAGE_DOWN },
1082         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1083         { 'b',          REQ_MOVE_PAGE_UP },
1084         { '-',          REQ_MOVE_PAGE_UP },
1086         /* Scrolling */
1087         { KEY_LEFT,     REQ_SCROLL_LEFT },
1088         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1089         { KEY_IC,       REQ_SCROLL_LINE_UP },
1090         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1091         { 'w',          REQ_SCROLL_PAGE_UP },
1092         { 's',          REQ_SCROLL_PAGE_DOWN },
1094         /* Searching */
1095         { '/',          REQ_SEARCH },
1096         { '?',          REQ_SEARCH_BACK },
1097         { 'n',          REQ_FIND_NEXT },
1098         { 'N',          REQ_FIND_PREV },
1100         /* Misc */
1101         { 'Q',          REQ_QUIT },
1102         { 'z',          REQ_STOP_LOADING },
1103         { 'v',          REQ_SHOW_VERSION },
1104         { 'r',          REQ_SCREEN_REDRAW },
1105         { '.',          REQ_TOGGLE_LINENO },
1106         { 'D',          REQ_TOGGLE_DATE },
1107         { 'A',          REQ_TOGGLE_AUTHOR },
1108         { 'g',          REQ_TOGGLE_REV_GRAPH },
1109         { 'F',          REQ_TOGGLE_REFS },
1110         { ':',          REQ_PROMPT },
1111         { 'u',          REQ_STATUS_UPDATE },
1112         { '!',          REQ_STATUS_REVERT },
1113         { 'M',          REQ_STATUS_MERGE },
1114         { '@',          REQ_STAGE_NEXT },
1115         { ',',          REQ_PARENT },
1116         { 'e',          REQ_EDIT },
1117 };
1119 #define KEYMAP_INFO \
1120         KEYMAP_(GENERIC), \
1121         KEYMAP_(MAIN), \
1122         KEYMAP_(DIFF), \
1123         KEYMAP_(LOG), \
1124         KEYMAP_(TREE), \
1125         KEYMAP_(BLOB), \
1126         KEYMAP_(BLAME), \
1127         KEYMAP_(PAGER), \
1128         KEYMAP_(HELP), \
1129         KEYMAP_(STATUS), \
1130         KEYMAP_(STAGE)
1132 enum keymap {
1133 #define KEYMAP_(name) KEYMAP_##name
1134         KEYMAP_INFO
1135 #undef  KEYMAP_
1136 };
1138 static const struct enum_map keymap_table[] = {
1139 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1140         KEYMAP_INFO
1141 #undef  KEYMAP_
1142 };
1144 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1146 struct keybinding_table {
1147         struct keybinding *data;
1148         size_t size;
1149 };
1151 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1153 static void
1154 add_keybinding(enum keymap keymap, enum request request, int key)
1156         struct keybinding_table *table = &keybindings[keymap];
1158         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1159         if (!table->data)
1160                 die("Failed to allocate keybinding");
1161         table->data[table->size].alias = key;
1162         table->data[table->size++].request = request;
1165 /* Looks for a key binding first in the given map, then in the generic map, and
1166  * lastly in the default keybindings. */
1167 static enum request
1168 get_keybinding(enum keymap keymap, int key)
1170         size_t i;
1172         for (i = 0; i < keybindings[keymap].size; i++)
1173                 if (keybindings[keymap].data[i].alias == key)
1174                         return keybindings[keymap].data[i].request;
1176         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1177                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1178                         return keybindings[KEYMAP_GENERIC].data[i].request;
1180         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1181                 if (default_keybindings[i].alias == key)
1182                         return default_keybindings[i].request;
1184         return (enum request) key;
1188 struct key {
1189         const char *name;
1190         int value;
1191 };
1193 static const struct key key_table[] = {
1194         { "Enter",      KEY_RETURN },
1195         { "Space",      ' ' },
1196         { "Backspace",  KEY_BACKSPACE },
1197         { "Tab",        KEY_TAB },
1198         { "Escape",     KEY_ESC },
1199         { "Left",       KEY_LEFT },
1200         { "Right",      KEY_RIGHT },
1201         { "Up",         KEY_UP },
1202         { "Down",       KEY_DOWN },
1203         { "Insert",     KEY_IC },
1204         { "Delete",     KEY_DC },
1205         { "Hash",       '#' },
1206         { "Home",       KEY_HOME },
1207         { "End",        KEY_END },
1208         { "PageUp",     KEY_PPAGE },
1209         { "PageDown",   KEY_NPAGE },
1210         { "F1",         KEY_F(1) },
1211         { "F2",         KEY_F(2) },
1212         { "F3",         KEY_F(3) },
1213         { "F4",         KEY_F(4) },
1214         { "F5",         KEY_F(5) },
1215         { "F6",         KEY_F(6) },
1216         { "F7",         KEY_F(7) },
1217         { "F8",         KEY_F(8) },
1218         { "F9",         KEY_F(9) },
1219         { "F10",        KEY_F(10) },
1220         { "F11",        KEY_F(11) },
1221         { "F12",        KEY_F(12) },
1222 };
1224 static int
1225 get_key_value(const char *name)
1227         int i;
1229         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1230                 if (!strcasecmp(key_table[i].name, name))
1231                         return key_table[i].value;
1233         if (strlen(name) == 1 && isprint(*name))
1234                 return (int) *name;
1236         return ERR;
1239 static const char *
1240 get_key_name(int key_value)
1242         static char key_char[] = "'X'";
1243         const char *seq = NULL;
1244         int key;
1246         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1247                 if (key_table[key].value == key_value)
1248                         seq = key_table[key].name;
1250         if (seq == NULL &&
1251             key_value < 127 &&
1252             isprint(key_value)) {
1253                 key_char[1] = (char) key_value;
1254                 seq = key_char;
1255         }
1257         return seq ? seq : "(no key)";
1260 static const char *
1261 get_key(enum request request)
1263         static char buf[BUFSIZ];
1264         size_t pos = 0;
1265         char *sep = "";
1266         int i;
1268         buf[pos] = 0;
1270         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1271                 const struct keybinding *keybinding = &default_keybindings[i];
1273                 if (keybinding->request != request)
1274                         continue;
1276                 if (!string_format_from(buf, &pos, "%s%s", sep,
1277                                         get_key_name(keybinding->alias)))
1278                         return "Too many keybindings!";
1279                 sep = ", ";
1280         }
1282         return buf;
1285 struct run_request {
1286         enum keymap keymap;
1287         int key;
1288         const char *argv[SIZEOF_ARG];
1289 };
1291 static struct run_request *run_request;
1292 static size_t run_requests;
1294 static enum request
1295 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1297         struct run_request *req;
1299         if (argc >= ARRAY_SIZE(req->argv) - 1)
1300                 return REQ_NONE;
1302         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1303         if (!req)
1304                 return REQ_NONE;
1306         run_request = req;
1307         req = &run_request[run_requests];
1308         req->keymap = keymap;
1309         req->key = key;
1310         req->argv[0] = NULL;
1312         if (!format_argv(req->argv, argv, FORMAT_NONE))
1313                 return REQ_NONE;
1315         return REQ_NONE + ++run_requests;
1318 static struct run_request *
1319 get_run_request(enum request request)
1321         if (request <= REQ_NONE)
1322                 return NULL;
1323         return &run_request[request - REQ_NONE - 1];
1326 static void
1327 add_builtin_run_requests(void)
1329         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1330         const char *gc[] = { "git", "gc", NULL };
1331         struct {
1332                 enum keymap keymap;
1333                 int key;
1334                 int argc;
1335                 const char **argv;
1336         } reqs[] = {
1337                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1338                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1339         };
1340         int i;
1342         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1343                 enum request req;
1345                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1346                 if (req != REQ_NONE)
1347                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1348         }
1351 /*
1352  * User config file handling.
1353  */
1355 static int   config_lineno;
1356 static bool  config_errors;
1357 static const char *config_msg;
1359 static const struct enum_map color_map[] = {
1360 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1361         COLOR_MAP(DEFAULT),
1362         COLOR_MAP(BLACK),
1363         COLOR_MAP(BLUE),
1364         COLOR_MAP(CYAN),
1365         COLOR_MAP(GREEN),
1366         COLOR_MAP(MAGENTA),
1367         COLOR_MAP(RED),
1368         COLOR_MAP(WHITE),
1369         COLOR_MAP(YELLOW),
1370 };
1372 static const struct enum_map attr_map[] = {
1373 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1374         ATTR_MAP(NORMAL),
1375         ATTR_MAP(BLINK),
1376         ATTR_MAP(BOLD),
1377         ATTR_MAP(DIM),
1378         ATTR_MAP(REVERSE),
1379         ATTR_MAP(STANDOUT),
1380         ATTR_MAP(UNDERLINE),
1381 };
1383 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1385 static int
1386 parse_int(int *opt, const char *arg, int min, int max)
1388         int value = atoi(arg);
1390         if (min <= value && value <= max) {
1391                 *opt = value;
1392                 return OK;
1393         }
1395         config_msg = "Integer value out of bound";
1396         return ERR;
1399 static bool
1400 set_color(int *color, const char *name)
1402         if (map_enum(color, color_map, name))
1403                 return TRUE;
1404         if (!prefixcmp(name, "color"))
1405                 return parse_int(color, name + 5, 0, 255) == OK;
1406         return FALSE;
1409 /* Wants: object fgcolor bgcolor [attribute] */
1410 static int
1411 option_color_command(int argc, const char *argv[])
1413         struct line_info *info;
1415         if (argc != 3 && argc != 4) {
1416                 config_msg = "Wrong number of arguments given to color command";
1417                 return ERR;
1418         }
1420         info = get_line_info(argv[0]);
1421         if (!info) {
1422                 static const struct enum_map obsolete[] = {
1423                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1424                         ENUM_MAP("main-date",   LINE_DATE),
1425                         ENUM_MAP("main-author", LINE_AUTHOR),
1426                 };
1427                 int index;
1429                 if (!map_enum(&index, obsolete, argv[0])) {
1430                         config_msg = "Unknown color name";
1431                         return ERR;
1432                 }
1433                 info = &line_info[index];
1434         }
1436         if (!set_color(&info->fg, argv[1]) ||
1437             !set_color(&info->bg, argv[2])) {
1438                 config_msg = "Unknown color";
1439                 return ERR;
1440         }
1442         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1443                 config_msg = "Unknown attribute";
1444                 return ERR;
1445         }
1447         return OK;
1450 static int parse_bool(bool *opt, const char *arg)
1452         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1453                 ? TRUE : FALSE;
1454         return OK;
1457 static int
1458 parse_string(char *opt, const char *arg, size_t optsize)
1460         int arglen = strlen(arg);
1462         switch (arg[0]) {
1463         case '\"':
1464         case '\'':
1465                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1466                         config_msg = "Unmatched quotation";
1467                         return ERR;
1468                 }
1469                 arg += 1; arglen -= 2;
1470         default:
1471                 string_ncopy_do(opt, optsize, arg, strlen(arg));
1472                 return OK;
1473         }
1476 /* Wants: name = value */
1477 static int
1478 option_set_command(int argc, const char *argv[])
1480         if (argc != 3) {
1481                 config_msg = "Wrong number of arguments given to set command";
1482                 return ERR;
1483         }
1485         if (strcmp(argv[1], "=")) {
1486                 config_msg = "No value assigned";
1487                 return ERR;
1488         }
1490         if (!strcmp(argv[0], "show-author"))
1491                 return parse_bool(&opt_author, argv[2]);
1493         if (!strcmp(argv[0], "show-date"))
1494                 return parse_bool(&opt_date, argv[2]);
1496         if (!strcmp(argv[0], "show-rev-graph"))
1497                 return parse_bool(&opt_rev_graph, argv[2]);
1499         if (!strcmp(argv[0], "show-refs"))
1500                 return parse_bool(&opt_show_refs, argv[2]);
1502         if (!strcmp(argv[0], "show-line-numbers"))
1503                 return parse_bool(&opt_line_number, argv[2]);
1505         if (!strcmp(argv[0], "line-graphics"))
1506                 return parse_bool(&opt_line_graphics, argv[2]);
1508         if (!strcmp(argv[0], "line-number-interval"))
1509                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1511         if (!strcmp(argv[0], "author-width"))
1512                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1514         if (!strcmp(argv[0], "tab-size"))
1515                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1517         if (!strcmp(argv[0], "commit-encoding"))
1518                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1520         config_msg = "Unknown variable name";
1521         return ERR;
1524 /* Wants: mode request key */
1525 static int
1526 option_bind_command(int argc, const char *argv[])
1528         enum request request;
1529         int keymap;
1530         int key;
1532         if (argc < 3) {
1533                 config_msg = "Wrong number of arguments given to bind command";
1534                 return ERR;
1535         }
1537         if (set_keymap(&keymap, argv[0]) == ERR) {
1538                 config_msg = "Unknown key map";
1539                 return ERR;
1540         }
1542         key = get_key_value(argv[1]);
1543         if (key == ERR) {
1544                 config_msg = "Unknown key";
1545                 return ERR;
1546         }
1548         request = get_request(argv[2]);
1549         if (request == REQ_NONE) {
1550                 static const struct enum_map obsolete[] = {
1551                         ENUM_MAP("cherry-pick",         REQ_NONE),
1552                         ENUM_MAP("screen-resize",       REQ_NONE),
1553                         ENUM_MAP("tree-parent",         REQ_PARENT),
1554                 };
1555                 int alias;
1557                 if (map_enum(&alias, obsolete, argv[2])) {
1558                         if (alias != REQ_NONE)
1559                                 add_keybinding(keymap, alias, key);
1560                         config_msg = "Obsolete request name";
1561                         return ERR;
1562                 }
1563         }
1564         if (request == REQ_NONE && *argv[2]++ == '!')
1565                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1566         if (request == REQ_NONE) {
1567                 config_msg = "Unknown request name";
1568                 return ERR;
1569         }
1571         add_keybinding(keymap, request, key);
1573         return OK;
1576 static int
1577 set_option(const char *opt, char *value)
1579         const char *argv[SIZEOF_ARG];
1580         int argc = 0;
1582         if (!argv_from_string(argv, &argc, value)) {
1583                 config_msg = "Too many option arguments";
1584                 return ERR;
1585         }
1587         if (!strcmp(opt, "color"))
1588                 return option_color_command(argc, argv);
1590         if (!strcmp(opt, "set"))
1591                 return option_set_command(argc, argv);
1593         if (!strcmp(opt, "bind"))
1594                 return option_bind_command(argc, argv);
1596         config_msg = "Unknown option command";
1597         return ERR;
1600 static int
1601 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1603         int status = OK;
1605         config_lineno++;
1606         config_msg = "Internal error";
1608         /* Check for comment markers, since read_properties() will
1609          * only ensure opt and value are split at first " \t". */
1610         optlen = strcspn(opt, "#");
1611         if (optlen == 0)
1612                 return OK;
1614         if (opt[optlen] != 0) {
1615                 config_msg = "No option value";
1616                 status = ERR;
1618         }  else {
1619                 /* Look for comment endings in the value. */
1620                 size_t len = strcspn(value, "#");
1622                 if (len < valuelen) {
1623                         valuelen = len;
1624                         value[valuelen] = 0;
1625                 }
1627                 status = set_option(opt, value);
1628         }
1630         if (status == ERR) {
1631                 warn("Error on line %d, near '%.*s': %s",
1632                      config_lineno, (int) optlen, opt, config_msg);
1633                 config_errors = TRUE;
1634         }
1636         /* Always keep going if errors are encountered. */
1637         return OK;
1640 static void
1641 load_option_file(const char *path)
1643         struct io io = {};
1645         /* It's OK that the file doesn't exist. */
1646         if (!io_open(&io, path))
1647                 return;
1649         config_lineno = 0;
1650         config_errors = FALSE;
1652         if (io_load(&io, " \t", read_option) == ERR ||
1653             config_errors == TRUE)
1654                 warn("Errors while loading %s.", path);
1657 static int
1658 load_options(void)
1660         const char *home = getenv("HOME");
1661         const char *tigrc_user = getenv("TIGRC_USER");
1662         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1663         char buf[SIZEOF_STR];
1665         add_builtin_run_requests();
1667         if (!tigrc_system)
1668                 tigrc_system = SYSCONFDIR "/tigrc";
1669         load_option_file(tigrc_system);
1671         if (!tigrc_user) {
1672                 if (!home || !string_format(buf, "%s/.tigrc", home))
1673                         return ERR;
1674                 tigrc_user = buf;
1675         }
1676         load_option_file(tigrc_user);
1678         return OK;
1682 /*
1683  * The viewer
1684  */
1686 struct view;
1687 struct view_ops;
1689 /* The display array of active views and the index of the current view. */
1690 static struct view *display[2];
1691 static unsigned int current_view;
1693 #define foreach_displayed_view(view, i) \
1694         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1696 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1698 /* Current head and commit ID */
1699 static char ref_blob[SIZEOF_REF]        = "";
1700 static char ref_commit[SIZEOF_REF]      = "HEAD";
1701 static char ref_head[SIZEOF_REF]        = "HEAD";
1703 struct view {
1704         const char *name;       /* View name */
1705         const char *cmd_env;    /* Command line set via environment */
1706         const char *id;         /* Points to either of ref_{head,commit,blob} */
1708         struct view_ops *ops;   /* View operations */
1710         enum keymap keymap;     /* What keymap does this view have */
1711         bool git_dir;           /* Whether the view requires a git directory. */
1713         char ref[SIZEOF_REF];   /* Hovered commit reference */
1714         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1716         int height, width;      /* The width and height of the main window */
1717         WINDOW *win;            /* The main window */
1718         WINDOW *title;          /* The title window living below the main window */
1720         /* Navigation */
1721         unsigned long offset;   /* Offset of the window top */
1722         unsigned long yoffset;  /* Offset from the window side. */
1723         unsigned long lineno;   /* Current line number */
1724         unsigned long p_offset; /* Previous offset of the window top */
1725         unsigned long p_yoffset;/* Previous offset from the window side */
1726         unsigned long p_lineno; /* Previous current line number */
1727         bool p_restore;         /* Should the previous position be restored. */
1729         /* Searching */
1730         char grep[SIZEOF_STR];  /* Search string */
1731         regex_t *regex;         /* Pre-compiled regexp */
1733         /* If non-NULL, points to the view that opened this view. If this view
1734          * is closed tig will switch back to the parent view. */
1735         struct view *parent;
1737         /* Buffering */
1738         size_t lines;           /* Total number of lines */
1739         struct line *line;      /* Line index */
1740         size_t line_alloc;      /* Total number of allocated lines */
1741         unsigned int digits;    /* Number of digits in the lines member. */
1743         /* Drawing */
1744         struct line *curline;   /* Line currently being drawn. */
1745         enum line_type curtype; /* Attribute currently used for drawing. */
1746         unsigned long col;      /* Column when drawing. */
1747         bool has_scrolled;      /* View was scrolled. */
1748         bool can_hscroll;       /* View can be scrolled horizontally. */
1750         /* Loading */
1751         struct io io;
1752         struct io *pipe;
1753         time_t start_time;
1754         time_t update_secs;
1755 };
1757 struct view_ops {
1758         /* What type of content being displayed. Used in the title bar. */
1759         const char *type;
1760         /* Default command arguments. */
1761         const char **argv;
1762         /* Open and reads in all view content. */
1763         bool (*open)(struct view *view);
1764         /* Read one line; updates view->line. */
1765         bool (*read)(struct view *view, char *data);
1766         /* Draw one line; @lineno must be < view->height. */
1767         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1768         /* Depending on view handle a special requests. */
1769         enum request (*request)(struct view *view, enum request request, struct line *line);
1770         /* Search for regexp in a line. */
1771         bool (*grep)(struct view *view, struct line *line);
1772         /* Select line */
1773         void (*select)(struct view *view, struct line *line);
1774 };
1776 static struct view_ops blame_ops;
1777 static struct view_ops blob_ops;
1778 static struct view_ops diff_ops;
1779 static struct view_ops help_ops;
1780 static struct view_ops log_ops;
1781 static struct view_ops main_ops;
1782 static struct view_ops pager_ops;
1783 static struct view_ops stage_ops;
1784 static struct view_ops status_ops;
1785 static struct view_ops tree_ops;
1787 #define VIEW_STR(name, env, ref, ops, map, git) \
1788         { name, #env, ref, ops, map, git }
1790 #define VIEW_(id, name, ops, git, ref) \
1791         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1794 static struct view views[] = {
1795         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1796         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1797         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1798         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1799         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1800         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1801         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1802         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1803         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1804         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1805 };
1807 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1808 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1810 #define foreach_view(view, i) \
1811         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1813 #define view_is_displayed(view) \
1814         (view == display[0] || view == display[1])
1817 enum line_graphic {
1818         LINE_GRAPHIC_VLINE
1819 };
1821 static int line_graphics[] = {
1822         /* LINE_GRAPHIC_VLINE: */ '|'
1823 };
1825 static inline void
1826 set_view_attr(struct view *view, enum line_type type)
1828         if (!view->curline->selected && view->curtype != type) {
1829                 wattrset(view->win, get_line_attr(type));
1830                 wchgat(view->win, -1, 0, type, NULL);
1831                 view->curtype = type;
1832         }
1835 static int
1836 draw_chars(struct view *view, enum line_type type, const char *string,
1837            int max_len, bool use_tilde)
1839         int len = 0;
1840         int col = 0;
1841         int trimmed = FALSE;
1842         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1844         if (max_len <= 0)
1845                 return 0;
1847         if (opt_utf8) {
1848                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1849         } else {
1850                 col = len = strlen(string);
1851                 if (len > max_len) {
1852                         if (use_tilde) {
1853                                 max_len -= 1;
1854                         }
1855                         col = len = max_len;
1856                         trimmed = TRUE;
1857                 }
1858         }
1860         set_view_attr(view, type);
1861         if (len > 0)
1862                 waddnstr(view->win, string, len);
1863         if (trimmed && use_tilde) {
1864                 set_view_attr(view, LINE_DELIMITER);
1865                 waddch(view->win, '~');
1866                 col++;
1867         }
1869         if (view->col + col >= view->width + view->yoffset)
1870                 view->can_hscroll = TRUE;
1872         return col;
1875 static int
1876 draw_space(struct view *view, enum line_type type, int max, int spaces)
1878         static char space[] = "                    ";
1879         int col = 0;
1881         spaces = MIN(max, spaces);
1883         while (spaces > 0) {
1884                 int len = MIN(spaces, sizeof(space) - 1);
1886                 col += draw_chars(view, type, space, spaces, FALSE);
1887                 spaces -= len;
1888         }
1890         return col;
1893 static bool
1894 draw_lineno(struct view *view, unsigned int lineno)
1896         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1897         char number[10];
1898         int digits3 = view->digits < 3 ? 3 : view->digits;
1899         int max_number = MIN(digits3, STRING_SIZE(number));
1900         int max = view->width - view->col;
1901         int col;
1903         if (max < max_number)
1904                 max_number = max;
1906         lineno += view->offset + 1;
1907         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1908                 static char fmt[] = "%1ld";
1910                 if (view->digits <= 9)
1911                         fmt[1] = '0' + digits3;
1913                 if (!string_format(number, fmt, lineno))
1914                         number[0] = 0;
1915                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1916         } else {
1917                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1918         }
1920         if (col < max && skip <= col) {
1921                 set_view_attr(view, LINE_DEFAULT);
1922                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1923         }
1924         col++;
1926         view->col += col;
1927         if (col < max && skip <= col)
1928                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1929         view->col++;
1931         return view->width + view->yoffset <= view->col;
1934 static bool
1935 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1937         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1938         return view->width - view->col <= 0;
1941 static bool
1942 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1944         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1945         int max = view->width - view->col;
1946         int i;
1948         if (max < size)
1949                 size = max;
1951         set_view_attr(view, type);
1952         /* Using waddch() instead of waddnstr() ensures that
1953          * they'll be rendered correctly for the cursor line. */
1954         for (i = skip; i < size; i++)
1955                 waddch(view->win, graphic[i]);
1957         view->col += size;
1958         if (size < max && skip <= size)
1959                 waddch(view->win, ' ');
1960         view->col++;
1962         return view->width - view->col <= 0;
1965 static bool
1966 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1968         int max = MIN(view->width - view->col, len);
1969         int col;
1971         if (text)
1972                 col = draw_chars(view, type, text, max - 1, trim);
1973         else
1974                 col = draw_space(view, type, max - 1, max - 1);
1976         view->col += col;
1977         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1978         return view->width + view->yoffset <= view->col;
1981 static bool
1982 draw_date(struct view *view, struct tm *time)
1984         char buf[DATE_COLS];
1985         char *date;
1986         int timelen = 0;
1988         if (time)
1989                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1990         date = timelen ? buf : NULL;
1992         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1995 static bool
1996 draw_author(struct view *view, const char *author)
1998         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2000         if (!trim) {
2001                 static char initials[10];
2002                 size_t pos;
2004 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2006                 memset(initials, 0, sizeof(initials));
2007                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2008                         while (is_initial_sep(*author))
2009                                 author++;
2010                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2011                         while (*author && !is_initial_sep(author[1]))
2012                                 author++;
2013                 }
2015                 author = initials;
2016         }
2018         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2021 static bool
2022 draw_mode(struct view *view, mode_t mode)
2024         static const char dir_mode[]    = "drwxr-xr-x";
2025         static const char link_mode[]   = "lrwxrwxrwx";
2026         static const char exe_mode[]    = "-rwxr-xr-x";
2027         static const char file_mode[]   = "-rw-r--r--";
2028         const char *str;
2030         if (S_ISDIR(mode))
2031                 str = dir_mode;
2032         else if (S_ISLNK(mode))
2033                 str = link_mode;
2034         else if (mode & S_IXUSR)
2035                 str = exe_mode;
2036         else
2037                 str = file_mode;
2039         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2042 static bool
2043 draw_view_line(struct view *view, unsigned int lineno)
2045         struct line *line;
2046         bool selected = (view->offset + lineno == view->lineno);
2048         assert(view_is_displayed(view));
2050         if (view->offset + lineno >= view->lines)
2051                 return FALSE;
2053         line = &view->line[view->offset + lineno];
2055         wmove(view->win, lineno, 0);
2056         if (line->cleareol)
2057                 wclrtoeol(view->win);
2058         view->col = 0;
2059         view->curline = line;
2060         view->curtype = LINE_NONE;
2061         line->selected = FALSE;
2062         line->dirty = line->cleareol = 0;
2064         if (selected) {
2065                 set_view_attr(view, LINE_CURSOR);
2066                 line->selected = TRUE;
2067                 view->ops->select(view, line);
2068         }
2070         return view->ops->draw(view, line, lineno);
2073 static void
2074 redraw_view_dirty(struct view *view)
2076         bool dirty = FALSE;
2077         int lineno;
2079         for (lineno = 0; lineno < view->height; lineno++) {
2080                 if (view->offset + lineno >= view->lines)
2081                         break;
2082                 if (!view->line[view->offset + lineno].dirty)
2083                         continue;
2084                 dirty = TRUE;
2085                 if (!draw_view_line(view, lineno))
2086                         break;
2087         }
2089         if (!dirty)
2090                 return;
2091         wnoutrefresh(view->win);
2094 static void
2095 redraw_view_from(struct view *view, int lineno)
2097         assert(0 <= lineno && lineno < view->height);
2099         if (lineno == 0)
2100                 view->can_hscroll = FALSE;
2102         for (; lineno < view->height; lineno++) {
2103                 if (!draw_view_line(view, lineno))
2104                         break;
2105         }
2107         wnoutrefresh(view->win);
2110 static void
2111 redraw_view(struct view *view)
2113         werase(view->win);
2114         redraw_view_from(view, 0);
2118 static void
2119 update_view_title(struct view *view)
2121         char buf[SIZEOF_STR];
2122         char state[SIZEOF_STR];
2123         size_t bufpos = 0, statelen = 0;
2125         assert(view_is_displayed(view));
2127         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2128                 unsigned int view_lines = view->offset + view->height;
2129                 unsigned int lines = view->lines
2130                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2131                                    : 0;
2133                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2134                                    view->ops->type,
2135                                    view->lineno + 1,
2136                                    view->lines,
2137                                    lines);
2139         }
2141         if (view->pipe) {
2142                 time_t secs = time(NULL) - view->start_time;
2144                 /* Three git seconds are a long time ... */
2145                 if (secs > 2)
2146                         string_format_from(state, &statelen, " loading %lds", secs);
2147         }
2149         string_format_from(buf, &bufpos, "[%s]", view->name);
2150         if (*view->ref && bufpos < view->width) {
2151                 size_t refsize = strlen(view->ref);
2152                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2154                 if (minsize < view->width)
2155                         refsize = view->width - minsize + 7;
2156                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2157         }
2159         if (statelen && bufpos < view->width) {
2160                 string_format_from(buf, &bufpos, "%s", state);
2161         }
2163         if (view == display[current_view])
2164                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2165         else
2166                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2168         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2169         wclrtoeol(view->title);
2170         wnoutrefresh(view->title);
2173 static void
2174 resize_display(void)
2176         int offset, i;
2177         struct view *base = display[0];
2178         struct view *view = display[1] ? display[1] : display[0];
2180         /* Setup window dimensions */
2182         getmaxyx(stdscr, base->height, base->width);
2184         /* Make room for the status window. */
2185         base->height -= 1;
2187         if (view != base) {
2188                 /* Horizontal split. */
2189                 view->width   = base->width;
2190                 view->height  = SCALE_SPLIT_VIEW(base->height);
2191                 base->height -= view->height;
2193                 /* Make room for the title bar. */
2194                 view->height -= 1;
2195         }
2197         /* Make room for the title bar. */
2198         base->height -= 1;
2200         offset = 0;
2202         foreach_displayed_view (view, i) {
2203                 if (!view->win) {
2204                         view->win = newwin(view->height, 0, offset, 0);
2205                         if (!view->win)
2206                                 die("Failed to create %s view", view->name);
2208                         scrollok(view->win, FALSE);
2210                         view->title = newwin(1, 0, offset + view->height, 0);
2211                         if (!view->title)
2212                                 die("Failed to create title window");
2214                 } else {
2215                         wresize(view->win, view->height, view->width);
2216                         mvwin(view->win,   offset, 0);
2217                         mvwin(view->title, offset + view->height, 0);
2218                 }
2220                 offset += view->height + 1;
2221         }
2224 static void
2225 redraw_display(bool clear)
2227         struct view *view;
2228         int i;
2230         foreach_displayed_view (view, i) {
2231                 if (clear)
2232                         wclear(view->win);
2233                 redraw_view(view);
2234                 update_view_title(view);
2235         }
2238 static void
2239 toggle_view_option(bool *option, const char *help)
2241         *option = !*option;
2242         redraw_display(FALSE);
2243         report("%sabling %s", *option ? "En" : "Dis", help);
2246 static void
2247 maximize_view(struct view *view)
2249         memset(display, 0, sizeof(display));
2250         current_view = 0;
2251         display[current_view] = view;
2252         resize_display();
2253         redraw_display(FALSE);
2254         report("");
2258 /*
2259  * Navigation
2260  */
2262 static bool
2263 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2265         if (lineno >= view->lines)
2266                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2268         if (offset > lineno || offset + view->height <= lineno) {
2269                 unsigned long half = view->height / 2;
2271                 if (lineno > half)
2272                         offset = lineno - half;
2273                 else
2274                         offset = 0;
2275         }
2277         if (offset != view->offset || lineno != view->lineno) {
2278                 view->offset = offset;
2279                 view->lineno = lineno;
2280                 return TRUE;
2281         }
2283         return FALSE;
2286 /* Scrolling backend */
2287 static void
2288 do_scroll_view(struct view *view, int lines)
2290         bool redraw_current_line = FALSE;
2292         /* The rendering expects the new offset. */
2293         view->offset += lines;
2295         assert(0 <= view->offset && view->offset < view->lines);
2296         assert(lines);
2298         /* Move current line into the view. */
2299         if (view->lineno < view->offset) {
2300                 view->lineno = view->offset;
2301                 redraw_current_line = TRUE;
2302         } else if (view->lineno >= view->offset + view->height) {
2303                 view->lineno = view->offset + view->height - 1;
2304                 redraw_current_line = TRUE;
2305         }
2307         assert(view->offset <= view->lineno && view->lineno < view->lines);
2309         /* Redraw the whole screen if scrolling is pointless. */
2310         if (view->height < ABS(lines)) {
2311                 redraw_view(view);
2313         } else {
2314                 int line = lines > 0 ? view->height - lines : 0;
2315                 int end = line + ABS(lines);
2317                 scrollok(view->win, TRUE);
2318                 wscrl(view->win, lines);
2319                 scrollok(view->win, FALSE);
2321                 while (line < end && draw_view_line(view, line))
2322                         line++;
2324                 if (redraw_current_line)
2325                         draw_view_line(view, view->lineno - view->offset);
2326                 wnoutrefresh(view->win);
2327         }
2329         view->has_scrolled = TRUE;
2330         report("");
2333 /* Scroll frontend */
2334 static void
2335 scroll_view(struct view *view, enum request request)
2337         int lines = 1;
2339         assert(view_is_displayed(view));
2341         switch (request) {
2342         case REQ_SCROLL_LEFT:
2343                 if (view->yoffset == 0) {
2344                         report("Cannot scroll beyond the first column");
2345                         return;
2346                 }
2347                 if (view->yoffset <= SCROLL_INTERVAL)
2348                         view->yoffset = 0;
2349                 else
2350                         view->yoffset -= SCROLL_INTERVAL;
2351                 redraw_view_from(view, 0);
2352                 report("");
2353                 return;
2354         case REQ_SCROLL_RIGHT:
2355                 if (!view->can_hscroll) {
2356                         report("Cannot scroll beyond the last column");
2357                         return;
2358                 }
2359                 view->yoffset += SCROLL_INTERVAL;
2360                 redraw_view(view);
2361                 report("");
2362                 return;
2363         case REQ_SCROLL_PAGE_DOWN:
2364                 lines = view->height;
2365         case REQ_SCROLL_LINE_DOWN:
2366                 if (view->offset + lines > view->lines)
2367                         lines = view->lines - view->offset;
2369                 if (lines == 0 || view->offset + view->height >= view->lines) {
2370                         report("Cannot scroll beyond the last line");
2371                         return;
2372                 }
2373                 break;
2375         case REQ_SCROLL_PAGE_UP:
2376                 lines = view->height;
2377         case REQ_SCROLL_LINE_UP:
2378                 if (lines > view->offset)
2379                         lines = view->offset;
2381                 if (lines == 0) {
2382                         report("Cannot scroll beyond the first line");
2383                         return;
2384                 }
2386                 lines = -lines;
2387                 break;
2389         default:
2390                 die("request %d not handled in switch", request);
2391         }
2393         do_scroll_view(view, lines);
2396 /* Cursor moving */
2397 static void
2398 move_view(struct view *view, enum request request)
2400         int scroll_steps = 0;
2401         int steps;
2403         switch (request) {
2404         case REQ_MOVE_FIRST_LINE:
2405                 steps = -view->lineno;
2406                 break;
2408         case REQ_MOVE_LAST_LINE:
2409                 steps = view->lines - view->lineno - 1;
2410                 break;
2412         case REQ_MOVE_PAGE_UP:
2413                 steps = view->height > view->lineno
2414                       ? -view->lineno : -view->height;
2415                 break;
2417         case REQ_MOVE_PAGE_DOWN:
2418                 steps = view->lineno + view->height >= view->lines
2419                       ? view->lines - view->lineno - 1 : view->height;
2420                 break;
2422         case REQ_MOVE_UP:
2423                 steps = -1;
2424                 break;
2426         case REQ_MOVE_DOWN:
2427                 steps = 1;
2428                 break;
2430         default:
2431                 die("request %d not handled in switch", request);
2432         }
2434         if (steps <= 0 && view->lineno == 0) {
2435                 report("Cannot move beyond the first line");
2436                 return;
2438         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2439                 report("Cannot move beyond the last line");
2440                 return;
2441         }
2443         /* Move the current line */
2444         view->lineno += steps;
2445         assert(0 <= view->lineno && view->lineno < view->lines);
2447         /* Check whether the view needs to be scrolled */
2448         if (view->lineno < view->offset ||
2449             view->lineno >= view->offset + view->height) {
2450                 scroll_steps = steps;
2451                 if (steps < 0 && -steps > view->offset) {
2452                         scroll_steps = -view->offset;
2454                 } else if (steps > 0) {
2455                         if (view->lineno == view->lines - 1 &&
2456                             view->lines > view->height) {
2457                                 scroll_steps = view->lines - view->offset - 1;
2458                                 if (scroll_steps >= view->height)
2459                                         scroll_steps -= view->height - 1;
2460                         }
2461                 }
2462         }
2464         if (!view_is_displayed(view)) {
2465                 view->offset += scroll_steps;
2466                 assert(0 <= view->offset && view->offset < view->lines);
2467                 view->ops->select(view, &view->line[view->lineno]);
2468                 return;
2469         }
2471         /* Repaint the old "current" line if we be scrolling */
2472         if (ABS(steps) < view->height)
2473                 draw_view_line(view, view->lineno - steps - view->offset);
2475         if (scroll_steps) {
2476                 do_scroll_view(view, scroll_steps);
2477                 return;
2478         }
2480         /* Draw the current line */
2481         draw_view_line(view, view->lineno - view->offset);
2483         wnoutrefresh(view->win);
2484         report("");
2488 /*
2489  * Searching
2490  */
2492 static void search_view(struct view *view, enum request request);
2494 static void
2495 select_view_line(struct view *view, unsigned long lineno)
2497         unsigned long old_lineno = view->lineno;
2498         unsigned long old_offset = view->offset;
2500         if (goto_view_line(view, view->offset, lineno)) {
2501                 if (view_is_displayed(view)) {
2502                         if (old_offset != view->offset) {
2503                                 redraw_view(view);
2504                         } else {
2505                                 draw_view_line(view, old_lineno - view->offset);
2506                                 draw_view_line(view, view->lineno - view->offset);
2507                                 wnoutrefresh(view->win);
2508                         }
2509                 } else {
2510                         view->ops->select(view, &view->line[view->lineno]);
2511                 }
2512         }
2515 static void
2516 find_next(struct view *view, enum request request)
2518         unsigned long lineno = view->lineno;
2519         int direction;
2521         if (!*view->grep) {
2522                 if (!*opt_search)
2523                         report("No previous search");
2524                 else
2525                         search_view(view, request);
2526                 return;
2527         }
2529         switch (request) {
2530         case REQ_SEARCH:
2531         case REQ_FIND_NEXT:
2532                 direction = 1;
2533                 break;
2535         case REQ_SEARCH_BACK:
2536         case REQ_FIND_PREV:
2537                 direction = -1;
2538                 break;
2540         default:
2541                 return;
2542         }
2544         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2545                 lineno += direction;
2547         /* Note, lineno is unsigned long so will wrap around in which case it
2548          * will become bigger than view->lines. */
2549         for (; lineno < view->lines; lineno += direction) {
2550                 if (view->ops->grep(view, &view->line[lineno])) {
2551                         select_view_line(view, lineno);
2552                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2553                         return;
2554                 }
2555         }
2557         report("No match found for '%s'", view->grep);
2560 static void
2561 search_view(struct view *view, enum request request)
2563         int regex_err;
2565         if (view->regex) {
2566                 regfree(view->regex);
2567                 *view->grep = 0;
2568         } else {
2569                 view->regex = calloc(1, sizeof(*view->regex));
2570                 if (!view->regex)
2571                         return;
2572         }
2574         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2575         if (regex_err != 0) {
2576                 char buf[SIZEOF_STR] = "unknown error";
2578                 regerror(regex_err, view->regex, buf, sizeof(buf));
2579                 report("Search failed: %s", buf);
2580                 return;
2581         }
2583         string_copy(view->grep, opt_search);
2585         find_next(view, request);
2588 /*
2589  * Incremental updating
2590  */
2592 static void
2593 reset_view(struct view *view)
2595         int i;
2597         for (i = 0; i < view->lines; i++)
2598                 free(view->line[i].data);
2599         free(view->line);
2601         view->p_offset = view->offset;
2602         view->p_yoffset = view->yoffset;
2603         view->p_lineno = view->lineno;
2605         view->line = NULL;
2606         view->offset = 0;
2607         view->yoffset = 0;
2608         view->lines  = 0;
2609         view->lineno = 0;
2610         view->line_alloc = 0;
2611         view->vid[0] = 0;
2612         view->update_secs = 0;
2615 static void
2616 free_argv(const char *argv[])
2618         int argc;
2620         for (argc = 0; argv[argc]; argc++)
2621                 free((void *) argv[argc]);
2624 static bool
2625 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2627         char buf[SIZEOF_STR];
2628         int argc;
2629         bool noreplace = flags == FORMAT_NONE;
2631         free_argv(dst_argv);
2633         for (argc = 0; src_argv[argc]; argc++) {
2634                 const char *arg = src_argv[argc];
2635                 size_t bufpos = 0;
2637                 while (arg) {
2638                         char *next = strstr(arg, "%(");
2639                         int len = next - arg;
2640                         const char *value;
2642                         if (!next || noreplace) {
2643                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2644                                         noreplace = TRUE;
2645                                 len = strlen(arg);
2646                                 value = "";
2648                         } else if (!prefixcmp(next, "%(directory)")) {
2649                                 value = opt_path;
2651                         } else if (!prefixcmp(next, "%(file)")) {
2652                                 value = opt_file;
2654                         } else if (!prefixcmp(next, "%(ref)")) {
2655                                 value = *opt_ref ? opt_ref : "HEAD";
2657                         } else if (!prefixcmp(next, "%(head)")) {
2658                                 value = ref_head;
2660                         } else if (!prefixcmp(next, "%(commit)")) {
2661                                 value = ref_commit;
2663                         } else if (!prefixcmp(next, "%(blob)")) {
2664                                 value = ref_blob;
2666                         } else {
2667                                 report("Unknown replacement: `%s`", next);
2668                                 return FALSE;
2669                         }
2671                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2672                                 return FALSE;
2674                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2675                 }
2677                 dst_argv[argc] = strdup(buf);
2678                 if (!dst_argv[argc])
2679                         break;
2680         }
2682         dst_argv[argc] = NULL;
2684         return src_argv[argc] == NULL;
2687 static bool
2688 restore_view_position(struct view *view)
2690         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2691                 return FALSE;
2693         /* Changing the view position cancels the restoring. */
2694         /* FIXME: Changing back to the first line is not detected. */
2695         if (view->offset != 0 || view->lineno != 0) {
2696                 view->p_restore = FALSE;
2697                 return FALSE;
2698         }
2700         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2701             view_is_displayed(view))
2702                 werase(view->win);
2704         view->yoffset = view->p_yoffset;
2705         view->p_restore = FALSE;
2707         return TRUE;
2710 static void
2711 end_update(struct view *view, bool force)
2713         if (!view->pipe)
2714                 return;
2715         while (!view->ops->read(view, NULL))
2716                 if (!force)
2717                         return;
2718         set_nonblocking_input(FALSE);
2719         if (force)
2720                 kill_io(view->pipe);
2721         done_io(view->pipe);
2722         view->pipe = NULL;
2725 static void
2726 setup_update(struct view *view, const char *vid)
2728         set_nonblocking_input(TRUE);
2729         reset_view(view);
2730         string_copy_rev(view->vid, vid);
2731         view->pipe = &view->io;
2732         view->start_time = time(NULL);
2735 static bool
2736 prepare_update(struct view *view, const char *argv[], const char *dir,
2737                enum format_flags flags)
2739         if (view->pipe)
2740                 end_update(view, TRUE);
2741         return init_io_rd(&view->io, argv, dir, flags);
2744 static bool
2745 prepare_update_file(struct view *view, const char *name)
2747         if (view->pipe)
2748                 end_update(view, TRUE);
2749         return io_open(&view->io, name);
2752 static bool
2753 begin_update(struct view *view, bool refresh)
2755         if (view->pipe)
2756                 end_update(view, TRUE);
2758         if (refresh) {
2759                 if (!start_io(&view->io))
2760                         return FALSE;
2762         } else {
2763                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2764                         opt_path[0] = 0;
2766                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2767                         return FALSE;
2769                 /* Put the current ref_* value to the view title ref
2770                  * member. This is needed by the blob view. Most other
2771                  * views sets it automatically after loading because the
2772                  * first line is a commit line. */
2773                 string_copy_rev(view->ref, view->id);
2774         }
2776         setup_update(view, view->id);
2778         return TRUE;
2781 #define ITEM_CHUNK_SIZE 256
2782 static void *
2783 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2785         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2786         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2788         if (mem == NULL || num_chunks != num_chunks_new) {
2789                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2790                 mem = realloc(mem, *size * item_size);
2791         }
2793         return mem;
2796 static struct line *
2797 realloc_lines(struct view *view, size_t line_size)
2799         size_t alloc = view->line_alloc;
2800         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2801                                          sizeof(*view->line));
2803         if (!tmp)
2804                 return NULL;
2806         view->line = tmp;
2807         view->line_alloc = alloc;
2808         return view->line;
2811 static bool
2812 update_view(struct view *view)
2814         char out_buffer[BUFSIZ * 2];
2815         char *line;
2816         /* Clear the view and redraw everything since the tree sorting
2817          * might have rearranged things. */
2818         bool redraw = view->lines == 0;
2819         bool can_read = TRUE;
2821         if (!view->pipe)
2822                 return TRUE;
2824         if (!io_can_read(view->pipe)) {
2825                 if (view->lines == 0) {
2826                         time_t secs = time(NULL) - view->start_time;
2828                         if (secs > 1 && secs > view->update_secs) {
2829                                 if (view->update_secs == 0)
2830                                         redraw_view(view);
2831                                 update_view_title(view);
2832                                 view->update_secs = secs;
2833                         }
2834                 }
2835                 return TRUE;
2836         }
2838         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2839                 if (opt_iconv != ICONV_NONE) {
2840                         ICONV_CONST char *inbuf = line;
2841                         size_t inlen = strlen(line) + 1;
2843                         char *outbuf = out_buffer;
2844                         size_t outlen = sizeof(out_buffer);
2846                         size_t ret;
2848                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2849                         if (ret != (size_t) -1)
2850                                 line = out_buffer;
2851                 }
2853                 if (!view->ops->read(view, line)) {
2854                         report("Allocation failure");
2855                         end_update(view, TRUE);
2856                         return FALSE;
2857                 }
2858         }
2860         {
2861                 unsigned long lines = view->lines;
2862                 int digits;
2864                 for (digits = 0; lines; digits++)
2865                         lines /= 10;
2867                 /* Keep the displayed view in sync with line number scaling. */
2868                 if (digits != view->digits) {
2869                         view->digits = digits;
2870                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2871                                 redraw = TRUE;
2872                 }
2873         }
2875         if (io_error(view->pipe)) {
2876                 report("Failed to read: %s", io_strerror(view->pipe));
2877                 end_update(view, TRUE);
2879         } else if (io_eof(view->pipe)) {
2880                 report("");
2881                 end_update(view, FALSE);
2882         }
2884         if (restore_view_position(view))
2885                 redraw = TRUE;
2887         if (!view_is_displayed(view))
2888                 return TRUE;
2890         if (redraw)
2891                 redraw_view_from(view, 0);
2892         else
2893                 redraw_view_dirty(view);
2895         /* Update the title _after_ the redraw so that if the redraw picks up a
2896          * commit reference in view->ref it'll be available here. */
2897         update_view_title(view);
2898         return TRUE;
2901 static struct line *
2902 add_line_data(struct view *view, void *data, enum line_type type)
2904         struct line *line;
2906         if (!realloc_lines(view, view->lines + 1))
2907                 return NULL;
2909         line = &view->line[view->lines++];
2910         memset(line, 0, sizeof(*line));
2911         line->type = type;
2912         line->data = data;
2913         line->dirty = 1;
2915         return line;
2918 static struct line *
2919 add_line_text(struct view *view, const char *text, enum line_type type)
2921         char *data = text ? strdup(text) : NULL;
2923         return data ? add_line_data(view, data, type) : NULL;
2926 static struct line *
2927 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2929         char buf[SIZEOF_STR];
2930         va_list args;
2932         va_start(args, fmt);
2933         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2934                 buf[0] = 0;
2935         va_end(args);
2937         return buf[0] ? add_line_text(view, buf, type) : NULL;
2940 /*
2941  * View opening
2942  */
2944 enum open_flags {
2945         OPEN_DEFAULT = 0,       /* Use default view switching. */
2946         OPEN_SPLIT = 1,         /* Split current view. */
2947         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
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 split = !!(flags & OPEN_SPLIT);
2956         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2957         bool nomaximize = !!(flags & OPEN_REFRESH);
2958         struct view *view = VIEW(request);
2959         int nviews = displayed_views();
2960         struct view *base_view = display[0];
2962         if (view == prev && nviews == 1 && !reload) {
2963                 report("Already in %s view", view->name);
2964                 return;
2965         }
2967         if (view->git_dir && !opt_git_dir[0]) {
2968                 report("The %s view is disabled in pager view", view->name);
2969                 return;
2970         }
2972         if (split) {
2973                 display[1] = view;
2974                 current_view = 1;
2975         } else if (!nomaximize) {
2976                 /* Maximize the current view. */
2977                 memset(display, 0, sizeof(display));
2978                 current_view = 0;
2979                 display[current_view] = view;
2980         }
2982         /* Resize the view when switching between split- and full-screen,
2983          * or when switching between two different full-screen views. */
2984         if (nviews != displayed_views() ||
2985             (nviews == 1 && base_view != display[0]))
2986                 resize_display();
2988         if (view->ops->open) {
2989                 if (view->pipe)
2990                         end_update(view, TRUE);
2991                 if (!view->ops->open(view)) {
2992                         report("Failed to load %s view", view->name);
2993                         return;
2994                 }
2995                 restore_view_position(view);
2997         } else if ((reload || strcmp(view->vid, view->id)) &&
2998                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2999                 report("Failed to load %s view", view->name);
3000                 return;
3001         }
3003         if (split && prev->lineno - prev->offset >= prev->height) {
3004                 /* Take the title line into account. */
3005                 int lines = prev->lineno - prev->offset - prev->height + 1;
3007                 /* Scroll the view that was split if the current line is
3008                  * outside the new limited view. */
3009                 do_scroll_view(prev, lines);
3010         }
3012         if (prev && view != prev) {
3013                 if (split) {
3014                         /* "Blur" the previous view. */
3015                         update_view_title(prev);
3016                 }
3018                 view->parent = prev;
3019         }
3021         if (view->pipe && view->lines == 0) {
3022                 /* Clear the old view and let the incremental updating refill
3023                  * the screen. */
3024                 werase(view->win);
3025                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3026                 report("");
3027         } else if (view_is_displayed(view)) {
3028                 redraw_view(view);
3029                 report("");
3030         }
3033 static void
3034 open_external_viewer(const char *argv[], const char *dir)
3036         def_prog_mode();           /* save current tty modes */
3037         endwin();                  /* restore original tty modes */
3038         run_io_fg(argv, dir);
3039         fprintf(stderr, "Press Enter to continue");
3040         getc(opt_tty);
3041         reset_prog_mode();
3042         redraw_display(TRUE);
3045 static void
3046 open_mergetool(const char *file)
3048         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3050         open_external_viewer(mergetool_argv, opt_cdup);
3053 static void
3054 open_editor(bool from_root, const char *file)
3056         const char *editor_argv[] = { "vi", file, NULL };
3057         const char *editor;
3059         editor = getenv("GIT_EDITOR");
3060         if (!editor && *opt_editor)
3061                 editor = opt_editor;
3062         if (!editor)
3063                 editor = getenv("VISUAL");
3064         if (!editor)
3065                 editor = getenv("EDITOR");
3066         if (!editor)
3067                 editor = "vi";
3069         editor_argv[0] = editor;
3070         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3073 static void
3074 open_run_request(enum request request)
3076         struct run_request *req = get_run_request(request);
3077         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3079         if (!req) {
3080                 report("Unknown run request");
3081                 return;
3082         }
3084         if (format_argv(argv, req->argv, FORMAT_ALL))
3085                 open_external_viewer(argv, NULL);
3086         free_argv(argv);
3089 /*
3090  * User request switch noodle
3091  */
3093 static int
3094 view_driver(struct view *view, enum request request)
3096         int i;
3098         if (request == REQ_NONE) {
3099                 doupdate();
3100                 return TRUE;
3101         }
3103         if (request > REQ_NONE) {
3104                 open_run_request(request);
3105                 /* FIXME: When all views can refresh always do this. */
3106                 if (view == VIEW(REQ_VIEW_STATUS) ||
3107                     view == VIEW(REQ_VIEW_MAIN) ||
3108                     view == VIEW(REQ_VIEW_LOG) ||
3109                     view == VIEW(REQ_VIEW_STAGE))
3110                         request = REQ_REFRESH;
3111                 else
3112                         return TRUE;
3113         }
3115         if (view && view->lines) {
3116                 request = view->ops->request(view, request, &view->line[view->lineno]);
3117                 if (request == REQ_NONE)
3118                         return TRUE;
3119         }
3121         switch (request) {
3122         case REQ_MOVE_UP:
3123         case REQ_MOVE_DOWN:
3124         case REQ_MOVE_PAGE_UP:
3125         case REQ_MOVE_PAGE_DOWN:
3126         case REQ_MOVE_FIRST_LINE:
3127         case REQ_MOVE_LAST_LINE:
3128                 move_view(view, request);
3129                 break;
3131         case REQ_SCROLL_LEFT:
3132         case REQ_SCROLL_RIGHT:
3133         case REQ_SCROLL_LINE_DOWN:
3134         case REQ_SCROLL_LINE_UP:
3135         case REQ_SCROLL_PAGE_DOWN:
3136         case REQ_SCROLL_PAGE_UP:
3137                 scroll_view(view, request);
3138                 break;
3140         case REQ_VIEW_BLAME:
3141                 if (!opt_file[0]) {
3142                         report("No file chosen, press %s to open tree view",
3143                                get_key(REQ_VIEW_TREE));
3144                         break;
3145                 }
3146                 open_view(view, request, OPEN_DEFAULT);
3147                 break;
3149         case REQ_VIEW_BLOB:
3150                 if (!ref_blob[0]) {
3151                         report("No file chosen, press %s to open tree view",
3152                                get_key(REQ_VIEW_TREE));
3153                         break;
3154                 }
3155                 open_view(view, request, OPEN_DEFAULT);
3156                 break;
3158         case REQ_VIEW_PAGER:
3159                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3160                         report("No pager content, press %s to run command from prompt",
3161                                get_key(REQ_PROMPT));
3162                         break;
3163                 }
3164                 open_view(view, request, OPEN_DEFAULT);
3165                 break;
3167         case REQ_VIEW_STAGE:
3168                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3169                         report("No stage content, press %s to open the status view and choose file",
3170                                get_key(REQ_VIEW_STATUS));
3171                         break;
3172                 }
3173                 open_view(view, request, OPEN_DEFAULT);
3174                 break;
3176         case REQ_VIEW_STATUS:
3177                 if (opt_is_inside_work_tree == FALSE) {
3178                         report("The status view requires a working tree");
3179                         break;
3180                 }
3181                 open_view(view, request, OPEN_DEFAULT);
3182                 break;
3184         case REQ_VIEW_MAIN:
3185         case REQ_VIEW_DIFF:
3186         case REQ_VIEW_LOG:
3187         case REQ_VIEW_TREE:
3188         case REQ_VIEW_HELP:
3189                 open_view(view, request, OPEN_DEFAULT);
3190                 break;
3192         case REQ_NEXT:
3193         case REQ_PREVIOUS:
3194                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3196                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3197                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3198                    (view == VIEW(REQ_VIEW_DIFF) &&
3199                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3200                    (view == VIEW(REQ_VIEW_STAGE) &&
3201                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3202                    (view == VIEW(REQ_VIEW_BLOB) &&
3203                      view->parent == VIEW(REQ_VIEW_TREE))) {
3204                         int line;
3206                         view = view->parent;
3207                         line = view->lineno;
3208                         move_view(view, request);
3209                         if (view_is_displayed(view))
3210                                 update_view_title(view);
3211                         if (line != view->lineno)
3212                                 view->ops->request(view, REQ_ENTER,
3213                                                    &view->line[view->lineno]);
3215                 } else {
3216                         move_view(view, request);
3217                 }
3218                 break;
3220         case REQ_VIEW_NEXT:
3221         {
3222                 int nviews = displayed_views();
3223                 int next_view = (current_view + 1) % nviews;
3225                 if (next_view == current_view) {
3226                         report("Only one view is displayed");
3227                         break;
3228                 }
3230                 current_view = next_view;
3231                 /* Blur out the title of the previous view. */
3232                 update_view_title(view);
3233                 report("");
3234                 break;
3235         }
3236         case REQ_REFRESH:
3237                 report("Refreshing is not yet supported for the %s view", view->name);
3238                 break;
3240         case REQ_MAXIMIZE:
3241                 if (displayed_views() == 2)
3242                         maximize_view(view);
3243                 break;
3245         case REQ_TOGGLE_LINENO:
3246                 toggle_view_option(&opt_line_number, "line numbers");
3247                 break;
3249         case REQ_TOGGLE_DATE:
3250                 toggle_view_option(&opt_date, "date display");
3251                 break;
3253         case REQ_TOGGLE_AUTHOR:
3254                 toggle_view_option(&opt_author, "author display");
3255                 break;
3257         case REQ_TOGGLE_REV_GRAPH:
3258                 toggle_view_option(&opt_rev_graph, "revision graph display");
3259                 break;
3261         case REQ_TOGGLE_REFS:
3262                 toggle_view_option(&opt_show_refs, "reference display");
3263                 break;
3265         case REQ_SEARCH:
3266         case REQ_SEARCH_BACK:
3267                 search_view(view, request);
3268                 break;
3270         case REQ_FIND_NEXT:
3271         case REQ_FIND_PREV:
3272                 find_next(view, request);
3273                 break;
3275         case REQ_STOP_LOADING:
3276                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3277                         view = &views[i];
3278                         if (view->pipe)
3279                                 report("Stopped loading the %s view", view->name),
3280                         end_update(view, TRUE);
3281                 }
3282                 break;
3284         case REQ_SHOW_VERSION:
3285                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3286                 return TRUE;
3288         case REQ_SCREEN_REDRAW:
3289                 redraw_display(TRUE);
3290                 break;
3292         case REQ_EDIT:
3293                 report("Nothing to edit");
3294                 break;
3296         case REQ_ENTER:
3297                 report("Nothing to enter");
3298                 break;
3300         case REQ_VIEW_CLOSE:
3301                 /* XXX: Mark closed views by letting view->parent point to the
3302                  * view itself. Parents to closed view should never be
3303                  * followed. */
3304                 if (view->parent &&
3305                     view->parent->parent != view->parent) {
3306                         maximize_view(view->parent);
3307                         view->parent = view;
3308                         break;
3309                 }
3310                 /* Fall-through */
3311         case REQ_QUIT:
3312                 return FALSE;
3314         default:
3315                 report("Unknown key, press 'h' for help");
3316                 return TRUE;
3317         }
3319         return TRUE;
3323 /*
3324  * View backend utilities
3325  */
3327 static void
3328 parse_timezone(time_t *time, const char *zone)
3330         long tz;
3332         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3333         tz += ('0' - zone[2]) * 60 * 60;
3334         tz += ('0' - zone[3]) * 60;
3335         tz += ('0' - zone[4]);
3337         if (zone[0] == '-')
3338                 tz = -tz;
3340         *time -= tz;
3343 /* Parse author lines where the name may be empty:
3344  *      author  <email@address.tld> 1138474660 +0100
3345  */
3346 static void
3347 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3349         char *nameend = strchr(ident, '<');
3350         char *emailend = strchr(ident, '>');
3352         if (nameend && emailend)
3353                 *nameend = *emailend = 0;
3354         ident = chomp_string(ident);
3355         if (!*ident) {
3356                 if (nameend)
3357                         ident = chomp_string(nameend + 1);
3358                 if (!*ident)
3359                         ident = "Unknown";
3360         }
3362         string_ncopy_do(author, authorsize, ident, strlen(ident));
3364         /* Parse epoch and timezone */
3365         if (emailend && emailend[1] == ' ') {
3366                 char *secs = emailend + 2;
3367                 char *zone = strchr(secs, ' ');
3368                 time_t time = (time_t) atol(secs);
3370                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3371                         parse_timezone(&time, zone + 1);
3373                 gmtime_r(&time, tm);
3374         }
3377 static enum input_status
3378 select_commit_parent_handler(void *data, char *buf, int c)
3380         size_t parents = *(size_t *) data;
3381         int parent = 0;
3383         if (!isdigit(c))
3384                 return INPUT_SKIP;
3386         if (*buf)
3387                 parent = atoi(buf) * 10;
3388         parent += c - '0';
3390         if (parent > parents)
3391                 return INPUT_SKIP;
3392         return INPUT_OK;
3395 static bool
3396 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3398         char buf[SIZEOF_STR * 4];
3399         const char *revlist_argv[] = {
3400                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3401         };
3402         int parents;
3404         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3405             !*chomp_string(buf) ||
3406             (parents = (strlen(buf) / 40) - 1) < 0) {
3407                 report("Failed to get parent information");
3408                 return FALSE;
3410         } else if (parents == 0) {
3411                 if (path)
3412                         report("Path '%s' does not exist in the parent", path);
3413                 else
3414                         report("The selected commit has no parents");
3415                 return FALSE;
3416         }
3418         if (parents > 1) {
3419                 char prompt[SIZEOF_STR];
3420                 char *result;
3422                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3423                         return FALSE;
3424                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3425                 if (!result)
3426                         return FALSE;
3427                 parents = atoi(result);
3428         }
3430         string_copy_rev(rev, &buf[41 * parents]);
3431         return TRUE;
3434 /*
3435  * Pager backend
3436  */
3438 static bool
3439 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3441         char text[SIZEOF_STR];
3443         if (opt_line_number && draw_lineno(view, lineno))
3444                 return TRUE;
3446         string_expand(text, sizeof(text), line->data, opt_tab_size);
3447         draw_text(view, line->type, text, TRUE);
3448         return TRUE;
3451 static bool
3452 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3454         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3455         char refbuf[SIZEOF_STR];
3456         char *ref = NULL;
3458         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3459                 ref = chomp_string(refbuf);
3461         if (!ref || !*ref)
3462                 return TRUE;
3464         /* This is the only fatal call, since it can "corrupt" the buffer. */
3465         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3466                 return FALSE;
3468         return TRUE;
3471 static void
3472 add_pager_refs(struct view *view, struct line *line)
3474         char buf[SIZEOF_STR];
3475         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3476         struct ref **refs;
3477         size_t bufpos = 0, refpos = 0;
3478         const char *sep = "Refs: ";
3479         bool is_tag = FALSE;
3481         assert(line->type == LINE_COMMIT);
3483         refs = get_refs(commit_id);
3484         if (!refs) {
3485                 if (view == VIEW(REQ_VIEW_DIFF))
3486                         goto try_add_describe_ref;
3487                 return;
3488         }
3490         do {
3491                 struct ref *ref = refs[refpos];
3492                 const char *fmt = ref->tag    ? "%s[%s]" :
3493                                   ref->remote ? "%s<%s>" : "%s%s";
3495                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3496                         return;
3497                 sep = ", ";
3498                 if (ref->tag)
3499                         is_tag = TRUE;
3500         } while (refs[refpos++]->next);
3502         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3503 try_add_describe_ref:
3504                 /* Add <tag>-g<commit_id> "fake" reference. */
3505                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3506                         return;
3507         }
3509         if (bufpos == 0)
3510                 return;
3512         add_line_text(view, buf, LINE_PP_REFS);
3515 static bool
3516 pager_read(struct view *view, char *data)
3518         struct line *line;
3520         if (!data)
3521                 return TRUE;
3523         line = add_line_text(view, data, get_line_type(data));
3524         if (!line)
3525                 return FALSE;
3527         if (line->type == LINE_COMMIT &&
3528             (view == VIEW(REQ_VIEW_DIFF) ||
3529              view == VIEW(REQ_VIEW_LOG)))
3530                 add_pager_refs(view, line);
3532         return TRUE;
3535 static enum request
3536 pager_request(struct view *view, enum request request, struct line *line)
3538         int split = 0;
3540         if (request != REQ_ENTER)
3541                 return request;
3543         if (line->type == LINE_COMMIT &&
3544            (view == VIEW(REQ_VIEW_LOG) ||
3545             view == VIEW(REQ_VIEW_PAGER))) {
3546                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3547                 split = 1;
3548         }
3550         /* Always scroll the view even if it was split. That way
3551          * you can use Enter to scroll through the log view and
3552          * split open each commit diff. */
3553         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3555         /* FIXME: A minor workaround. Scrolling the view will call report("")
3556          * but if we are scrolling a non-current view this won't properly
3557          * update the view title. */
3558         if (split)
3559                 update_view_title(view);
3561         return REQ_NONE;
3564 static bool
3565 pager_grep(struct view *view, struct line *line)
3567         regmatch_t pmatch;
3568         char *text = line->data;
3570         if (!*text)
3571                 return FALSE;
3573         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3574                 return FALSE;
3576         return TRUE;
3579 static void
3580 pager_select(struct view *view, struct line *line)
3582         if (line->type == LINE_COMMIT) {
3583                 char *text = (char *)line->data + STRING_SIZE("commit ");
3585                 if (view != VIEW(REQ_VIEW_PAGER))
3586                         string_copy_rev(view->ref, text);
3587                 string_copy_rev(ref_commit, text);
3588         }
3591 static struct view_ops pager_ops = {
3592         "line",
3593         NULL,
3594         NULL,
3595         pager_read,
3596         pager_draw,
3597         pager_request,
3598         pager_grep,
3599         pager_select,
3600 };
3602 static const char *log_argv[SIZEOF_ARG] = {
3603         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3604 };
3606 static enum request
3607 log_request(struct view *view, enum request request, struct line *line)
3609         switch (request) {
3610         case REQ_REFRESH:
3611                 load_refs();
3612                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3613                 return REQ_NONE;
3614         default:
3615                 return pager_request(view, request, line);
3616         }
3619 static struct view_ops log_ops = {
3620         "line",
3621         log_argv,
3622         NULL,
3623         pager_read,
3624         pager_draw,
3625         log_request,
3626         pager_grep,
3627         pager_select,
3628 };
3630 static const char *diff_argv[SIZEOF_ARG] = {
3631         "git", "show", "--pretty=fuller", "--no-color", "--root",
3632                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3633 };
3635 static struct view_ops diff_ops = {
3636         "line",
3637         diff_argv,
3638         NULL,
3639         pager_read,
3640         pager_draw,
3641         pager_request,
3642         pager_grep,
3643         pager_select,
3644 };
3646 /*
3647  * Help backend
3648  */
3650 static bool
3651 help_open(struct view *view)
3653         char buf[SIZEOF_STR];
3654         size_t bufpos;
3655         int i;
3657         if (view->lines > 0)
3658                 return TRUE;
3660         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3662         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3663                 const char *key;
3665                 if (req_info[i].request == REQ_NONE)
3666                         continue;
3668                 if (!req_info[i].request) {
3669                         add_line_text(view, "", LINE_DEFAULT);
3670                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3671                         continue;
3672                 }
3674                 key = get_key(req_info[i].request);
3675                 if (!*key)
3676                         key = "(no key defined)";
3678                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3679                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3680                         if (buf[bufpos] == '_')
3681                                 buf[bufpos] = '-';
3682                 }
3684                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3685                                 key, buf, req_info[i].help);
3686         }
3688         if (run_requests) {
3689                 add_line_text(view, "", LINE_DEFAULT);
3690                 add_line_text(view, "External commands:", LINE_DEFAULT);
3691         }
3693         for (i = 0; i < run_requests; i++) {
3694                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3695                 const char *key;
3696                 int argc;
3698                 if (!req)
3699                         continue;
3701                 key = get_key_name(req->key);
3702                 if (!*key)
3703                         key = "(no key defined)";
3705                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3706                         if (!string_format_from(buf, &bufpos, "%s%s",
3707                                                 argc ? " " : "", req->argv[argc]))
3708                                 return REQ_NONE;
3710                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3711                                 keymap_table[req->keymap].name, key, buf);
3712         }
3714         return TRUE;
3717 static struct view_ops help_ops = {
3718         "line",
3719         NULL,
3720         help_open,
3721         NULL,
3722         pager_draw,
3723         pager_request,
3724         pager_grep,
3725         pager_select,
3726 };
3729 /*
3730  * Tree backend
3731  */
3733 struct tree_stack_entry {
3734         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3735         unsigned long lineno;           /* Line number to restore */
3736         char *name;                     /* Position of name in opt_path */
3737 };
3739 /* The top of the path stack. */
3740 static struct tree_stack_entry *tree_stack = NULL;
3741 unsigned long tree_lineno = 0;
3743 static void
3744 pop_tree_stack_entry(void)
3746         struct tree_stack_entry *entry = tree_stack;
3748         tree_lineno = entry->lineno;
3749         entry->name[0] = 0;
3750         tree_stack = entry->prev;
3751         free(entry);
3754 static void
3755 push_tree_stack_entry(const char *name, unsigned long lineno)
3757         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3758         size_t pathlen = strlen(opt_path);
3760         if (!entry)
3761                 return;
3763         entry->prev = tree_stack;
3764         entry->name = opt_path + pathlen;
3765         tree_stack = entry;
3767         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3768                 pop_tree_stack_entry();
3769                 return;
3770         }
3772         /* Move the current line to the first tree entry. */
3773         tree_lineno = 1;
3774         entry->lineno = lineno;
3777 /* Parse output from git-ls-tree(1):
3778  *
3779  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3780  */
3782 #define SIZEOF_TREE_ATTR \
3783         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3785 #define SIZEOF_TREE_MODE \
3786         STRING_SIZE("100644 ")
3788 #define TREE_ID_OFFSET \
3789         STRING_SIZE("100644 blob ")
3791 struct tree_entry {
3792         char id[SIZEOF_REV];
3793         mode_t mode;
3794         struct tm time;                 /* Date from the author ident. */
3795         char author[75];                /* Author of the commit. */
3796         char name[1];
3797 };
3799 static const char *
3800 tree_path(struct line *line)
3802         return ((struct tree_entry *) line->data)->name;
3806 static int
3807 tree_compare_entry(struct line *line1, struct line *line2)
3809         if (line1->type != line2->type)
3810                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3811         return strcmp(tree_path(line1), tree_path(line2));
3814 static struct line *
3815 tree_entry(struct view *view, enum line_type type, const char *path,
3816            const char *mode, const char *id)
3818         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3819         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3821         if (!entry || !line) {
3822                 free(entry);
3823                 return NULL;
3824         }
3826         strncpy(entry->name, path, strlen(path));
3827         if (mode)
3828                 entry->mode = strtoul(mode, NULL, 8);
3829         if (id)
3830                 string_copy_rev(entry->id, id);
3832         return line;
3835 static bool
3836 tree_read_date(struct view *view, char *text, bool *read_date)
3838         static char author_name[SIZEOF_STR];
3839         static struct tm author_time;
3841         if (!text && *read_date) {
3842                 *read_date = FALSE;
3843                 return TRUE;
3845         } else if (!text) {
3846                 char *path = *opt_path ? opt_path : ".";
3847                 /* Find next entry to process */
3848                 const char *log_file[] = {
3849                         "git", "log", "--no-color", "--pretty=raw",
3850                                 "--cc", "--raw", view->id, "--", path, NULL
3851                 };
3852                 struct io io = {};
3854                 if (!view->lines) {
3855                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3856                         report("Tree is empty");
3857                         return TRUE;
3858                 }
3860                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3861                         report("Failed to load tree data");
3862                         return TRUE;
3863                 }
3865                 done_io(view->pipe);
3866                 view->io = io;
3867                 *read_date = TRUE;
3868                 return FALSE;
3870         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3871                 parse_author_line(text + STRING_SIZE("author "),
3872                                   author_name, sizeof(author_name), &author_time);
3874         } else if (*text == ':') {
3875                 char *pos;
3876                 size_t annotated = 1;
3877                 size_t i;
3879                 pos = strchr(text, '\t');
3880                 if (!pos)
3881                         return TRUE;
3882                 text = pos + 1;
3883                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3884                         text += strlen(opt_prefix);
3885                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3886                         text += strlen(opt_path);
3887                 pos = strchr(text, '/');
3888                 if (pos)
3889                         *pos = 0;
3891                 for (i = 1; i < view->lines; i++) {
3892                         struct line *line = &view->line[i];
3893                         struct tree_entry *entry = line->data;
3895                         annotated += !!*entry->author;
3896                         if (*entry->author || strcmp(entry->name, text))
3897                                 continue;
3899                         string_copy(entry->author, author_name);
3900                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3901                         line->dirty = 1;
3902                         break;
3903                 }
3905                 if (annotated == view->lines)
3906                         kill_io(view->pipe);
3907         }
3908         return TRUE;
3911 static bool
3912 tree_read(struct view *view, char *text)
3914         static bool read_date = FALSE;
3915         struct tree_entry *data;
3916         struct line *entry, *line;
3917         enum line_type type;
3918         size_t textlen = text ? strlen(text) : 0;
3919         char *path = text + SIZEOF_TREE_ATTR;
3921         if (read_date || !text)
3922                 return tree_read_date(view, text, &read_date);
3924         if (textlen <= SIZEOF_TREE_ATTR)
3925                 return FALSE;
3926         if (view->lines == 0 &&
3927             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3928                 return FALSE;
3930         /* Strip the path part ... */
3931         if (*opt_path) {
3932                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3933                 size_t striplen = strlen(opt_path);
3935                 if (pathlen > striplen)
3936                         memmove(path, path + striplen,
3937                                 pathlen - striplen + 1);
3939                 /* Insert "link" to parent directory. */
3940                 if (view->lines == 1 &&
3941                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3942                         return FALSE;
3943         }
3945         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3946         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3947         if (!entry)
3948                 return FALSE;
3949         data = entry->data;
3951         /* Skip "Directory ..." and ".." line. */
3952         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3953                 if (tree_compare_entry(line, entry) <= 0)
3954                         continue;
3956                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3958                 line->data = data;
3959                 line->type = type;
3960                 for (; line <= entry; line++)
3961                         line->dirty = line->cleareol = 1;
3962                 return TRUE;
3963         }
3965         if (tree_lineno > view->lineno) {
3966                 view->lineno = tree_lineno;
3967                 tree_lineno = 0;
3968         }
3970         return TRUE;
3973 static bool
3974 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3976         struct tree_entry *entry = line->data;
3978         if (line->type == LINE_TREE_HEAD) {
3979                 if (draw_text(view, line->type, "Directory path /", TRUE))
3980                         return TRUE;
3981         } else {
3982                 if (draw_mode(view, entry->mode))
3983                         return TRUE;
3985                 if (opt_author && draw_author(view, entry->author))
3986                         return TRUE;
3988                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3989                         return TRUE;
3990         }
3991         if (draw_text(view, line->type, entry->name, TRUE))
3992                 return TRUE;
3993         return TRUE;
3996 static void
3997 open_blob_editor()
3999         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4000         int fd = mkstemp(file);
4002         if (fd == -1)
4003                 report("Failed to create temporary file");
4004         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4005                 report("Failed to save blob data to file");
4006         else
4007                 open_editor(FALSE, file);
4008         if (fd != -1)
4009                 unlink(file);
4012 static enum request
4013 tree_request(struct view *view, enum request request, struct line *line)
4015         enum open_flags flags;
4017         switch (request) {
4018         case REQ_VIEW_BLAME:
4019                 if (line->type != LINE_TREE_FILE) {
4020                         report("Blame only supported for files");
4021                         return REQ_NONE;
4022                 }
4024                 string_copy(opt_ref, view->vid);
4025                 return request;
4027         case REQ_EDIT:
4028                 if (line->type != LINE_TREE_FILE) {
4029                         report("Edit only supported for files");
4030                 } else if (!is_head_commit(view->vid)) {
4031                         open_blob_editor();
4032                 } else {
4033                         open_editor(TRUE, opt_file);
4034                 }
4035                 return REQ_NONE;
4037         case REQ_PARENT:
4038                 if (!*opt_path) {
4039                         /* quit view if at top of tree */
4040                         return REQ_VIEW_CLOSE;
4041                 }
4042                 /* fake 'cd  ..' */
4043                 line = &view->line[1];
4044                 break;
4046         case REQ_ENTER:
4047                 break;
4049         default:
4050                 return request;
4051         }
4053         /* Cleanup the stack if the tree view is at a different tree. */
4054         while (!*opt_path && tree_stack)
4055                 pop_tree_stack_entry();
4057         switch (line->type) {
4058         case LINE_TREE_DIR:
4059                 /* Depending on whether it is a subdirectory or parent link
4060                  * mangle the path buffer. */
4061                 if (line == &view->line[1] && *opt_path) {
4062                         pop_tree_stack_entry();
4064                 } else {
4065                         const char *basename = tree_path(line);
4067                         push_tree_stack_entry(basename, view->lineno);
4068                 }
4070                 /* Trees and subtrees share the same ID, so they are not not
4071                  * unique like blobs. */
4072                 flags = OPEN_RELOAD;
4073                 request = REQ_VIEW_TREE;
4074                 break;
4076         case LINE_TREE_FILE:
4077                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4078                 request = REQ_VIEW_BLOB;
4079                 break;
4081         default:
4082                 return REQ_NONE;
4083         }
4085         open_view(view, request, flags);
4086         if (request == REQ_VIEW_TREE)
4087                 view->lineno = tree_lineno;
4089         return REQ_NONE;
4092 static void
4093 tree_select(struct view *view, struct line *line)
4095         struct tree_entry *entry = line->data;
4097         if (line->type == LINE_TREE_FILE) {
4098                 string_copy_rev(ref_blob, entry->id);
4099                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4101         } else if (line->type != LINE_TREE_DIR) {
4102                 return;
4103         }
4105         string_copy_rev(view->ref, entry->id);
4108 static const char *tree_argv[SIZEOF_ARG] = {
4109         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4110 };
4112 static struct view_ops tree_ops = {
4113         "file",
4114         tree_argv,
4115         NULL,
4116         tree_read,
4117         tree_draw,
4118         tree_request,
4119         pager_grep,
4120         tree_select,
4121 };
4123 static bool
4124 blob_read(struct view *view, char *line)
4126         if (!line)
4127                 return TRUE;
4128         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4131 static enum request
4132 blob_request(struct view *view, enum request request, struct line *line)
4134         switch (request) {
4135         case REQ_EDIT:
4136                 open_blob_editor();
4137                 return REQ_NONE;
4138         default:
4139                 return pager_request(view, request, line);
4140         }
4143 static const char *blob_argv[SIZEOF_ARG] = {
4144         "git", "cat-file", "blob", "%(blob)", NULL
4145 };
4147 static struct view_ops blob_ops = {
4148         "line",
4149         blob_argv,
4150         NULL,
4151         blob_read,
4152         pager_draw,
4153         blob_request,
4154         pager_grep,
4155         pager_select,
4156 };
4158 /*
4159  * Blame backend
4160  *
4161  * Loading the blame view is a two phase job:
4162  *
4163  *  1. File content is read either using opt_file from the
4164  *     filesystem or using git-cat-file.
4165  *  2. Then blame information is incrementally added by
4166  *     reading output from git-blame.
4167  */
4169 static const char *blame_head_argv[] = {
4170         "git", "blame", "--incremental", "--", "%(file)", NULL
4171 };
4173 static const char *blame_ref_argv[] = {
4174         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4175 };
4177 static const char *blame_cat_file_argv[] = {
4178         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4179 };
4181 struct blame_commit {
4182         char id[SIZEOF_REV];            /* SHA1 ID. */
4183         char title[128];                /* First line of the commit message. */
4184         char author[75];                /* Author of the commit. */
4185         struct tm time;                 /* Date from the author ident. */
4186         char filename[128];             /* Name of file. */
4187         bool has_previous;              /* Was a "previous" line detected. */
4188 };
4190 struct blame {
4191         struct blame_commit *commit;
4192         unsigned long lineno;
4193         char text[1];
4194 };
4196 static bool
4197 blame_open(struct view *view)
4199         if (*opt_ref || !io_open(&view->io, opt_file)) {
4200                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4201                         return FALSE;
4202         }
4204         setup_update(view, opt_file);
4205         string_format(view->ref, "%s ...", opt_file);
4207         return TRUE;
4210 static struct blame_commit *
4211 get_blame_commit(struct view *view, const char *id)
4213         size_t i;
4215         for (i = 0; i < view->lines; i++) {
4216                 struct blame *blame = view->line[i].data;
4218                 if (!blame->commit)
4219                         continue;
4221                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4222                         return blame->commit;
4223         }
4225         {
4226                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4228                 if (commit)
4229                         string_ncopy(commit->id, id, SIZEOF_REV);
4230                 return commit;
4231         }
4234 static bool
4235 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4237         const char *pos = *posref;
4239         *posref = NULL;
4240         pos = strchr(pos + 1, ' ');
4241         if (!pos || !isdigit(pos[1]))
4242                 return FALSE;
4243         *number = atoi(pos + 1);
4244         if (*number < min || *number > max)
4245                 return FALSE;
4247         *posref = pos;
4248         return TRUE;
4251 static struct blame_commit *
4252 parse_blame_commit(struct view *view, const char *text, int *blamed)
4254         struct blame_commit *commit;
4255         struct blame *blame;
4256         const char *pos = text + SIZEOF_REV - 2;
4257         size_t orig_lineno = 0;
4258         size_t lineno;
4259         size_t group;
4261         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4262                 return NULL;
4264         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4265             !parse_number(&pos, &lineno, 1, view->lines) ||
4266             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4267                 return NULL;
4269         commit = get_blame_commit(view, text);
4270         if (!commit)
4271                 return NULL;
4273         *blamed += group;
4274         while (group--) {
4275                 struct line *line = &view->line[lineno + group - 1];
4277                 blame = line->data;
4278                 blame->commit = commit;
4279                 blame->lineno = orig_lineno + group - 1;
4280                 line->dirty = 1;
4281         }
4283         return commit;
4286 static bool
4287 blame_read_file(struct view *view, const char *line, bool *read_file)
4289         if (!line) {
4290                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4291                 struct io io = {};
4293                 if (view->lines == 0 && !view->parent)
4294                         die("No blame exist for %s", view->vid);
4296                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4297                         report("Failed to load blame data");
4298                         return TRUE;
4299                 }
4301                 done_io(view->pipe);
4302                 view->io = io;
4303                 *read_file = FALSE;
4304                 return FALSE;
4306         } else {
4307                 size_t linelen = strlen(line);
4308                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4310                 if (!blame)
4311                         return FALSE;
4313                 blame->commit = NULL;
4314                 strncpy(blame->text, line, linelen);
4315                 blame->text[linelen] = 0;
4316                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4317         }
4320 static bool
4321 match_blame_header(const char *name, char **line)
4323         size_t namelen = strlen(name);
4324         bool matched = !strncmp(name, *line, namelen);
4326         if (matched)
4327                 *line += namelen;
4329         return matched;
4332 static bool
4333 blame_read(struct view *view, char *line)
4335         static struct blame_commit *commit = NULL;
4336         static int blamed = 0;
4337         static time_t author_time;
4338         static bool read_file = TRUE;
4340         if (read_file)
4341                 return blame_read_file(view, line, &read_file);
4343         if (!line) {
4344                 /* Reset all! */
4345                 commit = NULL;
4346                 blamed = 0;
4347                 read_file = TRUE;
4348                 string_format(view->ref, "%s", view->vid);
4349                 if (view_is_displayed(view)) {
4350                         update_view_title(view);
4351                         redraw_view_from(view, 0);
4352                 }
4353                 return TRUE;
4354         }
4356         if (!commit) {
4357                 commit = parse_blame_commit(view, line, &blamed);
4358                 string_format(view->ref, "%s %2d%%", view->vid,
4359                               view->lines ? blamed * 100 / view->lines : 0);
4361         } else if (match_blame_header("author ", &line)) {
4362                 string_ncopy(commit->author, line, strlen(line));
4364         } else if (match_blame_header("author-time ", &line)) {
4365                 author_time = (time_t) atol(line);
4367         } else if (match_blame_header("author-tz ", &line)) {
4368                 parse_timezone(&author_time, line);
4369                 gmtime_r(&author_time, &commit->time);
4371         } else if (match_blame_header("summary ", &line)) {
4372                 string_ncopy(commit->title, line, strlen(line));
4374         } else if (match_blame_header("previous ", &line)) {
4375                 commit->has_previous = TRUE;
4377         } else if (match_blame_header("filename ", &line)) {
4378                 string_ncopy(commit->filename, line, strlen(line));
4379                 commit = NULL;
4380         }
4382         return TRUE;
4385 static bool
4386 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4388         struct blame *blame = line->data;
4389         struct tm *time = NULL;
4390         const char *id = NULL, *author = NULL;
4391         char text[SIZEOF_STR];
4393         if (blame->commit && *blame->commit->filename) {
4394                 id = blame->commit->id;
4395                 author = blame->commit->author;
4396                 time = &blame->commit->time;
4397         }
4399         if (opt_date && draw_date(view, time))
4400                 return TRUE;
4402         if (opt_author && draw_author(view, author))
4403                 return TRUE;
4405         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4406                 return TRUE;
4408         if (draw_lineno(view, lineno))
4409                 return TRUE;
4411         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4412         draw_text(view, LINE_DEFAULT, text, TRUE);
4413         return TRUE;
4416 static bool
4417 check_blame_commit(struct blame *blame, bool check_null_id)
4419         if (!blame->commit)
4420                 report("Commit data not loaded yet");
4421         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4422                 report("No commit exist for the selected line");
4423         else
4424                 return TRUE;
4425         return FALSE;
4428 static void
4429 setup_blame_parent_line(struct view *view, struct blame *blame)
4431         const char *diff_tree_argv[] = {
4432                 "git", "diff-tree", "-U0", blame->commit->id,
4433                         "--", blame->commit->filename, NULL
4434         };
4435         struct io io = {};
4436         int parent_lineno = -1;
4437         int blamed_lineno = -1;
4438         char *line;
4440         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4441                 return;
4443         while ((line = io_get(&io, '\n', TRUE))) {
4444                 if (*line == '@') {
4445                         char *pos = strchr(line, '+');
4447                         parent_lineno = atoi(line + 4);
4448                         if (pos)
4449                                 blamed_lineno = atoi(pos + 1);
4451                 } else if (*line == '+' && parent_lineno != -1) {
4452                         if (blame->lineno == blamed_lineno - 1 &&
4453                             !strcmp(blame->text, line + 1)) {
4454                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4455                                 break;
4456                         }
4457                         blamed_lineno++;
4458                 }
4459         }
4461         done_io(&io);
4464 static enum request
4465 blame_request(struct view *view, enum request request, struct line *line)
4467         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4468         struct blame *blame = line->data;
4470         switch (request) {
4471         case REQ_VIEW_BLAME:
4472                 if (check_blame_commit(blame, TRUE)) {
4473                         string_copy(opt_ref, blame->commit->id);
4474                         string_copy(opt_file, blame->commit->filename);
4475                         if (blame->lineno)
4476                                 view->lineno = blame->lineno;
4477                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4478                 }
4479                 break;
4481         case REQ_PARENT:
4482                 if (check_blame_commit(blame, TRUE) &&
4483                     select_commit_parent(blame->commit->id, opt_ref,
4484                                          blame->commit->filename)) {
4485                         string_copy(opt_file, blame->commit->filename);
4486                         setup_blame_parent_line(view, blame);
4487                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4488                 }
4489                 break;
4491         case REQ_ENTER:
4492                 if (!check_blame_commit(blame, FALSE))
4493                         break;
4495                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4496                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4497                         break;
4499                 if (!strcmp(blame->commit->id, NULL_ID)) {
4500                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4501                         const char *diff_index_argv[] = {
4502                                 "git", "diff-index", "--root", "--patch-with-stat",
4503                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4504                         };
4506                         if (!blame->commit->has_previous) {
4507                                 diff_index_argv[1] = "diff";
4508                                 diff_index_argv[2] = "--no-color";
4509                                 diff_index_argv[6] = "--";
4510                                 diff_index_argv[7] = "/dev/null";
4511                         }
4513                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4514                                 report("Failed to allocate diff command");
4515                                 break;
4516                         }
4517                         flags |= OPEN_PREPARED;
4518                 }
4520                 open_view(view, REQ_VIEW_DIFF, flags);
4521                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4522                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4523                 break;
4525         default:
4526                 return request;
4527         }
4529         return REQ_NONE;
4532 static bool
4533 blame_grep(struct view *view, struct line *line)
4535         struct blame *blame = line->data;
4536         struct blame_commit *commit = blame->commit;
4537         regmatch_t pmatch;
4539 #define MATCH(text, on)                                                 \
4540         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4542         if (commit) {
4543                 char buf[DATE_COLS + 1];
4545                 if (MATCH(commit->title, 1) ||
4546                     MATCH(commit->author, opt_author) ||
4547                     MATCH(commit->id, opt_date))
4548                         return TRUE;
4550                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4551                     MATCH(buf, 1))
4552                         return TRUE;
4553         }
4555         return MATCH(blame->text, 1);
4557 #undef MATCH
4560 static void
4561 blame_select(struct view *view, struct line *line)
4563         struct blame *blame = line->data;
4564         struct blame_commit *commit = blame->commit;
4566         if (!commit)
4567                 return;
4569         if (!strcmp(commit->id, NULL_ID))
4570                 string_ncopy(ref_commit, "HEAD", 4);
4571         else
4572                 string_copy_rev(ref_commit, commit->id);
4575 static struct view_ops blame_ops = {
4576         "line",
4577         NULL,
4578         blame_open,
4579         blame_read,
4580         blame_draw,
4581         blame_request,
4582         blame_grep,
4583         blame_select,
4584 };
4586 /*
4587  * Status backend
4588  */
4590 struct status {
4591         char status;
4592         struct {
4593                 mode_t mode;
4594                 char rev[SIZEOF_REV];
4595                 char name[SIZEOF_STR];
4596         } old;
4597         struct {
4598                 mode_t mode;
4599                 char rev[SIZEOF_REV];
4600                 char name[SIZEOF_STR];
4601         } new;
4602 };
4604 static char status_onbranch[SIZEOF_STR];
4605 static struct status stage_status;
4606 static enum line_type stage_line_type;
4607 static size_t stage_chunks;
4608 static int *stage_chunk;
4610 /* This should work even for the "On branch" line. */
4611 static inline bool
4612 status_has_none(struct view *view, struct line *line)
4614         return line < view->line + view->lines && !line[1].data;
4617 /* Get fields from the diff line:
4618  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4619  */
4620 static inline bool
4621 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4623         const char *old_mode = buf +  1;
4624         const char *new_mode = buf +  8;
4625         const char *old_rev  = buf + 15;
4626         const char *new_rev  = buf + 56;
4627         const char *status   = buf + 97;
4629         if (bufsize < 98 ||
4630             old_mode[-1] != ':' ||
4631             new_mode[-1] != ' ' ||
4632             old_rev[-1]  != ' ' ||
4633             new_rev[-1]  != ' ' ||
4634             status[-1]   != ' ')
4635                 return FALSE;
4637         file->status = *status;
4639         string_copy_rev(file->old.rev, old_rev);
4640         string_copy_rev(file->new.rev, new_rev);
4642         file->old.mode = strtoul(old_mode, NULL, 8);
4643         file->new.mode = strtoul(new_mode, NULL, 8);
4645         file->old.name[0] = file->new.name[0] = 0;
4647         return TRUE;
4650 static bool
4651 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4653         struct status *unmerged = NULL;
4654         char *buf;
4655         struct io io = {};
4657         if (!run_io(&io, argv, NULL, IO_RD))
4658                 return FALSE;
4660         add_line_data(view, NULL, type);
4662         while ((buf = io_get(&io, 0, TRUE))) {
4663                 struct status *file = unmerged;
4665                 if (!file) {
4666                         file = calloc(1, sizeof(*file));
4667                         if (!file || !add_line_data(view, file, type))
4668                                 goto error_out;
4669                 }
4671                 /* Parse diff info part. */
4672                 if (status) {
4673                         file->status = status;
4674                         if (status == 'A')
4675                                 string_copy(file->old.rev, NULL_ID);
4677                 } else if (!file->status || file == unmerged) {
4678                         if (!status_get_diff(file, buf, strlen(buf)))
4679                                 goto error_out;
4681                         buf = io_get(&io, 0, TRUE);
4682                         if (!buf)
4683                                 break;
4685                         /* Collapse all modified entries that follow an
4686                          * associated unmerged entry. */
4687                         if (unmerged == file) {
4688                                 unmerged->status = 'U';
4689                                 unmerged = NULL;
4690                         } else if (file->status == 'U') {
4691                                 unmerged = file;
4692                         }
4693                 }
4695                 /* Grab the old name for rename/copy. */
4696                 if (!*file->old.name &&
4697                     (file->status == 'R' || file->status == 'C')) {
4698                         string_ncopy(file->old.name, buf, strlen(buf));
4700                         buf = io_get(&io, 0, TRUE);
4701                         if (!buf)
4702                                 break;
4703                 }
4705                 /* git-ls-files just delivers a NUL separated list of
4706                  * file names similar to the second half of the
4707                  * git-diff-* output. */
4708                 string_ncopy(file->new.name, buf, strlen(buf));
4709                 if (!*file->old.name)
4710                         string_copy(file->old.name, file->new.name);
4711                 file = NULL;
4712         }
4714         if (io_error(&io)) {
4715 error_out:
4716                 done_io(&io);
4717                 return FALSE;
4718         }
4720         if (!view->line[view->lines - 1].data)
4721                 add_line_data(view, NULL, LINE_STAT_NONE);
4723         done_io(&io);
4724         return TRUE;
4727 /* Don't show unmerged entries in the staged section. */
4728 static const char *status_diff_index_argv[] = {
4729         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4730                              "--cached", "-M", "HEAD", NULL
4731 };
4733 static const char *status_diff_files_argv[] = {
4734         "git", "diff-files", "-z", NULL
4735 };
4737 static const char *status_list_other_argv[] = {
4738         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4739 };
4741 static const char *status_list_no_head_argv[] = {
4742         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4743 };
4745 static const char *update_index_argv[] = {
4746         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4747 };
4749 /* Restore the previous line number to stay in the context or select a
4750  * line with something that can be updated. */
4751 static void
4752 status_restore(struct view *view)
4754         if (view->p_lineno >= view->lines)
4755                 view->p_lineno = view->lines - 1;
4756         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4757                 view->p_lineno++;
4758         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4759                 view->p_lineno--;
4761         /* If the above fails, always skip the "On branch" line. */
4762         if (view->p_lineno < view->lines)
4763                 view->lineno = view->p_lineno;
4764         else
4765                 view->lineno = 1;
4767         if (view->lineno < view->offset)
4768                 view->offset = view->lineno;
4769         else if (view->offset + view->height <= view->lineno)
4770                 view->offset = view->lineno - view->height + 1;
4772         view->p_restore = FALSE;
4775 static void
4776 status_update_onbranch(void)
4778         static const char *paths[][2] = {
4779                 { "rebase-apply/rebasing",      "Rebasing" },
4780                 { "rebase-apply/applying",      "Applying mailbox" },
4781                 { "rebase-apply/",              "Rebasing mailbox" },
4782                 { "rebase-merge/interactive",   "Interactive rebase" },
4783                 { "rebase-merge/",              "Rebase merge" },
4784                 { "MERGE_HEAD",                 "Merging" },
4785                 { "BISECT_LOG",                 "Bisecting" },
4786                 { "HEAD",                       "On branch" },
4787         };
4788         char buf[SIZEOF_STR];
4789         struct stat stat;
4790         int i;
4792         if (is_initial_commit()) {
4793                 string_copy(status_onbranch, "Initial commit");
4794                 return;
4795         }
4797         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4798                 char *head = opt_head;
4800                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4801                     lstat(buf, &stat) < 0)
4802                         continue;
4804                 if (!*opt_head) {
4805                         struct io io = {};
4807                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4808                             io_open(&io, buf) &&
4809                             io_read_buf(&io, buf, sizeof(buf))) {
4810                                 head = chomp_string(buf);
4811                                 if (!prefixcmp(head, "refs/heads/"))
4812                                         head += STRING_SIZE("refs/heads/");
4813                         }
4814                 }
4816                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4817                         string_copy(status_onbranch, opt_head);
4818                 return;
4819         }
4821         string_copy(status_onbranch, "Not currently on any branch");
4824 /* First parse staged info using git-diff-index(1), then parse unstaged
4825  * info using git-diff-files(1), and finally untracked files using
4826  * git-ls-files(1). */
4827 static bool
4828 status_open(struct view *view)
4830         reset_view(view);
4832         add_line_data(view, NULL, LINE_STAT_HEAD);
4833         status_update_onbranch();
4835         run_io_bg(update_index_argv);
4837         if (is_initial_commit()) {
4838                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4839                         return FALSE;
4840         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4841                 return FALSE;
4842         }
4844         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4845             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4846                 return FALSE;
4848         /* Restore the exact position or use the specialized restore
4849          * mode? */
4850         if (!view->p_restore)
4851                 status_restore(view);
4852         return TRUE;
4855 static bool
4856 status_draw(struct view *view, struct line *line, unsigned int lineno)
4858         struct status *status = line->data;
4859         enum line_type type;
4860         const char *text;
4862         if (!status) {
4863                 switch (line->type) {
4864                 case LINE_STAT_STAGED:
4865                         type = LINE_STAT_SECTION;
4866                         text = "Changes to be committed:";
4867                         break;
4869                 case LINE_STAT_UNSTAGED:
4870                         type = LINE_STAT_SECTION;
4871                         text = "Changed but not updated:";
4872                         break;
4874                 case LINE_STAT_UNTRACKED:
4875                         type = LINE_STAT_SECTION;
4876                         text = "Untracked files:";
4877                         break;
4879                 case LINE_STAT_NONE:
4880                         type = LINE_DEFAULT;
4881                         text = "  (no files)";
4882                         break;
4884                 case LINE_STAT_HEAD:
4885                         type = LINE_STAT_HEAD;
4886                         text = status_onbranch;
4887                         break;
4889                 default:
4890                         return FALSE;
4891                 }
4892         } else {
4893                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4895                 buf[0] = status->status;
4896                 if (draw_text(view, line->type, buf, TRUE))
4897                         return TRUE;
4898                 type = LINE_DEFAULT;
4899                 text = status->new.name;
4900         }
4902         draw_text(view, type, text, TRUE);
4903         return TRUE;
4906 static enum request
4907 status_load_error(struct view *view, struct view *stage, const char *path)
4909         if (displayed_views() == 2 || display[current_view] != view)
4910                 maximize_view(view);
4911         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4912         return REQ_NONE;
4915 static enum request
4916 status_enter(struct view *view, struct line *line)
4918         struct status *status = line->data;
4919         const char *oldpath = status ? status->old.name : NULL;
4920         /* Diffs for unmerged entries are empty when passing the new
4921          * path, so leave it empty. */
4922         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4923         const char *info;
4924         enum open_flags split;
4925         struct view *stage = VIEW(REQ_VIEW_STAGE);
4927         if (line->type == LINE_STAT_NONE ||
4928             (!status && line[1].type == LINE_STAT_NONE)) {
4929                 report("No file to diff");
4930                 return REQ_NONE;
4931         }
4933         switch (line->type) {
4934         case LINE_STAT_STAGED:
4935                 if (is_initial_commit()) {
4936                         const char *no_head_diff_argv[] = {
4937                                 "git", "diff", "--no-color", "--patch-with-stat",
4938                                         "--", "/dev/null", newpath, NULL
4939                         };
4941                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4942                                 return status_load_error(view, stage, newpath);
4943                 } else {
4944                         const char *index_show_argv[] = {
4945                                 "git", "diff-index", "--root", "--patch-with-stat",
4946                                         "-C", "-M", "--cached", "HEAD", "--",
4947                                         oldpath, newpath, NULL
4948                         };
4950                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4951                                 return status_load_error(view, stage, newpath);
4952                 }
4954                 if (status)
4955                         info = "Staged changes to %s";
4956                 else
4957                         info = "Staged changes";
4958                 break;
4960         case LINE_STAT_UNSTAGED:
4961         {
4962                 const char *files_show_argv[] = {
4963                         "git", "diff-files", "--root", "--patch-with-stat",
4964                                 "-C", "-M", "--", oldpath, newpath, NULL
4965                 };
4967                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4968                         return status_load_error(view, stage, newpath);
4969                 if (status)
4970                         info = "Unstaged changes to %s";
4971                 else
4972                         info = "Unstaged changes";
4973                 break;
4974         }
4975         case LINE_STAT_UNTRACKED:
4976                 if (!newpath) {
4977                         report("No file to show");
4978                         return REQ_NONE;
4979                 }
4981                 if (!suffixcmp(status->new.name, -1, "/")) {
4982                         report("Cannot display a directory");
4983                         return REQ_NONE;
4984                 }
4986                 if (!prepare_update_file(stage, newpath))
4987                         return status_load_error(view, stage, newpath);
4988                 info = "Untracked file %s";
4989                 break;
4991         case LINE_STAT_HEAD:
4992                 return REQ_NONE;
4994         default:
4995                 die("line type %d not handled in switch", line->type);
4996         }
4998         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4999         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5000         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5001                 if (status) {
5002                         stage_status = *status;
5003                 } else {
5004                         memset(&stage_status, 0, sizeof(stage_status));
5005                 }
5007                 stage_line_type = line->type;
5008                 stage_chunks = 0;
5009                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5010         }
5012         return REQ_NONE;
5015 static bool
5016 status_exists(struct status *status, enum line_type type)
5018         struct view *view = VIEW(REQ_VIEW_STATUS);
5019         unsigned long lineno;
5021         for (lineno = 0; lineno < view->lines; lineno++) {
5022                 struct line *line = &view->line[lineno];
5023                 struct status *pos = line->data;
5025                 if (line->type != type)
5026                         continue;
5027                 if (!pos && (!status || !status->status) && line[1].data) {
5028                         select_view_line(view, lineno);
5029                         return TRUE;
5030                 }
5031                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5032                         select_view_line(view, lineno);
5033                         return TRUE;
5034                 }
5035         }
5037         return FALSE;
5041 static bool
5042 status_update_prepare(struct io *io, enum line_type type)
5044         const char *staged_argv[] = {
5045                 "git", "update-index", "-z", "--index-info", NULL
5046         };
5047         const char *others_argv[] = {
5048                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5049         };
5051         switch (type) {
5052         case LINE_STAT_STAGED:
5053                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5055         case LINE_STAT_UNSTAGED:
5056                 return run_io(io, others_argv, opt_cdup, IO_WR);
5058         case LINE_STAT_UNTRACKED:
5059                 return run_io(io, others_argv, NULL, IO_WR);
5061         default:
5062                 die("line type %d not handled in switch", type);
5063                 return FALSE;
5064         }
5067 static bool
5068 status_update_write(struct io *io, struct status *status, enum line_type type)
5070         char buf[SIZEOF_STR];
5071         size_t bufsize = 0;
5073         switch (type) {
5074         case LINE_STAT_STAGED:
5075                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5076                                         status->old.mode,
5077                                         status->old.rev,
5078                                         status->old.name, 0))
5079                         return FALSE;
5080                 break;
5082         case LINE_STAT_UNSTAGED:
5083         case LINE_STAT_UNTRACKED:
5084                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5085                         return FALSE;
5086                 break;
5088         default:
5089                 die("line type %d not handled in switch", type);
5090         }
5092         return io_write(io, buf, bufsize);
5095 static bool
5096 status_update_file(struct status *status, enum line_type type)
5098         struct io io = {};
5099         bool result;
5101         if (!status_update_prepare(&io, type))
5102                 return FALSE;
5104         result = status_update_write(&io, status, type);
5105         return done_io(&io) && result;
5108 static bool
5109 status_update_files(struct view *view, struct line *line)
5111         char buf[sizeof(view->ref)];
5112         struct io io = {};
5113         bool result = TRUE;
5114         struct line *pos = view->line + view->lines;
5115         int files = 0;
5116         int file, done;
5118         if (!status_update_prepare(&io, line->type))
5119                 return FALSE;
5121         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5122                 files++;
5124         string_copy(buf, view->ref);
5125         for (file = 0, done = 5; result && file < files; line++, file++) {
5126                 int almost_done = file * 100 / files;
5128                 if (almost_done > done) {
5129                         done = almost_done;
5130                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5131                                       file, files, done);
5132                         update_view_title(view);
5133                         doupdate();
5134                 }
5135                 result = status_update_write(&io, line->data, line->type);
5136         }
5137         string_copy(view->ref, buf);
5139         return done_io(&io) && result;
5142 static bool
5143 status_update(struct view *view)
5145         struct line *line = &view->line[view->lineno];
5147         assert(view->lines);
5149         if (!line->data) {
5150                 /* This should work even for the "On branch" line. */
5151                 if (line < view->line + view->lines && !line[1].data) {
5152                         report("Nothing to update");
5153                         return FALSE;
5154                 }
5156                 if (!status_update_files(view, line + 1)) {
5157                         report("Failed to update file status");
5158                         return FALSE;
5159                 }
5161         } else if (!status_update_file(line->data, line->type)) {
5162                 report("Failed to update file status");
5163                 return FALSE;
5164         }
5166         return TRUE;
5169 static bool
5170 status_revert(struct status *status, enum line_type type, bool has_none)
5172         if (!status || type != LINE_STAT_UNSTAGED) {
5173                 if (type == LINE_STAT_STAGED) {
5174                         report("Cannot revert changes to staged files");
5175                 } else if (type == LINE_STAT_UNTRACKED) {
5176                         report("Cannot revert changes to untracked files");
5177                 } else if (has_none) {
5178                         report("Nothing to revert");
5179                 } else {
5180                         report("Cannot revert changes to multiple files");
5181                 }
5182                 return FALSE;
5184         } else {
5185                 char mode[10] = "100644";
5186                 const char *reset_argv[] = {
5187                         "git", "update-index", "--cacheinfo", mode,
5188                                 status->old.rev, status->old.name, NULL
5189                 };
5190                 const char *checkout_argv[] = {
5191                         "git", "checkout", "--", status->old.name, NULL
5192                 };
5194                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5195                         return FALSE;
5196                 string_format(mode, "%o", status->old.mode);
5197                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5198                         run_io_fg(checkout_argv, opt_cdup);
5199         }
5202 static enum request
5203 status_request(struct view *view, enum request request, struct line *line)
5205         struct status *status = line->data;
5207         switch (request) {
5208         case REQ_STATUS_UPDATE:
5209                 if (!status_update(view))
5210                         return REQ_NONE;
5211                 break;
5213         case REQ_STATUS_REVERT:
5214                 if (!status_revert(status, line->type, status_has_none(view, line)))
5215                         return REQ_NONE;
5216                 break;
5218         case REQ_STATUS_MERGE:
5219                 if (!status || status->status != 'U') {
5220                         report("Merging only possible for files with unmerged status ('U').");
5221                         return REQ_NONE;
5222                 }
5223                 open_mergetool(status->new.name);
5224                 break;
5226         case REQ_EDIT:
5227                 if (!status)
5228                         return request;
5229                 if (status->status == 'D') {
5230                         report("File has been deleted.");
5231                         return REQ_NONE;
5232                 }
5234                 open_editor(status->status != '?', status->new.name);
5235                 break;
5237         case REQ_VIEW_BLAME:
5238                 if (status) {
5239                         string_copy(opt_file, status->new.name);
5240                         opt_ref[0] = 0;
5241                 }
5242                 return request;
5244         case REQ_ENTER:
5245                 /* After returning the status view has been split to
5246                  * show the stage view. No further reloading is
5247                  * necessary. */
5248                 return status_enter(view, line);
5250         case REQ_REFRESH:
5251                 /* Simply reload the view. */
5252                 break;
5254         default:
5255                 return request;
5256         }
5258         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5260         return REQ_NONE;
5263 static void
5264 status_select(struct view *view, struct line *line)
5266         struct status *status = line->data;
5267         char file[SIZEOF_STR] = "all files";
5268         const char *text;
5269         const char *key;
5271         if (status && !string_format(file, "'%s'", status->new.name))
5272                 return;
5274         if (!status && line[1].type == LINE_STAT_NONE)
5275                 line++;
5277         switch (line->type) {
5278         case LINE_STAT_STAGED:
5279                 text = "Press %s to unstage %s for commit";
5280                 break;
5282         case LINE_STAT_UNSTAGED:
5283                 text = "Press %s to stage %s for commit";
5284                 break;
5286         case LINE_STAT_UNTRACKED:
5287                 text = "Press %s to stage %s for addition";
5288                 break;
5290         case LINE_STAT_HEAD:
5291         case LINE_STAT_NONE:
5292                 text = "Nothing to update";
5293                 break;
5295         default:
5296                 die("line type %d not handled in switch", line->type);
5297         }
5299         if (status && status->status == 'U') {
5300                 text = "Press %s to resolve conflict in %s";
5301                 key = get_key(REQ_STATUS_MERGE);
5303         } else {
5304                 key = get_key(REQ_STATUS_UPDATE);
5305         }
5307         string_format(view->ref, text, key, file);
5310 static bool
5311 status_grep(struct view *view, struct line *line)
5313         struct status *status = line->data;
5314         enum { S_STATUS, S_NAME, S_END } state;
5315         char buf[2] = "?";
5316         regmatch_t pmatch;
5318         if (!status)
5319                 return FALSE;
5321         for (state = S_STATUS; state < S_END; state++) {
5322                 const char *text;
5324                 switch (state) {
5325                 case S_NAME:    text = status->new.name;        break;
5326                 case S_STATUS:
5327                         buf[0] = status->status;
5328                         text = buf;
5329                         break;
5331                 default:
5332                         return FALSE;
5333                 }
5335                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5336                         return TRUE;
5337         }
5339         return FALSE;
5342 static struct view_ops status_ops = {
5343         "file",
5344         NULL,
5345         status_open,
5346         NULL,
5347         status_draw,
5348         status_request,
5349         status_grep,
5350         status_select,
5351 };
5354 static bool
5355 stage_diff_write(struct io *io, struct line *line, struct line *end)
5357         while (line < end) {
5358                 if (!io_write(io, line->data, strlen(line->data)) ||
5359                     !io_write(io, "\n", 1))
5360                         return FALSE;
5361                 line++;
5362                 if (line->type == LINE_DIFF_CHUNK ||
5363                     line->type == LINE_DIFF_HEADER)
5364                         break;
5365         }
5367         return TRUE;
5370 static struct line *
5371 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5373         for (; view->line < line; line--)
5374                 if (line->type == type)
5375                         return line;
5377         return NULL;
5380 static bool
5381 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5383         const char *apply_argv[SIZEOF_ARG] = {
5384                 "git", "apply", "--whitespace=nowarn", NULL
5385         };
5386         struct line *diff_hdr;
5387         struct io io = {};
5388         int argc = 3;
5390         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5391         if (!diff_hdr)
5392                 return FALSE;
5394         if (!revert)
5395                 apply_argv[argc++] = "--cached";
5396         if (revert || stage_line_type == LINE_STAT_STAGED)
5397                 apply_argv[argc++] = "-R";
5398         apply_argv[argc++] = "-";
5399         apply_argv[argc++] = NULL;
5400         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5401                 return FALSE;
5403         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5404             !stage_diff_write(&io, chunk, view->line + view->lines))
5405                 chunk = NULL;
5407         done_io(&io);
5408         run_io_bg(update_index_argv);
5410         return chunk ? TRUE : FALSE;
5413 static bool
5414 stage_update(struct view *view, struct line *line)
5416         struct line *chunk = NULL;
5418         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5419                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5421         if (chunk) {
5422                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5423                         report("Failed to apply chunk");
5424                         return FALSE;
5425                 }
5427         } else if (!stage_status.status) {
5428                 view = VIEW(REQ_VIEW_STATUS);
5430                 for (line = view->line; line < view->line + view->lines; line++)
5431                         if (line->type == stage_line_type)
5432                                 break;
5434                 if (!status_update_files(view, line + 1)) {
5435                         report("Failed to update files");
5436                         return FALSE;
5437                 }
5439         } else if (!status_update_file(&stage_status, stage_line_type)) {
5440                 report("Failed to update file");
5441                 return FALSE;
5442         }
5444         return TRUE;
5447 static bool
5448 stage_revert(struct view *view, struct line *line)
5450         struct line *chunk = NULL;
5452         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5453                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5455         if (chunk) {
5456                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5457                         return FALSE;
5459                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5460                         report("Failed to revert chunk");
5461                         return FALSE;
5462                 }
5463                 return TRUE;
5465         } else {
5466                 return status_revert(stage_status.status ? &stage_status : NULL,
5467                                      stage_line_type, FALSE);
5468         }
5472 static void
5473 stage_next(struct view *view, struct line *line)
5475         int i;
5477         if (!stage_chunks) {
5478                 static size_t alloc = 0;
5479                 int *tmp;
5481                 for (line = view->line; line < view->line + view->lines; line++) {
5482                         if (line->type != LINE_DIFF_CHUNK)
5483                                 continue;
5485                         tmp = realloc_items(stage_chunk, &alloc,
5486                                             stage_chunks, sizeof(*tmp));
5487                         if (!tmp) {
5488                                 report("Allocation failure");
5489                                 return;
5490                         }
5492                         stage_chunk = tmp;
5493                         stage_chunk[stage_chunks++] = line - view->line;
5494                 }
5495         }
5497         for (i = 0; i < stage_chunks; i++) {
5498                 if (stage_chunk[i] > view->lineno) {
5499                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5500                         report("Chunk %d of %d", i + 1, stage_chunks);
5501                         return;
5502                 }
5503         }
5505         report("No next chunk found");
5508 static enum request
5509 stage_request(struct view *view, enum request request, struct line *line)
5511         switch (request) {
5512         case REQ_STATUS_UPDATE:
5513                 if (!stage_update(view, line))
5514                         return REQ_NONE;
5515                 break;
5517         case REQ_STATUS_REVERT:
5518                 if (!stage_revert(view, line))
5519                         return REQ_NONE;
5520                 break;
5522         case REQ_STAGE_NEXT:
5523                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5524                         report("File is untracked; press %s to add",
5525                                get_key(REQ_STATUS_UPDATE));
5526                         return REQ_NONE;
5527                 }
5528                 stage_next(view, line);
5529                 return REQ_NONE;
5531         case REQ_EDIT:
5532                 if (!stage_status.new.name[0])
5533                         return request;
5534                 if (stage_status.status == 'D') {
5535                         report("File has been deleted.");
5536                         return REQ_NONE;
5537                 }
5539                 open_editor(stage_status.status != '?', stage_status.new.name);
5540                 break;
5542         case REQ_REFRESH:
5543                 /* Reload everything ... */
5544                 break;
5546         case REQ_VIEW_BLAME:
5547                 if (stage_status.new.name[0]) {
5548                         string_copy(opt_file, stage_status.new.name);
5549                         opt_ref[0] = 0;
5550                 }
5551                 return request;
5553         case REQ_ENTER:
5554                 return pager_request(view, request, line);
5556         default:
5557                 return request;
5558         }
5560         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5561         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5563         /* Check whether the staged entry still exists, and close the
5564          * stage view if it doesn't. */
5565         if (!status_exists(&stage_status, stage_line_type)) {
5566                 status_restore(VIEW(REQ_VIEW_STATUS));
5567                 return REQ_VIEW_CLOSE;
5568         }
5570         if (stage_line_type == LINE_STAT_UNTRACKED) {
5571                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5572                         report("Cannot display a directory");
5573                         return REQ_NONE;
5574                 }
5576                 if (!prepare_update_file(view, stage_status.new.name)) {
5577                         report("Failed to open file: %s", strerror(errno));
5578                         return REQ_NONE;
5579                 }
5580         }
5581         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5583         return REQ_NONE;
5586 static struct view_ops stage_ops = {
5587         "line",
5588         NULL,
5589         NULL,
5590         pager_read,
5591         pager_draw,
5592         stage_request,
5593         pager_grep,
5594         pager_select,
5595 };
5598 /*
5599  * Revision graph
5600  */
5602 struct commit {
5603         char id[SIZEOF_REV];            /* SHA1 ID. */
5604         char title[128];                /* First line of the commit message. */
5605         char author[75];                /* Author of the commit. */
5606         struct tm time;                 /* Date from the author ident. */
5607         struct ref **refs;              /* Repository references. */
5608         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5609         size_t graph_size;              /* The width of the graph array. */
5610         bool has_parents;               /* Rewritten --parents seen. */
5611 };
5613 /* Size of rev graph with no  "padding" columns */
5614 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5616 struct rev_graph {
5617         struct rev_graph *prev, *next, *parents;
5618         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5619         size_t size;
5620         struct commit *commit;
5621         size_t pos;
5622         unsigned int boundary:1;
5623 };
5625 /* Parents of the commit being visualized. */
5626 static struct rev_graph graph_parents[4];
5628 /* The current stack of revisions on the graph. */
5629 static struct rev_graph graph_stacks[4] = {
5630         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5631         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5632         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5633         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5634 };
5636 static inline bool
5637 graph_parent_is_merge(struct rev_graph *graph)
5639         return graph->parents->size > 1;
5642 static inline void
5643 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5645         struct commit *commit = graph->commit;
5647         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5648                 commit->graph[commit->graph_size++] = symbol;
5651 static void
5652 clear_rev_graph(struct rev_graph *graph)
5654         graph->boundary = 0;
5655         graph->size = graph->pos = 0;
5656         graph->commit = NULL;
5657         memset(graph->parents, 0, sizeof(*graph->parents));
5660 static void
5661 done_rev_graph(struct rev_graph *graph)
5663         if (graph_parent_is_merge(graph) &&
5664             graph->pos < graph->size - 1 &&
5665             graph->next->size == graph->size + graph->parents->size - 1) {
5666                 size_t i = graph->pos + graph->parents->size - 1;
5668                 graph->commit->graph_size = i * 2;
5669                 while (i < graph->next->size - 1) {
5670                         append_to_rev_graph(graph, ' ');
5671                         append_to_rev_graph(graph, '\\');
5672                         i++;
5673                 }
5674         }
5676         clear_rev_graph(graph);
5679 static void
5680 push_rev_graph(struct rev_graph *graph, const char *parent)
5682         int i;
5684         /* "Collapse" duplicate parents lines.
5685          *
5686          * FIXME: This needs to also update update the drawn graph but
5687          * for now it just serves as a method for pruning graph lines. */
5688         for (i = 0; i < graph->size; i++)
5689                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5690                         return;
5692         if (graph->size < SIZEOF_REVITEMS) {
5693                 string_copy_rev(graph->rev[graph->size++], parent);
5694         }
5697 static chtype
5698 get_rev_graph_symbol(struct rev_graph *graph)
5700         chtype symbol;
5702         if (graph->boundary)
5703                 symbol = REVGRAPH_BOUND;
5704         else if (graph->parents->size == 0)
5705                 symbol = REVGRAPH_INIT;
5706         else if (graph_parent_is_merge(graph))
5707                 symbol = REVGRAPH_MERGE;
5708         else if (graph->pos >= graph->size)
5709                 symbol = REVGRAPH_BRANCH;
5710         else
5711                 symbol = REVGRAPH_COMMIT;
5713         return symbol;
5716 static void
5717 draw_rev_graph(struct rev_graph *graph)
5719         struct rev_filler {
5720                 chtype separator, line;
5721         };
5722         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5723         static struct rev_filler fillers[] = {
5724                 { ' ',  '|' },
5725                 { '`',  '.' },
5726                 { '\'', ' ' },
5727                 { '/',  ' ' },
5728         };
5729         chtype symbol = get_rev_graph_symbol(graph);
5730         struct rev_filler *filler;
5731         size_t i;
5733         if (opt_line_graphics)
5734                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5736         filler = &fillers[DEFAULT];
5738         for (i = 0; i < graph->pos; i++) {
5739                 append_to_rev_graph(graph, filler->line);
5740                 if (graph_parent_is_merge(graph->prev) &&
5741                     graph->prev->pos == i)
5742                         filler = &fillers[RSHARP];
5744                 append_to_rev_graph(graph, filler->separator);
5745         }
5747         /* Place the symbol for this revision. */
5748         append_to_rev_graph(graph, symbol);
5750         if (graph->prev->size > graph->size)
5751                 filler = &fillers[RDIAG];
5752         else
5753                 filler = &fillers[DEFAULT];
5755         i++;
5757         for (; i < graph->size; i++) {
5758                 append_to_rev_graph(graph, filler->separator);
5759                 append_to_rev_graph(graph, filler->line);
5760                 if (graph_parent_is_merge(graph->prev) &&
5761                     i < graph->prev->pos + graph->parents->size)
5762                         filler = &fillers[RSHARP];
5763                 if (graph->prev->size > graph->size)
5764                         filler = &fillers[LDIAG];
5765         }
5767         if (graph->prev->size > graph->size) {
5768                 append_to_rev_graph(graph, filler->separator);
5769                 if (filler->line != ' ')
5770                         append_to_rev_graph(graph, filler->line);
5771         }
5774 /* Prepare the next rev graph */
5775 static void
5776 prepare_rev_graph(struct rev_graph *graph)
5778         size_t i;
5780         /* First, traverse all lines of revisions up to the active one. */
5781         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5782                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5783                         break;
5785                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5786         }
5788         /* Interleave the new revision parent(s). */
5789         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5790                 push_rev_graph(graph->next, graph->parents->rev[i]);
5792         /* Lastly, put any remaining revisions. */
5793         for (i = graph->pos + 1; i < graph->size; i++)
5794                 push_rev_graph(graph->next, graph->rev[i]);
5797 static void
5798 update_rev_graph(struct view *view, struct rev_graph *graph)
5800         /* If this is the finalizing update ... */
5801         if (graph->commit)
5802                 prepare_rev_graph(graph);
5804         /* Graph visualization needs a one rev look-ahead,
5805          * so the first update doesn't visualize anything. */
5806         if (!graph->prev->commit)
5807                 return;
5809         if (view->lines > 2)
5810                 view->line[view->lines - 3].dirty = 1;
5811         if (view->lines > 1)
5812                 view->line[view->lines - 2].dirty = 1;
5813         draw_rev_graph(graph->prev);
5814         done_rev_graph(graph->prev->prev);
5818 /*
5819  * Main view backend
5820  */
5822 static const char *main_argv[SIZEOF_ARG] = {
5823         "git", "log", "--no-color", "--pretty=raw", "--parents",
5824                       "--topo-order", "%(head)", NULL
5825 };
5827 static bool
5828 main_draw(struct view *view, struct line *line, unsigned int lineno)
5830         struct commit *commit = line->data;
5832         if (!*commit->author)
5833                 return FALSE;
5835         if (opt_date && draw_date(view, &commit->time))
5836                 return TRUE;
5838         if (opt_author && draw_author(view, commit->author))
5839                 return TRUE;
5841         if (opt_rev_graph && commit->graph_size &&
5842             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5843                 return TRUE;
5845         if (opt_show_refs && commit->refs) {
5846                 size_t i = 0;
5848                 do {
5849                         enum line_type type;
5851                         if (commit->refs[i]->head)
5852                                 type = LINE_MAIN_HEAD;
5853                         else if (commit->refs[i]->ltag)
5854                                 type = LINE_MAIN_LOCAL_TAG;
5855                         else if (commit->refs[i]->tag)
5856                                 type = LINE_MAIN_TAG;
5857                         else if (commit->refs[i]->tracked)
5858                                 type = LINE_MAIN_TRACKED;
5859                         else if (commit->refs[i]->remote)
5860                                 type = LINE_MAIN_REMOTE;
5861                         else
5862                                 type = LINE_MAIN_REF;
5864                         if (draw_text(view, type, "[", TRUE) ||
5865                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5866                             draw_text(view, type, "]", TRUE))
5867                                 return TRUE;
5869                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5870                                 return TRUE;
5871                 } while (commit->refs[i++]->next);
5872         }
5874         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5875         return TRUE;
5878 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5879 static bool
5880 main_read(struct view *view, char *line)
5882         static struct rev_graph *graph = graph_stacks;
5883         enum line_type type;
5884         struct commit *commit;
5886         if (!line) {
5887                 int i;
5889                 if (!view->lines && !view->parent)
5890                         die("No revisions match the given arguments.");
5891                 if (view->lines > 0) {
5892                         commit = view->line[view->lines - 1].data;
5893                         view->line[view->lines - 1].dirty = 1;
5894                         if (!*commit->author) {
5895                                 view->lines--;
5896                                 free(commit);
5897                                 graph->commit = NULL;
5898                         }
5899                 }
5900                 update_rev_graph(view, graph);
5902                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5903                         clear_rev_graph(&graph_stacks[i]);
5904                 return TRUE;
5905         }
5907         type = get_line_type(line);
5908         if (type == LINE_COMMIT) {
5909                 commit = calloc(1, sizeof(struct commit));
5910                 if (!commit)
5911                         return FALSE;
5913                 line += STRING_SIZE("commit ");
5914                 if (*line == '-') {
5915                         graph->boundary = 1;
5916                         line++;
5917                 }
5919                 string_copy_rev(commit->id, line);
5920                 commit->refs = get_refs(commit->id);
5921                 graph->commit = commit;
5922                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5924                 while ((line = strchr(line, ' '))) {
5925                         line++;
5926                         push_rev_graph(graph->parents, line);
5927                         commit->has_parents = TRUE;
5928                 }
5929                 return TRUE;
5930         }
5932         if (!view->lines)
5933                 return TRUE;
5934         commit = view->line[view->lines - 1].data;
5936         switch (type) {
5937         case LINE_PARENT:
5938                 if (commit->has_parents)
5939                         break;
5940                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5941                 break;
5943         case LINE_AUTHOR:
5944                 parse_author_line(line + STRING_SIZE("author "),
5945                                   commit->author, sizeof(commit->author),
5946                                   &commit->time);
5947                 update_rev_graph(view, graph);
5948                 graph = graph->next;
5949                 break;
5951         default:
5952                 /* Fill in the commit title if it has not already been set. */
5953                 if (commit->title[0])
5954                         break;
5956                 /* Require titles to start with a non-space character at the
5957                  * offset used by git log. */
5958                 if (strncmp(line, "    ", 4))
5959                         break;
5960                 line += 4;
5961                 /* Well, if the title starts with a whitespace character,
5962                  * try to be forgiving.  Otherwise we end up with no title. */
5963                 while (isspace(*line))
5964                         line++;
5965                 if (*line == '\0')
5966                         break;
5967                 /* FIXME: More graceful handling of titles; append "..." to
5968                  * shortened titles, etc. */
5970                 string_expand(commit->title, sizeof(commit->title), line, 1);
5971                 view->line[view->lines - 1].dirty = 1;
5972         }
5974         return TRUE;
5977 static enum request
5978 main_request(struct view *view, enum request request, struct line *line)
5980         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5982         switch (request) {
5983         case REQ_ENTER:
5984                 open_view(view, REQ_VIEW_DIFF, flags);
5985                 break;
5986         case REQ_REFRESH:
5987                 load_refs();
5988                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5989                 break;
5990         default:
5991                 return request;
5992         }
5994         return REQ_NONE;
5997 static bool
5998 grep_refs(struct ref **refs, regex_t *regex)
6000         regmatch_t pmatch;
6001         size_t i = 0;
6003         if (!refs)
6004                 return FALSE;
6005         do {
6006                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6007                         return TRUE;
6008         } while (refs[i++]->next);
6010         return FALSE;
6013 static bool
6014 main_grep(struct view *view, struct line *line)
6016         struct commit *commit = line->data;
6017         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6018         char buf[DATE_COLS + 1];
6019         regmatch_t pmatch;
6021         for (state = S_TITLE; state < S_END; state++) {
6022                 char *text;
6024                 switch (state) {
6025                 case S_TITLE:   text = commit->title;   break;
6026                 case S_AUTHOR:
6027                         if (!opt_author)
6028                                 continue;
6029                         text = commit->author;
6030                         break;
6031                 case S_DATE:
6032                         if (!opt_date)
6033                                 continue;
6034                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6035                                 continue;
6036                         text = buf;
6037                         break;
6038                 case S_REFS:
6039                         if (!opt_show_refs)
6040                                 continue;
6041                         if (grep_refs(commit->refs, view->regex) == TRUE)
6042                                 return TRUE;
6043                         continue;
6044                 default:
6045                         return FALSE;
6046                 }
6048                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6049                         return TRUE;
6050         }
6052         return FALSE;
6055 static void
6056 main_select(struct view *view, struct line *line)
6058         struct commit *commit = line->data;
6060         string_copy_rev(view->ref, commit->id);
6061         string_copy_rev(ref_commit, view->ref);
6064 static struct view_ops main_ops = {
6065         "commit",
6066         main_argv,
6067         NULL,
6068         main_read,
6069         main_draw,
6070         main_request,
6071         main_grep,
6072         main_select,
6073 };
6076 /*
6077  * Unicode / UTF-8 handling
6078  *
6079  * NOTE: Much of the following code for dealing with Unicode is derived from
6080  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6081  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6082  */
6084 static inline int
6085 unicode_width(unsigned long c)
6087         if (c >= 0x1100 &&
6088            (c <= 0x115f                         /* Hangul Jamo */
6089             || c == 0x2329
6090             || c == 0x232a
6091             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6092                                                 /* CJK ... Yi */
6093             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6094             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6095             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6096             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6097             || (c >= 0xffe0  && c <= 0xffe6)
6098             || (c >= 0x20000 && c <= 0x2fffd)
6099             || (c >= 0x30000 && c <= 0x3fffd)))
6100                 return 2;
6102         if (c == '\t')
6103                 return opt_tab_size;
6105         return 1;
6108 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6109  * Illegal bytes are set one. */
6110 static const unsigned char utf8_bytes[256] = {
6111         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,
6112         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,
6113         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,
6114         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,
6115         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,
6116         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,
6117         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,
6118         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,
6119 };
6121 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6122 static inline unsigned long
6123 utf8_to_unicode(const char *string, size_t length)
6125         unsigned long unicode;
6127         switch (length) {
6128         case 1:
6129                 unicode  =   string[0];
6130                 break;
6131         case 2:
6132                 unicode  =  (string[0] & 0x1f) << 6;
6133                 unicode +=  (string[1] & 0x3f);
6134                 break;
6135         case 3:
6136                 unicode  =  (string[0] & 0x0f) << 12;
6137                 unicode += ((string[1] & 0x3f) << 6);
6138                 unicode +=  (string[2] & 0x3f);
6139                 break;
6140         case 4:
6141                 unicode  =  (string[0] & 0x0f) << 18;
6142                 unicode += ((string[1] & 0x3f) << 12);
6143                 unicode += ((string[2] & 0x3f) << 6);
6144                 unicode +=  (string[3] & 0x3f);
6145                 break;
6146         case 5:
6147                 unicode  =  (string[0] & 0x0f) << 24;
6148                 unicode += ((string[1] & 0x3f) << 18);
6149                 unicode += ((string[2] & 0x3f) << 12);
6150                 unicode += ((string[3] & 0x3f) << 6);
6151                 unicode +=  (string[4] & 0x3f);
6152                 break;
6153         case 6:
6154                 unicode  =  (string[0] & 0x01) << 30;
6155                 unicode += ((string[1] & 0x3f) << 24);
6156                 unicode += ((string[2] & 0x3f) << 18);
6157                 unicode += ((string[3] & 0x3f) << 12);
6158                 unicode += ((string[4] & 0x3f) << 6);
6159                 unicode +=  (string[5] & 0x3f);
6160                 break;
6161         default:
6162                 die("Invalid Unicode length");
6163         }
6165         /* Invalid characters could return the special 0xfffd value but NUL
6166          * should be just as good. */
6167         return unicode > 0xffff ? 0 : unicode;
6170 /* Calculates how much of string can be shown within the given maximum width
6171  * and sets trimmed parameter to non-zero value if all of string could not be
6172  * shown. If the reserve flag is TRUE, it will reserve at least one
6173  * trailing character, which can be useful when drawing a delimiter.
6174  *
6175  * Returns the number of bytes to output from string to satisfy max_width. */
6176 static size_t
6177 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6179         const char *string = *start;
6180         const char *end = strchr(string, '\0');
6181         unsigned char last_bytes = 0;
6182         size_t last_ucwidth = 0;
6184         *width = 0;
6185         *trimmed = 0;
6187         while (string < end) {
6188                 int c = *(unsigned char *) string;
6189                 unsigned char bytes = utf8_bytes[c];
6190                 size_t ucwidth;
6191                 unsigned long unicode;
6193                 if (string + bytes > end)
6194                         break;
6196                 /* Change representation to figure out whether
6197                  * it is a single- or double-width character. */
6199                 unicode = utf8_to_unicode(string, bytes);
6200                 /* FIXME: Graceful handling of invalid Unicode character. */
6201                 if (!unicode)
6202                         break;
6204                 ucwidth = unicode_width(unicode);
6205                 if (skip > 0) {
6206                         skip -= ucwidth <= skip ? ucwidth : skip;
6207                         *start += bytes;
6208                 }
6209                 *width  += ucwidth;
6210                 if (*width > max_width) {
6211                         *trimmed = 1;
6212                         *width -= ucwidth;
6213                         if (reserve && *width == max_width) {
6214                                 string -= last_bytes;
6215                                 *width -= last_ucwidth;
6216                         }
6217                         break;
6218                 }
6220                 string  += bytes;
6221                 last_bytes = ucwidth ? bytes : 0;
6222                 last_ucwidth = ucwidth;
6223         }
6225         return string - *start;
6229 /*
6230  * Status management
6231  */
6233 /* Whether or not the curses interface has been initialized. */
6234 static bool cursed = FALSE;
6236 /* Terminal hacks and workarounds. */
6237 static bool use_scroll_redrawwin;
6238 static bool use_scroll_status_wclear;
6240 /* The status window is used for polling keystrokes. */
6241 static WINDOW *status_win;
6243 /* Reading from the prompt? */
6244 static bool input_mode = FALSE;
6246 static bool status_empty = FALSE;
6248 /* Update status and title window. */
6249 static void
6250 report(const char *msg, ...)
6252         struct view *view = display[current_view];
6254         if (input_mode)
6255                 return;
6257         if (!view) {
6258                 char buf[SIZEOF_STR];
6259                 va_list args;
6261                 va_start(args, msg);
6262                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6263                         buf[sizeof(buf) - 1] = 0;
6264                         buf[sizeof(buf) - 2] = '.';
6265                         buf[sizeof(buf) - 3] = '.';
6266                         buf[sizeof(buf) - 4] = '.';
6267                 }
6268                 va_end(args);
6269                 die("%s", buf);
6270         }
6272         if (!status_empty || *msg) {
6273                 va_list args;
6275                 va_start(args, msg);
6277                 wmove(status_win, 0, 0);
6278                 if (view->has_scrolled && use_scroll_status_wclear)
6279                         wclear(status_win);
6280                 if (*msg) {
6281                         vwprintw(status_win, msg, args);
6282                         status_empty = FALSE;
6283                 } else {
6284                         status_empty = TRUE;
6285                 }
6286                 wclrtoeol(status_win);
6287                 wnoutrefresh(status_win);
6289                 va_end(args);
6290         }
6292         update_view_title(view);
6295 /* Controls when nodelay should be in effect when polling user input. */
6296 static void
6297 set_nonblocking_input(bool loading)
6299         static unsigned int loading_views;
6301         if ((loading == FALSE && loading_views-- == 1) ||
6302             (loading == TRUE  && loading_views++ == 0))
6303                 nodelay(status_win, loading);
6306 static void
6307 init_display(void)
6309         const char *term;
6310         int x, y;
6312         /* Initialize the curses library */
6313         if (isatty(STDIN_FILENO)) {
6314                 cursed = !!initscr();
6315                 opt_tty = stdin;
6316         } else {
6317                 /* Leave stdin and stdout alone when acting as a pager. */
6318                 opt_tty = fopen("/dev/tty", "r+");
6319                 if (!opt_tty)
6320                         die("Failed to open /dev/tty");
6321                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6322         }
6324         if (!cursed)
6325                 die("Failed to initialize curses");
6327         nonl();         /* Disable conversion and detect newlines from input. */
6328         cbreak();       /* Take input chars one at a time, no wait for \n */
6329         noecho();       /* Don't echo input */
6330         leaveok(stdscr, FALSE);
6332         if (has_colors())
6333                 init_colors();
6335         getmaxyx(stdscr, y, x);
6336         status_win = newwin(1, 0, y - 1, 0);
6337         if (!status_win)
6338                 die("Failed to create status window");
6340         /* Enable keyboard mapping */
6341         keypad(status_win, TRUE);
6342         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6344         TABSIZE = opt_tab_size;
6345         if (opt_line_graphics) {
6346                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6347         }
6349         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6350         if (term && !strcmp(term, "gnome-terminal")) {
6351                 /* In the gnome-terminal-emulator, the message from
6352                  * scrolling up one line when impossible followed by
6353                  * scrolling down one line causes corruption of the
6354                  * status line. This is fixed by calling wclear. */
6355                 use_scroll_status_wclear = TRUE;
6356                 use_scroll_redrawwin = FALSE;
6358         } else if (term && !strcmp(term, "xrvt-xpm")) {
6359                 /* No problems with full optimizations in xrvt-(unicode)
6360                  * and aterm. */
6361                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6363         } else {
6364                 /* When scrolling in (u)xterm the last line in the
6365                  * scrolling direction will update slowly. */
6366                 use_scroll_redrawwin = TRUE;
6367                 use_scroll_status_wclear = FALSE;
6368         }
6371 static int
6372 get_input(int prompt_position)
6374         struct view *view;
6375         int i, key, cursor_y, cursor_x;
6377         if (prompt_position)
6378                 input_mode = TRUE;
6380         while (TRUE) {
6381                 foreach_view (view, i) {
6382                         update_view(view);
6383                         if (view_is_displayed(view) && view->has_scrolled &&
6384                             use_scroll_redrawwin)
6385                                 redrawwin(view->win);
6386                         view->has_scrolled = FALSE;
6387                 }
6389                 /* Update the cursor position. */
6390                 if (prompt_position) {
6391                         getbegyx(status_win, cursor_y, cursor_x);
6392                         cursor_x = prompt_position;
6393                 } else {
6394                         view = display[current_view];
6395                         getbegyx(view->win, cursor_y, cursor_x);
6396                         cursor_x = view->width - 1;
6397                         cursor_y += view->lineno - view->offset;
6398                 }
6399                 setsyx(cursor_y, cursor_x);
6401                 /* Refresh, accept single keystroke of input */
6402                 doupdate();
6403                 key = wgetch(status_win);
6405                 /* wgetch() with nodelay() enabled returns ERR when
6406                  * there's no input. */
6407                 if (key == ERR) {
6409                 } else if (key == KEY_RESIZE) {
6410                         int height, width;
6412                         getmaxyx(stdscr, height, width);
6414                         wresize(status_win, 1, width);
6415                         mvwin(status_win, height - 1, 0);
6416                         wnoutrefresh(status_win);
6417                         resize_display();
6418                         redraw_display(TRUE);
6420                 } else {
6421                         input_mode = FALSE;
6422                         return key;
6423                 }
6424         }
6427 static char *
6428 prompt_input(const char *prompt, input_handler handler, void *data)
6430         enum input_status status = INPUT_OK;
6431         static char buf[SIZEOF_STR];
6432         size_t pos = 0;
6434         buf[pos] = 0;
6436         while (status == INPUT_OK || status == INPUT_SKIP) {
6437                 int key;
6439                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6440                 wclrtoeol(status_win);
6442                 key = get_input(pos + 1);
6443                 switch (key) {
6444                 case KEY_RETURN:
6445                 case KEY_ENTER:
6446                 case '\n':
6447                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6448                         break;
6450                 case KEY_BACKSPACE:
6451                         if (pos > 0)
6452                                 buf[--pos] = 0;
6453                         else
6454                                 status = INPUT_CANCEL;
6455                         break;
6457                 case KEY_ESC:
6458                         status = INPUT_CANCEL;
6459                         break;
6461                 default:
6462                         if (pos >= sizeof(buf)) {
6463                                 report("Input string too long");
6464                                 return NULL;
6465                         }
6467                         status = handler(data, buf, key);
6468                         if (status == INPUT_OK)
6469                                 buf[pos++] = (char) key;
6470                 }
6471         }
6473         /* Clear the status window */
6474         status_empty = FALSE;
6475         report("");
6477         if (status == INPUT_CANCEL)
6478                 return NULL;
6480         buf[pos++] = 0;
6482         return buf;
6485 static enum input_status
6486 prompt_yesno_handler(void *data, char *buf, int c)
6488         if (c == 'y' || c == 'Y')
6489                 return INPUT_STOP;
6490         if (c == 'n' || c == 'N')
6491                 return INPUT_CANCEL;
6492         return INPUT_SKIP;
6495 static bool
6496 prompt_yesno(const char *prompt)
6498         char prompt2[SIZEOF_STR];
6500         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6501                 return FALSE;
6503         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6506 static enum input_status
6507 read_prompt_handler(void *data, char *buf, int c)
6509         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6512 static char *
6513 read_prompt(const char *prompt)
6515         return prompt_input(prompt, read_prompt_handler, NULL);
6518 /*
6519  * Repository properties
6520  */
6522 static struct ref *refs = NULL;
6523 static size_t refs_alloc = 0;
6524 static size_t refs_size = 0;
6526 /* Id <-> ref store */
6527 static struct ref ***id_refs = NULL;
6528 static size_t id_refs_alloc = 0;
6529 static size_t id_refs_size = 0;
6531 static int
6532 compare_refs(const void *ref1_, const void *ref2_)
6534         const struct ref *ref1 = *(const struct ref **)ref1_;
6535         const struct ref *ref2 = *(const struct ref **)ref2_;
6537         if (ref1->tag != ref2->tag)
6538                 return ref2->tag - ref1->tag;
6539         if (ref1->ltag != ref2->ltag)
6540                 return ref2->ltag - ref2->ltag;
6541         if (ref1->head != ref2->head)
6542                 return ref2->head - ref1->head;
6543         if (ref1->tracked != ref2->tracked)
6544                 return ref2->tracked - ref1->tracked;
6545         if (ref1->remote != ref2->remote)
6546                 return ref2->remote - ref1->remote;
6547         return strcmp(ref1->name, ref2->name);
6550 static struct ref **
6551 get_refs(const char *id)
6553         struct ref ***tmp_id_refs;
6554         struct ref **ref_list = NULL;
6555         size_t ref_list_alloc = 0;
6556         size_t ref_list_size = 0;
6557         size_t i;
6559         for (i = 0; i < id_refs_size; i++)
6560                 if (!strcmp(id, id_refs[i][0]->id))
6561                         return id_refs[i];
6563         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6564                                     sizeof(*id_refs));
6565         if (!tmp_id_refs)
6566                 return NULL;
6568         id_refs = tmp_id_refs;
6570         for (i = 0; i < refs_size; i++) {
6571                 struct ref **tmp;
6573                 if (strcmp(id, refs[i].id))
6574                         continue;
6576                 tmp = realloc_items(ref_list, &ref_list_alloc,
6577                                     ref_list_size + 1, sizeof(*ref_list));
6578                 if (!tmp) {
6579                         if (ref_list)
6580                                 free(ref_list);
6581                         return NULL;
6582                 }
6584                 ref_list = tmp;
6585                 ref_list[ref_list_size] = &refs[i];
6586                 /* XXX: The properties of the commit chains ensures that we can
6587                  * safely modify the shared ref. The repo references will
6588                  * always be similar for the same id. */
6589                 ref_list[ref_list_size]->next = 1;
6591                 ref_list_size++;
6592         }
6594         if (ref_list) {
6595                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6596                 ref_list[ref_list_size - 1]->next = 0;
6597                 id_refs[id_refs_size++] = ref_list;
6598         }
6600         return ref_list;
6603 static int
6604 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6606         struct ref *ref;
6607         bool tag = FALSE;
6608         bool ltag = FALSE;
6609         bool remote = FALSE;
6610         bool tracked = FALSE;
6611         bool check_replace = FALSE;
6612         bool head = FALSE;
6614         if (!prefixcmp(name, "refs/tags/")) {
6615                 if (!suffixcmp(name, namelen, "^{}")) {
6616                         namelen -= 3;
6617                         name[namelen] = 0;
6618                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6619                                 check_replace = TRUE;
6620                 } else {
6621                         ltag = TRUE;
6622                 }
6624                 tag = TRUE;
6625                 namelen -= STRING_SIZE("refs/tags/");
6626                 name    += STRING_SIZE("refs/tags/");
6628         } else if (!prefixcmp(name, "refs/remotes/")) {
6629                 remote = TRUE;
6630                 namelen -= STRING_SIZE("refs/remotes/");
6631                 name    += STRING_SIZE("refs/remotes/");
6632                 tracked  = !strcmp(opt_remote, name);
6634         } else if (!prefixcmp(name, "refs/heads/")) {
6635                 namelen -= STRING_SIZE("refs/heads/");
6636                 name    += STRING_SIZE("refs/heads/");
6637                 head     = !strncmp(opt_head, name, namelen);
6639         } else if (!strcmp(name, "HEAD")) {
6640                 string_ncopy(opt_head_rev, id, idlen);
6641                 return OK;
6642         }
6644         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6645                 /* it's an annotated tag, replace the previous SHA1 with the
6646                  * resolved commit id; relies on the fact git-ls-remote lists
6647                  * the commit id of an annotated tag right before the commit id
6648                  * it points to. */
6649                 refs[refs_size - 1].ltag = ltag;
6650                 string_copy_rev(refs[refs_size - 1].id, id);
6652                 return OK;
6653         }
6654         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6655         if (!refs)
6656                 return ERR;
6658         ref = &refs[refs_size++];
6659         ref->name = malloc(namelen + 1);
6660         if (!ref->name)
6661                 return ERR;
6663         strncpy(ref->name, name, namelen);
6664         ref->name[namelen] = 0;
6665         ref->head = head;
6666         ref->tag = tag;
6667         ref->ltag = ltag;
6668         ref->remote = remote;
6669         ref->tracked = tracked;
6670         string_copy_rev(ref->id, id);
6672         return OK;
6675 static int
6676 load_refs(void)
6678         static const char *ls_remote_argv[SIZEOF_ARG] = {
6679                 "git", "ls-remote", opt_git_dir, NULL
6680         };
6681         static bool init = FALSE;
6683         if (!init) {
6684                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6685                 init = TRUE;
6686         }
6688         if (!*opt_git_dir)
6689                 return OK;
6691         while (refs_size > 0)
6692                 free(refs[--refs_size].name);
6693         while (id_refs_size > 0)
6694                 free(id_refs[--id_refs_size]);
6696         return run_io_load(ls_remote_argv, "\t", read_ref);
6699 static void
6700 set_remote_branch(const char *name, const char *value, size_t valuelen)
6702         if (!strcmp(name, ".remote")) {
6703                 string_ncopy(opt_remote, value, valuelen);
6705         } else if (*opt_remote && !strcmp(name, ".merge")) {
6706                 size_t from = strlen(opt_remote);
6708                 if (!prefixcmp(value, "refs/heads/"))
6709                         value += STRING_SIZE("refs/heads/");
6711                 if (!string_format_from(opt_remote, &from, "/%s", value))
6712                         opt_remote[0] = 0;
6713         }
6716 static void
6717 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6719         const char *argv[SIZEOF_ARG] = { name, "=" };
6720         int argc = 1 + (cmd == option_set_command);
6721         int error = ERR;
6723         if (!argv_from_string(argv, &argc, value))
6724                 config_msg = "Too many option arguments";
6725         else
6726                 error = cmd(argc, argv);
6728         if (error == ERR)
6729                 warn("Option 'tig.%s': %s", name, config_msg);
6732 static bool
6733 set_environment_variable(const char *name, const char *value)
6735         size_t len = strlen(name) + 1 + strlen(value) + 1;
6736         char *env = malloc(len);
6738         if (env &&
6739             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6740             putenv(env) == 0)
6741                 return TRUE;
6742         free(env);
6743         return FALSE;
6746 static void
6747 set_work_tree(const char *value)
6749         char cwd[SIZEOF_STR];
6751         if (!getcwd(cwd, sizeof(cwd)))
6752                 die("Failed to get cwd path: %s", strerror(errno));
6753         if (chdir(opt_git_dir) < 0)
6754                 die("Failed to chdir(%s): %s", strerror(errno));
6755         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6756                 die("Failed to get git path: %s", strerror(errno));
6757         if (chdir(cwd) < 0)
6758                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6759         if (chdir(value) < 0)
6760                 die("Failed to chdir(%s): %s", value, strerror(errno));
6761         if (!getcwd(cwd, sizeof(cwd)))
6762                 die("Failed to get cwd path: %s", strerror(errno));
6763         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6764                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6765         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6766                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6767         opt_is_inside_work_tree = TRUE;
6770 static int
6771 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6773         if (!strcmp(name, "i18n.commitencoding"))
6774                 string_ncopy(opt_encoding, value, valuelen);
6776         else if (!strcmp(name, "core.editor"))
6777                 string_ncopy(opt_editor, value, valuelen);
6779         else if (!strcmp(name, "core.worktree"))
6780                 set_work_tree(value);
6782         else if (!prefixcmp(name, "tig.color."))
6783                 set_repo_config_option(name + 10, value, option_color_command);
6785         else if (!prefixcmp(name, "tig.bind."))
6786                 set_repo_config_option(name + 9, value, option_bind_command);
6788         else if (!prefixcmp(name, "tig."))
6789                 set_repo_config_option(name + 4, value, option_set_command);
6791         else if (*opt_head && !prefixcmp(name, "branch.") &&
6792                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6793                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6795         return OK;
6798 static int
6799 load_git_config(void)
6801         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6803         return run_io_load(config_list_argv, "=", read_repo_config_option);
6806 static int
6807 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6809         if (!opt_git_dir[0]) {
6810                 string_ncopy(opt_git_dir, name, namelen);
6812         } else if (opt_is_inside_work_tree == -1) {
6813                 /* This can be 3 different values depending on the
6814                  * version of git being used. If git-rev-parse does not
6815                  * understand --is-inside-work-tree it will simply echo
6816                  * the option else either "true" or "false" is printed.
6817                  * Default to true for the unknown case. */
6818                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6820         } else if (*name == '.') {
6821                 string_ncopy(opt_cdup, name, namelen);
6823         } else {
6824                 string_ncopy(opt_prefix, name, namelen);
6825         }
6827         return OK;
6830 static int
6831 load_repo_info(void)
6833         const char *head_argv[] = {
6834                 "git", "symbolic-ref", "HEAD", NULL
6835         };
6836         const char *rev_parse_argv[] = {
6837                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6838                         "--show-cdup", "--show-prefix", NULL
6839         };
6841         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6842                 chomp_string(opt_head);
6843                 if (!prefixcmp(opt_head, "refs/heads/")) {
6844                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6846                         memmove(opt_head, offset, strlen(offset) + 1);
6847                 }
6848         }
6850         return run_io_load(rev_parse_argv, "=", read_repo_info);
6854 /*
6855  * Main
6856  */
6858 static const char usage[] =
6859 "tig " TIG_VERSION " (" __DATE__ ")\n"
6860 "\n"
6861 "Usage: tig        [options] [revs] [--] [paths]\n"
6862 "   or: tig show   [options] [revs] [--] [paths]\n"
6863 "   or: tig blame  [rev] path\n"
6864 "   or: tig status\n"
6865 "   or: tig <      [git command output]\n"
6866 "\n"
6867 "Options:\n"
6868 "  -v, --version   Show version and exit\n"
6869 "  -h, --help      Show help message and exit";
6871 static void __NORETURN
6872 quit(int sig)
6874         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6875         if (cursed)
6876                 endwin();
6877         exit(0);
6880 static void __NORETURN
6881 die(const char *err, ...)
6883         va_list args;
6885         endwin();
6887         va_start(args, err);
6888         fputs("tig: ", stderr);
6889         vfprintf(stderr, err, args);
6890         fputs("\n", stderr);
6891         va_end(args);
6893         exit(1);
6896 static void
6897 warn(const char *msg, ...)
6899         va_list args;
6901         va_start(args, msg);
6902         fputs("tig warning: ", stderr);
6903         vfprintf(stderr, msg, args);
6904         fputs("\n", stderr);
6905         va_end(args);
6908 static enum request
6909 parse_options(int argc, const char *argv[])
6911         enum request request = REQ_VIEW_MAIN;
6912         const char *subcommand;
6913         bool seen_dashdash = FALSE;
6914         /* XXX: This is vulnerable to the user overriding options
6915          * required for the main view parser. */
6916         const char *custom_argv[SIZEOF_ARG] = {
6917                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6918                         "--topo-order", NULL
6919         };
6920         int i, j = 6;
6922         if (!isatty(STDIN_FILENO)) {
6923                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6924                 return REQ_VIEW_PAGER;
6925         }
6927         if (argc <= 1)
6928                 return REQ_NONE;
6930         subcommand = argv[1];
6931         if (!strcmp(subcommand, "status")) {
6932                 if (argc > 2)
6933                         warn("ignoring arguments after `%s'", subcommand);
6934                 return REQ_VIEW_STATUS;
6936         } else if (!strcmp(subcommand, "blame")) {
6937                 if (argc <= 2 || argc > 4)
6938                         die("invalid number of options to blame\n\n%s", usage);
6940                 i = 2;
6941                 if (argc == 4) {
6942                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6943                         i++;
6944                 }
6946                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6947                 return REQ_VIEW_BLAME;
6949         } else if (!strcmp(subcommand, "show")) {
6950                 request = REQ_VIEW_DIFF;
6952         } else {
6953                 subcommand = NULL;
6954         }
6956         if (subcommand) {
6957                 custom_argv[1] = subcommand;
6958                 j = 2;
6959         }
6961         for (i = 1 + !!subcommand; i < argc; i++) {
6962                 const char *opt = argv[i];
6964                 if (seen_dashdash || !strcmp(opt, "--")) {
6965                         seen_dashdash = TRUE;
6967                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6968                         printf("tig version %s\n", TIG_VERSION);
6969                         quit(0);
6971                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6972                         printf("%s\n", usage);
6973                         quit(0);
6974                 }
6976                 custom_argv[j++] = opt;
6977                 if (j >= ARRAY_SIZE(custom_argv))
6978                         die("command too long");
6979         }
6981         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6982                 die("Failed to format arguments"); 
6984         return request;
6987 int
6988 main(int argc, const char *argv[])
6990         enum request request = parse_options(argc, argv);
6991         struct view *view;
6992         size_t i;
6994         signal(SIGINT, quit);
6995         signal(SIGPIPE, SIG_IGN);
6997         if (setlocale(LC_ALL, "")) {
6998                 char *codeset = nl_langinfo(CODESET);
7000                 string_ncopy(opt_codeset, codeset, strlen(codeset));
7001         }
7003         if (load_repo_info() == ERR)
7004                 die("Failed to load repo info.");
7006         if (load_options() == ERR)
7007                 die("Failed to load user config.");
7009         if (load_git_config() == ERR)
7010                 die("Failed to load repo config.");
7012         /* Require a git repository unless when running in pager mode. */
7013         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7014                 die("Not a git repository");
7016         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7017                 opt_utf8 = FALSE;
7019         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7020                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7021                 if (opt_iconv == ICONV_NONE)
7022                         die("Failed to initialize character set conversion");
7023         }
7025         if (load_refs() == ERR)
7026                 die("Failed to load refs.");
7028         foreach_view (view, i)
7029                 argv_from_env(view->ops->argv, view->cmd_env);
7031         init_display();
7033         if (request != REQ_NONE)
7034                 open_view(NULL, request, OPEN_PREPARED);
7035         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7037         while (view_driver(display[current_view], request)) {
7038                 int key = get_input(0);
7040                 view = display[current_view];
7041                 request = get_keybinding(view->keymap, key);
7043                 /* Some low-level request handling. This keeps access to
7044                  * status_win restricted. */
7045                 switch (request) {
7046                 case REQ_PROMPT:
7047                 {
7048                         char *cmd = read_prompt(":");
7050                         if (cmd && isdigit(*cmd)) {
7051                                 int lineno = view->lineno + 1;
7053                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7054                                         select_view_line(view, lineno - 1);
7055                                         report("");
7056                                 } else {
7057                                         report("Unable to parse '%s' as a line number", cmd);
7058                                 }
7060                         } else if (cmd) {
7061                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7062                                 const char *argv[SIZEOF_ARG] = { "git" };
7063                                 int argc = 1;
7065                                 /* When running random commands, initially show the
7066                                  * command in the title. However, it maybe later be
7067                                  * overwritten if a commit line is selected. */
7068                                 string_ncopy(next->ref, cmd, strlen(cmd));
7070                                 if (!argv_from_string(argv, &argc, cmd)) {
7071                                         report("Too many arguments");
7072                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7073                                         report("Failed to format command");
7074                                 } else {
7075                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7076                                 }
7077                         }
7079                         request = REQ_NONE;
7080                         break;
7081                 }
7082                 case REQ_SEARCH:
7083                 case REQ_SEARCH_BACK:
7084                 {
7085                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7086                         char *search = read_prompt(prompt);
7088                         if (search)
7089                                 string_ncopy(opt_search, search, strlen(search));
7090                         else if (*opt_search)
7091                                 request = request == REQ_SEARCH ?
7092                                         REQ_FIND_NEXT :
7093                                         REQ_FIND_PREV;
7094                         else
7095                                 request = REQ_NONE;
7096                         break;
7097                 }
7098                 default:
7099                         break;
7100                 }
7101         }
7103         quit(0);
7105         return 0;