Code

Improve restoring of the view position to bound the offset
[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 size_t
192 string_expand_length(const char *line, int tabsize)
194         size_t size, pos;
196         for (size = pos = 0; line[pos]; pos++) {
197                 if (line[pos] == '\t' && tabsize > 0)
198                         size += tabsize - (size % tabsize);
199                 else
200                         size++;
201         }
202         return size;
205 static void
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
208         size_t size, pos;
210         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211                 if (src[pos] == '\t') {
212                         size_t expanded = tabsize - (size % tabsize);
214                         if (expanded + size >= dstlen - 1)
215                                 expanded = dstlen - size - 1;
216                         memcpy(dst + size, "        ", expanded);
217                         size += expanded;
218                 } else {
219                         dst[size++] = src[pos];
220                 }
221         }
223         dst[size] = 0;
226 static char *
227 chomp_string(char *name)
229         int namelen;
231         while (isspace(*name))
232                 name++;
234         namelen = strlen(name) - 1;
235         while (namelen > 0 && isspace(name[namelen]))
236                 name[namelen--] = 0;
238         return name;
241 static bool
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
244         va_list args;
245         size_t pos = bufpos ? *bufpos : 0;
247         va_start(args, fmt);
248         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
249         va_end(args);
251         if (bufpos)
252                 *bufpos = pos;
254         return pos >= bufsize ? FALSE : TRUE;
257 #define string_format(buf, fmt, args...) \
258         string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261         string_nformat(buf, sizeof(buf), from, fmt, args)
263 static int
264 string_enum_compare(const char *str1, const char *str2, int len)
266         size_t i;
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270         /* Diff-Header == DIFF_HEADER */
271         for (i = 0; i < len; i++) {
272                 if (toupper(str1[i]) == toupper(str2[i]))
273                         continue;
275                 if (string_enum_sep(str1[i]) &&
276                     string_enum_sep(str2[i]))
277                         continue;
279                 return str1[i] - str2[i];
280         }
282         return 0;
285 struct enum_map {
286         const char *name;
287         int namelen;
288         int value;
289 };
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
293 static bool
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
296         size_t namelen = strlen(name);
297         int i;
299         for (i = 0; i < map_size; i++)
300                 if (namelen == map[i].namelen &&
301                     !string_enum_compare(name, map[i].name, namelen)) {
302                         *value = map[i].value;
303                         return TRUE;
304                 }
306         return FALSE;
309 #define map_enum(attr, map, name) \
310         map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313         strncmp(str1, str2, STRING_SIZE(str2))
315 static inline int
316 suffixcmp(const char *str, int slen, const char *suffix)
318         size_t len = slen >= 0 ? slen : strlen(str);
319         size_t suffixlen = strlen(suffix);
321         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
325 static bool
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
328         int valuelen;
330         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331                 bool advance = cmd[valuelen] != 0;
333                 cmd[valuelen] = 0;
334                 argv[(*argc)++] = chomp_string(cmd);
335                 cmd = chomp_string(cmd + valuelen + advance);
336         }
338         if (*argc < SIZEOF_ARG)
339                 argv[*argc] = NULL;
340         return *argc < SIZEOF_ARG;
343 static void
344 argv_from_env(const char **argv, const char *name)
346         char *env = argv ? getenv(name) : NULL;
347         int argc = 0;
349         if (env && *env)
350                 env = strdup(env);
351         if (env && !argv_from_string(argv, &argc, env))
352                 die("Too many arguments in the `%s` environment variable", name);
356 /*
357  * Executing external commands.
358  */
360 enum io_type {
361         IO_FD,                  /* File descriptor based IO. */
362         IO_BG,                  /* Execute command in the background. */
363         IO_FG,                  /* Execute command with same std{in,out,err}. */
364         IO_RD,                  /* Read only fork+exec IO. */
365         IO_WR,                  /* Write only fork+exec IO. */
366         IO_AP,                  /* Append fork+exec output to file. */
367 };
369 struct io {
370         enum io_type type;      /* The requested type of pipe. */
371         const char *dir;        /* Directory from which to execute. */
372         pid_t pid;              /* Pipe for reading or writing. */
373         int pipe;               /* Pipe end for reading or writing. */
374         int error;              /* Error status. */
375         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
376         char *buf;              /* Read buffer. */
377         size_t bufalloc;        /* Allocated buffer size. */
378         size_t bufsize;         /* Buffer content size. */
379         char *bufpos;           /* Current buffer position. */
380         unsigned int eof:1;     /* Has end of file been reached. */
381 };
383 static void
384 reset_io(struct io *io)
386         io->pipe = -1;
387         io->pid = 0;
388         io->buf = io->bufpos = NULL;
389         io->bufalloc = io->bufsize = 0;
390         io->error = 0;
391         io->eof = 0;
394 static void
395 init_io(struct io *io, const char *dir, enum io_type type)
397         reset_io(io);
398         io->type = type;
399         io->dir = dir;
402 static bool
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404                 enum format_flags flags)
406         init_io(io, dir, IO_RD);
407         return format_argv(io->argv, argv, flags);
410 static bool
411 io_open(struct io *io, const char *name)
413         init_io(io, NULL, IO_FD);
414         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415         return io->pipe != -1;
418 static bool
419 kill_io(struct io *io)
421         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
424 static bool
425 done_io(struct io *io)
427         pid_t pid = io->pid;
429         if (io->pipe != -1)
430                 close(io->pipe);
431         free(io->buf);
432         reset_io(io);
434         while (pid > 0) {
435                 int status;
436                 pid_t waiting = waitpid(pid, &status, 0);
438                 if (waiting < 0) {
439                         if (errno == EINTR)
440                                 continue;
441                         report("waitpid failed (%s)", strerror(errno));
442                         return FALSE;
443                 }
445                 return waiting == pid &&
446                        !WIFSIGNALED(status) &&
447                        WIFEXITED(status) &&
448                        !WEXITSTATUS(status);
449         }
451         return TRUE;
454 static bool
455 start_io(struct io *io)
457         int pipefds[2] = { -1, -1 };
459         if (io->type == IO_FD)
460                 return TRUE;
462         if ((io->type == IO_RD || io->type == IO_WR) &&
463             pipe(pipefds) < 0)
464                 return FALSE;
465         else if (io->type == IO_AP)
466                 pipefds[1] = io->pipe;
468         if ((io->pid = fork())) {
469                 if (pipefds[!(io->type == IO_WR)] != -1)
470                         close(pipefds[!(io->type == IO_WR)]);
471                 if (io->pid != -1) {
472                         io->pipe = pipefds[!!(io->type == IO_WR)];
473                         return TRUE;
474                 }
476         } else {
477                 if (io->type != IO_FG) {
478                         int devnull = open("/dev/null", O_RDWR);
479                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
480                         int writefd = (io->type == IO_RD || io->type == IO_AP)
481                                                         ? pipefds[1] : devnull;
483                         dup2(readfd,  STDIN_FILENO);
484                         dup2(writefd, STDOUT_FILENO);
485                         dup2(devnull, STDERR_FILENO);
487                         close(devnull);
488                         if (pipefds[0] != -1)
489                                 close(pipefds[0]);
490                         if (pipefds[1] != -1)
491                                 close(pipefds[1]);
492                 }
494                 if (io->dir && *io->dir && chdir(io->dir) == -1)
495                         die("Failed to change directory: %s", strerror(errno));
497                 execvp(io->argv[0], (char *const*) io->argv);
498                 die("Failed to execute program: %s", strerror(errno));
499         }
501         if (pipefds[!!(io->type == IO_WR)] != -1)
502                 close(pipefds[!!(io->type == IO_WR)]);
503         return FALSE;
506 static bool
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
509         init_io(io, dir, type);
510         if (!format_argv(io->argv, argv, FORMAT_NONE))
511                 return FALSE;
512         return start_io(io);
515 static int
516 run_io_do(struct io *io)
518         return start_io(io) && done_io(io);
521 static int
522 run_io_bg(const char **argv)
524         struct io io = {};
526         init_io(&io, NULL, IO_BG);
527         if (!format_argv(io.argv, argv, FORMAT_NONE))
528                 return FALSE;
529         return run_io_do(&io);
532 static bool
533 run_io_fg(const char **argv, const char *dir)
535         struct io io = {};
537         init_io(&io, dir, IO_FG);
538         if (!format_argv(io.argv, argv, FORMAT_NONE))
539                 return FALSE;
540         return run_io_do(&io);
543 static bool
544 run_io_append(const char **argv, enum format_flags flags, int fd)
546         struct io io = {};
548         init_io(&io, NULL, IO_AP);
549         io.pipe = fd;
550         if (format_argv(io.argv, argv, flags))
551                 return run_io_do(&io);
552         close(fd);
553         return FALSE;
556 static bool
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
559         return init_io_rd(io, argv, NULL, flags) && start_io(io);
562 static bool
563 io_eof(struct io *io)
565         return io->eof;
568 static int
569 io_error(struct io *io)
571         return io->error;
574 static bool
575 io_strerror(struct io *io)
577         return strerror(io->error);
580 static bool
581 io_can_read(struct io *io)
583         struct timeval tv = { 0, 500 };
584         fd_set fds;
586         FD_ZERO(&fds);
587         FD_SET(io->pipe, &fds);
589         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
592 static ssize_t
593 io_read(struct io *io, void *buf, size_t bufsize)
595         do {
596                 ssize_t readsize = read(io->pipe, buf, bufsize);
598                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
599                         continue;
600                 else if (readsize == -1)
601                         io->error = errno;
602                 else if (readsize == 0)
603                         io->eof = 1;
604                 return readsize;
605         } while (1);
608 static char *
609 io_get(struct io *io, int c, bool can_read)
611         char *eol;
612         ssize_t readsize;
614         if (!io->buf) {
615                 io->buf = io->bufpos = malloc(BUFSIZ);
616                 if (!io->buf)
617                         return NULL;
618                 io->bufalloc = BUFSIZ;
619                 io->bufsize = 0;
620         }
622         while (TRUE) {
623                 if (io->bufsize > 0) {
624                         eol = memchr(io->bufpos, c, io->bufsize);
625                         if (eol) {
626                                 char *line = io->bufpos;
628                                 *eol = 0;
629                                 io->bufpos = eol + 1;
630                                 io->bufsize -= io->bufpos - line;
631                                 return line;
632                         }
633                 }
635                 if (io_eof(io)) {
636                         if (io->bufsize) {
637                                 io->bufpos[io->bufsize] = 0;
638                                 io->bufsize = 0;
639                                 return io->bufpos;
640                         }
641                         return NULL;
642                 }
644                 if (!can_read)
645                         return NULL;
647                 if (io->bufsize > 0 && io->bufpos > io->buf)
648                         memmove(io->buf, io->bufpos, io->bufsize);
650                 io->bufpos = io->buf;
651                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
652                 if (io_error(io))
653                         return NULL;
654                 io->bufsize += readsize;
655         }
658 static bool
659 io_write(struct io *io, const void *buf, size_t bufsize)
661         size_t written = 0;
663         while (!io_error(io) && written < bufsize) {
664                 ssize_t size;
666                 size = write(io->pipe, buf + written, bufsize - written);
667                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
668                         continue;
669                 else if (size == -1)
670                         io->error = errno;
671                 else
672                         written += size;
673         }
675         return written == bufsize;
678 static bool
679 io_read_buf(struct io *io, char buf[], size_t bufsize)
681         bool error;
683         io->buf = io->bufpos = buf;
684         io->bufalloc = bufsize;
685         error = !io_get(io, '\n', TRUE) && io_error(io);
686         io->buf = NULL;
688         return done_io(io) || error;
691 static bool
692 run_io_buf(const char **argv, char buf[], size_t bufsize)
694         struct io io = {};
696         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
699 static int
700 io_load(struct io *io, const char *separators,
701         int (*read_property)(char *, size_t, char *, size_t))
703         char *name;
704         int state = OK;
706         if (!start_io(io))
707                 return ERR;
709         while (state == OK && (name = io_get(io, '\n', TRUE))) {
710                 char *value;
711                 size_t namelen;
712                 size_t valuelen;
714                 name = chomp_string(name);
715                 namelen = strcspn(name, separators);
717                 if (name[namelen]) {
718                         name[namelen] = 0;
719                         value = chomp_string(name + namelen + 1);
720                         valuelen = strlen(value);
722                 } else {
723                         value = "";
724                         valuelen = 0;
725                 }
727                 state = read_property(name, namelen, value, valuelen);
728         }
730         if (state != ERR && io_error(io))
731                 state = ERR;
732         done_io(io);
734         return state;
737 static int
738 run_io_load(const char **argv, const char *separators,
739             int (*read_property)(char *, size_t, char *, size_t))
741         struct io io = {};
743         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
744                 ? io_load(&io, separators, read_property) : ERR;
748 /*
749  * User requests
750  */
752 #define REQ_INFO \
753         /* XXX: Keep the view request first and in sync with views[]. */ \
754         REQ_GROUP("View switching") \
755         REQ_(VIEW_MAIN,         "Show main view"), \
756         REQ_(VIEW_DIFF,         "Show diff view"), \
757         REQ_(VIEW_LOG,          "Show log view"), \
758         REQ_(VIEW_TREE,         "Show tree view"), \
759         REQ_(VIEW_BLOB,         "Show blob view"), \
760         REQ_(VIEW_BLAME,        "Show blame view"), \
761         REQ_(VIEW_HELP,         "Show help page"), \
762         REQ_(VIEW_PAGER,        "Show pager view"), \
763         REQ_(VIEW_STATUS,       "Show status view"), \
764         REQ_(VIEW_STAGE,        "Show stage view"), \
765         \
766         REQ_GROUP("View manipulation") \
767         REQ_(ENTER,             "Enter current line and scroll"), \
768         REQ_(NEXT,              "Move to next"), \
769         REQ_(PREVIOUS,          "Move to previous"), \
770         REQ_(PARENT,            "Move to parent"), \
771         REQ_(VIEW_NEXT,         "Move focus to next view"), \
772         REQ_(REFRESH,           "Reload and refresh"), \
773         REQ_(MAXIMIZE,          "Maximize the current view"), \
774         REQ_(VIEW_CLOSE,        "Close the current view"), \
775         REQ_(QUIT,              "Close all views and quit"), \
776         \
777         REQ_GROUP("View specific requests") \
778         REQ_(STATUS_UPDATE,     "Update file status"), \
779         REQ_(STATUS_REVERT,     "Revert file changes"), \
780         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
781         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
782         \
783         REQ_GROUP("Cursor navigation") \
784         REQ_(MOVE_UP,           "Move cursor one line up"), \
785         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
786         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
787         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
788         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
789         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
790         \
791         REQ_GROUP("Scrolling") \
792         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
793         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
794         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
795         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
796         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
797         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
798         \
799         REQ_GROUP("Searching") \
800         REQ_(SEARCH,            "Search the view"), \
801         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
802         REQ_(FIND_NEXT,         "Find next search match"), \
803         REQ_(FIND_PREV,         "Find previous search match"), \
804         \
805         REQ_GROUP("Option manipulation") \
806         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
807         REQ_(TOGGLE_DATE,       "Toggle date display"), \
808         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
809         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
810         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
811         \
812         REQ_GROUP("Misc") \
813         REQ_(PROMPT,            "Bring up the prompt"), \
814         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
815         REQ_(SHOW_VERSION,      "Show version information"), \
816         REQ_(STOP_LOADING,      "Stop all loading views"), \
817         REQ_(EDIT,              "Open in editor"), \
818         REQ_(NONE,              "Do nothing")
821 /* User action requests. */
822 enum request {
823 #define REQ_GROUP(help)
824 #define REQ_(req, help) REQ_##req
826         /* Offset all requests to avoid conflicts with ncurses getch values. */
827         REQ_OFFSET = KEY_MAX + 1,
828         REQ_INFO
830 #undef  REQ_GROUP
831 #undef  REQ_
832 };
834 struct request_info {
835         enum request request;
836         const char *name;
837         int namelen;
838         const char *help;
839 };
841 static struct request_info req_info[] = {
842 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
843 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
844         REQ_INFO
845 #undef  REQ_GROUP
846 #undef  REQ_
847 };
849 static enum request
850 get_request(const char *name)
852         int namelen = strlen(name);
853         int i;
855         for (i = 0; i < ARRAY_SIZE(req_info); i++)
856                 if (req_info[i].namelen == namelen &&
857                     !string_enum_compare(req_info[i].name, name, namelen))
858                         return req_info[i].request;
860         return REQ_NONE;
864 /*
865  * Options
866  */
868 /* Option and state variables. */
869 static bool opt_date                    = TRUE;
870 static bool opt_author                  = TRUE;
871 static bool opt_line_number             = FALSE;
872 static bool opt_line_graphics           = TRUE;
873 static bool opt_rev_graph               = FALSE;
874 static bool opt_show_refs               = TRUE;
875 static int opt_num_interval             = NUMBER_INTERVAL;
876 static int opt_tab_size                 = TAB_SIZE;
877 static int opt_author_cols              = AUTHOR_COLS-1;
878 static char opt_path[SIZEOF_STR]        = "";
879 static char opt_file[SIZEOF_STR]        = "";
880 static char opt_ref[SIZEOF_REF]         = "";
881 static char opt_head[SIZEOF_REF]        = "";
882 static char opt_head_rev[SIZEOF_REV]    = "";
883 static char opt_remote[SIZEOF_REF]      = "";
884 static char opt_encoding[20]            = "UTF-8";
885 static bool opt_utf8                    = TRUE;
886 static char opt_codeset[20]             = "UTF-8";
887 static iconv_t opt_iconv                = ICONV_NONE;
888 static char opt_search[SIZEOF_STR]      = "";
889 static char opt_cdup[SIZEOF_STR]        = "";
890 static char opt_prefix[SIZEOF_STR]      = "";
891 static char opt_git_dir[SIZEOF_STR]     = "";
892 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
893 static char opt_editor[SIZEOF_STR]      = "";
894 static FILE *opt_tty                    = NULL;
896 #define is_initial_commit()     (!*opt_head_rev)
897 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
900 /*
901  * Line-oriented content detection.
902  */
904 #define LINE_INFO \
905 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
906 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
907 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
908 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
909 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
910 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
911 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
912 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
913 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
919 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
920 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
921 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
922 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
923 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
924 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
926 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
927 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
928 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
929 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
930 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
931 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
932 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
933 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
934 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
935 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
936 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
937 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
938 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
939 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
940 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
941 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
942 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
943 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
944 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
945 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
946 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
947 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
948 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
949 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
950 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
951 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
952 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
953 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
954 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
955 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
956 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
957 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
958 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
959 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
961 enum line_type {
962 #define LINE(type, line, fg, bg, attr) \
963         LINE_##type
964         LINE_INFO,
965         LINE_NONE
966 #undef  LINE
967 };
969 struct line_info {
970         const char *name;       /* Option name. */
971         int namelen;            /* Size of option name. */
972         const char *line;       /* The start of line to match. */
973         int linelen;            /* Size of string to match. */
974         int fg, bg, attr;       /* Color and text attributes for the lines. */
975 };
977 static struct line_info line_info[] = {
978 #define LINE(type, line, fg, bg, attr) \
979         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
980         LINE_INFO
981 #undef  LINE
982 };
984 static enum line_type
985 get_line_type(const char *line)
987         int linelen = strlen(line);
988         enum line_type type;
990         for (type = 0; type < ARRAY_SIZE(line_info); type++)
991                 /* Case insensitive search matches Signed-off-by lines better. */
992                 if (linelen >= line_info[type].linelen &&
993                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
994                         return type;
996         return LINE_DEFAULT;
999 static inline int
1000 get_line_attr(enum line_type type)
1002         assert(type < ARRAY_SIZE(line_info));
1003         return COLOR_PAIR(type) | line_info[type].attr;
1006 static struct line_info *
1007 get_line_info(const char *name)
1009         size_t namelen = strlen(name);
1010         enum line_type type;
1012         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1013                 if (namelen == line_info[type].namelen &&
1014                     !string_enum_compare(line_info[type].name, name, namelen))
1015                         return &line_info[type];
1017         return NULL;
1020 static void
1021 init_colors(void)
1023         int default_bg = line_info[LINE_DEFAULT].bg;
1024         int default_fg = line_info[LINE_DEFAULT].fg;
1025         enum line_type type;
1027         start_color();
1029         if (assume_default_colors(default_fg, default_bg) == ERR) {
1030                 default_bg = COLOR_BLACK;
1031                 default_fg = COLOR_WHITE;
1032         }
1034         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1035                 struct line_info *info = &line_info[type];
1036                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1037                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1039                 init_pair(type, fg, bg);
1040         }
1043 struct line {
1044         enum line_type type;
1046         /* State flags */
1047         unsigned int selected:1;
1048         unsigned int dirty:1;
1049         unsigned int cleareol:1;
1051         void *data;             /* User data */
1052 };
1055 /*
1056  * Keys
1057  */
1059 struct keybinding {
1060         int alias;
1061         enum request request;
1062 };
1064 static struct keybinding default_keybindings[] = {
1065         /* View switching */
1066         { 'm',          REQ_VIEW_MAIN },
1067         { 'd',          REQ_VIEW_DIFF },
1068         { 'l',          REQ_VIEW_LOG },
1069         { 't',          REQ_VIEW_TREE },
1070         { 'f',          REQ_VIEW_BLOB },
1071         { 'B',          REQ_VIEW_BLAME },
1072         { 'p',          REQ_VIEW_PAGER },
1073         { 'h',          REQ_VIEW_HELP },
1074         { 'S',          REQ_VIEW_STATUS },
1075         { 'c',          REQ_VIEW_STAGE },
1077         /* View manipulation */
1078         { 'q',          REQ_VIEW_CLOSE },
1079         { KEY_TAB,      REQ_VIEW_NEXT },
1080         { KEY_RETURN,   REQ_ENTER },
1081         { KEY_UP,       REQ_PREVIOUS },
1082         { KEY_DOWN,     REQ_NEXT },
1083         { 'R',          REQ_REFRESH },
1084         { KEY_F(5),     REQ_REFRESH },
1085         { 'O',          REQ_MAXIMIZE },
1087         /* Cursor navigation */
1088         { 'k',          REQ_MOVE_UP },
1089         { 'j',          REQ_MOVE_DOWN },
1090         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1091         { KEY_END,      REQ_MOVE_LAST_LINE },
1092         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1093         { ' ',          REQ_MOVE_PAGE_DOWN },
1094         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1095         { 'b',          REQ_MOVE_PAGE_UP },
1096         { '-',          REQ_MOVE_PAGE_UP },
1098         /* Scrolling */
1099         { KEY_LEFT,     REQ_SCROLL_LEFT },
1100         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1101         { KEY_IC,       REQ_SCROLL_LINE_UP },
1102         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1103         { 'w',          REQ_SCROLL_PAGE_UP },
1104         { 's',          REQ_SCROLL_PAGE_DOWN },
1106         /* Searching */
1107         { '/',          REQ_SEARCH },
1108         { '?',          REQ_SEARCH_BACK },
1109         { 'n',          REQ_FIND_NEXT },
1110         { 'N',          REQ_FIND_PREV },
1112         /* Misc */
1113         { 'Q',          REQ_QUIT },
1114         { 'z',          REQ_STOP_LOADING },
1115         { 'v',          REQ_SHOW_VERSION },
1116         { 'r',          REQ_SCREEN_REDRAW },
1117         { '.',          REQ_TOGGLE_LINENO },
1118         { 'D',          REQ_TOGGLE_DATE },
1119         { 'A',          REQ_TOGGLE_AUTHOR },
1120         { 'g',          REQ_TOGGLE_REV_GRAPH },
1121         { 'F',          REQ_TOGGLE_REFS },
1122         { ':',          REQ_PROMPT },
1123         { 'u',          REQ_STATUS_UPDATE },
1124         { '!',          REQ_STATUS_REVERT },
1125         { 'M',          REQ_STATUS_MERGE },
1126         { '@',          REQ_STAGE_NEXT },
1127         { ',',          REQ_PARENT },
1128         { 'e',          REQ_EDIT },
1129 };
1131 #define KEYMAP_INFO \
1132         KEYMAP_(GENERIC), \
1133         KEYMAP_(MAIN), \
1134         KEYMAP_(DIFF), \
1135         KEYMAP_(LOG), \
1136         KEYMAP_(TREE), \
1137         KEYMAP_(BLOB), \
1138         KEYMAP_(BLAME), \
1139         KEYMAP_(PAGER), \
1140         KEYMAP_(HELP), \
1141         KEYMAP_(STATUS), \
1142         KEYMAP_(STAGE)
1144 enum keymap {
1145 #define KEYMAP_(name) KEYMAP_##name
1146         KEYMAP_INFO
1147 #undef  KEYMAP_
1148 };
1150 static struct enum_map keymap_table[] = {
1151 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1152         KEYMAP_INFO
1153 #undef  KEYMAP_
1154 };
1156 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1158 struct keybinding_table {
1159         struct keybinding *data;
1160         size_t size;
1161 };
1163 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1165 static void
1166 add_keybinding(enum keymap keymap, enum request request, int key)
1168         struct keybinding_table *table = &keybindings[keymap];
1170         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1171         if (!table->data)
1172                 die("Failed to allocate keybinding");
1173         table->data[table->size].alias = key;
1174         table->data[table->size++].request = request;
1177 /* Looks for a key binding first in the given map, then in the generic map, and
1178  * lastly in the default keybindings. */
1179 static enum request
1180 get_keybinding(enum keymap keymap, int key)
1182         size_t i;
1184         for (i = 0; i < keybindings[keymap].size; i++)
1185                 if (keybindings[keymap].data[i].alias == key)
1186                         return keybindings[keymap].data[i].request;
1188         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1189                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1190                         return keybindings[KEYMAP_GENERIC].data[i].request;
1192         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1193                 if (default_keybindings[i].alias == key)
1194                         return default_keybindings[i].request;
1196         return (enum request) key;
1200 struct key {
1201         const char *name;
1202         int value;
1203 };
1205 static struct key key_table[] = {
1206         { "Enter",      KEY_RETURN },
1207         { "Space",      ' ' },
1208         { "Backspace",  KEY_BACKSPACE },
1209         { "Tab",        KEY_TAB },
1210         { "Escape",     KEY_ESC },
1211         { "Left",       KEY_LEFT },
1212         { "Right",      KEY_RIGHT },
1213         { "Up",         KEY_UP },
1214         { "Down",       KEY_DOWN },
1215         { "Insert",     KEY_IC },
1216         { "Delete",     KEY_DC },
1217         { "Hash",       '#' },
1218         { "Home",       KEY_HOME },
1219         { "End",        KEY_END },
1220         { "PageUp",     KEY_PPAGE },
1221         { "PageDown",   KEY_NPAGE },
1222         { "F1",         KEY_F(1) },
1223         { "F2",         KEY_F(2) },
1224         { "F3",         KEY_F(3) },
1225         { "F4",         KEY_F(4) },
1226         { "F5",         KEY_F(5) },
1227         { "F6",         KEY_F(6) },
1228         { "F7",         KEY_F(7) },
1229         { "F8",         KEY_F(8) },
1230         { "F9",         KEY_F(9) },
1231         { "F10",        KEY_F(10) },
1232         { "F11",        KEY_F(11) },
1233         { "F12",        KEY_F(12) },
1234 };
1236 static int
1237 get_key_value(const char *name)
1239         int i;
1241         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1242                 if (!strcasecmp(key_table[i].name, name))
1243                         return key_table[i].value;
1245         if (strlen(name) == 1 && isprint(*name))
1246                 return (int) *name;
1248         return ERR;
1251 static const char *
1252 get_key_name(int key_value)
1254         static char key_char[] = "'X'";
1255         const char *seq = NULL;
1256         int key;
1258         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1259                 if (key_table[key].value == key_value)
1260                         seq = key_table[key].name;
1262         if (seq == NULL &&
1263             key_value < 127 &&
1264             isprint(key_value)) {
1265                 key_char[1] = (char) key_value;
1266                 seq = key_char;
1267         }
1269         return seq ? seq : "(no key)";
1272 static const char *
1273 get_key(enum request request)
1275         static char buf[BUFSIZ];
1276         size_t pos = 0;
1277         char *sep = "";
1278         int i;
1280         buf[pos] = 0;
1282         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1283                 struct keybinding *keybinding = &default_keybindings[i];
1285                 if (keybinding->request != request)
1286                         continue;
1288                 if (!string_format_from(buf, &pos, "%s%s", sep,
1289                                         get_key_name(keybinding->alias)))
1290                         return "Too many keybindings!";
1291                 sep = ", ";
1292         }
1294         return buf;
1297 struct run_request {
1298         enum keymap keymap;
1299         int key;
1300         const char *argv[SIZEOF_ARG];
1301 };
1303 static struct run_request *run_request;
1304 static size_t run_requests;
1306 static enum request
1307 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1309         struct run_request *req;
1311         if (argc >= ARRAY_SIZE(req->argv) - 1)
1312                 return REQ_NONE;
1314         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1315         if (!req)
1316                 return REQ_NONE;
1318         run_request = req;
1319         req = &run_request[run_requests];
1320         req->keymap = keymap;
1321         req->key = key;
1322         req->argv[0] = NULL;
1324         if (!format_argv(req->argv, argv, FORMAT_NONE))
1325                 return REQ_NONE;
1327         return REQ_NONE + ++run_requests;
1330 static struct run_request *
1331 get_run_request(enum request request)
1333         if (request <= REQ_NONE)
1334                 return NULL;
1335         return &run_request[request - REQ_NONE - 1];
1338 static void
1339 add_builtin_run_requests(void)
1341         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1342         const char *gc[] = { "git", "gc", NULL };
1343         struct {
1344                 enum keymap keymap;
1345                 int key;
1346                 int argc;
1347                 const char **argv;
1348         } reqs[] = {
1349                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1350                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1351         };
1352         int i;
1354         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1355                 enum request req;
1357                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1358                 if (req != REQ_NONE)
1359                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1360         }
1363 /*
1364  * User config file handling.
1365  */
1367 static int   config_lineno;
1368 static bool  config_errors;
1369 static const char *config_msg;
1371 static struct enum_map color_map[] = {
1372 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1373         COLOR_MAP(DEFAULT),
1374         COLOR_MAP(BLACK),
1375         COLOR_MAP(BLUE),
1376         COLOR_MAP(CYAN),
1377         COLOR_MAP(GREEN),
1378         COLOR_MAP(MAGENTA),
1379         COLOR_MAP(RED),
1380         COLOR_MAP(WHITE),
1381         COLOR_MAP(YELLOW),
1382 };
1384 static struct enum_map attr_map[] = {
1385 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1386         ATTR_MAP(NORMAL),
1387         ATTR_MAP(BLINK),
1388         ATTR_MAP(BOLD),
1389         ATTR_MAP(DIM),
1390         ATTR_MAP(REVERSE),
1391         ATTR_MAP(STANDOUT),
1392         ATTR_MAP(UNDERLINE),
1393 };
1395 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1397 static int
1398 parse_int(int *opt, const char *arg, int min, int max)
1400         int value = atoi(arg);
1402         if (min <= value && value <= max) {
1403                 *opt = value;
1404                 return OK;
1405         }
1407         config_msg = "Integer value out of bound";
1408         return ERR;
1411 static bool
1412 set_color(int *color, const char *name)
1414         if (map_enum(color, color_map, name))
1415                 return TRUE;
1416         if (!prefixcmp(name, "color"))
1417                 return parse_int(color, name + 5, 0, 255) == OK;
1418         return FALSE;
1421 /* Wants: object fgcolor bgcolor [attribute] */
1422 static int
1423 option_color_command(int argc, const char *argv[])
1425         struct line_info *info;
1427         if (argc != 3 && argc != 4) {
1428                 config_msg = "Wrong number of arguments given to color command";
1429                 return ERR;
1430         }
1432         info = get_line_info(argv[0]);
1433         if (!info) {
1434                 static struct enum_map obsolete[] = {
1435                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1436                         ENUM_MAP("main-date",   LINE_DATE),
1437                         ENUM_MAP("main-author", LINE_AUTHOR),
1438                 };
1439                 int index;
1441                 if (!map_enum(&index, obsolete, argv[0])) {
1442                         config_msg = "Unknown color name";
1443                         return ERR;
1444                 }
1445                 info = &line_info[index];
1446         }
1448         if (!set_color(&info->fg, argv[1]) ||
1449             !set_color(&info->bg, argv[2])) {
1450                 config_msg = "Unknown color";
1451                 return ERR;
1452         }
1454         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1455                 config_msg = "Unknown attribute";
1456                 return ERR;
1457         }
1459         return OK;
1462 static int parse_bool(bool *opt, const char *arg)
1464         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1465                 ? TRUE : FALSE;
1466         return OK;
1469 static int
1470 parse_string(char *opt, const char *arg, size_t optsize)
1472         int arglen = strlen(arg);
1474         switch (arg[0]) {
1475         case '\"':
1476         case '\'':
1477                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1478                         config_msg = "Unmatched quotation";
1479                         return ERR;
1480                 }
1481                 arg += 1; arglen -= 2;
1482         default:
1483                 string_ncopy_do(opt, optsize, arg, strlen(arg));
1484                 return OK;
1485         }
1488 /* Wants: name = value */
1489 static int
1490 option_set_command(int argc, const char *argv[])
1492         if (argc != 3) {
1493                 config_msg = "Wrong number of arguments given to set command";
1494                 return ERR;
1495         }
1497         if (strcmp(argv[1], "=")) {
1498                 config_msg = "No value assigned";
1499                 return ERR;
1500         }
1502         if (!strcmp(argv[0], "show-author"))
1503                 return parse_bool(&opt_author, argv[2]);
1505         if (!strcmp(argv[0], "show-date"))
1506                 return parse_bool(&opt_date, argv[2]);
1508         if (!strcmp(argv[0], "show-rev-graph"))
1509                 return parse_bool(&opt_rev_graph, argv[2]);
1511         if (!strcmp(argv[0], "show-refs"))
1512                 return parse_bool(&opt_show_refs, argv[2]);
1514         if (!strcmp(argv[0], "show-line-numbers"))
1515                 return parse_bool(&opt_line_number, argv[2]);
1517         if (!strcmp(argv[0], "line-graphics"))
1518                 return parse_bool(&opt_line_graphics, argv[2]);
1520         if (!strcmp(argv[0], "line-number-interval"))
1521                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1523         if (!strcmp(argv[0], "author-width"))
1524                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1526         if (!strcmp(argv[0], "tab-size"))
1527                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1529         if (!strcmp(argv[0], "commit-encoding"))
1530                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1532         config_msg = "Unknown variable name";
1533         return ERR;
1536 /* Wants: mode request key */
1537 static int
1538 option_bind_command(int argc, const char *argv[])
1540         enum request request;
1541         int keymap;
1542         int key;
1544         if (argc < 3) {
1545                 config_msg = "Wrong number of arguments given to bind command";
1546                 return ERR;
1547         }
1549         if (set_keymap(&keymap, argv[0]) == ERR) {
1550                 config_msg = "Unknown key map";
1551                 return ERR;
1552         }
1554         key = get_key_value(argv[1]);
1555         if (key == ERR) {
1556                 config_msg = "Unknown key";
1557                 return ERR;
1558         }
1560         request = get_request(argv[2]);
1561         if (request == REQ_NONE) {
1562                 static struct enum_map obsolete[] = {
1563                         ENUM_MAP("cherry-pick",         REQ_NONE),
1564                         ENUM_MAP("screen-resize",       REQ_NONE),
1565                         ENUM_MAP("tree-parent",         REQ_PARENT),
1566                 };
1567                 int alias;
1569                 if (map_enum(&alias, obsolete, argv[2])) {
1570                         if (alias != REQ_NONE)
1571                                 add_keybinding(keymap, alias, key);
1572                         config_msg = "Obsolete request name";
1573                         return ERR;
1574                 }
1575         }
1576         if (request == REQ_NONE && *argv[2]++ == '!')
1577                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1578         if (request == REQ_NONE) {
1579                 config_msg = "Unknown request name";
1580                 return ERR;
1581         }
1583         add_keybinding(keymap, request, key);
1585         return OK;
1588 static int
1589 set_option(const char *opt, char *value)
1591         const char *argv[SIZEOF_ARG];
1592         int argc = 0;
1594         if (!argv_from_string(argv, &argc, value)) {
1595                 config_msg = "Too many option arguments";
1596                 return ERR;
1597         }
1599         if (!strcmp(opt, "color"))
1600                 return option_color_command(argc, argv);
1602         if (!strcmp(opt, "set"))
1603                 return option_set_command(argc, argv);
1605         if (!strcmp(opt, "bind"))
1606                 return option_bind_command(argc, argv);
1608         config_msg = "Unknown option command";
1609         return ERR;
1612 static int
1613 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1615         int status = OK;
1617         config_lineno++;
1618         config_msg = "Internal error";
1620         /* Check for comment markers, since read_properties() will
1621          * only ensure opt and value are split at first " \t". */
1622         optlen = strcspn(opt, "#");
1623         if (optlen == 0)
1624                 return OK;
1626         if (opt[optlen] != 0) {
1627                 config_msg = "No option value";
1628                 status = ERR;
1630         }  else {
1631                 /* Look for comment endings in the value. */
1632                 size_t len = strcspn(value, "#");
1634                 if (len < valuelen) {
1635                         valuelen = len;
1636                         value[valuelen] = 0;
1637                 }
1639                 status = set_option(opt, value);
1640         }
1642         if (status == ERR) {
1643                 warn("Error on line %d, near '%.*s': %s",
1644                      config_lineno, (int) optlen, opt, config_msg);
1645                 config_errors = TRUE;
1646         }
1648         /* Always keep going if errors are encountered. */
1649         return OK;
1652 static void
1653 load_option_file(const char *path)
1655         struct io io = {};
1657         /* It's OK that the file doesn't exist. */
1658         if (!io_open(&io, path))
1659                 return;
1661         config_lineno = 0;
1662         config_errors = FALSE;
1664         if (io_load(&io, " \t", read_option) == ERR ||
1665             config_errors == TRUE)
1666                 warn("Errors while loading %s.", path);
1669 static int
1670 load_options(void)
1672         const char *home = getenv("HOME");
1673         const char *tigrc_user = getenv("TIGRC_USER");
1674         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1675         char buf[SIZEOF_STR];
1677         add_builtin_run_requests();
1679         if (!tigrc_system)
1680                 tigrc_system = SYSCONFDIR "/tigrc";
1681         load_option_file(tigrc_system);
1683         if (!tigrc_user) {
1684                 if (!home || !string_format(buf, "%s/.tigrc", home))
1685                         return ERR;
1686                 tigrc_user = buf;
1687         }
1688         load_option_file(tigrc_user);
1690         return OK;
1694 /*
1695  * The viewer
1696  */
1698 struct view;
1699 struct view_ops;
1701 /* The display array of active views and the index of the current view. */
1702 static struct view *display[2];
1703 static unsigned int current_view;
1705 #define foreach_displayed_view(view, i) \
1706         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1708 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1710 /* Current head and commit ID */
1711 static char ref_blob[SIZEOF_REF]        = "";
1712 static char ref_commit[SIZEOF_REF]      = "HEAD";
1713 static char ref_head[SIZEOF_REF]        = "HEAD";
1715 struct view {
1716         const char *name;       /* View name */
1717         const char *cmd_env;    /* Command line set via environment */
1718         const char *id;         /* Points to either of ref_{head,commit,blob} */
1720         struct view_ops *ops;   /* View operations */
1722         enum keymap keymap;     /* What keymap does this view have */
1723         bool git_dir;           /* Whether the view requires a git directory. */
1725         char ref[SIZEOF_REF];   /* Hovered commit reference */
1726         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1728         int height, width;      /* The width and height of the main window */
1729         WINDOW *win;            /* The main window */
1730         WINDOW *title;          /* The title window living below the main window */
1732         /* Navigation */
1733         unsigned long offset;   /* Offset of the window top */
1734         unsigned long yoffset;  /* Offset from the window side. */
1735         unsigned long lineno;   /* Current line number */
1736         unsigned long p_offset; /* Previous offset of the window top */
1737         unsigned long p_yoffset;/* Previous offset from the window side */
1738         unsigned long p_lineno; /* Previous current line number */
1739         bool p_restore;         /* Should the previous position be restored. */
1741         /* Searching */
1742         char grep[SIZEOF_STR];  /* Search string */
1743         regex_t *regex;         /* Pre-compiled regexp */
1745         /* If non-NULL, points to the view that opened this view. If this view
1746          * is closed tig will switch back to the parent view. */
1747         struct view *parent;
1749         /* Buffering */
1750         size_t lines;           /* Total number of lines */
1751         struct line *line;      /* Line index */
1752         size_t line_alloc;      /* Total number of allocated lines */
1753         unsigned int digits;    /* Number of digits in the lines member. */
1755         /* Drawing */
1756         struct line *curline;   /* Line currently being drawn. */
1757         enum line_type curtype; /* Attribute currently used for drawing. */
1758         unsigned long col;      /* Column when drawing. */
1759         bool has_scrolled;      /* View was scrolled. */
1760         bool can_hscroll;       /* View can be scrolled horizontally. */
1762         /* Loading */
1763         struct io io;
1764         struct io *pipe;
1765         time_t start_time;
1766         time_t update_secs;
1767 };
1769 struct view_ops {
1770         /* What type of content being displayed. Used in the title bar. */
1771         const char *type;
1772         /* Default command arguments. */
1773         const char **argv;
1774         /* Open and reads in all view content. */
1775         bool (*open)(struct view *view);
1776         /* Read one line; updates view->line. */
1777         bool (*read)(struct view *view, char *data);
1778         /* Draw one line; @lineno must be < view->height. */
1779         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1780         /* Depending on view handle a special requests. */
1781         enum request (*request)(struct view *view, enum request request, struct line *line);
1782         /* Search for regexp in a line. */
1783         bool (*grep)(struct view *view, struct line *line);
1784         /* Select line */
1785         void (*select)(struct view *view, struct line *line);
1786 };
1788 static struct view_ops blame_ops;
1789 static struct view_ops blob_ops;
1790 static struct view_ops diff_ops;
1791 static struct view_ops help_ops;
1792 static struct view_ops log_ops;
1793 static struct view_ops main_ops;
1794 static struct view_ops pager_ops;
1795 static struct view_ops stage_ops;
1796 static struct view_ops status_ops;
1797 static struct view_ops tree_ops;
1799 #define VIEW_STR(name, env, ref, ops, map, git) \
1800         { name, #env, ref, ops, map, git }
1802 #define VIEW_(id, name, ops, git, ref) \
1803         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1806 static struct view views[] = {
1807         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1808         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1809         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1810         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1811         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1812         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1813         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1814         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1815         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1816         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1817 };
1819 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1820 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1822 #define foreach_view(view, i) \
1823         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1825 #define view_is_displayed(view) \
1826         (view == display[0] || view == display[1])
1829 enum line_graphic {
1830         LINE_GRAPHIC_VLINE
1831 };
1833 static int line_graphics[] = {
1834         /* LINE_GRAPHIC_VLINE: */ '|'
1835 };
1837 static inline void
1838 set_view_attr(struct view *view, enum line_type type)
1840         if (!view->curline->selected && view->curtype != type) {
1841                 wattrset(view->win, get_line_attr(type));
1842                 wchgat(view->win, -1, 0, type, NULL);
1843                 view->curtype = type;
1844         }
1847 static int
1848 draw_chars(struct view *view, enum line_type type, const char *string,
1849            int max_len, bool use_tilde)
1851         int len = 0;
1852         int col = 0;
1853         int trimmed = FALSE;
1854         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1856         if (max_len <= 0)
1857                 return 0;
1859         if (opt_utf8) {
1860                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1861         } else {
1862                 col = len = strlen(string);
1863                 if (len > max_len) {
1864                         if (use_tilde) {
1865                                 max_len -= 1;
1866                         }
1867                         col = len = max_len;
1868                         trimmed = TRUE;
1869                 }
1870         }
1872         set_view_attr(view, type);
1873         if (len > 0)
1874                 waddnstr(view->win, string, len);
1875         if (trimmed && use_tilde) {
1876                 set_view_attr(view, LINE_DELIMITER);
1877                 waddch(view->win, '~');
1878                 col++;
1879         }
1881         if (view->col + col >= view->width + view->yoffset)
1882                 view->can_hscroll = TRUE;
1884         return col;
1887 static int
1888 draw_space(struct view *view, enum line_type type, int max, int spaces)
1890         static char space[] = "                    ";
1891         int col = 0;
1893         spaces = MIN(max, spaces);
1895         while (spaces > 0) {
1896                 int len = MIN(spaces, sizeof(space) - 1);
1898                 col += draw_chars(view, type, space, spaces, FALSE);
1899                 spaces -= len;
1900         }
1902         return col;
1905 static bool
1906 draw_lineno(struct view *view, unsigned int lineno)
1908         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1909         char number[10];
1910         int digits3 = view->digits < 3 ? 3 : view->digits;
1911         int max_number = MIN(digits3, STRING_SIZE(number));
1912         int max = view->width - view->col;
1913         int col;
1915         if (max < max_number)
1916                 max_number = max;
1918         lineno += view->offset + 1;
1919         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1920                 static char fmt[] = "%1ld";
1922                 if (view->digits <= 9)
1923                         fmt[1] = '0' + digits3;
1925                 if (!string_format(number, fmt, lineno))
1926                         number[0] = 0;
1927                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1928         } else {
1929                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1930         }
1932         if (col < max && skip <= col) {
1933                 set_view_attr(view, LINE_DEFAULT);
1934                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1935         }
1936         col++;
1938         view->col += col;
1939         if (col < max && skip <= col)
1940                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1941         view->col++;
1943         return view->width + view->yoffset <= view->col;
1946 static bool
1947 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1949         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1950         return view->width - view->col <= 0;
1953 static bool
1954 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1956         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1957         int max = view->width - view->col;
1958         int i;
1960         if (max < size)
1961                 size = max;
1963         set_view_attr(view, type);
1964         /* Using waddch() instead of waddnstr() ensures that
1965          * they'll be rendered correctly for the cursor line. */
1966         for (i = skip; i < size; i++)
1967                 waddch(view->win, graphic[i]);
1969         view->col += size;
1970         if (size < max && skip <= size)
1971                 waddch(view->win, ' ');
1972         view->col++;
1974         return view->width - view->col <= 0;
1977 static bool
1978 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1980         int max = MIN(view->width - view->col, len);
1981         int col;
1983         if (text)
1984                 col = draw_chars(view, type, text, max - 1, trim);
1985         else
1986                 col = draw_space(view, type, max - 1, max - 1);
1988         view->col += col;
1989         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1990         return view->width + view->yoffset <= view->col;
1993 static bool
1994 draw_date(struct view *view, struct tm *time)
1996         char buf[DATE_COLS];
1997         char *date;
1998         int timelen = 0;
2000         if (time)
2001                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2002         date = timelen ? buf : NULL;
2004         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2007 static bool
2008 draw_author(struct view *view, const char *author)
2010         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2012         if (!trim) {
2013                 static char initials[10];
2014                 size_t pos;
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018                 memset(initials, 0, sizeof(initials));
2019                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020                         while (is_initial_sep(*author))
2021                                 author++;
2022                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023                         while (*author && !is_initial_sep(author[1]))
2024                                 author++;
2025                 }
2027                 author = initials;
2028         }
2030         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2033 static bool
2034 draw_mode(struct view *view, mode_t mode)
2036         static const char dir_mode[]    = "drwxr-xr-x";
2037         static const char link_mode[]   = "lrwxrwxrwx";
2038         static const char exe_mode[]    = "-rwxr-xr-x";
2039         static const char file_mode[]   = "-rw-r--r--";
2040         const char *str;
2042         if (S_ISDIR(mode))
2043                 str = dir_mode;
2044         else if (S_ISLNK(mode))
2045                 str = link_mode;
2046         else if (mode & S_IXUSR)
2047                 str = exe_mode;
2048         else
2049                 str = file_mode;
2051         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2054 static bool
2055 draw_view_line(struct view *view, unsigned int lineno)
2057         struct line *line;
2058         bool selected = (view->offset + lineno == view->lineno);
2060         assert(view_is_displayed(view));
2062         if (view->offset + lineno >= view->lines)
2063                 return FALSE;
2065         line = &view->line[view->offset + lineno];
2067         wmove(view->win, lineno, 0);
2068         if (line->cleareol)
2069                 wclrtoeol(view->win);
2070         view->col = 0;
2071         view->curline = line;
2072         view->curtype = LINE_NONE;
2073         line->selected = FALSE;
2074         line->dirty = line->cleareol = 0;
2076         if (selected) {
2077                 set_view_attr(view, LINE_CURSOR);
2078                 line->selected = TRUE;
2079                 view->ops->select(view, line);
2080         }
2082         return view->ops->draw(view, line, lineno);
2085 static void
2086 redraw_view_dirty(struct view *view)
2088         bool dirty = FALSE;
2089         int lineno;
2091         for (lineno = 0; lineno < view->height; lineno++) {
2092                 if (view->offset + lineno >= view->lines)
2093                         break;
2094                 if (!view->line[view->offset + lineno].dirty)
2095                         continue;
2096                 dirty = TRUE;
2097                 if (!draw_view_line(view, lineno))
2098                         break;
2099         }
2101         if (!dirty)
2102                 return;
2103         wnoutrefresh(view->win);
2106 static void
2107 redraw_view_from(struct view *view, int lineno)
2109         assert(0 <= lineno && lineno < view->height);
2111         if (lineno == 0)
2112                 view->can_hscroll = FALSE;
2114         for (; lineno < view->height; lineno++) {
2115                 if (!draw_view_line(view, lineno))
2116                         break;
2117         }
2119         wnoutrefresh(view->win);
2122 static void
2123 redraw_view(struct view *view)
2125         werase(view->win);
2126         redraw_view_from(view, 0);
2130 static void
2131 update_view_title(struct view *view)
2133         char buf[SIZEOF_STR];
2134         char state[SIZEOF_STR];
2135         size_t bufpos = 0, statelen = 0;
2137         assert(view_is_displayed(view));
2139         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2140                 unsigned int view_lines = view->offset + view->height;
2141                 unsigned int lines = view->lines
2142                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2143                                    : 0;
2145                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2146                                    view->ops->type,
2147                                    view->lineno + 1,
2148                                    view->lines,
2149                                    lines);
2151         }
2153         if (view->pipe) {
2154                 time_t secs = time(NULL) - view->start_time;
2156                 /* Three git seconds are a long time ... */
2157                 if (secs > 2)
2158                         string_format_from(state, &statelen, " loading %lds", secs);
2159         }
2161         string_format_from(buf, &bufpos, "[%s]", view->name);
2162         if (*view->ref && bufpos < view->width) {
2163                 size_t refsize = strlen(view->ref);
2164                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2166                 if (minsize < view->width)
2167                         refsize = view->width - minsize + 7;
2168                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2169         }
2171         if (statelen && bufpos < view->width) {
2172                 string_format_from(buf, &bufpos, "%s", state);
2173         }
2175         if (view == display[current_view])
2176                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2177         else
2178                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2180         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2181         wclrtoeol(view->title);
2182         wnoutrefresh(view->title);
2185 static void
2186 resize_display(void)
2188         int offset, i;
2189         struct view *base = display[0];
2190         struct view *view = display[1] ? display[1] : display[0];
2192         /* Setup window dimensions */
2194         getmaxyx(stdscr, base->height, base->width);
2196         /* Make room for the status window. */
2197         base->height -= 1;
2199         if (view != base) {
2200                 /* Horizontal split. */
2201                 view->width   = base->width;
2202                 view->height  = SCALE_SPLIT_VIEW(base->height);
2203                 base->height -= view->height;
2205                 /* Make room for the title bar. */
2206                 view->height -= 1;
2207         }
2209         /* Make room for the title bar. */
2210         base->height -= 1;
2212         offset = 0;
2214         foreach_displayed_view (view, i) {
2215                 if (!view->win) {
2216                         view->win = newwin(view->height, 0, offset, 0);
2217                         if (!view->win)
2218                                 die("Failed to create %s view", view->name);
2220                         scrollok(view->win, FALSE);
2222                         view->title = newwin(1, 0, offset + view->height, 0);
2223                         if (!view->title)
2224                                 die("Failed to create title window");
2226                 } else {
2227                         wresize(view->win, view->height, view->width);
2228                         mvwin(view->win,   offset, 0);
2229                         mvwin(view->title, offset + view->height, 0);
2230                 }
2232                 offset += view->height + 1;
2233         }
2236 static void
2237 redraw_display(bool clear)
2239         struct view *view;
2240         int i;
2242         foreach_displayed_view (view, i) {
2243                 if (clear)
2244                         wclear(view->win);
2245                 redraw_view(view);
2246                 update_view_title(view);
2247         }
2250 static void
2251 toggle_view_option(bool *option, const char *help)
2253         *option = !*option;
2254         redraw_display(FALSE);
2255         report("%sabling %s", *option ? "En" : "Dis", help);
2258 /*
2259  * Navigation
2260  */
2262 /* Scrolling backend */
2263 static void
2264 do_scroll_view(struct view *view, int lines)
2266         bool redraw_current_line = FALSE;
2268         /* The rendering expects the new offset. */
2269         view->offset += lines;
2271         assert(0 <= view->offset && view->offset < view->lines);
2272         assert(lines);
2274         /* Move current line into the view. */
2275         if (view->lineno < view->offset) {
2276                 view->lineno = view->offset;
2277                 redraw_current_line = TRUE;
2278         } else if (view->lineno >= view->offset + view->height) {
2279                 view->lineno = view->offset + view->height - 1;
2280                 redraw_current_line = TRUE;
2281         }
2283         assert(view->offset <= view->lineno && view->lineno < view->lines);
2285         /* Redraw the whole screen if scrolling is pointless. */
2286         if (view->height < ABS(lines)) {
2287                 redraw_view(view);
2289         } else {
2290                 int line = lines > 0 ? view->height - lines : 0;
2291                 int end = line + ABS(lines);
2293                 scrollok(view->win, TRUE);
2294                 wscrl(view->win, lines);
2295                 scrollok(view->win, FALSE);
2297                 while (line < end && draw_view_line(view, line))
2298                         line++;
2300                 if (redraw_current_line)
2301                         draw_view_line(view, view->lineno - view->offset);
2302                 wnoutrefresh(view->win);
2303         }
2305         view->has_scrolled = TRUE;
2306         report("");
2309 /* Scroll frontend */
2310 static void
2311 scroll_view(struct view *view, enum request request)
2313         int lines = 1;
2315         assert(view_is_displayed(view));
2317         switch (request) {
2318         case REQ_SCROLL_LEFT:
2319                 if (view->yoffset == 0) {
2320                         report("Cannot scroll beyond the first column");
2321                         return;
2322                 }
2323                 if (view->yoffset <= SCROLL_INTERVAL)
2324                         view->yoffset = 0;
2325                 else
2326                         view->yoffset -= SCROLL_INTERVAL;
2327                 redraw_view_from(view, 0);
2328                 report("");
2329                 return;
2330         case REQ_SCROLL_RIGHT:
2331                 if (!view->can_hscroll) {
2332                         report("Cannot scroll beyond the last column");
2333                         return;
2334                 }
2335                 view->yoffset += SCROLL_INTERVAL;
2336                 redraw_view(view);
2337                 report("");
2338                 return;
2339         case REQ_SCROLL_PAGE_DOWN:
2340                 lines = view->height;
2341         case REQ_SCROLL_LINE_DOWN:
2342                 if (view->offset + lines > view->lines)
2343                         lines = view->lines - view->offset;
2345                 if (lines == 0 || view->offset + view->height >= view->lines) {
2346                         report("Cannot scroll beyond the last line");
2347                         return;
2348                 }
2349                 break;
2351         case REQ_SCROLL_PAGE_UP:
2352                 lines = view->height;
2353         case REQ_SCROLL_LINE_UP:
2354                 if (lines > view->offset)
2355                         lines = view->offset;
2357                 if (lines == 0) {
2358                         report("Cannot scroll beyond the first line");
2359                         return;
2360                 }
2362                 lines = -lines;
2363                 break;
2365         default:
2366                 die("request %d not handled in switch", request);
2367         }
2369         do_scroll_view(view, lines);
2372 /* Cursor moving */
2373 static void
2374 move_view(struct view *view, enum request request)
2376         int scroll_steps = 0;
2377         int steps;
2379         switch (request) {
2380         case REQ_MOVE_FIRST_LINE:
2381                 steps = -view->lineno;
2382                 break;
2384         case REQ_MOVE_LAST_LINE:
2385                 steps = view->lines - view->lineno - 1;
2386                 break;
2388         case REQ_MOVE_PAGE_UP:
2389                 steps = view->height > view->lineno
2390                       ? -view->lineno : -view->height;
2391                 break;
2393         case REQ_MOVE_PAGE_DOWN:
2394                 steps = view->lineno + view->height >= view->lines
2395                       ? view->lines - view->lineno - 1 : view->height;
2396                 break;
2398         case REQ_MOVE_UP:
2399                 steps = -1;
2400                 break;
2402         case REQ_MOVE_DOWN:
2403                 steps = 1;
2404                 break;
2406         default:
2407                 die("request %d not handled in switch", request);
2408         }
2410         if (steps <= 0 && view->lineno == 0) {
2411                 report("Cannot move beyond the first line");
2412                 return;
2414         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2415                 report("Cannot move beyond the last line");
2416                 return;
2417         }
2419         /* Move the current line */
2420         view->lineno += steps;
2421         assert(0 <= view->lineno && view->lineno < view->lines);
2423         /* Check whether the view needs to be scrolled */
2424         if (view->lineno < view->offset ||
2425             view->lineno >= view->offset + view->height) {
2426                 scroll_steps = steps;
2427                 if (steps < 0 && -steps > view->offset) {
2428                         scroll_steps = -view->offset;
2430                 } else if (steps > 0) {
2431                         if (view->lineno == view->lines - 1 &&
2432                             view->lines > view->height) {
2433                                 scroll_steps = view->lines - view->offset - 1;
2434                                 if (scroll_steps >= view->height)
2435                                         scroll_steps -= view->height - 1;
2436                         }
2437                 }
2438         }
2440         if (!view_is_displayed(view)) {
2441                 view->offset += scroll_steps;
2442                 assert(0 <= view->offset && view->offset < view->lines);
2443                 view->ops->select(view, &view->line[view->lineno]);
2444                 return;
2445         }
2447         /* Repaint the old "current" line if we be scrolling */
2448         if (ABS(steps) < view->height)
2449                 draw_view_line(view, view->lineno - steps - view->offset);
2451         if (scroll_steps) {
2452                 do_scroll_view(view, scroll_steps);
2453                 return;
2454         }
2456         /* Draw the current line */
2457         draw_view_line(view, view->lineno - view->offset);
2459         wnoutrefresh(view->win);
2460         report("");
2464 /*
2465  * Searching
2466  */
2468 static void search_view(struct view *view, enum request request);
2470 static void
2471 select_view_line(struct view *view, unsigned long lineno)
2473         if (lineno - view->offset >= view->height) {
2474                 view->offset = lineno;
2475                 view->lineno = lineno;
2476                 if (view_is_displayed(view))
2477                         redraw_view(view);
2479         } else {
2480                 unsigned long old_lineno = view->lineno - view->offset;
2482                 view->lineno = lineno;
2483                 if (view_is_displayed(view)) {
2484                         draw_view_line(view, old_lineno);
2485                         draw_view_line(view, view->lineno - view->offset);
2486                         wnoutrefresh(view->win);
2487                 } else {
2488                         view->ops->select(view, &view->line[view->lineno]);
2489                 }
2490         }
2493 static void
2494 find_next(struct view *view, enum request request)
2496         unsigned long lineno = view->lineno;
2497         int direction;
2499         if (!*view->grep) {
2500                 if (!*opt_search)
2501                         report("No previous search");
2502                 else
2503                         search_view(view, request);
2504                 return;
2505         }
2507         switch (request) {
2508         case REQ_SEARCH:
2509         case REQ_FIND_NEXT:
2510                 direction = 1;
2511                 break;
2513         case REQ_SEARCH_BACK:
2514         case REQ_FIND_PREV:
2515                 direction = -1;
2516                 break;
2518         default:
2519                 return;
2520         }
2522         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2523                 lineno += direction;
2525         /* Note, lineno is unsigned long so will wrap around in which case it
2526          * will become bigger than view->lines. */
2527         for (; lineno < view->lines; lineno += direction) {
2528                 if (view->ops->grep(view, &view->line[lineno])) {
2529                         select_view_line(view, lineno);
2530                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2531                         return;
2532                 }
2533         }
2535         report("No match found for '%s'", view->grep);
2538 static void
2539 search_view(struct view *view, enum request request)
2541         int regex_err;
2543         if (view->regex) {
2544                 regfree(view->regex);
2545                 *view->grep = 0;
2546         } else {
2547                 view->regex = calloc(1, sizeof(*view->regex));
2548                 if (!view->regex)
2549                         return;
2550         }
2552         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2553         if (regex_err != 0) {
2554                 char buf[SIZEOF_STR] = "unknown error";
2556                 regerror(regex_err, view->regex, buf, sizeof(buf));
2557                 report("Search failed: %s", buf);
2558                 return;
2559         }
2561         string_copy(view->grep, opt_search);
2563         find_next(view, request);
2566 /*
2567  * Incremental updating
2568  */
2570 static void
2571 reset_view(struct view *view)
2573         int i;
2575         for (i = 0; i < view->lines; i++)
2576                 free(view->line[i].data);
2577         free(view->line);
2579         view->p_offset = view->offset;
2580         view->p_yoffset = view->yoffset;
2581         view->p_lineno = view->lineno;
2583         view->line = NULL;
2584         view->offset = 0;
2585         view->yoffset = 0;
2586         view->lines  = 0;
2587         view->lineno = 0;
2588         view->line_alloc = 0;
2589         view->vid[0] = 0;
2590         view->update_secs = 0;
2593 static void
2594 free_argv(const char *argv[])
2596         int argc;
2598         for (argc = 0; argv[argc]; argc++)
2599                 free((void *) argv[argc]);
2602 static bool
2603 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2605         char buf[SIZEOF_STR];
2606         int argc;
2607         bool noreplace = flags == FORMAT_NONE;
2609         free_argv(dst_argv);
2611         for (argc = 0; src_argv[argc]; argc++) {
2612                 const char *arg = src_argv[argc];
2613                 size_t bufpos = 0;
2615                 while (arg) {
2616                         char *next = strstr(arg, "%(");
2617                         int len = next - arg;
2618                         const char *value;
2620                         if (!next || noreplace) {
2621                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2622                                         noreplace = TRUE;
2623                                 len = strlen(arg);
2624                                 value = "";
2626                         } else if (!prefixcmp(next, "%(directory)")) {
2627                                 value = opt_path;
2629                         } else if (!prefixcmp(next, "%(file)")) {
2630                                 value = opt_file;
2632                         } else if (!prefixcmp(next, "%(ref)")) {
2633                                 value = *opt_ref ? opt_ref : "HEAD";
2635                         } else if (!prefixcmp(next, "%(head)")) {
2636                                 value = ref_head;
2638                         } else if (!prefixcmp(next, "%(commit)")) {
2639                                 value = ref_commit;
2641                         } else if (!prefixcmp(next, "%(blob)")) {
2642                                 value = ref_blob;
2644                         } else {
2645                                 report("Unknown replacement: `%s`", next);
2646                                 return FALSE;
2647                         }
2649                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2650                                 return FALSE;
2652                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2653                 }
2655                 dst_argv[argc] = strdup(buf);
2656                 if (!dst_argv[argc])
2657                         break;
2658         }
2660         dst_argv[argc] = NULL;
2662         return src_argv[argc] == NULL;
2665 static bool
2666 restore_view_position(struct view *view)
2668         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2669                 return FALSE;
2671         /* Changing the view position cancels the restoring. */
2672         /* FIXME: Changing back to the first line is not detected. */
2673         if (view->offset != 0 || view->lineno != 0) {
2674                 view->p_restore = FALSE;
2675                 return FALSE;
2676         }
2678         if (view->p_lineno >= view->lines)
2679                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2681         if (view->p_offset > view->p_lineno ||
2682             view->p_offset + view->height <= view->p_lineno) {
2683                 unsigned long half = view->height / 2;
2685                 if (view->p_lineno > half)
2686                         view->p_offset = view->p_lineno - half;
2687                 else
2688                         view->p_offset = 0;
2689         }
2691         if (view_is_displayed(view) &&
2692             view->offset != view->p_offset &&
2693             view->lineno != view->p_lineno)
2694                 werase(view->win);
2696         view->offset = view->p_offset;
2697         view->yoffset = view->p_yoffset;
2698         view->lineno = view->p_lineno;
2699         view->p_restore = FALSE;
2701         return TRUE;
2704 static void
2705 end_update(struct view *view, bool force)
2707         if (!view->pipe)
2708                 return;
2709         while (!view->ops->read(view, NULL))
2710                 if (!force)
2711                         return;
2712         set_nonblocking_input(FALSE);
2713         if (force)
2714                 kill_io(view->pipe);
2715         done_io(view->pipe);
2716         view->pipe = NULL;
2719 static void
2720 setup_update(struct view *view, const char *vid)
2722         set_nonblocking_input(TRUE);
2723         reset_view(view);
2724         string_copy_rev(view->vid, vid);
2725         view->pipe = &view->io;
2726         view->start_time = time(NULL);
2729 static bool
2730 prepare_update(struct view *view, const char *argv[], const char *dir,
2731                enum format_flags flags)
2733         if (view->pipe)
2734                 end_update(view, TRUE);
2735         return init_io_rd(&view->io, argv, dir, flags);
2738 static bool
2739 prepare_update_file(struct view *view, const char *name)
2741         if (view->pipe)
2742                 end_update(view, TRUE);
2743         return io_open(&view->io, name);
2746 static bool
2747 begin_update(struct view *view, bool refresh)
2749         if (view->pipe)
2750                 end_update(view, TRUE);
2752         if (refresh) {
2753                 if (!start_io(&view->io))
2754                         return FALSE;
2756         } else {
2757                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2758                         opt_path[0] = 0;
2760                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2761                         return FALSE;
2763                 /* Put the current ref_* value to the view title ref
2764                  * member. This is needed by the blob view. Most other
2765                  * views sets it automatically after loading because the
2766                  * first line is a commit line. */
2767                 string_copy_rev(view->ref, view->id);
2768         }
2770         setup_update(view, view->id);
2772         return TRUE;
2775 #define ITEM_CHUNK_SIZE 256
2776 static void *
2777 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2779         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2780         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2782         if (mem == NULL || num_chunks != num_chunks_new) {
2783                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2784                 mem = realloc(mem, *size * item_size);
2785         }
2787         return mem;
2790 static struct line *
2791 realloc_lines(struct view *view, size_t line_size)
2793         size_t alloc = view->line_alloc;
2794         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2795                                          sizeof(*view->line));
2797         if (!tmp)
2798                 return NULL;
2800         view->line = tmp;
2801         view->line_alloc = alloc;
2802         return view->line;
2805 static bool
2806 update_view(struct view *view)
2808         char out_buffer[BUFSIZ * 2];
2809         char *line;
2810         /* Clear the view and redraw everything since the tree sorting
2811          * might have rearranged things. */
2812         bool redraw = view->lines == 0;
2813         bool can_read = TRUE;
2815         if (!view->pipe)
2816                 return TRUE;
2818         if (!io_can_read(view->pipe)) {
2819                 if (view->lines == 0) {
2820                         time_t secs = time(NULL) - view->start_time;
2822                         if (secs > 1 && secs > view->update_secs) {
2823                                 if (view->update_secs == 0)
2824                                         redraw_view(view);
2825                                 update_view_title(view);
2826                                 view->update_secs = secs;
2827                         }
2828                 }
2829                 return TRUE;
2830         }
2832         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2833                 if (opt_iconv != ICONV_NONE) {
2834                         ICONV_CONST char *inbuf = line;
2835                         size_t inlen = strlen(line) + 1;
2837                         char *outbuf = out_buffer;
2838                         size_t outlen = sizeof(out_buffer);
2840                         size_t ret;
2842                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2843                         if (ret != (size_t) -1)
2844                                 line = out_buffer;
2845                 }
2847                 if (!view->ops->read(view, line)) {
2848                         report("Allocation failure");
2849                         end_update(view, TRUE);
2850                         return FALSE;
2851                 }
2852         }
2854         {
2855                 unsigned long lines = view->lines;
2856                 int digits;
2858                 for (digits = 0; lines; digits++)
2859                         lines /= 10;
2861                 /* Keep the displayed view in sync with line number scaling. */
2862                 if (digits != view->digits) {
2863                         view->digits = digits;
2864                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2865                                 redraw = TRUE;
2866                 }
2867         }
2869         if (io_error(view->pipe)) {
2870                 report("Failed to read: %s", io_strerror(view->pipe));
2871                 end_update(view, TRUE);
2873         } else if (io_eof(view->pipe)) {
2874                 report("");
2875                 end_update(view, FALSE);
2876         }
2878         if (restore_view_position(view))
2879                 redraw = TRUE;
2881         if (!view_is_displayed(view))
2882                 return TRUE;
2884         if (redraw)
2885                 redraw_view_from(view, 0);
2886         else
2887                 redraw_view_dirty(view);
2889         /* Update the title _after_ the redraw so that if the redraw picks up a
2890          * commit reference in view->ref it'll be available here. */
2891         update_view_title(view);
2892         return TRUE;
2895 static struct line *
2896 add_line_data(struct view *view, void *data, enum line_type type)
2898         struct line *line;
2900         if (!realloc_lines(view, view->lines + 1))
2901                 return NULL;
2903         line = &view->line[view->lines++];
2904         memset(line, 0, sizeof(*line));
2905         line->type = type;
2906         line->data = data;
2907         line->dirty = 1;
2909         return line;
2912 static struct line *
2913 add_line_text(struct view *view, const char *text, enum line_type type)
2915         char *data = text ? strdup(text) : NULL;
2917         return data ? add_line_data(view, data, type) : NULL;
2920 static struct line *
2921 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2923         char buf[SIZEOF_STR];
2924         va_list args;
2926         va_start(args, fmt);
2927         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2928                 buf[0] = 0;
2929         va_end(args);
2931         return buf[0] ? add_line_text(view, buf, type) : NULL;
2934 /*
2935  * View opening
2936  */
2938 enum open_flags {
2939         OPEN_DEFAULT = 0,       /* Use default view switching. */
2940         OPEN_SPLIT = 1,         /* Split current view. */
2941         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2942         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2943         OPEN_PREPARED = 32,     /* Open already prepared command. */
2944 };
2946 static void
2947 open_view(struct view *prev, enum request request, enum open_flags flags)
2949         bool split = !!(flags & OPEN_SPLIT);
2950         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2951         bool nomaximize = !!(flags & OPEN_REFRESH);
2952         struct view *view = VIEW(request);
2953         int nviews = displayed_views();
2954         struct view *base_view = display[0];
2956         if (view == prev && nviews == 1 && !reload) {
2957                 report("Already in %s view", view->name);
2958                 return;
2959         }
2961         if (view->git_dir && !opt_git_dir[0]) {
2962                 report("The %s view is disabled in pager view", view->name);
2963                 return;
2964         }
2966         if (split) {
2967                 display[1] = view;
2968                 current_view = 1;
2969         } else if (!nomaximize) {
2970                 /* Maximize the current view. */
2971                 memset(display, 0, sizeof(display));
2972                 current_view = 0;
2973                 display[current_view] = view;
2974         }
2976         /* Resize the view when switching between split- and full-screen,
2977          * or when switching between two different full-screen views. */
2978         if (nviews != displayed_views() ||
2979             (nviews == 1 && base_view != display[0]))
2980                 resize_display();
2982         if (view->ops->open) {
2983                 if (view->pipe)
2984                         end_update(view, TRUE);
2985                 if (!view->ops->open(view)) {
2986                         report("Failed to load %s view", view->name);
2987                         return;
2988                 }
2989                 restore_view_position(view);
2991         } else if ((reload || strcmp(view->vid, view->id)) &&
2992                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2993                 report("Failed to load %s view", view->name);
2994                 return;
2995         }
2997         if (split && prev->lineno - prev->offset >= prev->height) {
2998                 /* Take the title line into account. */
2999                 int lines = prev->lineno - prev->offset - prev->height + 1;
3001                 /* Scroll the view that was split if the current line is
3002                  * outside the new limited view. */
3003                 do_scroll_view(prev, lines);
3004         }
3006         if (prev && view != prev) {
3007                 if (split) {
3008                         /* "Blur" the previous view. */
3009                         update_view_title(prev);
3010                 }
3012                 view->parent = prev;
3013         }
3015         if (view->pipe && view->lines == 0) {
3016                 /* Clear the old view and let the incremental updating refill
3017                  * the screen. */
3018                 werase(view->win);
3019                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3020                 report("");
3021         } else if (view_is_displayed(view)) {
3022                 redraw_view(view);
3023                 report("");
3024         }
3027 static void
3028 open_external_viewer(const char *argv[], const char *dir)
3030         def_prog_mode();           /* save current tty modes */
3031         endwin();                  /* restore original tty modes */
3032         run_io_fg(argv, dir);
3033         fprintf(stderr, "Press Enter to continue");
3034         getc(opt_tty);
3035         reset_prog_mode();
3036         redraw_display(TRUE);
3039 static void
3040 open_mergetool(const char *file)
3042         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3044         open_external_viewer(mergetool_argv, opt_cdup);
3047 static void
3048 open_editor(bool from_root, const char *file)
3050         const char *editor_argv[] = { "vi", file, NULL };
3051         const char *editor;
3053         editor = getenv("GIT_EDITOR");
3054         if (!editor && *opt_editor)
3055                 editor = opt_editor;
3056         if (!editor)
3057                 editor = getenv("VISUAL");
3058         if (!editor)
3059                 editor = getenv("EDITOR");
3060         if (!editor)
3061                 editor = "vi";
3063         editor_argv[0] = editor;
3064         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3067 static void
3068 open_run_request(enum request request)
3070         struct run_request *req = get_run_request(request);
3071         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3073         if (!req) {
3074                 report("Unknown run request");
3075                 return;
3076         }
3078         if (format_argv(argv, req->argv, FORMAT_ALL))
3079                 open_external_viewer(argv, NULL);
3080         free_argv(argv);
3083 /*
3084  * User request switch noodle
3085  */
3087 static int
3088 view_driver(struct view *view, enum request request)
3090         int i;
3092         if (request == REQ_NONE) {
3093                 doupdate();
3094                 return TRUE;
3095         }
3097         if (request > REQ_NONE) {
3098                 open_run_request(request);
3099                 /* FIXME: When all views can refresh always do this. */
3100                 if (view == VIEW(REQ_VIEW_STATUS) ||
3101                     view == VIEW(REQ_VIEW_MAIN) ||
3102                     view == VIEW(REQ_VIEW_LOG) ||
3103                     view == VIEW(REQ_VIEW_STAGE))
3104                         request = REQ_REFRESH;
3105                 else
3106                         return TRUE;
3107         }
3109         if (view && view->lines) {
3110                 request = view->ops->request(view, request, &view->line[view->lineno]);
3111                 if (request == REQ_NONE)
3112                         return TRUE;
3113         }
3115         switch (request) {
3116         case REQ_MOVE_UP:
3117         case REQ_MOVE_DOWN:
3118         case REQ_MOVE_PAGE_UP:
3119         case REQ_MOVE_PAGE_DOWN:
3120         case REQ_MOVE_FIRST_LINE:
3121         case REQ_MOVE_LAST_LINE:
3122                 move_view(view, request);
3123                 break;
3125         case REQ_SCROLL_LEFT:
3126         case REQ_SCROLL_RIGHT:
3127         case REQ_SCROLL_LINE_DOWN:
3128         case REQ_SCROLL_LINE_UP:
3129         case REQ_SCROLL_PAGE_DOWN:
3130         case REQ_SCROLL_PAGE_UP:
3131                 scroll_view(view, request);
3132                 break;
3134         case REQ_VIEW_BLAME:
3135                 if (!opt_file[0]) {
3136                         report("No file chosen, press %s to open tree view",
3137                                get_key(REQ_VIEW_TREE));
3138                         break;
3139                 }
3140                 open_view(view, request, OPEN_DEFAULT);
3141                 break;
3143         case REQ_VIEW_BLOB:
3144                 if (!ref_blob[0]) {
3145                         report("No file chosen, press %s to open tree view",
3146                                get_key(REQ_VIEW_TREE));
3147                         break;
3148                 }
3149                 open_view(view, request, OPEN_DEFAULT);
3150                 break;
3152         case REQ_VIEW_PAGER:
3153                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3154                         report("No pager content, press %s to run command from prompt",
3155                                get_key(REQ_PROMPT));
3156                         break;
3157                 }
3158                 open_view(view, request, OPEN_DEFAULT);
3159                 break;
3161         case REQ_VIEW_STAGE:
3162                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3163                         report("No stage content, press %s to open the status view and choose file",
3164                                get_key(REQ_VIEW_STATUS));
3165                         break;
3166                 }
3167                 open_view(view, request, OPEN_DEFAULT);
3168                 break;
3170         case REQ_VIEW_STATUS:
3171                 if (opt_is_inside_work_tree == FALSE) {
3172                         report("The status view requires a working tree");
3173                         break;
3174                 }
3175                 open_view(view, request, OPEN_DEFAULT);
3176                 break;
3178         case REQ_VIEW_MAIN:
3179         case REQ_VIEW_DIFF:
3180         case REQ_VIEW_LOG:
3181         case REQ_VIEW_TREE:
3182         case REQ_VIEW_HELP:
3183                 open_view(view, request, OPEN_DEFAULT);
3184                 break;
3186         case REQ_NEXT:
3187         case REQ_PREVIOUS:
3188                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3190                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3191                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3192                    (view == VIEW(REQ_VIEW_DIFF) &&
3193                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3194                    (view == VIEW(REQ_VIEW_STAGE) &&
3195                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3196                    (view == VIEW(REQ_VIEW_BLOB) &&
3197                      view->parent == VIEW(REQ_VIEW_TREE))) {
3198                         int line;
3200                         view = view->parent;
3201                         line = view->lineno;
3202                         move_view(view, request);
3203                         if (view_is_displayed(view))
3204                                 update_view_title(view);
3205                         if (line != view->lineno)
3206                                 view->ops->request(view, REQ_ENTER,
3207                                                    &view->line[view->lineno]);
3209                 } else {
3210                         move_view(view, request);
3211                 }
3212                 break;
3214         case REQ_VIEW_NEXT:
3215         {
3216                 int nviews = displayed_views();
3217                 int next_view = (current_view + 1) % nviews;
3219                 if (next_view == current_view) {
3220                         report("Only one view is displayed");
3221                         break;
3222                 }
3224                 current_view = next_view;
3225                 /* Blur out the title of the previous view. */
3226                 update_view_title(view);
3227                 report("");
3228                 break;
3229         }
3230         case REQ_REFRESH:
3231                 report("Refreshing is not yet supported for the %s view", view->name);
3232                 break;
3234         case REQ_MAXIMIZE:
3235                 if (displayed_views() == 2)
3236                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3237                 break;
3239         case REQ_TOGGLE_LINENO:
3240                 toggle_view_option(&opt_line_number, "line numbers");
3241                 break;
3243         case REQ_TOGGLE_DATE:
3244                 toggle_view_option(&opt_date, "date display");
3245                 break;
3247         case REQ_TOGGLE_AUTHOR:
3248                 toggle_view_option(&opt_author, "author display");
3249                 break;
3251         case REQ_TOGGLE_REV_GRAPH:
3252                 toggle_view_option(&opt_rev_graph, "revision graph display");
3253                 break;
3255         case REQ_TOGGLE_REFS:
3256                 toggle_view_option(&opt_show_refs, "reference display");
3257                 break;
3259         case REQ_SEARCH:
3260         case REQ_SEARCH_BACK:
3261                 search_view(view, request);
3262                 break;
3264         case REQ_FIND_NEXT:
3265         case REQ_FIND_PREV:
3266                 find_next(view, request);
3267                 break;
3269         case REQ_STOP_LOADING:
3270                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3271                         view = &views[i];
3272                         if (view->pipe)
3273                                 report("Stopped loading the %s view", view->name),
3274                         end_update(view, TRUE);
3275                 }
3276                 break;
3278         case REQ_SHOW_VERSION:
3279                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3280                 return TRUE;
3282         case REQ_SCREEN_REDRAW:
3283                 redraw_display(TRUE);
3284                 break;
3286         case REQ_EDIT:
3287                 report("Nothing to edit");
3288                 break;
3290         case REQ_ENTER:
3291                 report("Nothing to enter");
3292                 break;
3294         case REQ_VIEW_CLOSE:
3295                 /* XXX: Mark closed views by letting view->parent point to the
3296                  * view itself. Parents to closed view should never be
3297                  * followed. */
3298                 if (view->parent &&
3299                     view->parent->parent != view->parent) {
3300                         memset(display, 0, sizeof(display));
3301                         current_view = 0;
3302                         display[current_view] = view->parent;
3303                         view->parent = view;
3304                         resize_display();
3305                         redraw_display(FALSE);
3306                         report("");
3307                         break;
3308                 }
3309                 /* Fall-through */
3310         case REQ_QUIT:
3311                 return FALSE;
3313         default:
3314                 report("Unknown key, press 'h' for help");
3315                 return TRUE;
3316         }
3318         return TRUE;
3322 /*
3323  * View backend utilities
3324  */
3326 static void
3327 parse_timezone(time_t *time, const char *zone)
3329         long tz;
3331         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3332         tz += ('0' - zone[2]) * 60 * 60;
3333         tz += ('0' - zone[3]) * 60;
3334         tz += ('0' - zone[4]);
3336         if (zone[0] == '-')
3337                 tz = -tz;
3339         *time -= tz;
3342 /* Parse author lines where the name may be empty:
3343  *      author  <email@address.tld> 1138474660 +0100
3344  */
3345 static void
3346 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3348         char *nameend = strchr(ident, '<');
3349         char *emailend = strchr(ident, '>');
3351         if (nameend && emailend)
3352                 *nameend = *emailend = 0;
3353         ident = chomp_string(ident);
3354         if (!*ident) {
3355                 if (nameend)
3356                         ident = chomp_string(nameend + 1);
3357                 if (!*ident)
3358                         ident = "Unknown";
3359         }
3361         string_ncopy_do(author, authorsize, ident, strlen(ident));
3363         /* Parse epoch and timezone */
3364         if (emailend && emailend[1] == ' ') {
3365                 char *secs = emailend + 2;
3366                 char *zone = strchr(secs, ' ');
3367                 time_t time = (time_t) atol(secs);
3369                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3370                         parse_timezone(&time, zone + 1);
3372                 gmtime_r(&time, tm);
3373         }
3376 static enum input_status
3377 select_commit_parent_handler(void *data, char *buf, int c)
3379         size_t parents = *(size_t *) data;
3380         int parent = 0;
3382         if (!isdigit(c))
3383                 return INPUT_SKIP;
3385         if (*buf)
3386                 parent = atoi(buf) * 10;
3387         parent += c - '0';
3389         if (parent > parents)
3390                 return INPUT_SKIP;
3391         return INPUT_OK;
3394 static bool
3395 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3397         char buf[SIZEOF_STR * 4];
3398         const char *revlist_argv[] = {
3399                 "git", "rev-list", "-1", "--parents", id, NULL
3400         };
3401         int parents;
3403         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3404             !*chomp_string(buf) ||
3405             (parents = (strlen(buf) / 40) - 1) < 0) {
3406                 report("Failed to get parent information");
3407                 return FALSE;
3409         } else if (parents == 0) {
3410                 report("The selected commit has no parents");
3411                 return FALSE;
3412         }
3414         if (parents > 1) {
3415                 char prompt[SIZEOF_STR];
3416                 char *result;
3418                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3419                         return FALSE;
3420                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3421                 if (!result)
3422                         return FALSE;
3423                 parents = atoi(result);
3424         }
3426         string_copy_rev(rev, &buf[41 * parents]);
3427         return TRUE;
3430 /*
3431  * Pager backend
3432  */
3434 static bool
3435 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3437         char text[SIZEOF_STR];
3439         if (opt_line_number && draw_lineno(view, lineno))
3440                 return TRUE;
3442         string_expand(text, sizeof(text), line->data, opt_tab_size);
3443         draw_text(view, line->type, text, TRUE);
3444         return TRUE;
3447 static bool
3448 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3450         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3451         char refbuf[SIZEOF_STR];
3452         char *ref = NULL;
3454         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3455                 ref = chomp_string(refbuf);
3457         if (!ref || !*ref)
3458                 return TRUE;
3460         /* This is the only fatal call, since it can "corrupt" the buffer. */
3461         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3462                 return FALSE;
3464         return TRUE;
3467 static void
3468 add_pager_refs(struct view *view, struct line *line)
3470         char buf[SIZEOF_STR];
3471         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3472         struct ref **refs;
3473         size_t bufpos = 0, refpos = 0;
3474         const char *sep = "Refs: ";
3475         bool is_tag = FALSE;
3477         assert(line->type == LINE_COMMIT);
3479         refs = get_refs(commit_id);
3480         if (!refs) {
3481                 if (view == VIEW(REQ_VIEW_DIFF))
3482                         goto try_add_describe_ref;
3483                 return;
3484         }
3486         do {
3487                 struct ref *ref = refs[refpos];
3488                 const char *fmt = ref->tag    ? "%s[%s]" :
3489                                   ref->remote ? "%s<%s>" : "%s%s";
3491                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3492                         return;
3493                 sep = ", ";
3494                 if (ref->tag)
3495                         is_tag = TRUE;
3496         } while (refs[refpos++]->next);
3498         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3499 try_add_describe_ref:
3500                 /* Add <tag>-g<commit_id> "fake" reference. */
3501                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3502                         return;
3503         }
3505         if (bufpos == 0)
3506                 return;
3508         add_line_text(view, buf, LINE_PP_REFS);
3511 static bool
3512 pager_read(struct view *view, char *data)
3514         struct line *line;
3516         if (!data)
3517                 return TRUE;
3519         line = add_line_text(view, data, get_line_type(data));
3520         if (!line)
3521                 return FALSE;
3523         if (line->type == LINE_COMMIT &&
3524             (view == VIEW(REQ_VIEW_DIFF) ||
3525              view == VIEW(REQ_VIEW_LOG)))
3526                 add_pager_refs(view, line);
3528         return TRUE;
3531 static enum request
3532 pager_request(struct view *view, enum request request, struct line *line)
3534         int split = 0;
3536         if (request != REQ_ENTER)
3537                 return request;
3539         if (line->type == LINE_COMMIT &&
3540            (view == VIEW(REQ_VIEW_LOG) ||
3541             view == VIEW(REQ_VIEW_PAGER))) {
3542                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3543                 split = 1;
3544         }
3546         /* Always scroll the view even if it was split. That way
3547          * you can use Enter to scroll through the log view and
3548          * split open each commit diff. */
3549         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3551         /* FIXME: A minor workaround. Scrolling the view will call report("")
3552          * but if we are scrolling a non-current view this won't properly
3553          * update the view title. */
3554         if (split)
3555                 update_view_title(view);
3557         return REQ_NONE;
3560 static bool
3561 pager_grep(struct view *view, struct line *line)
3563         regmatch_t pmatch;
3564         char *text = line->data;
3566         if (!*text)
3567                 return FALSE;
3569         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3570                 return FALSE;
3572         return TRUE;
3575 static void
3576 pager_select(struct view *view, struct line *line)
3578         if (line->type == LINE_COMMIT) {
3579                 char *text = (char *)line->data + STRING_SIZE("commit ");
3581                 if (view != VIEW(REQ_VIEW_PAGER))
3582                         string_copy_rev(view->ref, text);
3583                 string_copy_rev(ref_commit, text);
3584         }
3587 static struct view_ops pager_ops = {
3588         "line",
3589         NULL,
3590         NULL,
3591         pager_read,
3592         pager_draw,
3593         pager_request,
3594         pager_grep,
3595         pager_select,
3596 };
3598 static const char *log_argv[SIZEOF_ARG] = {
3599         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3600 };
3602 static enum request
3603 log_request(struct view *view, enum request request, struct line *line)
3605         switch (request) {
3606         case REQ_REFRESH:
3607                 load_refs();
3608                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3609                 return REQ_NONE;
3610         default:
3611                 return pager_request(view, request, line);
3612         }
3615 static struct view_ops log_ops = {
3616         "line",
3617         log_argv,
3618         NULL,
3619         pager_read,
3620         pager_draw,
3621         log_request,
3622         pager_grep,
3623         pager_select,
3624 };
3626 static const char *diff_argv[SIZEOF_ARG] = {
3627         "git", "show", "--pretty=fuller", "--no-color", "--root",
3628                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3629 };
3631 static struct view_ops diff_ops = {
3632         "line",
3633         diff_argv,
3634         NULL,
3635         pager_read,
3636         pager_draw,
3637         pager_request,
3638         pager_grep,
3639         pager_select,
3640 };
3642 /*
3643  * Help backend
3644  */
3646 static bool
3647 help_open(struct view *view)
3649         char buf[SIZEOF_STR];
3650         size_t bufpos;
3651         int i;
3653         if (view->lines > 0)
3654                 return TRUE;
3656         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3658         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3659                 const char *key;
3661                 if (req_info[i].request == REQ_NONE)
3662                         continue;
3664                 if (!req_info[i].request) {
3665                         add_line_text(view, "", LINE_DEFAULT);
3666                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3667                         continue;
3668                 }
3670                 key = get_key(req_info[i].request);
3671                 if (!*key)
3672                         key = "(no key defined)";
3674                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3675                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3676                         if (buf[bufpos] == '_')
3677                                 buf[bufpos] = '-';
3678                 }
3680                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3681                                 key, buf, req_info[i].help);
3682         }
3684         if (run_requests) {
3685                 add_line_text(view, "", LINE_DEFAULT);
3686                 add_line_text(view, "External commands:", LINE_DEFAULT);
3687         }
3689         for (i = 0; i < run_requests; i++) {
3690                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3691                 const char *key;
3692                 int argc;
3694                 if (!req)
3695                         continue;
3697                 key = get_key_name(req->key);
3698                 if (!*key)
3699                         key = "(no key defined)";
3701                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3702                         if (!string_format_from(buf, &bufpos, "%s%s",
3703                                                 argc ? " " : "", req->argv[argc]))
3704                                 return REQ_NONE;
3706                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3707                                 keymap_table[req->keymap].name, key, buf);
3708         }
3710         return TRUE;
3713 static struct view_ops help_ops = {
3714         "line",
3715         NULL,
3716         help_open,
3717         NULL,
3718         pager_draw,
3719         pager_request,
3720         pager_grep,
3721         pager_select,
3722 };
3725 /*
3726  * Tree backend
3727  */
3729 struct tree_stack_entry {
3730         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3731         unsigned long lineno;           /* Line number to restore */
3732         char *name;                     /* Position of name in opt_path */
3733 };
3735 /* The top of the path stack. */
3736 static struct tree_stack_entry *tree_stack = NULL;
3737 unsigned long tree_lineno = 0;
3739 static void
3740 pop_tree_stack_entry(void)
3742         struct tree_stack_entry *entry = tree_stack;
3744         tree_lineno = entry->lineno;
3745         entry->name[0] = 0;
3746         tree_stack = entry->prev;
3747         free(entry);
3750 static void
3751 push_tree_stack_entry(const char *name, unsigned long lineno)
3753         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3754         size_t pathlen = strlen(opt_path);
3756         if (!entry)
3757                 return;
3759         entry->prev = tree_stack;
3760         entry->name = opt_path + pathlen;
3761         tree_stack = entry;
3763         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3764                 pop_tree_stack_entry();
3765                 return;
3766         }
3768         /* Move the current line to the first tree entry. */
3769         tree_lineno = 1;
3770         entry->lineno = lineno;
3773 /* Parse output from git-ls-tree(1):
3774  *
3775  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3776  */
3778 #define SIZEOF_TREE_ATTR \
3779         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3781 #define SIZEOF_TREE_MODE \
3782         STRING_SIZE("100644 ")
3784 #define TREE_ID_OFFSET \
3785         STRING_SIZE("100644 blob ")
3787 struct tree_entry {
3788         char id[SIZEOF_REV];
3789         mode_t mode;
3790         struct tm time;                 /* Date from the author ident. */
3791         char author[75];                /* Author of the commit. */
3792         char name[1];
3793 };
3795 static const char *
3796 tree_path(struct line *line)
3798         return ((struct tree_entry *) line->data)->name;
3802 static int
3803 tree_compare_entry(struct line *line1, struct line *line2)
3805         if (line1->type != line2->type)
3806                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3807         return strcmp(tree_path(line1), tree_path(line2));
3810 static struct line *
3811 tree_entry(struct view *view, enum line_type type, const char *path,
3812            const char *mode, const char *id)
3814         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3815         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3817         if (!entry || !line) {
3818                 free(entry);
3819                 return NULL;
3820         }
3822         strncpy(entry->name, path, strlen(path));
3823         if (mode)
3824                 entry->mode = strtoul(mode, NULL, 8);
3825         if (id)
3826                 string_copy_rev(entry->id, id);
3828         return line;
3831 static bool
3832 tree_read_date(struct view *view, char *text, bool *read_date)
3834         static char author_name[SIZEOF_STR];
3835         static struct tm author_time;
3837         if (!text && *read_date) {
3838                 *read_date = FALSE;
3839                 return TRUE;
3841         } else if (!text) {
3842                 char *path = *opt_path ? opt_path : ".";
3843                 /* Find next entry to process */
3844                 const char *log_file[] = {
3845                         "git", "log", "--no-color", "--pretty=raw",
3846                                 "--cc", "--raw", view->id, "--", path, NULL
3847                 };
3848                 struct io io = {};
3850                 if (!view->lines) {
3851                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3852                         report("Tree is empty");
3853                         return TRUE;
3854                 }
3856                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3857                         report("Failed to load tree data");
3858                         return TRUE;
3859                 }
3861                 done_io(view->pipe);
3862                 view->io = io;
3863                 *read_date = TRUE;
3864                 return FALSE;
3866         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3867                 parse_author_line(text + STRING_SIZE("author "),
3868                                   author_name, sizeof(author_name), &author_time);
3870         } else if (*text == ':') {
3871                 char *pos;
3872                 size_t annotated = 1;
3873                 size_t i;
3875                 pos = strchr(text, '\t');
3876                 if (!pos)
3877                         return TRUE;
3878                 text = pos + 1;
3879                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3880                         text += strlen(opt_prefix);
3881                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3882                         text += strlen(opt_path);
3883                 pos = strchr(text, '/');
3884                 if (pos)
3885                         *pos = 0;
3887                 for (i = 1; i < view->lines; i++) {
3888                         struct line *line = &view->line[i];
3889                         struct tree_entry *entry = line->data;
3891                         annotated += !!*entry->author;
3892                         if (*entry->author || strcmp(entry->name, text))
3893                                 continue;
3895                         string_copy(entry->author, author_name);
3896                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3897                         line->dirty = 1;
3898                         break;
3899                 }
3901                 if (annotated == view->lines)
3902                         kill_io(view->pipe);
3903         }
3904         return TRUE;
3907 static bool
3908 tree_read(struct view *view, char *text)
3910         static bool read_date = FALSE;
3911         struct tree_entry *data;
3912         struct line *entry, *line;
3913         enum line_type type;
3914         size_t textlen = text ? strlen(text) : 0;
3915         char *path = text + SIZEOF_TREE_ATTR;
3917         if (read_date || !text)
3918                 return tree_read_date(view, text, &read_date);
3920         if (textlen <= SIZEOF_TREE_ATTR)
3921                 return FALSE;
3922         if (view->lines == 0 &&
3923             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3924                 return FALSE;
3926         /* Strip the path part ... */
3927         if (*opt_path) {
3928                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3929                 size_t striplen = strlen(opt_path);
3931                 if (pathlen > striplen)
3932                         memmove(path, path + striplen,
3933                                 pathlen - striplen + 1);
3935                 /* Insert "link" to parent directory. */
3936                 if (view->lines == 1 &&
3937                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3938                         return FALSE;
3939         }
3941         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3942         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3943         if (!entry)
3944                 return FALSE;
3945         data = entry->data;
3947         /* Skip "Directory ..." and ".." line. */
3948         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3949                 if (tree_compare_entry(line, entry) <= 0)
3950                         continue;
3952                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3954                 line->data = data;
3955                 line->type = type;
3956                 for (; line <= entry; line++)
3957                         line->dirty = line->cleareol = 1;
3958                 return TRUE;
3959         }
3961         if (tree_lineno > view->lineno) {
3962                 view->lineno = tree_lineno;
3963                 tree_lineno = 0;
3964         }
3966         return TRUE;
3969 static bool
3970 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3972         struct tree_entry *entry = line->data;
3974         if (line->type == LINE_TREE_HEAD) {
3975                 if (draw_text(view, line->type, "Directory path /", TRUE))
3976                         return TRUE;
3977         } else {
3978                 if (draw_mode(view, entry->mode))
3979                         return TRUE;
3981                 if (opt_author && draw_author(view, entry->author))
3982                         return TRUE;
3984                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3985                         return TRUE;
3986         }
3987         if (draw_text(view, line->type, entry->name, TRUE))
3988                 return TRUE;
3989         return TRUE;
3992 static void
3993 open_blob_editor()
3995         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3996         int fd = mkstemp(file);
3998         if (fd == -1)
3999                 report("Failed to create temporary file");
4000         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4001                 report("Failed to save blob data to file");
4002         else
4003                 open_editor(FALSE, file);
4004         if (fd != -1)
4005                 unlink(file);
4008 static enum request
4009 tree_request(struct view *view, enum request request, struct line *line)
4011         enum open_flags flags;
4013         switch (request) {
4014         case REQ_VIEW_BLAME:
4015                 if (line->type != LINE_TREE_FILE) {
4016                         report("Blame only supported for files");
4017                         return REQ_NONE;
4018                 }
4020                 string_copy(opt_ref, view->vid);
4021                 return request;
4023         case REQ_EDIT:
4024                 if (line->type != LINE_TREE_FILE) {
4025                         report("Edit only supported for files");
4026                 } else if (!is_head_commit(view->vid)) {
4027                         open_blob_editor();
4028                 } else {
4029                         open_editor(TRUE, opt_file);
4030                 }
4031                 return REQ_NONE;
4033         case REQ_PARENT:
4034                 if (!*opt_path) {
4035                         /* quit view if at top of tree */
4036                         return REQ_VIEW_CLOSE;
4037                 }
4038                 /* fake 'cd  ..' */
4039                 line = &view->line[1];
4040                 break;
4042         case REQ_ENTER:
4043                 break;
4045         default:
4046                 return request;
4047         }
4049         /* Cleanup the stack if the tree view is at a different tree. */
4050         while (!*opt_path && tree_stack)
4051                 pop_tree_stack_entry();
4053         switch (line->type) {
4054         case LINE_TREE_DIR:
4055                 /* Depending on whether it is a subdirectory or parent link
4056                  * mangle the path buffer. */
4057                 if (line == &view->line[1] && *opt_path) {
4058                         pop_tree_stack_entry();
4060                 } else {
4061                         const char *basename = tree_path(line);
4063                         push_tree_stack_entry(basename, view->lineno);
4064                 }
4066                 /* Trees and subtrees share the same ID, so they are not not
4067                  * unique like blobs. */
4068                 flags = OPEN_RELOAD;
4069                 request = REQ_VIEW_TREE;
4070                 break;
4072         case LINE_TREE_FILE:
4073                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4074                 request = REQ_VIEW_BLOB;
4075                 break;
4077         default:
4078                 return REQ_NONE;
4079         }
4081         open_view(view, request, flags);
4082         if (request == REQ_VIEW_TREE)
4083                 view->lineno = tree_lineno;
4085         return REQ_NONE;
4088 static void
4089 tree_select(struct view *view, struct line *line)
4091         struct tree_entry *entry = line->data;
4093         if (line->type == LINE_TREE_FILE) {
4094                 string_copy_rev(ref_blob, entry->id);
4095                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4097         } else if (line->type != LINE_TREE_DIR) {
4098                 return;
4099         }
4101         string_copy_rev(view->ref, entry->id);
4104 static const char *tree_argv[SIZEOF_ARG] = {
4105         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4106 };
4108 static struct view_ops tree_ops = {
4109         "file",
4110         tree_argv,
4111         NULL,
4112         tree_read,
4113         tree_draw,
4114         tree_request,
4115         pager_grep,
4116         tree_select,
4117 };
4119 static bool
4120 blob_read(struct view *view, char *line)
4122         if (!line)
4123                 return TRUE;
4124         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4127 static enum request
4128 blob_request(struct view *view, enum request request, struct line *line)
4130         switch (request) {
4131         case REQ_EDIT:
4132                 open_blob_editor();
4133                 return REQ_NONE;
4134         default:
4135                 return pager_request(view, request, line);
4136         }
4139 static const char *blob_argv[SIZEOF_ARG] = {
4140         "git", "cat-file", "blob", "%(blob)", NULL
4141 };
4143 static struct view_ops blob_ops = {
4144         "line",
4145         blob_argv,
4146         NULL,
4147         blob_read,
4148         pager_draw,
4149         blob_request,
4150         pager_grep,
4151         pager_select,
4152 };
4154 /*
4155  * Blame backend
4156  *
4157  * Loading the blame view is a two phase job:
4158  *
4159  *  1. File content is read either using opt_file from the
4160  *     filesystem or using git-cat-file.
4161  *  2. Then blame information is incrementally added by
4162  *     reading output from git-blame.
4163  */
4165 static const char *blame_head_argv[] = {
4166         "git", "blame", "--incremental", "--", "%(file)", NULL
4167 };
4169 static const char *blame_ref_argv[] = {
4170         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4171 };
4173 static const char *blame_cat_file_argv[] = {
4174         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4175 };
4177 struct blame_commit {
4178         char id[SIZEOF_REV];            /* SHA1 ID. */
4179         char title[128];                /* First line of the commit message. */
4180         char author[75];                /* Author of the commit. */
4181         struct tm time;                 /* Date from the author ident. */
4182         char filename[128];             /* Name of file. */
4183         bool has_previous;              /* Was a "previous" line detected. */
4184 };
4186 struct blame {
4187         struct blame_commit *commit;
4188         char text[1];
4189 };
4191 static bool
4192 blame_open(struct view *view)
4194         if (*opt_ref || !io_open(&view->io, opt_file)) {
4195                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4196                         return FALSE;
4197         }
4199         setup_update(view, opt_file);
4200         string_format(view->ref, "%s ...", opt_file);
4202         return TRUE;
4205 static struct blame_commit *
4206 get_blame_commit(struct view *view, const char *id)
4208         size_t i;
4210         for (i = 0; i < view->lines; i++) {
4211                 struct blame *blame = view->line[i].data;
4213                 if (!blame->commit)
4214                         continue;
4216                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4217                         return blame->commit;
4218         }
4220         {
4221                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4223                 if (commit)
4224                         string_ncopy(commit->id, id, SIZEOF_REV);
4225                 return commit;
4226         }
4229 static bool
4230 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4232         const char *pos = *posref;
4234         *posref = NULL;
4235         pos = strchr(pos + 1, ' ');
4236         if (!pos || !isdigit(pos[1]))
4237                 return FALSE;
4238         *number = atoi(pos + 1);
4239         if (*number < min || *number > max)
4240                 return FALSE;
4242         *posref = pos;
4243         return TRUE;
4246 static struct blame_commit *
4247 parse_blame_commit(struct view *view, const char *text, int *blamed)
4249         struct blame_commit *commit;
4250         struct blame *blame;
4251         const char *pos = text + SIZEOF_REV - 1;
4252         size_t lineno;
4253         size_t group;
4255         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4256                 return NULL;
4258         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4259             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4260                 return NULL;
4262         commit = get_blame_commit(view, text);
4263         if (!commit)
4264                 return NULL;
4266         *blamed += group;
4267         while (group--) {
4268                 struct line *line = &view->line[lineno + group - 1];
4270                 blame = line->data;
4271                 blame->commit = commit;
4272                 line->dirty = 1;
4273         }
4275         return commit;
4278 static bool
4279 blame_read_file(struct view *view, const char *line, bool *read_file)
4281         if (!line) {
4282                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4283                 struct io io = {};
4285                 if (view->lines == 0 && !view->parent)
4286                         die("No blame exist for %s", view->vid);
4288                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4289                         report("Failed to load blame data");
4290                         return TRUE;
4291                 }
4293                 done_io(view->pipe);
4294                 view->io = io;
4295                 *read_file = FALSE;
4296                 return FALSE;
4298         } else {
4299                 size_t linelen = string_expand_length(line, opt_tab_size);
4300                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4302                 if (!blame)
4303                         return FALSE;
4305                 blame->commit = NULL;
4306                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4307                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4308         }
4311 static bool
4312 match_blame_header(const char *name, char **line)
4314         size_t namelen = strlen(name);
4315         bool matched = !strncmp(name, *line, namelen);
4317         if (matched)
4318                 *line += namelen;
4320         return matched;
4323 static bool
4324 blame_read(struct view *view, char *line)
4326         static struct blame_commit *commit = NULL;
4327         static int blamed = 0;
4328         static time_t author_time;
4329         static bool read_file = TRUE;
4331         if (read_file)
4332                 return blame_read_file(view, line, &read_file);
4334         if (!line) {
4335                 /* Reset all! */
4336                 commit = NULL;
4337                 blamed = 0;
4338                 read_file = TRUE;
4339                 string_format(view->ref, "%s", view->vid);
4340                 if (view_is_displayed(view)) {
4341                         update_view_title(view);
4342                         redraw_view_from(view, 0);
4343                 }
4344                 return TRUE;
4345         }
4347         if (!commit) {
4348                 commit = parse_blame_commit(view, line, &blamed);
4349                 string_format(view->ref, "%s %2d%%", view->vid,
4350                               view->lines ? blamed * 100 / view->lines : 0);
4352         } else if (match_blame_header("author ", &line)) {
4353                 string_ncopy(commit->author, line, strlen(line));
4355         } else if (match_blame_header("author-time ", &line)) {
4356                 author_time = (time_t) atol(line);
4358         } else if (match_blame_header("author-tz ", &line)) {
4359                 parse_timezone(&author_time, line);
4360                 gmtime_r(&author_time, &commit->time);
4362         } else if (match_blame_header("summary ", &line)) {
4363                 string_ncopy(commit->title, line, strlen(line));
4365         } else if (match_blame_header("previous ", &line)) {
4366                 commit->has_previous = TRUE;
4368         } else if (match_blame_header("filename ", &line)) {
4369                 string_ncopy(commit->filename, line, strlen(line));
4370                 commit = NULL;
4371         }
4373         return TRUE;
4376 static bool
4377 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4379         struct blame *blame = line->data;
4380         struct tm *time = NULL;
4381         const char *id = NULL, *author = NULL;
4383         if (blame->commit && *blame->commit->filename) {
4384                 id = blame->commit->id;
4385                 author = blame->commit->author;
4386                 time = &blame->commit->time;
4387         }
4389         if (opt_date && draw_date(view, time))
4390                 return TRUE;
4392         if (opt_author && draw_author(view, author))
4393                 return TRUE;
4395         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4396                 return TRUE;
4398         if (draw_lineno(view, lineno))
4399                 return TRUE;
4401         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4402         return TRUE;
4405 static bool
4406 check_blame_commit(struct blame *blame)
4408         if (!blame->commit)
4409                 report("Commit data not loaded yet");
4410         else if (!strcmp(blame->commit->id, NULL_ID))
4411                 report("No commit exist for the selected line");
4412         else
4413                 return TRUE;
4414         return FALSE;
4417 static enum request
4418 blame_request(struct view *view, enum request request, struct line *line)
4420         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4421         struct blame *blame = line->data;
4423         switch (request) {
4424         case REQ_VIEW_BLAME:
4425                 if (check_blame_commit(blame)) {
4426                         string_copy(opt_ref, blame->commit->id);
4427                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4428                 }
4429                 break;
4431         case REQ_PARENT:
4432                 if (check_blame_commit(blame) &&
4433                     select_commit_parent(blame->commit->id, opt_ref))
4434                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4435                 break;
4437         case REQ_ENTER:
4438                 if (!blame->commit) {
4439                         report("No commit loaded yet");
4440                         break;
4441                 }
4443                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4444                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4445                         break;
4447                 if (!strcmp(blame->commit->id, NULL_ID)) {
4448                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4449                         const char *diff_index_argv[] = {
4450                                 "git", "diff-index", "--root", "--patch-with-stat",
4451                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4452                         };
4454                         if (!blame->commit->has_previous) {
4455                                 diff_index_argv[1] = "diff";
4456                                 diff_index_argv[2] = "--no-color";
4457                                 diff_index_argv[6] = "--";
4458                                 diff_index_argv[7] = "/dev/null";
4459                         }
4461                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4462                                 report("Failed to allocate diff command");
4463                                 break;
4464                         }
4465                         flags |= OPEN_PREPARED;
4466                 }
4468                 open_view(view, REQ_VIEW_DIFF, flags);
4469                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4470                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4471                 break;
4473         default:
4474                 return request;
4475         }
4477         return REQ_NONE;
4480 static bool
4481 blame_grep(struct view *view, struct line *line)
4483         struct blame *blame = line->data;
4484         struct blame_commit *commit = blame->commit;
4485         regmatch_t pmatch;
4487 #define MATCH(text, on)                                                 \
4488         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4490         if (commit) {
4491                 char buf[DATE_COLS + 1];
4493                 if (MATCH(commit->title, 1) ||
4494                     MATCH(commit->author, opt_author) ||
4495                     MATCH(commit->id, opt_date))
4496                         return TRUE;
4498                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4499                     MATCH(buf, 1))
4500                         return TRUE;
4501         }
4503         return MATCH(blame->text, 1);
4505 #undef MATCH
4508 static void
4509 blame_select(struct view *view, struct line *line)
4511         struct blame *blame = line->data;
4512         struct blame_commit *commit = blame->commit;
4514         if (!commit)
4515                 return;
4517         if (!strcmp(commit->id, NULL_ID))
4518                 string_ncopy(ref_commit, "HEAD", 4);
4519         else
4520                 string_copy_rev(ref_commit, commit->id);
4523 static struct view_ops blame_ops = {
4524         "line",
4525         NULL,
4526         blame_open,
4527         blame_read,
4528         blame_draw,
4529         blame_request,
4530         blame_grep,
4531         blame_select,
4532 };
4534 /*
4535  * Status backend
4536  */
4538 struct status {
4539         char status;
4540         struct {
4541                 mode_t mode;
4542                 char rev[SIZEOF_REV];
4543                 char name[SIZEOF_STR];
4544         } old;
4545         struct {
4546                 mode_t mode;
4547                 char rev[SIZEOF_REV];
4548                 char name[SIZEOF_STR];
4549         } new;
4550 };
4552 static char status_onbranch[SIZEOF_STR];
4553 static struct status stage_status;
4554 static enum line_type stage_line_type;
4555 static size_t stage_chunks;
4556 static int *stage_chunk;
4558 /* This should work even for the "On branch" line. */
4559 static inline bool
4560 status_has_none(struct view *view, struct line *line)
4562         return line < view->line + view->lines && !line[1].data;
4565 /* Get fields from the diff line:
4566  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4567  */
4568 static inline bool
4569 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4571         const char *old_mode = buf +  1;
4572         const char *new_mode = buf +  8;
4573         const char *old_rev  = buf + 15;
4574         const char *new_rev  = buf + 56;
4575         const char *status   = buf + 97;
4577         if (bufsize < 98 ||
4578             old_mode[-1] != ':' ||
4579             new_mode[-1] != ' ' ||
4580             old_rev[-1]  != ' ' ||
4581             new_rev[-1]  != ' ' ||
4582             status[-1]   != ' ')
4583                 return FALSE;
4585         file->status = *status;
4587         string_copy_rev(file->old.rev, old_rev);
4588         string_copy_rev(file->new.rev, new_rev);
4590         file->old.mode = strtoul(old_mode, NULL, 8);
4591         file->new.mode = strtoul(new_mode, NULL, 8);
4593         file->old.name[0] = file->new.name[0] = 0;
4595         return TRUE;
4598 static bool
4599 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4601         struct status *unmerged = NULL;
4602         char *buf;
4603         struct io io = {};
4605         if (!run_io(&io, argv, NULL, IO_RD))
4606                 return FALSE;
4608         add_line_data(view, NULL, type);
4610         while ((buf = io_get(&io, 0, TRUE))) {
4611                 struct status *file = unmerged;
4613                 if (!file) {
4614                         file = calloc(1, sizeof(*file));
4615                         if (!file || !add_line_data(view, file, type))
4616                                 goto error_out;
4617                 }
4619                 /* Parse diff info part. */
4620                 if (status) {
4621                         file->status = status;
4622                         if (status == 'A')
4623                                 string_copy(file->old.rev, NULL_ID);
4625                 } else if (!file->status || file == unmerged) {
4626                         if (!status_get_diff(file, buf, strlen(buf)))
4627                                 goto error_out;
4629                         buf = io_get(&io, 0, TRUE);
4630                         if (!buf)
4631                                 break;
4633                         /* Collapse all modified entries that follow an
4634                          * associated unmerged entry. */
4635                         if (unmerged == file) {
4636                                 unmerged->status = 'U';
4637                                 unmerged = NULL;
4638                         } else if (file->status == 'U') {
4639                                 unmerged = file;
4640                         }
4641                 }
4643                 /* Grab the old name for rename/copy. */
4644                 if (!*file->old.name &&
4645                     (file->status == 'R' || file->status == 'C')) {
4646                         string_ncopy(file->old.name, buf, strlen(buf));
4648                         buf = io_get(&io, 0, TRUE);
4649                         if (!buf)
4650                                 break;
4651                 }
4653                 /* git-ls-files just delivers a NUL separated list of
4654                  * file names similar to the second half of the
4655                  * git-diff-* output. */
4656                 string_ncopy(file->new.name, buf, strlen(buf));
4657                 if (!*file->old.name)
4658                         string_copy(file->old.name, file->new.name);
4659                 file = NULL;
4660         }
4662         if (io_error(&io)) {
4663 error_out:
4664                 done_io(&io);
4665                 return FALSE;
4666         }
4668         if (!view->line[view->lines - 1].data)
4669                 add_line_data(view, NULL, LINE_STAT_NONE);
4671         done_io(&io);
4672         return TRUE;
4675 /* Don't show unmerged entries in the staged section. */
4676 static const char *status_diff_index_argv[] = {
4677         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4678                              "--cached", "-M", "HEAD", NULL
4679 };
4681 static const char *status_diff_files_argv[] = {
4682         "git", "diff-files", "-z", NULL
4683 };
4685 static const char *status_list_other_argv[] = {
4686         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4687 };
4689 static const char *status_list_no_head_argv[] = {
4690         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4691 };
4693 static const char *update_index_argv[] = {
4694         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4695 };
4697 /* Restore the previous line number to stay in the context or select a
4698  * line with something that can be updated. */
4699 static void
4700 status_restore(struct view *view)
4702         if (view->p_lineno >= view->lines)
4703                 view->p_lineno = view->lines - 1;
4704         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4705                 view->p_lineno++;
4706         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4707                 view->p_lineno--;
4709         /* If the above fails, always skip the "On branch" line. */
4710         if (view->p_lineno < view->lines)
4711                 view->lineno = view->p_lineno;
4712         else
4713                 view->lineno = 1;
4715         if (view->lineno < view->offset)
4716                 view->offset = view->lineno;
4717         else if (view->offset + view->height <= view->lineno)
4718                 view->offset = view->lineno - view->height + 1;
4720         view->p_restore = FALSE;
4723 static void
4724 status_update_onbranch(void)
4726         static const char *paths[][2] = {
4727                 { "rebase-apply/rebasing",      "Rebasing" },
4728                 { "rebase-apply/applying",      "Applying mailbox" },
4729                 { "rebase-apply/",              "Rebasing mailbox" },
4730                 { "rebase-merge/interactive",   "Interactive rebase" },
4731                 { "rebase-merge/",              "Rebase merge" },
4732                 { "MERGE_HEAD",                 "Merging" },
4733                 { "BISECT_LOG",                 "Bisecting" },
4734                 { "HEAD",                       "On branch" },
4735         };
4736         char buf[SIZEOF_STR];
4737         struct stat stat;
4738         int i;
4740         if (is_initial_commit()) {
4741                 string_copy(status_onbranch, "Initial commit");
4742                 return;
4743         }
4745         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4746                 char *head = opt_head;
4748                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4749                     lstat(buf, &stat) < 0)
4750                         continue;
4752                 if (!*opt_head) {
4753                         struct io io = {};
4755                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4756                             io_open(&io, buf) &&
4757                             io_read_buf(&io, buf, sizeof(buf))) {
4758                                 head = chomp_string(buf);
4759                                 if (!prefixcmp(head, "refs/heads/"))
4760                                         head += STRING_SIZE("refs/heads/");
4761                         }
4762                 }
4764                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4765                         string_copy(status_onbranch, opt_head);
4766                 return;
4767         }
4769         string_copy(status_onbranch, "Not currently on any branch");
4772 /* First parse staged info using git-diff-index(1), then parse unstaged
4773  * info using git-diff-files(1), and finally untracked files using
4774  * git-ls-files(1). */
4775 static bool
4776 status_open(struct view *view)
4778         reset_view(view);
4780         add_line_data(view, NULL, LINE_STAT_HEAD);
4781         status_update_onbranch();
4783         run_io_bg(update_index_argv);
4785         if (is_initial_commit()) {
4786                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4787                         return FALSE;
4788         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4789                 return FALSE;
4790         }
4792         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4793             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4794                 return FALSE;
4796         /* Restore the exact position or use the specialized restore
4797          * mode? */
4798         if (!view->p_restore)
4799                 status_restore(view);
4800         return TRUE;
4803 static bool
4804 status_draw(struct view *view, struct line *line, unsigned int lineno)
4806         struct status *status = line->data;
4807         enum line_type type;
4808         const char *text;
4810         if (!status) {
4811                 switch (line->type) {
4812                 case LINE_STAT_STAGED:
4813                         type = LINE_STAT_SECTION;
4814                         text = "Changes to be committed:";
4815                         break;
4817                 case LINE_STAT_UNSTAGED:
4818                         type = LINE_STAT_SECTION;
4819                         text = "Changed but not updated:";
4820                         break;
4822                 case LINE_STAT_UNTRACKED:
4823                         type = LINE_STAT_SECTION;
4824                         text = "Untracked files:";
4825                         break;
4827                 case LINE_STAT_NONE:
4828                         type = LINE_DEFAULT;
4829                         text = "  (no files)";
4830                         break;
4832                 case LINE_STAT_HEAD:
4833                         type = LINE_STAT_HEAD;
4834                         text = status_onbranch;
4835                         break;
4837                 default:
4838                         return FALSE;
4839                 }
4840         } else {
4841                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4843                 buf[0] = status->status;
4844                 if (draw_text(view, line->type, buf, TRUE))
4845                         return TRUE;
4846                 type = LINE_DEFAULT;
4847                 text = status->new.name;
4848         }
4850         draw_text(view, type, text, TRUE);
4851         return TRUE;
4854 static enum request
4855 status_enter(struct view *view, struct line *line)
4857         struct status *status = line->data;
4858         const char *oldpath = status ? status->old.name : NULL;
4859         /* Diffs for unmerged entries are empty when passing the new
4860          * path, so leave it empty. */
4861         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4862         const char *info;
4863         enum open_flags split;
4864         struct view *stage = VIEW(REQ_VIEW_STAGE);
4866         if (line->type == LINE_STAT_NONE ||
4867             (!status && line[1].type == LINE_STAT_NONE)) {
4868                 report("No file to diff");
4869                 return REQ_NONE;
4870         }
4872         switch (line->type) {
4873         case LINE_STAT_STAGED:
4874                 if (is_initial_commit()) {
4875                         const char *no_head_diff_argv[] = {
4876                                 "git", "diff", "--no-color", "--patch-with-stat",
4877                                         "--", "/dev/null", newpath, NULL
4878                         };
4880                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4881                                 return REQ_QUIT;
4882                 } else {
4883                         const char *index_show_argv[] = {
4884                                 "git", "diff-index", "--root", "--patch-with-stat",
4885                                         "-C", "-M", "--cached", "HEAD", "--",
4886                                         oldpath, newpath, NULL
4887                         };
4889                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4890                                 return REQ_QUIT;
4891                 }
4893                 if (status)
4894                         info = "Staged changes to %s";
4895                 else
4896                         info = "Staged changes";
4897                 break;
4899         case LINE_STAT_UNSTAGED:
4900         {
4901                 const char *files_show_argv[] = {
4902                         "git", "diff-files", "--root", "--patch-with-stat",
4903                                 "-C", "-M", "--", oldpath, newpath, NULL
4904                 };
4906                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4907                         return REQ_QUIT;
4908                 if (status)
4909                         info = "Unstaged changes to %s";
4910                 else
4911                         info = "Unstaged changes";
4912                 break;
4913         }
4914         case LINE_STAT_UNTRACKED:
4915                 if (!newpath) {
4916                         report("No file to show");
4917                         return REQ_NONE;
4918                 }
4920                 if (!suffixcmp(status->new.name, -1, "/")) {
4921                         report("Cannot display a directory");
4922                         return REQ_NONE;
4923                 }
4925                 if (!prepare_update_file(stage, newpath))
4926                         return REQ_QUIT;
4927                 info = "Untracked file %s";
4928                 break;
4930         case LINE_STAT_HEAD:
4931                 return REQ_NONE;
4933         default:
4934                 die("line type %d not handled in switch", line->type);
4935         }
4937         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4938         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4939         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4940                 if (status) {
4941                         stage_status = *status;
4942                 } else {
4943                         memset(&stage_status, 0, sizeof(stage_status));
4944                 }
4946                 stage_line_type = line->type;
4947                 stage_chunks = 0;
4948                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4949         }
4951         return REQ_NONE;
4954 static bool
4955 status_exists(struct status *status, enum line_type type)
4957         struct view *view = VIEW(REQ_VIEW_STATUS);
4958         unsigned long lineno;
4960         for (lineno = 0; lineno < view->lines; lineno++) {
4961                 struct line *line = &view->line[lineno];
4962                 struct status *pos = line->data;
4964                 if (line->type != type)
4965                         continue;
4966                 if (!pos && (!status || !status->status) && line[1].data) {
4967                         select_view_line(view, lineno);
4968                         return TRUE;
4969                 }
4970                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4971                         select_view_line(view, lineno);
4972                         return TRUE;
4973                 }
4974         }
4976         return FALSE;
4980 static bool
4981 status_update_prepare(struct io *io, enum line_type type)
4983         const char *staged_argv[] = {
4984                 "git", "update-index", "-z", "--index-info", NULL
4985         };
4986         const char *others_argv[] = {
4987                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4988         };
4990         switch (type) {
4991         case LINE_STAT_STAGED:
4992                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4994         case LINE_STAT_UNSTAGED:
4995                 return run_io(io, others_argv, opt_cdup, IO_WR);
4997         case LINE_STAT_UNTRACKED:
4998                 return run_io(io, others_argv, NULL, IO_WR);
5000         default:
5001                 die("line type %d not handled in switch", type);
5002                 return FALSE;
5003         }
5006 static bool
5007 status_update_write(struct io *io, struct status *status, enum line_type type)
5009         char buf[SIZEOF_STR];
5010         size_t bufsize = 0;
5012         switch (type) {
5013         case LINE_STAT_STAGED:
5014                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5015                                         status->old.mode,
5016                                         status->old.rev,
5017                                         status->old.name, 0))
5018                         return FALSE;
5019                 break;
5021         case LINE_STAT_UNSTAGED:
5022         case LINE_STAT_UNTRACKED:
5023                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5024                         return FALSE;
5025                 break;
5027         default:
5028                 die("line type %d not handled in switch", type);
5029         }
5031         return io_write(io, buf, bufsize);
5034 static bool
5035 status_update_file(struct status *status, enum line_type type)
5037         struct io io = {};
5038         bool result;
5040         if (!status_update_prepare(&io, type))
5041                 return FALSE;
5043         result = status_update_write(&io, status, type);
5044         done_io(&io);
5045         return result;
5048 static bool
5049 status_update_files(struct view *view, struct line *line)
5051         struct io io = {};
5052         bool result = TRUE;
5053         struct line *pos = view->line + view->lines;
5054         int files = 0;
5055         int file, done;
5057         if (!status_update_prepare(&io, line->type))
5058                 return FALSE;
5060         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5061                 files++;
5063         for (file = 0, done = 0; result && file < files; line++, file++) {
5064                 int almost_done = file * 100 / files;
5066                 if (almost_done > done) {
5067                         done = almost_done;
5068                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5069                                       file, files, done);
5070                         update_view_title(view);
5071                 }
5072                 result = status_update_write(&io, line->data, line->type);
5073         }
5075         done_io(&io);
5076         return result;
5079 static bool
5080 status_update(struct view *view)
5082         struct line *line = &view->line[view->lineno];
5084         assert(view->lines);
5086         if (!line->data) {
5087                 /* This should work even for the "On branch" line. */
5088                 if (line < view->line + view->lines && !line[1].data) {
5089                         report("Nothing to update");
5090                         return FALSE;
5091                 }
5093                 if (!status_update_files(view, line + 1)) {
5094                         report("Failed to update file status");
5095                         return FALSE;
5096                 }
5098         } else if (!status_update_file(line->data, line->type)) {
5099                 report("Failed to update file status");
5100                 return FALSE;
5101         }
5103         return TRUE;
5106 static bool
5107 status_revert(struct status *status, enum line_type type, bool has_none)
5109         if (!status || type != LINE_STAT_UNSTAGED) {
5110                 if (type == LINE_STAT_STAGED) {
5111                         report("Cannot revert changes to staged files");
5112                 } else if (type == LINE_STAT_UNTRACKED) {
5113                         report("Cannot revert changes to untracked files");
5114                 } else if (has_none) {
5115                         report("Nothing to revert");
5116                 } else {
5117                         report("Cannot revert changes to multiple files");
5118                 }
5119                 return FALSE;
5121         } else {
5122                 char mode[10] = "100644";
5123                 const char *reset_argv[] = {
5124                         "git", "update-index", "--cacheinfo", mode,
5125                                 status->old.rev, status->old.name, NULL
5126                 };
5127                 const char *checkout_argv[] = {
5128                         "git", "checkout", "--", status->old.name, NULL
5129                 };
5131                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5132                         return FALSE;
5133                 string_format(mode, "%o", status->old.mode);
5134                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5135                         run_io_fg(checkout_argv, opt_cdup);
5136         }
5139 static enum request
5140 status_request(struct view *view, enum request request, struct line *line)
5142         struct status *status = line->data;
5144         switch (request) {
5145         case REQ_STATUS_UPDATE:
5146                 if (!status_update(view))
5147                         return REQ_NONE;
5148                 break;
5150         case REQ_STATUS_REVERT:
5151                 if (!status_revert(status, line->type, status_has_none(view, line)))
5152                         return REQ_NONE;
5153                 break;
5155         case REQ_STATUS_MERGE:
5156                 if (!status || status->status != 'U') {
5157                         report("Merging only possible for files with unmerged status ('U').");
5158                         return REQ_NONE;
5159                 }
5160                 open_mergetool(status->new.name);
5161                 break;
5163         case REQ_EDIT:
5164                 if (!status)
5165                         return request;
5166                 if (status->status == 'D') {
5167                         report("File has been deleted.");
5168                         return REQ_NONE;
5169                 }
5171                 open_editor(status->status != '?', status->new.name);
5172                 break;
5174         case REQ_VIEW_BLAME:
5175                 if (status) {
5176                         string_copy(opt_file, status->new.name);
5177                         opt_ref[0] = 0;
5178                 }
5179                 return request;
5181         case REQ_ENTER:
5182                 /* After returning the status view has been split to
5183                  * show the stage view. No further reloading is
5184                  * necessary. */
5185                 status_enter(view, line);
5186                 return REQ_NONE;
5188         case REQ_REFRESH:
5189                 /* Simply reload the view. */
5190                 break;
5192         default:
5193                 return request;
5194         }
5196         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5198         return REQ_NONE;
5201 static void
5202 status_select(struct view *view, struct line *line)
5204         struct status *status = line->data;
5205         char file[SIZEOF_STR] = "all files";
5206         const char *text;
5207         const char *key;
5209         if (status && !string_format(file, "'%s'", status->new.name))
5210                 return;
5212         if (!status && line[1].type == LINE_STAT_NONE)
5213                 line++;
5215         switch (line->type) {
5216         case LINE_STAT_STAGED:
5217                 text = "Press %s to unstage %s for commit";
5218                 break;
5220         case LINE_STAT_UNSTAGED:
5221                 text = "Press %s to stage %s for commit";
5222                 break;
5224         case LINE_STAT_UNTRACKED:
5225                 text = "Press %s to stage %s for addition";
5226                 break;
5228         case LINE_STAT_HEAD:
5229         case LINE_STAT_NONE:
5230                 text = "Nothing to update";
5231                 break;
5233         default:
5234                 die("line type %d not handled in switch", line->type);
5235         }
5237         if (status && status->status == 'U') {
5238                 text = "Press %s to resolve conflict in %s";
5239                 key = get_key(REQ_STATUS_MERGE);
5241         } else {
5242                 key = get_key(REQ_STATUS_UPDATE);
5243         }
5245         string_format(view->ref, text, key, file);
5248 static bool
5249 status_grep(struct view *view, struct line *line)
5251         struct status *status = line->data;
5252         enum { S_STATUS, S_NAME, S_END } state;
5253         char buf[2] = "?";
5254         regmatch_t pmatch;
5256         if (!status)
5257                 return FALSE;
5259         for (state = S_STATUS; state < S_END; state++) {
5260                 const char *text;
5262                 switch (state) {
5263                 case S_NAME:    text = status->new.name;        break;
5264                 case S_STATUS:
5265                         buf[0] = status->status;
5266                         text = buf;
5267                         break;
5269                 default:
5270                         return FALSE;
5271                 }
5273                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5274                         return TRUE;
5275         }
5277         return FALSE;
5280 static struct view_ops status_ops = {
5281         "file",
5282         NULL,
5283         status_open,
5284         NULL,
5285         status_draw,
5286         status_request,
5287         status_grep,
5288         status_select,
5289 };
5292 static bool
5293 stage_diff_write(struct io *io, struct line *line, struct line *end)
5295         while (line < end) {
5296                 if (!io_write(io, line->data, strlen(line->data)) ||
5297                     !io_write(io, "\n", 1))
5298                         return FALSE;
5299                 line++;
5300                 if (line->type == LINE_DIFF_CHUNK ||
5301                     line->type == LINE_DIFF_HEADER)
5302                         break;
5303         }
5305         return TRUE;
5308 static struct line *
5309 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5311         for (; view->line < line; line--)
5312                 if (line->type == type)
5313                         return line;
5315         return NULL;
5318 static bool
5319 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5321         const char *apply_argv[SIZEOF_ARG] = {
5322                 "git", "apply", "--whitespace=nowarn", NULL
5323         };
5324         struct line *diff_hdr;
5325         struct io io = {};
5326         int argc = 3;
5328         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5329         if (!diff_hdr)
5330                 return FALSE;
5332         if (!revert)
5333                 apply_argv[argc++] = "--cached";
5334         if (revert || stage_line_type == LINE_STAT_STAGED)
5335                 apply_argv[argc++] = "-R";
5336         apply_argv[argc++] = "-";
5337         apply_argv[argc++] = NULL;
5338         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5339                 return FALSE;
5341         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5342             !stage_diff_write(&io, chunk, view->line + view->lines))
5343                 chunk = NULL;
5345         done_io(&io);
5346         run_io_bg(update_index_argv);
5348         return chunk ? TRUE : FALSE;
5351 static bool
5352 stage_update(struct view *view, struct line *line)
5354         struct line *chunk = NULL;
5356         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5357                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5359         if (chunk) {
5360                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5361                         report("Failed to apply chunk");
5362                         return FALSE;
5363                 }
5365         } else if (!stage_status.status) {
5366                 view = VIEW(REQ_VIEW_STATUS);
5368                 for (line = view->line; line < view->line + view->lines; line++)
5369                         if (line->type == stage_line_type)
5370                                 break;
5372                 if (!status_update_files(view, line + 1)) {
5373                         report("Failed to update files");
5374                         return FALSE;
5375                 }
5377         } else if (!status_update_file(&stage_status, stage_line_type)) {
5378                 report("Failed to update file");
5379                 return FALSE;
5380         }
5382         return TRUE;
5385 static bool
5386 stage_revert(struct view *view, struct line *line)
5388         struct line *chunk = NULL;
5390         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5391                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5393         if (chunk) {
5394                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5395                         return FALSE;
5397                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5398                         report("Failed to revert chunk");
5399                         return FALSE;
5400                 }
5401                 return TRUE;
5403         } else {
5404                 return status_revert(stage_status.status ? &stage_status : NULL,
5405                                      stage_line_type, FALSE);
5406         }
5410 static void
5411 stage_next(struct view *view, struct line *line)
5413         int i;
5415         if (!stage_chunks) {
5416                 static size_t alloc = 0;
5417                 int *tmp;
5419                 for (line = view->line; line < view->line + view->lines; line++) {
5420                         if (line->type != LINE_DIFF_CHUNK)
5421                                 continue;
5423                         tmp = realloc_items(stage_chunk, &alloc,
5424                                             stage_chunks, sizeof(*tmp));
5425                         if (!tmp) {
5426                                 report("Allocation failure");
5427                                 return;
5428                         }
5430                         stage_chunk = tmp;
5431                         stage_chunk[stage_chunks++] = line - view->line;
5432                 }
5433         }
5435         for (i = 0; i < stage_chunks; i++) {
5436                 if (stage_chunk[i] > view->lineno) {
5437                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5438                         report("Chunk %d of %d", i + 1, stage_chunks);
5439                         return;
5440                 }
5441         }
5443         report("No next chunk found");
5446 static enum request
5447 stage_request(struct view *view, enum request request, struct line *line)
5449         switch (request) {
5450         case REQ_STATUS_UPDATE:
5451                 if (!stage_update(view, line))
5452                         return REQ_NONE;
5453                 break;
5455         case REQ_STATUS_REVERT:
5456                 if (!stage_revert(view, line))
5457                         return REQ_NONE;
5458                 break;
5460         case REQ_STAGE_NEXT:
5461                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5462                         report("File is untracked; press %s to add",
5463                                get_key(REQ_STATUS_UPDATE));
5464                         return REQ_NONE;
5465                 }
5466                 stage_next(view, line);
5467                 return REQ_NONE;
5469         case REQ_EDIT:
5470                 if (!stage_status.new.name[0])
5471                         return request;
5472                 if (stage_status.status == 'D') {
5473                         report("File has been deleted.");
5474                         return REQ_NONE;
5475                 }
5477                 open_editor(stage_status.status != '?', stage_status.new.name);
5478                 break;
5480         case REQ_REFRESH:
5481                 /* Reload everything ... */
5482                 break;
5484         case REQ_VIEW_BLAME:
5485                 if (stage_status.new.name[0]) {
5486                         string_copy(opt_file, stage_status.new.name);
5487                         opt_ref[0] = 0;
5488                 }
5489                 return request;
5491         case REQ_ENTER:
5492                 return pager_request(view, request, line);
5494         default:
5495                 return request;
5496         }
5498         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5499         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5501         /* Check whether the staged entry still exists, and close the
5502          * stage view if it doesn't. */
5503         if (!status_exists(&stage_status, stage_line_type)) {
5504                 status_restore(VIEW(REQ_VIEW_STATUS));
5505                 return REQ_VIEW_CLOSE;
5506         }
5508         if (stage_line_type == LINE_STAT_UNTRACKED) {
5509                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5510                         report("Cannot display a directory");
5511                         return REQ_NONE;
5512                 }
5514                 if (!prepare_update_file(view, stage_status.new.name)) {
5515                         report("Failed to open file: %s", strerror(errno));
5516                         return REQ_NONE;
5517                 }
5518         }
5519         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5521         return REQ_NONE;
5524 static struct view_ops stage_ops = {
5525         "line",
5526         NULL,
5527         NULL,
5528         pager_read,
5529         pager_draw,
5530         stage_request,
5531         pager_grep,
5532         pager_select,
5533 };
5536 /*
5537  * Revision graph
5538  */
5540 struct commit {
5541         char id[SIZEOF_REV];            /* SHA1 ID. */
5542         char title[128];                /* First line of the commit message. */
5543         char author[75];                /* Author of the commit. */
5544         struct tm time;                 /* Date from the author ident. */
5545         struct ref **refs;              /* Repository references. */
5546         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5547         size_t graph_size;              /* The width of the graph array. */
5548         bool has_parents;               /* Rewritten --parents seen. */
5549 };
5551 /* Size of rev graph with no  "padding" columns */
5552 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5554 struct rev_graph {
5555         struct rev_graph *prev, *next, *parents;
5556         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5557         size_t size;
5558         struct commit *commit;
5559         size_t pos;
5560         unsigned int boundary:1;
5561 };
5563 /* Parents of the commit being visualized. */
5564 static struct rev_graph graph_parents[4];
5566 /* The current stack of revisions on the graph. */
5567 static struct rev_graph graph_stacks[4] = {
5568         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5569         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5570         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5571         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5572 };
5574 static inline bool
5575 graph_parent_is_merge(struct rev_graph *graph)
5577         return graph->parents->size > 1;
5580 static inline void
5581 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5583         struct commit *commit = graph->commit;
5585         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5586                 commit->graph[commit->graph_size++] = symbol;
5589 static void
5590 clear_rev_graph(struct rev_graph *graph)
5592         graph->boundary = 0;
5593         graph->size = graph->pos = 0;
5594         graph->commit = NULL;
5595         memset(graph->parents, 0, sizeof(*graph->parents));
5598 static void
5599 done_rev_graph(struct rev_graph *graph)
5601         if (graph_parent_is_merge(graph) &&
5602             graph->pos < graph->size - 1 &&
5603             graph->next->size == graph->size + graph->parents->size - 1) {
5604                 size_t i = graph->pos + graph->parents->size - 1;
5606                 graph->commit->graph_size = i * 2;
5607                 while (i < graph->next->size - 1) {
5608                         append_to_rev_graph(graph, ' ');
5609                         append_to_rev_graph(graph, '\\');
5610                         i++;
5611                 }
5612         }
5614         clear_rev_graph(graph);
5617 static void
5618 push_rev_graph(struct rev_graph *graph, const char *parent)
5620         int i;
5622         /* "Collapse" duplicate parents lines.
5623          *
5624          * FIXME: This needs to also update update the drawn graph but
5625          * for now it just serves as a method for pruning graph lines. */
5626         for (i = 0; i < graph->size; i++)
5627                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5628                         return;
5630         if (graph->size < SIZEOF_REVITEMS) {
5631                 string_copy_rev(graph->rev[graph->size++], parent);
5632         }
5635 static chtype
5636 get_rev_graph_symbol(struct rev_graph *graph)
5638         chtype symbol;
5640         if (graph->boundary)
5641                 symbol = REVGRAPH_BOUND;
5642         else if (graph->parents->size == 0)
5643                 symbol = REVGRAPH_INIT;
5644         else if (graph_parent_is_merge(graph))
5645                 symbol = REVGRAPH_MERGE;
5646         else if (graph->pos >= graph->size)
5647                 symbol = REVGRAPH_BRANCH;
5648         else
5649                 symbol = REVGRAPH_COMMIT;
5651         return symbol;
5654 static void
5655 draw_rev_graph(struct rev_graph *graph)
5657         struct rev_filler {
5658                 chtype separator, line;
5659         };
5660         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5661         static struct rev_filler fillers[] = {
5662                 { ' ',  '|' },
5663                 { '`',  '.' },
5664                 { '\'', ' ' },
5665                 { '/',  ' ' },
5666         };
5667         chtype symbol = get_rev_graph_symbol(graph);
5668         struct rev_filler *filler;
5669         size_t i;
5671         if (opt_line_graphics)
5672                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5674         filler = &fillers[DEFAULT];
5676         for (i = 0; i < graph->pos; i++) {
5677                 append_to_rev_graph(graph, filler->line);
5678                 if (graph_parent_is_merge(graph->prev) &&
5679                     graph->prev->pos == i)
5680                         filler = &fillers[RSHARP];
5682                 append_to_rev_graph(graph, filler->separator);
5683         }
5685         /* Place the symbol for this revision. */
5686         append_to_rev_graph(graph, symbol);
5688         if (graph->prev->size > graph->size)
5689                 filler = &fillers[RDIAG];
5690         else
5691                 filler = &fillers[DEFAULT];
5693         i++;
5695         for (; i < graph->size; i++) {
5696                 append_to_rev_graph(graph, filler->separator);
5697                 append_to_rev_graph(graph, filler->line);
5698                 if (graph_parent_is_merge(graph->prev) &&
5699                     i < graph->prev->pos + graph->parents->size)
5700                         filler = &fillers[RSHARP];
5701                 if (graph->prev->size > graph->size)
5702                         filler = &fillers[LDIAG];
5703         }
5705         if (graph->prev->size > graph->size) {
5706                 append_to_rev_graph(graph, filler->separator);
5707                 if (filler->line != ' ')
5708                         append_to_rev_graph(graph, filler->line);
5709         }
5712 /* Prepare the next rev graph */
5713 static void
5714 prepare_rev_graph(struct rev_graph *graph)
5716         size_t i;
5718         /* First, traverse all lines of revisions up to the active one. */
5719         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5720                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5721                         break;
5723                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5724         }
5726         /* Interleave the new revision parent(s). */
5727         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5728                 push_rev_graph(graph->next, graph->parents->rev[i]);
5730         /* Lastly, put any remaining revisions. */
5731         for (i = graph->pos + 1; i < graph->size; i++)
5732                 push_rev_graph(graph->next, graph->rev[i]);
5735 static void
5736 update_rev_graph(struct view *view, struct rev_graph *graph)
5738         /* If this is the finalizing update ... */
5739         if (graph->commit)
5740                 prepare_rev_graph(graph);
5742         /* Graph visualization needs a one rev look-ahead,
5743          * so the first update doesn't visualize anything. */
5744         if (!graph->prev->commit)
5745                 return;
5747         if (view->lines > 2)
5748                 view->line[view->lines - 3].dirty = 1;
5749         if (view->lines > 1)
5750                 view->line[view->lines - 2].dirty = 1;
5751         draw_rev_graph(graph->prev);
5752         done_rev_graph(graph->prev->prev);
5756 /*
5757  * Main view backend
5758  */
5760 static const char *main_argv[SIZEOF_ARG] = {
5761         "git", "log", "--no-color", "--pretty=raw", "--parents",
5762                       "--topo-order", "%(head)", NULL
5763 };
5765 static bool
5766 main_draw(struct view *view, struct line *line, unsigned int lineno)
5768         struct commit *commit = line->data;
5770         if (!*commit->author)
5771                 return FALSE;
5773         if (opt_date && draw_date(view, &commit->time))
5774                 return TRUE;
5776         if (opt_author && draw_author(view, commit->author))
5777                 return TRUE;
5779         if (opt_rev_graph && commit->graph_size &&
5780             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5781                 return TRUE;
5783         if (opt_show_refs && commit->refs) {
5784                 size_t i = 0;
5786                 do {
5787                         enum line_type type;
5789                         if (commit->refs[i]->head)
5790                                 type = LINE_MAIN_HEAD;
5791                         else if (commit->refs[i]->ltag)
5792                                 type = LINE_MAIN_LOCAL_TAG;
5793                         else if (commit->refs[i]->tag)
5794                                 type = LINE_MAIN_TAG;
5795                         else if (commit->refs[i]->tracked)
5796                                 type = LINE_MAIN_TRACKED;
5797                         else if (commit->refs[i]->remote)
5798                                 type = LINE_MAIN_REMOTE;
5799                         else
5800                                 type = LINE_MAIN_REF;
5802                         if (draw_text(view, type, "[", TRUE) ||
5803                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5804                             draw_text(view, type, "]", TRUE))
5805                                 return TRUE;
5807                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5808                                 return TRUE;
5809                 } while (commit->refs[i++]->next);
5810         }
5812         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5813         return TRUE;
5816 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5817 static bool
5818 main_read(struct view *view, char *line)
5820         static struct rev_graph *graph = graph_stacks;
5821         enum line_type type;
5822         struct commit *commit;
5824         if (!line) {
5825                 int i;
5827                 if (!view->lines && !view->parent)
5828                         die("No revisions match the given arguments.");
5829                 if (view->lines > 0) {
5830                         commit = view->line[view->lines - 1].data;
5831                         view->line[view->lines - 1].dirty = 1;
5832                         if (!*commit->author) {
5833                                 view->lines--;
5834                                 free(commit);
5835                                 graph->commit = NULL;
5836                         }
5837                 }
5838                 update_rev_graph(view, graph);
5840                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5841                         clear_rev_graph(&graph_stacks[i]);
5842                 return TRUE;
5843         }
5845         type = get_line_type(line);
5846         if (type == LINE_COMMIT) {
5847                 commit = calloc(1, sizeof(struct commit));
5848                 if (!commit)
5849                         return FALSE;
5851                 line += STRING_SIZE("commit ");
5852                 if (*line == '-') {
5853                         graph->boundary = 1;
5854                         line++;
5855                 }
5857                 string_copy_rev(commit->id, line);
5858                 commit->refs = get_refs(commit->id);
5859                 graph->commit = commit;
5860                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5862                 while ((line = strchr(line, ' '))) {
5863                         line++;
5864                         push_rev_graph(graph->parents, line);
5865                         commit->has_parents = TRUE;
5866                 }
5867                 return TRUE;
5868         }
5870         if (!view->lines)
5871                 return TRUE;
5872         commit = view->line[view->lines - 1].data;
5874         switch (type) {
5875         case LINE_PARENT:
5876                 if (commit->has_parents)
5877                         break;
5878                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5879                 break;
5881         case LINE_AUTHOR:
5882                 parse_author_line(line + STRING_SIZE("author "),
5883                                   commit->author, sizeof(commit->author),
5884                                   &commit->time);
5885                 update_rev_graph(view, graph);
5886                 graph = graph->next;
5887                 break;
5889         default:
5890                 /* Fill in the commit title if it has not already been set. */
5891                 if (commit->title[0])
5892                         break;
5894                 /* Require titles to start with a non-space character at the
5895                  * offset used by git log. */
5896                 if (strncmp(line, "    ", 4))
5897                         break;
5898                 line += 4;
5899                 /* Well, if the title starts with a whitespace character,
5900                  * try to be forgiving.  Otherwise we end up with no title. */
5901                 while (isspace(*line))
5902                         line++;
5903                 if (*line == '\0')
5904                         break;
5905                 /* FIXME: More graceful handling of titles; append "..." to
5906                  * shortened titles, etc. */
5908                 string_expand(commit->title, sizeof(commit->title), line, 1);
5909                 view->line[view->lines - 1].dirty = 1;
5910         }
5912         return TRUE;
5915 static enum request
5916 main_request(struct view *view, enum request request, struct line *line)
5918         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5920         switch (request) {
5921         case REQ_ENTER:
5922                 open_view(view, REQ_VIEW_DIFF, flags);
5923                 break;
5924         case REQ_REFRESH:
5925                 load_refs();
5926                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5927                 break;
5928         default:
5929                 return request;
5930         }
5932         return REQ_NONE;
5935 static bool
5936 grep_refs(struct ref **refs, regex_t *regex)
5938         regmatch_t pmatch;
5939         size_t i = 0;
5941         if (!refs)
5942                 return FALSE;
5943         do {
5944                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5945                         return TRUE;
5946         } while (refs[i++]->next);
5948         return FALSE;
5951 static bool
5952 main_grep(struct view *view, struct line *line)
5954         struct commit *commit = line->data;
5955         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5956         char buf[DATE_COLS + 1];
5957         regmatch_t pmatch;
5959         for (state = S_TITLE; state < S_END; state++) {
5960                 char *text;
5962                 switch (state) {
5963                 case S_TITLE:   text = commit->title;   break;
5964                 case S_AUTHOR:
5965                         if (!opt_author)
5966                                 continue;
5967                         text = commit->author;
5968                         break;
5969                 case S_DATE:
5970                         if (!opt_date)
5971                                 continue;
5972                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5973                                 continue;
5974                         text = buf;
5975                         break;
5976                 case S_REFS:
5977                         if (!opt_show_refs)
5978                                 continue;
5979                         if (grep_refs(commit->refs, view->regex) == TRUE)
5980                                 return TRUE;
5981                         continue;
5982                 default:
5983                         return FALSE;
5984                 }
5986                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5987                         return TRUE;
5988         }
5990         return FALSE;
5993 static void
5994 main_select(struct view *view, struct line *line)
5996         struct commit *commit = line->data;
5998         string_copy_rev(view->ref, commit->id);
5999         string_copy_rev(ref_commit, view->ref);
6002 static struct view_ops main_ops = {
6003         "commit",
6004         main_argv,
6005         NULL,
6006         main_read,
6007         main_draw,
6008         main_request,
6009         main_grep,
6010         main_select,
6011 };
6014 /*
6015  * Unicode / UTF-8 handling
6016  *
6017  * NOTE: Much of the following code for dealing with Unicode is derived from
6018  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6019  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6020  */
6022 static inline int
6023 unicode_width(unsigned long c)
6025         if (c >= 0x1100 &&
6026            (c <= 0x115f                         /* Hangul Jamo */
6027             || c == 0x2329
6028             || c == 0x232a
6029             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6030                                                 /* CJK ... Yi */
6031             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6032             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6033             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6034             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6035             || (c >= 0xffe0  && c <= 0xffe6)
6036             || (c >= 0x20000 && c <= 0x2fffd)
6037             || (c >= 0x30000 && c <= 0x3fffd)))
6038                 return 2;
6040         if (c == '\t')
6041                 return opt_tab_size;
6043         return 1;
6046 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6047  * Illegal bytes are set one. */
6048 static const unsigned char utf8_bytes[256] = {
6049         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,
6050         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,
6051         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,
6052         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,
6053         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,
6054         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,
6055         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,
6056         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,
6057 };
6059 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6060 static inline unsigned long
6061 utf8_to_unicode(const char *string, size_t length)
6063         unsigned long unicode;
6065         switch (length) {
6066         case 1:
6067                 unicode  =   string[0];
6068                 break;
6069         case 2:
6070                 unicode  =  (string[0] & 0x1f) << 6;
6071                 unicode +=  (string[1] & 0x3f);
6072                 break;
6073         case 3:
6074                 unicode  =  (string[0] & 0x0f) << 12;
6075                 unicode += ((string[1] & 0x3f) << 6);
6076                 unicode +=  (string[2] & 0x3f);
6077                 break;
6078         case 4:
6079                 unicode  =  (string[0] & 0x0f) << 18;
6080                 unicode += ((string[1] & 0x3f) << 12);
6081                 unicode += ((string[2] & 0x3f) << 6);
6082                 unicode +=  (string[3] & 0x3f);
6083                 break;
6084         case 5:
6085                 unicode  =  (string[0] & 0x0f) << 24;
6086                 unicode += ((string[1] & 0x3f) << 18);
6087                 unicode += ((string[2] & 0x3f) << 12);
6088                 unicode += ((string[3] & 0x3f) << 6);
6089                 unicode +=  (string[4] & 0x3f);
6090                 break;
6091         case 6:
6092                 unicode  =  (string[0] & 0x01) << 30;
6093                 unicode += ((string[1] & 0x3f) << 24);
6094                 unicode += ((string[2] & 0x3f) << 18);
6095                 unicode += ((string[3] & 0x3f) << 12);
6096                 unicode += ((string[4] & 0x3f) << 6);
6097                 unicode +=  (string[5] & 0x3f);
6098                 break;
6099         default:
6100                 die("Invalid Unicode length");
6101         }
6103         /* Invalid characters could return the special 0xfffd value but NUL
6104          * should be just as good. */
6105         return unicode > 0xffff ? 0 : unicode;
6108 /* Calculates how much of string can be shown within the given maximum width
6109  * and sets trimmed parameter to non-zero value if all of string could not be
6110  * shown. If the reserve flag is TRUE, it will reserve at least one
6111  * trailing character, which can be useful when drawing a delimiter.
6112  *
6113  * Returns the number of bytes to output from string to satisfy max_width. */
6114 static size_t
6115 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6117         const char *string = *start;
6118         const char *end = strchr(string, '\0');
6119         unsigned char last_bytes = 0;
6120         size_t last_ucwidth = 0;
6122         *width = 0;
6123         *trimmed = 0;
6125         while (string < end) {
6126                 int c = *(unsigned char *) string;
6127                 unsigned char bytes = utf8_bytes[c];
6128                 size_t ucwidth;
6129                 unsigned long unicode;
6131                 if (string + bytes > end)
6132                         break;
6134                 /* Change representation to figure out whether
6135                  * it is a single- or double-width character. */
6137                 unicode = utf8_to_unicode(string, bytes);
6138                 /* FIXME: Graceful handling of invalid Unicode character. */
6139                 if (!unicode)
6140                         break;
6142                 ucwidth = unicode_width(unicode);
6143                 if (skip > 0) {
6144                         skip -= ucwidth <= skip ? ucwidth : skip;
6145                         *start += bytes;
6146                 }
6147                 *width  += ucwidth;
6148                 if (*width > max_width) {
6149                         *trimmed = 1;
6150                         *width -= ucwidth;
6151                         if (reserve && *width == max_width) {
6152                                 string -= last_bytes;
6153                                 *width -= last_ucwidth;
6154                         }
6155                         break;
6156                 }
6158                 string  += bytes;
6159                 last_bytes = ucwidth ? bytes : 0;
6160                 last_ucwidth = ucwidth;
6161         }
6163         return string - *start;
6167 /*
6168  * Status management
6169  */
6171 /* Whether or not the curses interface has been initialized. */
6172 static bool cursed = FALSE;
6174 /* Terminal hacks and workarounds. */
6175 static bool use_scroll_redrawwin;
6176 static bool use_scroll_status_wclear;
6178 /* The status window is used for polling keystrokes. */
6179 static WINDOW *status_win;
6181 /* Reading from the prompt? */
6182 static bool input_mode = FALSE;
6184 static bool status_empty = FALSE;
6186 /* Update status and title window. */
6187 static void
6188 report(const char *msg, ...)
6190         struct view *view = display[current_view];
6192         if (input_mode)
6193                 return;
6195         if (!view) {
6196                 char buf[SIZEOF_STR];
6197                 va_list args;
6199                 va_start(args, msg);
6200                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6201                         buf[sizeof(buf) - 1] = 0;
6202                         buf[sizeof(buf) - 2] = '.';
6203                         buf[sizeof(buf) - 3] = '.';
6204                         buf[sizeof(buf) - 4] = '.';
6205                 }
6206                 va_end(args);
6207                 die("%s", buf);
6208         }
6210         if (!status_empty || *msg) {
6211                 va_list args;
6213                 va_start(args, msg);
6215                 wmove(status_win, 0, 0);
6216                 if (view->has_scrolled && use_scroll_status_wclear)
6217                         wclear(status_win);
6218                 if (*msg) {
6219                         vwprintw(status_win, msg, args);
6220                         status_empty = FALSE;
6221                 } else {
6222                         status_empty = TRUE;
6223                 }
6224                 wclrtoeol(status_win);
6225                 wnoutrefresh(status_win);
6227                 va_end(args);
6228         }
6230         update_view_title(view);
6233 /* Controls when nodelay should be in effect when polling user input. */
6234 static void
6235 set_nonblocking_input(bool loading)
6237         static unsigned int loading_views;
6239         if ((loading == FALSE && loading_views-- == 1) ||
6240             (loading == TRUE  && loading_views++ == 0))
6241                 nodelay(status_win, loading);
6244 static void
6245 init_display(void)
6247         const char *term;
6248         int x, y;
6250         /* Initialize the curses library */
6251         if (isatty(STDIN_FILENO)) {
6252                 cursed = !!initscr();
6253                 opt_tty = stdin;
6254         } else {
6255                 /* Leave stdin and stdout alone when acting as a pager. */
6256                 opt_tty = fopen("/dev/tty", "r+");
6257                 if (!opt_tty)
6258                         die("Failed to open /dev/tty");
6259                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6260         }
6262         if (!cursed)
6263                 die("Failed to initialize curses");
6265         nonl();         /* Disable conversion and detect newlines from input. */
6266         cbreak();       /* Take input chars one at a time, no wait for \n */
6267         noecho();       /* Don't echo input */
6268         leaveok(stdscr, FALSE);
6270         if (has_colors())
6271                 init_colors();
6273         getmaxyx(stdscr, y, x);
6274         status_win = newwin(1, 0, y - 1, 0);
6275         if (!status_win)
6276                 die("Failed to create status window");
6278         /* Enable keyboard mapping */
6279         keypad(status_win, TRUE);
6280         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6282         TABSIZE = opt_tab_size;
6283         if (opt_line_graphics) {
6284                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6285         }
6287         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6288         if (term && !strcmp(term, "gnome-terminal")) {
6289                 /* In the gnome-terminal-emulator, the message from
6290                  * scrolling up one line when impossible followed by
6291                  * scrolling down one line causes corruption of the
6292                  * status line. This is fixed by calling wclear. */
6293                 use_scroll_status_wclear = TRUE;
6294                 use_scroll_redrawwin = FALSE;
6296         } else if (term && !strcmp(term, "xrvt-xpm")) {
6297                 /* No problems with full optimizations in xrvt-(unicode)
6298                  * and aterm. */
6299                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6301         } else {
6302                 /* When scrolling in (u)xterm the last line in the
6303                  * scrolling direction will update slowly. */
6304                 use_scroll_redrawwin = TRUE;
6305                 use_scroll_status_wclear = FALSE;
6306         }
6309 static int
6310 get_input(int prompt_position)
6312         struct view *view;
6313         int i, key, cursor_y, cursor_x;
6315         if (prompt_position)
6316                 input_mode = TRUE;
6318         while (TRUE) {
6319                 foreach_view (view, i) {
6320                         update_view(view);
6321                         if (view_is_displayed(view) && view->has_scrolled &&
6322                             use_scroll_redrawwin)
6323                                 redrawwin(view->win);
6324                         view->has_scrolled = FALSE;
6325                 }
6327                 /* Update the cursor position. */
6328                 if (prompt_position) {
6329                         getbegyx(status_win, cursor_y, cursor_x);
6330                         cursor_x = prompt_position;
6331                 } else {
6332                         view = display[current_view];
6333                         getbegyx(view->win, cursor_y, cursor_x);
6334                         cursor_x = view->width - 1;
6335                         cursor_y += view->lineno - view->offset;
6336                 }
6337                 setsyx(cursor_y, cursor_x);
6339                 /* Refresh, accept single keystroke of input */
6340                 doupdate();
6341                 key = wgetch(status_win);
6343                 /* wgetch() with nodelay() enabled returns ERR when
6344                  * there's no input. */
6345                 if (key == ERR) {
6347                 } else if (key == KEY_RESIZE) {
6348                         int height, width;
6350                         getmaxyx(stdscr, height, width);
6352                         wresize(status_win, 1, width);
6353                         mvwin(status_win, height - 1, 0);
6354                         wnoutrefresh(status_win);
6355                         resize_display();
6356                         redraw_display(TRUE);
6358                 } else {
6359                         input_mode = FALSE;
6360                         return key;
6361                 }
6362         }
6365 static char *
6366 prompt_input(const char *prompt, input_handler handler, void *data)
6368         enum input_status status = INPUT_OK;
6369         static char buf[SIZEOF_STR];
6370         size_t pos = 0;
6372         buf[pos] = 0;
6374         while (status == INPUT_OK || status == INPUT_SKIP) {
6375                 int key;
6377                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6378                 wclrtoeol(status_win);
6380                 key = get_input(pos + 1);
6381                 switch (key) {
6382                 case KEY_RETURN:
6383                 case KEY_ENTER:
6384                 case '\n':
6385                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6386                         break;
6388                 case KEY_BACKSPACE:
6389                         if (pos > 0)
6390                                 buf[--pos] = 0;
6391                         else
6392                                 status = INPUT_CANCEL;
6393                         break;
6395                 case KEY_ESC:
6396                         status = INPUT_CANCEL;
6397                         break;
6399                 default:
6400                         if (pos >= sizeof(buf)) {
6401                                 report("Input string too long");
6402                                 return NULL;
6403                         }
6405                         status = handler(data, buf, key);
6406                         if (status == INPUT_OK)
6407                                 buf[pos++] = (char) key;
6408                 }
6409         }
6411         /* Clear the status window */
6412         status_empty = FALSE;
6413         report("");
6415         if (status == INPUT_CANCEL)
6416                 return NULL;
6418         buf[pos++] = 0;
6420         return buf;
6423 static enum input_status
6424 prompt_yesno_handler(void *data, char *buf, int c)
6426         if (c == 'y' || c == 'Y')
6427                 return INPUT_STOP;
6428         if (c == 'n' || c == 'N')
6429                 return INPUT_CANCEL;
6430         return INPUT_SKIP;
6433 static bool
6434 prompt_yesno(const char *prompt)
6436         char prompt2[SIZEOF_STR];
6438         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6439                 return FALSE;
6441         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6444 static enum input_status
6445 read_prompt_handler(void *data, char *buf, int c)
6447         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6450 static char *
6451 read_prompt(const char *prompt)
6453         return prompt_input(prompt, read_prompt_handler, NULL);
6456 /*
6457  * Repository properties
6458  */
6460 static struct ref *refs = NULL;
6461 static size_t refs_alloc = 0;
6462 static size_t refs_size = 0;
6464 /* Id <-> ref store */
6465 static struct ref ***id_refs = NULL;
6466 static size_t id_refs_alloc = 0;
6467 static size_t id_refs_size = 0;
6469 static int
6470 compare_refs(const void *ref1_, const void *ref2_)
6472         const struct ref *ref1 = *(const struct ref **)ref1_;
6473         const struct ref *ref2 = *(const struct ref **)ref2_;
6475         if (ref1->tag != ref2->tag)
6476                 return ref2->tag - ref1->tag;
6477         if (ref1->ltag != ref2->ltag)
6478                 return ref2->ltag - ref2->ltag;
6479         if (ref1->head != ref2->head)
6480                 return ref2->head - ref1->head;
6481         if (ref1->tracked != ref2->tracked)
6482                 return ref2->tracked - ref1->tracked;
6483         if (ref1->remote != ref2->remote)
6484                 return ref2->remote - ref1->remote;
6485         return strcmp(ref1->name, ref2->name);
6488 static struct ref **
6489 get_refs(const char *id)
6491         struct ref ***tmp_id_refs;
6492         struct ref **ref_list = NULL;
6493         size_t ref_list_alloc = 0;
6494         size_t ref_list_size = 0;
6495         size_t i;
6497         for (i = 0; i < id_refs_size; i++)
6498                 if (!strcmp(id, id_refs[i][0]->id))
6499                         return id_refs[i];
6501         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6502                                     sizeof(*id_refs));
6503         if (!tmp_id_refs)
6504                 return NULL;
6506         id_refs = tmp_id_refs;
6508         for (i = 0; i < refs_size; i++) {
6509                 struct ref **tmp;
6511                 if (strcmp(id, refs[i].id))
6512                         continue;
6514                 tmp = realloc_items(ref_list, &ref_list_alloc,
6515                                     ref_list_size + 1, sizeof(*ref_list));
6516                 if (!tmp) {
6517                         if (ref_list)
6518                                 free(ref_list);
6519                         return NULL;
6520                 }
6522                 ref_list = tmp;
6523                 ref_list[ref_list_size] = &refs[i];
6524                 /* XXX: The properties of the commit chains ensures that we can
6525                  * safely modify the shared ref. The repo references will
6526                  * always be similar for the same id. */
6527                 ref_list[ref_list_size]->next = 1;
6529                 ref_list_size++;
6530         }
6532         if (ref_list) {
6533                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6534                 ref_list[ref_list_size - 1]->next = 0;
6535                 id_refs[id_refs_size++] = ref_list;
6536         }
6538         return ref_list;
6541 static int
6542 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6544         struct ref *ref;
6545         bool tag = FALSE;
6546         bool ltag = FALSE;
6547         bool remote = FALSE;
6548         bool tracked = FALSE;
6549         bool check_replace = FALSE;
6550         bool head = FALSE;
6552         if (!prefixcmp(name, "refs/tags/")) {
6553                 if (!suffixcmp(name, namelen, "^{}")) {
6554                         namelen -= 3;
6555                         name[namelen] = 0;
6556                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6557                                 check_replace = TRUE;
6558                 } else {
6559                         ltag = TRUE;
6560                 }
6562                 tag = TRUE;
6563                 namelen -= STRING_SIZE("refs/tags/");
6564                 name    += STRING_SIZE("refs/tags/");
6566         } else if (!prefixcmp(name, "refs/remotes/")) {
6567                 remote = TRUE;
6568                 namelen -= STRING_SIZE("refs/remotes/");
6569                 name    += STRING_SIZE("refs/remotes/");
6570                 tracked  = !strcmp(opt_remote, name);
6572         } else if (!prefixcmp(name, "refs/heads/")) {
6573                 namelen -= STRING_SIZE("refs/heads/");
6574                 name    += STRING_SIZE("refs/heads/");
6575                 head     = !strncmp(opt_head, name, namelen);
6577         } else if (!strcmp(name, "HEAD")) {
6578                 string_ncopy(opt_head_rev, id, idlen);
6579                 return OK;
6580         }
6582         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6583                 /* it's an annotated tag, replace the previous SHA1 with the
6584                  * resolved commit id; relies on the fact git-ls-remote lists
6585                  * the commit id of an annotated tag right before the commit id
6586                  * it points to. */
6587                 refs[refs_size - 1].ltag = ltag;
6588                 string_copy_rev(refs[refs_size - 1].id, id);
6590                 return OK;
6591         }
6592         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6593         if (!refs)
6594                 return ERR;
6596         ref = &refs[refs_size++];
6597         ref->name = malloc(namelen + 1);
6598         if (!ref->name)
6599                 return ERR;
6601         strncpy(ref->name, name, namelen);
6602         ref->name[namelen] = 0;
6603         ref->head = head;
6604         ref->tag = tag;
6605         ref->ltag = ltag;
6606         ref->remote = remote;
6607         ref->tracked = tracked;
6608         string_copy_rev(ref->id, id);
6610         return OK;
6613 static int
6614 load_refs(void)
6616         static const char *ls_remote_argv[SIZEOF_ARG] = {
6617                 "git", "ls-remote", ".", NULL
6618         };
6619         static bool init = FALSE;
6621         if (!init) {
6622                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6623                 init = TRUE;
6624         }
6626         if (!*opt_git_dir)
6627                 return OK;
6629         while (refs_size > 0)
6630                 free(refs[--refs_size].name);
6631         while (id_refs_size > 0)
6632                 free(id_refs[--id_refs_size]);
6634         return run_io_load(ls_remote_argv, "\t", read_ref);
6637 static void
6638 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6640         const char *argv[SIZEOF_ARG] = { name, "=" };
6641         int argc = 1 + (cmd == option_set_command);
6642         int error = ERR;
6644         if (!argv_from_string(argv, &argc, value))
6645                 config_msg = "Too many option arguments";
6646         else
6647                 error = cmd(argc, argv);
6649         if (error == ERR)
6650                 warn("Option 'tig.%s': %s", name, config_msg);
6653 static int
6654 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6656         if (!strcmp(name, "i18n.commitencoding"))
6657                 string_ncopy(opt_encoding, value, valuelen);
6659         if (!strcmp(name, "core.editor"))
6660                 string_ncopy(opt_editor, value, valuelen);
6662         if (!prefixcmp(name, "tig.color."))
6663                 set_repo_config_option(name + 10, value, option_color_command);
6665         else if (!prefixcmp(name, "tig.bind."))
6666                 set_repo_config_option(name + 9, value, option_bind_command);
6668         else if (!prefixcmp(name, "tig."))
6669                 set_repo_config_option(name + 4, value, option_set_command);
6671         /* branch.<head>.remote */
6672         if (*opt_head &&
6673             !strncmp(name, "branch.", 7) &&
6674             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6675             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6676                 string_ncopy(opt_remote, value, valuelen);
6678         if (*opt_head && *opt_remote &&
6679             !strncmp(name, "branch.", 7) &&
6680             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6681             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6682                 size_t from = strlen(opt_remote);
6684                 if (!prefixcmp(value, "refs/heads/")) {
6685                         value += STRING_SIZE("refs/heads/");
6686                         valuelen -= STRING_SIZE("refs/heads/");
6687                 }
6689                 if (!string_format_from(opt_remote, &from, "/%s", value))
6690                         opt_remote[0] = 0;
6691         }
6693         return OK;
6696 static int
6697 load_git_config(void)
6699         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6701         return run_io_load(config_list_argv, "=", read_repo_config_option);
6704 static int
6705 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6707         if (!opt_git_dir[0]) {
6708                 string_ncopy(opt_git_dir, name, namelen);
6710         } else if (opt_is_inside_work_tree == -1) {
6711                 /* This can be 3 different values depending on the
6712                  * version of git being used. If git-rev-parse does not
6713                  * understand --is-inside-work-tree it will simply echo
6714                  * the option else either "true" or "false" is printed.
6715                  * Default to true for the unknown case. */
6716                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6718         } else if (*name == '.') {
6719                 string_ncopy(opt_cdup, name, namelen);
6721         } else {
6722                 string_ncopy(opt_prefix, name, namelen);
6723         }
6725         return OK;
6728 static int
6729 load_repo_info(void)
6731         const char *head_argv[] = {
6732                 "git", "symbolic-ref", "HEAD", NULL
6733         };
6734         const char *rev_parse_argv[] = {
6735                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6736                         "--show-cdup", "--show-prefix", NULL
6737         };
6739         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6740                 chomp_string(opt_head);
6741                 if (!prefixcmp(opt_head, "refs/heads/")) {
6742                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6744                         memmove(opt_head, offset, strlen(offset) + 1);
6745                 }
6746         }
6748         return run_io_load(rev_parse_argv, "=", read_repo_info);
6752 /*
6753  * Main
6754  */
6756 static const char usage[] =
6757 "tig " TIG_VERSION " (" __DATE__ ")\n"
6758 "\n"
6759 "Usage: tig        [options] [revs] [--] [paths]\n"
6760 "   or: tig show   [options] [revs] [--] [paths]\n"
6761 "   or: tig blame  [rev] path\n"
6762 "   or: tig status\n"
6763 "   or: tig <      [git command output]\n"
6764 "\n"
6765 "Options:\n"
6766 "  -v, --version   Show version and exit\n"
6767 "  -h, --help      Show help message and exit";
6769 static void __NORETURN
6770 quit(int sig)
6772         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6773         if (cursed)
6774                 endwin();
6775         exit(0);
6778 static void __NORETURN
6779 die(const char *err, ...)
6781         va_list args;
6783         endwin();
6785         va_start(args, err);
6786         fputs("tig: ", stderr);
6787         vfprintf(stderr, err, args);
6788         fputs("\n", stderr);
6789         va_end(args);
6791         exit(1);
6794 static void
6795 warn(const char *msg, ...)
6797         va_list args;
6799         va_start(args, msg);
6800         fputs("tig warning: ", stderr);
6801         vfprintf(stderr, msg, args);
6802         fputs("\n", stderr);
6803         va_end(args);
6806 static enum request
6807 parse_options(int argc, const char *argv[])
6809         enum request request = REQ_VIEW_MAIN;
6810         const char *subcommand;
6811         bool seen_dashdash = FALSE;
6812         /* XXX: This is vulnerable to the user overriding options
6813          * required for the main view parser. */
6814         const char *custom_argv[SIZEOF_ARG] = {
6815                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6816                         "--topo-order", NULL
6817         };
6818         int i, j = 6;
6820         if (!isatty(STDIN_FILENO)) {
6821                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6822                 return REQ_VIEW_PAGER;
6823         }
6825         if (argc <= 1)
6826                 return REQ_NONE;
6828         subcommand = argv[1];
6829         if (!strcmp(subcommand, "status")) {
6830                 if (argc > 2)
6831                         warn("ignoring arguments after `%s'", subcommand);
6832                 return REQ_VIEW_STATUS;
6834         } else if (!strcmp(subcommand, "blame")) {
6835                 if (argc <= 2 || argc > 4)
6836                         die("invalid number of options to blame\n\n%s", usage);
6838                 i = 2;
6839                 if (argc == 4) {
6840                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6841                         i++;
6842                 }
6844                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6845                 return REQ_VIEW_BLAME;
6847         } else if (!strcmp(subcommand, "show")) {
6848                 request = REQ_VIEW_DIFF;
6850         } else {
6851                 subcommand = NULL;
6852         }
6854         if (subcommand) {
6855                 custom_argv[1] = subcommand;
6856                 j = 2;
6857         }
6859         for (i = 1 + !!subcommand; i < argc; i++) {
6860                 const char *opt = argv[i];
6862                 if (seen_dashdash || !strcmp(opt, "--")) {
6863                         seen_dashdash = TRUE;
6865                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6866                         printf("tig version %s\n", TIG_VERSION);
6867                         quit(0);
6869                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6870                         printf("%s\n", usage);
6871                         quit(0);
6872                 }
6874                 custom_argv[j++] = opt;
6875                 if (j >= ARRAY_SIZE(custom_argv))
6876                         die("command too long");
6877         }
6879         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6880                 die("Failed to format arguments"); 
6882         return request;
6885 int
6886 main(int argc, const char *argv[])
6888         enum request request = parse_options(argc, argv);
6889         struct view *view;
6890         size_t i;
6892         signal(SIGINT, quit);
6894         if (setlocale(LC_ALL, "")) {
6895                 char *codeset = nl_langinfo(CODESET);
6897                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6898         }
6900         if (load_repo_info() == ERR)
6901                 die("Failed to load repo info.");
6903         if (load_options() == ERR)
6904                 die("Failed to load user config.");
6906         if (load_git_config() == ERR)
6907                 die("Failed to load repo config.");
6909         /* Require a git repository unless when running in pager mode. */
6910         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6911                 die("Not a git repository");
6913         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6914                 opt_utf8 = FALSE;
6916         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6917                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6918                 if (opt_iconv == ICONV_NONE)
6919                         die("Failed to initialize character set conversion");
6920         }
6922         if (load_refs() == ERR)
6923                 die("Failed to load refs.");
6925         foreach_view (view, i)
6926                 argv_from_env(view->ops->argv, view->cmd_env);
6928         init_display();
6930         if (request != REQ_NONE)
6931                 open_view(NULL, request, OPEN_PREPARED);
6932         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6934         while (view_driver(display[current_view], request)) {
6935                 int key = get_input(0);
6937                 view = display[current_view];
6938                 request = get_keybinding(view->keymap, key);
6940                 /* Some low-level request handling. This keeps access to
6941                  * status_win restricted. */
6942                 switch (request) {
6943                 case REQ_PROMPT:
6944                 {
6945                         char *cmd = read_prompt(":");
6947                         if (cmd && isdigit(*cmd)) {
6948                                 int lineno = view->lineno + 1;
6950                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
6951                                         select_view_line(view, lineno - 1);
6952                                         report("");
6953                                 } else {
6954                                         report("Unable to parse '%s' as a line number", cmd);
6955                                 }
6957                         } else if (cmd) {
6958                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6959                                 const char *argv[SIZEOF_ARG] = { "git" };
6960                                 int argc = 1;
6962                                 /* When running random commands, initially show the
6963                                  * command in the title. However, it maybe later be
6964                                  * overwritten if a commit line is selected. */
6965                                 string_ncopy(next->ref, cmd, strlen(cmd));
6967                                 if (!argv_from_string(argv, &argc, cmd)) {
6968                                         report("Too many arguments");
6969                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6970                                         report("Failed to format command");
6971                                 } else {
6972                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6973                                 }
6974                         }
6976                         request = REQ_NONE;
6977                         break;
6978                 }
6979                 case REQ_SEARCH:
6980                 case REQ_SEARCH_BACK:
6981                 {
6982                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6983                         char *search = read_prompt(prompt);
6985                         if (search)
6986                                 string_ncopy(opt_search, search, strlen(search));
6987                         else if (*opt_search)
6988                                 request = request == REQ_SEARCH ?
6989                                         REQ_FIND_NEXT :
6990                                         REQ_FIND_PREV;
6991                         else
6992                                 request = REQ_NONE;
6993                         break;
6994                 }
6995                 default:
6996                         break;
6997                 }
6998         }
7000         quit(0);
7002         return 0;