Code

Introduce common view position update helper
[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 static bool
2263 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2265         if (lineno >= view->lines)
2266                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2268         if (offset > lineno || offset + view->height <= lineno) {
2269                 unsigned long half = view->height / 2;
2271                 if (lineno > half)
2272                         offset = lineno - half;
2273                 else
2274                         offset = 0;
2275         }
2277         if (offset != view->offset || lineno != view->lineno) {
2278                 view->offset = offset;
2279                 view->lineno = lineno;
2280                 return TRUE;
2281         }
2283         return FALSE;
2286 /* Scrolling backend */
2287 static void
2288 do_scroll_view(struct view *view, int lines)
2290         bool redraw_current_line = FALSE;
2292         /* The rendering expects the new offset. */
2293         view->offset += lines;
2295         assert(0 <= view->offset && view->offset < view->lines);
2296         assert(lines);
2298         /* Move current line into the view. */
2299         if (view->lineno < view->offset) {
2300                 view->lineno = view->offset;
2301                 redraw_current_line = TRUE;
2302         } else if (view->lineno >= view->offset + view->height) {
2303                 view->lineno = view->offset + view->height - 1;
2304                 redraw_current_line = TRUE;
2305         }
2307         assert(view->offset <= view->lineno && view->lineno < view->lines);
2309         /* Redraw the whole screen if scrolling is pointless. */
2310         if (view->height < ABS(lines)) {
2311                 redraw_view(view);
2313         } else {
2314                 int line = lines > 0 ? view->height - lines : 0;
2315                 int end = line + ABS(lines);
2317                 scrollok(view->win, TRUE);
2318                 wscrl(view->win, lines);
2319                 scrollok(view->win, FALSE);
2321                 while (line < end && draw_view_line(view, line))
2322                         line++;
2324                 if (redraw_current_line)
2325                         draw_view_line(view, view->lineno - view->offset);
2326                 wnoutrefresh(view->win);
2327         }
2329         view->has_scrolled = TRUE;
2330         report("");
2333 /* Scroll frontend */
2334 static void
2335 scroll_view(struct view *view, enum request request)
2337         int lines = 1;
2339         assert(view_is_displayed(view));
2341         switch (request) {
2342         case REQ_SCROLL_LEFT:
2343                 if (view->yoffset == 0) {
2344                         report("Cannot scroll beyond the first column");
2345                         return;
2346                 }
2347                 if (view->yoffset <= SCROLL_INTERVAL)
2348                         view->yoffset = 0;
2349                 else
2350                         view->yoffset -= SCROLL_INTERVAL;
2351                 redraw_view_from(view, 0);
2352                 report("");
2353                 return;
2354         case REQ_SCROLL_RIGHT:
2355                 if (!view->can_hscroll) {
2356                         report("Cannot scroll beyond the last column");
2357                         return;
2358                 }
2359                 view->yoffset += SCROLL_INTERVAL;
2360                 redraw_view(view);
2361                 report("");
2362                 return;
2363         case REQ_SCROLL_PAGE_DOWN:
2364                 lines = view->height;
2365         case REQ_SCROLL_LINE_DOWN:
2366                 if (view->offset + lines > view->lines)
2367                         lines = view->lines - view->offset;
2369                 if (lines == 0 || view->offset + view->height >= view->lines) {
2370                         report("Cannot scroll beyond the last line");
2371                         return;
2372                 }
2373                 break;
2375         case REQ_SCROLL_PAGE_UP:
2376                 lines = view->height;
2377         case REQ_SCROLL_LINE_UP:
2378                 if (lines > view->offset)
2379                         lines = view->offset;
2381                 if (lines == 0) {
2382                         report("Cannot scroll beyond the first line");
2383                         return;
2384                 }
2386                 lines = -lines;
2387                 break;
2389         default:
2390                 die("request %d not handled in switch", request);
2391         }
2393         do_scroll_view(view, lines);
2396 /* Cursor moving */
2397 static void
2398 move_view(struct view *view, enum request request)
2400         int scroll_steps = 0;
2401         int steps;
2403         switch (request) {
2404         case REQ_MOVE_FIRST_LINE:
2405                 steps = -view->lineno;
2406                 break;
2408         case REQ_MOVE_LAST_LINE:
2409                 steps = view->lines - view->lineno - 1;
2410                 break;
2412         case REQ_MOVE_PAGE_UP:
2413                 steps = view->height > view->lineno
2414                       ? -view->lineno : -view->height;
2415                 break;
2417         case REQ_MOVE_PAGE_DOWN:
2418                 steps = view->lineno + view->height >= view->lines
2419                       ? view->lines - view->lineno - 1 : view->height;
2420                 break;
2422         case REQ_MOVE_UP:
2423                 steps = -1;
2424                 break;
2426         case REQ_MOVE_DOWN:
2427                 steps = 1;
2428                 break;
2430         default:
2431                 die("request %d not handled in switch", request);
2432         }
2434         if (steps <= 0 && view->lineno == 0) {
2435                 report("Cannot move beyond the first line");
2436                 return;
2438         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2439                 report("Cannot move beyond the last line");
2440                 return;
2441         }
2443         /* Move the current line */
2444         view->lineno += steps;
2445         assert(0 <= view->lineno && view->lineno < view->lines);
2447         /* Check whether the view needs to be scrolled */
2448         if (view->lineno < view->offset ||
2449             view->lineno >= view->offset + view->height) {
2450                 scroll_steps = steps;
2451                 if (steps < 0 && -steps > view->offset) {
2452                         scroll_steps = -view->offset;
2454                 } else if (steps > 0) {
2455                         if (view->lineno == view->lines - 1 &&
2456                             view->lines > view->height) {
2457                                 scroll_steps = view->lines - view->offset - 1;
2458                                 if (scroll_steps >= view->height)
2459                                         scroll_steps -= view->height - 1;
2460                         }
2461                 }
2462         }
2464         if (!view_is_displayed(view)) {
2465                 view->offset += scroll_steps;
2466                 assert(0 <= view->offset && view->offset < view->lines);
2467                 view->ops->select(view, &view->line[view->lineno]);
2468                 return;
2469         }
2471         /* Repaint the old "current" line if we be scrolling */
2472         if (ABS(steps) < view->height)
2473                 draw_view_line(view, view->lineno - steps - view->offset);
2475         if (scroll_steps) {
2476                 do_scroll_view(view, scroll_steps);
2477                 return;
2478         }
2480         /* Draw the current line */
2481         draw_view_line(view, view->lineno - view->offset);
2483         wnoutrefresh(view->win);
2484         report("");
2488 /*
2489  * Searching
2490  */
2492 static void search_view(struct view *view, enum request request);
2494 static void
2495 select_view_line(struct view *view, unsigned long lineno)
2497         unsigned long old_lineno = view->lineno;
2498         unsigned long old_offset = view->offset;
2500         if (goto_view_line(view, view->offset, lineno)) {
2501                 if (view_is_displayed(view)) {
2502                         if (old_offset != view->offset) {
2503                                 redraw_view(view);
2504                         } else {
2505                                 draw_view_line(view, old_lineno - view->offset);
2506                                 draw_view_line(view, view->lineno - view->offset);
2507                                 wnoutrefresh(view->win);
2508                         }
2509                 } else {
2510                         view->ops->select(view, &view->line[view->lineno]);
2511                 }
2512         }
2515 static void
2516 find_next(struct view *view, enum request request)
2518         unsigned long lineno = view->lineno;
2519         int direction;
2521         if (!*view->grep) {
2522                 if (!*opt_search)
2523                         report("No previous search");
2524                 else
2525                         search_view(view, request);
2526                 return;
2527         }
2529         switch (request) {
2530         case REQ_SEARCH:
2531         case REQ_FIND_NEXT:
2532                 direction = 1;
2533                 break;
2535         case REQ_SEARCH_BACK:
2536         case REQ_FIND_PREV:
2537                 direction = -1;
2538                 break;
2540         default:
2541                 return;
2542         }
2544         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2545                 lineno += direction;
2547         /* Note, lineno is unsigned long so will wrap around in which case it
2548          * will become bigger than view->lines. */
2549         for (; lineno < view->lines; lineno += direction) {
2550                 if (view->ops->grep(view, &view->line[lineno])) {
2551                         select_view_line(view, lineno);
2552                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2553                         return;
2554                 }
2555         }
2557         report("No match found for '%s'", view->grep);
2560 static void
2561 search_view(struct view *view, enum request request)
2563         int regex_err;
2565         if (view->regex) {
2566                 regfree(view->regex);
2567                 *view->grep = 0;
2568         } else {
2569                 view->regex = calloc(1, sizeof(*view->regex));
2570                 if (!view->regex)
2571                         return;
2572         }
2574         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2575         if (regex_err != 0) {
2576                 char buf[SIZEOF_STR] = "unknown error";
2578                 regerror(regex_err, view->regex, buf, sizeof(buf));
2579                 report("Search failed: %s", buf);
2580                 return;
2581         }
2583         string_copy(view->grep, opt_search);
2585         find_next(view, request);
2588 /*
2589  * Incremental updating
2590  */
2592 static void
2593 reset_view(struct view *view)
2595         int i;
2597         for (i = 0; i < view->lines; i++)
2598                 free(view->line[i].data);
2599         free(view->line);
2601         view->p_offset = view->offset;
2602         view->p_yoffset = view->yoffset;
2603         view->p_lineno = view->lineno;
2605         view->line = NULL;
2606         view->offset = 0;
2607         view->yoffset = 0;
2608         view->lines  = 0;
2609         view->lineno = 0;
2610         view->line_alloc = 0;
2611         view->vid[0] = 0;
2612         view->update_secs = 0;
2615 static void
2616 free_argv(const char *argv[])
2618         int argc;
2620         for (argc = 0; argv[argc]; argc++)
2621                 free((void *) argv[argc]);
2624 static bool
2625 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2627         char buf[SIZEOF_STR];
2628         int argc;
2629         bool noreplace = flags == FORMAT_NONE;
2631         free_argv(dst_argv);
2633         for (argc = 0; src_argv[argc]; argc++) {
2634                 const char *arg = src_argv[argc];
2635                 size_t bufpos = 0;
2637                 while (arg) {
2638                         char *next = strstr(arg, "%(");
2639                         int len = next - arg;
2640                         const char *value;
2642                         if (!next || noreplace) {
2643                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2644                                         noreplace = TRUE;
2645                                 len = strlen(arg);
2646                                 value = "";
2648                         } else if (!prefixcmp(next, "%(directory)")) {
2649                                 value = opt_path;
2651                         } else if (!prefixcmp(next, "%(file)")) {
2652                                 value = opt_file;
2654                         } else if (!prefixcmp(next, "%(ref)")) {
2655                                 value = *opt_ref ? opt_ref : "HEAD";
2657                         } else if (!prefixcmp(next, "%(head)")) {
2658                                 value = ref_head;
2660                         } else if (!prefixcmp(next, "%(commit)")) {
2661                                 value = ref_commit;
2663                         } else if (!prefixcmp(next, "%(blob)")) {
2664                                 value = ref_blob;
2666                         } else {
2667                                 report("Unknown replacement: `%s`", next);
2668                                 return FALSE;
2669                         }
2671                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2672                                 return FALSE;
2674                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2675                 }
2677                 dst_argv[argc] = strdup(buf);
2678                 if (!dst_argv[argc])
2679                         break;
2680         }
2682         dst_argv[argc] = NULL;
2684         return src_argv[argc] == NULL;
2687 static bool
2688 restore_view_position(struct view *view)
2690         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2691                 return FALSE;
2693         /* Changing the view position cancels the restoring. */
2694         /* FIXME: Changing back to the first line is not detected. */
2695         if (view->offset != 0 || view->lineno != 0) {
2696                 view->p_restore = FALSE;
2697                 return FALSE;
2698         }
2700         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2701             view_is_displayed(view))
2702                 werase(view->win);
2704         view->yoffset = view->p_yoffset;
2705         view->p_restore = FALSE;
2707         return TRUE;
2710 static void
2711 end_update(struct view *view, bool force)
2713         if (!view->pipe)
2714                 return;
2715         while (!view->ops->read(view, NULL))
2716                 if (!force)
2717                         return;
2718         set_nonblocking_input(FALSE);
2719         if (force)
2720                 kill_io(view->pipe);
2721         done_io(view->pipe);
2722         view->pipe = NULL;
2725 static void
2726 setup_update(struct view *view, const char *vid)
2728         set_nonblocking_input(TRUE);
2729         reset_view(view);
2730         string_copy_rev(view->vid, vid);
2731         view->pipe = &view->io;
2732         view->start_time = time(NULL);
2735 static bool
2736 prepare_update(struct view *view, const char *argv[], const char *dir,
2737                enum format_flags flags)
2739         if (view->pipe)
2740                 end_update(view, TRUE);
2741         return init_io_rd(&view->io, argv, dir, flags);
2744 static bool
2745 prepare_update_file(struct view *view, const char *name)
2747         if (view->pipe)
2748                 end_update(view, TRUE);
2749         return io_open(&view->io, name);
2752 static bool
2753 begin_update(struct view *view, bool refresh)
2755         if (view->pipe)
2756                 end_update(view, TRUE);
2758         if (refresh) {
2759                 if (!start_io(&view->io))
2760                         return FALSE;
2762         } else {
2763                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2764                         opt_path[0] = 0;
2766                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2767                         return FALSE;
2769                 /* Put the current ref_* value to the view title ref
2770                  * member. This is needed by the blob view. Most other
2771                  * views sets it automatically after loading because the
2772                  * first line is a commit line. */
2773                 string_copy_rev(view->ref, view->id);
2774         }
2776         setup_update(view, view->id);
2778         return TRUE;
2781 #define ITEM_CHUNK_SIZE 256
2782 static void *
2783 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2785         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2786         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2788         if (mem == NULL || num_chunks != num_chunks_new) {
2789                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2790                 mem = realloc(mem, *size * item_size);
2791         }
2793         return mem;
2796 static struct line *
2797 realloc_lines(struct view *view, size_t line_size)
2799         size_t alloc = view->line_alloc;
2800         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2801                                          sizeof(*view->line));
2803         if (!tmp)
2804                 return NULL;
2806         view->line = tmp;
2807         view->line_alloc = alloc;
2808         return view->line;
2811 static bool
2812 update_view(struct view *view)
2814         char out_buffer[BUFSIZ * 2];
2815         char *line;
2816         /* Clear the view and redraw everything since the tree sorting
2817          * might have rearranged things. */
2818         bool redraw = view->lines == 0;
2819         bool can_read = TRUE;
2821         if (!view->pipe)
2822                 return TRUE;
2824         if (!io_can_read(view->pipe)) {
2825                 if (view->lines == 0) {
2826                         time_t secs = time(NULL) - view->start_time;
2828                         if (secs > 1 && secs > view->update_secs) {
2829                                 if (view->update_secs == 0)
2830                                         redraw_view(view);
2831                                 update_view_title(view);
2832                                 view->update_secs = secs;
2833                         }
2834                 }
2835                 return TRUE;
2836         }
2838         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2839                 if (opt_iconv != ICONV_NONE) {
2840                         ICONV_CONST char *inbuf = line;
2841                         size_t inlen = strlen(line) + 1;
2843                         char *outbuf = out_buffer;
2844                         size_t outlen = sizeof(out_buffer);
2846                         size_t ret;
2848                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2849                         if (ret != (size_t) -1)
2850                                 line = out_buffer;
2851                 }
2853                 if (!view->ops->read(view, line)) {
2854                         report("Allocation failure");
2855                         end_update(view, TRUE);
2856                         return FALSE;
2857                 }
2858         }
2860         {
2861                 unsigned long lines = view->lines;
2862                 int digits;
2864                 for (digits = 0; lines; digits++)
2865                         lines /= 10;
2867                 /* Keep the displayed view in sync with line number scaling. */
2868                 if (digits != view->digits) {
2869                         view->digits = digits;
2870                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2871                                 redraw = TRUE;
2872                 }
2873         }
2875         if (io_error(view->pipe)) {
2876                 report("Failed to read: %s", io_strerror(view->pipe));
2877                 end_update(view, TRUE);
2879         } else if (io_eof(view->pipe)) {
2880                 report("");
2881                 end_update(view, FALSE);
2882         }
2884         if (restore_view_position(view))
2885                 redraw = TRUE;
2887         if (!view_is_displayed(view))
2888                 return TRUE;
2890         if (redraw)
2891                 redraw_view_from(view, 0);
2892         else
2893                 redraw_view_dirty(view);
2895         /* Update the title _after_ the redraw so that if the redraw picks up a
2896          * commit reference in view->ref it'll be available here. */
2897         update_view_title(view);
2898         return TRUE;
2901 static struct line *
2902 add_line_data(struct view *view, void *data, enum line_type type)
2904         struct line *line;
2906         if (!realloc_lines(view, view->lines + 1))
2907                 return NULL;
2909         line = &view->line[view->lines++];
2910         memset(line, 0, sizeof(*line));
2911         line->type = type;
2912         line->data = data;
2913         line->dirty = 1;
2915         return line;
2918 static struct line *
2919 add_line_text(struct view *view, const char *text, enum line_type type)
2921         char *data = text ? strdup(text) : NULL;
2923         return data ? add_line_data(view, data, type) : NULL;
2926 static struct line *
2927 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2929         char buf[SIZEOF_STR];
2930         va_list args;
2932         va_start(args, fmt);
2933         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2934                 buf[0] = 0;
2935         va_end(args);
2937         return buf[0] ? add_line_text(view, buf, type) : NULL;
2940 /*
2941  * View opening
2942  */
2944 enum open_flags {
2945         OPEN_DEFAULT = 0,       /* Use default view switching. */
2946         OPEN_SPLIT = 1,         /* Split current view. */
2947         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2948         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2949         OPEN_PREPARED = 32,     /* Open already prepared command. */
2950 };
2952 static void
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2955         bool split = !!(flags & OPEN_SPLIT);
2956         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2957         bool nomaximize = !!(flags & OPEN_REFRESH);
2958         struct view *view = VIEW(request);
2959         int nviews = displayed_views();
2960         struct view *base_view = display[0];
2962         if (view == prev && nviews == 1 && !reload) {
2963                 report("Already in %s view", view->name);
2964                 return;
2965         }
2967         if (view->git_dir && !opt_git_dir[0]) {
2968                 report("The %s view is disabled in pager view", view->name);
2969                 return;
2970         }
2972         if (split) {
2973                 display[1] = view;
2974                 current_view = 1;
2975         } else if (!nomaximize) {
2976                 /* Maximize the current view. */
2977                 memset(display, 0, sizeof(display));
2978                 current_view = 0;
2979                 display[current_view] = view;
2980         }
2982         /* Resize the view when switching between split- and full-screen,
2983          * or when switching between two different full-screen views. */
2984         if (nviews != displayed_views() ||
2985             (nviews == 1 && base_view != display[0]))
2986                 resize_display();
2988         if (view->ops->open) {
2989                 if (view->pipe)
2990                         end_update(view, TRUE);
2991                 if (!view->ops->open(view)) {
2992                         report("Failed to load %s view", view->name);
2993                         return;
2994                 }
2995                 restore_view_position(view);
2997         } else if ((reload || strcmp(view->vid, view->id)) &&
2998                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2999                 report("Failed to load %s view", view->name);
3000                 return;
3001         }
3003         if (split && prev->lineno - prev->offset >= prev->height) {
3004                 /* Take the title line into account. */
3005                 int lines = prev->lineno - prev->offset - prev->height + 1;
3007                 /* Scroll the view that was split if the current line is
3008                  * outside the new limited view. */
3009                 do_scroll_view(prev, lines);
3010         }
3012         if (prev && view != prev) {
3013                 if (split) {
3014                         /* "Blur" the previous view. */
3015                         update_view_title(prev);
3016                 }
3018                 view->parent = prev;
3019         }
3021         if (view->pipe && view->lines == 0) {
3022                 /* Clear the old view and let the incremental updating refill
3023                  * the screen. */
3024                 werase(view->win);
3025                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3026                 report("");
3027         } else if (view_is_displayed(view)) {
3028                 redraw_view(view);
3029                 report("");
3030         }
3033 static void
3034 open_external_viewer(const char *argv[], const char *dir)
3036         def_prog_mode();           /* save current tty modes */
3037         endwin();                  /* restore original tty modes */
3038         run_io_fg(argv, dir);
3039         fprintf(stderr, "Press Enter to continue");
3040         getc(opt_tty);
3041         reset_prog_mode();
3042         redraw_display(TRUE);
3045 static void
3046 open_mergetool(const char *file)
3048         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3050         open_external_viewer(mergetool_argv, opt_cdup);
3053 static void
3054 open_editor(bool from_root, const char *file)
3056         const char *editor_argv[] = { "vi", file, NULL };
3057         const char *editor;
3059         editor = getenv("GIT_EDITOR");
3060         if (!editor && *opt_editor)
3061                 editor = opt_editor;
3062         if (!editor)
3063                 editor = getenv("VISUAL");
3064         if (!editor)
3065                 editor = getenv("EDITOR");
3066         if (!editor)
3067                 editor = "vi";
3069         editor_argv[0] = editor;
3070         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3073 static void
3074 open_run_request(enum request request)
3076         struct run_request *req = get_run_request(request);
3077         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3079         if (!req) {
3080                 report("Unknown run request");
3081                 return;
3082         }
3084         if (format_argv(argv, req->argv, FORMAT_ALL))
3085                 open_external_viewer(argv, NULL);
3086         free_argv(argv);
3089 /*
3090  * User request switch noodle
3091  */
3093 static int
3094 view_driver(struct view *view, enum request request)
3096         int i;
3098         if (request == REQ_NONE) {
3099                 doupdate();
3100                 return TRUE;
3101         }
3103         if (request > REQ_NONE) {
3104                 open_run_request(request);
3105                 /* FIXME: When all views can refresh always do this. */
3106                 if (view == VIEW(REQ_VIEW_STATUS) ||
3107                     view == VIEW(REQ_VIEW_MAIN) ||
3108                     view == VIEW(REQ_VIEW_LOG) ||
3109                     view == VIEW(REQ_VIEW_STAGE))
3110                         request = REQ_REFRESH;
3111                 else
3112                         return TRUE;
3113         }
3115         if (view && view->lines) {
3116                 request = view->ops->request(view, request, &view->line[view->lineno]);
3117                 if (request == REQ_NONE)
3118                         return TRUE;
3119         }
3121         switch (request) {
3122         case REQ_MOVE_UP:
3123         case REQ_MOVE_DOWN:
3124         case REQ_MOVE_PAGE_UP:
3125         case REQ_MOVE_PAGE_DOWN:
3126         case REQ_MOVE_FIRST_LINE:
3127         case REQ_MOVE_LAST_LINE:
3128                 move_view(view, request);
3129                 break;
3131         case REQ_SCROLL_LEFT:
3132         case REQ_SCROLL_RIGHT:
3133         case REQ_SCROLL_LINE_DOWN:
3134         case REQ_SCROLL_LINE_UP:
3135         case REQ_SCROLL_PAGE_DOWN:
3136         case REQ_SCROLL_PAGE_UP:
3137                 scroll_view(view, request);
3138                 break;
3140         case REQ_VIEW_BLAME:
3141                 if (!opt_file[0]) {
3142                         report("No file chosen, press %s to open tree view",
3143                                get_key(REQ_VIEW_TREE));
3144                         break;
3145                 }
3146                 open_view(view, request, OPEN_DEFAULT);
3147                 break;
3149         case REQ_VIEW_BLOB:
3150                 if (!ref_blob[0]) {
3151                         report("No file chosen, press %s to open tree view",
3152                                get_key(REQ_VIEW_TREE));
3153                         break;
3154                 }
3155                 open_view(view, request, OPEN_DEFAULT);
3156                 break;
3158         case REQ_VIEW_PAGER:
3159                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3160                         report("No pager content, press %s to run command from prompt",
3161                                get_key(REQ_PROMPT));
3162                         break;
3163                 }
3164                 open_view(view, request, OPEN_DEFAULT);
3165                 break;
3167         case REQ_VIEW_STAGE:
3168                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3169                         report("No stage content, press %s to open the status view and choose file",
3170                                get_key(REQ_VIEW_STATUS));
3171                         break;
3172                 }
3173                 open_view(view, request, OPEN_DEFAULT);
3174                 break;
3176         case REQ_VIEW_STATUS:
3177                 if (opt_is_inside_work_tree == FALSE) {
3178                         report("The status view requires a working tree");
3179                         break;
3180                 }
3181                 open_view(view, request, OPEN_DEFAULT);
3182                 break;
3184         case REQ_VIEW_MAIN:
3185         case REQ_VIEW_DIFF:
3186         case REQ_VIEW_LOG:
3187         case REQ_VIEW_TREE:
3188         case REQ_VIEW_HELP:
3189                 open_view(view, request, OPEN_DEFAULT);
3190                 break;
3192         case REQ_NEXT:
3193         case REQ_PREVIOUS:
3194                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3196                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3197                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3198                    (view == VIEW(REQ_VIEW_DIFF) &&
3199                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3200                    (view == VIEW(REQ_VIEW_STAGE) &&
3201                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3202                    (view == VIEW(REQ_VIEW_BLOB) &&
3203                      view->parent == VIEW(REQ_VIEW_TREE))) {
3204                         int line;
3206                         view = view->parent;
3207                         line = view->lineno;
3208                         move_view(view, request);
3209                         if (view_is_displayed(view))
3210                                 update_view_title(view);
3211                         if (line != view->lineno)
3212                                 view->ops->request(view, REQ_ENTER,
3213                                                    &view->line[view->lineno]);
3215                 } else {
3216                         move_view(view, request);
3217                 }
3218                 break;
3220         case REQ_VIEW_NEXT:
3221         {
3222                 int nviews = displayed_views();
3223                 int next_view = (current_view + 1) % nviews;
3225                 if (next_view == current_view) {
3226                         report("Only one view is displayed");
3227                         break;
3228                 }
3230                 current_view = next_view;
3231                 /* Blur out the title of the previous view. */
3232                 update_view_title(view);
3233                 report("");
3234                 break;
3235         }
3236         case REQ_REFRESH:
3237                 report("Refreshing is not yet supported for the %s view", view->name);
3238                 break;
3240         case REQ_MAXIMIZE:
3241                 if (displayed_views() == 2)
3242                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3243                 break;
3245         case REQ_TOGGLE_LINENO:
3246                 toggle_view_option(&opt_line_number, "line numbers");
3247                 break;
3249         case REQ_TOGGLE_DATE:
3250                 toggle_view_option(&opt_date, "date display");
3251                 break;
3253         case REQ_TOGGLE_AUTHOR:
3254                 toggle_view_option(&opt_author, "author display");
3255                 break;
3257         case REQ_TOGGLE_REV_GRAPH:
3258                 toggle_view_option(&opt_rev_graph, "revision graph display");
3259                 break;
3261         case REQ_TOGGLE_REFS:
3262                 toggle_view_option(&opt_show_refs, "reference display");
3263                 break;
3265         case REQ_SEARCH:
3266         case REQ_SEARCH_BACK:
3267                 search_view(view, request);
3268                 break;
3270         case REQ_FIND_NEXT:
3271         case REQ_FIND_PREV:
3272                 find_next(view, request);
3273                 break;
3275         case REQ_STOP_LOADING:
3276                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3277                         view = &views[i];
3278                         if (view->pipe)
3279                                 report("Stopped loading the %s view", view->name),
3280                         end_update(view, TRUE);
3281                 }
3282                 break;
3284         case REQ_SHOW_VERSION:
3285                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3286                 return TRUE;
3288         case REQ_SCREEN_REDRAW:
3289                 redraw_display(TRUE);
3290                 break;
3292         case REQ_EDIT:
3293                 report("Nothing to edit");
3294                 break;
3296         case REQ_ENTER:
3297                 report("Nothing to enter");
3298                 break;
3300         case REQ_VIEW_CLOSE:
3301                 /* XXX: Mark closed views by letting view->parent point to the
3302                  * view itself. Parents to closed view should never be
3303                  * followed. */
3304                 if (view->parent &&
3305                     view->parent->parent != view->parent) {
3306                         memset(display, 0, sizeof(display));
3307                         current_view = 0;
3308                         display[current_view] = view->parent;
3309                         view->parent = view;
3310                         resize_display();
3311                         redraw_display(FALSE);
3312                         report("");
3313                         break;
3314                 }
3315                 /* Fall-through */
3316         case REQ_QUIT:
3317                 return FALSE;
3319         default:
3320                 report("Unknown key, press 'h' for help");
3321                 return TRUE;
3322         }
3324         return TRUE;
3328 /*
3329  * View backend utilities
3330  */
3332 static void
3333 parse_timezone(time_t *time, const char *zone)
3335         long tz;
3337         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3338         tz += ('0' - zone[2]) * 60 * 60;
3339         tz += ('0' - zone[3]) * 60;
3340         tz += ('0' - zone[4]);
3342         if (zone[0] == '-')
3343                 tz = -tz;
3345         *time -= tz;
3348 /* Parse author lines where the name may be empty:
3349  *      author  <email@address.tld> 1138474660 +0100
3350  */
3351 static void
3352 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3354         char *nameend = strchr(ident, '<');
3355         char *emailend = strchr(ident, '>');
3357         if (nameend && emailend)
3358                 *nameend = *emailend = 0;
3359         ident = chomp_string(ident);
3360         if (!*ident) {
3361                 if (nameend)
3362                         ident = chomp_string(nameend + 1);
3363                 if (!*ident)
3364                         ident = "Unknown";
3365         }
3367         string_ncopy_do(author, authorsize, ident, strlen(ident));
3369         /* Parse epoch and timezone */
3370         if (emailend && emailend[1] == ' ') {
3371                 char *secs = emailend + 2;
3372                 char *zone = strchr(secs, ' ');
3373                 time_t time = (time_t) atol(secs);
3375                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3376                         parse_timezone(&time, zone + 1);
3378                 gmtime_r(&time, tm);
3379         }
3382 static enum input_status
3383 select_commit_parent_handler(void *data, char *buf, int c)
3385         size_t parents = *(size_t *) data;
3386         int parent = 0;
3388         if (!isdigit(c))
3389                 return INPUT_SKIP;
3391         if (*buf)
3392                 parent = atoi(buf) * 10;
3393         parent += c - '0';
3395         if (parent > parents)
3396                 return INPUT_SKIP;
3397         return INPUT_OK;
3400 static bool
3401 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3403         char buf[SIZEOF_STR * 4];
3404         const char *revlist_argv[] = {
3405                 "git", "rev-list", "-1", "--parents", id, NULL
3406         };
3407         int parents;
3409         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3410             !*chomp_string(buf) ||
3411             (parents = (strlen(buf) / 40) - 1) < 0) {
3412                 report("Failed to get parent information");
3413                 return FALSE;
3415         } else if (parents == 0) {
3416                 report("The selected commit has no parents");
3417                 return FALSE;
3418         }
3420         if (parents > 1) {
3421                 char prompt[SIZEOF_STR];
3422                 char *result;
3424                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3425                         return FALSE;
3426                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3427                 if (!result)
3428                         return FALSE;
3429                 parents = atoi(result);
3430         }
3432         string_copy_rev(rev, &buf[41 * parents]);
3433         return TRUE;
3436 /*
3437  * Pager backend
3438  */
3440 static bool
3441 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3443         char text[SIZEOF_STR];
3445         if (opt_line_number && draw_lineno(view, lineno))
3446                 return TRUE;
3448         string_expand(text, sizeof(text), line->data, opt_tab_size);
3449         draw_text(view, line->type, text, TRUE);
3450         return TRUE;
3453 static bool
3454 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3456         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3457         char refbuf[SIZEOF_STR];
3458         char *ref = NULL;
3460         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3461                 ref = chomp_string(refbuf);
3463         if (!ref || !*ref)
3464                 return TRUE;
3466         /* This is the only fatal call, since it can "corrupt" the buffer. */
3467         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3468                 return FALSE;
3470         return TRUE;
3473 static void
3474 add_pager_refs(struct view *view, struct line *line)
3476         char buf[SIZEOF_STR];
3477         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3478         struct ref **refs;
3479         size_t bufpos = 0, refpos = 0;
3480         const char *sep = "Refs: ";
3481         bool is_tag = FALSE;
3483         assert(line->type == LINE_COMMIT);
3485         refs = get_refs(commit_id);
3486         if (!refs) {
3487                 if (view == VIEW(REQ_VIEW_DIFF))
3488                         goto try_add_describe_ref;
3489                 return;
3490         }
3492         do {
3493                 struct ref *ref = refs[refpos];
3494                 const char *fmt = ref->tag    ? "%s[%s]" :
3495                                   ref->remote ? "%s<%s>" : "%s%s";
3497                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3498                         return;
3499                 sep = ", ";
3500                 if (ref->tag)
3501                         is_tag = TRUE;
3502         } while (refs[refpos++]->next);
3504         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3505 try_add_describe_ref:
3506                 /* Add <tag>-g<commit_id> "fake" reference. */
3507                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3508                         return;
3509         }
3511         if (bufpos == 0)
3512                 return;
3514         add_line_text(view, buf, LINE_PP_REFS);
3517 static bool
3518 pager_read(struct view *view, char *data)
3520         struct line *line;
3522         if (!data)
3523                 return TRUE;
3525         line = add_line_text(view, data, get_line_type(data));
3526         if (!line)
3527                 return FALSE;
3529         if (line->type == LINE_COMMIT &&
3530             (view == VIEW(REQ_VIEW_DIFF) ||
3531              view == VIEW(REQ_VIEW_LOG)))
3532                 add_pager_refs(view, line);
3534         return TRUE;
3537 static enum request
3538 pager_request(struct view *view, enum request request, struct line *line)
3540         int split = 0;
3542         if (request != REQ_ENTER)
3543                 return request;
3545         if (line->type == LINE_COMMIT &&
3546            (view == VIEW(REQ_VIEW_LOG) ||
3547             view == VIEW(REQ_VIEW_PAGER))) {
3548                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3549                 split = 1;
3550         }
3552         /* Always scroll the view even if it was split. That way
3553          * you can use Enter to scroll through the log view and
3554          * split open each commit diff. */
3555         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3557         /* FIXME: A minor workaround. Scrolling the view will call report("")
3558          * but if we are scrolling a non-current view this won't properly
3559          * update the view title. */
3560         if (split)
3561                 update_view_title(view);
3563         return REQ_NONE;
3566 static bool
3567 pager_grep(struct view *view, struct line *line)
3569         regmatch_t pmatch;
3570         char *text = line->data;
3572         if (!*text)
3573                 return FALSE;
3575         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3576                 return FALSE;
3578         return TRUE;
3581 static void
3582 pager_select(struct view *view, struct line *line)
3584         if (line->type == LINE_COMMIT) {
3585                 char *text = (char *)line->data + STRING_SIZE("commit ");
3587                 if (view != VIEW(REQ_VIEW_PAGER))
3588                         string_copy_rev(view->ref, text);
3589                 string_copy_rev(ref_commit, text);
3590         }
3593 static struct view_ops pager_ops = {
3594         "line",
3595         NULL,
3596         NULL,
3597         pager_read,
3598         pager_draw,
3599         pager_request,
3600         pager_grep,
3601         pager_select,
3602 };
3604 static const char *log_argv[SIZEOF_ARG] = {
3605         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3606 };
3608 static enum request
3609 log_request(struct view *view, enum request request, struct line *line)
3611         switch (request) {
3612         case REQ_REFRESH:
3613                 load_refs();
3614                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3615                 return REQ_NONE;
3616         default:
3617                 return pager_request(view, request, line);
3618         }
3621 static struct view_ops log_ops = {
3622         "line",
3623         log_argv,
3624         NULL,
3625         pager_read,
3626         pager_draw,
3627         log_request,
3628         pager_grep,
3629         pager_select,
3630 };
3632 static const char *diff_argv[SIZEOF_ARG] = {
3633         "git", "show", "--pretty=fuller", "--no-color", "--root",
3634                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3635 };
3637 static struct view_ops diff_ops = {
3638         "line",
3639         diff_argv,
3640         NULL,
3641         pager_read,
3642         pager_draw,
3643         pager_request,
3644         pager_grep,
3645         pager_select,
3646 };
3648 /*
3649  * Help backend
3650  */
3652 static bool
3653 help_open(struct view *view)
3655         char buf[SIZEOF_STR];
3656         size_t bufpos;
3657         int i;
3659         if (view->lines > 0)
3660                 return TRUE;
3662         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3664         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3665                 const char *key;
3667                 if (req_info[i].request == REQ_NONE)
3668                         continue;
3670                 if (!req_info[i].request) {
3671                         add_line_text(view, "", LINE_DEFAULT);
3672                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3673                         continue;
3674                 }
3676                 key = get_key(req_info[i].request);
3677                 if (!*key)
3678                         key = "(no key defined)";
3680                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3681                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3682                         if (buf[bufpos] == '_')
3683                                 buf[bufpos] = '-';
3684                 }
3686                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3687                                 key, buf, req_info[i].help);
3688         }
3690         if (run_requests) {
3691                 add_line_text(view, "", LINE_DEFAULT);
3692                 add_line_text(view, "External commands:", LINE_DEFAULT);
3693         }
3695         for (i = 0; i < run_requests; i++) {
3696                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3697                 const char *key;
3698                 int argc;
3700                 if (!req)
3701                         continue;
3703                 key = get_key_name(req->key);
3704                 if (!*key)
3705                         key = "(no key defined)";
3707                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3708                         if (!string_format_from(buf, &bufpos, "%s%s",
3709                                                 argc ? " " : "", req->argv[argc]))
3710                                 return REQ_NONE;
3712                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3713                                 keymap_table[req->keymap].name, key, buf);
3714         }
3716         return TRUE;
3719 static struct view_ops help_ops = {
3720         "line",
3721         NULL,
3722         help_open,
3723         NULL,
3724         pager_draw,
3725         pager_request,
3726         pager_grep,
3727         pager_select,
3728 };
3731 /*
3732  * Tree backend
3733  */
3735 struct tree_stack_entry {
3736         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3737         unsigned long lineno;           /* Line number to restore */
3738         char *name;                     /* Position of name in opt_path */
3739 };
3741 /* The top of the path stack. */
3742 static struct tree_stack_entry *tree_stack = NULL;
3743 unsigned long tree_lineno = 0;
3745 static void
3746 pop_tree_stack_entry(void)
3748         struct tree_stack_entry *entry = tree_stack;
3750         tree_lineno = entry->lineno;
3751         entry->name[0] = 0;
3752         tree_stack = entry->prev;
3753         free(entry);
3756 static void
3757 push_tree_stack_entry(const char *name, unsigned long lineno)
3759         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3760         size_t pathlen = strlen(opt_path);
3762         if (!entry)
3763                 return;
3765         entry->prev = tree_stack;
3766         entry->name = opt_path + pathlen;
3767         tree_stack = entry;
3769         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3770                 pop_tree_stack_entry();
3771                 return;
3772         }
3774         /* Move the current line to the first tree entry. */
3775         tree_lineno = 1;
3776         entry->lineno = lineno;
3779 /* Parse output from git-ls-tree(1):
3780  *
3781  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3782  */
3784 #define SIZEOF_TREE_ATTR \
3785         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3787 #define SIZEOF_TREE_MODE \
3788         STRING_SIZE("100644 ")
3790 #define TREE_ID_OFFSET \
3791         STRING_SIZE("100644 blob ")
3793 struct tree_entry {
3794         char id[SIZEOF_REV];
3795         mode_t mode;
3796         struct tm time;                 /* Date from the author ident. */
3797         char author[75];                /* Author of the commit. */
3798         char name[1];
3799 };
3801 static const char *
3802 tree_path(struct line *line)
3804         return ((struct tree_entry *) line->data)->name;
3808 static int
3809 tree_compare_entry(struct line *line1, struct line *line2)
3811         if (line1->type != line2->type)
3812                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3813         return strcmp(tree_path(line1), tree_path(line2));
3816 static struct line *
3817 tree_entry(struct view *view, enum line_type type, const char *path,
3818            const char *mode, const char *id)
3820         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3821         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3823         if (!entry || !line) {
3824                 free(entry);
3825                 return NULL;
3826         }
3828         strncpy(entry->name, path, strlen(path));
3829         if (mode)
3830                 entry->mode = strtoul(mode, NULL, 8);
3831         if (id)
3832                 string_copy_rev(entry->id, id);
3834         return line;
3837 static bool
3838 tree_read_date(struct view *view, char *text, bool *read_date)
3840         static char author_name[SIZEOF_STR];
3841         static struct tm author_time;
3843         if (!text && *read_date) {
3844                 *read_date = FALSE;
3845                 return TRUE;
3847         } else if (!text) {
3848                 char *path = *opt_path ? opt_path : ".";
3849                 /* Find next entry to process */
3850                 const char *log_file[] = {
3851                         "git", "log", "--no-color", "--pretty=raw",
3852                                 "--cc", "--raw", view->id, "--", path, NULL
3853                 };
3854                 struct io io = {};
3856                 if (!view->lines) {
3857                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3858                         report("Tree is empty");
3859                         return TRUE;
3860                 }
3862                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3863                         report("Failed to load tree data");
3864                         return TRUE;
3865                 }
3867                 done_io(view->pipe);
3868                 view->io = io;
3869                 *read_date = TRUE;
3870                 return FALSE;
3872         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3873                 parse_author_line(text + STRING_SIZE("author "),
3874                                   author_name, sizeof(author_name), &author_time);
3876         } else if (*text == ':') {
3877                 char *pos;
3878                 size_t annotated = 1;
3879                 size_t i;
3881                 pos = strchr(text, '\t');
3882                 if (!pos)
3883                         return TRUE;
3884                 text = pos + 1;
3885                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3886                         text += strlen(opt_prefix);
3887                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3888                         text += strlen(opt_path);
3889                 pos = strchr(text, '/');
3890                 if (pos)
3891                         *pos = 0;
3893                 for (i = 1; i < view->lines; i++) {
3894                         struct line *line = &view->line[i];
3895                         struct tree_entry *entry = line->data;
3897                         annotated += !!*entry->author;
3898                         if (*entry->author || strcmp(entry->name, text))
3899                                 continue;
3901                         string_copy(entry->author, author_name);
3902                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3903                         line->dirty = 1;
3904                         break;
3905                 }
3907                 if (annotated == view->lines)
3908                         kill_io(view->pipe);
3909         }
3910         return TRUE;
3913 static bool
3914 tree_read(struct view *view, char *text)
3916         static bool read_date = FALSE;
3917         struct tree_entry *data;
3918         struct line *entry, *line;
3919         enum line_type type;
3920         size_t textlen = text ? strlen(text) : 0;
3921         char *path = text + SIZEOF_TREE_ATTR;
3923         if (read_date || !text)
3924                 return tree_read_date(view, text, &read_date);
3926         if (textlen <= SIZEOF_TREE_ATTR)
3927                 return FALSE;
3928         if (view->lines == 0 &&
3929             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3930                 return FALSE;
3932         /* Strip the path part ... */
3933         if (*opt_path) {
3934                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3935                 size_t striplen = strlen(opt_path);
3937                 if (pathlen > striplen)
3938                         memmove(path, path + striplen,
3939                                 pathlen - striplen + 1);
3941                 /* Insert "link" to parent directory. */
3942                 if (view->lines == 1 &&
3943                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3944                         return FALSE;
3945         }
3947         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3948         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3949         if (!entry)
3950                 return FALSE;
3951         data = entry->data;
3953         /* Skip "Directory ..." and ".." line. */
3954         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3955                 if (tree_compare_entry(line, entry) <= 0)
3956                         continue;
3958                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3960                 line->data = data;
3961                 line->type = type;
3962                 for (; line <= entry; line++)
3963                         line->dirty = line->cleareol = 1;
3964                 return TRUE;
3965         }
3967         if (tree_lineno > view->lineno) {
3968                 view->lineno = tree_lineno;
3969                 tree_lineno = 0;
3970         }
3972         return TRUE;
3975 static bool
3976 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3978         struct tree_entry *entry = line->data;
3980         if (line->type == LINE_TREE_HEAD) {
3981                 if (draw_text(view, line->type, "Directory path /", TRUE))
3982                         return TRUE;
3983         } else {
3984                 if (draw_mode(view, entry->mode))
3985                         return TRUE;
3987                 if (opt_author && draw_author(view, entry->author))
3988                         return TRUE;
3990                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3991                         return TRUE;
3992         }
3993         if (draw_text(view, line->type, entry->name, TRUE))
3994                 return TRUE;
3995         return TRUE;
3998 static void
3999 open_blob_editor()
4001         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4002         int fd = mkstemp(file);
4004         if (fd == -1)
4005                 report("Failed to create temporary file");
4006         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4007                 report("Failed to save blob data to file");
4008         else
4009                 open_editor(FALSE, file);
4010         if (fd != -1)
4011                 unlink(file);
4014 static enum request
4015 tree_request(struct view *view, enum request request, struct line *line)
4017         enum open_flags flags;
4019         switch (request) {
4020         case REQ_VIEW_BLAME:
4021                 if (line->type != LINE_TREE_FILE) {
4022                         report("Blame only supported for files");
4023                         return REQ_NONE;
4024                 }
4026                 string_copy(opt_ref, view->vid);
4027                 return request;
4029         case REQ_EDIT:
4030                 if (line->type != LINE_TREE_FILE) {
4031                         report("Edit only supported for files");
4032                 } else if (!is_head_commit(view->vid)) {
4033                         open_blob_editor();
4034                 } else {
4035                         open_editor(TRUE, opt_file);
4036                 }
4037                 return REQ_NONE;
4039         case REQ_PARENT:
4040                 if (!*opt_path) {
4041                         /* quit view if at top of tree */
4042                         return REQ_VIEW_CLOSE;
4043                 }
4044                 /* fake 'cd  ..' */
4045                 line = &view->line[1];
4046                 break;
4048         case REQ_ENTER:
4049                 break;
4051         default:
4052                 return request;
4053         }
4055         /* Cleanup the stack if the tree view is at a different tree. */
4056         while (!*opt_path && tree_stack)
4057                 pop_tree_stack_entry();
4059         switch (line->type) {
4060         case LINE_TREE_DIR:
4061                 /* Depending on whether it is a subdirectory or parent link
4062                  * mangle the path buffer. */
4063                 if (line == &view->line[1] && *opt_path) {
4064                         pop_tree_stack_entry();
4066                 } else {
4067                         const char *basename = tree_path(line);
4069                         push_tree_stack_entry(basename, view->lineno);
4070                 }
4072                 /* Trees and subtrees share the same ID, so they are not not
4073                  * unique like blobs. */
4074                 flags = OPEN_RELOAD;
4075                 request = REQ_VIEW_TREE;
4076                 break;
4078         case LINE_TREE_FILE:
4079                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4080                 request = REQ_VIEW_BLOB;
4081                 break;
4083         default:
4084                 return REQ_NONE;
4085         }
4087         open_view(view, request, flags);
4088         if (request == REQ_VIEW_TREE)
4089                 view->lineno = tree_lineno;
4091         return REQ_NONE;
4094 static void
4095 tree_select(struct view *view, struct line *line)
4097         struct tree_entry *entry = line->data;
4099         if (line->type == LINE_TREE_FILE) {
4100                 string_copy_rev(ref_blob, entry->id);
4101                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4103         } else if (line->type != LINE_TREE_DIR) {
4104                 return;
4105         }
4107         string_copy_rev(view->ref, entry->id);
4110 static const char *tree_argv[SIZEOF_ARG] = {
4111         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4112 };
4114 static struct view_ops tree_ops = {
4115         "file",
4116         tree_argv,
4117         NULL,
4118         tree_read,
4119         tree_draw,
4120         tree_request,
4121         pager_grep,
4122         tree_select,
4123 };
4125 static bool
4126 blob_read(struct view *view, char *line)
4128         if (!line)
4129                 return TRUE;
4130         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4133 static enum request
4134 blob_request(struct view *view, enum request request, struct line *line)
4136         switch (request) {
4137         case REQ_EDIT:
4138                 open_blob_editor();
4139                 return REQ_NONE;
4140         default:
4141                 return pager_request(view, request, line);
4142         }
4145 static const char *blob_argv[SIZEOF_ARG] = {
4146         "git", "cat-file", "blob", "%(blob)", NULL
4147 };
4149 static struct view_ops blob_ops = {
4150         "line",
4151         blob_argv,
4152         NULL,
4153         blob_read,
4154         pager_draw,
4155         blob_request,
4156         pager_grep,
4157         pager_select,
4158 };
4160 /*
4161  * Blame backend
4162  *
4163  * Loading the blame view is a two phase job:
4164  *
4165  *  1. File content is read either using opt_file from the
4166  *     filesystem or using git-cat-file.
4167  *  2. Then blame information is incrementally added by
4168  *     reading output from git-blame.
4169  */
4171 static const char *blame_head_argv[] = {
4172         "git", "blame", "--incremental", "--", "%(file)", NULL
4173 };
4175 static const char *blame_ref_argv[] = {
4176         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4177 };
4179 static const char *blame_cat_file_argv[] = {
4180         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4181 };
4183 struct blame_commit {
4184         char id[SIZEOF_REV];            /* SHA1 ID. */
4185         char title[128];                /* First line of the commit message. */
4186         char author[75];                /* Author of the commit. */
4187         struct tm time;                 /* Date from the author ident. */
4188         char filename[128];             /* Name of file. */
4189         bool has_previous;              /* Was a "previous" line detected. */
4190 };
4192 struct blame {
4193         struct blame_commit *commit;
4194         char text[1];
4195 };
4197 static bool
4198 blame_open(struct view *view)
4200         if (*opt_ref || !io_open(&view->io, opt_file)) {
4201                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4202                         return FALSE;
4203         }
4205         setup_update(view, opt_file);
4206         string_format(view->ref, "%s ...", opt_file);
4208         return TRUE;
4211 static struct blame_commit *
4212 get_blame_commit(struct view *view, const char *id)
4214         size_t i;
4216         for (i = 0; i < view->lines; i++) {
4217                 struct blame *blame = view->line[i].data;
4219                 if (!blame->commit)
4220                         continue;
4222                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4223                         return blame->commit;
4224         }
4226         {
4227                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4229                 if (commit)
4230                         string_ncopy(commit->id, id, SIZEOF_REV);
4231                 return commit;
4232         }
4235 static bool
4236 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4238         const char *pos = *posref;
4240         *posref = NULL;
4241         pos = strchr(pos + 1, ' ');
4242         if (!pos || !isdigit(pos[1]))
4243                 return FALSE;
4244         *number = atoi(pos + 1);
4245         if (*number < min || *number > max)
4246                 return FALSE;
4248         *posref = pos;
4249         return TRUE;
4252 static struct blame_commit *
4253 parse_blame_commit(struct view *view, const char *text, int *blamed)
4255         struct blame_commit *commit;
4256         struct blame *blame;
4257         const char *pos = text + SIZEOF_REV - 1;
4258         size_t lineno;
4259         size_t group;
4261         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4262                 return NULL;
4264         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4265             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4266                 return NULL;
4268         commit = get_blame_commit(view, text);
4269         if (!commit)
4270                 return NULL;
4272         *blamed += group;
4273         while (group--) {
4274                 struct line *line = &view->line[lineno + group - 1];
4276                 blame = line->data;
4277                 blame->commit = commit;
4278                 line->dirty = 1;
4279         }
4281         return commit;
4284 static bool
4285 blame_read_file(struct view *view, const char *line, bool *read_file)
4287         if (!line) {
4288                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4289                 struct io io = {};
4291                 if (view->lines == 0 && !view->parent)
4292                         die("No blame exist for %s", view->vid);
4294                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4295                         report("Failed to load blame data");
4296                         return TRUE;
4297                 }
4299                 done_io(view->pipe);
4300                 view->io = io;
4301                 *read_file = FALSE;
4302                 return FALSE;
4304         } else {
4305                 size_t linelen = string_expand_length(line, opt_tab_size);
4306                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4308                 if (!blame)
4309                         return FALSE;
4311                 blame->commit = NULL;
4312                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4313                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4314         }
4317 static bool
4318 match_blame_header(const char *name, char **line)
4320         size_t namelen = strlen(name);
4321         bool matched = !strncmp(name, *line, namelen);
4323         if (matched)
4324                 *line += namelen;
4326         return matched;
4329 static bool
4330 blame_read(struct view *view, char *line)
4332         static struct blame_commit *commit = NULL;
4333         static int blamed = 0;
4334         static time_t author_time;
4335         static bool read_file = TRUE;
4337         if (read_file)
4338                 return blame_read_file(view, line, &read_file);
4340         if (!line) {
4341                 /* Reset all! */
4342                 commit = NULL;
4343                 blamed = 0;
4344                 read_file = TRUE;
4345                 string_format(view->ref, "%s", view->vid);
4346                 if (view_is_displayed(view)) {
4347                         update_view_title(view);
4348                         redraw_view_from(view, 0);
4349                 }
4350                 return TRUE;
4351         }
4353         if (!commit) {
4354                 commit = parse_blame_commit(view, line, &blamed);
4355                 string_format(view->ref, "%s %2d%%", view->vid,
4356                               view->lines ? blamed * 100 / view->lines : 0);
4358         } else if (match_blame_header("author ", &line)) {
4359                 string_ncopy(commit->author, line, strlen(line));
4361         } else if (match_blame_header("author-time ", &line)) {
4362                 author_time = (time_t) atol(line);
4364         } else if (match_blame_header("author-tz ", &line)) {
4365                 parse_timezone(&author_time, line);
4366                 gmtime_r(&author_time, &commit->time);
4368         } else if (match_blame_header("summary ", &line)) {
4369                 string_ncopy(commit->title, line, strlen(line));
4371         } else if (match_blame_header("previous ", &line)) {
4372                 commit->has_previous = TRUE;
4374         } else if (match_blame_header("filename ", &line)) {
4375                 string_ncopy(commit->filename, line, strlen(line));
4376                 commit = NULL;
4377         }
4379         return TRUE;
4382 static bool
4383 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4385         struct blame *blame = line->data;
4386         struct tm *time = NULL;
4387         const char *id = NULL, *author = NULL;
4389         if (blame->commit && *blame->commit->filename) {
4390                 id = blame->commit->id;
4391                 author = blame->commit->author;
4392                 time = &blame->commit->time;
4393         }
4395         if (opt_date && draw_date(view, time))
4396                 return TRUE;
4398         if (opt_author && draw_author(view, author))
4399                 return TRUE;
4401         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4402                 return TRUE;
4404         if (draw_lineno(view, lineno))
4405                 return TRUE;
4407         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4408         return TRUE;
4411 static bool
4412 check_blame_commit(struct blame *blame)
4414         if (!blame->commit)
4415                 report("Commit data not loaded yet");
4416         else if (!strcmp(blame->commit->id, NULL_ID))
4417                 report("No commit exist for the selected line");
4418         else
4419                 return TRUE;
4420         return FALSE;
4423 static enum request
4424 blame_request(struct view *view, enum request request, struct line *line)
4426         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4427         struct blame *blame = line->data;
4429         switch (request) {
4430         case REQ_VIEW_BLAME:
4431                 if (check_blame_commit(blame)) {
4432                         string_copy(opt_ref, blame->commit->id);
4433                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4434                 }
4435                 break;
4437         case REQ_PARENT:
4438                 if (check_blame_commit(blame) &&
4439                     select_commit_parent(blame->commit->id, opt_ref))
4440                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4441                 break;
4443         case REQ_ENTER:
4444                 if (!blame->commit) {
4445                         report("No commit loaded yet");
4446                         break;
4447                 }
4449                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4450                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4451                         break;
4453                 if (!strcmp(blame->commit->id, NULL_ID)) {
4454                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4455                         const char *diff_index_argv[] = {
4456                                 "git", "diff-index", "--root", "--patch-with-stat",
4457                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4458                         };
4460                         if (!blame->commit->has_previous) {
4461                                 diff_index_argv[1] = "diff";
4462                                 diff_index_argv[2] = "--no-color";
4463                                 diff_index_argv[6] = "--";
4464                                 diff_index_argv[7] = "/dev/null";
4465                         }
4467                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4468                                 report("Failed to allocate diff command");
4469                                 break;
4470                         }
4471                         flags |= OPEN_PREPARED;
4472                 }
4474                 open_view(view, REQ_VIEW_DIFF, flags);
4475                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4476                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4477                 break;
4479         default:
4480                 return request;
4481         }
4483         return REQ_NONE;
4486 static bool
4487 blame_grep(struct view *view, struct line *line)
4489         struct blame *blame = line->data;
4490         struct blame_commit *commit = blame->commit;
4491         regmatch_t pmatch;
4493 #define MATCH(text, on)                                                 \
4494         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4496         if (commit) {
4497                 char buf[DATE_COLS + 1];
4499                 if (MATCH(commit->title, 1) ||
4500                     MATCH(commit->author, opt_author) ||
4501                     MATCH(commit->id, opt_date))
4502                         return TRUE;
4504                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4505                     MATCH(buf, 1))
4506                         return TRUE;
4507         }
4509         return MATCH(blame->text, 1);
4511 #undef MATCH
4514 static void
4515 blame_select(struct view *view, struct line *line)
4517         struct blame *blame = line->data;
4518         struct blame_commit *commit = blame->commit;
4520         if (!commit)
4521                 return;
4523         if (!strcmp(commit->id, NULL_ID))
4524                 string_ncopy(ref_commit, "HEAD", 4);
4525         else
4526                 string_copy_rev(ref_commit, commit->id);
4529 static struct view_ops blame_ops = {
4530         "line",
4531         NULL,
4532         blame_open,
4533         blame_read,
4534         blame_draw,
4535         blame_request,
4536         blame_grep,
4537         blame_select,
4538 };
4540 /*
4541  * Status backend
4542  */
4544 struct status {
4545         char status;
4546         struct {
4547                 mode_t mode;
4548                 char rev[SIZEOF_REV];
4549                 char name[SIZEOF_STR];
4550         } old;
4551         struct {
4552                 mode_t mode;
4553                 char rev[SIZEOF_REV];
4554                 char name[SIZEOF_STR];
4555         } new;
4556 };
4558 static char status_onbranch[SIZEOF_STR];
4559 static struct status stage_status;
4560 static enum line_type stage_line_type;
4561 static size_t stage_chunks;
4562 static int *stage_chunk;
4564 /* This should work even for the "On branch" line. */
4565 static inline bool
4566 status_has_none(struct view *view, struct line *line)
4568         return line < view->line + view->lines && !line[1].data;
4571 /* Get fields from the diff line:
4572  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4573  */
4574 static inline bool
4575 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4577         const char *old_mode = buf +  1;
4578         const char *new_mode = buf +  8;
4579         const char *old_rev  = buf + 15;
4580         const char *new_rev  = buf + 56;
4581         const char *status   = buf + 97;
4583         if (bufsize < 98 ||
4584             old_mode[-1] != ':' ||
4585             new_mode[-1] != ' ' ||
4586             old_rev[-1]  != ' ' ||
4587             new_rev[-1]  != ' ' ||
4588             status[-1]   != ' ')
4589                 return FALSE;
4591         file->status = *status;
4593         string_copy_rev(file->old.rev, old_rev);
4594         string_copy_rev(file->new.rev, new_rev);
4596         file->old.mode = strtoul(old_mode, NULL, 8);
4597         file->new.mode = strtoul(new_mode, NULL, 8);
4599         file->old.name[0] = file->new.name[0] = 0;
4601         return TRUE;
4604 static bool
4605 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4607         struct status *unmerged = NULL;
4608         char *buf;
4609         struct io io = {};
4611         if (!run_io(&io, argv, NULL, IO_RD))
4612                 return FALSE;
4614         add_line_data(view, NULL, type);
4616         while ((buf = io_get(&io, 0, TRUE))) {
4617                 struct status *file = unmerged;
4619                 if (!file) {
4620                         file = calloc(1, sizeof(*file));
4621                         if (!file || !add_line_data(view, file, type))
4622                                 goto error_out;
4623                 }
4625                 /* Parse diff info part. */
4626                 if (status) {
4627                         file->status = status;
4628                         if (status == 'A')
4629                                 string_copy(file->old.rev, NULL_ID);
4631                 } else if (!file->status || file == unmerged) {
4632                         if (!status_get_diff(file, buf, strlen(buf)))
4633                                 goto error_out;
4635                         buf = io_get(&io, 0, TRUE);
4636                         if (!buf)
4637                                 break;
4639                         /* Collapse all modified entries that follow an
4640                          * associated unmerged entry. */
4641                         if (unmerged == file) {
4642                                 unmerged->status = 'U';
4643                                 unmerged = NULL;
4644                         } else if (file->status == 'U') {
4645                                 unmerged = file;
4646                         }
4647                 }
4649                 /* Grab the old name for rename/copy. */
4650                 if (!*file->old.name &&
4651                     (file->status == 'R' || file->status == 'C')) {
4652                         string_ncopy(file->old.name, buf, strlen(buf));
4654                         buf = io_get(&io, 0, TRUE);
4655                         if (!buf)
4656                                 break;
4657                 }
4659                 /* git-ls-files just delivers a NUL separated list of
4660                  * file names similar to the second half of the
4661                  * git-diff-* output. */
4662                 string_ncopy(file->new.name, buf, strlen(buf));
4663                 if (!*file->old.name)
4664                         string_copy(file->old.name, file->new.name);
4665                 file = NULL;
4666         }
4668         if (io_error(&io)) {
4669 error_out:
4670                 done_io(&io);
4671                 return FALSE;
4672         }
4674         if (!view->line[view->lines - 1].data)
4675                 add_line_data(view, NULL, LINE_STAT_NONE);
4677         done_io(&io);
4678         return TRUE;
4681 /* Don't show unmerged entries in the staged section. */
4682 static const char *status_diff_index_argv[] = {
4683         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4684                              "--cached", "-M", "HEAD", NULL
4685 };
4687 static const char *status_diff_files_argv[] = {
4688         "git", "diff-files", "-z", NULL
4689 };
4691 static const char *status_list_other_argv[] = {
4692         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4693 };
4695 static const char *status_list_no_head_argv[] = {
4696         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4697 };
4699 static const char *update_index_argv[] = {
4700         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4701 };
4703 /* Restore the previous line number to stay in the context or select a
4704  * line with something that can be updated. */
4705 static void
4706 status_restore(struct view *view)
4708         if (view->p_lineno >= view->lines)
4709                 view->p_lineno = view->lines - 1;
4710         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4711                 view->p_lineno++;
4712         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4713                 view->p_lineno--;
4715         /* If the above fails, always skip the "On branch" line. */
4716         if (view->p_lineno < view->lines)
4717                 view->lineno = view->p_lineno;
4718         else
4719                 view->lineno = 1;
4721         if (view->lineno < view->offset)
4722                 view->offset = view->lineno;
4723         else if (view->offset + view->height <= view->lineno)
4724                 view->offset = view->lineno - view->height + 1;
4726         view->p_restore = FALSE;
4729 static void
4730 status_update_onbranch(void)
4732         static const char *paths[][2] = {
4733                 { "rebase-apply/rebasing",      "Rebasing" },
4734                 { "rebase-apply/applying",      "Applying mailbox" },
4735                 { "rebase-apply/",              "Rebasing mailbox" },
4736                 { "rebase-merge/interactive",   "Interactive rebase" },
4737                 { "rebase-merge/",              "Rebase merge" },
4738                 { "MERGE_HEAD",                 "Merging" },
4739                 { "BISECT_LOG",                 "Bisecting" },
4740                 { "HEAD",                       "On branch" },
4741         };
4742         char buf[SIZEOF_STR];
4743         struct stat stat;
4744         int i;
4746         if (is_initial_commit()) {
4747                 string_copy(status_onbranch, "Initial commit");
4748                 return;
4749         }
4751         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4752                 char *head = opt_head;
4754                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4755                     lstat(buf, &stat) < 0)
4756                         continue;
4758                 if (!*opt_head) {
4759                         struct io io = {};
4761                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4762                             io_open(&io, buf) &&
4763                             io_read_buf(&io, buf, sizeof(buf))) {
4764                                 head = chomp_string(buf);
4765                                 if (!prefixcmp(head, "refs/heads/"))
4766                                         head += STRING_SIZE("refs/heads/");
4767                         }
4768                 }
4770                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4771                         string_copy(status_onbranch, opt_head);
4772                 return;
4773         }
4775         string_copy(status_onbranch, "Not currently on any branch");
4778 /* First parse staged info using git-diff-index(1), then parse unstaged
4779  * info using git-diff-files(1), and finally untracked files using
4780  * git-ls-files(1). */
4781 static bool
4782 status_open(struct view *view)
4784         reset_view(view);
4786         add_line_data(view, NULL, LINE_STAT_HEAD);
4787         status_update_onbranch();
4789         run_io_bg(update_index_argv);
4791         if (is_initial_commit()) {
4792                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4793                         return FALSE;
4794         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4795                 return FALSE;
4796         }
4798         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4799             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4800                 return FALSE;
4802         /* Restore the exact position or use the specialized restore
4803          * mode? */
4804         if (!view->p_restore)
4805                 status_restore(view);
4806         return TRUE;
4809 static bool
4810 status_draw(struct view *view, struct line *line, unsigned int lineno)
4812         struct status *status = line->data;
4813         enum line_type type;
4814         const char *text;
4816         if (!status) {
4817                 switch (line->type) {
4818                 case LINE_STAT_STAGED:
4819                         type = LINE_STAT_SECTION;
4820                         text = "Changes to be committed:";
4821                         break;
4823                 case LINE_STAT_UNSTAGED:
4824                         type = LINE_STAT_SECTION;
4825                         text = "Changed but not updated:";
4826                         break;
4828                 case LINE_STAT_UNTRACKED:
4829                         type = LINE_STAT_SECTION;
4830                         text = "Untracked files:";
4831                         break;
4833                 case LINE_STAT_NONE:
4834                         type = LINE_DEFAULT;
4835                         text = "  (no files)";
4836                         break;
4838                 case LINE_STAT_HEAD:
4839                         type = LINE_STAT_HEAD;
4840                         text = status_onbranch;
4841                         break;
4843                 default:
4844                         return FALSE;
4845                 }
4846         } else {
4847                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4849                 buf[0] = status->status;
4850                 if (draw_text(view, line->type, buf, TRUE))
4851                         return TRUE;
4852                 type = LINE_DEFAULT;
4853                 text = status->new.name;
4854         }
4856         draw_text(view, type, text, TRUE);
4857         return TRUE;
4860 static enum request
4861 status_enter(struct view *view, struct line *line)
4863         struct status *status = line->data;
4864         const char *oldpath = status ? status->old.name : NULL;
4865         /* Diffs for unmerged entries are empty when passing the new
4866          * path, so leave it empty. */
4867         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4868         const char *info;
4869         enum open_flags split;
4870         struct view *stage = VIEW(REQ_VIEW_STAGE);
4872         if (line->type == LINE_STAT_NONE ||
4873             (!status && line[1].type == LINE_STAT_NONE)) {
4874                 report("No file to diff");
4875                 return REQ_NONE;
4876         }
4878         switch (line->type) {
4879         case LINE_STAT_STAGED:
4880                 if (is_initial_commit()) {
4881                         const char *no_head_diff_argv[] = {
4882                                 "git", "diff", "--no-color", "--patch-with-stat",
4883                                         "--", "/dev/null", newpath, NULL
4884                         };
4886                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4887                                 return REQ_QUIT;
4888                 } else {
4889                         const char *index_show_argv[] = {
4890                                 "git", "diff-index", "--root", "--patch-with-stat",
4891                                         "-C", "-M", "--cached", "HEAD", "--",
4892                                         oldpath, newpath, NULL
4893                         };
4895                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4896                                 return REQ_QUIT;
4897                 }
4899                 if (status)
4900                         info = "Staged changes to %s";
4901                 else
4902                         info = "Staged changes";
4903                 break;
4905         case LINE_STAT_UNSTAGED:
4906         {
4907                 const char *files_show_argv[] = {
4908                         "git", "diff-files", "--root", "--patch-with-stat",
4909                                 "-C", "-M", "--", oldpath, newpath, NULL
4910                 };
4912                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4913                         return REQ_QUIT;
4914                 if (status)
4915                         info = "Unstaged changes to %s";
4916                 else
4917                         info = "Unstaged changes";
4918                 break;
4919         }
4920         case LINE_STAT_UNTRACKED:
4921                 if (!newpath) {
4922                         report("No file to show");
4923                         return REQ_NONE;
4924                 }
4926                 if (!suffixcmp(status->new.name, -1, "/")) {
4927                         report("Cannot display a directory");
4928                         return REQ_NONE;
4929                 }
4931                 if (!prepare_update_file(stage, newpath))
4932                         return REQ_QUIT;
4933                 info = "Untracked file %s";
4934                 break;
4936         case LINE_STAT_HEAD:
4937                 return REQ_NONE;
4939         default:
4940                 die("line type %d not handled in switch", line->type);
4941         }
4943         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4944         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4945         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4946                 if (status) {
4947                         stage_status = *status;
4948                 } else {
4949                         memset(&stage_status, 0, sizeof(stage_status));
4950                 }
4952                 stage_line_type = line->type;
4953                 stage_chunks = 0;
4954                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4955         }
4957         return REQ_NONE;
4960 static bool
4961 status_exists(struct status *status, enum line_type type)
4963         struct view *view = VIEW(REQ_VIEW_STATUS);
4964         unsigned long lineno;
4966         for (lineno = 0; lineno < view->lines; lineno++) {
4967                 struct line *line = &view->line[lineno];
4968                 struct status *pos = line->data;
4970                 if (line->type != type)
4971                         continue;
4972                 if (!pos && (!status || !status->status) && line[1].data) {
4973                         select_view_line(view, lineno);
4974                         return TRUE;
4975                 }
4976                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4977                         select_view_line(view, lineno);
4978                         return TRUE;
4979                 }
4980         }
4982         return FALSE;
4986 static bool
4987 status_update_prepare(struct io *io, enum line_type type)
4989         const char *staged_argv[] = {
4990                 "git", "update-index", "-z", "--index-info", NULL
4991         };
4992         const char *others_argv[] = {
4993                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4994         };
4996         switch (type) {
4997         case LINE_STAT_STAGED:
4998                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5000         case LINE_STAT_UNSTAGED:
5001                 return run_io(io, others_argv, opt_cdup, IO_WR);
5003         case LINE_STAT_UNTRACKED:
5004                 return run_io(io, others_argv, NULL, IO_WR);
5006         default:
5007                 die("line type %d not handled in switch", type);
5008                 return FALSE;
5009         }
5012 static bool
5013 status_update_write(struct io *io, struct status *status, enum line_type type)
5015         char buf[SIZEOF_STR];
5016         size_t bufsize = 0;
5018         switch (type) {
5019         case LINE_STAT_STAGED:
5020                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5021                                         status->old.mode,
5022                                         status->old.rev,
5023                                         status->old.name, 0))
5024                         return FALSE;
5025                 break;
5027         case LINE_STAT_UNSTAGED:
5028         case LINE_STAT_UNTRACKED:
5029                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5030                         return FALSE;
5031                 break;
5033         default:
5034                 die("line type %d not handled in switch", type);
5035         }
5037         return io_write(io, buf, bufsize);
5040 static bool
5041 status_update_file(struct status *status, enum line_type type)
5043         struct io io = {};
5044         bool result;
5046         if (!status_update_prepare(&io, type))
5047                 return FALSE;
5049         result = status_update_write(&io, status, type);
5050         done_io(&io);
5051         return result;
5054 static bool
5055 status_update_files(struct view *view, struct line *line)
5057         struct io io = {};
5058         bool result = TRUE;
5059         struct line *pos = view->line + view->lines;
5060         int files = 0;
5061         int file, done;
5063         if (!status_update_prepare(&io, line->type))
5064                 return FALSE;
5066         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5067                 files++;
5069         for (file = 0, done = 0; result && file < files; line++, file++) {
5070                 int almost_done = file * 100 / files;
5072                 if (almost_done > done) {
5073                         done = almost_done;
5074                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5075                                       file, files, done);
5076                         update_view_title(view);
5077                 }
5078                 result = status_update_write(&io, line->data, line->type);
5079         }
5081         done_io(&io);
5082         return result;
5085 static bool
5086 status_update(struct view *view)
5088         struct line *line = &view->line[view->lineno];
5090         assert(view->lines);
5092         if (!line->data) {
5093                 /* This should work even for the "On branch" line. */
5094                 if (line < view->line + view->lines && !line[1].data) {
5095                         report("Nothing to update");
5096                         return FALSE;
5097                 }
5099                 if (!status_update_files(view, line + 1)) {
5100                         report("Failed to update file status");
5101                         return FALSE;
5102                 }
5104         } else if (!status_update_file(line->data, line->type)) {
5105                 report("Failed to update file status");
5106                 return FALSE;
5107         }
5109         return TRUE;
5112 static bool
5113 status_revert(struct status *status, enum line_type type, bool has_none)
5115         if (!status || type != LINE_STAT_UNSTAGED) {
5116                 if (type == LINE_STAT_STAGED) {
5117                         report("Cannot revert changes to staged files");
5118                 } else if (type == LINE_STAT_UNTRACKED) {
5119                         report("Cannot revert changes to untracked files");
5120                 } else if (has_none) {
5121                         report("Nothing to revert");
5122                 } else {
5123                         report("Cannot revert changes to multiple files");
5124                 }
5125                 return FALSE;
5127         } else {
5128                 char mode[10] = "100644";
5129                 const char *reset_argv[] = {
5130                         "git", "update-index", "--cacheinfo", mode,
5131                                 status->old.rev, status->old.name, NULL
5132                 };
5133                 const char *checkout_argv[] = {
5134                         "git", "checkout", "--", status->old.name, NULL
5135                 };
5137                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5138                         return FALSE;
5139                 string_format(mode, "%o", status->old.mode);
5140                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5141                         run_io_fg(checkout_argv, opt_cdup);
5142         }
5145 static enum request
5146 status_request(struct view *view, enum request request, struct line *line)
5148         struct status *status = line->data;
5150         switch (request) {
5151         case REQ_STATUS_UPDATE:
5152                 if (!status_update(view))
5153                         return REQ_NONE;
5154                 break;
5156         case REQ_STATUS_REVERT:
5157                 if (!status_revert(status, line->type, status_has_none(view, line)))
5158                         return REQ_NONE;
5159                 break;
5161         case REQ_STATUS_MERGE:
5162                 if (!status || status->status != 'U') {
5163                         report("Merging only possible for files with unmerged status ('U').");
5164                         return REQ_NONE;
5165                 }
5166                 open_mergetool(status->new.name);
5167                 break;
5169         case REQ_EDIT:
5170                 if (!status)
5171                         return request;
5172                 if (status->status == 'D') {
5173                         report("File has been deleted.");
5174                         return REQ_NONE;
5175                 }
5177                 open_editor(status->status != '?', status->new.name);
5178                 break;
5180         case REQ_VIEW_BLAME:
5181                 if (status) {
5182                         string_copy(opt_file, status->new.name);
5183                         opt_ref[0] = 0;
5184                 }
5185                 return request;
5187         case REQ_ENTER:
5188                 /* After returning the status view has been split to
5189                  * show the stage view. No further reloading is
5190                  * necessary. */
5191                 status_enter(view, line);
5192                 return REQ_NONE;
5194         case REQ_REFRESH:
5195                 /* Simply reload the view. */
5196                 break;
5198         default:
5199                 return request;
5200         }
5202         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5204         return REQ_NONE;
5207 static void
5208 status_select(struct view *view, struct line *line)
5210         struct status *status = line->data;
5211         char file[SIZEOF_STR] = "all files";
5212         const char *text;
5213         const char *key;
5215         if (status && !string_format(file, "'%s'", status->new.name))
5216                 return;
5218         if (!status && line[1].type == LINE_STAT_NONE)
5219                 line++;
5221         switch (line->type) {
5222         case LINE_STAT_STAGED:
5223                 text = "Press %s to unstage %s for commit";
5224                 break;
5226         case LINE_STAT_UNSTAGED:
5227                 text = "Press %s to stage %s for commit";
5228                 break;
5230         case LINE_STAT_UNTRACKED:
5231                 text = "Press %s to stage %s for addition";
5232                 break;
5234         case LINE_STAT_HEAD:
5235         case LINE_STAT_NONE:
5236                 text = "Nothing to update";
5237                 break;
5239         default:
5240                 die("line type %d not handled in switch", line->type);
5241         }
5243         if (status && status->status == 'U') {
5244                 text = "Press %s to resolve conflict in %s";
5245                 key = get_key(REQ_STATUS_MERGE);
5247         } else {
5248                 key = get_key(REQ_STATUS_UPDATE);
5249         }
5251         string_format(view->ref, text, key, file);
5254 static bool
5255 status_grep(struct view *view, struct line *line)
5257         struct status *status = line->data;
5258         enum { S_STATUS, S_NAME, S_END } state;
5259         char buf[2] = "?";
5260         regmatch_t pmatch;
5262         if (!status)
5263                 return FALSE;
5265         for (state = S_STATUS; state < S_END; state++) {
5266                 const char *text;
5268                 switch (state) {
5269                 case S_NAME:    text = status->new.name;        break;
5270                 case S_STATUS:
5271                         buf[0] = status->status;
5272                         text = buf;
5273                         break;
5275                 default:
5276                         return FALSE;
5277                 }
5279                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5280                         return TRUE;
5281         }
5283         return FALSE;
5286 static struct view_ops status_ops = {
5287         "file",
5288         NULL,
5289         status_open,
5290         NULL,
5291         status_draw,
5292         status_request,
5293         status_grep,
5294         status_select,
5295 };
5298 static bool
5299 stage_diff_write(struct io *io, struct line *line, struct line *end)
5301         while (line < end) {
5302                 if (!io_write(io, line->data, strlen(line->data)) ||
5303                     !io_write(io, "\n", 1))
5304                         return FALSE;
5305                 line++;
5306                 if (line->type == LINE_DIFF_CHUNK ||
5307                     line->type == LINE_DIFF_HEADER)
5308                         break;
5309         }
5311         return TRUE;
5314 static struct line *
5315 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5317         for (; view->line < line; line--)
5318                 if (line->type == type)
5319                         return line;
5321         return NULL;
5324 static bool
5325 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5327         const char *apply_argv[SIZEOF_ARG] = {
5328                 "git", "apply", "--whitespace=nowarn", NULL
5329         };
5330         struct line *diff_hdr;
5331         struct io io = {};
5332         int argc = 3;
5334         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5335         if (!diff_hdr)
5336                 return FALSE;
5338         if (!revert)
5339                 apply_argv[argc++] = "--cached";
5340         if (revert || stage_line_type == LINE_STAT_STAGED)
5341                 apply_argv[argc++] = "-R";
5342         apply_argv[argc++] = "-";
5343         apply_argv[argc++] = NULL;
5344         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5345                 return FALSE;
5347         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5348             !stage_diff_write(&io, chunk, view->line + view->lines))
5349                 chunk = NULL;
5351         done_io(&io);
5352         run_io_bg(update_index_argv);
5354         return chunk ? TRUE : FALSE;
5357 static bool
5358 stage_update(struct view *view, struct line *line)
5360         struct line *chunk = NULL;
5362         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5363                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5365         if (chunk) {
5366                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5367                         report("Failed to apply chunk");
5368                         return FALSE;
5369                 }
5371         } else if (!stage_status.status) {
5372                 view = VIEW(REQ_VIEW_STATUS);
5374                 for (line = view->line; line < view->line + view->lines; line++)
5375                         if (line->type == stage_line_type)
5376                                 break;
5378                 if (!status_update_files(view, line + 1)) {
5379                         report("Failed to update files");
5380                         return FALSE;
5381                 }
5383         } else if (!status_update_file(&stage_status, stage_line_type)) {
5384                 report("Failed to update file");
5385                 return FALSE;
5386         }
5388         return TRUE;
5391 static bool
5392 stage_revert(struct view *view, struct line *line)
5394         struct line *chunk = NULL;
5396         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5397                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5399         if (chunk) {
5400                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5401                         return FALSE;
5403                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5404                         report("Failed to revert chunk");
5405                         return FALSE;
5406                 }
5407                 return TRUE;
5409         } else {
5410                 return status_revert(stage_status.status ? &stage_status : NULL,
5411                                      stage_line_type, FALSE);
5412         }
5416 static void
5417 stage_next(struct view *view, struct line *line)
5419         int i;
5421         if (!stage_chunks) {
5422                 static size_t alloc = 0;
5423                 int *tmp;
5425                 for (line = view->line; line < view->line + view->lines; line++) {
5426                         if (line->type != LINE_DIFF_CHUNK)
5427                                 continue;
5429                         tmp = realloc_items(stage_chunk, &alloc,
5430                                             stage_chunks, sizeof(*tmp));
5431                         if (!tmp) {
5432                                 report("Allocation failure");
5433                                 return;
5434                         }
5436                         stage_chunk = tmp;
5437                         stage_chunk[stage_chunks++] = line - view->line;
5438                 }
5439         }
5441         for (i = 0; i < stage_chunks; i++) {
5442                 if (stage_chunk[i] > view->lineno) {
5443                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5444                         report("Chunk %d of %d", i + 1, stage_chunks);
5445                         return;
5446                 }
5447         }
5449         report("No next chunk found");
5452 static enum request
5453 stage_request(struct view *view, enum request request, struct line *line)
5455         switch (request) {
5456         case REQ_STATUS_UPDATE:
5457                 if (!stage_update(view, line))
5458                         return REQ_NONE;
5459                 break;
5461         case REQ_STATUS_REVERT:
5462                 if (!stage_revert(view, line))
5463                         return REQ_NONE;
5464                 break;
5466         case REQ_STAGE_NEXT:
5467                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5468                         report("File is untracked; press %s to add",
5469                                get_key(REQ_STATUS_UPDATE));
5470                         return REQ_NONE;
5471                 }
5472                 stage_next(view, line);
5473                 return REQ_NONE;
5475         case REQ_EDIT:
5476                 if (!stage_status.new.name[0])
5477                         return request;
5478                 if (stage_status.status == 'D') {
5479                         report("File has been deleted.");
5480                         return REQ_NONE;
5481                 }
5483                 open_editor(stage_status.status != '?', stage_status.new.name);
5484                 break;
5486         case REQ_REFRESH:
5487                 /* Reload everything ... */
5488                 break;
5490         case REQ_VIEW_BLAME:
5491                 if (stage_status.new.name[0]) {
5492                         string_copy(opt_file, stage_status.new.name);
5493                         opt_ref[0] = 0;
5494                 }
5495                 return request;
5497         case REQ_ENTER:
5498                 return pager_request(view, request, line);
5500         default:
5501                 return request;
5502         }
5504         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5505         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5507         /* Check whether the staged entry still exists, and close the
5508          * stage view if it doesn't. */
5509         if (!status_exists(&stage_status, stage_line_type)) {
5510                 status_restore(VIEW(REQ_VIEW_STATUS));
5511                 return REQ_VIEW_CLOSE;
5512         }
5514         if (stage_line_type == LINE_STAT_UNTRACKED) {
5515                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5516                         report("Cannot display a directory");
5517                         return REQ_NONE;
5518                 }
5520                 if (!prepare_update_file(view, stage_status.new.name)) {
5521                         report("Failed to open file: %s", strerror(errno));
5522                         return REQ_NONE;
5523                 }
5524         }
5525         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5527         return REQ_NONE;
5530 static struct view_ops stage_ops = {
5531         "line",
5532         NULL,
5533         NULL,
5534         pager_read,
5535         pager_draw,
5536         stage_request,
5537         pager_grep,
5538         pager_select,
5539 };
5542 /*
5543  * Revision graph
5544  */
5546 struct commit {
5547         char id[SIZEOF_REV];            /* SHA1 ID. */
5548         char title[128];                /* First line of the commit message. */
5549         char author[75];                /* Author of the commit. */
5550         struct tm time;                 /* Date from the author ident. */
5551         struct ref **refs;              /* Repository references. */
5552         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5553         size_t graph_size;              /* The width of the graph array. */
5554         bool has_parents;               /* Rewritten --parents seen. */
5555 };
5557 /* Size of rev graph with no  "padding" columns */
5558 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5560 struct rev_graph {
5561         struct rev_graph *prev, *next, *parents;
5562         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5563         size_t size;
5564         struct commit *commit;
5565         size_t pos;
5566         unsigned int boundary:1;
5567 };
5569 /* Parents of the commit being visualized. */
5570 static struct rev_graph graph_parents[4];
5572 /* The current stack of revisions on the graph. */
5573 static struct rev_graph graph_stacks[4] = {
5574         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5575         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5576         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5577         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5578 };
5580 static inline bool
5581 graph_parent_is_merge(struct rev_graph *graph)
5583         return graph->parents->size > 1;
5586 static inline void
5587 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5589         struct commit *commit = graph->commit;
5591         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5592                 commit->graph[commit->graph_size++] = symbol;
5595 static void
5596 clear_rev_graph(struct rev_graph *graph)
5598         graph->boundary = 0;
5599         graph->size = graph->pos = 0;
5600         graph->commit = NULL;
5601         memset(graph->parents, 0, sizeof(*graph->parents));
5604 static void
5605 done_rev_graph(struct rev_graph *graph)
5607         if (graph_parent_is_merge(graph) &&
5608             graph->pos < graph->size - 1 &&
5609             graph->next->size == graph->size + graph->parents->size - 1) {
5610                 size_t i = graph->pos + graph->parents->size - 1;
5612                 graph->commit->graph_size = i * 2;
5613                 while (i < graph->next->size - 1) {
5614                         append_to_rev_graph(graph, ' ');
5615                         append_to_rev_graph(graph, '\\');
5616                         i++;
5617                 }
5618         }
5620         clear_rev_graph(graph);
5623 static void
5624 push_rev_graph(struct rev_graph *graph, const char *parent)
5626         int i;
5628         /* "Collapse" duplicate parents lines.
5629          *
5630          * FIXME: This needs to also update update the drawn graph but
5631          * for now it just serves as a method for pruning graph lines. */
5632         for (i = 0; i < graph->size; i++)
5633                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5634                         return;
5636         if (graph->size < SIZEOF_REVITEMS) {
5637                 string_copy_rev(graph->rev[graph->size++], parent);
5638         }
5641 static chtype
5642 get_rev_graph_symbol(struct rev_graph *graph)
5644         chtype symbol;
5646         if (graph->boundary)
5647                 symbol = REVGRAPH_BOUND;
5648         else if (graph->parents->size == 0)
5649                 symbol = REVGRAPH_INIT;
5650         else if (graph_parent_is_merge(graph))
5651                 symbol = REVGRAPH_MERGE;
5652         else if (graph->pos >= graph->size)
5653                 symbol = REVGRAPH_BRANCH;
5654         else
5655                 symbol = REVGRAPH_COMMIT;
5657         return symbol;
5660 static void
5661 draw_rev_graph(struct rev_graph *graph)
5663         struct rev_filler {
5664                 chtype separator, line;
5665         };
5666         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5667         static struct rev_filler fillers[] = {
5668                 { ' ',  '|' },
5669                 { '`',  '.' },
5670                 { '\'', ' ' },
5671                 { '/',  ' ' },
5672         };
5673         chtype symbol = get_rev_graph_symbol(graph);
5674         struct rev_filler *filler;
5675         size_t i;
5677         if (opt_line_graphics)
5678                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5680         filler = &fillers[DEFAULT];
5682         for (i = 0; i < graph->pos; i++) {
5683                 append_to_rev_graph(graph, filler->line);
5684                 if (graph_parent_is_merge(graph->prev) &&
5685                     graph->prev->pos == i)
5686                         filler = &fillers[RSHARP];
5688                 append_to_rev_graph(graph, filler->separator);
5689         }
5691         /* Place the symbol for this revision. */
5692         append_to_rev_graph(graph, symbol);
5694         if (graph->prev->size > graph->size)
5695                 filler = &fillers[RDIAG];
5696         else
5697                 filler = &fillers[DEFAULT];
5699         i++;
5701         for (; i < graph->size; i++) {
5702                 append_to_rev_graph(graph, filler->separator);
5703                 append_to_rev_graph(graph, filler->line);
5704                 if (graph_parent_is_merge(graph->prev) &&
5705                     i < graph->prev->pos + graph->parents->size)
5706                         filler = &fillers[RSHARP];
5707                 if (graph->prev->size > graph->size)
5708                         filler = &fillers[LDIAG];
5709         }
5711         if (graph->prev->size > graph->size) {
5712                 append_to_rev_graph(graph, filler->separator);
5713                 if (filler->line != ' ')
5714                         append_to_rev_graph(graph, filler->line);
5715         }
5718 /* Prepare the next rev graph */
5719 static void
5720 prepare_rev_graph(struct rev_graph *graph)
5722         size_t i;
5724         /* First, traverse all lines of revisions up to the active one. */
5725         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5726                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5727                         break;
5729                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5730         }
5732         /* Interleave the new revision parent(s). */
5733         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5734                 push_rev_graph(graph->next, graph->parents->rev[i]);
5736         /* Lastly, put any remaining revisions. */
5737         for (i = graph->pos + 1; i < graph->size; i++)
5738                 push_rev_graph(graph->next, graph->rev[i]);
5741 static void
5742 update_rev_graph(struct view *view, struct rev_graph *graph)
5744         /* If this is the finalizing update ... */
5745         if (graph->commit)
5746                 prepare_rev_graph(graph);
5748         /* Graph visualization needs a one rev look-ahead,
5749          * so the first update doesn't visualize anything. */
5750         if (!graph->prev->commit)
5751                 return;
5753         if (view->lines > 2)
5754                 view->line[view->lines - 3].dirty = 1;
5755         if (view->lines > 1)
5756                 view->line[view->lines - 2].dirty = 1;
5757         draw_rev_graph(graph->prev);
5758         done_rev_graph(graph->prev->prev);
5762 /*
5763  * Main view backend
5764  */
5766 static const char *main_argv[SIZEOF_ARG] = {
5767         "git", "log", "--no-color", "--pretty=raw", "--parents",
5768                       "--topo-order", "%(head)", NULL
5769 };
5771 static bool
5772 main_draw(struct view *view, struct line *line, unsigned int lineno)
5774         struct commit *commit = line->data;
5776         if (!*commit->author)
5777                 return FALSE;
5779         if (opt_date && draw_date(view, &commit->time))
5780                 return TRUE;
5782         if (opt_author && draw_author(view, commit->author))
5783                 return TRUE;
5785         if (opt_rev_graph && commit->graph_size &&
5786             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5787                 return TRUE;
5789         if (opt_show_refs && commit->refs) {
5790                 size_t i = 0;
5792                 do {
5793                         enum line_type type;
5795                         if (commit->refs[i]->head)
5796                                 type = LINE_MAIN_HEAD;
5797                         else if (commit->refs[i]->ltag)
5798                                 type = LINE_MAIN_LOCAL_TAG;
5799                         else if (commit->refs[i]->tag)
5800                                 type = LINE_MAIN_TAG;
5801                         else if (commit->refs[i]->tracked)
5802                                 type = LINE_MAIN_TRACKED;
5803                         else if (commit->refs[i]->remote)
5804                                 type = LINE_MAIN_REMOTE;
5805                         else
5806                                 type = LINE_MAIN_REF;
5808                         if (draw_text(view, type, "[", TRUE) ||
5809                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5810                             draw_text(view, type, "]", TRUE))
5811                                 return TRUE;
5813                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5814                                 return TRUE;
5815                 } while (commit->refs[i++]->next);
5816         }
5818         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5819         return TRUE;
5822 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5823 static bool
5824 main_read(struct view *view, char *line)
5826         static struct rev_graph *graph = graph_stacks;
5827         enum line_type type;
5828         struct commit *commit;
5830         if (!line) {
5831                 int i;
5833                 if (!view->lines && !view->parent)
5834                         die("No revisions match the given arguments.");
5835                 if (view->lines > 0) {
5836                         commit = view->line[view->lines - 1].data;
5837                         view->line[view->lines - 1].dirty = 1;
5838                         if (!*commit->author) {
5839                                 view->lines--;
5840                                 free(commit);
5841                                 graph->commit = NULL;
5842                         }
5843                 }
5844                 update_rev_graph(view, graph);
5846                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5847                         clear_rev_graph(&graph_stacks[i]);
5848                 return TRUE;
5849         }
5851         type = get_line_type(line);
5852         if (type == LINE_COMMIT) {
5853                 commit = calloc(1, sizeof(struct commit));
5854                 if (!commit)
5855                         return FALSE;
5857                 line += STRING_SIZE("commit ");
5858                 if (*line == '-') {
5859                         graph->boundary = 1;
5860                         line++;
5861                 }
5863                 string_copy_rev(commit->id, line);
5864                 commit->refs = get_refs(commit->id);
5865                 graph->commit = commit;
5866                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5868                 while ((line = strchr(line, ' '))) {
5869                         line++;
5870                         push_rev_graph(graph->parents, line);
5871                         commit->has_parents = TRUE;
5872                 }
5873                 return TRUE;
5874         }
5876         if (!view->lines)
5877                 return TRUE;
5878         commit = view->line[view->lines - 1].data;
5880         switch (type) {
5881         case LINE_PARENT:
5882                 if (commit->has_parents)
5883                         break;
5884                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5885                 break;
5887         case LINE_AUTHOR:
5888                 parse_author_line(line + STRING_SIZE("author "),
5889                                   commit->author, sizeof(commit->author),
5890                                   &commit->time);
5891                 update_rev_graph(view, graph);
5892                 graph = graph->next;
5893                 break;
5895         default:
5896                 /* Fill in the commit title if it has not already been set. */
5897                 if (commit->title[0])
5898                         break;
5900                 /* Require titles to start with a non-space character at the
5901                  * offset used by git log. */
5902                 if (strncmp(line, "    ", 4))
5903                         break;
5904                 line += 4;
5905                 /* Well, if the title starts with a whitespace character,
5906                  * try to be forgiving.  Otherwise we end up with no title. */
5907                 while (isspace(*line))
5908                         line++;
5909                 if (*line == '\0')
5910                         break;
5911                 /* FIXME: More graceful handling of titles; append "..." to
5912                  * shortened titles, etc. */
5914                 string_expand(commit->title, sizeof(commit->title), line, 1);
5915                 view->line[view->lines - 1].dirty = 1;
5916         }
5918         return TRUE;
5921 static enum request
5922 main_request(struct view *view, enum request request, struct line *line)
5924         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5926         switch (request) {
5927         case REQ_ENTER:
5928                 open_view(view, REQ_VIEW_DIFF, flags);
5929                 break;
5930         case REQ_REFRESH:
5931                 load_refs();
5932                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5933                 break;
5934         default:
5935                 return request;
5936         }
5938         return REQ_NONE;
5941 static bool
5942 grep_refs(struct ref **refs, regex_t *regex)
5944         regmatch_t pmatch;
5945         size_t i = 0;
5947         if (!refs)
5948                 return FALSE;
5949         do {
5950                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5951                         return TRUE;
5952         } while (refs[i++]->next);
5954         return FALSE;
5957 static bool
5958 main_grep(struct view *view, struct line *line)
5960         struct commit *commit = line->data;
5961         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5962         char buf[DATE_COLS + 1];
5963         regmatch_t pmatch;
5965         for (state = S_TITLE; state < S_END; state++) {
5966                 char *text;
5968                 switch (state) {
5969                 case S_TITLE:   text = commit->title;   break;
5970                 case S_AUTHOR:
5971                         if (!opt_author)
5972                                 continue;
5973                         text = commit->author;
5974                         break;
5975                 case S_DATE:
5976                         if (!opt_date)
5977                                 continue;
5978                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5979                                 continue;
5980                         text = buf;
5981                         break;
5982                 case S_REFS:
5983                         if (!opt_show_refs)
5984                                 continue;
5985                         if (grep_refs(commit->refs, view->regex) == TRUE)
5986                                 return TRUE;
5987                         continue;
5988                 default:
5989                         return FALSE;
5990                 }
5992                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5993                         return TRUE;
5994         }
5996         return FALSE;
5999 static void
6000 main_select(struct view *view, struct line *line)
6002         struct commit *commit = line->data;
6004         string_copy_rev(view->ref, commit->id);
6005         string_copy_rev(ref_commit, view->ref);
6008 static struct view_ops main_ops = {
6009         "commit",
6010         main_argv,
6011         NULL,
6012         main_read,
6013         main_draw,
6014         main_request,
6015         main_grep,
6016         main_select,
6017 };
6020 /*
6021  * Unicode / UTF-8 handling
6022  *
6023  * NOTE: Much of the following code for dealing with Unicode is derived from
6024  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6025  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6026  */
6028 static inline int
6029 unicode_width(unsigned long c)
6031         if (c >= 0x1100 &&
6032            (c <= 0x115f                         /* Hangul Jamo */
6033             || c == 0x2329
6034             || c == 0x232a
6035             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6036                                                 /* CJK ... Yi */
6037             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6038             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6039             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6040             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6041             || (c >= 0xffe0  && c <= 0xffe6)
6042             || (c >= 0x20000 && c <= 0x2fffd)
6043             || (c >= 0x30000 && c <= 0x3fffd)))
6044                 return 2;
6046         if (c == '\t')
6047                 return opt_tab_size;
6049         return 1;
6052 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6053  * Illegal bytes are set one. */
6054 static const unsigned char utf8_bytes[256] = {
6055         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,
6056         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,
6057         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,
6058         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,
6059         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,
6060         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,
6061         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,
6062         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,
6063 };
6065 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6066 static inline unsigned long
6067 utf8_to_unicode(const char *string, size_t length)
6069         unsigned long unicode;
6071         switch (length) {
6072         case 1:
6073                 unicode  =   string[0];
6074                 break;
6075         case 2:
6076                 unicode  =  (string[0] & 0x1f) << 6;
6077                 unicode +=  (string[1] & 0x3f);
6078                 break;
6079         case 3:
6080                 unicode  =  (string[0] & 0x0f) << 12;
6081                 unicode += ((string[1] & 0x3f) << 6);
6082                 unicode +=  (string[2] & 0x3f);
6083                 break;
6084         case 4:
6085                 unicode  =  (string[0] & 0x0f) << 18;
6086                 unicode += ((string[1] & 0x3f) << 12);
6087                 unicode += ((string[2] & 0x3f) << 6);
6088                 unicode +=  (string[3] & 0x3f);
6089                 break;
6090         case 5:
6091                 unicode  =  (string[0] & 0x0f) << 24;
6092                 unicode += ((string[1] & 0x3f) << 18);
6093                 unicode += ((string[2] & 0x3f) << 12);
6094                 unicode += ((string[3] & 0x3f) << 6);
6095                 unicode +=  (string[4] & 0x3f);
6096                 break;
6097         case 6:
6098                 unicode  =  (string[0] & 0x01) << 30;
6099                 unicode += ((string[1] & 0x3f) << 24);
6100                 unicode += ((string[2] & 0x3f) << 18);
6101                 unicode += ((string[3] & 0x3f) << 12);
6102                 unicode += ((string[4] & 0x3f) << 6);
6103                 unicode +=  (string[5] & 0x3f);
6104                 break;
6105         default:
6106                 die("Invalid Unicode length");
6107         }
6109         /* Invalid characters could return the special 0xfffd value but NUL
6110          * should be just as good. */
6111         return unicode > 0xffff ? 0 : unicode;
6114 /* Calculates how much of string can be shown within the given maximum width
6115  * and sets trimmed parameter to non-zero value if all of string could not be
6116  * shown. If the reserve flag is TRUE, it will reserve at least one
6117  * trailing character, which can be useful when drawing a delimiter.
6118  *
6119  * Returns the number of bytes to output from string to satisfy max_width. */
6120 static size_t
6121 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6123         const char *string = *start;
6124         const char *end = strchr(string, '\0');
6125         unsigned char last_bytes = 0;
6126         size_t last_ucwidth = 0;
6128         *width = 0;
6129         *trimmed = 0;
6131         while (string < end) {
6132                 int c = *(unsigned char *) string;
6133                 unsigned char bytes = utf8_bytes[c];
6134                 size_t ucwidth;
6135                 unsigned long unicode;
6137                 if (string + bytes > end)
6138                         break;
6140                 /* Change representation to figure out whether
6141                  * it is a single- or double-width character. */
6143                 unicode = utf8_to_unicode(string, bytes);
6144                 /* FIXME: Graceful handling of invalid Unicode character. */
6145                 if (!unicode)
6146                         break;
6148                 ucwidth = unicode_width(unicode);
6149                 if (skip > 0) {
6150                         skip -= ucwidth <= skip ? ucwidth : skip;
6151                         *start += bytes;
6152                 }
6153                 *width  += ucwidth;
6154                 if (*width > max_width) {
6155                         *trimmed = 1;
6156                         *width -= ucwidth;
6157                         if (reserve && *width == max_width) {
6158                                 string -= last_bytes;
6159                                 *width -= last_ucwidth;
6160                         }
6161                         break;
6162                 }
6164                 string  += bytes;
6165                 last_bytes = ucwidth ? bytes : 0;
6166                 last_ucwidth = ucwidth;
6167         }
6169         return string - *start;
6173 /*
6174  * Status management
6175  */
6177 /* Whether or not the curses interface has been initialized. */
6178 static bool cursed = FALSE;
6180 /* Terminal hacks and workarounds. */
6181 static bool use_scroll_redrawwin;
6182 static bool use_scroll_status_wclear;
6184 /* The status window is used for polling keystrokes. */
6185 static WINDOW *status_win;
6187 /* Reading from the prompt? */
6188 static bool input_mode = FALSE;
6190 static bool status_empty = FALSE;
6192 /* Update status and title window. */
6193 static void
6194 report(const char *msg, ...)
6196         struct view *view = display[current_view];
6198         if (input_mode)
6199                 return;
6201         if (!view) {
6202                 char buf[SIZEOF_STR];
6203                 va_list args;
6205                 va_start(args, msg);
6206                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6207                         buf[sizeof(buf) - 1] = 0;
6208                         buf[sizeof(buf) - 2] = '.';
6209                         buf[sizeof(buf) - 3] = '.';
6210                         buf[sizeof(buf) - 4] = '.';
6211                 }
6212                 va_end(args);
6213                 die("%s", buf);
6214         }
6216         if (!status_empty || *msg) {
6217                 va_list args;
6219                 va_start(args, msg);
6221                 wmove(status_win, 0, 0);
6222                 if (view->has_scrolled && use_scroll_status_wclear)
6223                         wclear(status_win);
6224                 if (*msg) {
6225                         vwprintw(status_win, msg, args);
6226                         status_empty = FALSE;
6227                 } else {
6228                         status_empty = TRUE;
6229                 }
6230                 wclrtoeol(status_win);
6231                 wnoutrefresh(status_win);
6233                 va_end(args);
6234         }
6236         update_view_title(view);
6239 /* Controls when nodelay should be in effect when polling user input. */
6240 static void
6241 set_nonblocking_input(bool loading)
6243         static unsigned int loading_views;
6245         if ((loading == FALSE && loading_views-- == 1) ||
6246             (loading == TRUE  && loading_views++ == 0))
6247                 nodelay(status_win, loading);
6250 static void
6251 init_display(void)
6253         const char *term;
6254         int x, y;
6256         /* Initialize the curses library */
6257         if (isatty(STDIN_FILENO)) {
6258                 cursed = !!initscr();
6259                 opt_tty = stdin;
6260         } else {
6261                 /* Leave stdin and stdout alone when acting as a pager. */
6262                 opt_tty = fopen("/dev/tty", "r+");
6263                 if (!opt_tty)
6264                         die("Failed to open /dev/tty");
6265                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6266         }
6268         if (!cursed)
6269                 die("Failed to initialize curses");
6271         nonl();         /* Disable conversion and detect newlines from input. */
6272         cbreak();       /* Take input chars one at a time, no wait for \n */
6273         noecho();       /* Don't echo input */
6274         leaveok(stdscr, FALSE);
6276         if (has_colors())
6277                 init_colors();
6279         getmaxyx(stdscr, y, x);
6280         status_win = newwin(1, 0, y - 1, 0);
6281         if (!status_win)
6282                 die("Failed to create status window");
6284         /* Enable keyboard mapping */
6285         keypad(status_win, TRUE);
6286         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6288         TABSIZE = opt_tab_size;
6289         if (opt_line_graphics) {
6290                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6291         }
6293         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6294         if (term && !strcmp(term, "gnome-terminal")) {
6295                 /* In the gnome-terminal-emulator, the message from
6296                  * scrolling up one line when impossible followed by
6297                  * scrolling down one line causes corruption of the
6298                  * status line. This is fixed by calling wclear. */
6299                 use_scroll_status_wclear = TRUE;
6300                 use_scroll_redrawwin = FALSE;
6302         } else if (term && !strcmp(term, "xrvt-xpm")) {
6303                 /* No problems with full optimizations in xrvt-(unicode)
6304                  * and aterm. */
6305                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6307         } else {
6308                 /* When scrolling in (u)xterm the last line in the
6309                  * scrolling direction will update slowly. */
6310                 use_scroll_redrawwin = TRUE;
6311                 use_scroll_status_wclear = FALSE;
6312         }
6315 static int
6316 get_input(int prompt_position)
6318         struct view *view;
6319         int i, key, cursor_y, cursor_x;
6321         if (prompt_position)
6322                 input_mode = TRUE;
6324         while (TRUE) {
6325                 foreach_view (view, i) {
6326                         update_view(view);
6327                         if (view_is_displayed(view) && view->has_scrolled &&
6328                             use_scroll_redrawwin)
6329                                 redrawwin(view->win);
6330                         view->has_scrolled = FALSE;
6331                 }
6333                 /* Update the cursor position. */
6334                 if (prompt_position) {
6335                         getbegyx(status_win, cursor_y, cursor_x);
6336                         cursor_x = prompt_position;
6337                 } else {
6338                         view = display[current_view];
6339                         getbegyx(view->win, cursor_y, cursor_x);
6340                         cursor_x = view->width - 1;
6341                         cursor_y += view->lineno - view->offset;
6342                 }
6343                 setsyx(cursor_y, cursor_x);
6345                 /* Refresh, accept single keystroke of input */
6346                 doupdate();
6347                 key = wgetch(status_win);
6349                 /* wgetch() with nodelay() enabled returns ERR when
6350                  * there's no input. */
6351                 if (key == ERR) {
6353                 } else if (key == KEY_RESIZE) {
6354                         int height, width;
6356                         getmaxyx(stdscr, height, width);
6358                         wresize(status_win, 1, width);
6359                         mvwin(status_win, height - 1, 0);
6360                         wnoutrefresh(status_win);
6361                         resize_display();
6362                         redraw_display(TRUE);
6364                 } else {
6365                         input_mode = FALSE;
6366                         return key;
6367                 }
6368         }
6371 static char *
6372 prompt_input(const char *prompt, input_handler handler, void *data)
6374         enum input_status status = INPUT_OK;
6375         static char buf[SIZEOF_STR];
6376         size_t pos = 0;
6378         buf[pos] = 0;
6380         while (status == INPUT_OK || status == INPUT_SKIP) {
6381                 int key;
6383                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6384                 wclrtoeol(status_win);
6386                 key = get_input(pos + 1);
6387                 switch (key) {
6388                 case KEY_RETURN:
6389                 case KEY_ENTER:
6390                 case '\n':
6391                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6392                         break;
6394                 case KEY_BACKSPACE:
6395                         if (pos > 0)
6396                                 buf[--pos] = 0;
6397                         else
6398                                 status = INPUT_CANCEL;
6399                         break;
6401                 case KEY_ESC:
6402                         status = INPUT_CANCEL;
6403                         break;
6405                 default:
6406                         if (pos >= sizeof(buf)) {
6407                                 report("Input string too long");
6408                                 return NULL;
6409                         }
6411                         status = handler(data, buf, key);
6412                         if (status == INPUT_OK)
6413                                 buf[pos++] = (char) key;
6414                 }
6415         }
6417         /* Clear the status window */
6418         status_empty = FALSE;
6419         report("");
6421         if (status == INPUT_CANCEL)
6422                 return NULL;
6424         buf[pos++] = 0;
6426         return buf;
6429 static enum input_status
6430 prompt_yesno_handler(void *data, char *buf, int c)
6432         if (c == 'y' || c == 'Y')
6433                 return INPUT_STOP;
6434         if (c == 'n' || c == 'N')
6435                 return INPUT_CANCEL;
6436         return INPUT_SKIP;
6439 static bool
6440 prompt_yesno(const char *prompt)
6442         char prompt2[SIZEOF_STR];
6444         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6445                 return FALSE;
6447         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6450 static enum input_status
6451 read_prompt_handler(void *data, char *buf, int c)
6453         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6456 static char *
6457 read_prompt(const char *prompt)
6459         return prompt_input(prompt, read_prompt_handler, NULL);
6462 /*
6463  * Repository properties
6464  */
6466 static struct ref *refs = NULL;
6467 static size_t refs_alloc = 0;
6468 static size_t refs_size = 0;
6470 /* Id <-> ref store */
6471 static struct ref ***id_refs = NULL;
6472 static size_t id_refs_alloc = 0;
6473 static size_t id_refs_size = 0;
6475 static int
6476 compare_refs(const void *ref1_, const void *ref2_)
6478         const struct ref *ref1 = *(const struct ref **)ref1_;
6479         const struct ref *ref2 = *(const struct ref **)ref2_;
6481         if (ref1->tag != ref2->tag)
6482                 return ref2->tag - ref1->tag;
6483         if (ref1->ltag != ref2->ltag)
6484                 return ref2->ltag - ref2->ltag;
6485         if (ref1->head != ref2->head)
6486                 return ref2->head - ref1->head;
6487         if (ref1->tracked != ref2->tracked)
6488                 return ref2->tracked - ref1->tracked;
6489         if (ref1->remote != ref2->remote)
6490                 return ref2->remote - ref1->remote;
6491         return strcmp(ref1->name, ref2->name);
6494 static struct ref **
6495 get_refs(const char *id)
6497         struct ref ***tmp_id_refs;
6498         struct ref **ref_list = NULL;
6499         size_t ref_list_alloc = 0;
6500         size_t ref_list_size = 0;
6501         size_t i;
6503         for (i = 0; i < id_refs_size; i++)
6504                 if (!strcmp(id, id_refs[i][0]->id))
6505                         return id_refs[i];
6507         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6508                                     sizeof(*id_refs));
6509         if (!tmp_id_refs)
6510                 return NULL;
6512         id_refs = tmp_id_refs;
6514         for (i = 0; i < refs_size; i++) {
6515                 struct ref **tmp;
6517                 if (strcmp(id, refs[i].id))
6518                         continue;
6520                 tmp = realloc_items(ref_list, &ref_list_alloc,
6521                                     ref_list_size + 1, sizeof(*ref_list));
6522                 if (!tmp) {
6523                         if (ref_list)
6524                                 free(ref_list);
6525                         return NULL;
6526                 }
6528                 ref_list = tmp;
6529                 ref_list[ref_list_size] = &refs[i];
6530                 /* XXX: The properties of the commit chains ensures that we can
6531                  * safely modify the shared ref. The repo references will
6532                  * always be similar for the same id. */
6533                 ref_list[ref_list_size]->next = 1;
6535                 ref_list_size++;
6536         }
6538         if (ref_list) {
6539                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6540                 ref_list[ref_list_size - 1]->next = 0;
6541                 id_refs[id_refs_size++] = ref_list;
6542         }
6544         return ref_list;
6547 static int
6548 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6550         struct ref *ref;
6551         bool tag = FALSE;
6552         bool ltag = FALSE;
6553         bool remote = FALSE;
6554         bool tracked = FALSE;
6555         bool check_replace = FALSE;
6556         bool head = FALSE;
6558         if (!prefixcmp(name, "refs/tags/")) {
6559                 if (!suffixcmp(name, namelen, "^{}")) {
6560                         namelen -= 3;
6561                         name[namelen] = 0;
6562                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6563                                 check_replace = TRUE;
6564                 } else {
6565                         ltag = TRUE;
6566                 }
6568                 tag = TRUE;
6569                 namelen -= STRING_SIZE("refs/tags/");
6570                 name    += STRING_SIZE("refs/tags/");
6572         } else if (!prefixcmp(name, "refs/remotes/")) {
6573                 remote = TRUE;
6574                 namelen -= STRING_SIZE("refs/remotes/");
6575                 name    += STRING_SIZE("refs/remotes/");
6576                 tracked  = !strcmp(opt_remote, name);
6578         } else if (!prefixcmp(name, "refs/heads/")) {
6579                 namelen -= STRING_SIZE("refs/heads/");
6580                 name    += STRING_SIZE("refs/heads/");
6581                 head     = !strncmp(opt_head, name, namelen);
6583         } else if (!strcmp(name, "HEAD")) {
6584                 string_ncopy(opt_head_rev, id, idlen);
6585                 return OK;
6586         }
6588         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6589                 /* it's an annotated tag, replace the previous SHA1 with the
6590                  * resolved commit id; relies on the fact git-ls-remote lists
6591                  * the commit id of an annotated tag right before the commit id
6592                  * it points to. */
6593                 refs[refs_size - 1].ltag = ltag;
6594                 string_copy_rev(refs[refs_size - 1].id, id);
6596                 return OK;
6597         }
6598         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6599         if (!refs)
6600                 return ERR;
6602         ref = &refs[refs_size++];
6603         ref->name = malloc(namelen + 1);
6604         if (!ref->name)
6605                 return ERR;
6607         strncpy(ref->name, name, namelen);
6608         ref->name[namelen] = 0;
6609         ref->head = head;
6610         ref->tag = tag;
6611         ref->ltag = ltag;
6612         ref->remote = remote;
6613         ref->tracked = tracked;
6614         string_copy_rev(ref->id, id);
6616         return OK;
6619 static int
6620 load_refs(void)
6622         static const char *ls_remote_argv[SIZEOF_ARG] = {
6623                 "git", "ls-remote", ".", NULL
6624         };
6625         static bool init = FALSE;
6627         if (!init) {
6628                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6629                 init = TRUE;
6630         }
6632         if (!*opt_git_dir)
6633                 return OK;
6635         while (refs_size > 0)
6636                 free(refs[--refs_size].name);
6637         while (id_refs_size > 0)
6638                 free(id_refs[--id_refs_size]);
6640         return run_io_load(ls_remote_argv, "\t", read_ref);
6643 static void
6644 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6646         const char *argv[SIZEOF_ARG] = { name, "=" };
6647         int argc = 1 + (cmd == option_set_command);
6648         int error = ERR;
6650         if (!argv_from_string(argv, &argc, value))
6651                 config_msg = "Too many option arguments";
6652         else
6653                 error = cmd(argc, argv);
6655         if (error == ERR)
6656                 warn("Option 'tig.%s': %s", name, config_msg);
6659 static int
6660 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6662         if (!strcmp(name, "i18n.commitencoding"))
6663                 string_ncopy(opt_encoding, value, valuelen);
6665         if (!strcmp(name, "core.editor"))
6666                 string_ncopy(opt_editor, value, valuelen);
6668         if (!prefixcmp(name, "tig.color."))
6669                 set_repo_config_option(name + 10, value, option_color_command);
6671         else if (!prefixcmp(name, "tig.bind."))
6672                 set_repo_config_option(name + 9, value, option_bind_command);
6674         else if (!prefixcmp(name, "tig."))
6675                 set_repo_config_option(name + 4, value, option_set_command);
6677         /* branch.<head>.remote */
6678         if (*opt_head &&
6679             !strncmp(name, "branch.", 7) &&
6680             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6681             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6682                 string_ncopy(opt_remote, value, valuelen);
6684         if (*opt_head && *opt_remote &&
6685             !strncmp(name, "branch.", 7) &&
6686             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6687             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6688                 size_t from = strlen(opt_remote);
6690                 if (!prefixcmp(value, "refs/heads/")) {
6691                         value += STRING_SIZE("refs/heads/");
6692                         valuelen -= STRING_SIZE("refs/heads/");
6693                 }
6695                 if (!string_format_from(opt_remote, &from, "/%s", value))
6696                         opt_remote[0] = 0;
6697         }
6699         return OK;
6702 static int
6703 load_git_config(void)
6705         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6707         return run_io_load(config_list_argv, "=", read_repo_config_option);
6710 static int
6711 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6713         if (!opt_git_dir[0]) {
6714                 string_ncopy(opt_git_dir, name, namelen);
6716         } else if (opt_is_inside_work_tree == -1) {
6717                 /* This can be 3 different values depending on the
6718                  * version of git being used. If git-rev-parse does not
6719                  * understand --is-inside-work-tree it will simply echo
6720                  * the option else either "true" or "false" is printed.
6721                  * Default to true for the unknown case. */
6722                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6724         } else if (*name == '.') {
6725                 string_ncopy(opt_cdup, name, namelen);
6727         } else {
6728                 string_ncopy(opt_prefix, name, namelen);
6729         }
6731         return OK;
6734 static int
6735 load_repo_info(void)
6737         const char *head_argv[] = {
6738                 "git", "symbolic-ref", "HEAD", NULL
6739         };
6740         const char *rev_parse_argv[] = {
6741                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6742                         "--show-cdup", "--show-prefix", NULL
6743         };
6745         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6746                 chomp_string(opt_head);
6747                 if (!prefixcmp(opt_head, "refs/heads/")) {
6748                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6750                         memmove(opt_head, offset, strlen(offset) + 1);
6751                 }
6752         }
6754         return run_io_load(rev_parse_argv, "=", read_repo_info);
6758 /*
6759  * Main
6760  */
6762 static const char usage[] =
6763 "tig " TIG_VERSION " (" __DATE__ ")\n"
6764 "\n"
6765 "Usage: tig        [options] [revs] [--] [paths]\n"
6766 "   or: tig show   [options] [revs] [--] [paths]\n"
6767 "   or: tig blame  [rev] path\n"
6768 "   or: tig status\n"
6769 "   or: tig <      [git command output]\n"
6770 "\n"
6771 "Options:\n"
6772 "  -v, --version   Show version and exit\n"
6773 "  -h, --help      Show help message and exit";
6775 static void __NORETURN
6776 quit(int sig)
6778         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6779         if (cursed)
6780                 endwin();
6781         exit(0);
6784 static void __NORETURN
6785 die(const char *err, ...)
6787         va_list args;
6789         endwin();
6791         va_start(args, err);
6792         fputs("tig: ", stderr);
6793         vfprintf(stderr, err, args);
6794         fputs("\n", stderr);
6795         va_end(args);
6797         exit(1);
6800 static void
6801 warn(const char *msg, ...)
6803         va_list args;
6805         va_start(args, msg);
6806         fputs("tig warning: ", stderr);
6807         vfprintf(stderr, msg, args);
6808         fputs("\n", stderr);
6809         va_end(args);
6812 static enum request
6813 parse_options(int argc, const char *argv[])
6815         enum request request = REQ_VIEW_MAIN;
6816         const char *subcommand;
6817         bool seen_dashdash = FALSE;
6818         /* XXX: This is vulnerable to the user overriding options
6819          * required for the main view parser. */
6820         const char *custom_argv[SIZEOF_ARG] = {
6821                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6822                         "--topo-order", NULL
6823         };
6824         int i, j = 6;
6826         if (!isatty(STDIN_FILENO)) {
6827                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6828                 return REQ_VIEW_PAGER;
6829         }
6831         if (argc <= 1)
6832                 return REQ_NONE;
6834         subcommand = argv[1];
6835         if (!strcmp(subcommand, "status")) {
6836                 if (argc > 2)
6837                         warn("ignoring arguments after `%s'", subcommand);
6838                 return REQ_VIEW_STATUS;
6840         } else if (!strcmp(subcommand, "blame")) {
6841                 if (argc <= 2 || argc > 4)
6842                         die("invalid number of options to blame\n\n%s", usage);
6844                 i = 2;
6845                 if (argc == 4) {
6846                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6847                         i++;
6848                 }
6850                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6851                 return REQ_VIEW_BLAME;
6853         } else if (!strcmp(subcommand, "show")) {
6854                 request = REQ_VIEW_DIFF;
6856         } else {
6857                 subcommand = NULL;
6858         }
6860         if (subcommand) {
6861                 custom_argv[1] = subcommand;
6862                 j = 2;
6863         }
6865         for (i = 1 + !!subcommand; i < argc; i++) {
6866                 const char *opt = argv[i];
6868                 if (seen_dashdash || !strcmp(opt, "--")) {
6869                         seen_dashdash = TRUE;
6871                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6872                         printf("tig version %s\n", TIG_VERSION);
6873                         quit(0);
6875                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6876                         printf("%s\n", usage);
6877                         quit(0);
6878                 }
6880                 custom_argv[j++] = opt;
6881                 if (j >= ARRAY_SIZE(custom_argv))
6882                         die("command too long");
6883         }
6885         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6886                 die("Failed to format arguments"); 
6888         return request;
6891 int
6892 main(int argc, const char *argv[])
6894         enum request request = parse_options(argc, argv);
6895         struct view *view;
6896         size_t i;
6898         signal(SIGINT, quit);
6900         if (setlocale(LC_ALL, "")) {
6901                 char *codeset = nl_langinfo(CODESET);
6903                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6904         }
6906         if (load_repo_info() == ERR)
6907                 die("Failed to load repo info.");
6909         if (load_options() == ERR)
6910                 die("Failed to load user config.");
6912         if (load_git_config() == ERR)
6913                 die("Failed to load repo config.");
6915         /* Require a git repository unless when running in pager mode. */
6916         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6917                 die("Not a git repository");
6919         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6920                 opt_utf8 = FALSE;
6922         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6923                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6924                 if (opt_iconv == ICONV_NONE)
6925                         die("Failed to initialize character set conversion");
6926         }
6928         if (load_refs() == ERR)
6929                 die("Failed to load refs.");
6931         foreach_view (view, i)
6932                 argv_from_env(view->ops->argv, view->cmd_env);
6934         init_display();
6936         if (request != REQ_NONE)
6937                 open_view(NULL, request, OPEN_PREPARED);
6938         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6940         while (view_driver(display[current_view], request)) {
6941                 int key = get_input(0);
6943                 view = display[current_view];
6944                 request = get_keybinding(view->keymap, key);
6946                 /* Some low-level request handling. This keeps access to
6947                  * status_win restricted. */
6948                 switch (request) {
6949                 case REQ_PROMPT:
6950                 {
6951                         char *cmd = read_prompt(":");
6953                         if (cmd && isdigit(*cmd)) {
6954                                 int lineno = view->lineno + 1;
6956                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
6957                                         select_view_line(view, lineno - 1);
6958                                         report("");
6959                                 } else {
6960                                         report("Unable to parse '%s' as a line number", cmd);
6961                                 }
6963                         } else if (cmd) {
6964                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6965                                 const char *argv[SIZEOF_ARG] = { "git" };
6966                                 int argc = 1;
6968                                 /* When running random commands, initially show the
6969                                  * command in the title. However, it maybe later be
6970                                  * overwritten if a commit line is selected. */
6971                                 string_ncopy(next->ref, cmd, strlen(cmd));
6973                                 if (!argv_from_string(argv, &argc, cmd)) {
6974                                         report("Too many arguments");
6975                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6976                                         report("Failed to format command");
6977                                 } else {
6978                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6979                                 }
6980                         }
6982                         request = REQ_NONE;
6983                         break;
6984                 }
6985                 case REQ_SEARCH:
6986                 case REQ_SEARCH_BACK:
6987                 {
6988                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6989                         char *search = read_prompt(prompt);
6991                         if (search)
6992                                 string_ncopy(opt_search, search, strlen(search));
6993                         else if (*opt_search)
6994                                 request = request == REQ_SEARCH ?
6995                                         REQ_FIND_NEXT :
6996                                         REQ_FIND_PREV;
6997                         else
6998                                 request = REQ_NONE;
6999                         break;
7000                 }
7001                 default:
7002                         break;
7003                 }
7004         }
7006         quit(0);
7008         return 0;