Code

Refactor and share view maximization code from view-close handling
[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         return io->pipe != -1;
404 static bool
405 kill_io(struct io *io)
407         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
410 static bool
411 done_io(struct io *io)
413         pid_t pid = io->pid;
415         if (io->pipe != -1)
416                 close(io->pipe);
417         free(io->buf);
418         reset_io(io);
420         while (pid > 0) {
421                 int status;
422                 pid_t waiting = waitpid(pid, &status, 0);
424                 if (waiting < 0) {
425                         if (errno == EINTR)
426                                 continue;
427                         report("waitpid failed (%s)", strerror(errno));
428                         return FALSE;
429                 }
431                 return waiting == pid &&
432                        !WIFSIGNALED(status) &&
433                        WIFEXITED(status) &&
434                        !WEXITSTATUS(status);
435         }
437         return TRUE;
440 static bool
441 start_io(struct io *io)
443         int pipefds[2] = { -1, -1 };
445         if (io->type == IO_FD)
446                 return TRUE;
448         if ((io->type == IO_RD || io->type == IO_WR) &&
449             pipe(pipefds) < 0)
450                 return FALSE;
451         else if (io->type == IO_AP)
452                 pipefds[1] = io->pipe;
454         if ((io->pid = fork())) {
455                 if (pipefds[!(io->type == IO_WR)] != -1)
456                         close(pipefds[!(io->type == IO_WR)]);
457                 if (io->pid != -1) {
458                         io->pipe = pipefds[!!(io->type == IO_WR)];
459                         return TRUE;
460                 }
462         } else {
463                 if (io->type != IO_FG) {
464                         int devnull = open("/dev/null", O_RDWR);
465                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
466                         int writefd = (io->type == IO_RD || io->type == IO_AP)
467                                                         ? pipefds[1] : devnull;
469                         dup2(readfd,  STDIN_FILENO);
470                         dup2(writefd, STDOUT_FILENO);
471                         dup2(devnull, STDERR_FILENO);
473                         close(devnull);
474                         if (pipefds[0] != -1)
475                                 close(pipefds[0]);
476                         if (pipefds[1] != -1)
477                                 close(pipefds[1]);
478                 }
480                 if (io->dir && *io->dir && chdir(io->dir) == -1)
481                         die("Failed to change directory: %s", strerror(errno));
483                 execvp(io->argv[0], (char *const*) io->argv);
484                 die("Failed to execute program: %s", strerror(errno));
485         }
487         if (pipefds[!!(io->type == IO_WR)] != -1)
488                 close(pipefds[!!(io->type == IO_WR)]);
489         return FALSE;
492 static bool
493 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
495         init_io(io, dir, type);
496         if (!format_argv(io->argv, argv, FORMAT_NONE))
497                 return FALSE;
498         return start_io(io);
501 static int
502 run_io_do(struct io *io)
504         return start_io(io) && done_io(io);
507 static int
508 run_io_bg(const char **argv)
510         struct io io = {};
512         init_io(&io, NULL, IO_BG);
513         if (!format_argv(io.argv, argv, FORMAT_NONE))
514                 return FALSE;
515         return run_io_do(&io);
518 static bool
519 run_io_fg(const char **argv, const char *dir)
521         struct io io = {};
523         init_io(&io, dir, IO_FG);
524         if (!format_argv(io.argv, argv, FORMAT_NONE))
525                 return FALSE;
526         return run_io_do(&io);
529 static bool
530 run_io_append(const char **argv, enum format_flags flags, int fd)
532         struct io io = {};
534         init_io(&io, NULL, IO_AP);
535         io.pipe = fd;
536         if (format_argv(io.argv, argv, flags))
537                 return run_io_do(&io);
538         close(fd);
539         return FALSE;
542 static bool
543 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
545         return init_io_rd(io, argv, NULL, flags) && start_io(io);
548 static bool
549 io_eof(struct io *io)
551         return io->eof;
554 static int
555 io_error(struct io *io)
557         return io->error;
560 static char *
561 io_strerror(struct io *io)
563         return strerror(io->error);
566 static bool
567 io_can_read(struct io *io)
569         struct timeval tv = { 0, 500 };
570         fd_set fds;
572         FD_ZERO(&fds);
573         FD_SET(io->pipe, &fds);
575         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
578 static ssize_t
579 io_read(struct io *io, void *buf, size_t bufsize)
581         do {
582                 ssize_t readsize = read(io->pipe, buf, bufsize);
584                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
585                         continue;
586                 else if (readsize == -1)
587                         io->error = errno;
588                 else if (readsize == 0)
589                         io->eof = 1;
590                 return readsize;
591         } while (1);
594 static char *
595 io_get(struct io *io, int c, bool can_read)
597         char *eol;
598         ssize_t readsize;
600         if (!io->buf) {
601                 io->buf = io->bufpos = malloc(BUFSIZ);
602                 if (!io->buf)
603                         return NULL;
604                 io->bufalloc = BUFSIZ;
605                 io->bufsize = 0;
606         }
608         while (TRUE) {
609                 if (io->bufsize > 0) {
610                         eol = memchr(io->bufpos, c, io->bufsize);
611                         if (eol) {
612                                 char *line = io->bufpos;
614                                 *eol = 0;
615                                 io->bufpos = eol + 1;
616                                 io->bufsize -= io->bufpos - line;
617                                 return line;
618                         }
619                 }
621                 if (io_eof(io)) {
622                         if (io->bufsize) {
623                                 io->bufpos[io->bufsize] = 0;
624                                 io->bufsize = 0;
625                                 return io->bufpos;
626                         }
627                         return NULL;
628                 }
630                 if (!can_read)
631                         return NULL;
633                 if (io->bufsize > 0 && io->bufpos > io->buf)
634                         memmove(io->buf, io->bufpos, io->bufsize);
636                 io->bufpos = io->buf;
637                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
638                 if (io_error(io))
639                         return NULL;
640                 io->bufsize += readsize;
641         }
644 static bool
645 io_write(struct io *io, const void *buf, size_t bufsize)
647         size_t written = 0;
649         while (!io_error(io) && written < bufsize) {
650                 ssize_t size;
652                 size = write(io->pipe, buf + written, bufsize - written);
653                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
654                         continue;
655                 else if (size == -1)
656                         io->error = errno;
657                 else
658                         written += size;
659         }
661         return written == bufsize;
664 static bool
665 io_read_buf(struct io *io, char buf[], size_t bufsize)
667         bool error;
669         io->buf = io->bufpos = buf;
670         io->bufalloc = bufsize;
671         error = !io_get(io, '\n', TRUE) && io_error(io);
672         io->buf = NULL;
674         return done_io(io) || error;
677 static bool
678 run_io_buf(const char **argv, char buf[], size_t bufsize)
680         struct io io = {};
682         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
685 static int
686 io_load(struct io *io, const char *separators,
687         int (*read_property)(char *, size_t, char *, size_t))
689         char *name;
690         int state = OK;
692         if (!start_io(io))
693                 return ERR;
695         while (state == OK && (name = io_get(io, '\n', TRUE))) {
696                 char *value;
697                 size_t namelen;
698                 size_t valuelen;
700                 name = chomp_string(name);
701                 namelen = strcspn(name, separators);
703                 if (name[namelen]) {
704                         name[namelen] = 0;
705                         value = chomp_string(name + namelen + 1);
706                         valuelen = strlen(value);
708                 } else {
709                         value = "";
710                         valuelen = 0;
711                 }
713                 state = read_property(name, namelen, value, valuelen);
714         }
716         if (state != ERR && io_error(io))
717                 state = ERR;
718         done_io(io);
720         return state;
723 static int
724 run_io_load(const char **argv, const char *separators,
725             int (*read_property)(char *, size_t, char *, size_t))
727         struct io io = {};
729         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
730                 ? io_load(&io, separators, read_property) : ERR;
734 /*
735  * User requests
736  */
738 #define REQ_INFO \
739         /* XXX: Keep the view request first and in sync with views[]. */ \
740         REQ_GROUP("View switching") \
741         REQ_(VIEW_MAIN,         "Show main view"), \
742         REQ_(VIEW_DIFF,         "Show diff view"), \
743         REQ_(VIEW_LOG,          "Show log view"), \
744         REQ_(VIEW_TREE,         "Show tree view"), \
745         REQ_(VIEW_BLOB,         "Show blob view"), \
746         REQ_(VIEW_BLAME,        "Show blame view"), \
747         REQ_(VIEW_HELP,         "Show help page"), \
748         REQ_(VIEW_PAGER,        "Show pager view"), \
749         REQ_(VIEW_STATUS,       "Show status view"), \
750         REQ_(VIEW_STAGE,        "Show stage view"), \
751         \
752         REQ_GROUP("View manipulation") \
753         REQ_(ENTER,             "Enter current line and scroll"), \
754         REQ_(NEXT,              "Move to next"), \
755         REQ_(PREVIOUS,          "Move to previous"), \
756         REQ_(PARENT,            "Move to parent"), \
757         REQ_(VIEW_NEXT,         "Move focus to next view"), \
758         REQ_(REFRESH,           "Reload and refresh"), \
759         REQ_(MAXIMIZE,          "Maximize the current view"), \
760         REQ_(VIEW_CLOSE,        "Close the current view"), \
761         REQ_(QUIT,              "Close all views and quit"), \
762         \
763         REQ_GROUP("View specific requests") \
764         REQ_(STATUS_UPDATE,     "Update file status"), \
765         REQ_(STATUS_REVERT,     "Revert file changes"), \
766         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
767         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
768         \
769         REQ_GROUP("Cursor navigation") \
770         REQ_(MOVE_UP,           "Move cursor one line up"), \
771         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
772         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
773         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
774         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
775         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
776         \
777         REQ_GROUP("Scrolling") \
778         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
779         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
780         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
781         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
782         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
783         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
784         \
785         REQ_GROUP("Searching") \
786         REQ_(SEARCH,            "Search the view"), \
787         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
788         REQ_(FIND_NEXT,         "Find next search match"), \
789         REQ_(FIND_PREV,         "Find previous search match"), \
790         \
791         REQ_GROUP("Option manipulation") \
792         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
793         REQ_(TOGGLE_DATE,       "Toggle date display"), \
794         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
795         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
796         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
797         \
798         REQ_GROUP("Misc") \
799         REQ_(PROMPT,            "Bring up the prompt"), \
800         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
801         REQ_(SHOW_VERSION,      "Show version information"), \
802         REQ_(STOP_LOADING,      "Stop all loading views"), \
803         REQ_(EDIT,              "Open in editor"), \
804         REQ_(NONE,              "Do nothing")
807 /* User action requests. */
808 enum request {
809 #define REQ_GROUP(help)
810 #define REQ_(req, help) REQ_##req
812         /* Offset all requests to avoid conflicts with ncurses getch values. */
813         REQ_OFFSET = KEY_MAX + 1,
814         REQ_INFO
816 #undef  REQ_GROUP
817 #undef  REQ_
818 };
820 struct request_info {
821         enum request request;
822         const char *name;
823         int namelen;
824         const char *help;
825 };
827 static const struct request_info req_info[] = {
828 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
829 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
830         REQ_INFO
831 #undef  REQ_GROUP
832 #undef  REQ_
833 };
835 static enum request
836 get_request(const char *name)
838         int namelen = strlen(name);
839         int i;
841         for (i = 0; i < ARRAY_SIZE(req_info); i++)
842                 if (req_info[i].namelen == namelen &&
843                     !string_enum_compare(req_info[i].name, name, namelen))
844                         return req_info[i].request;
846         return REQ_NONE;
850 /*
851  * Options
852  */
854 /* Option and state variables. */
855 static bool opt_date                    = TRUE;
856 static bool opt_author                  = TRUE;
857 static bool opt_line_number             = FALSE;
858 static bool opt_line_graphics           = TRUE;
859 static bool opt_rev_graph               = FALSE;
860 static bool opt_show_refs               = TRUE;
861 static int opt_num_interval             = NUMBER_INTERVAL;
862 static int opt_tab_size                 = TAB_SIZE;
863 static int opt_author_cols              = AUTHOR_COLS-1;
864 static char opt_path[SIZEOF_STR]        = "";
865 static char opt_file[SIZEOF_STR]        = "";
866 static char opt_ref[SIZEOF_REF]         = "";
867 static char opt_head[SIZEOF_REF]        = "";
868 static char opt_head_rev[SIZEOF_REV]    = "";
869 static char opt_remote[SIZEOF_REF]      = "";
870 static char opt_encoding[20]            = "UTF-8";
871 static bool opt_utf8                    = TRUE;
872 static char opt_codeset[20]             = "UTF-8";
873 static iconv_t opt_iconv                = ICONV_NONE;
874 static char opt_search[SIZEOF_STR]      = "";
875 static char opt_cdup[SIZEOF_STR]        = "";
876 static char opt_prefix[SIZEOF_STR]      = "";
877 static char opt_git_dir[SIZEOF_STR]     = "";
878 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
879 static char opt_editor[SIZEOF_STR]      = "";
880 static FILE *opt_tty                    = NULL;
882 #define is_initial_commit()     (!*opt_head_rev)
883 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
886 /*
887  * Line-oriented content detection.
888  */
890 #define LINE_INFO \
891 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
892 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
893 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
894 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
895 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
896 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
897 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
898 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
899 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
900 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
901 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
902 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
905 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
906 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
907 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
908 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
910 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
911 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
913 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
915 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
916 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
917 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
918 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
919 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
920 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
921 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
922 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
923 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
924 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
925 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
926 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
927 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
928 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
929 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
930 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
931 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
932 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
933 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
934 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
935 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
936 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
937 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
938 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
939 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
940 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
941 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
942 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
944 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
945 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
947 enum line_type {
948 #define LINE(type, line, fg, bg, attr) \
949         LINE_##type
950         LINE_INFO,
951         LINE_NONE
952 #undef  LINE
953 };
955 struct line_info {
956         const char *name;       /* Option name. */
957         int namelen;            /* Size of option name. */
958         const char *line;       /* The start of line to match. */
959         int linelen;            /* Size of string to match. */
960         int fg, bg, attr;       /* Color and text attributes for the lines. */
961 };
963 static struct line_info line_info[] = {
964 #define LINE(type, line, fg, bg, attr) \
965         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
966         LINE_INFO
967 #undef  LINE
968 };
970 static enum line_type
971 get_line_type(const char *line)
973         int linelen = strlen(line);
974         enum line_type type;
976         for (type = 0; type < ARRAY_SIZE(line_info); type++)
977                 /* Case insensitive search matches Signed-off-by lines better. */
978                 if (linelen >= line_info[type].linelen &&
979                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
980                         return type;
982         return LINE_DEFAULT;
985 static inline int
986 get_line_attr(enum line_type type)
988         assert(type < ARRAY_SIZE(line_info));
989         return COLOR_PAIR(type) | line_info[type].attr;
992 static struct line_info *
993 get_line_info(const char *name)
995         size_t namelen = strlen(name);
996         enum line_type type;
998         for (type = 0; type < ARRAY_SIZE(line_info); type++)
999                 if (namelen == line_info[type].namelen &&
1000                     !string_enum_compare(line_info[type].name, name, namelen))
1001                         return &line_info[type];
1003         return NULL;
1006 static void
1007 init_colors(void)
1009         int default_bg = line_info[LINE_DEFAULT].bg;
1010         int default_fg = line_info[LINE_DEFAULT].fg;
1011         enum line_type type;
1013         start_color();
1015         if (assume_default_colors(default_fg, default_bg) == ERR) {
1016                 default_bg = COLOR_BLACK;
1017                 default_fg = COLOR_WHITE;
1018         }
1020         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1021                 struct line_info *info = &line_info[type];
1022                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1023                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1025                 init_pair(type, fg, bg);
1026         }
1029 struct line {
1030         enum line_type type;
1032         /* State flags */
1033         unsigned int selected:1;
1034         unsigned int dirty:1;
1035         unsigned int cleareol:1;
1037         void *data;             /* User data */
1038 };
1041 /*
1042  * Keys
1043  */
1045 struct keybinding {
1046         int alias;
1047         enum request request;
1048 };
1050 static const struct keybinding default_keybindings[] = {
1051         /* View switching */
1052         { 'm',          REQ_VIEW_MAIN },
1053         { 'd',          REQ_VIEW_DIFF },
1054         { 'l',          REQ_VIEW_LOG },
1055         { 't',          REQ_VIEW_TREE },
1056         { 'f',          REQ_VIEW_BLOB },
1057         { 'B',          REQ_VIEW_BLAME },
1058         { 'p',          REQ_VIEW_PAGER },
1059         { 'h',          REQ_VIEW_HELP },
1060         { 'S',          REQ_VIEW_STATUS },
1061         { 'c',          REQ_VIEW_STAGE },
1063         /* View manipulation */
1064         { 'q',          REQ_VIEW_CLOSE },
1065         { KEY_TAB,      REQ_VIEW_NEXT },
1066         { KEY_RETURN,   REQ_ENTER },
1067         { KEY_UP,       REQ_PREVIOUS },
1068         { KEY_DOWN,     REQ_NEXT },
1069         { 'R',          REQ_REFRESH },
1070         { KEY_F(5),     REQ_REFRESH },
1071         { 'O',          REQ_MAXIMIZE },
1073         /* Cursor navigation */
1074         { 'k',          REQ_MOVE_UP },
1075         { 'j',          REQ_MOVE_DOWN },
1076         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1077         { KEY_END,      REQ_MOVE_LAST_LINE },
1078         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1079         { ' ',          REQ_MOVE_PAGE_DOWN },
1080         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1081         { 'b',          REQ_MOVE_PAGE_UP },
1082         { '-',          REQ_MOVE_PAGE_UP },
1084         /* Scrolling */
1085         { KEY_LEFT,     REQ_SCROLL_LEFT },
1086         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1087         { KEY_IC,       REQ_SCROLL_LINE_UP },
1088         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1089         { 'w',          REQ_SCROLL_PAGE_UP },
1090         { 's',          REQ_SCROLL_PAGE_DOWN },
1092         /* Searching */
1093         { '/',          REQ_SEARCH },
1094         { '?',          REQ_SEARCH_BACK },
1095         { 'n',          REQ_FIND_NEXT },
1096         { 'N',          REQ_FIND_PREV },
1098         /* Misc */
1099         { 'Q',          REQ_QUIT },
1100         { 'z',          REQ_STOP_LOADING },
1101         { 'v',          REQ_SHOW_VERSION },
1102         { 'r',          REQ_SCREEN_REDRAW },
1103         { '.',          REQ_TOGGLE_LINENO },
1104         { 'D',          REQ_TOGGLE_DATE },
1105         { 'A',          REQ_TOGGLE_AUTHOR },
1106         { 'g',          REQ_TOGGLE_REV_GRAPH },
1107         { 'F',          REQ_TOGGLE_REFS },
1108         { ':',          REQ_PROMPT },
1109         { 'u',          REQ_STATUS_UPDATE },
1110         { '!',          REQ_STATUS_REVERT },
1111         { 'M',          REQ_STATUS_MERGE },
1112         { '@',          REQ_STAGE_NEXT },
1113         { ',',          REQ_PARENT },
1114         { 'e',          REQ_EDIT },
1115 };
1117 #define KEYMAP_INFO \
1118         KEYMAP_(GENERIC), \
1119         KEYMAP_(MAIN), \
1120         KEYMAP_(DIFF), \
1121         KEYMAP_(LOG), \
1122         KEYMAP_(TREE), \
1123         KEYMAP_(BLOB), \
1124         KEYMAP_(BLAME), \
1125         KEYMAP_(PAGER), \
1126         KEYMAP_(HELP), \
1127         KEYMAP_(STATUS), \
1128         KEYMAP_(STAGE)
1130 enum keymap {
1131 #define KEYMAP_(name) KEYMAP_##name
1132         KEYMAP_INFO
1133 #undef  KEYMAP_
1134 };
1136 static const struct enum_map keymap_table[] = {
1137 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1138         KEYMAP_INFO
1139 #undef  KEYMAP_
1140 };
1142 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1144 struct keybinding_table {
1145         struct keybinding *data;
1146         size_t size;
1147 };
1149 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1151 static void
1152 add_keybinding(enum keymap keymap, enum request request, int key)
1154         struct keybinding_table *table = &keybindings[keymap];
1156         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1157         if (!table->data)
1158                 die("Failed to allocate keybinding");
1159         table->data[table->size].alias = key;
1160         table->data[table->size++].request = request;
1163 /* Looks for a key binding first in the given map, then in the generic map, and
1164  * lastly in the default keybindings. */
1165 static enum request
1166 get_keybinding(enum keymap keymap, int key)
1168         size_t i;
1170         for (i = 0; i < keybindings[keymap].size; i++)
1171                 if (keybindings[keymap].data[i].alias == key)
1172                         return keybindings[keymap].data[i].request;
1174         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1175                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1176                         return keybindings[KEYMAP_GENERIC].data[i].request;
1178         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1179                 if (default_keybindings[i].alias == key)
1180                         return default_keybindings[i].request;
1182         return (enum request) key;
1186 struct key {
1187         const char *name;
1188         int value;
1189 };
1191 static const struct key key_table[] = {
1192         { "Enter",      KEY_RETURN },
1193         { "Space",      ' ' },
1194         { "Backspace",  KEY_BACKSPACE },
1195         { "Tab",        KEY_TAB },
1196         { "Escape",     KEY_ESC },
1197         { "Left",       KEY_LEFT },
1198         { "Right",      KEY_RIGHT },
1199         { "Up",         KEY_UP },
1200         { "Down",       KEY_DOWN },
1201         { "Insert",     KEY_IC },
1202         { "Delete",     KEY_DC },
1203         { "Hash",       '#' },
1204         { "Home",       KEY_HOME },
1205         { "End",        KEY_END },
1206         { "PageUp",     KEY_PPAGE },
1207         { "PageDown",   KEY_NPAGE },
1208         { "F1",         KEY_F(1) },
1209         { "F2",         KEY_F(2) },
1210         { "F3",         KEY_F(3) },
1211         { "F4",         KEY_F(4) },
1212         { "F5",         KEY_F(5) },
1213         { "F6",         KEY_F(6) },
1214         { "F7",         KEY_F(7) },
1215         { "F8",         KEY_F(8) },
1216         { "F9",         KEY_F(9) },
1217         { "F10",        KEY_F(10) },
1218         { "F11",        KEY_F(11) },
1219         { "F12",        KEY_F(12) },
1220 };
1222 static int
1223 get_key_value(const char *name)
1225         int i;
1227         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1228                 if (!strcasecmp(key_table[i].name, name))
1229                         return key_table[i].value;
1231         if (strlen(name) == 1 && isprint(*name))
1232                 return (int) *name;
1234         return ERR;
1237 static const char *
1238 get_key_name(int key_value)
1240         static char key_char[] = "'X'";
1241         const char *seq = NULL;
1242         int key;
1244         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1245                 if (key_table[key].value == key_value)
1246                         seq = key_table[key].name;
1248         if (seq == NULL &&
1249             key_value < 127 &&
1250             isprint(key_value)) {
1251                 key_char[1] = (char) key_value;
1252                 seq = key_char;
1253         }
1255         return seq ? seq : "(no key)";
1258 static const char *
1259 get_key(enum request request)
1261         static char buf[BUFSIZ];
1262         size_t pos = 0;
1263         char *sep = "";
1264         int i;
1266         buf[pos] = 0;
1268         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1269                 const struct keybinding *keybinding = &default_keybindings[i];
1271                 if (keybinding->request != request)
1272                         continue;
1274                 if (!string_format_from(buf, &pos, "%s%s", sep,
1275                                         get_key_name(keybinding->alias)))
1276                         return "Too many keybindings!";
1277                 sep = ", ";
1278         }
1280         return buf;
1283 struct run_request {
1284         enum keymap keymap;
1285         int key;
1286         const char *argv[SIZEOF_ARG];
1287 };
1289 static struct run_request *run_request;
1290 static size_t run_requests;
1292 static enum request
1293 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1295         struct run_request *req;
1297         if (argc >= ARRAY_SIZE(req->argv) - 1)
1298                 return REQ_NONE;
1300         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1301         if (!req)
1302                 return REQ_NONE;
1304         run_request = req;
1305         req = &run_request[run_requests];
1306         req->keymap = keymap;
1307         req->key = key;
1308         req->argv[0] = NULL;
1310         if (!format_argv(req->argv, argv, FORMAT_NONE))
1311                 return REQ_NONE;
1313         return REQ_NONE + ++run_requests;
1316 static struct run_request *
1317 get_run_request(enum request request)
1319         if (request <= REQ_NONE)
1320                 return NULL;
1321         return &run_request[request - REQ_NONE - 1];
1324 static void
1325 add_builtin_run_requests(void)
1327         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1328         const char *gc[] = { "git", "gc", NULL };
1329         struct {
1330                 enum keymap keymap;
1331                 int key;
1332                 int argc;
1333                 const char **argv;
1334         } reqs[] = {
1335                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1336                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1337         };
1338         int i;
1340         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1341                 enum request req;
1343                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1344                 if (req != REQ_NONE)
1345                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1346         }
1349 /*
1350  * User config file handling.
1351  */
1353 static int   config_lineno;
1354 static bool  config_errors;
1355 static const char *config_msg;
1357 static const struct enum_map color_map[] = {
1358 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1359         COLOR_MAP(DEFAULT),
1360         COLOR_MAP(BLACK),
1361         COLOR_MAP(BLUE),
1362         COLOR_MAP(CYAN),
1363         COLOR_MAP(GREEN),
1364         COLOR_MAP(MAGENTA),
1365         COLOR_MAP(RED),
1366         COLOR_MAP(WHITE),
1367         COLOR_MAP(YELLOW),
1368 };
1370 static const struct enum_map attr_map[] = {
1371 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1372         ATTR_MAP(NORMAL),
1373         ATTR_MAP(BLINK),
1374         ATTR_MAP(BOLD),
1375         ATTR_MAP(DIM),
1376         ATTR_MAP(REVERSE),
1377         ATTR_MAP(STANDOUT),
1378         ATTR_MAP(UNDERLINE),
1379 };
1381 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1383 static int
1384 parse_int(int *opt, const char *arg, int min, int max)
1386         int value = atoi(arg);
1388         if (min <= value && value <= max) {
1389                 *opt = value;
1390                 return OK;
1391         }
1393         config_msg = "Integer value out of bound";
1394         return ERR;
1397 static bool
1398 set_color(int *color, const char *name)
1400         if (map_enum(color, color_map, name))
1401                 return TRUE;
1402         if (!prefixcmp(name, "color"))
1403                 return parse_int(color, name + 5, 0, 255) == OK;
1404         return FALSE;
1407 /* Wants: object fgcolor bgcolor [attribute] */
1408 static int
1409 option_color_command(int argc, const char *argv[])
1411         struct line_info *info;
1413         if (argc != 3 && argc != 4) {
1414                 config_msg = "Wrong number of arguments given to color command";
1415                 return ERR;
1416         }
1418         info = get_line_info(argv[0]);
1419         if (!info) {
1420                 static const struct enum_map obsolete[] = {
1421                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1422                         ENUM_MAP("main-date",   LINE_DATE),
1423                         ENUM_MAP("main-author", LINE_AUTHOR),
1424                 };
1425                 int index;
1427                 if (!map_enum(&index, obsolete, argv[0])) {
1428                         config_msg = "Unknown color name";
1429                         return ERR;
1430                 }
1431                 info = &line_info[index];
1432         }
1434         if (!set_color(&info->fg, argv[1]) ||
1435             !set_color(&info->bg, argv[2])) {
1436                 config_msg = "Unknown color";
1437                 return ERR;
1438         }
1440         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1441                 config_msg = "Unknown attribute";
1442                 return ERR;
1443         }
1445         return OK;
1448 static int parse_bool(bool *opt, const char *arg)
1450         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1451                 ? TRUE : FALSE;
1452         return OK;
1455 static int
1456 parse_string(char *opt, const char *arg, size_t optsize)
1458         int arglen = strlen(arg);
1460         switch (arg[0]) {
1461         case '\"':
1462         case '\'':
1463                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1464                         config_msg = "Unmatched quotation";
1465                         return ERR;
1466                 }
1467                 arg += 1; arglen -= 2;
1468         default:
1469                 string_ncopy_do(opt, optsize, arg, strlen(arg));
1470                 return OK;
1471         }
1474 /* Wants: name = value */
1475 static int
1476 option_set_command(int argc, const char *argv[])
1478         if (argc != 3) {
1479                 config_msg = "Wrong number of arguments given to set command";
1480                 return ERR;
1481         }
1483         if (strcmp(argv[1], "=")) {
1484                 config_msg = "No value assigned";
1485                 return ERR;
1486         }
1488         if (!strcmp(argv[0], "show-author"))
1489                 return parse_bool(&opt_author, argv[2]);
1491         if (!strcmp(argv[0], "show-date"))
1492                 return parse_bool(&opt_date, argv[2]);
1494         if (!strcmp(argv[0], "show-rev-graph"))
1495                 return parse_bool(&opt_rev_graph, argv[2]);
1497         if (!strcmp(argv[0], "show-refs"))
1498                 return parse_bool(&opt_show_refs, argv[2]);
1500         if (!strcmp(argv[0], "show-line-numbers"))
1501                 return parse_bool(&opt_line_number, argv[2]);
1503         if (!strcmp(argv[0], "line-graphics"))
1504                 return parse_bool(&opt_line_graphics, argv[2]);
1506         if (!strcmp(argv[0], "line-number-interval"))
1507                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1509         if (!strcmp(argv[0], "author-width"))
1510                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1512         if (!strcmp(argv[0], "tab-size"))
1513                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1515         if (!strcmp(argv[0], "commit-encoding"))
1516                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1518         config_msg = "Unknown variable name";
1519         return ERR;
1522 /* Wants: mode request key */
1523 static int
1524 option_bind_command(int argc, const char *argv[])
1526         enum request request;
1527         int keymap;
1528         int key;
1530         if (argc < 3) {
1531                 config_msg = "Wrong number of arguments given to bind command";
1532                 return ERR;
1533         }
1535         if (set_keymap(&keymap, argv[0]) == ERR) {
1536                 config_msg = "Unknown key map";
1537                 return ERR;
1538         }
1540         key = get_key_value(argv[1]);
1541         if (key == ERR) {
1542                 config_msg = "Unknown key";
1543                 return ERR;
1544         }
1546         request = get_request(argv[2]);
1547         if (request == REQ_NONE) {
1548                 static const struct enum_map obsolete[] = {
1549                         ENUM_MAP("cherry-pick",         REQ_NONE),
1550                         ENUM_MAP("screen-resize",       REQ_NONE),
1551                         ENUM_MAP("tree-parent",         REQ_PARENT),
1552                 };
1553                 int alias;
1555                 if (map_enum(&alias, obsolete, argv[2])) {
1556                         if (alias != REQ_NONE)
1557                                 add_keybinding(keymap, alias, key);
1558                         config_msg = "Obsolete request name";
1559                         return ERR;
1560                 }
1561         }
1562         if (request == REQ_NONE && *argv[2]++ == '!')
1563                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1564         if (request == REQ_NONE) {
1565                 config_msg = "Unknown request name";
1566                 return ERR;
1567         }
1569         add_keybinding(keymap, request, key);
1571         return OK;
1574 static int
1575 set_option(const char *opt, char *value)
1577         const char *argv[SIZEOF_ARG];
1578         int argc = 0;
1580         if (!argv_from_string(argv, &argc, value)) {
1581                 config_msg = "Too many option arguments";
1582                 return ERR;
1583         }
1585         if (!strcmp(opt, "color"))
1586                 return option_color_command(argc, argv);
1588         if (!strcmp(opt, "set"))
1589                 return option_set_command(argc, argv);
1591         if (!strcmp(opt, "bind"))
1592                 return option_bind_command(argc, argv);
1594         config_msg = "Unknown option command";
1595         return ERR;
1598 static int
1599 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1601         int status = OK;
1603         config_lineno++;
1604         config_msg = "Internal error";
1606         /* Check for comment markers, since read_properties() will
1607          * only ensure opt and value are split at first " \t". */
1608         optlen = strcspn(opt, "#");
1609         if (optlen == 0)
1610                 return OK;
1612         if (opt[optlen] != 0) {
1613                 config_msg = "No option value";
1614                 status = ERR;
1616         }  else {
1617                 /* Look for comment endings in the value. */
1618                 size_t len = strcspn(value, "#");
1620                 if (len < valuelen) {
1621                         valuelen = len;
1622                         value[valuelen] = 0;
1623                 }
1625                 status = set_option(opt, value);
1626         }
1628         if (status == ERR) {
1629                 warn("Error on line %d, near '%.*s': %s",
1630                      config_lineno, (int) optlen, opt, config_msg);
1631                 config_errors = TRUE;
1632         }
1634         /* Always keep going if errors are encountered. */
1635         return OK;
1638 static void
1639 load_option_file(const char *path)
1641         struct io io = {};
1643         /* It's OK that the file doesn't exist. */
1644         if (!io_open(&io, path))
1645                 return;
1647         config_lineno = 0;
1648         config_errors = FALSE;
1650         if (io_load(&io, " \t", read_option) == ERR ||
1651             config_errors == TRUE)
1652                 warn("Errors while loading %s.", path);
1655 static int
1656 load_options(void)
1658         const char *home = getenv("HOME");
1659         const char *tigrc_user = getenv("TIGRC_USER");
1660         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1661         char buf[SIZEOF_STR];
1663         add_builtin_run_requests();
1665         if (!tigrc_system)
1666                 tigrc_system = SYSCONFDIR "/tigrc";
1667         load_option_file(tigrc_system);
1669         if (!tigrc_user) {
1670                 if (!home || !string_format(buf, "%s/.tigrc", home))
1671                         return ERR;
1672                 tigrc_user = buf;
1673         }
1674         load_option_file(tigrc_user);
1676         return OK;
1680 /*
1681  * The viewer
1682  */
1684 struct view;
1685 struct view_ops;
1687 /* The display array of active views and the index of the current view. */
1688 static struct view *display[2];
1689 static unsigned int current_view;
1691 #define foreach_displayed_view(view, i) \
1692         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1694 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1696 /* Current head and commit ID */
1697 static char ref_blob[SIZEOF_REF]        = "";
1698 static char ref_commit[SIZEOF_REF]      = "HEAD";
1699 static char ref_head[SIZEOF_REF]        = "HEAD";
1701 struct view {
1702         const char *name;       /* View name */
1703         const char *cmd_env;    /* Command line set via environment */
1704         const char *id;         /* Points to either of ref_{head,commit,blob} */
1706         struct view_ops *ops;   /* View operations */
1708         enum keymap keymap;     /* What keymap does this view have */
1709         bool git_dir;           /* Whether the view requires a git directory. */
1711         char ref[SIZEOF_REF];   /* Hovered commit reference */
1712         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1714         int height, width;      /* The width and height of the main window */
1715         WINDOW *win;            /* The main window */
1716         WINDOW *title;          /* The title window living below the main window */
1718         /* Navigation */
1719         unsigned long offset;   /* Offset of the window top */
1720         unsigned long yoffset;  /* Offset from the window side. */
1721         unsigned long lineno;   /* Current line number */
1722         unsigned long p_offset; /* Previous offset of the window top */
1723         unsigned long p_yoffset;/* Previous offset from the window side */
1724         unsigned long p_lineno; /* Previous current line number */
1725         bool p_restore;         /* Should the previous position be restored. */
1727         /* Searching */
1728         char grep[SIZEOF_STR];  /* Search string */
1729         regex_t *regex;         /* Pre-compiled regexp */
1731         /* If non-NULL, points to the view that opened this view. If this view
1732          * is closed tig will switch back to the parent view. */
1733         struct view *parent;
1735         /* Buffering */
1736         size_t lines;           /* Total number of lines */
1737         struct line *line;      /* Line index */
1738         size_t line_alloc;      /* Total number of allocated lines */
1739         unsigned int digits;    /* Number of digits in the lines member. */
1741         /* Drawing */
1742         struct line *curline;   /* Line currently being drawn. */
1743         enum line_type curtype; /* Attribute currently used for drawing. */
1744         unsigned long col;      /* Column when drawing. */
1745         bool has_scrolled;      /* View was scrolled. */
1746         bool can_hscroll;       /* View can be scrolled horizontally. */
1748         /* Loading */
1749         struct io io;
1750         struct io *pipe;
1751         time_t start_time;
1752         time_t update_secs;
1753 };
1755 struct view_ops {
1756         /* What type of content being displayed. Used in the title bar. */
1757         const char *type;
1758         /* Default command arguments. */
1759         const char **argv;
1760         /* Open and reads in all view content. */
1761         bool (*open)(struct view *view);
1762         /* Read one line; updates view->line. */
1763         bool (*read)(struct view *view, char *data);
1764         /* Draw one line; @lineno must be < view->height. */
1765         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1766         /* Depending on view handle a special requests. */
1767         enum request (*request)(struct view *view, enum request request, struct line *line);
1768         /* Search for regexp in a line. */
1769         bool (*grep)(struct view *view, struct line *line);
1770         /* Select line */
1771         void (*select)(struct view *view, struct line *line);
1772 };
1774 static struct view_ops blame_ops;
1775 static struct view_ops blob_ops;
1776 static struct view_ops diff_ops;
1777 static struct view_ops help_ops;
1778 static struct view_ops log_ops;
1779 static struct view_ops main_ops;
1780 static struct view_ops pager_ops;
1781 static struct view_ops stage_ops;
1782 static struct view_ops status_ops;
1783 static struct view_ops tree_ops;
1785 #define VIEW_STR(name, env, ref, ops, map, git) \
1786         { name, #env, ref, ops, map, git }
1788 #define VIEW_(id, name, ops, git, ref) \
1789         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1792 static struct view views[] = {
1793         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1794         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1795         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1796         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1797         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1798         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1799         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1800         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1801         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1802         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1803 };
1805 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1806 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1808 #define foreach_view(view, i) \
1809         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1811 #define view_is_displayed(view) \
1812         (view == display[0] || view == display[1])
1815 enum line_graphic {
1816         LINE_GRAPHIC_VLINE
1817 };
1819 static int line_graphics[] = {
1820         /* LINE_GRAPHIC_VLINE: */ '|'
1821 };
1823 static inline void
1824 set_view_attr(struct view *view, enum line_type type)
1826         if (!view->curline->selected && view->curtype != type) {
1827                 wattrset(view->win, get_line_attr(type));
1828                 wchgat(view->win, -1, 0, type, NULL);
1829                 view->curtype = type;
1830         }
1833 static int
1834 draw_chars(struct view *view, enum line_type type, const char *string,
1835            int max_len, bool use_tilde)
1837         int len = 0;
1838         int col = 0;
1839         int trimmed = FALSE;
1840         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1842         if (max_len <= 0)
1843                 return 0;
1845         if (opt_utf8) {
1846                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1847         } else {
1848                 col = len = strlen(string);
1849                 if (len > max_len) {
1850                         if (use_tilde) {
1851                                 max_len -= 1;
1852                         }
1853                         col = len = max_len;
1854                         trimmed = TRUE;
1855                 }
1856         }
1858         set_view_attr(view, type);
1859         if (len > 0)
1860                 waddnstr(view->win, string, len);
1861         if (trimmed && use_tilde) {
1862                 set_view_attr(view, LINE_DELIMITER);
1863                 waddch(view->win, '~');
1864                 col++;
1865         }
1867         if (view->col + col >= view->width + view->yoffset)
1868                 view->can_hscroll = TRUE;
1870         return col;
1873 static int
1874 draw_space(struct view *view, enum line_type type, int max, int spaces)
1876         static char space[] = "                    ";
1877         int col = 0;
1879         spaces = MIN(max, spaces);
1881         while (spaces > 0) {
1882                 int len = MIN(spaces, sizeof(space) - 1);
1884                 col += draw_chars(view, type, space, spaces, FALSE);
1885                 spaces -= len;
1886         }
1888         return col;
1891 static bool
1892 draw_lineno(struct view *view, unsigned int lineno)
1894         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1895         char number[10];
1896         int digits3 = view->digits < 3 ? 3 : view->digits;
1897         int max_number = MIN(digits3, STRING_SIZE(number));
1898         int max = view->width - view->col;
1899         int col;
1901         if (max < max_number)
1902                 max_number = max;
1904         lineno += view->offset + 1;
1905         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1906                 static char fmt[] = "%1ld";
1908                 if (view->digits <= 9)
1909                         fmt[1] = '0' + digits3;
1911                 if (!string_format(number, fmt, lineno))
1912                         number[0] = 0;
1913                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1914         } else {
1915                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1916         }
1918         if (col < max && skip <= col) {
1919                 set_view_attr(view, LINE_DEFAULT);
1920                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1921         }
1922         col++;
1924         view->col += col;
1925         if (col < max && skip <= col)
1926                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1927         view->col++;
1929         return view->width + view->yoffset <= view->col;
1932 static bool
1933 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1935         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1936         return view->width - view->col <= 0;
1939 static bool
1940 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1942         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1943         int max = view->width - view->col;
1944         int i;
1946         if (max < size)
1947                 size = max;
1949         set_view_attr(view, type);
1950         /* Using waddch() instead of waddnstr() ensures that
1951          * they'll be rendered correctly for the cursor line. */
1952         for (i = skip; i < size; i++)
1953                 waddch(view->win, graphic[i]);
1955         view->col += size;
1956         if (size < max && skip <= size)
1957                 waddch(view->win, ' ');
1958         view->col++;
1960         return view->width - view->col <= 0;
1963 static bool
1964 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1966         int max = MIN(view->width - view->col, len);
1967         int col;
1969         if (text)
1970                 col = draw_chars(view, type, text, max - 1, trim);
1971         else
1972                 col = draw_space(view, type, max - 1, max - 1);
1974         view->col += col;
1975         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1976         return view->width + view->yoffset <= view->col;
1979 static bool
1980 draw_date(struct view *view, struct tm *time)
1982         char buf[DATE_COLS];
1983         char *date;
1984         int timelen = 0;
1986         if (time)
1987                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1988         date = timelen ? buf : NULL;
1990         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1993 static bool
1994 draw_author(struct view *view, const char *author)
1996         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1998         if (!trim) {
1999                 static char initials[10];
2000                 size_t pos;
2002 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2004                 memset(initials, 0, sizeof(initials));
2005                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2006                         while (is_initial_sep(*author))
2007                                 author++;
2008                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2009                         while (*author && !is_initial_sep(author[1]))
2010                                 author++;
2011                 }
2013                 author = initials;
2014         }
2016         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2019 static bool
2020 draw_mode(struct view *view, mode_t mode)
2022         static const char dir_mode[]    = "drwxr-xr-x";
2023         static const char link_mode[]   = "lrwxrwxrwx";
2024         static const char exe_mode[]    = "-rwxr-xr-x";
2025         static const char file_mode[]   = "-rw-r--r--";
2026         const char *str;
2028         if (S_ISDIR(mode))
2029                 str = dir_mode;
2030         else if (S_ISLNK(mode))
2031                 str = link_mode;
2032         else if (mode & S_IXUSR)
2033                 str = exe_mode;
2034         else
2035                 str = file_mode;
2037         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2040 static bool
2041 draw_view_line(struct view *view, unsigned int lineno)
2043         struct line *line;
2044         bool selected = (view->offset + lineno == view->lineno);
2046         assert(view_is_displayed(view));
2048         if (view->offset + lineno >= view->lines)
2049                 return FALSE;
2051         line = &view->line[view->offset + lineno];
2053         wmove(view->win, lineno, 0);
2054         if (line->cleareol)
2055                 wclrtoeol(view->win);
2056         view->col = 0;
2057         view->curline = line;
2058         view->curtype = LINE_NONE;
2059         line->selected = FALSE;
2060         line->dirty = line->cleareol = 0;
2062         if (selected) {
2063                 set_view_attr(view, LINE_CURSOR);
2064                 line->selected = TRUE;
2065                 view->ops->select(view, line);
2066         }
2068         return view->ops->draw(view, line, lineno);
2071 static void
2072 redraw_view_dirty(struct view *view)
2074         bool dirty = FALSE;
2075         int lineno;
2077         for (lineno = 0; lineno < view->height; lineno++) {
2078                 if (view->offset + lineno >= view->lines)
2079                         break;
2080                 if (!view->line[view->offset + lineno].dirty)
2081                         continue;
2082                 dirty = TRUE;
2083                 if (!draw_view_line(view, lineno))
2084                         break;
2085         }
2087         if (!dirty)
2088                 return;
2089         wnoutrefresh(view->win);
2092 static void
2093 redraw_view_from(struct view *view, int lineno)
2095         assert(0 <= lineno && lineno < view->height);
2097         if (lineno == 0)
2098                 view->can_hscroll = FALSE;
2100         for (; lineno < view->height; lineno++) {
2101                 if (!draw_view_line(view, lineno))
2102                         break;
2103         }
2105         wnoutrefresh(view->win);
2108 static void
2109 redraw_view(struct view *view)
2111         werase(view->win);
2112         redraw_view_from(view, 0);
2116 static void
2117 update_view_title(struct view *view)
2119         char buf[SIZEOF_STR];
2120         char state[SIZEOF_STR];
2121         size_t bufpos = 0, statelen = 0;
2123         assert(view_is_displayed(view));
2125         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2126                 unsigned int view_lines = view->offset + view->height;
2127                 unsigned int lines = view->lines
2128                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2129                                    : 0;
2131                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2132                                    view->ops->type,
2133                                    view->lineno + 1,
2134                                    view->lines,
2135                                    lines);
2137         }
2139         if (view->pipe) {
2140                 time_t secs = time(NULL) - view->start_time;
2142                 /* Three git seconds are a long time ... */
2143                 if (secs > 2)
2144                         string_format_from(state, &statelen, " loading %lds", secs);
2145         }
2147         string_format_from(buf, &bufpos, "[%s]", view->name);
2148         if (*view->ref && bufpos < view->width) {
2149                 size_t refsize = strlen(view->ref);
2150                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2152                 if (minsize < view->width)
2153                         refsize = view->width - minsize + 7;
2154                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2155         }
2157         if (statelen && bufpos < view->width) {
2158                 string_format_from(buf, &bufpos, "%s", state);
2159         }
2161         if (view == display[current_view])
2162                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2163         else
2164                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2166         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2167         wclrtoeol(view->title);
2168         wnoutrefresh(view->title);
2171 static void
2172 resize_display(void)
2174         int offset, i;
2175         struct view *base = display[0];
2176         struct view *view = display[1] ? display[1] : display[0];
2178         /* Setup window dimensions */
2180         getmaxyx(stdscr, base->height, base->width);
2182         /* Make room for the status window. */
2183         base->height -= 1;
2185         if (view != base) {
2186                 /* Horizontal split. */
2187                 view->width   = base->width;
2188                 view->height  = SCALE_SPLIT_VIEW(base->height);
2189                 base->height -= view->height;
2191                 /* Make room for the title bar. */
2192                 view->height -= 1;
2193         }
2195         /* Make room for the title bar. */
2196         base->height -= 1;
2198         offset = 0;
2200         foreach_displayed_view (view, i) {
2201                 if (!view->win) {
2202                         view->win = newwin(view->height, 0, offset, 0);
2203                         if (!view->win)
2204                                 die("Failed to create %s view", view->name);
2206                         scrollok(view->win, FALSE);
2208                         view->title = newwin(1, 0, offset + view->height, 0);
2209                         if (!view->title)
2210                                 die("Failed to create title window");
2212                 } else {
2213                         wresize(view->win, view->height, view->width);
2214                         mvwin(view->win,   offset, 0);
2215                         mvwin(view->title, offset + view->height, 0);
2216                 }
2218                 offset += view->height + 1;
2219         }
2222 static void
2223 redraw_display(bool clear)
2225         struct view *view;
2226         int i;
2228         foreach_displayed_view (view, i) {
2229                 if (clear)
2230                         wclear(view->win);
2231                 redraw_view(view);
2232                 update_view_title(view);
2233         }
2236 static void
2237 toggle_view_option(bool *option, const char *help)
2239         *option = !*option;
2240         redraw_display(FALSE);
2241         report("%sabling %s", *option ? "En" : "Dis", help);
2244 static void
2245 maximize_view(struct view *view)
2247         memset(display, 0, sizeof(display));
2248         current_view = 0;
2249         display[current_view] = view;
2250         resize_display();
2251         redraw_display(FALSE);
2252         report("");
2256 /*
2257  * Navigation
2258  */
2260 static bool
2261 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2263         if (lineno >= view->lines)
2264                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2266         if (offset > lineno || offset + view->height <= lineno) {
2267                 unsigned long half = view->height / 2;
2269                 if (lineno > half)
2270                         offset = lineno - half;
2271                 else
2272                         offset = 0;
2273         }
2275         if (offset != view->offset || lineno != view->lineno) {
2276                 view->offset = offset;
2277                 view->lineno = lineno;
2278                 return TRUE;
2279         }
2281         return FALSE;
2284 /* Scrolling backend */
2285 static void
2286 do_scroll_view(struct view *view, int lines)
2288         bool redraw_current_line = FALSE;
2290         /* The rendering expects the new offset. */
2291         view->offset += lines;
2293         assert(0 <= view->offset && view->offset < view->lines);
2294         assert(lines);
2296         /* Move current line into the view. */
2297         if (view->lineno < view->offset) {
2298                 view->lineno = view->offset;
2299                 redraw_current_line = TRUE;
2300         } else if (view->lineno >= view->offset + view->height) {
2301                 view->lineno = view->offset + view->height - 1;
2302                 redraw_current_line = TRUE;
2303         }
2305         assert(view->offset <= view->lineno && view->lineno < view->lines);
2307         /* Redraw the whole screen if scrolling is pointless. */
2308         if (view->height < ABS(lines)) {
2309                 redraw_view(view);
2311         } else {
2312                 int line = lines > 0 ? view->height - lines : 0;
2313                 int end = line + ABS(lines);
2315                 scrollok(view->win, TRUE);
2316                 wscrl(view->win, lines);
2317                 scrollok(view->win, FALSE);
2319                 while (line < end && draw_view_line(view, line))
2320                         line++;
2322                 if (redraw_current_line)
2323                         draw_view_line(view, view->lineno - view->offset);
2324                 wnoutrefresh(view->win);
2325         }
2327         view->has_scrolled = TRUE;
2328         report("");
2331 /* Scroll frontend */
2332 static void
2333 scroll_view(struct view *view, enum request request)
2335         int lines = 1;
2337         assert(view_is_displayed(view));
2339         switch (request) {
2340         case REQ_SCROLL_LEFT:
2341                 if (view->yoffset == 0) {
2342                         report("Cannot scroll beyond the first column");
2343                         return;
2344                 }
2345                 if (view->yoffset <= SCROLL_INTERVAL)
2346                         view->yoffset = 0;
2347                 else
2348                         view->yoffset -= SCROLL_INTERVAL;
2349                 redraw_view_from(view, 0);
2350                 report("");
2351                 return;
2352         case REQ_SCROLL_RIGHT:
2353                 if (!view->can_hscroll) {
2354                         report("Cannot scroll beyond the last column");
2355                         return;
2356                 }
2357                 view->yoffset += SCROLL_INTERVAL;
2358                 redraw_view(view);
2359                 report("");
2360                 return;
2361         case REQ_SCROLL_PAGE_DOWN:
2362                 lines = view->height;
2363         case REQ_SCROLL_LINE_DOWN:
2364                 if (view->offset + lines > view->lines)
2365                         lines = view->lines - view->offset;
2367                 if (lines == 0 || view->offset + view->height >= view->lines) {
2368                         report("Cannot scroll beyond the last line");
2369                         return;
2370                 }
2371                 break;
2373         case REQ_SCROLL_PAGE_UP:
2374                 lines = view->height;
2375         case REQ_SCROLL_LINE_UP:
2376                 if (lines > view->offset)
2377                         lines = view->offset;
2379                 if (lines == 0) {
2380                         report("Cannot scroll beyond the first line");
2381                         return;
2382                 }
2384                 lines = -lines;
2385                 break;
2387         default:
2388                 die("request %d not handled in switch", request);
2389         }
2391         do_scroll_view(view, lines);
2394 /* Cursor moving */
2395 static void
2396 move_view(struct view *view, enum request request)
2398         int scroll_steps = 0;
2399         int steps;
2401         switch (request) {
2402         case REQ_MOVE_FIRST_LINE:
2403                 steps = -view->lineno;
2404                 break;
2406         case REQ_MOVE_LAST_LINE:
2407                 steps = view->lines - view->lineno - 1;
2408                 break;
2410         case REQ_MOVE_PAGE_UP:
2411                 steps = view->height > view->lineno
2412                       ? -view->lineno : -view->height;
2413                 break;
2415         case REQ_MOVE_PAGE_DOWN:
2416                 steps = view->lineno + view->height >= view->lines
2417                       ? view->lines - view->lineno - 1 : view->height;
2418                 break;
2420         case REQ_MOVE_UP:
2421                 steps = -1;
2422                 break;
2424         case REQ_MOVE_DOWN:
2425                 steps = 1;
2426                 break;
2428         default:
2429                 die("request %d not handled in switch", request);
2430         }
2432         if (steps <= 0 && view->lineno == 0) {
2433                 report("Cannot move beyond the first line");
2434                 return;
2436         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2437                 report("Cannot move beyond the last line");
2438                 return;
2439         }
2441         /* Move the current line */
2442         view->lineno += steps;
2443         assert(0 <= view->lineno && view->lineno < view->lines);
2445         /* Check whether the view needs to be scrolled */
2446         if (view->lineno < view->offset ||
2447             view->lineno >= view->offset + view->height) {
2448                 scroll_steps = steps;
2449                 if (steps < 0 && -steps > view->offset) {
2450                         scroll_steps = -view->offset;
2452                 } else if (steps > 0) {
2453                         if (view->lineno == view->lines - 1 &&
2454                             view->lines > view->height) {
2455                                 scroll_steps = view->lines - view->offset - 1;
2456                                 if (scroll_steps >= view->height)
2457                                         scroll_steps -= view->height - 1;
2458                         }
2459                 }
2460         }
2462         if (!view_is_displayed(view)) {
2463                 view->offset += scroll_steps;
2464                 assert(0 <= view->offset && view->offset < view->lines);
2465                 view->ops->select(view, &view->line[view->lineno]);
2466                 return;
2467         }
2469         /* Repaint the old "current" line if we be scrolling */
2470         if (ABS(steps) < view->height)
2471                 draw_view_line(view, view->lineno - steps - view->offset);
2473         if (scroll_steps) {
2474                 do_scroll_view(view, scroll_steps);
2475                 return;
2476         }
2478         /* Draw the current line */
2479         draw_view_line(view, view->lineno - view->offset);
2481         wnoutrefresh(view->win);
2482         report("");
2486 /*
2487  * Searching
2488  */
2490 static void search_view(struct view *view, enum request request);
2492 static void
2493 select_view_line(struct view *view, unsigned long lineno)
2495         unsigned long old_lineno = view->lineno;
2496         unsigned long old_offset = view->offset;
2498         if (goto_view_line(view, view->offset, lineno)) {
2499                 if (view_is_displayed(view)) {
2500                         if (old_offset != view->offset) {
2501                                 redraw_view(view);
2502                         } else {
2503                                 draw_view_line(view, old_lineno - view->offset);
2504                                 draw_view_line(view, view->lineno - view->offset);
2505                                 wnoutrefresh(view->win);
2506                         }
2507                 } else {
2508                         view->ops->select(view, &view->line[view->lineno]);
2509                 }
2510         }
2513 static void
2514 find_next(struct view *view, enum request request)
2516         unsigned long lineno = view->lineno;
2517         int direction;
2519         if (!*view->grep) {
2520                 if (!*opt_search)
2521                         report("No previous search");
2522                 else
2523                         search_view(view, request);
2524                 return;
2525         }
2527         switch (request) {
2528         case REQ_SEARCH:
2529         case REQ_FIND_NEXT:
2530                 direction = 1;
2531                 break;
2533         case REQ_SEARCH_BACK:
2534         case REQ_FIND_PREV:
2535                 direction = -1;
2536                 break;
2538         default:
2539                 return;
2540         }
2542         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2543                 lineno += direction;
2545         /* Note, lineno is unsigned long so will wrap around in which case it
2546          * will become bigger than view->lines. */
2547         for (; lineno < view->lines; lineno += direction) {
2548                 if (view->ops->grep(view, &view->line[lineno])) {
2549                         select_view_line(view, lineno);
2550                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2551                         return;
2552                 }
2553         }
2555         report("No match found for '%s'", view->grep);
2558 static void
2559 search_view(struct view *view, enum request request)
2561         int regex_err;
2563         if (view->regex) {
2564                 regfree(view->regex);
2565                 *view->grep = 0;
2566         } else {
2567                 view->regex = calloc(1, sizeof(*view->regex));
2568                 if (!view->regex)
2569                         return;
2570         }
2572         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2573         if (regex_err != 0) {
2574                 char buf[SIZEOF_STR] = "unknown error";
2576                 regerror(regex_err, view->regex, buf, sizeof(buf));
2577                 report("Search failed: %s", buf);
2578                 return;
2579         }
2581         string_copy(view->grep, opt_search);
2583         find_next(view, request);
2586 /*
2587  * Incremental updating
2588  */
2590 static void
2591 reset_view(struct view *view)
2593         int i;
2595         for (i = 0; i < view->lines; i++)
2596                 free(view->line[i].data);
2597         free(view->line);
2599         view->p_offset = view->offset;
2600         view->p_yoffset = view->yoffset;
2601         view->p_lineno = view->lineno;
2603         view->line = NULL;
2604         view->offset = 0;
2605         view->yoffset = 0;
2606         view->lines  = 0;
2607         view->lineno = 0;
2608         view->line_alloc = 0;
2609         view->vid[0] = 0;
2610         view->update_secs = 0;
2613 static void
2614 free_argv(const char *argv[])
2616         int argc;
2618         for (argc = 0; argv[argc]; argc++)
2619                 free((void *) argv[argc]);
2622 static bool
2623 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2625         char buf[SIZEOF_STR];
2626         int argc;
2627         bool noreplace = flags == FORMAT_NONE;
2629         free_argv(dst_argv);
2631         for (argc = 0; src_argv[argc]; argc++) {
2632                 const char *arg = src_argv[argc];
2633                 size_t bufpos = 0;
2635                 while (arg) {
2636                         char *next = strstr(arg, "%(");
2637                         int len = next - arg;
2638                         const char *value;
2640                         if (!next || noreplace) {
2641                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2642                                         noreplace = TRUE;
2643                                 len = strlen(arg);
2644                                 value = "";
2646                         } else if (!prefixcmp(next, "%(directory)")) {
2647                                 value = opt_path;
2649                         } else if (!prefixcmp(next, "%(file)")) {
2650                                 value = opt_file;
2652                         } else if (!prefixcmp(next, "%(ref)")) {
2653                                 value = *opt_ref ? opt_ref : "HEAD";
2655                         } else if (!prefixcmp(next, "%(head)")) {
2656                                 value = ref_head;
2658                         } else if (!prefixcmp(next, "%(commit)")) {
2659                                 value = ref_commit;
2661                         } else if (!prefixcmp(next, "%(blob)")) {
2662                                 value = ref_blob;
2664                         } else {
2665                                 report("Unknown replacement: `%s`", next);
2666                                 return FALSE;
2667                         }
2669                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2670                                 return FALSE;
2672                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2673                 }
2675                 dst_argv[argc] = strdup(buf);
2676                 if (!dst_argv[argc])
2677                         break;
2678         }
2680         dst_argv[argc] = NULL;
2682         return src_argv[argc] == NULL;
2685 static bool
2686 restore_view_position(struct view *view)
2688         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2689                 return FALSE;
2691         /* Changing the view position cancels the restoring. */
2692         /* FIXME: Changing back to the first line is not detected. */
2693         if (view->offset != 0 || view->lineno != 0) {
2694                 view->p_restore = FALSE;
2695                 return FALSE;
2696         }
2698         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2699             view_is_displayed(view))
2700                 werase(view->win);
2702         view->yoffset = view->p_yoffset;
2703         view->p_restore = FALSE;
2705         return TRUE;
2708 static void
2709 end_update(struct view *view, bool force)
2711         if (!view->pipe)
2712                 return;
2713         while (!view->ops->read(view, NULL))
2714                 if (!force)
2715                         return;
2716         set_nonblocking_input(FALSE);
2717         if (force)
2718                 kill_io(view->pipe);
2719         done_io(view->pipe);
2720         view->pipe = NULL;
2723 static void
2724 setup_update(struct view *view, const char *vid)
2726         set_nonblocking_input(TRUE);
2727         reset_view(view);
2728         string_copy_rev(view->vid, vid);
2729         view->pipe = &view->io;
2730         view->start_time = time(NULL);
2733 static bool
2734 prepare_update(struct view *view, const char *argv[], const char *dir,
2735                enum format_flags flags)
2737         if (view->pipe)
2738                 end_update(view, TRUE);
2739         return init_io_rd(&view->io, argv, dir, flags);
2742 static bool
2743 prepare_update_file(struct view *view, const char *name)
2745         if (view->pipe)
2746                 end_update(view, TRUE);
2747         return io_open(&view->io, name);
2750 static bool
2751 begin_update(struct view *view, bool refresh)
2753         if (view->pipe)
2754                 end_update(view, TRUE);
2756         if (refresh) {
2757                 if (!start_io(&view->io))
2758                         return FALSE;
2760         } else {
2761                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2762                         opt_path[0] = 0;
2764                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2765                         return FALSE;
2767                 /* Put the current ref_* value to the view title ref
2768                  * member. This is needed by the blob view. Most other
2769                  * views sets it automatically after loading because the
2770                  * first line is a commit line. */
2771                 string_copy_rev(view->ref, view->id);
2772         }
2774         setup_update(view, view->id);
2776         return TRUE;
2779 #define ITEM_CHUNK_SIZE 256
2780 static void *
2781 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2783         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2784         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2786         if (mem == NULL || num_chunks != num_chunks_new) {
2787                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2788                 mem = realloc(mem, *size * item_size);
2789         }
2791         return mem;
2794 static struct line *
2795 realloc_lines(struct view *view, size_t line_size)
2797         size_t alloc = view->line_alloc;
2798         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2799                                          sizeof(*view->line));
2801         if (!tmp)
2802                 return NULL;
2804         view->line = tmp;
2805         view->line_alloc = alloc;
2806         return view->line;
2809 static bool
2810 update_view(struct view *view)
2812         char out_buffer[BUFSIZ * 2];
2813         char *line;
2814         /* Clear the view and redraw everything since the tree sorting
2815          * might have rearranged things. */
2816         bool redraw = view->lines == 0;
2817         bool can_read = TRUE;
2819         if (!view->pipe)
2820                 return TRUE;
2822         if (!io_can_read(view->pipe)) {
2823                 if (view->lines == 0) {
2824                         time_t secs = time(NULL) - view->start_time;
2826                         if (secs > 1 && secs > view->update_secs) {
2827                                 if (view->update_secs == 0)
2828                                         redraw_view(view);
2829                                 update_view_title(view);
2830                                 view->update_secs = secs;
2831                         }
2832                 }
2833                 return TRUE;
2834         }
2836         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2837                 if (opt_iconv != ICONV_NONE) {
2838                         ICONV_CONST char *inbuf = line;
2839                         size_t inlen = strlen(line) + 1;
2841                         char *outbuf = out_buffer;
2842                         size_t outlen = sizeof(out_buffer);
2844                         size_t ret;
2846                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2847                         if (ret != (size_t) -1)
2848                                 line = out_buffer;
2849                 }
2851                 if (!view->ops->read(view, line)) {
2852                         report("Allocation failure");
2853                         end_update(view, TRUE);
2854                         return FALSE;
2855                 }
2856         }
2858         {
2859                 unsigned long lines = view->lines;
2860                 int digits;
2862                 for (digits = 0; lines; digits++)
2863                         lines /= 10;
2865                 /* Keep the displayed view in sync with line number scaling. */
2866                 if (digits != view->digits) {
2867                         view->digits = digits;
2868                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2869                                 redraw = TRUE;
2870                 }
2871         }
2873         if (io_error(view->pipe)) {
2874                 report("Failed to read: %s", io_strerror(view->pipe));
2875                 end_update(view, TRUE);
2877         } else if (io_eof(view->pipe)) {
2878                 report("");
2879                 end_update(view, FALSE);
2880         }
2882         if (restore_view_position(view))
2883                 redraw = TRUE;
2885         if (!view_is_displayed(view))
2886                 return TRUE;
2888         if (redraw)
2889                 redraw_view_from(view, 0);
2890         else
2891                 redraw_view_dirty(view);
2893         /* Update the title _after_ the redraw so that if the redraw picks up a
2894          * commit reference in view->ref it'll be available here. */
2895         update_view_title(view);
2896         return TRUE;
2899 static struct line *
2900 add_line_data(struct view *view, void *data, enum line_type type)
2902         struct line *line;
2904         if (!realloc_lines(view, view->lines + 1))
2905                 return NULL;
2907         line = &view->line[view->lines++];
2908         memset(line, 0, sizeof(*line));
2909         line->type = type;
2910         line->data = data;
2911         line->dirty = 1;
2913         return line;
2916 static struct line *
2917 add_line_text(struct view *view, const char *text, enum line_type type)
2919         char *data = text ? strdup(text) : NULL;
2921         return data ? add_line_data(view, data, type) : NULL;
2924 static struct line *
2925 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2927         char buf[SIZEOF_STR];
2928         va_list args;
2930         va_start(args, fmt);
2931         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2932                 buf[0] = 0;
2933         va_end(args);
2935         return buf[0] ? add_line_text(view, buf, type) : NULL;
2938 /*
2939  * View opening
2940  */
2942 enum open_flags {
2943         OPEN_DEFAULT = 0,       /* Use default view switching. */
2944         OPEN_SPLIT = 1,         /* Split current view. */
2945         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2946         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2947         OPEN_PREPARED = 32,     /* Open already prepared command. */
2948 };
2950 static void
2951 open_view(struct view *prev, enum request request, enum open_flags flags)
2953         bool split = !!(flags & OPEN_SPLIT);
2954         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2955         bool nomaximize = !!(flags & OPEN_REFRESH);
2956         struct view *view = VIEW(request);
2957         int nviews = displayed_views();
2958         struct view *base_view = display[0];
2960         if (view == prev && nviews == 1 && !reload) {
2961                 report("Already in %s view", view->name);
2962                 return;
2963         }
2965         if (view->git_dir && !opt_git_dir[0]) {
2966                 report("The %s view is disabled in pager view", view->name);
2967                 return;
2968         }
2970         if (split) {
2971                 display[1] = view;
2972                 current_view = 1;
2973         } else if (!nomaximize) {
2974                 /* Maximize the current view. */
2975                 memset(display, 0, sizeof(display));
2976                 current_view = 0;
2977                 display[current_view] = view;
2978         }
2980         /* Resize the view when switching between split- and full-screen,
2981          * or when switching between two different full-screen views. */
2982         if (nviews != displayed_views() ||
2983             (nviews == 1 && base_view != display[0]))
2984                 resize_display();
2986         if (view->ops->open) {
2987                 if (view->pipe)
2988                         end_update(view, TRUE);
2989                 if (!view->ops->open(view)) {
2990                         report("Failed to load %s view", view->name);
2991                         return;
2992                 }
2993                 restore_view_position(view);
2995         } else if ((reload || strcmp(view->vid, view->id)) &&
2996                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2997                 report("Failed to load %s view", view->name);
2998                 return;
2999         }
3001         if (split && prev->lineno - prev->offset >= prev->height) {
3002                 /* Take the title line into account. */
3003                 int lines = prev->lineno - prev->offset - prev->height + 1;
3005                 /* Scroll the view that was split if the current line is
3006                  * outside the new limited view. */
3007                 do_scroll_view(prev, lines);
3008         }
3010         if (prev && view != prev) {
3011                 if (split) {
3012                         /* "Blur" the previous view. */
3013                         update_view_title(prev);
3014                 }
3016                 view->parent = prev;
3017         }
3019         if (view->pipe && view->lines == 0) {
3020                 /* Clear the old view and let the incremental updating refill
3021                  * the screen. */
3022                 werase(view->win);
3023                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3024                 report("");
3025         } else if (view_is_displayed(view)) {
3026                 redraw_view(view);
3027                 report("");
3028         }
3031 static void
3032 open_external_viewer(const char *argv[], const char *dir)
3034         def_prog_mode();           /* save current tty modes */
3035         endwin();                  /* restore original tty modes */
3036         run_io_fg(argv, dir);
3037         fprintf(stderr, "Press Enter to continue");
3038         getc(opt_tty);
3039         reset_prog_mode();
3040         redraw_display(TRUE);
3043 static void
3044 open_mergetool(const char *file)
3046         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3048         open_external_viewer(mergetool_argv, opt_cdup);
3051 static void
3052 open_editor(bool from_root, const char *file)
3054         const char *editor_argv[] = { "vi", file, NULL };
3055         const char *editor;
3057         editor = getenv("GIT_EDITOR");
3058         if (!editor && *opt_editor)
3059                 editor = opt_editor;
3060         if (!editor)
3061                 editor = getenv("VISUAL");
3062         if (!editor)
3063                 editor = getenv("EDITOR");
3064         if (!editor)
3065                 editor = "vi";
3067         editor_argv[0] = editor;
3068         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3071 static void
3072 open_run_request(enum request request)
3074         struct run_request *req = get_run_request(request);
3075         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3077         if (!req) {
3078                 report("Unknown run request");
3079                 return;
3080         }
3082         if (format_argv(argv, req->argv, FORMAT_ALL))
3083                 open_external_viewer(argv, NULL);
3084         free_argv(argv);
3087 /*
3088  * User request switch noodle
3089  */
3091 static int
3092 view_driver(struct view *view, enum request request)
3094         int i;
3096         if (request == REQ_NONE) {
3097                 doupdate();
3098                 return TRUE;
3099         }
3101         if (request > REQ_NONE) {
3102                 open_run_request(request);
3103                 /* FIXME: When all views can refresh always do this. */
3104                 if (view == VIEW(REQ_VIEW_STATUS) ||
3105                     view == VIEW(REQ_VIEW_MAIN) ||
3106                     view == VIEW(REQ_VIEW_LOG) ||
3107                     view == VIEW(REQ_VIEW_STAGE))
3108                         request = REQ_REFRESH;
3109                 else
3110                         return TRUE;
3111         }
3113         if (view && view->lines) {
3114                 request = view->ops->request(view, request, &view->line[view->lineno]);
3115                 if (request == REQ_NONE)
3116                         return TRUE;
3117         }
3119         switch (request) {
3120         case REQ_MOVE_UP:
3121         case REQ_MOVE_DOWN:
3122         case REQ_MOVE_PAGE_UP:
3123         case REQ_MOVE_PAGE_DOWN:
3124         case REQ_MOVE_FIRST_LINE:
3125         case REQ_MOVE_LAST_LINE:
3126                 move_view(view, request);
3127                 break;
3129         case REQ_SCROLL_LEFT:
3130         case REQ_SCROLL_RIGHT:
3131         case REQ_SCROLL_LINE_DOWN:
3132         case REQ_SCROLL_LINE_UP:
3133         case REQ_SCROLL_PAGE_DOWN:
3134         case REQ_SCROLL_PAGE_UP:
3135                 scroll_view(view, request);
3136                 break;
3138         case REQ_VIEW_BLAME:
3139                 if (!opt_file[0]) {
3140                         report("No file chosen, press %s to open tree view",
3141                                get_key(REQ_VIEW_TREE));
3142                         break;
3143                 }
3144                 open_view(view, request, OPEN_DEFAULT);
3145                 break;
3147         case REQ_VIEW_BLOB:
3148                 if (!ref_blob[0]) {
3149                         report("No file chosen, press %s to open tree view",
3150                                get_key(REQ_VIEW_TREE));
3151                         break;
3152                 }
3153                 open_view(view, request, OPEN_DEFAULT);
3154                 break;
3156         case REQ_VIEW_PAGER:
3157                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3158                         report("No pager content, press %s to run command from prompt",
3159                                get_key(REQ_PROMPT));
3160                         break;
3161                 }
3162                 open_view(view, request, OPEN_DEFAULT);
3163                 break;
3165         case REQ_VIEW_STAGE:
3166                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3167                         report("No stage content, press %s to open the status view and choose file",
3168                                get_key(REQ_VIEW_STATUS));
3169                         break;
3170                 }
3171                 open_view(view, request, OPEN_DEFAULT);
3172                 break;
3174         case REQ_VIEW_STATUS:
3175                 if (opt_is_inside_work_tree == FALSE) {
3176                         report("The status view requires a working tree");
3177                         break;
3178                 }
3179                 open_view(view, request, OPEN_DEFAULT);
3180                 break;
3182         case REQ_VIEW_MAIN:
3183         case REQ_VIEW_DIFF:
3184         case REQ_VIEW_LOG:
3185         case REQ_VIEW_TREE:
3186         case REQ_VIEW_HELP:
3187                 open_view(view, request, OPEN_DEFAULT);
3188                 break;
3190         case REQ_NEXT:
3191         case REQ_PREVIOUS:
3192                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3194                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3195                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3196                    (view == VIEW(REQ_VIEW_DIFF) &&
3197                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3198                    (view == VIEW(REQ_VIEW_STAGE) &&
3199                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3200                    (view == VIEW(REQ_VIEW_BLOB) &&
3201                      view->parent == VIEW(REQ_VIEW_TREE))) {
3202                         int line;
3204                         view = view->parent;
3205                         line = view->lineno;
3206                         move_view(view, request);
3207                         if (view_is_displayed(view))
3208                                 update_view_title(view);
3209                         if (line != view->lineno)
3210                                 view->ops->request(view, REQ_ENTER,
3211                                                    &view->line[view->lineno]);
3213                 } else {
3214                         move_view(view, request);
3215                 }
3216                 break;
3218         case REQ_VIEW_NEXT:
3219         {
3220                 int nviews = displayed_views();
3221                 int next_view = (current_view + 1) % nviews;
3223                 if (next_view == current_view) {
3224                         report("Only one view is displayed");
3225                         break;
3226                 }
3228                 current_view = next_view;
3229                 /* Blur out the title of the previous view. */
3230                 update_view_title(view);
3231                 report("");
3232                 break;
3233         }
3234         case REQ_REFRESH:
3235                 report("Refreshing is not yet supported for the %s view", view->name);
3236                 break;
3238         case REQ_MAXIMIZE:
3239                 if (displayed_views() == 2)
3240                         maximize_view(view);
3241                 break;
3243         case REQ_TOGGLE_LINENO:
3244                 toggle_view_option(&opt_line_number, "line numbers");
3245                 break;
3247         case REQ_TOGGLE_DATE:
3248                 toggle_view_option(&opt_date, "date display");
3249                 break;
3251         case REQ_TOGGLE_AUTHOR:
3252                 toggle_view_option(&opt_author, "author display");
3253                 break;
3255         case REQ_TOGGLE_REV_GRAPH:
3256                 toggle_view_option(&opt_rev_graph, "revision graph display");
3257                 break;
3259         case REQ_TOGGLE_REFS:
3260                 toggle_view_option(&opt_show_refs, "reference display");
3261                 break;
3263         case REQ_SEARCH:
3264         case REQ_SEARCH_BACK:
3265                 search_view(view, request);
3266                 break;
3268         case REQ_FIND_NEXT:
3269         case REQ_FIND_PREV:
3270                 find_next(view, request);
3271                 break;
3273         case REQ_STOP_LOADING:
3274                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3275                         view = &views[i];
3276                         if (view->pipe)
3277                                 report("Stopped loading the %s view", view->name),
3278                         end_update(view, TRUE);
3279                 }
3280                 break;
3282         case REQ_SHOW_VERSION:
3283                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3284                 return TRUE;
3286         case REQ_SCREEN_REDRAW:
3287                 redraw_display(TRUE);
3288                 break;
3290         case REQ_EDIT:
3291                 report("Nothing to edit");
3292                 break;
3294         case REQ_ENTER:
3295                 report("Nothing to enter");
3296                 break;
3298         case REQ_VIEW_CLOSE:
3299                 /* XXX: Mark closed views by letting view->parent point to the
3300                  * view itself. Parents to closed view should never be
3301                  * followed. */
3302                 if (view->parent &&
3303                     view->parent->parent != view->parent) {
3304                         maximize_view(view->parent);
3305                         view->parent = view;
3306                         break;
3307                 }
3308                 /* Fall-through */
3309         case REQ_QUIT:
3310                 return FALSE;
3312         default:
3313                 report("Unknown key, press 'h' for help");
3314                 return TRUE;
3315         }
3317         return TRUE;
3321 /*
3322  * View backend utilities
3323  */
3325 static void
3326 parse_timezone(time_t *time, const char *zone)
3328         long tz;
3330         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3331         tz += ('0' - zone[2]) * 60 * 60;
3332         tz += ('0' - zone[3]) * 60;
3333         tz += ('0' - zone[4]);
3335         if (zone[0] == '-')
3336                 tz = -tz;
3338         *time -= tz;
3341 /* Parse author lines where the name may be empty:
3342  *      author  <email@address.tld> 1138474660 +0100
3343  */
3344 static void
3345 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3347         char *nameend = strchr(ident, '<');
3348         char *emailend = strchr(ident, '>');
3350         if (nameend && emailend)
3351                 *nameend = *emailend = 0;
3352         ident = chomp_string(ident);
3353         if (!*ident) {
3354                 if (nameend)
3355                         ident = chomp_string(nameend + 1);
3356                 if (!*ident)
3357                         ident = "Unknown";
3358         }
3360         string_ncopy_do(author, authorsize, ident, strlen(ident));
3362         /* Parse epoch and timezone */
3363         if (emailend && emailend[1] == ' ') {
3364                 char *secs = emailend + 2;
3365                 char *zone = strchr(secs, ' ');
3366                 time_t time = (time_t) atol(secs);
3368                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3369                         parse_timezone(&time, zone + 1);
3371                 gmtime_r(&time, tm);
3372         }
3375 static enum input_status
3376 select_commit_parent_handler(void *data, char *buf, int c)
3378         size_t parents = *(size_t *) data;
3379         int parent = 0;
3381         if (!isdigit(c))
3382                 return INPUT_SKIP;
3384         if (*buf)
3385                 parent = atoi(buf) * 10;
3386         parent += c - '0';
3388         if (parent > parents)
3389                 return INPUT_SKIP;
3390         return INPUT_OK;
3393 static bool
3394 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3396         char buf[SIZEOF_STR * 4];
3397         const char *revlist_argv[] = {
3398                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3399         };
3400         int parents;
3402         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3403             !*chomp_string(buf) ||
3404             (parents = (strlen(buf) / 40) - 1) < 0) {
3405                 report("Failed to get parent information");
3406                 return FALSE;
3408         } else if (parents == 0) {
3409                 if (path)
3410                         report("Path '%s' does not exist in the parent", path);
3411                 else
3412                         report("The selected commit has no parents");
3413                 return FALSE;
3414         }
3416         if (parents > 1) {
3417                 char prompt[SIZEOF_STR];
3418                 char *result;
3420                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3421                         return FALSE;
3422                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3423                 if (!result)
3424                         return FALSE;
3425                 parents = atoi(result);
3426         }
3428         string_copy_rev(rev, &buf[41 * parents]);
3429         return TRUE;
3432 /*
3433  * Pager backend
3434  */
3436 static bool
3437 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3439         char text[SIZEOF_STR];
3441         if (opt_line_number && draw_lineno(view, lineno))
3442                 return TRUE;
3444         string_expand(text, sizeof(text), line->data, opt_tab_size);
3445         draw_text(view, line->type, text, TRUE);
3446         return TRUE;
3449 static bool
3450 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3452         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3453         char refbuf[SIZEOF_STR];
3454         char *ref = NULL;
3456         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3457                 ref = chomp_string(refbuf);
3459         if (!ref || !*ref)
3460                 return TRUE;
3462         /* This is the only fatal call, since it can "corrupt" the buffer. */
3463         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3464                 return FALSE;
3466         return TRUE;
3469 static void
3470 add_pager_refs(struct view *view, struct line *line)
3472         char buf[SIZEOF_STR];
3473         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3474         struct ref **refs;
3475         size_t bufpos = 0, refpos = 0;
3476         const char *sep = "Refs: ";
3477         bool is_tag = FALSE;
3479         assert(line->type == LINE_COMMIT);
3481         refs = get_refs(commit_id);
3482         if (!refs) {
3483                 if (view == VIEW(REQ_VIEW_DIFF))
3484                         goto try_add_describe_ref;
3485                 return;
3486         }
3488         do {
3489                 struct ref *ref = refs[refpos];
3490                 const char *fmt = ref->tag    ? "%s[%s]" :
3491                                   ref->remote ? "%s<%s>" : "%s%s";
3493                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3494                         return;
3495                 sep = ", ";
3496                 if (ref->tag)
3497                         is_tag = TRUE;
3498         } while (refs[refpos++]->next);
3500         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3501 try_add_describe_ref:
3502                 /* Add <tag>-g<commit_id> "fake" reference. */
3503                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3504                         return;
3505         }
3507         if (bufpos == 0)
3508                 return;
3510         add_line_text(view, buf, LINE_PP_REFS);
3513 static bool
3514 pager_read(struct view *view, char *data)
3516         struct line *line;
3518         if (!data)
3519                 return TRUE;
3521         line = add_line_text(view, data, get_line_type(data));
3522         if (!line)
3523                 return FALSE;
3525         if (line->type == LINE_COMMIT &&
3526             (view == VIEW(REQ_VIEW_DIFF) ||
3527              view == VIEW(REQ_VIEW_LOG)))
3528                 add_pager_refs(view, line);
3530         return TRUE;
3533 static enum request
3534 pager_request(struct view *view, enum request request, struct line *line)
3536         int split = 0;
3538         if (request != REQ_ENTER)
3539                 return request;
3541         if (line->type == LINE_COMMIT &&
3542            (view == VIEW(REQ_VIEW_LOG) ||
3543             view == VIEW(REQ_VIEW_PAGER))) {
3544                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3545                 split = 1;
3546         }
3548         /* Always scroll the view even if it was split. That way
3549          * you can use Enter to scroll through the log view and
3550          * split open each commit diff. */
3551         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3553         /* FIXME: A minor workaround. Scrolling the view will call report("")
3554          * but if we are scrolling a non-current view this won't properly
3555          * update the view title. */
3556         if (split)
3557                 update_view_title(view);
3559         return REQ_NONE;
3562 static bool
3563 pager_grep(struct view *view, struct line *line)
3565         regmatch_t pmatch;
3566         char *text = line->data;
3568         if (!*text)
3569                 return FALSE;
3571         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3572                 return FALSE;
3574         return TRUE;
3577 static void
3578 pager_select(struct view *view, struct line *line)
3580         if (line->type == LINE_COMMIT) {
3581                 char *text = (char *)line->data + STRING_SIZE("commit ");
3583                 if (view != VIEW(REQ_VIEW_PAGER))
3584                         string_copy_rev(view->ref, text);
3585                 string_copy_rev(ref_commit, text);
3586         }
3589 static struct view_ops pager_ops = {
3590         "line",
3591         NULL,
3592         NULL,
3593         pager_read,
3594         pager_draw,
3595         pager_request,
3596         pager_grep,
3597         pager_select,
3598 };
3600 static const char *log_argv[SIZEOF_ARG] = {
3601         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3602 };
3604 static enum request
3605 log_request(struct view *view, enum request request, struct line *line)
3607         switch (request) {
3608         case REQ_REFRESH:
3609                 load_refs();
3610                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3611                 return REQ_NONE;
3612         default:
3613                 return pager_request(view, request, line);
3614         }
3617 static struct view_ops log_ops = {
3618         "line",
3619         log_argv,
3620         NULL,
3621         pager_read,
3622         pager_draw,
3623         log_request,
3624         pager_grep,
3625         pager_select,
3626 };
3628 static const char *diff_argv[SIZEOF_ARG] = {
3629         "git", "show", "--pretty=fuller", "--no-color", "--root",
3630                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3631 };
3633 static struct view_ops diff_ops = {
3634         "line",
3635         diff_argv,
3636         NULL,
3637         pager_read,
3638         pager_draw,
3639         pager_request,
3640         pager_grep,
3641         pager_select,
3642 };
3644 /*
3645  * Help backend
3646  */
3648 static bool
3649 help_open(struct view *view)
3651         char buf[SIZEOF_STR];
3652         size_t bufpos;
3653         int i;
3655         if (view->lines > 0)
3656                 return TRUE;
3658         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3660         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3661                 const char *key;
3663                 if (req_info[i].request == REQ_NONE)
3664                         continue;
3666                 if (!req_info[i].request) {
3667                         add_line_text(view, "", LINE_DEFAULT);
3668                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3669                         continue;
3670                 }
3672                 key = get_key(req_info[i].request);
3673                 if (!*key)
3674                         key = "(no key defined)";
3676                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3677                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3678                         if (buf[bufpos] == '_')
3679                                 buf[bufpos] = '-';
3680                 }
3682                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3683                                 key, buf, req_info[i].help);
3684         }
3686         if (run_requests) {
3687                 add_line_text(view, "", LINE_DEFAULT);
3688                 add_line_text(view, "External commands:", LINE_DEFAULT);
3689         }
3691         for (i = 0; i < run_requests; i++) {
3692                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3693                 const char *key;
3694                 int argc;
3696                 if (!req)
3697                         continue;
3699                 key = get_key_name(req->key);
3700                 if (!*key)
3701                         key = "(no key defined)";
3703                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3704                         if (!string_format_from(buf, &bufpos, "%s%s",
3705                                                 argc ? " " : "", req->argv[argc]))
3706                                 return REQ_NONE;
3708                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3709                                 keymap_table[req->keymap].name, key, buf);
3710         }
3712         return TRUE;
3715 static struct view_ops help_ops = {
3716         "line",
3717         NULL,
3718         help_open,
3719         NULL,
3720         pager_draw,
3721         pager_request,
3722         pager_grep,
3723         pager_select,
3724 };
3727 /*
3728  * Tree backend
3729  */
3731 struct tree_stack_entry {
3732         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3733         unsigned long lineno;           /* Line number to restore */
3734         char *name;                     /* Position of name in opt_path */
3735 };
3737 /* The top of the path stack. */
3738 static struct tree_stack_entry *tree_stack = NULL;
3739 unsigned long tree_lineno = 0;
3741 static void
3742 pop_tree_stack_entry(void)
3744         struct tree_stack_entry *entry = tree_stack;
3746         tree_lineno = entry->lineno;
3747         entry->name[0] = 0;
3748         tree_stack = entry->prev;
3749         free(entry);
3752 static void
3753 push_tree_stack_entry(const char *name, unsigned long lineno)
3755         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3756         size_t pathlen = strlen(opt_path);
3758         if (!entry)
3759                 return;
3761         entry->prev = tree_stack;
3762         entry->name = opt_path + pathlen;
3763         tree_stack = entry;
3765         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3766                 pop_tree_stack_entry();
3767                 return;
3768         }
3770         /* Move the current line to the first tree entry. */
3771         tree_lineno = 1;
3772         entry->lineno = lineno;
3775 /* Parse output from git-ls-tree(1):
3776  *
3777  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3778  */
3780 #define SIZEOF_TREE_ATTR \
3781         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3783 #define SIZEOF_TREE_MODE \
3784         STRING_SIZE("100644 ")
3786 #define TREE_ID_OFFSET \
3787         STRING_SIZE("100644 blob ")
3789 struct tree_entry {
3790         char id[SIZEOF_REV];
3791         mode_t mode;
3792         struct tm time;                 /* Date from the author ident. */
3793         char author[75];                /* Author of the commit. */
3794         char name[1];
3795 };
3797 static const char *
3798 tree_path(struct line *line)
3800         return ((struct tree_entry *) line->data)->name;
3804 static int
3805 tree_compare_entry(struct line *line1, struct line *line2)
3807         if (line1->type != line2->type)
3808                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3809         return strcmp(tree_path(line1), tree_path(line2));
3812 static struct line *
3813 tree_entry(struct view *view, enum line_type type, const char *path,
3814            const char *mode, const char *id)
3816         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3817         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3819         if (!entry || !line) {
3820                 free(entry);
3821                 return NULL;
3822         }
3824         strncpy(entry->name, path, strlen(path));
3825         if (mode)
3826                 entry->mode = strtoul(mode, NULL, 8);
3827         if (id)
3828                 string_copy_rev(entry->id, id);
3830         return line;
3833 static bool
3834 tree_read_date(struct view *view, char *text, bool *read_date)
3836         static char author_name[SIZEOF_STR];
3837         static struct tm author_time;
3839         if (!text && *read_date) {
3840                 *read_date = FALSE;
3841                 return TRUE;
3843         } else if (!text) {
3844                 char *path = *opt_path ? opt_path : ".";
3845                 /* Find next entry to process */
3846                 const char *log_file[] = {
3847                         "git", "log", "--no-color", "--pretty=raw",
3848                                 "--cc", "--raw", view->id, "--", path, NULL
3849                 };
3850                 struct io io = {};
3852                 if (!view->lines) {
3853                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3854                         report("Tree is empty");
3855                         return TRUE;
3856                 }
3858                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3859                         report("Failed to load tree data");
3860                         return TRUE;
3861                 }
3863                 done_io(view->pipe);
3864                 view->io = io;
3865                 *read_date = TRUE;
3866                 return FALSE;
3868         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3869                 parse_author_line(text + STRING_SIZE("author "),
3870                                   author_name, sizeof(author_name), &author_time);
3872         } else if (*text == ':') {
3873                 char *pos;
3874                 size_t annotated = 1;
3875                 size_t i;
3877                 pos = strchr(text, '\t');
3878                 if (!pos)
3879                         return TRUE;
3880                 text = pos + 1;
3881                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3882                         text += strlen(opt_prefix);
3883                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3884                         text += strlen(opt_path);
3885                 pos = strchr(text, '/');
3886                 if (pos)
3887                         *pos = 0;
3889                 for (i = 1; i < view->lines; i++) {
3890                         struct line *line = &view->line[i];
3891                         struct tree_entry *entry = line->data;
3893                         annotated += !!*entry->author;
3894                         if (*entry->author || strcmp(entry->name, text))
3895                                 continue;
3897                         string_copy(entry->author, author_name);
3898                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3899                         line->dirty = 1;
3900                         break;
3901                 }
3903                 if (annotated == view->lines)
3904                         kill_io(view->pipe);
3905         }
3906         return TRUE;
3909 static bool
3910 tree_read(struct view *view, char *text)
3912         static bool read_date = FALSE;
3913         struct tree_entry *data;
3914         struct line *entry, *line;
3915         enum line_type type;
3916         size_t textlen = text ? strlen(text) : 0;
3917         char *path = text + SIZEOF_TREE_ATTR;
3919         if (read_date || !text)
3920                 return tree_read_date(view, text, &read_date);
3922         if (textlen <= SIZEOF_TREE_ATTR)
3923                 return FALSE;
3924         if (view->lines == 0 &&
3925             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3926                 return FALSE;
3928         /* Strip the path part ... */
3929         if (*opt_path) {
3930                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3931                 size_t striplen = strlen(opt_path);
3933                 if (pathlen > striplen)
3934                         memmove(path, path + striplen,
3935                                 pathlen - striplen + 1);
3937                 /* Insert "link" to parent directory. */
3938                 if (view->lines == 1 &&
3939                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3940                         return FALSE;
3941         }
3943         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3944         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3945         if (!entry)
3946                 return FALSE;
3947         data = entry->data;
3949         /* Skip "Directory ..." and ".." line. */
3950         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3951                 if (tree_compare_entry(line, entry) <= 0)
3952                         continue;
3954                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3956                 line->data = data;
3957                 line->type = type;
3958                 for (; line <= entry; line++)
3959                         line->dirty = line->cleareol = 1;
3960                 return TRUE;
3961         }
3963         if (tree_lineno > view->lineno) {
3964                 view->lineno = tree_lineno;
3965                 tree_lineno = 0;
3966         }
3968         return TRUE;
3971 static bool
3972 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3974         struct tree_entry *entry = line->data;
3976         if (line->type == LINE_TREE_HEAD) {
3977                 if (draw_text(view, line->type, "Directory path /", TRUE))
3978                         return TRUE;
3979         } else {
3980                 if (draw_mode(view, entry->mode))
3981                         return TRUE;
3983                 if (opt_author && draw_author(view, entry->author))
3984                         return TRUE;
3986                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3987                         return TRUE;
3988         }
3989         if (draw_text(view, line->type, entry->name, TRUE))
3990                 return TRUE;
3991         return TRUE;
3994 static void
3995 open_blob_editor()
3997         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3998         int fd = mkstemp(file);
4000         if (fd == -1)
4001                 report("Failed to create temporary file");
4002         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4003                 report("Failed to save blob data to file");
4004         else
4005                 open_editor(FALSE, file);
4006         if (fd != -1)
4007                 unlink(file);
4010 static enum request
4011 tree_request(struct view *view, enum request request, struct line *line)
4013         enum open_flags flags;
4015         switch (request) {
4016         case REQ_VIEW_BLAME:
4017                 if (line->type != LINE_TREE_FILE) {
4018                         report("Blame only supported for files");
4019                         return REQ_NONE;
4020                 }
4022                 string_copy(opt_ref, view->vid);
4023                 return request;
4025         case REQ_EDIT:
4026                 if (line->type != LINE_TREE_FILE) {
4027                         report("Edit only supported for files");
4028                 } else if (!is_head_commit(view->vid)) {
4029                         open_blob_editor();
4030                 } else {
4031                         open_editor(TRUE, opt_file);
4032                 }
4033                 return REQ_NONE;
4035         case REQ_PARENT:
4036                 if (!*opt_path) {
4037                         /* quit view if at top of tree */
4038                         return REQ_VIEW_CLOSE;
4039                 }
4040                 /* fake 'cd  ..' */
4041                 line = &view->line[1];
4042                 break;
4044         case REQ_ENTER:
4045                 break;
4047         default:
4048                 return request;
4049         }
4051         /* Cleanup the stack if the tree view is at a different tree. */
4052         while (!*opt_path && tree_stack)
4053                 pop_tree_stack_entry();
4055         switch (line->type) {
4056         case LINE_TREE_DIR:
4057                 /* Depending on whether it is a subdirectory or parent link
4058                  * mangle the path buffer. */
4059                 if (line == &view->line[1] && *opt_path) {
4060                         pop_tree_stack_entry();
4062                 } else {
4063                         const char *basename = tree_path(line);
4065                         push_tree_stack_entry(basename, view->lineno);
4066                 }
4068                 /* Trees and subtrees share the same ID, so they are not not
4069                  * unique like blobs. */
4070                 flags = OPEN_RELOAD;
4071                 request = REQ_VIEW_TREE;
4072                 break;
4074         case LINE_TREE_FILE:
4075                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4076                 request = REQ_VIEW_BLOB;
4077                 break;
4079         default:
4080                 return REQ_NONE;
4081         }
4083         open_view(view, request, flags);
4084         if (request == REQ_VIEW_TREE)
4085                 view->lineno = tree_lineno;
4087         return REQ_NONE;
4090 static void
4091 tree_select(struct view *view, struct line *line)
4093         struct tree_entry *entry = line->data;
4095         if (line->type == LINE_TREE_FILE) {
4096                 string_copy_rev(ref_blob, entry->id);
4097                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4099         } else if (line->type != LINE_TREE_DIR) {
4100                 return;
4101         }
4103         string_copy_rev(view->ref, entry->id);
4106 static const char *tree_argv[SIZEOF_ARG] = {
4107         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4108 };
4110 static struct view_ops tree_ops = {
4111         "file",
4112         tree_argv,
4113         NULL,
4114         tree_read,
4115         tree_draw,
4116         tree_request,
4117         pager_grep,
4118         tree_select,
4119 };
4121 static bool
4122 blob_read(struct view *view, char *line)
4124         if (!line)
4125                 return TRUE;
4126         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4129 static enum request
4130 blob_request(struct view *view, enum request request, struct line *line)
4132         switch (request) {
4133         case REQ_EDIT:
4134                 open_blob_editor();
4135                 return REQ_NONE;
4136         default:
4137                 return pager_request(view, request, line);
4138         }
4141 static const char *blob_argv[SIZEOF_ARG] = {
4142         "git", "cat-file", "blob", "%(blob)", NULL
4143 };
4145 static struct view_ops blob_ops = {
4146         "line",
4147         blob_argv,
4148         NULL,
4149         blob_read,
4150         pager_draw,
4151         blob_request,
4152         pager_grep,
4153         pager_select,
4154 };
4156 /*
4157  * Blame backend
4158  *
4159  * Loading the blame view is a two phase job:
4160  *
4161  *  1. File content is read either using opt_file from the
4162  *     filesystem or using git-cat-file.
4163  *  2. Then blame information is incrementally added by
4164  *     reading output from git-blame.
4165  */
4167 static const char *blame_head_argv[] = {
4168         "git", "blame", "--incremental", "--", "%(file)", NULL
4169 };
4171 static const char *blame_ref_argv[] = {
4172         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4173 };
4175 static const char *blame_cat_file_argv[] = {
4176         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4177 };
4179 struct blame_commit {
4180         char id[SIZEOF_REV];            /* SHA1 ID. */
4181         char title[128];                /* First line of the commit message. */
4182         char author[75];                /* Author of the commit. */
4183         struct tm time;                 /* Date from the author ident. */
4184         char filename[128];             /* Name of file. */
4185         bool has_previous;              /* Was a "previous" line detected. */
4186 };
4188 struct blame {
4189         struct blame_commit *commit;
4190         unsigned long lineno;
4191         char text[1];
4192 };
4194 static bool
4195 blame_open(struct view *view)
4197         if (*opt_ref || !io_open(&view->io, opt_file)) {
4198                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4199                         return FALSE;
4200         }
4202         setup_update(view, opt_file);
4203         string_format(view->ref, "%s ...", opt_file);
4205         return TRUE;
4208 static struct blame_commit *
4209 get_blame_commit(struct view *view, const char *id)
4211         size_t i;
4213         for (i = 0; i < view->lines; i++) {
4214                 struct blame *blame = view->line[i].data;
4216                 if (!blame->commit)
4217                         continue;
4219                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4220                         return blame->commit;
4221         }
4223         {
4224                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4226                 if (commit)
4227                         string_ncopy(commit->id, id, SIZEOF_REV);
4228                 return commit;
4229         }
4232 static bool
4233 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4235         const char *pos = *posref;
4237         *posref = NULL;
4238         pos = strchr(pos + 1, ' ');
4239         if (!pos || !isdigit(pos[1]))
4240                 return FALSE;
4241         *number = atoi(pos + 1);
4242         if (*number < min || *number > max)
4243                 return FALSE;
4245         *posref = pos;
4246         return TRUE;
4249 static struct blame_commit *
4250 parse_blame_commit(struct view *view, const char *text, int *blamed)
4252         struct blame_commit *commit;
4253         struct blame *blame;
4254         const char *pos = text + SIZEOF_REV - 2;
4255         size_t orig_lineno = 0;
4256         size_t lineno;
4257         size_t group;
4259         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4260                 return NULL;
4262         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4263             !parse_number(&pos, &lineno, 1, view->lines) ||
4264             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4265                 return NULL;
4267         commit = get_blame_commit(view, text);
4268         if (!commit)
4269                 return NULL;
4271         *blamed += group;
4272         while (group--) {
4273                 struct line *line = &view->line[lineno + group - 1];
4275                 blame = line->data;
4276                 blame->commit = commit;
4277                 blame->lineno = orig_lineno + group - 1;
4278                 line->dirty = 1;
4279         }
4281         return commit;
4284 static bool
4285 blame_read_file(struct view *view, const char *line, bool *read_file)
4287         if (!line) {
4288                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4289                 struct io io = {};
4291                 if (view->lines == 0 && !view->parent)
4292                         die("No blame exist for %s", view->vid);
4294                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4295                         report("Failed to load blame data");
4296                         return TRUE;
4297                 }
4299                 done_io(view->pipe);
4300                 view->io = io;
4301                 *read_file = FALSE;
4302                 return FALSE;
4304         } else {
4305                 size_t linelen = strlen(line);
4306                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4308                 if (!blame)
4309                         return FALSE;
4311                 blame->commit = NULL;
4312                 strncpy(blame->text, line, linelen);
4313                 blame->text[linelen] = 0;
4314                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4315         }
4318 static bool
4319 match_blame_header(const char *name, char **line)
4321         size_t namelen = strlen(name);
4322         bool matched = !strncmp(name, *line, namelen);
4324         if (matched)
4325                 *line += namelen;
4327         return matched;
4330 static bool
4331 blame_read(struct view *view, char *line)
4333         static struct blame_commit *commit = NULL;
4334         static int blamed = 0;
4335         static time_t author_time;
4336         static bool read_file = TRUE;
4338         if (read_file)
4339                 return blame_read_file(view, line, &read_file);
4341         if (!line) {
4342                 /* Reset all! */
4343                 commit = NULL;
4344                 blamed = 0;
4345                 read_file = TRUE;
4346                 string_format(view->ref, "%s", view->vid);
4347                 if (view_is_displayed(view)) {
4348                         update_view_title(view);
4349                         redraw_view_from(view, 0);
4350                 }
4351                 return TRUE;
4352         }
4354         if (!commit) {
4355                 commit = parse_blame_commit(view, line, &blamed);
4356                 string_format(view->ref, "%s %2d%%", view->vid,
4357                               view->lines ? blamed * 100 / view->lines : 0);
4359         } else if (match_blame_header("author ", &line)) {
4360                 string_ncopy(commit->author, line, strlen(line));
4362         } else if (match_blame_header("author-time ", &line)) {
4363                 author_time = (time_t) atol(line);
4365         } else if (match_blame_header("author-tz ", &line)) {
4366                 parse_timezone(&author_time, line);
4367                 gmtime_r(&author_time, &commit->time);
4369         } else if (match_blame_header("summary ", &line)) {
4370                 string_ncopy(commit->title, line, strlen(line));
4372         } else if (match_blame_header("previous ", &line)) {
4373                 commit->has_previous = TRUE;
4375         } else if (match_blame_header("filename ", &line)) {
4376                 string_ncopy(commit->filename, line, strlen(line));
4377                 commit = NULL;
4378         }
4380         return TRUE;
4383 static bool
4384 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4386         struct blame *blame = line->data;
4387         struct tm *time = NULL;
4388         const char *id = NULL, *author = NULL;
4389         char text[SIZEOF_STR];
4391         if (blame->commit && *blame->commit->filename) {
4392                 id = blame->commit->id;
4393                 author = blame->commit->author;
4394                 time = &blame->commit->time;
4395         }
4397         if (opt_date && draw_date(view, time))
4398                 return TRUE;
4400         if (opt_author && draw_author(view, author))
4401                 return TRUE;
4403         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4404                 return TRUE;
4406         if (draw_lineno(view, lineno))
4407                 return TRUE;
4409         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4410         draw_text(view, LINE_DEFAULT, text, TRUE);
4411         return TRUE;
4414 static bool
4415 check_blame_commit(struct blame *blame, bool check_null_id)
4417         if (!blame->commit)
4418                 report("Commit data not loaded yet");
4419         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4420                 report("No commit exist for the selected line");
4421         else
4422                 return TRUE;
4423         return FALSE;
4426 static void
4427 setup_blame_parent_line(struct view *view, struct blame *blame)
4429         const char *diff_tree_argv[] = {
4430                 "git", "diff-tree", "-U0", blame->commit->id,
4431                         "--", blame->commit->filename, NULL
4432         };
4433         struct io io = {};
4434         int parent_lineno = -1;
4435         int blamed_lineno = -1;
4436         char *line;
4438         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4439                 return;
4441         while ((line = io_get(&io, '\n', TRUE))) {
4442                 if (*line == '@') {
4443                         char *pos = strchr(line, '+');
4445                         parent_lineno = atoi(line + 4);
4446                         if (pos)
4447                                 blamed_lineno = atoi(pos + 1);
4449                 } else if (*line == '+' && parent_lineno != -1) {
4450                         if (blame->lineno == blamed_lineno - 1 &&
4451                             !strcmp(blame->text, line + 1)) {
4452                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4453                                 break;
4454                         }
4455                         blamed_lineno++;
4456                 }
4457         }
4459         done_io(&io);
4462 static enum request
4463 blame_request(struct view *view, enum request request, struct line *line)
4465         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4466         struct blame *blame = line->data;
4468         switch (request) {
4469         case REQ_VIEW_BLAME:
4470                 if (check_blame_commit(blame, TRUE)) {
4471                         string_copy(opt_ref, blame->commit->id);
4472                         string_copy(opt_file, blame->commit->filename);
4473                         if (blame->lineno)
4474                                 view->lineno = blame->lineno;
4475                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4476                 }
4477                 break;
4479         case REQ_PARENT:
4480                 if (check_blame_commit(blame, TRUE) &&
4481                     select_commit_parent(blame->commit->id, opt_ref,
4482                                          blame->commit->filename)) {
4483                         string_copy(opt_file, blame->commit->filename);
4484                         setup_blame_parent_line(view, blame);
4485                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4486                 }
4487                 break;
4489         case REQ_ENTER:
4490                 if (!check_blame_commit(blame, FALSE))
4491                         break;
4493                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4494                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4495                         break;
4497                 if (!strcmp(blame->commit->id, NULL_ID)) {
4498                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4499                         const char *diff_index_argv[] = {
4500                                 "git", "diff-index", "--root", "--patch-with-stat",
4501                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4502                         };
4504                         if (!blame->commit->has_previous) {
4505                                 diff_index_argv[1] = "diff";
4506                                 diff_index_argv[2] = "--no-color";
4507                                 diff_index_argv[6] = "--";
4508                                 diff_index_argv[7] = "/dev/null";
4509                         }
4511                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4512                                 report("Failed to allocate diff command");
4513                                 break;
4514                         }
4515                         flags |= OPEN_PREPARED;
4516                 }
4518                 open_view(view, REQ_VIEW_DIFF, flags);
4519                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4520                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4521                 break;
4523         default:
4524                 return request;
4525         }
4527         return REQ_NONE;
4530 static bool
4531 blame_grep(struct view *view, struct line *line)
4533         struct blame *blame = line->data;
4534         struct blame_commit *commit = blame->commit;
4535         regmatch_t pmatch;
4537 #define MATCH(text, on)                                                 \
4538         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4540         if (commit) {
4541                 char buf[DATE_COLS + 1];
4543                 if (MATCH(commit->title, 1) ||
4544                     MATCH(commit->author, opt_author) ||
4545                     MATCH(commit->id, opt_date))
4546                         return TRUE;
4548                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4549                     MATCH(buf, 1))
4550                         return TRUE;
4551         }
4553         return MATCH(blame->text, 1);
4555 #undef MATCH
4558 static void
4559 blame_select(struct view *view, struct line *line)
4561         struct blame *blame = line->data;
4562         struct blame_commit *commit = blame->commit;
4564         if (!commit)
4565                 return;
4567         if (!strcmp(commit->id, NULL_ID))
4568                 string_ncopy(ref_commit, "HEAD", 4);
4569         else
4570                 string_copy_rev(ref_commit, commit->id);
4573 static struct view_ops blame_ops = {
4574         "line",
4575         NULL,
4576         blame_open,
4577         blame_read,
4578         blame_draw,
4579         blame_request,
4580         blame_grep,
4581         blame_select,
4582 };
4584 /*
4585  * Status backend
4586  */
4588 struct status {
4589         char status;
4590         struct {
4591                 mode_t mode;
4592                 char rev[SIZEOF_REV];
4593                 char name[SIZEOF_STR];
4594         } old;
4595         struct {
4596                 mode_t mode;
4597                 char rev[SIZEOF_REV];
4598                 char name[SIZEOF_STR];
4599         } new;
4600 };
4602 static char status_onbranch[SIZEOF_STR];
4603 static struct status stage_status;
4604 static enum line_type stage_line_type;
4605 static size_t stage_chunks;
4606 static int *stage_chunk;
4608 /* This should work even for the "On branch" line. */
4609 static inline bool
4610 status_has_none(struct view *view, struct line *line)
4612         return line < view->line + view->lines && !line[1].data;
4615 /* Get fields from the diff line:
4616  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4617  */
4618 static inline bool
4619 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4621         const char *old_mode = buf +  1;
4622         const char *new_mode = buf +  8;
4623         const char *old_rev  = buf + 15;
4624         const char *new_rev  = buf + 56;
4625         const char *status   = buf + 97;
4627         if (bufsize < 98 ||
4628             old_mode[-1] != ':' ||
4629             new_mode[-1] != ' ' ||
4630             old_rev[-1]  != ' ' ||
4631             new_rev[-1]  != ' ' ||
4632             status[-1]   != ' ')
4633                 return FALSE;
4635         file->status = *status;
4637         string_copy_rev(file->old.rev, old_rev);
4638         string_copy_rev(file->new.rev, new_rev);
4640         file->old.mode = strtoul(old_mode, NULL, 8);
4641         file->new.mode = strtoul(new_mode, NULL, 8);
4643         file->old.name[0] = file->new.name[0] = 0;
4645         return TRUE;
4648 static bool
4649 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4651         struct status *unmerged = NULL;
4652         char *buf;
4653         struct io io = {};
4655         if (!run_io(&io, argv, NULL, IO_RD))
4656                 return FALSE;
4658         add_line_data(view, NULL, type);
4660         while ((buf = io_get(&io, 0, TRUE))) {
4661                 struct status *file = unmerged;
4663                 if (!file) {
4664                         file = calloc(1, sizeof(*file));
4665                         if (!file || !add_line_data(view, file, type))
4666                                 goto error_out;
4667                 }
4669                 /* Parse diff info part. */
4670                 if (status) {
4671                         file->status = status;
4672                         if (status == 'A')
4673                                 string_copy(file->old.rev, NULL_ID);
4675                 } else if (!file->status || file == unmerged) {
4676                         if (!status_get_diff(file, buf, strlen(buf)))
4677                                 goto error_out;
4679                         buf = io_get(&io, 0, TRUE);
4680                         if (!buf)
4681                                 break;
4683                         /* Collapse all modified entries that follow an
4684                          * associated unmerged entry. */
4685                         if (unmerged == file) {
4686                                 unmerged->status = 'U';
4687                                 unmerged = NULL;
4688                         } else if (file->status == 'U') {
4689                                 unmerged = file;
4690                         }
4691                 }
4693                 /* Grab the old name for rename/copy. */
4694                 if (!*file->old.name &&
4695                     (file->status == 'R' || file->status == 'C')) {
4696                         string_ncopy(file->old.name, buf, strlen(buf));
4698                         buf = io_get(&io, 0, TRUE);
4699                         if (!buf)
4700                                 break;
4701                 }
4703                 /* git-ls-files just delivers a NUL separated list of
4704                  * file names similar to the second half of the
4705                  * git-diff-* output. */
4706                 string_ncopy(file->new.name, buf, strlen(buf));
4707                 if (!*file->old.name)
4708                         string_copy(file->old.name, file->new.name);
4709                 file = NULL;
4710         }
4712         if (io_error(&io)) {
4713 error_out:
4714                 done_io(&io);
4715                 return FALSE;
4716         }
4718         if (!view->line[view->lines - 1].data)
4719                 add_line_data(view, NULL, LINE_STAT_NONE);
4721         done_io(&io);
4722         return TRUE;
4725 /* Don't show unmerged entries in the staged section. */
4726 static const char *status_diff_index_argv[] = {
4727         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4728                              "--cached", "-M", "HEAD", NULL
4729 };
4731 static const char *status_diff_files_argv[] = {
4732         "git", "diff-files", "-z", NULL
4733 };
4735 static const char *status_list_other_argv[] = {
4736         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4737 };
4739 static const char *status_list_no_head_argv[] = {
4740         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4741 };
4743 static const char *update_index_argv[] = {
4744         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4745 };
4747 /* Restore the previous line number to stay in the context or select a
4748  * line with something that can be updated. */
4749 static void
4750 status_restore(struct view *view)
4752         if (view->p_lineno >= view->lines)
4753                 view->p_lineno = view->lines - 1;
4754         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4755                 view->p_lineno++;
4756         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4757                 view->p_lineno--;
4759         /* If the above fails, always skip the "On branch" line. */
4760         if (view->p_lineno < view->lines)
4761                 view->lineno = view->p_lineno;
4762         else
4763                 view->lineno = 1;
4765         if (view->lineno < view->offset)
4766                 view->offset = view->lineno;
4767         else if (view->offset + view->height <= view->lineno)
4768                 view->offset = view->lineno - view->height + 1;
4770         view->p_restore = FALSE;
4773 static void
4774 status_update_onbranch(void)
4776         static const char *paths[][2] = {
4777                 { "rebase-apply/rebasing",      "Rebasing" },
4778                 { "rebase-apply/applying",      "Applying mailbox" },
4779                 { "rebase-apply/",              "Rebasing mailbox" },
4780                 { "rebase-merge/interactive",   "Interactive rebase" },
4781                 { "rebase-merge/",              "Rebase merge" },
4782                 { "MERGE_HEAD",                 "Merging" },
4783                 { "BISECT_LOG",                 "Bisecting" },
4784                 { "HEAD",                       "On branch" },
4785         };
4786         char buf[SIZEOF_STR];
4787         struct stat stat;
4788         int i;
4790         if (is_initial_commit()) {
4791                 string_copy(status_onbranch, "Initial commit");
4792                 return;
4793         }
4795         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4796                 char *head = opt_head;
4798                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4799                     lstat(buf, &stat) < 0)
4800                         continue;
4802                 if (!*opt_head) {
4803                         struct io io = {};
4805                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4806                             io_open(&io, buf) &&
4807                             io_read_buf(&io, buf, sizeof(buf))) {
4808                                 head = chomp_string(buf);
4809                                 if (!prefixcmp(head, "refs/heads/"))
4810                                         head += STRING_SIZE("refs/heads/");
4811                         }
4812                 }
4814                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4815                         string_copy(status_onbranch, opt_head);
4816                 return;
4817         }
4819         string_copy(status_onbranch, "Not currently on any branch");
4822 /* First parse staged info using git-diff-index(1), then parse unstaged
4823  * info using git-diff-files(1), and finally untracked files using
4824  * git-ls-files(1). */
4825 static bool
4826 status_open(struct view *view)
4828         reset_view(view);
4830         add_line_data(view, NULL, LINE_STAT_HEAD);
4831         status_update_onbranch();
4833         run_io_bg(update_index_argv);
4835         if (is_initial_commit()) {
4836                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4837                         return FALSE;
4838         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4839                 return FALSE;
4840         }
4842         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4843             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4844                 return FALSE;
4846         /* Restore the exact position or use the specialized restore
4847          * mode? */
4848         if (!view->p_restore)
4849                 status_restore(view);
4850         return TRUE;
4853 static bool
4854 status_draw(struct view *view, struct line *line, unsigned int lineno)
4856         struct status *status = line->data;
4857         enum line_type type;
4858         const char *text;
4860         if (!status) {
4861                 switch (line->type) {
4862                 case LINE_STAT_STAGED:
4863                         type = LINE_STAT_SECTION;
4864                         text = "Changes to be committed:";
4865                         break;
4867                 case LINE_STAT_UNSTAGED:
4868                         type = LINE_STAT_SECTION;
4869                         text = "Changed but not updated:";
4870                         break;
4872                 case LINE_STAT_UNTRACKED:
4873                         type = LINE_STAT_SECTION;
4874                         text = "Untracked files:";
4875                         break;
4877                 case LINE_STAT_NONE:
4878                         type = LINE_DEFAULT;
4879                         text = "  (no files)";
4880                         break;
4882                 case LINE_STAT_HEAD:
4883                         type = LINE_STAT_HEAD;
4884                         text = status_onbranch;
4885                         break;
4887                 default:
4888                         return FALSE;
4889                 }
4890         } else {
4891                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4893                 buf[0] = status->status;
4894                 if (draw_text(view, line->type, buf, TRUE))
4895                         return TRUE;
4896                 type = LINE_DEFAULT;
4897                 text = status->new.name;
4898         }
4900         draw_text(view, type, text, TRUE);
4901         return TRUE;
4904 static enum request
4905 status_enter(struct view *view, struct line *line)
4907         struct status *status = line->data;
4908         const char *oldpath = status ? status->old.name : NULL;
4909         /* Diffs for unmerged entries are empty when passing the new
4910          * path, so leave it empty. */
4911         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4912         const char *info;
4913         enum open_flags split;
4914         struct view *stage = VIEW(REQ_VIEW_STAGE);
4916         if (line->type == LINE_STAT_NONE ||
4917             (!status && line[1].type == LINE_STAT_NONE)) {
4918                 report("No file to diff");
4919                 return REQ_NONE;
4920         }
4922         switch (line->type) {
4923         case LINE_STAT_STAGED:
4924                 if (is_initial_commit()) {
4925                         const char *no_head_diff_argv[] = {
4926                                 "git", "diff", "--no-color", "--patch-with-stat",
4927                                         "--", "/dev/null", newpath, NULL
4928                         };
4930                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4931                                 return REQ_QUIT;
4932                 } else {
4933                         const char *index_show_argv[] = {
4934                                 "git", "diff-index", "--root", "--patch-with-stat",
4935                                         "-C", "-M", "--cached", "HEAD", "--",
4936                                         oldpath, newpath, NULL
4937                         };
4939                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4940                                 return REQ_QUIT;
4941                 }
4943                 if (status)
4944                         info = "Staged changes to %s";
4945                 else
4946                         info = "Staged changes";
4947                 break;
4949         case LINE_STAT_UNSTAGED:
4950         {
4951                 const char *files_show_argv[] = {
4952                         "git", "diff-files", "--root", "--patch-with-stat",
4953                                 "-C", "-M", "--", oldpath, newpath, NULL
4954                 };
4956                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4957                         return REQ_QUIT;
4958                 if (status)
4959                         info = "Unstaged changes to %s";
4960                 else
4961                         info = "Unstaged changes";
4962                 break;
4963         }
4964         case LINE_STAT_UNTRACKED:
4965                 if (!newpath) {
4966                         report("No file to show");
4967                         return REQ_NONE;
4968                 }
4970                 if (!suffixcmp(status->new.name, -1, "/")) {
4971                         report("Cannot display a directory");
4972                         return REQ_NONE;
4973                 }
4975                 if (!prepare_update_file(stage, newpath))
4976                         return REQ_QUIT;
4977                 info = "Untracked file %s";
4978                 break;
4980         case LINE_STAT_HEAD:
4981                 return REQ_NONE;
4983         default:
4984                 die("line type %d not handled in switch", line->type);
4985         }
4987         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4988         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4989         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4990                 if (status) {
4991                         stage_status = *status;
4992                 } else {
4993                         memset(&stage_status, 0, sizeof(stage_status));
4994                 }
4996                 stage_line_type = line->type;
4997                 stage_chunks = 0;
4998                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4999         }
5001         return REQ_NONE;
5004 static bool
5005 status_exists(struct status *status, enum line_type type)
5007         struct view *view = VIEW(REQ_VIEW_STATUS);
5008         unsigned long lineno;
5010         for (lineno = 0; lineno < view->lines; lineno++) {
5011                 struct line *line = &view->line[lineno];
5012                 struct status *pos = line->data;
5014                 if (line->type != type)
5015                         continue;
5016                 if (!pos && (!status || !status->status) && line[1].data) {
5017                         select_view_line(view, lineno);
5018                         return TRUE;
5019                 }
5020                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5021                         select_view_line(view, lineno);
5022                         return TRUE;
5023                 }
5024         }
5026         return FALSE;
5030 static bool
5031 status_update_prepare(struct io *io, enum line_type type)
5033         const char *staged_argv[] = {
5034                 "git", "update-index", "-z", "--index-info", NULL
5035         };
5036         const char *others_argv[] = {
5037                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5038         };
5040         switch (type) {
5041         case LINE_STAT_STAGED:
5042                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5044         case LINE_STAT_UNSTAGED:
5045                 return run_io(io, others_argv, opt_cdup, IO_WR);
5047         case LINE_STAT_UNTRACKED:
5048                 return run_io(io, others_argv, NULL, IO_WR);
5050         default:
5051                 die("line type %d not handled in switch", type);
5052                 return FALSE;
5053         }
5056 static bool
5057 status_update_write(struct io *io, struct status *status, enum line_type type)
5059         char buf[SIZEOF_STR];
5060         size_t bufsize = 0;
5062         switch (type) {
5063         case LINE_STAT_STAGED:
5064                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5065                                         status->old.mode,
5066                                         status->old.rev,
5067                                         status->old.name, 0))
5068                         return FALSE;
5069                 break;
5071         case LINE_STAT_UNSTAGED:
5072         case LINE_STAT_UNTRACKED:
5073                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5074                         return FALSE;
5075                 break;
5077         default:
5078                 die("line type %d not handled in switch", type);
5079         }
5081         return io_write(io, buf, bufsize);
5084 static bool
5085 status_update_file(struct status *status, enum line_type type)
5087         struct io io = {};
5088         bool result;
5090         if (!status_update_prepare(&io, type))
5091                 return FALSE;
5093         result = status_update_write(&io, status, type);
5094         done_io(&io);
5095         return result;
5098 static bool
5099 status_update_files(struct view *view, struct line *line)
5101         struct io io = {};
5102         bool result = TRUE;
5103         struct line *pos = view->line + view->lines;
5104         int files = 0;
5105         int file, done;
5107         if (!status_update_prepare(&io, line->type))
5108                 return FALSE;
5110         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5111                 files++;
5113         for (file = 0, done = 0; result && file < files; line++, file++) {
5114                 int almost_done = file * 100 / files;
5116                 if (almost_done > done) {
5117                         done = almost_done;
5118                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5119                                       file, files, done);
5120                         update_view_title(view);
5121                 }
5122                 result = status_update_write(&io, line->data, line->type);
5123         }
5125         done_io(&io);
5126         return result;
5129 static bool
5130 status_update(struct view *view)
5132         struct line *line = &view->line[view->lineno];
5134         assert(view->lines);
5136         if (!line->data) {
5137                 /* This should work even for the "On branch" line. */
5138                 if (line < view->line + view->lines && !line[1].data) {
5139                         report("Nothing to update");
5140                         return FALSE;
5141                 }
5143                 if (!status_update_files(view, line + 1)) {
5144                         report("Failed to update file status");
5145                         return FALSE;
5146                 }
5148         } else if (!status_update_file(line->data, line->type)) {
5149                 report("Failed to update file status");
5150                 return FALSE;
5151         }
5153         return TRUE;
5156 static bool
5157 status_revert(struct status *status, enum line_type type, bool has_none)
5159         if (!status || type != LINE_STAT_UNSTAGED) {
5160                 if (type == LINE_STAT_STAGED) {
5161                         report("Cannot revert changes to staged files");
5162                 } else if (type == LINE_STAT_UNTRACKED) {
5163                         report("Cannot revert changes to untracked files");
5164                 } else if (has_none) {
5165                         report("Nothing to revert");
5166                 } else {
5167                         report("Cannot revert changes to multiple files");
5168                 }
5169                 return FALSE;
5171         } else {
5172                 char mode[10] = "100644";
5173                 const char *reset_argv[] = {
5174                         "git", "update-index", "--cacheinfo", mode,
5175                                 status->old.rev, status->old.name, NULL
5176                 };
5177                 const char *checkout_argv[] = {
5178                         "git", "checkout", "--", status->old.name, NULL
5179                 };
5181                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5182                         return FALSE;
5183                 string_format(mode, "%o", status->old.mode);
5184                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5185                         run_io_fg(checkout_argv, opt_cdup);
5186         }
5189 static enum request
5190 status_request(struct view *view, enum request request, struct line *line)
5192         struct status *status = line->data;
5194         switch (request) {
5195         case REQ_STATUS_UPDATE:
5196                 if (!status_update(view))
5197                         return REQ_NONE;
5198                 break;
5200         case REQ_STATUS_REVERT:
5201                 if (!status_revert(status, line->type, status_has_none(view, line)))
5202                         return REQ_NONE;
5203                 break;
5205         case REQ_STATUS_MERGE:
5206                 if (!status || status->status != 'U') {
5207                         report("Merging only possible for files with unmerged status ('U').");
5208                         return REQ_NONE;
5209                 }
5210                 open_mergetool(status->new.name);
5211                 break;
5213         case REQ_EDIT:
5214                 if (!status)
5215                         return request;
5216                 if (status->status == 'D') {
5217                         report("File has been deleted.");
5218                         return REQ_NONE;
5219                 }
5221                 open_editor(status->status != '?', status->new.name);
5222                 break;
5224         case REQ_VIEW_BLAME:
5225                 if (status) {
5226                         string_copy(opt_file, status->new.name);
5227                         opt_ref[0] = 0;
5228                 }
5229                 return request;
5231         case REQ_ENTER:
5232                 /* After returning the status view has been split to
5233                  * show the stage view. No further reloading is
5234                  * necessary. */
5235                 status_enter(view, line);
5236                 return REQ_NONE;
5238         case REQ_REFRESH:
5239                 /* Simply reload the view. */
5240                 break;
5242         default:
5243                 return request;
5244         }
5246         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5248         return REQ_NONE;
5251 static void
5252 status_select(struct view *view, struct line *line)
5254         struct status *status = line->data;
5255         char file[SIZEOF_STR] = "all files";
5256         const char *text;
5257         const char *key;
5259         if (status && !string_format(file, "'%s'", status->new.name))
5260                 return;
5262         if (!status && line[1].type == LINE_STAT_NONE)
5263                 line++;
5265         switch (line->type) {
5266         case LINE_STAT_STAGED:
5267                 text = "Press %s to unstage %s for commit";
5268                 break;
5270         case LINE_STAT_UNSTAGED:
5271                 text = "Press %s to stage %s for commit";
5272                 break;
5274         case LINE_STAT_UNTRACKED:
5275                 text = "Press %s to stage %s for addition";
5276                 break;
5278         case LINE_STAT_HEAD:
5279         case LINE_STAT_NONE:
5280                 text = "Nothing to update";
5281                 break;
5283         default:
5284                 die("line type %d not handled in switch", line->type);
5285         }
5287         if (status && status->status == 'U') {
5288                 text = "Press %s to resolve conflict in %s";
5289                 key = get_key(REQ_STATUS_MERGE);
5291         } else {
5292                 key = get_key(REQ_STATUS_UPDATE);
5293         }
5295         string_format(view->ref, text, key, file);
5298 static bool
5299 status_grep(struct view *view, struct line *line)
5301         struct status *status = line->data;
5302         enum { S_STATUS, S_NAME, S_END } state;
5303         char buf[2] = "?";
5304         regmatch_t pmatch;
5306         if (!status)
5307                 return FALSE;
5309         for (state = S_STATUS; state < S_END; state++) {
5310                 const char *text;
5312                 switch (state) {
5313                 case S_NAME:    text = status->new.name;        break;
5314                 case S_STATUS:
5315                         buf[0] = status->status;
5316                         text = buf;
5317                         break;
5319                 default:
5320                         return FALSE;
5321                 }
5323                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5324                         return TRUE;
5325         }
5327         return FALSE;
5330 static struct view_ops status_ops = {
5331         "file",
5332         NULL,
5333         status_open,
5334         NULL,
5335         status_draw,
5336         status_request,
5337         status_grep,
5338         status_select,
5339 };
5342 static bool
5343 stage_diff_write(struct io *io, struct line *line, struct line *end)
5345         while (line < end) {
5346                 if (!io_write(io, line->data, strlen(line->data)) ||
5347                     !io_write(io, "\n", 1))
5348                         return FALSE;
5349                 line++;
5350                 if (line->type == LINE_DIFF_CHUNK ||
5351                     line->type == LINE_DIFF_HEADER)
5352                         break;
5353         }
5355         return TRUE;
5358 static struct line *
5359 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5361         for (; view->line < line; line--)
5362                 if (line->type == type)
5363                         return line;
5365         return NULL;
5368 static bool
5369 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5371         const char *apply_argv[SIZEOF_ARG] = {
5372                 "git", "apply", "--whitespace=nowarn", NULL
5373         };
5374         struct line *diff_hdr;
5375         struct io io = {};
5376         int argc = 3;
5378         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5379         if (!diff_hdr)
5380                 return FALSE;
5382         if (!revert)
5383                 apply_argv[argc++] = "--cached";
5384         if (revert || stage_line_type == LINE_STAT_STAGED)
5385                 apply_argv[argc++] = "-R";
5386         apply_argv[argc++] = "-";
5387         apply_argv[argc++] = NULL;
5388         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5389                 return FALSE;
5391         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5392             !stage_diff_write(&io, chunk, view->line + view->lines))
5393                 chunk = NULL;
5395         done_io(&io);
5396         run_io_bg(update_index_argv);
5398         return chunk ? TRUE : FALSE;
5401 static bool
5402 stage_update(struct view *view, struct line *line)
5404         struct line *chunk = NULL;
5406         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5407                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5409         if (chunk) {
5410                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5411                         report("Failed to apply chunk");
5412                         return FALSE;
5413                 }
5415         } else if (!stage_status.status) {
5416                 view = VIEW(REQ_VIEW_STATUS);
5418                 for (line = view->line; line < view->line + view->lines; line++)
5419                         if (line->type == stage_line_type)
5420                                 break;
5422                 if (!status_update_files(view, line + 1)) {
5423                         report("Failed to update files");
5424                         return FALSE;
5425                 }
5427         } else if (!status_update_file(&stage_status, stage_line_type)) {
5428                 report("Failed to update file");
5429                 return FALSE;
5430         }
5432         return TRUE;
5435 static bool
5436 stage_revert(struct view *view, struct line *line)
5438         struct line *chunk = NULL;
5440         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5441                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5443         if (chunk) {
5444                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5445                         return FALSE;
5447                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5448                         report("Failed to revert chunk");
5449                         return FALSE;
5450                 }
5451                 return TRUE;
5453         } else {
5454                 return status_revert(stage_status.status ? &stage_status : NULL,
5455                                      stage_line_type, FALSE);
5456         }
5460 static void
5461 stage_next(struct view *view, struct line *line)
5463         int i;
5465         if (!stage_chunks) {
5466                 static size_t alloc = 0;
5467                 int *tmp;
5469                 for (line = view->line; line < view->line + view->lines; line++) {
5470                         if (line->type != LINE_DIFF_CHUNK)
5471                                 continue;
5473                         tmp = realloc_items(stage_chunk, &alloc,
5474                                             stage_chunks, sizeof(*tmp));
5475                         if (!tmp) {
5476                                 report("Allocation failure");
5477                                 return;
5478                         }
5480                         stage_chunk = tmp;
5481                         stage_chunk[stage_chunks++] = line - view->line;
5482                 }
5483         }
5485         for (i = 0; i < stage_chunks; i++) {
5486                 if (stage_chunk[i] > view->lineno) {
5487                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5488                         report("Chunk %d of %d", i + 1, stage_chunks);
5489                         return;
5490                 }
5491         }
5493         report("No next chunk found");
5496 static enum request
5497 stage_request(struct view *view, enum request request, struct line *line)
5499         switch (request) {
5500         case REQ_STATUS_UPDATE:
5501                 if (!stage_update(view, line))
5502                         return REQ_NONE;
5503                 break;
5505         case REQ_STATUS_REVERT:
5506                 if (!stage_revert(view, line))
5507                         return REQ_NONE;
5508                 break;
5510         case REQ_STAGE_NEXT:
5511                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5512                         report("File is untracked; press %s to add",
5513                                get_key(REQ_STATUS_UPDATE));
5514                         return REQ_NONE;
5515                 }
5516                 stage_next(view, line);
5517                 return REQ_NONE;
5519         case REQ_EDIT:
5520                 if (!stage_status.new.name[0])
5521                         return request;
5522                 if (stage_status.status == 'D') {
5523                         report("File has been deleted.");
5524                         return REQ_NONE;
5525                 }
5527                 open_editor(stage_status.status != '?', stage_status.new.name);
5528                 break;
5530         case REQ_REFRESH:
5531                 /* Reload everything ... */
5532                 break;
5534         case REQ_VIEW_BLAME:
5535                 if (stage_status.new.name[0]) {
5536                         string_copy(opt_file, stage_status.new.name);
5537                         opt_ref[0] = 0;
5538                 }
5539                 return request;
5541         case REQ_ENTER:
5542                 return pager_request(view, request, line);
5544         default:
5545                 return request;
5546         }
5548         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5549         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5551         /* Check whether the staged entry still exists, and close the
5552          * stage view if it doesn't. */
5553         if (!status_exists(&stage_status, stage_line_type)) {
5554                 status_restore(VIEW(REQ_VIEW_STATUS));
5555                 return REQ_VIEW_CLOSE;
5556         }
5558         if (stage_line_type == LINE_STAT_UNTRACKED) {
5559                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5560                         report("Cannot display a directory");
5561                         return REQ_NONE;
5562                 }
5564                 if (!prepare_update_file(view, stage_status.new.name)) {
5565                         report("Failed to open file: %s", strerror(errno));
5566                         return REQ_NONE;
5567                 }
5568         }
5569         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5571         return REQ_NONE;
5574 static struct view_ops stage_ops = {
5575         "line",
5576         NULL,
5577         NULL,
5578         pager_read,
5579         pager_draw,
5580         stage_request,
5581         pager_grep,
5582         pager_select,
5583 };
5586 /*
5587  * Revision graph
5588  */
5590 struct commit {
5591         char id[SIZEOF_REV];            /* SHA1 ID. */
5592         char title[128];                /* First line of the commit message. */
5593         char author[75];                /* Author of the commit. */
5594         struct tm time;                 /* Date from the author ident. */
5595         struct ref **refs;              /* Repository references. */
5596         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5597         size_t graph_size;              /* The width of the graph array. */
5598         bool has_parents;               /* Rewritten --parents seen. */
5599 };
5601 /* Size of rev graph with no  "padding" columns */
5602 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5604 struct rev_graph {
5605         struct rev_graph *prev, *next, *parents;
5606         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5607         size_t size;
5608         struct commit *commit;
5609         size_t pos;
5610         unsigned int boundary:1;
5611 };
5613 /* Parents of the commit being visualized. */
5614 static struct rev_graph graph_parents[4];
5616 /* The current stack of revisions on the graph. */
5617 static struct rev_graph graph_stacks[4] = {
5618         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5619         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5620         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5621         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5622 };
5624 static inline bool
5625 graph_parent_is_merge(struct rev_graph *graph)
5627         return graph->parents->size > 1;
5630 static inline void
5631 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5633         struct commit *commit = graph->commit;
5635         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5636                 commit->graph[commit->graph_size++] = symbol;
5639 static void
5640 clear_rev_graph(struct rev_graph *graph)
5642         graph->boundary = 0;
5643         graph->size = graph->pos = 0;
5644         graph->commit = NULL;
5645         memset(graph->parents, 0, sizeof(*graph->parents));
5648 static void
5649 done_rev_graph(struct rev_graph *graph)
5651         if (graph_parent_is_merge(graph) &&
5652             graph->pos < graph->size - 1 &&
5653             graph->next->size == graph->size + graph->parents->size - 1) {
5654                 size_t i = graph->pos + graph->parents->size - 1;
5656                 graph->commit->graph_size = i * 2;
5657                 while (i < graph->next->size - 1) {
5658                         append_to_rev_graph(graph, ' ');
5659                         append_to_rev_graph(graph, '\\');
5660                         i++;
5661                 }
5662         }
5664         clear_rev_graph(graph);
5667 static void
5668 push_rev_graph(struct rev_graph *graph, const char *parent)
5670         int i;
5672         /* "Collapse" duplicate parents lines.
5673          *
5674          * FIXME: This needs to also update update the drawn graph but
5675          * for now it just serves as a method for pruning graph lines. */
5676         for (i = 0; i < graph->size; i++)
5677                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5678                         return;
5680         if (graph->size < SIZEOF_REVITEMS) {
5681                 string_copy_rev(graph->rev[graph->size++], parent);
5682         }
5685 static chtype
5686 get_rev_graph_symbol(struct rev_graph *graph)
5688         chtype symbol;
5690         if (graph->boundary)
5691                 symbol = REVGRAPH_BOUND;
5692         else if (graph->parents->size == 0)
5693                 symbol = REVGRAPH_INIT;
5694         else if (graph_parent_is_merge(graph))
5695                 symbol = REVGRAPH_MERGE;
5696         else if (graph->pos >= graph->size)
5697                 symbol = REVGRAPH_BRANCH;
5698         else
5699                 symbol = REVGRAPH_COMMIT;
5701         return symbol;
5704 static void
5705 draw_rev_graph(struct rev_graph *graph)
5707         struct rev_filler {
5708                 chtype separator, line;
5709         };
5710         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5711         static struct rev_filler fillers[] = {
5712                 { ' ',  '|' },
5713                 { '`',  '.' },
5714                 { '\'', ' ' },
5715                 { '/',  ' ' },
5716         };
5717         chtype symbol = get_rev_graph_symbol(graph);
5718         struct rev_filler *filler;
5719         size_t i;
5721         if (opt_line_graphics)
5722                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5724         filler = &fillers[DEFAULT];
5726         for (i = 0; i < graph->pos; i++) {
5727                 append_to_rev_graph(graph, filler->line);
5728                 if (graph_parent_is_merge(graph->prev) &&
5729                     graph->prev->pos == i)
5730                         filler = &fillers[RSHARP];
5732                 append_to_rev_graph(graph, filler->separator);
5733         }
5735         /* Place the symbol for this revision. */
5736         append_to_rev_graph(graph, symbol);
5738         if (graph->prev->size > graph->size)
5739                 filler = &fillers[RDIAG];
5740         else
5741                 filler = &fillers[DEFAULT];
5743         i++;
5745         for (; i < graph->size; i++) {
5746                 append_to_rev_graph(graph, filler->separator);
5747                 append_to_rev_graph(graph, filler->line);
5748                 if (graph_parent_is_merge(graph->prev) &&
5749                     i < graph->prev->pos + graph->parents->size)
5750                         filler = &fillers[RSHARP];
5751                 if (graph->prev->size > graph->size)
5752                         filler = &fillers[LDIAG];
5753         }
5755         if (graph->prev->size > graph->size) {
5756                 append_to_rev_graph(graph, filler->separator);
5757                 if (filler->line != ' ')
5758                         append_to_rev_graph(graph, filler->line);
5759         }
5762 /* Prepare the next rev graph */
5763 static void
5764 prepare_rev_graph(struct rev_graph *graph)
5766         size_t i;
5768         /* First, traverse all lines of revisions up to the active one. */
5769         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5770                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5771                         break;
5773                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5774         }
5776         /* Interleave the new revision parent(s). */
5777         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5778                 push_rev_graph(graph->next, graph->parents->rev[i]);
5780         /* Lastly, put any remaining revisions. */
5781         for (i = graph->pos + 1; i < graph->size; i++)
5782                 push_rev_graph(graph->next, graph->rev[i]);
5785 static void
5786 update_rev_graph(struct view *view, struct rev_graph *graph)
5788         /* If this is the finalizing update ... */
5789         if (graph->commit)
5790                 prepare_rev_graph(graph);
5792         /* Graph visualization needs a one rev look-ahead,
5793          * so the first update doesn't visualize anything. */
5794         if (!graph->prev->commit)
5795                 return;
5797         if (view->lines > 2)
5798                 view->line[view->lines - 3].dirty = 1;
5799         if (view->lines > 1)
5800                 view->line[view->lines - 2].dirty = 1;
5801         draw_rev_graph(graph->prev);
5802         done_rev_graph(graph->prev->prev);
5806 /*
5807  * Main view backend
5808  */
5810 static const char *main_argv[SIZEOF_ARG] = {
5811         "git", "log", "--no-color", "--pretty=raw", "--parents",
5812                       "--topo-order", "%(head)", NULL
5813 };
5815 static bool
5816 main_draw(struct view *view, struct line *line, unsigned int lineno)
5818         struct commit *commit = line->data;
5820         if (!*commit->author)
5821                 return FALSE;
5823         if (opt_date && draw_date(view, &commit->time))
5824                 return TRUE;
5826         if (opt_author && draw_author(view, commit->author))
5827                 return TRUE;
5829         if (opt_rev_graph && commit->graph_size &&
5830             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5831                 return TRUE;
5833         if (opt_show_refs && commit->refs) {
5834                 size_t i = 0;
5836                 do {
5837                         enum line_type type;
5839                         if (commit->refs[i]->head)
5840                                 type = LINE_MAIN_HEAD;
5841                         else if (commit->refs[i]->ltag)
5842                                 type = LINE_MAIN_LOCAL_TAG;
5843                         else if (commit->refs[i]->tag)
5844                                 type = LINE_MAIN_TAG;
5845                         else if (commit->refs[i]->tracked)
5846                                 type = LINE_MAIN_TRACKED;
5847                         else if (commit->refs[i]->remote)
5848                                 type = LINE_MAIN_REMOTE;
5849                         else
5850                                 type = LINE_MAIN_REF;
5852                         if (draw_text(view, type, "[", TRUE) ||
5853                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5854                             draw_text(view, type, "]", TRUE))
5855                                 return TRUE;
5857                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5858                                 return TRUE;
5859                 } while (commit->refs[i++]->next);
5860         }
5862         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5863         return TRUE;
5866 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5867 static bool
5868 main_read(struct view *view, char *line)
5870         static struct rev_graph *graph = graph_stacks;
5871         enum line_type type;
5872         struct commit *commit;
5874         if (!line) {
5875                 int i;
5877                 if (!view->lines && !view->parent)
5878                         die("No revisions match the given arguments.");
5879                 if (view->lines > 0) {
5880                         commit = view->line[view->lines - 1].data;
5881                         view->line[view->lines - 1].dirty = 1;
5882                         if (!*commit->author) {
5883                                 view->lines--;
5884                                 free(commit);
5885                                 graph->commit = NULL;
5886                         }
5887                 }
5888                 update_rev_graph(view, graph);
5890                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5891                         clear_rev_graph(&graph_stacks[i]);
5892                 return TRUE;
5893         }
5895         type = get_line_type(line);
5896         if (type == LINE_COMMIT) {
5897                 commit = calloc(1, sizeof(struct commit));
5898                 if (!commit)
5899                         return FALSE;
5901                 line += STRING_SIZE("commit ");
5902                 if (*line == '-') {
5903                         graph->boundary = 1;
5904                         line++;
5905                 }
5907                 string_copy_rev(commit->id, line);
5908                 commit->refs = get_refs(commit->id);
5909                 graph->commit = commit;
5910                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5912                 while ((line = strchr(line, ' '))) {
5913                         line++;
5914                         push_rev_graph(graph->parents, line);
5915                         commit->has_parents = TRUE;
5916                 }
5917                 return TRUE;
5918         }
5920         if (!view->lines)
5921                 return TRUE;
5922         commit = view->line[view->lines - 1].data;
5924         switch (type) {
5925         case LINE_PARENT:
5926                 if (commit->has_parents)
5927                         break;
5928                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5929                 break;
5931         case LINE_AUTHOR:
5932                 parse_author_line(line + STRING_SIZE("author "),
5933                                   commit->author, sizeof(commit->author),
5934                                   &commit->time);
5935                 update_rev_graph(view, graph);
5936                 graph = graph->next;
5937                 break;
5939         default:
5940                 /* Fill in the commit title if it has not already been set. */
5941                 if (commit->title[0])
5942                         break;
5944                 /* Require titles to start with a non-space character at the
5945                  * offset used by git log. */
5946                 if (strncmp(line, "    ", 4))
5947                         break;
5948                 line += 4;
5949                 /* Well, if the title starts with a whitespace character,
5950                  * try to be forgiving.  Otherwise we end up with no title. */
5951                 while (isspace(*line))
5952                         line++;
5953                 if (*line == '\0')
5954                         break;
5955                 /* FIXME: More graceful handling of titles; append "..." to
5956                  * shortened titles, etc. */
5958                 string_expand(commit->title, sizeof(commit->title), line, 1);
5959                 view->line[view->lines - 1].dirty = 1;
5960         }
5962         return TRUE;
5965 static enum request
5966 main_request(struct view *view, enum request request, struct line *line)
5968         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5970         switch (request) {
5971         case REQ_ENTER:
5972                 open_view(view, REQ_VIEW_DIFF, flags);
5973                 break;
5974         case REQ_REFRESH:
5975                 load_refs();
5976                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5977                 break;
5978         default:
5979                 return request;
5980         }
5982         return REQ_NONE;
5985 static bool
5986 grep_refs(struct ref **refs, regex_t *regex)
5988         regmatch_t pmatch;
5989         size_t i = 0;
5991         if (!refs)
5992                 return FALSE;
5993         do {
5994                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5995                         return TRUE;
5996         } while (refs[i++]->next);
5998         return FALSE;
6001 static bool
6002 main_grep(struct view *view, struct line *line)
6004         struct commit *commit = line->data;
6005         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6006         char buf[DATE_COLS + 1];
6007         regmatch_t pmatch;
6009         for (state = S_TITLE; state < S_END; state++) {
6010                 char *text;
6012                 switch (state) {
6013                 case S_TITLE:   text = commit->title;   break;
6014                 case S_AUTHOR:
6015                         if (!opt_author)
6016                                 continue;
6017                         text = commit->author;
6018                         break;
6019                 case S_DATE:
6020                         if (!opt_date)
6021                                 continue;
6022                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6023                                 continue;
6024                         text = buf;
6025                         break;
6026                 case S_REFS:
6027                         if (!opt_show_refs)
6028                                 continue;
6029                         if (grep_refs(commit->refs, view->regex) == TRUE)
6030                                 return TRUE;
6031                         continue;
6032                 default:
6033                         return FALSE;
6034                 }
6036                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6037                         return TRUE;
6038         }
6040         return FALSE;
6043 static void
6044 main_select(struct view *view, struct line *line)
6046         struct commit *commit = line->data;
6048         string_copy_rev(view->ref, commit->id);
6049         string_copy_rev(ref_commit, view->ref);
6052 static struct view_ops main_ops = {
6053         "commit",
6054         main_argv,
6055         NULL,
6056         main_read,
6057         main_draw,
6058         main_request,
6059         main_grep,
6060         main_select,
6061 };
6064 /*
6065  * Unicode / UTF-8 handling
6066  *
6067  * NOTE: Much of the following code for dealing with Unicode is derived from
6068  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6069  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6070  */
6072 static inline int
6073 unicode_width(unsigned long c)
6075         if (c >= 0x1100 &&
6076            (c <= 0x115f                         /* Hangul Jamo */
6077             || c == 0x2329
6078             || c == 0x232a
6079             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6080                                                 /* CJK ... Yi */
6081             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6082             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6083             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6084             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6085             || (c >= 0xffe0  && c <= 0xffe6)
6086             || (c >= 0x20000 && c <= 0x2fffd)
6087             || (c >= 0x30000 && c <= 0x3fffd)))
6088                 return 2;
6090         if (c == '\t')
6091                 return opt_tab_size;
6093         return 1;
6096 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6097  * Illegal bytes are set one. */
6098 static const unsigned char utf8_bytes[256] = {
6099         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,
6100         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,
6101         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,
6102         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,
6103         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,
6104         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,
6105         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,
6106         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,
6107 };
6109 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6110 static inline unsigned long
6111 utf8_to_unicode(const char *string, size_t length)
6113         unsigned long unicode;
6115         switch (length) {
6116         case 1:
6117                 unicode  =   string[0];
6118                 break;
6119         case 2:
6120                 unicode  =  (string[0] & 0x1f) << 6;
6121                 unicode +=  (string[1] & 0x3f);
6122                 break;
6123         case 3:
6124                 unicode  =  (string[0] & 0x0f) << 12;
6125                 unicode += ((string[1] & 0x3f) << 6);
6126                 unicode +=  (string[2] & 0x3f);
6127                 break;
6128         case 4:
6129                 unicode  =  (string[0] & 0x0f) << 18;
6130                 unicode += ((string[1] & 0x3f) << 12);
6131                 unicode += ((string[2] & 0x3f) << 6);
6132                 unicode +=  (string[3] & 0x3f);
6133                 break;
6134         case 5:
6135                 unicode  =  (string[0] & 0x0f) << 24;
6136                 unicode += ((string[1] & 0x3f) << 18);
6137                 unicode += ((string[2] & 0x3f) << 12);
6138                 unicode += ((string[3] & 0x3f) << 6);
6139                 unicode +=  (string[4] & 0x3f);
6140                 break;
6141         case 6:
6142                 unicode  =  (string[0] & 0x01) << 30;
6143                 unicode += ((string[1] & 0x3f) << 24);
6144                 unicode += ((string[2] & 0x3f) << 18);
6145                 unicode += ((string[3] & 0x3f) << 12);
6146                 unicode += ((string[4] & 0x3f) << 6);
6147                 unicode +=  (string[5] & 0x3f);
6148                 break;
6149         default:
6150                 die("Invalid Unicode length");
6151         }
6153         /* Invalid characters could return the special 0xfffd value but NUL
6154          * should be just as good. */
6155         return unicode > 0xffff ? 0 : unicode;
6158 /* Calculates how much of string can be shown within the given maximum width
6159  * and sets trimmed parameter to non-zero value if all of string could not be
6160  * shown. If the reserve flag is TRUE, it will reserve at least one
6161  * trailing character, which can be useful when drawing a delimiter.
6162  *
6163  * Returns the number of bytes to output from string to satisfy max_width. */
6164 static size_t
6165 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6167         const char *string = *start;
6168         const char *end = strchr(string, '\0');
6169         unsigned char last_bytes = 0;
6170         size_t last_ucwidth = 0;
6172         *width = 0;
6173         *trimmed = 0;
6175         while (string < end) {
6176                 int c = *(unsigned char *) string;
6177                 unsigned char bytes = utf8_bytes[c];
6178                 size_t ucwidth;
6179                 unsigned long unicode;
6181                 if (string + bytes > end)
6182                         break;
6184                 /* Change representation to figure out whether
6185                  * it is a single- or double-width character. */
6187                 unicode = utf8_to_unicode(string, bytes);
6188                 /* FIXME: Graceful handling of invalid Unicode character. */
6189                 if (!unicode)
6190                         break;
6192                 ucwidth = unicode_width(unicode);
6193                 if (skip > 0) {
6194                         skip -= ucwidth <= skip ? ucwidth : skip;
6195                         *start += bytes;
6196                 }
6197                 *width  += ucwidth;
6198                 if (*width > max_width) {
6199                         *trimmed = 1;
6200                         *width -= ucwidth;
6201                         if (reserve && *width == max_width) {
6202                                 string -= last_bytes;
6203                                 *width -= last_ucwidth;
6204                         }
6205                         break;
6206                 }
6208                 string  += bytes;
6209                 last_bytes = ucwidth ? bytes : 0;
6210                 last_ucwidth = ucwidth;
6211         }
6213         return string - *start;
6217 /*
6218  * Status management
6219  */
6221 /* Whether or not the curses interface has been initialized. */
6222 static bool cursed = FALSE;
6224 /* Terminal hacks and workarounds. */
6225 static bool use_scroll_redrawwin;
6226 static bool use_scroll_status_wclear;
6228 /* The status window is used for polling keystrokes. */
6229 static WINDOW *status_win;
6231 /* Reading from the prompt? */
6232 static bool input_mode = FALSE;
6234 static bool status_empty = FALSE;
6236 /* Update status and title window. */
6237 static void
6238 report(const char *msg, ...)
6240         struct view *view = display[current_view];
6242         if (input_mode)
6243                 return;
6245         if (!view) {
6246                 char buf[SIZEOF_STR];
6247                 va_list args;
6249                 va_start(args, msg);
6250                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6251                         buf[sizeof(buf) - 1] = 0;
6252                         buf[sizeof(buf) - 2] = '.';
6253                         buf[sizeof(buf) - 3] = '.';
6254                         buf[sizeof(buf) - 4] = '.';
6255                 }
6256                 va_end(args);
6257                 die("%s", buf);
6258         }
6260         if (!status_empty || *msg) {
6261                 va_list args;
6263                 va_start(args, msg);
6265                 wmove(status_win, 0, 0);
6266                 if (view->has_scrolled && use_scroll_status_wclear)
6267                         wclear(status_win);
6268                 if (*msg) {
6269                         vwprintw(status_win, msg, args);
6270                         status_empty = FALSE;
6271                 } else {
6272                         status_empty = TRUE;
6273                 }
6274                 wclrtoeol(status_win);
6275                 wnoutrefresh(status_win);
6277                 va_end(args);
6278         }
6280         update_view_title(view);
6283 /* Controls when nodelay should be in effect when polling user input. */
6284 static void
6285 set_nonblocking_input(bool loading)
6287         static unsigned int loading_views;
6289         if ((loading == FALSE && loading_views-- == 1) ||
6290             (loading == TRUE  && loading_views++ == 0))
6291                 nodelay(status_win, loading);
6294 static void
6295 init_display(void)
6297         const char *term;
6298         int x, y;
6300         /* Initialize the curses library */
6301         if (isatty(STDIN_FILENO)) {
6302                 cursed = !!initscr();
6303                 opt_tty = stdin;
6304         } else {
6305                 /* Leave stdin and stdout alone when acting as a pager. */
6306                 opt_tty = fopen("/dev/tty", "r+");
6307                 if (!opt_tty)
6308                         die("Failed to open /dev/tty");
6309                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6310         }
6312         if (!cursed)
6313                 die("Failed to initialize curses");
6315         nonl();         /* Disable conversion and detect newlines from input. */
6316         cbreak();       /* Take input chars one at a time, no wait for \n */
6317         noecho();       /* Don't echo input */
6318         leaveok(stdscr, FALSE);
6320         if (has_colors())
6321                 init_colors();
6323         getmaxyx(stdscr, y, x);
6324         status_win = newwin(1, 0, y - 1, 0);
6325         if (!status_win)
6326                 die("Failed to create status window");
6328         /* Enable keyboard mapping */
6329         keypad(status_win, TRUE);
6330         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6332         TABSIZE = opt_tab_size;
6333         if (opt_line_graphics) {
6334                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6335         }
6337         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6338         if (term && !strcmp(term, "gnome-terminal")) {
6339                 /* In the gnome-terminal-emulator, the message from
6340                  * scrolling up one line when impossible followed by
6341                  * scrolling down one line causes corruption of the
6342                  * status line. This is fixed by calling wclear. */
6343                 use_scroll_status_wclear = TRUE;
6344                 use_scroll_redrawwin = FALSE;
6346         } else if (term && !strcmp(term, "xrvt-xpm")) {
6347                 /* No problems with full optimizations in xrvt-(unicode)
6348                  * and aterm. */
6349                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6351         } else {
6352                 /* When scrolling in (u)xterm the last line in the
6353                  * scrolling direction will update slowly. */
6354                 use_scroll_redrawwin = TRUE;
6355                 use_scroll_status_wclear = FALSE;
6356         }
6359 static int
6360 get_input(int prompt_position)
6362         struct view *view;
6363         int i, key, cursor_y, cursor_x;
6365         if (prompt_position)
6366                 input_mode = TRUE;
6368         while (TRUE) {
6369                 foreach_view (view, i) {
6370                         update_view(view);
6371                         if (view_is_displayed(view) && view->has_scrolled &&
6372                             use_scroll_redrawwin)
6373                                 redrawwin(view->win);
6374                         view->has_scrolled = FALSE;
6375                 }
6377                 /* Update the cursor position. */
6378                 if (prompt_position) {
6379                         getbegyx(status_win, cursor_y, cursor_x);
6380                         cursor_x = prompt_position;
6381                 } else {
6382                         view = display[current_view];
6383                         getbegyx(view->win, cursor_y, cursor_x);
6384                         cursor_x = view->width - 1;
6385                         cursor_y += view->lineno - view->offset;
6386                 }
6387                 setsyx(cursor_y, cursor_x);
6389                 /* Refresh, accept single keystroke of input */
6390                 doupdate();
6391                 key = wgetch(status_win);
6393                 /* wgetch() with nodelay() enabled returns ERR when
6394                  * there's no input. */
6395                 if (key == ERR) {
6397                 } else if (key == KEY_RESIZE) {
6398                         int height, width;
6400                         getmaxyx(stdscr, height, width);
6402                         wresize(status_win, 1, width);
6403                         mvwin(status_win, height - 1, 0);
6404                         wnoutrefresh(status_win);
6405                         resize_display();
6406                         redraw_display(TRUE);
6408                 } else {
6409                         input_mode = FALSE;
6410                         return key;
6411                 }
6412         }
6415 static char *
6416 prompt_input(const char *prompt, input_handler handler, void *data)
6418         enum input_status status = INPUT_OK;
6419         static char buf[SIZEOF_STR];
6420         size_t pos = 0;
6422         buf[pos] = 0;
6424         while (status == INPUT_OK || status == INPUT_SKIP) {
6425                 int key;
6427                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6428                 wclrtoeol(status_win);
6430                 key = get_input(pos + 1);
6431                 switch (key) {
6432                 case KEY_RETURN:
6433                 case KEY_ENTER:
6434                 case '\n':
6435                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6436                         break;
6438                 case KEY_BACKSPACE:
6439                         if (pos > 0)
6440                                 buf[--pos] = 0;
6441                         else
6442                                 status = INPUT_CANCEL;
6443                         break;
6445                 case KEY_ESC:
6446                         status = INPUT_CANCEL;
6447                         break;
6449                 default:
6450                         if (pos >= sizeof(buf)) {
6451                                 report("Input string too long");
6452                                 return NULL;
6453                         }
6455                         status = handler(data, buf, key);
6456                         if (status == INPUT_OK)
6457                                 buf[pos++] = (char) key;
6458                 }
6459         }
6461         /* Clear the status window */
6462         status_empty = FALSE;
6463         report("");
6465         if (status == INPUT_CANCEL)
6466                 return NULL;
6468         buf[pos++] = 0;
6470         return buf;
6473 static enum input_status
6474 prompt_yesno_handler(void *data, char *buf, int c)
6476         if (c == 'y' || c == 'Y')
6477                 return INPUT_STOP;
6478         if (c == 'n' || c == 'N')
6479                 return INPUT_CANCEL;
6480         return INPUT_SKIP;
6483 static bool
6484 prompt_yesno(const char *prompt)
6486         char prompt2[SIZEOF_STR];
6488         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6489                 return FALSE;
6491         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6494 static enum input_status
6495 read_prompt_handler(void *data, char *buf, int c)
6497         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6500 static char *
6501 read_prompt(const char *prompt)
6503         return prompt_input(prompt, read_prompt_handler, NULL);
6506 /*
6507  * Repository properties
6508  */
6510 static struct ref *refs = NULL;
6511 static size_t refs_alloc = 0;
6512 static size_t refs_size = 0;
6514 /* Id <-> ref store */
6515 static struct ref ***id_refs = NULL;
6516 static size_t id_refs_alloc = 0;
6517 static size_t id_refs_size = 0;
6519 static int
6520 compare_refs(const void *ref1_, const void *ref2_)
6522         const struct ref *ref1 = *(const struct ref **)ref1_;
6523         const struct ref *ref2 = *(const struct ref **)ref2_;
6525         if (ref1->tag != ref2->tag)
6526                 return ref2->tag - ref1->tag;
6527         if (ref1->ltag != ref2->ltag)
6528                 return ref2->ltag - ref2->ltag;
6529         if (ref1->head != ref2->head)
6530                 return ref2->head - ref1->head;
6531         if (ref1->tracked != ref2->tracked)
6532                 return ref2->tracked - ref1->tracked;
6533         if (ref1->remote != ref2->remote)
6534                 return ref2->remote - ref1->remote;
6535         return strcmp(ref1->name, ref2->name);
6538 static struct ref **
6539 get_refs(const char *id)
6541         struct ref ***tmp_id_refs;
6542         struct ref **ref_list = NULL;
6543         size_t ref_list_alloc = 0;
6544         size_t ref_list_size = 0;
6545         size_t i;
6547         for (i = 0; i < id_refs_size; i++)
6548                 if (!strcmp(id, id_refs[i][0]->id))
6549                         return id_refs[i];
6551         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6552                                     sizeof(*id_refs));
6553         if (!tmp_id_refs)
6554                 return NULL;
6556         id_refs = tmp_id_refs;
6558         for (i = 0; i < refs_size; i++) {
6559                 struct ref **tmp;
6561                 if (strcmp(id, refs[i].id))
6562                         continue;
6564                 tmp = realloc_items(ref_list, &ref_list_alloc,
6565                                     ref_list_size + 1, sizeof(*ref_list));
6566                 if (!tmp) {
6567                         if (ref_list)
6568                                 free(ref_list);
6569                         return NULL;
6570                 }
6572                 ref_list = tmp;
6573                 ref_list[ref_list_size] = &refs[i];
6574                 /* XXX: The properties of the commit chains ensures that we can
6575                  * safely modify the shared ref. The repo references will
6576                  * always be similar for the same id. */
6577                 ref_list[ref_list_size]->next = 1;
6579                 ref_list_size++;
6580         }
6582         if (ref_list) {
6583                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6584                 ref_list[ref_list_size - 1]->next = 0;
6585                 id_refs[id_refs_size++] = ref_list;
6586         }
6588         return ref_list;
6591 static int
6592 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6594         struct ref *ref;
6595         bool tag = FALSE;
6596         bool ltag = FALSE;
6597         bool remote = FALSE;
6598         bool tracked = FALSE;
6599         bool check_replace = FALSE;
6600         bool head = FALSE;
6602         if (!prefixcmp(name, "refs/tags/")) {
6603                 if (!suffixcmp(name, namelen, "^{}")) {
6604                         namelen -= 3;
6605                         name[namelen] = 0;
6606                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6607                                 check_replace = TRUE;
6608                 } else {
6609                         ltag = TRUE;
6610                 }
6612                 tag = TRUE;
6613                 namelen -= STRING_SIZE("refs/tags/");
6614                 name    += STRING_SIZE("refs/tags/");
6616         } else if (!prefixcmp(name, "refs/remotes/")) {
6617                 remote = TRUE;
6618                 namelen -= STRING_SIZE("refs/remotes/");
6619                 name    += STRING_SIZE("refs/remotes/");
6620                 tracked  = !strcmp(opt_remote, name);
6622         } else if (!prefixcmp(name, "refs/heads/")) {
6623                 namelen -= STRING_SIZE("refs/heads/");
6624                 name    += STRING_SIZE("refs/heads/");
6625                 head     = !strncmp(opt_head, name, namelen);
6627         } else if (!strcmp(name, "HEAD")) {
6628                 string_ncopy(opt_head_rev, id, idlen);
6629                 return OK;
6630         }
6632         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6633                 /* it's an annotated tag, replace the previous SHA1 with the
6634                  * resolved commit id; relies on the fact git-ls-remote lists
6635                  * the commit id of an annotated tag right before the commit id
6636                  * it points to. */
6637                 refs[refs_size - 1].ltag = ltag;
6638                 string_copy_rev(refs[refs_size - 1].id, id);
6640                 return OK;
6641         }
6642         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6643         if (!refs)
6644                 return ERR;
6646         ref = &refs[refs_size++];
6647         ref->name = malloc(namelen + 1);
6648         if (!ref->name)
6649                 return ERR;
6651         strncpy(ref->name, name, namelen);
6652         ref->name[namelen] = 0;
6653         ref->head = head;
6654         ref->tag = tag;
6655         ref->ltag = ltag;
6656         ref->remote = remote;
6657         ref->tracked = tracked;
6658         string_copy_rev(ref->id, id);
6660         return OK;
6663 static int
6664 load_refs(void)
6666         static const char *ls_remote_argv[SIZEOF_ARG] = {
6667                 "git", "ls-remote", opt_git_dir, NULL
6668         };
6669         static bool init = FALSE;
6671         if (!init) {
6672                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6673                 init = TRUE;
6674         }
6676         if (!*opt_git_dir)
6677                 return OK;
6679         while (refs_size > 0)
6680                 free(refs[--refs_size].name);
6681         while (id_refs_size > 0)
6682                 free(id_refs[--id_refs_size]);
6684         return run_io_load(ls_remote_argv, "\t", read_ref);
6687 static void
6688 set_remote_branch(const char *name, const char *value, size_t valuelen)
6690         if (!strcmp(name, ".remote")) {
6691                 string_ncopy(opt_remote, value, valuelen);
6693         } else if (*opt_remote && !strcmp(name, ".merge")) {
6694                 size_t from = strlen(opt_remote);
6696                 if (!prefixcmp(value, "refs/heads/"))
6697                         value += STRING_SIZE("refs/heads/");
6699                 if (!string_format_from(opt_remote, &from, "/%s", value))
6700                         opt_remote[0] = 0;
6701         }
6704 static void
6705 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6707         const char *argv[SIZEOF_ARG] = { name, "=" };
6708         int argc = 1 + (cmd == option_set_command);
6709         int error = ERR;
6711         if (!argv_from_string(argv, &argc, value))
6712                 config_msg = "Too many option arguments";
6713         else
6714                 error = cmd(argc, argv);
6716         if (error == ERR)
6717                 warn("Option 'tig.%s': %s", name, config_msg);
6720 static void
6721 set_work_tree(const char *value)
6723         char cwd[SIZEOF_STR];
6725         if (!getcwd(cwd, sizeof(cwd)))
6726                 die("Failed to get cwd path: %s", strerror(errno));
6727         if (chdir(opt_git_dir) < 0)
6728                 die("Failed to chdir(%s): %s", strerror(errno));
6729         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6730                 die("Failed to get git path: %s", strerror(errno));
6731         if (chdir(cwd) < 0)
6732                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6733         if (chdir(value) < 0)
6734                 die("Failed to chdir(%s): %s", value, strerror(errno));
6735         if (!getcwd(cwd, sizeof(cwd)))
6736                 die("Failed to get cwd path: %s", strerror(errno));
6737         if (setenv("GIT_WORK_TREE", cwd, TRUE) < 0)
6738                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6739         if (setenv("GIT_DIR", opt_git_dir, TRUE) < 0)
6740                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6741         opt_is_inside_work_tree = TRUE;
6744 static int
6745 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6747         if (!strcmp(name, "i18n.commitencoding"))
6748                 string_ncopy(opt_encoding, value, valuelen);
6750         else if (!strcmp(name, "core.editor"))
6751                 string_ncopy(opt_editor, value, valuelen);
6753         else if (!strcmp(name, "core.worktree"))
6754                 set_work_tree(value);
6756         else if (!prefixcmp(name, "tig.color."))
6757                 set_repo_config_option(name + 10, value, option_color_command);
6759         else if (!prefixcmp(name, "tig.bind."))
6760                 set_repo_config_option(name + 9, value, option_bind_command);
6762         else if (!prefixcmp(name, "tig."))
6763                 set_repo_config_option(name + 4, value, option_set_command);
6765         else if (*opt_head && !prefixcmp(name, "branch.") &&
6766                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6767                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6769         return OK;
6772 static int
6773 load_git_config(void)
6775         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6777         return run_io_load(config_list_argv, "=", read_repo_config_option);
6780 static int
6781 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6783         if (!opt_git_dir[0]) {
6784                 string_ncopy(opt_git_dir, name, namelen);
6786         } else if (opt_is_inside_work_tree == -1) {
6787                 /* This can be 3 different values depending on the
6788                  * version of git being used. If git-rev-parse does not
6789                  * understand --is-inside-work-tree it will simply echo
6790                  * the option else either "true" or "false" is printed.
6791                  * Default to true for the unknown case. */
6792                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6794         } else if (*name == '.') {
6795                 string_ncopy(opt_cdup, name, namelen);
6797         } else {
6798                 string_ncopy(opt_prefix, name, namelen);
6799         }
6801         return OK;
6804 static int
6805 load_repo_info(void)
6807         const char *head_argv[] = {
6808                 "git", "symbolic-ref", "HEAD", NULL
6809         };
6810         const char *rev_parse_argv[] = {
6811                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6812                         "--show-cdup", "--show-prefix", NULL
6813         };
6815         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6816                 chomp_string(opt_head);
6817                 if (!prefixcmp(opt_head, "refs/heads/")) {
6818                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6820                         memmove(opt_head, offset, strlen(offset) + 1);
6821                 }
6822         }
6824         return run_io_load(rev_parse_argv, "=", read_repo_info);
6828 /*
6829  * Main
6830  */
6832 static const char usage[] =
6833 "tig " TIG_VERSION " (" __DATE__ ")\n"
6834 "\n"
6835 "Usage: tig        [options] [revs] [--] [paths]\n"
6836 "   or: tig show   [options] [revs] [--] [paths]\n"
6837 "   or: tig blame  [rev] path\n"
6838 "   or: tig status\n"
6839 "   or: tig <      [git command output]\n"
6840 "\n"
6841 "Options:\n"
6842 "  -v, --version   Show version and exit\n"
6843 "  -h, --help      Show help message and exit";
6845 static void __NORETURN
6846 quit(int sig)
6848         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6849         if (cursed)
6850                 endwin();
6851         exit(0);
6854 static void __NORETURN
6855 die(const char *err, ...)
6857         va_list args;
6859         endwin();
6861         va_start(args, err);
6862         fputs("tig: ", stderr);
6863         vfprintf(stderr, err, args);
6864         fputs("\n", stderr);
6865         va_end(args);
6867         exit(1);
6870 static void
6871 warn(const char *msg, ...)
6873         va_list args;
6875         va_start(args, msg);
6876         fputs("tig warning: ", stderr);
6877         vfprintf(stderr, msg, args);
6878         fputs("\n", stderr);
6879         va_end(args);
6882 static enum request
6883 parse_options(int argc, const char *argv[])
6885         enum request request = REQ_VIEW_MAIN;
6886         const char *subcommand;
6887         bool seen_dashdash = FALSE;
6888         /* XXX: This is vulnerable to the user overriding options
6889          * required for the main view parser. */
6890         const char *custom_argv[SIZEOF_ARG] = {
6891                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6892                         "--topo-order", NULL
6893         };
6894         int i, j = 6;
6896         if (!isatty(STDIN_FILENO)) {
6897                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6898                 return REQ_VIEW_PAGER;
6899         }
6901         if (argc <= 1)
6902                 return REQ_NONE;
6904         subcommand = argv[1];
6905         if (!strcmp(subcommand, "status")) {
6906                 if (argc > 2)
6907                         warn("ignoring arguments after `%s'", subcommand);
6908                 return REQ_VIEW_STATUS;
6910         } else if (!strcmp(subcommand, "blame")) {
6911                 if (argc <= 2 || argc > 4)
6912                         die("invalid number of options to blame\n\n%s", usage);
6914                 i = 2;
6915                 if (argc == 4) {
6916                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6917                         i++;
6918                 }
6920                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6921                 return REQ_VIEW_BLAME;
6923         } else if (!strcmp(subcommand, "show")) {
6924                 request = REQ_VIEW_DIFF;
6926         } else {
6927                 subcommand = NULL;
6928         }
6930         if (subcommand) {
6931                 custom_argv[1] = subcommand;
6932                 j = 2;
6933         }
6935         for (i = 1 + !!subcommand; i < argc; i++) {
6936                 const char *opt = argv[i];
6938                 if (seen_dashdash || !strcmp(opt, "--")) {
6939                         seen_dashdash = TRUE;
6941                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6942                         printf("tig version %s\n", TIG_VERSION);
6943                         quit(0);
6945                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6946                         printf("%s\n", usage);
6947                         quit(0);
6948                 }
6950                 custom_argv[j++] = opt;
6951                 if (j >= ARRAY_SIZE(custom_argv))
6952                         die("command too long");
6953         }
6955         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6956                 die("Failed to format arguments"); 
6958         return request;
6961 int
6962 main(int argc, const char *argv[])
6964         enum request request = parse_options(argc, argv);
6965         struct view *view;
6966         size_t i;
6968         signal(SIGINT, quit);
6970         if (setlocale(LC_ALL, "")) {
6971                 char *codeset = nl_langinfo(CODESET);
6973                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6974         }
6976         if (load_repo_info() == ERR)
6977                 die("Failed to load repo info.");
6979         if (load_options() == ERR)
6980                 die("Failed to load user config.");
6982         if (load_git_config() == ERR)
6983                 die("Failed to load repo config.");
6985         /* Require a git repository unless when running in pager mode. */
6986         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6987                 die("Not a git repository");
6989         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6990                 opt_utf8 = FALSE;
6992         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6993                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6994                 if (opt_iconv == ICONV_NONE)
6995                         die("Failed to initialize character set conversion");
6996         }
6998         if (load_refs() == ERR)
6999                 die("Failed to load refs.");
7001         foreach_view (view, i)
7002                 argv_from_env(view->ops->argv, view->cmd_env);
7004         init_display();
7006         if (request != REQ_NONE)
7007                 open_view(NULL, request, OPEN_PREPARED);
7008         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7010         while (view_driver(display[current_view], request)) {
7011                 int key = get_input(0);
7013                 view = display[current_view];
7014                 request = get_keybinding(view->keymap, key);
7016                 /* Some low-level request handling. This keeps access to
7017                  * status_win restricted. */
7018                 switch (request) {
7019                 case REQ_PROMPT:
7020                 {
7021                         char *cmd = read_prompt(":");
7023                         if (cmd && isdigit(*cmd)) {
7024                                 int lineno = view->lineno + 1;
7026                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7027                                         select_view_line(view, lineno - 1);
7028                                         report("");
7029                                 } else {
7030                                         report("Unable to parse '%s' as a line number", cmd);
7031                                 }
7033                         } else if (cmd) {
7034                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7035                                 const char *argv[SIZEOF_ARG] = { "git" };
7036                                 int argc = 1;
7038                                 /* When running random commands, initially show the
7039                                  * command in the title. However, it maybe later be
7040                                  * overwritten if a commit line is selected. */
7041                                 string_ncopy(next->ref, cmd, strlen(cmd));
7043                                 if (!argv_from_string(argv, &argc, cmd)) {
7044                                         report("Too many arguments");
7045                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7046                                         report("Failed to format command");
7047                                 } else {
7048                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7049                                 }
7050                         }
7052                         request = REQ_NONE;
7053                         break;
7054                 }
7055                 case REQ_SEARCH:
7056                 case REQ_SEARCH_BACK:
7057                 {
7058                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7059                         char *search = read_prompt(prompt);
7061                         if (search)
7062                                 string_ncopy(opt_search, search, strlen(search));
7063                         else if (*opt_search)
7064                                 request = request == REQ_SEARCH ?
7065                                         REQ_FIND_NEXT :
7066                                         REQ_FIND_PREV;
7067                         else
7068                                 request = REQ_NONE;
7069                         break;
7070                 }
7071                 default:
7072                         break;
7073                 }
7074         }
7076         quit(0);
7078         return 0;