Code

Simplify searching in view lines by defining grep_text utility
[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
113 #define TAB_SIZE        8
115 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
117 #define NULL_ID         "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #ifndef GIT_CONFIG
122 #define GIT_CONFIG "config"
123 #endif
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_TAB         '\t'
127 #define KEY_RETURN      '\r'
128 #define KEY_ESC         27
131 struct ref {
132         char *name;             /* Ref name; tag or head names are shortened. */
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         unsigned int head:1;    /* Is it the current HEAD? */
135         unsigned int tag:1;     /* Is it a tag? */
136         unsigned int ltag:1;    /* If so, is the tag local? */
137         unsigned int remote:1;  /* Is it a remote ref? */
138         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(const char *id);
144 enum format_flags {
145         FORMAT_ALL,             /* Perform replacement in all arguments. */
146         FORMAT_DASH,            /* Perform replacement up until "--". */
147         FORMAT_NONE             /* No replacement should be performed. */
148 };
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153         INPUT_OK,
154         INPUT_SKIP,
155         INPUT_STOP,
156         INPUT_CANCEL
157 };
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
164 /*
165  * String helpers
166  */
168 static inline void
169 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
171         if (srclen > dstlen - 1)
172                 srclen = dstlen - 1;
174         strncpy(dst, src, srclen);
175         dst[srclen] = 0;
178 /* Shorthands for safely copying into a fixed buffer. */
180 #define string_copy(dst, src) \
181         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
183 #define string_ncopy(dst, src, srclen) \
184         string_ncopy_do(dst, sizeof(dst), src, srclen)
186 #define string_copy_rev(dst, src) \
187         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
189 #define string_add(dst, from, src) \
190         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
192 static void
193 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
195         size_t size, pos;
197         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
198                 if (src[pos] == '\t') {
199                         size_t expanded = tabsize - (size % tabsize);
201                         if (expanded + size >= dstlen - 1)
202                                 expanded = dstlen - size - 1;
203                         memcpy(dst + size, "        ", expanded);
204                         size += expanded;
205                 } else {
206                         dst[size++] = src[pos];
207                 }
208         }
210         dst[size] = 0;
213 static char *
214 chomp_string(char *name)
216         int namelen;
218         while (isspace(*name))
219                 name++;
221         namelen = strlen(name) - 1;
222         while (namelen > 0 && isspace(name[namelen]))
223                 name[namelen--] = 0;
225         return name;
228 static bool
229 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231         va_list args;
232         size_t pos = bufpos ? *bufpos : 0;
234         va_start(args, fmt);
235         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236         va_end(args);
238         if (bufpos)
239                 *bufpos = pos;
241         return pos >= bufsize ? FALSE : TRUE;
244 #define string_format(buf, fmt, args...) \
245         string_nformat(buf, sizeof(buf), NULL, fmt, args)
247 #define string_format_from(buf, from, fmt, args...) \
248         string_nformat(buf, sizeof(buf), from, fmt, args)
250 static int
251 string_enum_compare(const char *str1, const char *str2, int len)
253         size_t i;
255 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
257         /* Diff-Header == DIFF_HEADER */
258         for (i = 0; i < len; i++) {
259                 if (toupper(str1[i]) == toupper(str2[i]))
260                         continue;
262                 if (string_enum_sep(str1[i]) &&
263                     string_enum_sep(str2[i]))
264                         continue;
266                 return str1[i] - str2[i];
267         }
269         return 0;
272 struct enum_map {
273         const char *name;
274         int namelen;
275         int value;
276 };
278 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
280 static bool
281 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
283         size_t namelen = strlen(name);
284         int i;
286         for (i = 0; i < map_size; i++)
287                 if (namelen == map[i].namelen &&
288                     !string_enum_compare(name, map[i].name, namelen)) {
289                         *value = map[i].value;
290                         return TRUE;
291                 }
293         return FALSE;
296 #define map_enum(attr, map, name) \
297         map_enum_do(map, ARRAY_SIZE(map), attr, name)
299 #define prefixcmp(str1, str2) \
300         strncmp(str1, str2, STRING_SIZE(str2))
302 static inline int
303 suffixcmp(const char *str, int slen, const char *suffix)
305         size_t len = slen >= 0 ? slen : strlen(str);
306         size_t suffixlen = strlen(suffix);
308         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
312 static const char *
313 mkdate(const time_t *time)
315         static char buf[DATE_COLS + 1];
316         struct tm tm;
318         gmtime_r(time, &tm);
319         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
323 static bool
324 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
326         int valuelen;
328         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
329                 bool advance = cmd[valuelen] != 0;
331                 cmd[valuelen] = 0;
332                 argv[(*argc)++] = chomp_string(cmd);
333                 cmd = chomp_string(cmd + valuelen + advance);
334         }
336         if (*argc < SIZEOF_ARG)
337                 argv[*argc] = NULL;
338         return *argc < SIZEOF_ARG;
341 static void
342 argv_from_env(const char **argv, const char *name)
344         char *env = argv ? getenv(name) : NULL;
345         int argc = 0;
347         if (env && *env)
348                 env = strdup(env);
349         if (env && !argv_from_string(argv, &argc, env))
350                 die("Too many arguments in the `%s` environment variable", name);
354 /*
355  * Executing external commands.
356  */
358 enum io_type {
359         IO_FD,                  /* File descriptor based IO. */
360         IO_BG,                  /* Execute command in the background. */
361         IO_FG,                  /* Execute command with same std{in,out,err}. */
362         IO_RD,                  /* Read only fork+exec IO. */
363         IO_WR,                  /* Write only fork+exec IO. */
364         IO_AP,                  /* Append fork+exec output to file. */
365 };
367 struct io {
368         enum io_type type;      /* The requested type of pipe. */
369         const char *dir;        /* Directory from which to execute. */
370         pid_t pid;              /* Pipe for reading or writing. */
371         int pipe;               /* Pipe end for reading or writing. */
372         int error;              /* Error status. */
373         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
374         char *buf;              /* Read buffer. */
375         size_t bufalloc;        /* Allocated buffer size. */
376         size_t bufsize;         /* Buffer content size. */
377         char *bufpos;           /* Current buffer position. */
378         unsigned int eof:1;     /* Has end of file been reached. */
379 };
381 static void
382 reset_io(struct io *io)
384         io->pipe = -1;
385         io->pid = 0;
386         io->buf = io->bufpos = NULL;
387         io->bufalloc = io->bufsize = 0;
388         io->error = 0;
389         io->eof = 0;
392 static void
393 init_io(struct io *io, const char *dir, enum io_type type)
395         reset_io(io);
396         io->type = type;
397         io->dir = dir;
400 static bool
401 init_io_rd(struct io *io, const char *argv[], const char *dir,
402                 enum format_flags flags)
404         init_io(io, dir, IO_RD);
405         return format_argv(io->argv, argv, flags);
408 static bool
409 io_open(struct io *io, const char *name)
411         init_io(io, NULL, IO_FD);
412         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
413         if (io->pipe == -1)
414                 io->error = errno;
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 char *
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 const 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 double opt_hscroll               = 0.50;
877 static int opt_tab_size                 = TAB_SIZE;
878 static int opt_author_cols              = AUTHOR_COLS-1;
879 static char opt_path[SIZEOF_STR]        = "";
880 static char opt_file[SIZEOF_STR]        = "";
881 static char opt_ref[SIZEOF_REF]         = "";
882 static char opt_head[SIZEOF_REF]        = "";
883 static char opt_head_rev[SIZEOF_REV]    = "";
884 static char opt_remote[SIZEOF_REF]      = "";
885 static char opt_encoding[20]            = "UTF-8";
886 static bool opt_utf8                    = TRUE;
887 static char opt_codeset[20]             = "UTF-8";
888 static iconv_t opt_iconv                = ICONV_NONE;
889 static char opt_search[SIZEOF_STR]      = "";
890 static char opt_cdup[SIZEOF_STR]        = "";
891 static char opt_prefix[SIZEOF_STR]      = "";
892 static char opt_git_dir[SIZEOF_STR]     = "";
893 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
894 static char opt_editor[SIZEOF_STR]      = "";
895 static FILE *opt_tty                    = NULL;
897 #define is_initial_commit()     (!*opt_head_rev)
898 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
901 /*
902  * Line-oriented content detection.
903  */
905 #define LINE_INFO \
906 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
907 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
908 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
909 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
910 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
911 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
912 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
913 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
920 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
921 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
922 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
923 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
924 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
927 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
928 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
929 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
930 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
931 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
932 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
933 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
934 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
935 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
936 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
937 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
938 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
939 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
940 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
941 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
942 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
943 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
944 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
945 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
946 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
947 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
948 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
949 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
950 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
951 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
952 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
953 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
954 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
955 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
956 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
957 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
958 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
959 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
960 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
962 enum line_type {
963 #define LINE(type, line, fg, bg, attr) \
964         LINE_##type
965         LINE_INFO,
966         LINE_NONE
967 #undef  LINE
968 };
970 struct line_info {
971         const char *name;       /* Option name. */
972         int namelen;            /* Size of option name. */
973         const char *line;       /* The start of line to match. */
974         int linelen;            /* Size of string to match. */
975         int fg, bg, attr;       /* Color and text attributes for the lines. */
976 };
978 static struct line_info line_info[] = {
979 #define LINE(type, line, fg, bg, attr) \
980         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
981         LINE_INFO
982 #undef  LINE
983 };
985 static enum line_type
986 get_line_type(const char *line)
988         int linelen = strlen(line);
989         enum line_type type;
991         for (type = 0; type < ARRAY_SIZE(line_info); type++)
992                 /* Case insensitive search matches Signed-off-by lines better. */
993                 if (linelen >= line_info[type].linelen &&
994                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
995                         return type;
997         return LINE_DEFAULT;
1000 static inline int
1001 get_line_attr(enum line_type type)
1003         assert(type < ARRAY_SIZE(line_info));
1004         return COLOR_PAIR(type) | line_info[type].attr;
1007 static struct line_info *
1008 get_line_info(const char *name)
1010         size_t namelen = strlen(name);
1011         enum line_type type;
1013         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1014                 if (namelen == line_info[type].namelen &&
1015                     !string_enum_compare(line_info[type].name, name, namelen))
1016                         return &line_info[type];
1018         return NULL;
1021 static void
1022 init_colors(void)
1024         int default_bg = line_info[LINE_DEFAULT].bg;
1025         int default_fg = line_info[LINE_DEFAULT].fg;
1026         enum line_type type;
1028         start_color();
1030         if (assume_default_colors(default_fg, default_bg) == ERR) {
1031                 default_bg = COLOR_BLACK;
1032                 default_fg = COLOR_WHITE;
1033         }
1035         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1036                 struct line_info *info = &line_info[type];
1037                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1038                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1040                 init_pair(type, fg, bg);
1041         }
1044 struct line {
1045         enum line_type type;
1047         /* State flags */
1048         unsigned int selected:1;
1049         unsigned int dirty:1;
1050         unsigned int cleareol:1;
1052         void *data;             /* User data */
1053 };
1056 /*
1057  * Keys
1058  */
1060 struct keybinding {
1061         int alias;
1062         enum request request;
1063 };
1065 static const struct keybinding default_keybindings[] = {
1066         /* View switching */
1067         { 'm',          REQ_VIEW_MAIN },
1068         { 'd',          REQ_VIEW_DIFF },
1069         { 'l',          REQ_VIEW_LOG },
1070         { 't',          REQ_VIEW_TREE },
1071         { 'f',          REQ_VIEW_BLOB },
1072         { 'B',          REQ_VIEW_BLAME },
1073         { 'p',          REQ_VIEW_PAGER },
1074         { 'h',          REQ_VIEW_HELP },
1075         { 'S',          REQ_VIEW_STATUS },
1076         { 'c',          REQ_VIEW_STAGE },
1078         /* View manipulation */
1079         { 'q',          REQ_VIEW_CLOSE },
1080         { KEY_TAB,      REQ_VIEW_NEXT },
1081         { KEY_RETURN,   REQ_ENTER },
1082         { KEY_UP,       REQ_PREVIOUS },
1083         { KEY_DOWN,     REQ_NEXT },
1084         { 'R',          REQ_REFRESH },
1085         { KEY_F(5),     REQ_REFRESH },
1086         { 'O',          REQ_MAXIMIZE },
1088         /* Cursor navigation */
1089         { 'k',          REQ_MOVE_UP },
1090         { 'j',          REQ_MOVE_DOWN },
1091         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1092         { KEY_END,      REQ_MOVE_LAST_LINE },
1093         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1094         { ' ',          REQ_MOVE_PAGE_DOWN },
1095         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1096         { 'b',          REQ_MOVE_PAGE_UP },
1097         { '-',          REQ_MOVE_PAGE_UP },
1099         /* Scrolling */
1100         { KEY_LEFT,     REQ_SCROLL_LEFT },
1101         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1102         { KEY_IC,       REQ_SCROLL_LINE_UP },
1103         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1104         { 'w',          REQ_SCROLL_PAGE_UP },
1105         { 's',          REQ_SCROLL_PAGE_DOWN },
1107         /* Searching */
1108         { '/',          REQ_SEARCH },
1109         { '?',          REQ_SEARCH_BACK },
1110         { 'n',          REQ_FIND_NEXT },
1111         { 'N',          REQ_FIND_PREV },
1113         /* Misc */
1114         { 'Q',          REQ_QUIT },
1115         { 'z',          REQ_STOP_LOADING },
1116         { 'v',          REQ_SHOW_VERSION },
1117         { 'r',          REQ_SCREEN_REDRAW },
1118         { '.',          REQ_TOGGLE_LINENO },
1119         { 'D',          REQ_TOGGLE_DATE },
1120         { 'A',          REQ_TOGGLE_AUTHOR },
1121         { 'g',          REQ_TOGGLE_REV_GRAPH },
1122         { 'F',          REQ_TOGGLE_REFS },
1123         { ':',          REQ_PROMPT },
1124         { 'u',          REQ_STATUS_UPDATE },
1125         { '!',          REQ_STATUS_REVERT },
1126         { 'M',          REQ_STATUS_MERGE },
1127         { '@',          REQ_STAGE_NEXT },
1128         { ',',          REQ_PARENT },
1129         { 'e',          REQ_EDIT },
1130 };
1132 #define KEYMAP_INFO \
1133         KEYMAP_(GENERIC), \
1134         KEYMAP_(MAIN), \
1135         KEYMAP_(DIFF), \
1136         KEYMAP_(LOG), \
1137         KEYMAP_(TREE), \
1138         KEYMAP_(BLOB), \
1139         KEYMAP_(BLAME), \
1140         KEYMAP_(PAGER), \
1141         KEYMAP_(HELP), \
1142         KEYMAP_(STATUS), \
1143         KEYMAP_(STAGE)
1145 enum keymap {
1146 #define KEYMAP_(name) KEYMAP_##name
1147         KEYMAP_INFO
1148 #undef  KEYMAP_
1149 };
1151 static const struct enum_map keymap_table[] = {
1152 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1153         KEYMAP_INFO
1154 #undef  KEYMAP_
1155 };
1157 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1159 struct keybinding_table {
1160         struct keybinding *data;
1161         size_t size;
1162 };
1164 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1166 static void
1167 add_keybinding(enum keymap keymap, enum request request, int key)
1169         struct keybinding_table *table = &keybindings[keymap];
1171         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1172         if (!table->data)
1173                 die("Failed to allocate keybinding");
1174         table->data[table->size].alias = key;
1175         table->data[table->size++].request = request;
1178 /* Looks for a key binding first in the given map, then in the generic map, and
1179  * lastly in the default keybindings. */
1180 static enum request
1181 get_keybinding(enum keymap keymap, int key)
1183         size_t i;
1185         for (i = 0; i < keybindings[keymap].size; i++)
1186                 if (keybindings[keymap].data[i].alias == key)
1187                         return keybindings[keymap].data[i].request;
1189         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1190                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1191                         return keybindings[KEYMAP_GENERIC].data[i].request;
1193         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1194                 if (default_keybindings[i].alias == key)
1195                         return default_keybindings[i].request;
1197         return (enum request) key;
1201 struct key {
1202         const char *name;
1203         int value;
1204 };
1206 static const struct key key_table[] = {
1207         { "Enter",      KEY_RETURN },
1208         { "Space",      ' ' },
1209         { "Backspace",  KEY_BACKSPACE },
1210         { "Tab",        KEY_TAB },
1211         { "Escape",     KEY_ESC },
1212         { "Left",       KEY_LEFT },
1213         { "Right",      KEY_RIGHT },
1214         { "Up",         KEY_UP },
1215         { "Down",       KEY_DOWN },
1216         { "Insert",     KEY_IC },
1217         { "Delete",     KEY_DC },
1218         { "Hash",       '#' },
1219         { "Home",       KEY_HOME },
1220         { "End",        KEY_END },
1221         { "PageUp",     KEY_PPAGE },
1222         { "PageDown",   KEY_NPAGE },
1223         { "F1",         KEY_F(1) },
1224         { "F2",         KEY_F(2) },
1225         { "F3",         KEY_F(3) },
1226         { "F4",         KEY_F(4) },
1227         { "F5",         KEY_F(5) },
1228         { "F6",         KEY_F(6) },
1229         { "F7",         KEY_F(7) },
1230         { "F8",         KEY_F(8) },
1231         { "F9",         KEY_F(9) },
1232         { "F10",        KEY_F(10) },
1233         { "F11",        KEY_F(11) },
1234         { "F12",        KEY_F(12) },
1235 };
1237 static int
1238 get_key_value(const char *name)
1240         int i;
1242         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1243                 if (!strcasecmp(key_table[i].name, name))
1244                         return key_table[i].value;
1246         if (strlen(name) == 1 && isprint(*name))
1247                 return (int) *name;
1249         return ERR;
1252 static const char *
1253 get_key_name(int key_value)
1255         static char key_char[] = "'X'";
1256         const char *seq = NULL;
1257         int key;
1259         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1260                 if (key_table[key].value == key_value)
1261                         seq = key_table[key].name;
1263         if (seq == NULL &&
1264             key_value < 127 &&
1265             isprint(key_value)) {
1266                 key_char[1] = (char) key_value;
1267                 seq = key_char;
1268         }
1270         return seq ? seq : "(no key)";
1273 static const char *
1274 get_key(enum request request)
1276         static char buf[BUFSIZ];
1277         size_t pos = 0;
1278         char *sep = "";
1279         int i;
1281         buf[pos] = 0;
1283         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1284                 const struct keybinding *keybinding = &default_keybindings[i];
1286                 if (keybinding->request != request)
1287                         continue;
1289                 if (!string_format_from(buf, &pos, "%s%s", sep,
1290                                         get_key_name(keybinding->alias)))
1291                         return "Too many keybindings!";
1292                 sep = ", ";
1293         }
1295         return buf;
1298 struct run_request {
1299         enum keymap keymap;
1300         int key;
1301         const char *argv[SIZEOF_ARG];
1302 };
1304 static struct run_request *run_request;
1305 static size_t run_requests;
1307 static enum request
1308 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1310         struct run_request *req;
1312         if (argc >= ARRAY_SIZE(req->argv) - 1)
1313                 return REQ_NONE;
1315         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1316         if (!req)
1317                 return REQ_NONE;
1319         run_request = req;
1320         req = &run_request[run_requests];
1321         req->keymap = keymap;
1322         req->key = key;
1323         req->argv[0] = NULL;
1325         if (!format_argv(req->argv, argv, FORMAT_NONE))
1326                 return REQ_NONE;
1328         return REQ_NONE + ++run_requests;
1331 static struct run_request *
1332 get_run_request(enum request request)
1334         if (request <= REQ_NONE)
1335                 return NULL;
1336         return &run_request[request - REQ_NONE - 1];
1339 static void
1340 add_builtin_run_requests(void)
1342         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1343         const char *gc[] = { "git", "gc", NULL };
1344         struct {
1345                 enum keymap keymap;
1346                 int key;
1347                 int argc;
1348                 const char **argv;
1349         } reqs[] = {
1350                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1351                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1352         };
1353         int i;
1355         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1356                 enum request req;
1358                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1359                 if (req != REQ_NONE)
1360                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1361         }
1364 /*
1365  * User config file handling.
1366  */
1368 static int   config_lineno;
1369 static bool  config_errors;
1370 static const char *config_msg;
1372 static const struct enum_map color_map[] = {
1373 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1374         COLOR_MAP(DEFAULT),
1375         COLOR_MAP(BLACK),
1376         COLOR_MAP(BLUE),
1377         COLOR_MAP(CYAN),
1378         COLOR_MAP(GREEN),
1379         COLOR_MAP(MAGENTA),
1380         COLOR_MAP(RED),
1381         COLOR_MAP(WHITE),
1382         COLOR_MAP(YELLOW),
1383 };
1385 static const struct enum_map attr_map[] = {
1386 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1387         ATTR_MAP(NORMAL),
1388         ATTR_MAP(BLINK),
1389         ATTR_MAP(BOLD),
1390         ATTR_MAP(DIM),
1391         ATTR_MAP(REVERSE),
1392         ATTR_MAP(STANDOUT),
1393         ATTR_MAP(UNDERLINE),
1394 };
1396 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1398 static int parse_step(double *opt, const char *arg)
1400         *opt = atoi(arg);
1401         if (!strchr(arg, '%'))
1402                 return OK;
1404         /* "Shift down" so 100% and 1 does not conflict. */
1405         *opt = (*opt - 1) / 100;
1406         if (*opt >= 1.0) {
1407                 *opt = 0.99;
1408                 config_msg = "Step value larger than 100%";
1409                 return ERR;
1410         }
1411         if (*opt < 0.0) {
1412                 *opt = 1;
1413                 config_msg = "Invalid step value";
1414                 return ERR;
1415         }
1416         return OK;
1419 static int
1420 parse_int(int *opt, const char *arg, int min, int max)
1422         int value = atoi(arg);
1424         if (min <= value && value <= max) {
1425                 *opt = value;
1426                 return OK;
1427         }
1429         config_msg = "Integer value out of bound";
1430         return ERR;
1433 static bool
1434 set_color(int *color, const char *name)
1436         if (map_enum(color, color_map, name))
1437                 return TRUE;
1438         if (!prefixcmp(name, "color"))
1439                 return parse_int(color, name + 5, 0, 255) == OK;
1440         return FALSE;
1443 /* Wants: object fgcolor bgcolor [attribute] */
1444 static int
1445 option_color_command(int argc, const char *argv[])
1447         struct line_info *info;
1449         if (argc != 3 && argc != 4) {
1450                 config_msg = "Wrong number of arguments given to color command";
1451                 return ERR;
1452         }
1454         info = get_line_info(argv[0]);
1455         if (!info) {
1456                 static const struct enum_map obsolete[] = {
1457                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1458                         ENUM_MAP("main-date",   LINE_DATE),
1459                         ENUM_MAP("main-author", LINE_AUTHOR),
1460                 };
1461                 int index;
1463                 if (!map_enum(&index, obsolete, argv[0])) {
1464                         config_msg = "Unknown color name";
1465                         return ERR;
1466                 }
1467                 info = &line_info[index];
1468         }
1470         if (!set_color(&info->fg, argv[1]) ||
1471             !set_color(&info->bg, argv[2])) {
1472                 config_msg = "Unknown color";
1473                 return ERR;
1474         }
1476         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1477                 config_msg = "Unknown attribute";
1478                 return ERR;
1479         }
1481         return OK;
1484 static int parse_bool(bool *opt, const char *arg)
1486         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1487                 ? TRUE : FALSE;
1488         return OK;
1491 static int
1492 parse_string(char *opt, const char *arg, size_t optsize)
1494         int arglen = strlen(arg);
1496         switch (arg[0]) {
1497         case '\"':
1498         case '\'':
1499                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1500                         config_msg = "Unmatched quotation";
1501                         return ERR;
1502                 }
1503                 arg += 1; arglen -= 2;
1504         default:
1505                 string_ncopy_do(opt, optsize, arg, arglen);
1506                 return OK;
1507         }
1510 /* Wants: name = value */
1511 static int
1512 option_set_command(int argc, const char *argv[])
1514         if (argc != 3) {
1515                 config_msg = "Wrong number of arguments given to set command";
1516                 return ERR;
1517         }
1519         if (strcmp(argv[1], "=")) {
1520                 config_msg = "No value assigned";
1521                 return ERR;
1522         }
1524         if (!strcmp(argv[0], "show-author"))
1525                 return parse_bool(&opt_author, argv[2]);
1527         if (!strcmp(argv[0], "show-date"))
1528                 return parse_bool(&opt_date, argv[2]);
1530         if (!strcmp(argv[0], "show-rev-graph"))
1531                 return parse_bool(&opt_rev_graph, argv[2]);
1533         if (!strcmp(argv[0], "show-refs"))
1534                 return parse_bool(&opt_show_refs, argv[2]);
1536         if (!strcmp(argv[0], "show-line-numbers"))
1537                 return parse_bool(&opt_line_number, argv[2]);
1539         if (!strcmp(argv[0], "line-graphics"))
1540                 return parse_bool(&opt_line_graphics, argv[2]);
1542         if (!strcmp(argv[0], "line-number-interval"))
1543                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1545         if (!strcmp(argv[0], "author-width"))
1546                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1548         if (!strcmp(argv[0], "horizontal-scroll"))
1549                 return parse_step(&opt_hscroll, argv[2]);
1551         if (!strcmp(argv[0], "tab-size"))
1552                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1554         if (!strcmp(argv[0], "commit-encoding"))
1555                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1557         config_msg = "Unknown variable name";
1558         return ERR;
1561 /* Wants: mode request key */
1562 static int
1563 option_bind_command(int argc, const char *argv[])
1565         enum request request;
1566         int keymap;
1567         int key;
1569         if (argc < 3) {
1570                 config_msg = "Wrong number of arguments given to bind command";
1571                 return ERR;
1572         }
1574         if (set_keymap(&keymap, argv[0]) == ERR) {
1575                 config_msg = "Unknown key map";
1576                 return ERR;
1577         }
1579         key = get_key_value(argv[1]);
1580         if (key == ERR) {
1581                 config_msg = "Unknown key";
1582                 return ERR;
1583         }
1585         request = get_request(argv[2]);
1586         if (request == REQ_NONE) {
1587                 static const struct enum_map obsolete[] = {
1588                         ENUM_MAP("cherry-pick",         REQ_NONE),
1589                         ENUM_MAP("screen-resize",       REQ_NONE),
1590                         ENUM_MAP("tree-parent",         REQ_PARENT),
1591                 };
1592                 int alias;
1594                 if (map_enum(&alias, obsolete, argv[2])) {
1595                         if (alias != REQ_NONE)
1596                                 add_keybinding(keymap, alias, key);
1597                         config_msg = "Obsolete request name";
1598                         return ERR;
1599                 }
1600         }
1601         if (request == REQ_NONE && *argv[2]++ == '!')
1602                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1603         if (request == REQ_NONE) {
1604                 config_msg = "Unknown request name";
1605                 return ERR;
1606         }
1608         add_keybinding(keymap, request, key);
1610         return OK;
1613 static int
1614 set_option(const char *opt, char *value)
1616         const char *argv[SIZEOF_ARG];
1617         int argc = 0;
1619         if (!argv_from_string(argv, &argc, value)) {
1620                 config_msg = "Too many option arguments";
1621                 return ERR;
1622         }
1624         if (!strcmp(opt, "color"))
1625                 return option_color_command(argc, argv);
1627         if (!strcmp(opt, "set"))
1628                 return option_set_command(argc, argv);
1630         if (!strcmp(opt, "bind"))
1631                 return option_bind_command(argc, argv);
1633         config_msg = "Unknown option command";
1634         return ERR;
1637 static int
1638 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1640         int status = OK;
1642         config_lineno++;
1643         config_msg = "Internal error";
1645         /* Check for comment markers, since read_properties() will
1646          * only ensure opt and value are split at first " \t". */
1647         optlen = strcspn(opt, "#");
1648         if (optlen == 0)
1649                 return OK;
1651         if (opt[optlen] != 0) {
1652                 config_msg = "No option value";
1653                 status = ERR;
1655         }  else {
1656                 /* Look for comment endings in the value. */
1657                 size_t len = strcspn(value, "#");
1659                 if (len < valuelen) {
1660                         valuelen = len;
1661                         value[valuelen] = 0;
1662                 }
1664                 status = set_option(opt, value);
1665         }
1667         if (status == ERR) {
1668                 warn("Error on line %d, near '%.*s': %s",
1669                      config_lineno, (int) optlen, opt, config_msg);
1670                 config_errors = TRUE;
1671         }
1673         /* Always keep going if errors are encountered. */
1674         return OK;
1677 static void
1678 load_option_file(const char *path)
1680         struct io io = {};
1682         /* It's OK that the file doesn't exist. */
1683         if (!io_open(&io, path))
1684                 return;
1686         config_lineno = 0;
1687         config_errors = FALSE;
1689         if (io_load(&io, " \t", read_option) == ERR ||
1690             config_errors == TRUE)
1691                 warn("Errors while loading %s.", path);
1694 static int
1695 load_options(void)
1697         const char *home = getenv("HOME");
1698         const char *tigrc_user = getenv("TIGRC_USER");
1699         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1700         char buf[SIZEOF_STR];
1702         add_builtin_run_requests();
1704         if (!tigrc_system)
1705                 tigrc_system = SYSCONFDIR "/tigrc";
1706         load_option_file(tigrc_system);
1708         if (!tigrc_user) {
1709                 if (!home || !string_format(buf, "%s/.tigrc", home))
1710                         return ERR;
1711                 tigrc_user = buf;
1712         }
1713         load_option_file(tigrc_user);
1715         return OK;
1719 /*
1720  * The viewer
1721  */
1723 struct view;
1724 struct view_ops;
1726 /* The display array of active views and the index of the current view. */
1727 static struct view *display[2];
1728 static unsigned int current_view;
1730 #define foreach_displayed_view(view, i) \
1731         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1733 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1735 /* Current head and commit ID */
1736 static char ref_blob[SIZEOF_REF]        = "";
1737 static char ref_commit[SIZEOF_REF]      = "HEAD";
1738 static char ref_head[SIZEOF_REF]        = "HEAD";
1740 struct view {
1741         const char *name;       /* View name */
1742         const char *cmd_env;    /* Command line set via environment */
1743         const char *id;         /* Points to either of ref_{head,commit,blob} */
1745         struct view_ops *ops;   /* View operations */
1747         enum keymap keymap;     /* What keymap does this view have */
1748         bool git_dir;           /* Whether the view requires a git directory. */
1750         char ref[SIZEOF_REF];   /* Hovered commit reference */
1751         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1753         int height, width;      /* The width and height of the main window */
1754         WINDOW *win;            /* The main window */
1755         WINDOW *title;          /* The title window living below the main window */
1757         /* Navigation */
1758         unsigned long offset;   /* Offset of the window top */
1759         unsigned long yoffset;  /* Offset from the window side. */
1760         unsigned long lineno;   /* Current line number */
1761         unsigned long p_offset; /* Previous offset of the window top */
1762         unsigned long p_yoffset;/* Previous offset from the window side */
1763         unsigned long p_lineno; /* Previous current line number */
1764         bool p_restore;         /* Should the previous position be restored. */
1766         /* Searching */
1767         char grep[SIZEOF_STR];  /* Search string */
1768         regex_t *regex;         /* Pre-compiled regexp */
1770         /* If non-NULL, points to the view that opened this view. If this view
1771          * is closed tig will switch back to the parent view. */
1772         struct view *parent;
1774         /* Buffering */
1775         size_t lines;           /* Total number of lines */
1776         struct line *line;      /* Line index */
1777         size_t line_alloc;      /* Total number of allocated lines */
1778         unsigned int digits;    /* Number of digits in the lines member. */
1780         /* Drawing */
1781         struct line *curline;   /* Line currently being drawn. */
1782         enum line_type curtype; /* Attribute currently used for drawing. */
1783         unsigned long col;      /* Column when drawing. */
1784         bool has_scrolled;      /* View was scrolled. */
1786         /* Loading */
1787         struct io io;
1788         struct io *pipe;
1789         time_t start_time;
1790         time_t update_secs;
1791 };
1793 struct view_ops {
1794         /* What type of content being displayed. Used in the title bar. */
1795         const char *type;
1796         /* Default command arguments. */
1797         const char **argv;
1798         /* Open and reads in all view content. */
1799         bool (*open)(struct view *view);
1800         /* Read one line; updates view->line. */
1801         bool (*read)(struct view *view, char *data);
1802         /* Draw one line; @lineno must be < view->height. */
1803         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1804         /* Depending on view handle a special requests. */
1805         enum request (*request)(struct view *view, enum request request, struct line *line);
1806         /* Search for regexp in a line. */
1807         bool (*grep)(struct view *view, struct line *line);
1808         /* Select line */
1809         void (*select)(struct view *view, struct line *line);
1810 };
1812 static struct view_ops blame_ops;
1813 static struct view_ops blob_ops;
1814 static struct view_ops diff_ops;
1815 static struct view_ops help_ops;
1816 static struct view_ops log_ops;
1817 static struct view_ops main_ops;
1818 static struct view_ops pager_ops;
1819 static struct view_ops stage_ops;
1820 static struct view_ops status_ops;
1821 static struct view_ops tree_ops;
1823 #define VIEW_STR(name, env, ref, ops, map, git) \
1824         { name, #env, ref, ops, map, git }
1826 #define VIEW_(id, name, ops, git, ref) \
1827         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1830 static struct view views[] = {
1831         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1832         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1833         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1834         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1835         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1836         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1837         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1838         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1839         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1840         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1841 };
1843 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1844 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1846 #define foreach_view(view, i) \
1847         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1849 #define view_is_displayed(view) \
1850         (view == display[0] || view == display[1])
1853 enum line_graphic {
1854         LINE_GRAPHIC_VLINE
1855 };
1857 static chtype line_graphics[] = {
1858         /* LINE_GRAPHIC_VLINE: */ '|'
1859 };
1861 static inline void
1862 set_view_attr(struct view *view, enum line_type type)
1864         if (!view->curline->selected && view->curtype != type) {
1865                 wattrset(view->win, get_line_attr(type));
1866                 wchgat(view->win, -1, 0, type, NULL);
1867                 view->curtype = type;
1868         }
1871 static int
1872 draw_chars(struct view *view, enum line_type type, const char *string,
1873            int max_len, bool use_tilde)
1875         int len = 0;
1876         int col = 0;
1877         int trimmed = FALSE;
1878         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1880         if (max_len <= 0)
1881                 return 0;
1883         if (opt_utf8) {
1884                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1885         } else {
1886                 col = len = strlen(string);
1887                 if (len > max_len) {
1888                         if (use_tilde) {
1889                                 max_len -= 1;
1890                         }
1891                         col = len = max_len;
1892                         trimmed = TRUE;
1893                 }
1894         }
1896         set_view_attr(view, type);
1897         if (len > 0)
1898                 waddnstr(view->win, string, len);
1899         if (trimmed && use_tilde) {
1900                 set_view_attr(view, LINE_DELIMITER);
1901                 waddch(view->win, '~');
1902                 col++;
1903         }
1905         return col;
1908 static int
1909 draw_space(struct view *view, enum line_type type, int max, int spaces)
1911         static char space[] = "                    ";
1912         int col = 0;
1914         spaces = MIN(max, spaces);
1916         while (spaces > 0) {
1917                 int len = MIN(spaces, sizeof(space) - 1);
1919                 col += draw_chars(view, type, space, len, FALSE);
1920                 spaces -= len;
1921         }
1923         return col;
1926 static bool
1927 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1929         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1930         return view->width + view->yoffset <= view->col;
1933 static bool
1934 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1936         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1937         int max = view->width + view->yoffset - view->col;
1938         int i;
1940         if (max < size)
1941                 size = max;
1943         set_view_attr(view, type);
1944         /* Using waddch() instead of waddnstr() ensures that
1945          * they'll be rendered correctly for the cursor line. */
1946         for (i = skip; i < size; i++)
1947                 waddch(view->win, graphic[i]);
1949         view->col += size;
1950         if (size < max && skip <= size)
1951                 waddch(view->win, ' ');
1952         view->col++;
1954         return view->width + view->yoffset <= view->col;
1957 static bool
1958 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1960         int max = MIN(view->width + view->yoffset - view->col, len);
1961         int col;
1963         if (text)
1964                 col = draw_chars(view, type, text, max - 1, trim);
1965         else
1966                 col = draw_space(view, type, max - 1, max - 1);
1968         view->col += col;
1969         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1970         return view->width + view->yoffset <= view->col;
1973 static bool
1974 draw_date(struct view *view, time_t *time)
1976         const char *date = mkdate(time);
1978         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1981 static bool
1982 draw_author(struct view *view, const char *author)
1984         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1986         if (!trim) {
1987                 static char initials[10];
1988                 size_t pos;
1990 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
1992                 memset(initials, 0, sizeof(initials));
1993                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
1994                         while (is_initial_sep(*author))
1995                                 author++;
1996                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
1997                         while (*author && !is_initial_sep(author[1]))
1998                                 author++;
1999                 }
2001                 author = initials;
2002         }
2004         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2007 static bool
2008 draw_mode(struct view *view, mode_t mode)
2010         const char *str;
2012         if (S_ISDIR(mode))
2013                 str = "drwxr-xr-x";
2014         else if (S_ISLNK(mode))
2015                 str = "lrwxrwxrwx";
2016         else if (S_ISGITLINK(mode))
2017                 str = "m---------";
2018         else if (S_ISREG(mode) && mode & S_IXUSR)
2019                 str = "-rwxr-xr-x";
2020         else if (S_ISREG(mode))
2021                 str = "-rw-r--r--";
2022         else
2023                 str = "----------";
2025         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2028 static bool
2029 draw_lineno(struct view *view, unsigned int lineno)
2031         char number[10];
2032         int digits3 = view->digits < 3 ? 3 : view->digits;
2033         int max = MIN(view->width + view->yoffset - view->col, digits3);
2034         char *text = NULL;
2036         lineno += view->offset + 1;
2037         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2038                 static char fmt[] = "%1ld";
2040                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2041                 if (string_format(number, fmt, lineno))
2042                         text = number;
2043         }
2044         if (text)
2045                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2046         else
2047                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2048         return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2051 static bool
2052 draw_view_line(struct view *view, unsigned int lineno)
2054         struct line *line;
2055         bool selected = (view->offset + lineno == view->lineno);
2057         assert(view_is_displayed(view));
2059         if (view->offset + lineno >= view->lines)
2060                 return FALSE;
2062         line = &view->line[view->offset + lineno];
2064         wmove(view->win, lineno, 0);
2065         if (line->cleareol)
2066                 wclrtoeol(view->win);
2067         view->col = 0;
2068         view->curline = line;
2069         view->curtype = LINE_NONE;
2070         line->selected = FALSE;
2071         line->dirty = line->cleareol = 0;
2073         if (selected) {
2074                 set_view_attr(view, LINE_CURSOR);
2075                 line->selected = TRUE;
2076                 view->ops->select(view, line);
2077         }
2079         return view->ops->draw(view, line, lineno);
2082 static void
2083 redraw_view_dirty(struct view *view)
2085         bool dirty = FALSE;
2086         int lineno;
2088         for (lineno = 0; lineno < view->height; lineno++) {
2089                 if (view->offset + lineno >= view->lines)
2090                         break;
2091                 if (!view->line[view->offset + lineno].dirty)
2092                         continue;
2093                 dirty = TRUE;
2094                 if (!draw_view_line(view, lineno))
2095                         break;
2096         }
2098         if (!dirty)
2099                 return;
2100         wnoutrefresh(view->win);
2103 static void
2104 redraw_view_from(struct view *view, int lineno)
2106         assert(0 <= lineno && lineno < view->height);
2108         for (; lineno < view->height; lineno++) {
2109                 if (!draw_view_line(view, lineno))
2110                         break;
2111         }
2113         wnoutrefresh(view->win);
2116 static void
2117 redraw_view(struct view *view)
2119         werase(view->win);
2120         redraw_view_from(view, 0);
2124 static void
2125 update_view_title(struct view *view)
2127         char buf[SIZEOF_STR];
2128         char state[SIZEOF_STR];
2129         size_t bufpos = 0, statelen = 0;
2131         assert(view_is_displayed(view));
2133         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2134                 unsigned int view_lines = view->offset + view->height;
2135                 unsigned int lines = view->lines
2136                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2137                                    : 0;
2139                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2140                                    view->ops->type,
2141                                    view->lineno + 1,
2142                                    view->lines,
2143                                    lines);
2145         }
2147         if (view->pipe) {
2148                 time_t secs = time(NULL) - view->start_time;
2150                 /* Three git seconds are a long time ... */
2151                 if (secs > 2)
2152                         string_format_from(state, &statelen, " loading %lds", secs);
2153         }
2155         string_format_from(buf, &bufpos, "[%s]", view->name);
2156         if (*view->ref && bufpos < view->width) {
2157                 size_t refsize = strlen(view->ref);
2158                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2160                 if (minsize < view->width)
2161                         refsize = view->width - minsize + 7;
2162                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2163         }
2165         if (statelen && bufpos < view->width) {
2166                 string_format_from(buf, &bufpos, "%s", state);
2167         }
2169         if (view == display[current_view])
2170                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2171         else
2172                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2174         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2175         wclrtoeol(view->title);
2176         wnoutrefresh(view->title);
2179 static void
2180 resize_display(void)
2182         int offset, i;
2183         struct view *base = display[0];
2184         struct view *view = display[1] ? display[1] : display[0];
2186         /* Setup window dimensions */
2188         getmaxyx(stdscr, base->height, base->width);
2190         /* Make room for the status window. */
2191         base->height -= 1;
2193         if (view != base) {
2194                 /* Horizontal split. */
2195                 view->width   = base->width;
2196                 view->height  = SCALE_SPLIT_VIEW(base->height);
2197                 base->height -= view->height;
2199                 /* Make room for the title bar. */
2200                 view->height -= 1;
2201         }
2203         /* Make room for the title bar. */
2204         base->height -= 1;
2206         offset = 0;
2208         foreach_displayed_view (view, i) {
2209                 if (!view->win) {
2210                         view->win = newwin(view->height, 0, offset, 0);
2211                         if (!view->win)
2212                                 die("Failed to create %s view", view->name);
2214                         scrollok(view->win, FALSE);
2216                         view->title = newwin(1, 0, offset + view->height, 0);
2217                         if (!view->title)
2218                                 die("Failed to create title window");
2220                 } else {
2221                         wresize(view->win, view->height, view->width);
2222                         mvwin(view->win,   offset, 0);
2223                         mvwin(view->title, offset + view->height, 0);
2224                 }
2226                 offset += view->height + 1;
2227         }
2230 static void
2231 redraw_display(bool clear)
2233         struct view *view;
2234         int i;
2236         foreach_displayed_view (view, i) {
2237                 if (clear)
2238                         wclear(view->win);
2239                 redraw_view(view);
2240                 update_view_title(view);
2241         }
2244 static void
2245 toggle_view_option(bool *option, const char *help)
2247         *option = !*option;
2248         redraw_display(FALSE);
2249         report("%sabling %s", *option ? "En" : "Dis", help);
2252 static void
2253 maximize_view(struct view *view)
2255         memset(display, 0, sizeof(display));
2256         current_view = 0;
2257         display[current_view] = view;
2258         resize_display();
2259         redraw_display(FALSE);
2260         report("");
2264 /*
2265  * Navigation
2266  */
2268 static bool
2269 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2271         if (lineno >= view->lines)
2272                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2274         if (offset > lineno || offset + view->height <= lineno) {
2275                 unsigned long half = view->height / 2;
2277                 if (lineno > half)
2278                         offset = lineno - half;
2279                 else
2280                         offset = 0;
2281         }
2283         if (offset != view->offset || lineno != view->lineno) {
2284                 view->offset = offset;
2285                 view->lineno = lineno;
2286                 return TRUE;
2287         }
2289         return FALSE;
2292 static int
2293 apply_step(double step, int value)
2295         if (step >= 1)
2296                 return (int) step;
2297         value *= step + 0.01;
2298         return value ? value : 1;
2301 /* Scrolling backend */
2302 static void
2303 do_scroll_view(struct view *view, int lines)
2305         bool redraw_current_line = FALSE;
2307         /* The rendering expects the new offset. */
2308         view->offset += lines;
2310         assert(0 <= view->offset && view->offset < view->lines);
2311         assert(lines);
2313         /* Move current line into the view. */
2314         if (view->lineno < view->offset) {
2315                 view->lineno = view->offset;
2316                 redraw_current_line = TRUE;
2317         } else if (view->lineno >= view->offset + view->height) {
2318                 view->lineno = view->offset + view->height - 1;
2319                 redraw_current_line = TRUE;
2320         }
2322         assert(view->offset <= view->lineno && view->lineno < view->lines);
2324         /* Redraw the whole screen if scrolling is pointless. */
2325         if (view->height < ABS(lines)) {
2326                 redraw_view(view);
2328         } else {
2329                 int line = lines > 0 ? view->height - lines : 0;
2330                 int end = line + ABS(lines);
2332                 scrollok(view->win, TRUE);
2333                 wscrl(view->win, lines);
2334                 scrollok(view->win, FALSE);
2336                 while (line < end && draw_view_line(view, line))
2337                         line++;
2339                 if (redraw_current_line)
2340                         draw_view_line(view, view->lineno - view->offset);
2341                 wnoutrefresh(view->win);
2342         }
2344         view->has_scrolled = TRUE;
2345         report("");
2348 /* Scroll frontend */
2349 static void
2350 scroll_view(struct view *view, enum request request)
2352         int lines = 1;
2354         assert(view_is_displayed(view));
2356         switch (request) {
2357         case REQ_SCROLL_LEFT:
2358                 if (view->yoffset == 0) {
2359                         report("Cannot scroll beyond the first column");
2360                         return;
2361                 }
2362                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2363                         view->yoffset = 0;
2364                 else
2365                         view->yoffset -= apply_step(opt_hscroll, view->width);
2366                 redraw_view_from(view, 0);
2367                 report("");
2368                 return;
2369         case REQ_SCROLL_RIGHT:
2370                 view->yoffset += apply_step(opt_hscroll, view->width);
2371                 redraw_view(view);
2372                 report("");
2373                 return;
2374         case REQ_SCROLL_PAGE_DOWN:
2375                 lines = view->height;
2376         case REQ_SCROLL_LINE_DOWN:
2377                 if (view->offset + lines > view->lines)
2378                         lines = view->lines - view->offset;
2380                 if (lines == 0 || view->offset + view->height >= view->lines) {
2381                         report("Cannot scroll beyond the last line");
2382                         return;
2383                 }
2384                 break;
2386         case REQ_SCROLL_PAGE_UP:
2387                 lines = view->height;
2388         case REQ_SCROLL_LINE_UP:
2389                 if (lines > view->offset)
2390                         lines = view->offset;
2392                 if (lines == 0) {
2393                         report("Cannot scroll beyond the first line");
2394                         return;
2395                 }
2397                 lines = -lines;
2398                 break;
2400         default:
2401                 die("request %d not handled in switch", request);
2402         }
2404         do_scroll_view(view, lines);
2407 /* Cursor moving */
2408 static void
2409 move_view(struct view *view, enum request request)
2411         int scroll_steps = 0;
2412         int steps;
2414         switch (request) {
2415         case REQ_MOVE_FIRST_LINE:
2416                 steps = -view->lineno;
2417                 break;
2419         case REQ_MOVE_LAST_LINE:
2420                 steps = view->lines - view->lineno - 1;
2421                 break;
2423         case REQ_MOVE_PAGE_UP:
2424                 steps = view->height > view->lineno
2425                       ? -view->lineno : -view->height;
2426                 break;
2428         case REQ_MOVE_PAGE_DOWN:
2429                 steps = view->lineno + view->height >= view->lines
2430                       ? view->lines - view->lineno - 1 : view->height;
2431                 break;
2433         case REQ_MOVE_UP:
2434                 steps = -1;
2435                 break;
2437         case REQ_MOVE_DOWN:
2438                 steps = 1;
2439                 break;
2441         default:
2442                 die("request %d not handled in switch", request);
2443         }
2445         if (steps <= 0 && view->lineno == 0) {
2446                 report("Cannot move beyond the first line");
2447                 return;
2449         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2450                 report("Cannot move beyond the last line");
2451                 return;
2452         }
2454         /* Move the current line */
2455         view->lineno += steps;
2456         assert(0 <= view->lineno && view->lineno < view->lines);
2458         /* Check whether the view needs to be scrolled */
2459         if (view->lineno < view->offset ||
2460             view->lineno >= view->offset + view->height) {
2461                 scroll_steps = steps;
2462                 if (steps < 0 && -steps > view->offset) {
2463                         scroll_steps = -view->offset;
2465                 } else if (steps > 0) {
2466                         if (view->lineno == view->lines - 1 &&
2467                             view->lines > view->height) {
2468                                 scroll_steps = view->lines - view->offset - 1;
2469                                 if (scroll_steps >= view->height)
2470                                         scroll_steps -= view->height - 1;
2471                         }
2472                 }
2473         }
2475         if (!view_is_displayed(view)) {
2476                 view->offset += scroll_steps;
2477                 assert(0 <= view->offset && view->offset < view->lines);
2478                 view->ops->select(view, &view->line[view->lineno]);
2479                 return;
2480         }
2482         /* Repaint the old "current" line if we be scrolling */
2483         if (ABS(steps) < view->height)
2484                 draw_view_line(view, view->lineno - steps - view->offset);
2486         if (scroll_steps) {
2487                 do_scroll_view(view, scroll_steps);
2488                 return;
2489         }
2491         /* Draw the current line */
2492         draw_view_line(view, view->lineno - view->offset);
2494         wnoutrefresh(view->win);
2495         report("");
2499 /*
2500  * Searching
2501  */
2503 static void search_view(struct view *view, enum request request);
2505 static bool
2506 grep_text(struct view *view, const char *text[])
2508         regmatch_t pmatch;
2509         size_t i;
2511         for (i = 0; text[i]; i++)
2512                 if (*text[i] &&
2513                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2514                         return TRUE;
2515         return FALSE;
2518 static void
2519 select_view_line(struct view *view, unsigned long lineno)
2521         unsigned long old_lineno = view->lineno;
2522         unsigned long old_offset = view->offset;
2524         if (goto_view_line(view, view->offset, lineno)) {
2525                 if (view_is_displayed(view)) {
2526                         if (old_offset != view->offset) {
2527                                 redraw_view(view);
2528                         } else {
2529                                 draw_view_line(view, old_lineno - view->offset);
2530                                 draw_view_line(view, view->lineno - view->offset);
2531                                 wnoutrefresh(view->win);
2532                         }
2533                 } else {
2534                         view->ops->select(view, &view->line[view->lineno]);
2535                 }
2536         }
2539 static void
2540 find_next(struct view *view, enum request request)
2542         unsigned long lineno = view->lineno;
2543         int direction;
2545         if (!*view->grep) {
2546                 if (!*opt_search)
2547                         report("No previous search");
2548                 else
2549                         search_view(view, request);
2550                 return;
2551         }
2553         switch (request) {
2554         case REQ_SEARCH:
2555         case REQ_FIND_NEXT:
2556                 direction = 1;
2557                 break;
2559         case REQ_SEARCH_BACK:
2560         case REQ_FIND_PREV:
2561                 direction = -1;
2562                 break;
2564         default:
2565                 return;
2566         }
2568         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2569                 lineno += direction;
2571         /* Note, lineno is unsigned long so will wrap around in which case it
2572          * will become bigger than view->lines. */
2573         for (; lineno < view->lines; lineno += direction) {
2574                 if (view->ops->grep(view, &view->line[lineno])) {
2575                         select_view_line(view, lineno);
2576                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2577                         return;
2578                 }
2579         }
2581         report("No match found for '%s'", view->grep);
2584 static void
2585 search_view(struct view *view, enum request request)
2587         int regex_err;
2589         if (view->regex) {
2590                 regfree(view->regex);
2591                 *view->grep = 0;
2592         } else {
2593                 view->regex = calloc(1, sizeof(*view->regex));
2594                 if (!view->regex)
2595                         return;
2596         }
2598         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2599         if (regex_err != 0) {
2600                 char buf[SIZEOF_STR] = "unknown error";
2602                 regerror(regex_err, view->regex, buf, sizeof(buf));
2603                 report("Search failed: %s", buf);
2604                 return;
2605         }
2607         string_copy(view->grep, opt_search);
2609         find_next(view, request);
2612 /*
2613  * Incremental updating
2614  */
2616 static void
2617 reset_view(struct view *view)
2619         int i;
2621         for (i = 0; i < view->lines; i++)
2622                 free(view->line[i].data);
2623         free(view->line);
2625         view->p_offset = view->offset;
2626         view->p_yoffset = view->yoffset;
2627         view->p_lineno = view->lineno;
2629         view->line = NULL;
2630         view->offset = 0;
2631         view->yoffset = 0;
2632         view->lines  = 0;
2633         view->lineno = 0;
2634         view->line_alloc = 0;
2635         view->vid[0] = 0;
2636         view->update_secs = 0;
2639 static void
2640 free_argv(const char *argv[])
2642         int argc;
2644         for (argc = 0; argv[argc]; argc++)
2645                 free((void *) argv[argc]);
2648 static bool
2649 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2651         char buf[SIZEOF_STR];
2652         int argc;
2653         bool noreplace = flags == FORMAT_NONE;
2655         free_argv(dst_argv);
2657         for (argc = 0; src_argv[argc]; argc++) {
2658                 const char *arg = src_argv[argc];
2659                 size_t bufpos = 0;
2661                 while (arg) {
2662                         char *next = strstr(arg, "%(");
2663                         int len = next - arg;
2664                         const char *value;
2666                         if (!next || noreplace) {
2667                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2668                                         noreplace = TRUE;
2669                                 len = strlen(arg);
2670                                 value = "";
2672                         } else if (!prefixcmp(next, "%(directory)")) {
2673                                 value = opt_path;
2675                         } else if (!prefixcmp(next, "%(file)")) {
2676                                 value = opt_file;
2678                         } else if (!prefixcmp(next, "%(ref)")) {
2679                                 value = *opt_ref ? opt_ref : "HEAD";
2681                         } else if (!prefixcmp(next, "%(head)")) {
2682                                 value = ref_head;
2684                         } else if (!prefixcmp(next, "%(commit)")) {
2685                                 value = ref_commit;
2687                         } else if (!prefixcmp(next, "%(blob)")) {
2688                                 value = ref_blob;
2690                         } else {
2691                                 report("Unknown replacement: `%s`", next);
2692                                 return FALSE;
2693                         }
2695                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2696                                 return FALSE;
2698                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2699                 }
2701                 dst_argv[argc] = strdup(buf);
2702                 if (!dst_argv[argc])
2703                         break;
2704         }
2706         dst_argv[argc] = NULL;
2708         return src_argv[argc] == NULL;
2711 static bool
2712 restore_view_position(struct view *view)
2714         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2715                 return FALSE;
2717         /* Changing the view position cancels the restoring. */
2718         /* FIXME: Changing back to the first line is not detected. */
2719         if (view->offset != 0 || view->lineno != 0) {
2720                 view->p_restore = FALSE;
2721                 return FALSE;
2722         }
2724         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2725             view_is_displayed(view))
2726                 werase(view->win);
2728         view->yoffset = view->p_yoffset;
2729         view->p_restore = FALSE;
2731         return TRUE;
2734 static void
2735 end_update(struct view *view, bool force)
2737         if (!view->pipe)
2738                 return;
2739         while (!view->ops->read(view, NULL))
2740                 if (!force)
2741                         return;
2742         set_nonblocking_input(FALSE);
2743         if (force)
2744                 kill_io(view->pipe);
2745         done_io(view->pipe);
2746         view->pipe = NULL;
2749 static void
2750 setup_update(struct view *view, const char *vid)
2752         set_nonblocking_input(TRUE);
2753         reset_view(view);
2754         string_copy_rev(view->vid, vid);
2755         view->pipe = &view->io;
2756         view->start_time = time(NULL);
2759 static bool
2760 prepare_update(struct view *view, const char *argv[], const char *dir,
2761                enum format_flags flags)
2763         if (view->pipe)
2764                 end_update(view, TRUE);
2765         return init_io_rd(&view->io, argv, dir, flags);
2768 static bool
2769 prepare_update_file(struct view *view, const char *name)
2771         if (view->pipe)
2772                 end_update(view, TRUE);
2773         return io_open(&view->io, name);
2776 static bool
2777 begin_update(struct view *view, bool refresh)
2779         if (view->pipe)
2780                 end_update(view, TRUE);
2782         if (refresh) {
2783                 if (!start_io(&view->io))
2784                         return FALSE;
2786         } else {
2787                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2788                         opt_path[0] = 0;
2790                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2791                         return FALSE;
2793                 /* Put the current ref_* value to the view title ref
2794                  * member. This is needed by the blob view. Most other
2795                  * views sets it automatically after loading because the
2796                  * first line is a commit line. */
2797                 string_copy_rev(view->ref, view->id);
2798         }
2800         setup_update(view, view->id);
2802         return TRUE;
2805 #define ITEM_CHUNK_SIZE 256
2806 static void *
2807 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2809         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2810         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2812         if (mem == NULL || num_chunks != num_chunks_new) {
2813                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2814                 mem = realloc(mem, *size * item_size);
2815         }
2817         return mem;
2820 static struct line *
2821 realloc_lines(struct view *view, size_t line_size)
2823         size_t alloc = view->line_alloc;
2824         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2825                                          sizeof(*view->line));
2827         if (!tmp)
2828                 return NULL;
2830         view->line = tmp;
2831         view->line_alloc = alloc;
2832         return view->line;
2835 static bool
2836 update_view(struct view *view)
2838         char out_buffer[BUFSIZ * 2];
2839         char *line;
2840         /* Clear the view and redraw everything since the tree sorting
2841          * might have rearranged things. */
2842         bool redraw = view->lines == 0;
2843         bool can_read = TRUE;
2845         if (!view->pipe)
2846                 return TRUE;
2848         if (!io_can_read(view->pipe)) {
2849                 if (view->lines == 0 && view_is_displayed(view)) {
2850                         time_t secs = time(NULL) - view->start_time;
2852                         if (secs > 1 && secs > view->update_secs) {
2853                                 if (view->update_secs == 0)
2854                                         redraw_view(view);
2855                                 update_view_title(view);
2856                                 view->update_secs = secs;
2857                         }
2858                 }
2859                 return TRUE;
2860         }
2862         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2863                 if (opt_iconv != ICONV_NONE) {
2864                         ICONV_CONST char *inbuf = line;
2865                         size_t inlen = strlen(line) + 1;
2867                         char *outbuf = out_buffer;
2868                         size_t outlen = sizeof(out_buffer);
2870                         size_t ret;
2872                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2873                         if (ret != (size_t) -1)
2874                                 line = out_buffer;
2875                 }
2877                 if (!view->ops->read(view, line)) {
2878                         report("Allocation failure");
2879                         end_update(view, TRUE);
2880                         return FALSE;
2881                 }
2882         }
2884         {
2885                 unsigned long lines = view->lines;
2886                 int digits;
2888                 for (digits = 0; lines; digits++)
2889                         lines /= 10;
2891                 /* Keep the displayed view in sync with line number scaling. */
2892                 if (digits != view->digits) {
2893                         view->digits = digits;
2894                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2895                                 redraw = TRUE;
2896                 }
2897         }
2899         if (io_error(view->pipe)) {
2900                 report("Failed to read: %s", io_strerror(view->pipe));
2901                 end_update(view, TRUE);
2903         } else if (io_eof(view->pipe)) {
2904                 report("");
2905                 end_update(view, FALSE);
2906         }
2908         if (restore_view_position(view))
2909                 redraw = TRUE;
2911         if (!view_is_displayed(view))
2912                 return TRUE;
2914         if (redraw)
2915                 redraw_view_from(view, 0);
2916         else
2917                 redraw_view_dirty(view);
2919         /* Update the title _after_ the redraw so that if the redraw picks up a
2920          * commit reference in view->ref it'll be available here. */
2921         update_view_title(view);
2922         return TRUE;
2925 static struct line *
2926 add_line_data(struct view *view, void *data, enum line_type type)
2928         struct line *line;
2930         if (!realloc_lines(view, view->lines + 1))
2931                 return NULL;
2933         line = &view->line[view->lines++];
2934         memset(line, 0, sizeof(*line));
2935         line->type = type;
2936         line->data = data;
2937         line->dirty = 1;
2939         return line;
2942 static struct line *
2943 add_line_text(struct view *view, const char *text, enum line_type type)
2945         char *data = text ? strdup(text) : NULL;
2947         return data ? add_line_data(view, data, type) : NULL;
2950 static struct line *
2951 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2953         char buf[SIZEOF_STR];
2954         va_list args;
2956         va_start(args, fmt);
2957         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2958                 buf[0] = 0;
2959         va_end(args);
2961         return buf[0] ? add_line_text(view, buf, type) : NULL;
2964 /*
2965  * View opening
2966  */
2968 enum open_flags {
2969         OPEN_DEFAULT = 0,       /* Use default view switching. */
2970         OPEN_SPLIT = 1,         /* Split current view. */
2971         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2972         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2973         OPEN_PREPARED = 32,     /* Open already prepared command. */
2974 };
2976 static void
2977 open_view(struct view *prev, enum request request, enum open_flags flags)
2979         bool split = !!(flags & OPEN_SPLIT);
2980         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2981         bool nomaximize = !!(flags & OPEN_REFRESH);
2982         struct view *view = VIEW(request);
2983         int nviews = displayed_views();
2984         struct view *base_view = display[0];
2986         if (view == prev && nviews == 1 && !reload) {
2987                 report("Already in %s view", view->name);
2988                 return;
2989         }
2991         if (view->git_dir && !opt_git_dir[0]) {
2992                 report("The %s view is disabled in pager view", view->name);
2993                 return;
2994         }
2996         if (split) {
2997                 display[1] = view;
2998                 current_view = 1;
2999         } else if (!nomaximize) {
3000                 /* Maximize the current view. */
3001                 memset(display, 0, sizeof(display));
3002                 current_view = 0;
3003                 display[current_view] = view;
3004         }
3006         /* Resize the view when switching between split- and full-screen,
3007          * or when switching between two different full-screen views. */
3008         if (nviews != displayed_views() ||
3009             (nviews == 1 && base_view != display[0]))
3010                 resize_display();
3012         if (view->ops->open) {
3013                 if (view->pipe)
3014                         end_update(view, TRUE);
3015                 if (!view->ops->open(view)) {
3016                         report("Failed to load %s view", view->name);
3017                         return;
3018                 }
3019                 restore_view_position(view);
3021         } else if ((reload || strcmp(view->vid, view->id)) &&
3022                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3023                 report("Failed to load %s view", view->name);
3024                 return;
3025         }
3027         if (split && prev->lineno - prev->offset >= prev->height) {
3028                 /* Take the title line into account. */
3029                 int lines = prev->lineno - prev->offset - prev->height + 1;
3031                 /* Scroll the view that was split if the current line is
3032                  * outside the new limited view. */
3033                 do_scroll_view(prev, lines);
3034         }
3036         if (prev && view != prev) {
3037                 if (split) {
3038                         /* "Blur" the previous view. */
3039                         update_view_title(prev);
3040                 }
3042                 view->parent = prev;
3043         }
3045         if (view->pipe && view->lines == 0) {
3046                 /* Clear the old view and let the incremental updating refill
3047                  * the screen. */
3048                 werase(view->win);
3049                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3050                 report("");
3051         } else if (view_is_displayed(view)) {
3052                 redraw_view(view);
3053                 report("");
3054         }
3057 static void
3058 open_external_viewer(const char *argv[], const char *dir)
3060         def_prog_mode();           /* save current tty modes */
3061         endwin();                  /* restore original tty modes */
3062         run_io_fg(argv, dir);
3063         fprintf(stderr, "Press Enter to continue");
3064         getc(opt_tty);
3065         reset_prog_mode();
3066         redraw_display(TRUE);
3069 static void
3070 open_mergetool(const char *file)
3072         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3074         open_external_viewer(mergetool_argv, opt_cdup);
3077 static void
3078 open_editor(bool from_root, const char *file)
3080         const char *editor_argv[] = { "vi", file, NULL };
3081         const char *editor;
3083         editor = getenv("GIT_EDITOR");
3084         if (!editor && *opt_editor)
3085                 editor = opt_editor;
3086         if (!editor)
3087                 editor = getenv("VISUAL");
3088         if (!editor)
3089                 editor = getenv("EDITOR");
3090         if (!editor)
3091                 editor = "vi";
3093         editor_argv[0] = editor;
3094         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3097 static void
3098 open_run_request(enum request request)
3100         struct run_request *req = get_run_request(request);
3101         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3103         if (!req) {
3104                 report("Unknown run request");
3105                 return;
3106         }
3108         if (format_argv(argv, req->argv, FORMAT_ALL))
3109                 open_external_viewer(argv, NULL);
3110         free_argv(argv);
3113 /*
3114  * User request switch noodle
3115  */
3117 static int
3118 view_driver(struct view *view, enum request request)
3120         int i;
3122         if (request == REQ_NONE)
3123                 return TRUE;
3125         if (request > REQ_NONE) {
3126                 open_run_request(request);
3127                 /* FIXME: When all views can refresh always do this. */
3128                 if (view == VIEW(REQ_VIEW_STATUS) ||
3129                     view == VIEW(REQ_VIEW_MAIN) ||
3130                     view == VIEW(REQ_VIEW_LOG) ||
3131                     view == VIEW(REQ_VIEW_STAGE))
3132                         request = REQ_REFRESH;
3133                 else
3134                         return TRUE;
3135         }
3137         if (view && view->lines) {
3138                 request = view->ops->request(view, request, &view->line[view->lineno]);
3139                 if (request == REQ_NONE)
3140                         return TRUE;
3141         }
3143         switch (request) {
3144         case REQ_MOVE_UP:
3145         case REQ_MOVE_DOWN:
3146         case REQ_MOVE_PAGE_UP:
3147         case REQ_MOVE_PAGE_DOWN:
3148         case REQ_MOVE_FIRST_LINE:
3149         case REQ_MOVE_LAST_LINE:
3150                 move_view(view, request);
3151                 break;
3153         case REQ_SCROLL_LEFT:
3154         case REQ_SCROLL_RIGHT:
3155         case REQ_SCROLL_LINE_DOWN:
3156         case REQ_SCROLL_LINE_UP:
3157         case REQ_SCROLL_PAGE_DOWN:
3158         case REQ_SCROLL_PAGE_UP:
3159                 scroll_view(view, request);
3160                 break;
3162         case REQ_VIEW_BLAME:
3163                 if (!opt_file[0]) {
3164                         report("No file chosen, press %s to open tree view",
3165                                get_key(REQ_VIEW_TREE));
3166                         break;
3167                 }
3168                 open_view(view, request, OPEN_DEFAULT);
3169                 break;
3171         case REQ_VIEW_BLOB:
3172                 if (!ref_blob[0]) {
3173                         report("No file chosen, press %s to open tree view",
3174                                get_key(REQ_VIEW_TREE));
3175                         break;
3176                 }
3177                 open_view(view, request, OPEN_DEFAULT);
3178                 break;
3180         case REQ_VIEW_PAGER:
3181                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3182                         report("No pager content, press %s to run command from prompt",
3183                                get_key(REQ_PROMPT));
3184                         break;
3185                 }
3186                 open_view(view, request, OPEN_DEFAULT);
3187                 break;
3189         case REQ_VIEW_STAGE:
3190                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3191                         report("No stage content, press %s to open the status view and choose file",
3192                                get_key(REQ_VIEW_STATUS));
3193                         break;
3194                 }
3195                 open_view(view, request, OPEN_DEFAULT);
3196                 break;
3198         case REQ_VIEW_STATUS:
3199                 if (opt_is_inside_work_tree == FALSE) {
3200                         report("The status view requires a working tree");
3201                         break;
3202                 }
3203                 open_view(view, request, OPEN_DEFAULT);
3204                 break;
3206         case REQ_VIEW_MAIN:
3207         case REQ_VIEW_DIFF:
3208         case REQ_VIEW_LOG:
3209         case REQ_VIEW_TREE:
3210         case REQ_VIEW_HELP:
3211                 open_view(view, request, OPEN_DEFAULT);
3212                 break;
3214         case REQ_NEXT:
3215         case REQ_PREVIOUS:
3216                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3218                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3219                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3220                    (view == VIEW(REQ_VIEW_DIFF) &&
3221                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3222                    (view == VIEW(REQ_VIEW_STAGE) &&
3223                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3224                    (view == VIEW(REQ_VIEW_BLOB) &&
3225                      view->parent == VIEW(REQ_VIEW_TREE))) {
3226                         int line;
3228                         view = view->parent;
3229                         line = view->lineno;
3230                         move_view(view, request);
3231                         if (view_is_displayed(view))
3232                                 update_view_title(view);
3233                         if (line != view->lineno)
3234                                 view->ops->request(view, REQ_ENTER,
3235                                                    &view->line[view->lineno]);
3237                 } else {
3238                         move_view(view, request);
3239                 }
3240                 break;
3242         case REQ_VIEW_NEXT:
3243         {
3244                 int nviews = displayed_views();
3245                 int next_view = (current_view + 1) % nviews;
3247                 if (next_view == current_view) {
3248                         report("Only one view is displayed");
3249                         break;
3250                 }
3252                 current_view = next_view;
3253                 /* Blur out the title of the previous view. */
3254                 update_view_title(view);
3255                 report("");
3256                 break;
3257         }
3258         case REQ_REFRESH:
3259                 report("Refreshing is not yet supported for the %s view", view->name);
3260                 break;
3262         case REQ_MAXIMIZE:
3263                 if (displayed_views() == 2)
3264                         maximize_view(view);
3265                 break;
3267         case REQ_TOGGLE_LINENO:
3268                 toggle_view_option(&opt_line_number, "line numbers");
3269                 break;
3271         case REQ_TOGGLE_DATE:
3272                 toggle_view_option(&opt_date, "date display");
3273                 break;
3275         case REQ_TOGGLE_AUTHOR:
3276                 toggle_view_option(&opt_author, "author display");
3277                 break;
3279         case REQ_TOGGLE_REV_GRAPH:
3280                 toggle_view_option(&opt_rev_graph, "revision graph display");
3281                 break;
3283         case REQ_TOGGLE_REFS:
3284                 toggle_view_option(&opt_show_refs, "reference display");
3285                 break;
3287         case REQ_SEARCH:
3288         case REQ_SEARCH_BACK:
3289                 search_view(view, request);
3290                 break;
3292         case REQ_FIND_NEXT:
3293         case REQ_FIND_PREV:
3294                 find_next(view, request);
3295                 break;
3297         case REQ_STOP_LOADING:
3298                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3299                         view = &views[i];
3300                         if (view->pipe)
3301                                 report("Stopped loading the %s view", view->name),
3302                         end_update(view, TRUE);
3303                 }
3304                 break;
3306         case REQ_SHOW_VERSION:
3307                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3308                 return TRUE;
3310         case REQ_SCREEN_REDRAW:
3311                 redraw_display(TRUE);
3312                 break;
3314         case REQ_EDIT:
3315                 report("Nothing to edit");
3316                 break;
3318         case REQ_ENTER:
3319                 report("Nothing to enter");
3320                 break;
3322         case REQ_VIEW_CLOSE:
3323                 /* XXX: Mark closed views by letting view->parent point to the
3324                  * view itself. Parents to closed view should never be
3325                  * followed. */
3326                 if (view->parent &&
3327                     view->parent->parent != view->parent) {
3328                         maximize_view(view->parent);
3329                         view->parent = view;
3330                         break;
3331                 }
3332                 /* Fall-through */
3333         case REQ_QUIT:
3334                 return FALSE;
3336         default:
3337                 report("Unknown key, press 'h' for help");
3338                 return TRUE;
3339         }
3341         return TRUE;
3345 /*
3346  * View backend utilities
3347  */
3349 /* Small author cache to reduce memory consumption. It uses binary
3350  * search to lookup or find place to position new entries. No entries
3351  * are ever freed. */
3352 static const char *
3353 get_author(const char *name)
3355         static const char **authors;
3356         static size_t authors_alloc;
3357         static size_t authors_size;
3358         const char **tmp;
3359         int from = 0, to = authors_size - 1;
3361         while (from <= to) {
3362                 size_t pos = (to + from) / 2;
3363                 int cmp = strcmp(name, authors[pos]);
3365                 if (!cmp)
3366                         return authors[pos];
3368                 if (cmp < 0)
3369                         to = pos - 1;
3370                 else
3371                         from = pos + 1;
3372         }
3374         tmp = realloc_items(authors, &authors_alloc, authors_size + 1, sizeof(*authors));
3375         if (!tmp)
3376                 return NULL;
3377         name = strdup(name);
3378         if (!name)
3379                 return NULL;
3381         authors = tmp;
3382         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3383         authors[from] = name;
3384         authors_size++;
3386         return name;
3389 static void
3390 parse_timezone(time_t *time, const char *zone)
3392         long tz;
3394         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3395         tz += ('0' - zone[2]) * 60 * 60;
3396         tz += ('0' - zone[3]) * 60;
3397         tz += ('0' - zone[4]);
3399         if (zone[0] == '-')
3400                 tz = -tz;
3402         *time -= tz;
3405 /* Parse author lines where the name may be empty:
3406  *      author  <email@address.tld> 1138474660 +0100
3407  */
3408 static void
3409 parse_author_line(char *ident, const char **author, time_t *time)
3411         char *nameend = strchr(ident, '<');
3412         char *emailend = strchr(ident, '>');
3414         if (nameend && emailend)
3415                 *nameend = *emailend = 0;
3416         ident = chomp_string(ident);
3417         if (!*ident) {
3418                 if (nameend)
3419                         ident = chomp_string(nameend + 1);
3420                 if (!*ident)
3421                         ident = "Unknown";
3422         }
3424         *author = get_author(ident);
3426         /* Parse epoch and timezone */
3427         if (emailend && emailend[1] == ' ') {
3428                 char *secs = emailend + 2;
3429                 char *zone = strchr(secs, ' ');
3431                 *time = (time_t) atol(secs);
3433                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3434                         parse_timezone(time, zone + 1);
3435         }
3438 static enum input_status
3439 select_commit_parent_handler(void *data, char *buf, int c)
3441         size_t parents = *(size_t *) data;
3442         int parent = 0;
3444         if (!isdigit(c))
3445                 return INPUT_SKIP;
3447         if (*buf)
3448                 parent = atoi(buf) * 10;
3449         parent += c - '0';
3451         if (parent > parents)
3452                 return INPUT_SKIP;
3453         return INPUT_OK;
3456 static bool
3457 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3459         char buf[SIZEOF_STR * 4];
3460         const char *revlist_argv[] = {
3461                 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3462         };
3463         int parents;
3465         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3466             !*chomp_string(buf) ||
3467             (parents = (strlen(buf) / 40) - 1) < 0) {
3468                 report("Failed to get parent information");
3469                 return FALSE;
3471         } else if (parents == 0) {
3472                 if (path)
3473                         report("Path '%s' does not exist in the parent", path);
3474                 else
3475                         report("The selected commit has no parents");
3476                 return FALSE;
3477         }
3479         if (parents > 1) {
3480                 char prompt[SIZEOF_STR];
3481                 char *result;
3483                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3484                         return FALSE;
3485                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3486                 if (!result)
3487                         return FALSE;
3488                 parents = atoi(result);
3489         }
3491         string_copy_rev(rev, &buf[41 * parents]);
3492         return TRUE;
3495 /*
3496  * Pager backend
3497  */
3499 static bool
3500 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3502         char text[SIZEOF_STR];
3504         if (opt_line_number && draw_lineno(view, lineno))
3505                 return TRUE;
3507         string_expand(text, sizeof(text), line->data, opt_tab_size);
3508         draw_text(view, line->type, text, TRUE);
3509         return TRUE;
3512 static bool
3513 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3515         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3516         char refbuf[SIZEOF_STR];
3517         char *ref = NULL;
3519         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3520                 ref = chomp_string(refbuf);
3522         if (!ref || !*ref)
3523                 return TRUE;
3525         /* This is the only fatal call, since it can "corrupt" the buffer. */
3526         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3527                 return FALSE;
3529         return TRUE;
3532 static void
3533 add_pager_refs(struct view *view, struct line *line)
3535         char buf[SIZEOF_STR];
3536         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3537         struct ref **refs;
3538         size_t bufpos = 0, refpos = 0;
3539         const char *sep = "Refs: ";
3540         bool is_tag = FALSE;
3542         assert(line->type == LINE_COMMIT);
3544         refs = get_refs(commit_id);
3545         if (!refs) {
3546                 if (view == VIEW(REQ_VIEW_DIFF))
3547                         goto try_add_describe_ref;
3548                 return;
3549         }
3551         do {
3552                 struct ref *ref = refs[refpos];
3553                 const char *fmt = ref->tag    ? "%s[%s]" :
3554                                   ref->remote ? "%s<%s>" : "%s%s";
3556                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3557                         return;
3558                 sep = ", ";
3559                 if (ref->tag)
3560                         is_tag = TRUE;
3561         } while (refs[refpos++]->next);
3563         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3564 try_add_describe_ref:
3565                 /* Add <tag>-g<commit_id> "fake" reference. */
3566                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3567                         return;
3568         }
3570         if (bufpos == 0)
3571                 return;
3573         add_line_text(view, buf, LINE_PP_REFS);
3576 static bool
3577 pager_read(struct view *view, char *data)
3579         struct line *line;
3581         if (!data)
3582                 return TRUE;
3584         line = add_line_text(view, data, get_line_type(data));
3585         if (!line)
3586                 return FALSE;
3588         if (line->type == LINE_COMMIT &&
3589             (view == VIEW(REQ_VIEW_DIFF) ||
3590              view == VIEW(REQ_VIEW_LOG)))
3591                 add_pager_refs(view, line);
3593         return TRUE;
3596 static enum request
3597 pager_request(struct view *view, enum request request, struct line *line)
3599         int split = 0;
3601         if (request != REQ_ENTER)
3602                 return request;
3604         if (line->type == LINE_COMMIT &&
3605            (view == VIEW(REQ_VIEW_LOG) ||
3606             view == VIEW(REQ_VIEW_PAGER))) {
3607                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3608                 split = 1;
3609         }
3611         /* Always scroll the view even if it was split. That way
3612          * you can use Enter to scroll through the log view and
3613          * split open each commit diff. */
3614         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3616         /* FIXME: A minor workaround. Scrolling the view will call report("")
3617          * but if we are scrolling a non-current view this won't properly
3618          * update the view title. */
3619         if (split)
3620                 update_view_title(view);
3622         return REQ_NONE;
3625 static bool
3626 pager_grep(struct view *view, struct line *line)
3628         const char *text[] = { line->data, NULL };
3630         return grep_text(view, text);
3633 static void
3634 pager_select(struct view *view, struct line *line)
3636         if (line->type == LINE_COMMIT) {
3637                 char *text = (char *)line->data + STRING_SIZE("commit ");
3639                 if (view != VIEW(REQ_VIEW_PAGER))
3640                         string_copy_rev(view->ref, text);
3641                 string_copy_rev(ref_commit, text);
3642         }
3645 static struct view_ops pager_ops = {
3646         "line",
3647         NULL,
3648         NULL,
3649         pager_read,
3650         pager_draw,
3651         pager_request,
3652         pager_grep,
3653         pager_select,
3654 };
3656 static const char *log_argv[SIZEOF_ARG] = {
3657         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3658 };
3660 static enum request
3661 log_request(struct view *view, enum request request, struct line *line)
3663         switch (request) {
3664         case REQ_REFRESH:
3665                 load_refs();
3666                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3667                 return REQ_NONE;
3668         default:
3669                 return pager_request(view, request, line);
3670         }
3673 static struct view_ops log_ops = {
3674         "line",
3675         log_argv,
3676         NULL,
3677         pager_read,
3678         pager_draw,
3679         log_request,
3680         pager_grep,
3681         pager_select,
3682 };
3684 static const char *diff_argv[SIZEOF_ARG] = {
3685         "git", "show", "--pretty=fuller", "--no-color", "--root",
3686                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3687 };
3689 static struct view_ops diff_ops = {
3690         "line",
3691         diff_argv,
3692         NULL,
3693         pager_read,
3694         pager_draw,
3695         pager_request,
3696         pager_grep,
3697         pager_select,
3698 };
3700 /*
3701  * Help backend
3702  */
3704 static bool
3705 help_open(struct view *view)
3707         char buf[SIZEOF_STR];
3708         size_t bufpos;
3709         int i;
3711         if (view->lines > 0)
3712                 return TRUE;
3714         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3716         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3717                 const char *key;
3719                 if (req_info[i].request == REQ_NONE)
3720                         continue;
3722                 if (!req_info[i].request) {
3723                         add_line_text(view, "", LINE_DEFAULT);
3724                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3725                         continue;
3726                 }
3728                 key = get_key(req_info[i].request);
3729                 if (!*key)
3730                         key = "(no key defined)";
3732                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3733                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3734                         if (buf[bufpos] == '_')
3735                                 buf[bufpos] = '-';
3736                 }
3738                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3739                                 key, buf, req_info[i].help);
3740         }
3742         if (run_requests) {
3743                 add_line_text(view, "", LINE_DEFAULT);
3744                 add_line_text(view, "External commands:", LINE_DEFAULT);
3745         }
3747         for (i = 0; i < run_requests; i++) {
3748                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3749                 const char *key;
3750                 int argc;
3752                 if (!req)
3753                         continue;
3755                 key = get_key_name(req->key);
3756                 if (!*key)
3757                         key = "(no key defined)";
3759                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3760                         if (!string_format_from(buf, &bufpos, "%s%s",
3761                                                 argc ? " " : "", req->argv[argc]))
3762                                 return REQ_NONE;
3764                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3765                                 keymap_table[req->keymap].name, key, buf);
3766         }
3768         return TRUE;
3771 static struct view_ops help_ops = {
3772         "line",
3773         NULL,
3774         help_open,
3775         NULL,
3776         pager_draw,
3777         pager_request,
3778         pager_grep,
3779         pager_select,
3780 };
3783 /*
3784  * Tree backend
3785  */
3787 struct tree_stack_entry {
3788         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3789         unsigned long lineno;           /* Line number to restore */
3790         char *name;                     /* Position of name in opt_path */
3791 };
3793 /* The top of the path stack. */
3794 static struct tree_stack_entry *tree_stack = NULL;
3795 unsigned long tree_lineno = 0;
3797 static void
3798 pop_tree_stack_entry(void)
3800         struct tree_stack_entry *entry = tree_stack;
3802         tree_lineno = entry->lineno;
3803         entry->name[0] = 0;
3804         tree_stack = entry->prev;
3805         free(entry);
3808 static void
3809 push_tree_stack_entry(const char *name, unsigned long lineno)
3811         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3812         size_t pathlen = strlen(opt_path);
3814         if (!entry)
3815                 return;
3817         entry->prev = tree_stack;
3818         entry->name = opt_path + pathlen;
3819         tree_stack = entry;
3821         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3822                 pop_tree_stack_entry();
3823                 return;
3824         }
3826         /* Move the current line to the first tree entry. */
3827         tree_lineno = 1;
3828         entry->lineno = lineno;
3831 /* Parse output from git-ls-tree(1):
3832  *
3833  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3834  */
3836 #define SIZEOF_TREE_ATTR \
3837         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3839 #define SIZEOF_TREE_MODE \
3840         STRING_SIZE("100644 ")
3842 #define TREE_ID_OFFSET \
3843         STRING_SIZE("100644 blob ")
3845 struct tree_entry {
3846         char id[SIZEOF_REV];
3847         mode_t mode;
3848         time_t time;                    /* Date from the author ident. */
3849         const char *author;             /* Author of the commit. */
3850         char name[1];
3851 };
3853 static const char *
3854 tree_path(struct line *line)
3856         return ((struct tree_entry *) line->data)->name;
3860 static int
3861 tree_compare_entry(struct line *line1, struct line *line2)
3863         if (line1->type != line2->type)
3864                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3865         return strcmp(tree_path(line1), tree_path(line2));
3868 static struct line *
3869 tree_entry(struct view *view, enum line_type type, const char *path,
3870            const char *mode, const char *id)
3872         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3873         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3875         if (!entry || !line) {
3876                 free(entry);
3877                 return NULL;
3878         }
3880         strncpy(entry->name, path, strlen(path));
3881         if (mode)
3882                 entry->mode = strtoul(mode, NULL, 8);
3883         if (id)
3884                 string_copy_rev(entry->id, id);
3886         return line;
3889 static bool
3890 tree_read_date(struct view *view, char *text, bool *read_date)
3892         static const char *author_name;
3893         static time_t author_time;
3895         if (!text && *read_date) {
3896                 *read_date = FALSE;
3897                 return TRUE;
3899         } else if (!text) {
3900                 char *path = *opt_path ? opt_path : ".";
3901                 /* Find next entry to process */
3902                 const char *log_file[] = {
3903                         "git", "log", "--no-color", "--pretty=raw",
3904                                 "--cc", "--raw", view->id, "--", path, NULL
3905                 };
3906                 struct io io = {};
3908                 if (!view->lines) {
3909                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3910                         report("Tree is empty");
3911                         return TRUE;
3912                 }
3914                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3915                         report("Failed to load tree data");
3916                         return TRUE;
3917                 }
3919                 done_io(view->pipe);
3920                 view->io = io;
3921                 *read_date = TRUE;
3922                 return FALSE;
3924         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3925                 parse_author_line(text + STRING_SIZE("author "),
3926                                   &author_name, &author_time);
3928         } else if (*text == ':') {
3929                 char *pos;
3930                 size_t annotated = 1;
3931                 size_t i;
3933                 pos = strchr(text, '\t');
3934                 if (!pos)
3935                         return TRUE;
3936                 text = pos + 1;
3937                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3938                         text += strlen(opt_prefix);
3939                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3940                         text += strlen(opt_path);
3941                 pos = strchr(text, '/');
3942                 if (pos)
3943                         *pos = 0;
3945                 for (i = 1; i < view->lines; i++) {
3946                         struct line *line = &view->line[i];
3947                         struct tree_entry *entry = line->data;
3949                         annotated += !!entry->author;
3950                         if (entry->author || strcmp(entry->name, text))
3951                                 continue;
3953                         entry->author = author_name;
3954                         entry->time = author_time;
3955                         line->dirty = 1;
3956                         break;
3957                 }
3959                 if (annotated == view->lines)
3960                         kill_io(view->pipe);
3961         }
3962         return TRUE;
3965 static bool
3966 tree_read(struct view *view, char *text)
3968         static bool read_date = FALSE;
3969         struct tree_entry *data;
3970         struct line *entry, *line;
3971         enum line_type type;
3972         size_t textlen = text ? strlen(text) : 0;
3973         char *path = text + SIZEOF_TREE_ATTR;
3975         if (read_date || !text)
3976                 return tree_read_date(view, text, &read_date);
3978         if (textlen <= SIZEOF_TREE_ATTR)
3979                 return FALSE;
3980         if (view->lines == 0 &&
3981             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3982                 return FALSE;
3984         /* Strip the path part ... */
3985         if (*opt_path) {
3986                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3987                 size_t striplen = strlen(opt_path);
3989                 if (pathlen > striplen)
3990                         memmove(path, path + striplen,
3991                                 pathlen - striplen + 1);
3993                 /* Insert "link" to parent directory. */
3994                 if (view->lines == 1 &&
3995                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3996                         return FALSE;
3997         }
3999         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4000         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4001         if (!entry)
4002                 return FALSE;
4003         data = entry->data;
4005         /* Skip "Directory ..." and ".." line. */
4006         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4007                 if (tree_compare_entry(line, entry) <= 0)
4008                         continue;
4010                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4012                 line->data = data;
4013                 line->type = type;
4014                 for (; line <= entry; line++)
4015                         line->dirty = line->cleareol = 1;
4016                 return TRUE;
4017         }
4019         if (tree_lineno > view->lineno) {
4020                 view->lineno = tree_lineno;
4021                 tree_lineno = 0;
4022         }
4024         return TRUE;
4027 static bool
4028 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4030         struct tree_entry *entry = line->data;
4032         if (line->type == LINE_TREE_HEAD) {
4033                 if (draw_text(view, line->type, "Directory path /", TRUE))
4034                         return TRUE;
4035         } else {
4036                 if (draw_mode(view, entry->mode))
4037                         return TRUE;
4039                 if (opt_author && draw_author(view, entry->author))
4040                         return TRUE;
4042                 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4043                         return TRUE;
4044         }
4045         if (draw_text(view, line->type, entry->name, TRUE))
4046                 return TRUE;
4047         return TRUE;
4050 static void
4051 open_blob_editor()
4053         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4054         int fd = mkstemp(file);
4056         if (fd == -1)
4057                 report("Failed to create temporary file");
4058         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4059                 report("Failed to save blob data to file");
4060         else
4061                 open_editor(FALSE, file);
4062         if (fd != -1)
4063                 unlink(file);
4066 static enum request
4067 tree_request(struct view *view, enum request request, struct line *line)
4069         enum open_flags flags;
4071         switch (request) {
4072         case REQ_VIEW_BLAME:
4073                 if (line->type != LINE_TREE_FILE) {
4074                         report("Blame only supported for files");
4075                         return REQ_NONE;
4076                 }
4078                 string_copy(opt_ref, view->vid);
4079                 return request;
4081         case REQ_EDIT:
4082                 if (line->type != LINE_TREE_FILE) {
4083                         report("Edit only supported for files");
4084                 } else if (!is_head_commit(view->vid)) {
4085                         open_blob_editor();
4086                 } else {
4087                         open_editor(TRUE, opt_file);
4088                 }
4089                 return REQ_NONE;
4091         case REQ_PARENT:
4092                 if (!*opt_path) {
4093                         /* quit view if at top of tree */
4094                         return REQ_VIEW_CLOSE;
4095                 }
4096                 /* fake 'cd  ..' */
4097                 line = &view->line[1];
4098                 break;
4100         case REQ_ENTER:
4101                 break;
4103         default:
4104                 return request;
4105         }
4107         /* Cleanup the stack if the tree view is at a different tree. */
4108         while (!*opt_path && tree_stack)
4109                 pop_tree_stack_entry();
4111         switch (line->type) {
4112         case LINE_TREE_DIR:
4113                 /* Depending on whether it is a subdirectory or parent link
4114                  * mangle the path buffer. */
4115                 if (line == &view->line[1] && *opt_path) {
4116                         pop_tree_stack_entry();
4118                 } else {
4119                         const char *basename = tree_path(line);
4121                         push_tree_stack_entry(basename, view->lineno);
4122                 }
4124                 /* Trees and subtrees share the same ID, so they are not not
4125                  * unique like blobs. */
4126                 flags = OPEN_RELOAD;
4127                 request = REQ_VIEW_TREE;
4128                 break;
4130         case LINE_TREE_FILE:
4131                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4132                 request = REQ_VIEW_BLOB;
4133                 break;
4135         default:
4136                 return REQ_NONE;
4137         }
4139         open_view(view, request, flags);
4140         if (request == REQ_VIEW_TREE)
4141                 view->lineno = tree_lineno;
4143         return REQ_NONE;
4146 static void
4147 tree_select(struct view *view, struct line *line)
4149         struct tree_entry *entry = line->data;
4151         if (line->type == LINE_TREE_FILE) {
4152                 string_copy_rev(ref_blob, entry->id);
4153                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4155         } else if (line->type != LINE_TREE_DIR) {
4156                 return;
4157         }
4159         string_copy_rev(view->ref, entry->id);
4162 static const char *tree_argv[SIZEOF_ARG] = {
4163         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4164 };
4166 static struct view_ops tree_ops = {
4167         "file",
4168         tree_argv,
4169         NULL,
4170         tree_read,
4171         tree_draw,
4172         tree_request,
4173         pager_grep,
4174         tree_select,
4175 };
4177 static bool
4178 blob_read(struct view *view, char *line)
4180         if (!line)
4181                 return TRUE;
4182         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4185 static enum request
4186 blob_request(struct view *view, enum request request, struct line *line)
4188         switch (request) {
4189         case REQ_EDIT:
4190                 open_blob_editor();
4191                 return REQ_NONE;
4192         default:
4193                 return pager_request(view, request, line);
4194         }
4197 static const char *blob_argv[SIZEOF_ARG] = {
4198         "git", "cat-file", "blob", "%(blob)", NULL
4199 };
4201 static struct view_ops blob_ops = {
4202         "line",
4203         blob_argv,
4204         NULL,
4205         blob_read,
4206         pager_draw,
4207         blob_request,
4208         pager_grep,
4209         pager_select,
4210 };
4212 /*
4213  * Blame backend
4214  *
4215  * Loading the blame view is a two phase job:
4216  *
4217  *  1. File content is read either using opt_file from the
4218  *     filesystem or using git-cat-file.
4219  *  2. Then blame information is incrementally added by
4220  *     reading output from git-blame.
4221  */
4223 static const char *blame_head_argv[] = {
4224         "git", "blame", "--incremental", "--", "%(file)", NULL
4225 };
4227 static const char *blame_ref_argv[] = {
4228         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4229 };
4231 static const char *blame_cat_file_argv[] = {
4232         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4233 };
4235 struct blame_commit {
4236         char id[SIZEOF_REV];            /* SHA1 ID. */
4237         char title[128];                /* First line of the commit message. */
4238         const char *author;             /* Author of the commit. */
4239         time_t time;                    /* Date from the author ident. */
4240         char filename[128];             /* Name of file. */
4241         bool has_previous;              /* Was a "previous" line detected. */
4242 };
4244 struct blame {
4245         struct blame_commit *commit;
4246         unsigned long lineno;
4247         char text[1];
4248 };
4250 static bool
4251 blame_open(struct view *view)
4253         if (*opt_ref || !io_open(&view->io, opt_file)) {
4254                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4255                         return FALSE;
4256         }
4258         setup_update(view, opt_file);
4259         string_format(view->ref, "%s ...", opt_file);
4261         return TRUE;
4264 static struct blame_commit *
4265 get_blame_commit(struct view *view, const char *id)
4267         size_t i;
4269         for (i = 0; i < view->lines; i++) {
4270                 struct blame *blame = view->line[i].data;
4272                 if (!blame->commit)
4273                         continue;
4275                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4276                         return blame->commit;
4277         }
4279         {
4280                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4282                 if (commit)
4283                         string_ncopy(commit->id, id, SIZEOF_REV);
4284                 return commit;
4285         }
4288 static bool
4289 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4291         const char *pos = *posref;
4293         *posref = NULL;
4294         pos = strchr(pos + 1, ' ');
4295         if (!pos || !isdigit(pos[1]))
4296                 return FALSE;
4297         *number = atoi(pos + 1);
4298         if (*number < min || *number > max)
4299                 return FALSE;
4301         *posref = pos;
4302         return TRUE;
4305 static struct blame_commit *
4306 parse_blame_commit(struct view *view, const char *text, int *blamed)
4308         struct blame_commit *commit;
4309         struct blame *blame;
4310         const char *pos = text + SIZEOF_REV - 2;
4311         size_t orig_lineno = 0;
4312         size_t lineno;
4313         size_t group;
4315         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4316                 return NULL;
4318         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4319             !parse_number(&pos, &lineno, 1, view->lines) ||
4320             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4321                 return NULL;
4323         commit = get_blame_commit(view, text);
4324         if (!commit)
4325                 return NULL;
4327         *blamed += group;
4328         while (group--) {
4329                 struct line *line = &view->line[lineno + group - 1];
4331                 blame = line->data;
4332                 blame->commit = commit;
4333                 blame->lineno = orig_lineno + group - 1;
4334                 line->dirty = 1;
4335         }
4337         return commit;
4340 static bool
4341 blame_read_file(struct view *view, const char *line, bool *read_file)
4343         if (!line) {
4344                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4345                 struct io io = {};
4347                 if (view->lines == 0 && !view->parent)
4348                         die("No blame exist for %s", view->vid);
4350                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4351                         report("Failed to load blame data");
4352                         return TRUE;
4353                 }
4355                 done_io(view->pipe);
4356                 view->io = io;
4357                 *read_file = FALSE;
4358                 return FALSE;
4360         } else {
4361                 size_t linelen = strlen(line);
4362                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4364                 if (!blame)
4365                         return FALSE;
4367                 blame->commit = NULL;
4368                 strncpy(blame->text, line, linelen);
4369                 blame->text[linelen] = 0;
4370                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4371         }
4374 static bool
4375 match_blame_header(const char *name, char **line)
4377         size_t namelen = strlen(name);
4378         bool matched = !strncmp(name, *line, namelen);
4380         if (matched)
4381                 *line += namelen;
4383         return matched;
4386 static bool
4387 blame_read(struct view *view, char *line)
4389         static struct blame_commit *commit = NULL;
4390         static int blamed = 0;
4391         static bool read_file = TRUE;
4393         if (read_file)
4394                 return blame_read_file(view, line, &read_file);
4396         if (!line) {
4397                 /* Reset all! */
4398                 commit = NULL;
4399                 blamed = 0;
4400                 read_file = TRUE;
4401                 string_format(view->ref, "%s", view->vid);
4402                 if (view_is_displayed(view)) {
4403                         update_view_title(view);
4404                         redraw_view_from(view, 0);
4405                 }
4406                 return TRUE;
4407         }
4409         if (!commit) {
4410                 commit = parse_blame_commit(view, line, &blamed);
4411                 string_format(view->ref, "%s %2d%%", view->vid,
4412                               view->lines ? blamed * 100 / view->lines : 0);
4414         } else if (match_blame_header("author ", &line)) {
4415                 commit->author = get_author(line);
4417         } else if (match_blame_header("author-time ", &line)) {
4418                 commit->time = (time_t) atol(line);
4420         } else if (match_blame_header("author-tz ", &line)) {
4421                 parse_timezone(&commit->time, line);
4423         } else if (match_blame_header("summary ", &line)) {
4424                 string_ncopy(commit->title, line, strlen(line));
4426         } else if (match_blame_header("previous ", &line)) {
4427                 commit->has_previous = TRUE;
4429         } else if (match_blame_header("filename ", &line)) {
4430                 string_ncopy(commit->filename, line, strlen(line));
4431                 commit = NULL;
4432         }
4434         return TRUE;
4437 static bool
4438 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4440         struct blame *blame = line->data;
4441         time_t *time = NULL;
4442         const char *id = NULL, *author = NULL;
4443         char text[SIZEOF_STR];
4445         if (blame->commit && *blame->commit->filename) {
4446                 id = blame->commit->id;
4447                 author = blame->commit->author;
4448                 time = &blame->commit->time;
4449         }
4451         if (opt_date && draw_date(view, time))
4452                 return TRUE;
4454         if (opt_author && draw_author(view, author))
4455                 return TRUE;
4457         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4458                 return TRUE;
4460         if (draw_lineno(view, lineno))
4461                 return TRUE;
4463         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4464         draw_text(view, LINE_DEFAULT, text, TRUE);
4465         return TRUE;
4468 static bool
4469 check_blame_commit(struct blame *blame, bool check_null_id)
4471         if (!blame->commit)
4472                 report("Commit data not loaded yet");
4473         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4474                 report("No commit exist for the selected line");
4475         else
4476                 return TRUE;
4477         return FALSE;
4480 static void
4481 setup_blame_parent_line(struct view *view, struct blame *blame)
4483         const char *diff_tree_argv[] = {
4484                 "git", "diff-tree", "-U0", blame->commit->id,
4485                         "--", blame->commit->filename, NULL
4486         };
4487         struct io io = {};
4488         int parent_lineno = -1;
4489         int blamed_lineno = -1;
4490         char *line;
4492         if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4493                 return;
4495         while ((line = io_get(&io, '\n', TRUE))) {
4496                 if (*line == '@') {
4497                         char *pos = strchr(line, '+');
4499                         parent_lineno = atoi(line + 4);
4500                         if (pos)
4501                                 blamed_lineno = atoi(pos + 1);
4503                 } else if (*line == '+' && parent_lineno != -1) {
4504                         if (blame->lineno == blamed_lineno - 1 &&
4505                             !strcmp(blame->text, line + 1)) {
4506                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4507                                 break;
4508                         }
4509                         blamed_lineno++;
4510                 }
4511         }
4513         done_io(&io);
4516 static enum request
4517 blame_request(struct view *view, enum request request, struct line *line)
4519         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4520         struct blame *blame = line->data;
4522         switch (request) {
4523         case REQ_VIEW_BLAME:
4524                 if (check_blame_commit(blame, TRUE)) {
4525                         string_copy(opt_ref, blame->commit->id);
4526                         string_copy(opt_file, blame->commit->filename);
4527                         if (blame->lineno)
4528                                 view->lineno = blame->lineno;
4529                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4530                 }
4531                 break;
4533         case REQ_PARENT:
4534                 if (check_blame_commit(blame, TRUE) &&
4535                     select_commit_parent(blame->commit->id, opt_ref,
4536                                          blame->commit->filename)) {
4537                         string_copy(opt_file, blame->commit->filename);
4538                         setup_blame_parent_line(view, blame);
4539                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4540                 }
4541                 break;
4543         case REQ_ENTER:
4544                 if (!check_blame_commit(blame, FALSE))
4545                         break;
4547                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4548                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4549                         break;
4551                 if (!strcmp(blame->commit->id, NULL_ID)) {
4552                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4553                         const char *diff_index_argv[] = {
4554                                 "git", "diff-index", "--root", "--patch-with-stat",
4555                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4556                         };
4558                         if (!blame->commit->has_previous) {
4559                                 diff_index_argv[1] = "diff";
4560                                 diff_index_argv[2] = "--no-color";
4561                                 diff_index_argv[6] = "--";
4562                                 diff_index_argv[7] = "/dev/null";
4563                         }
4565                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4566                                 report("Failed to allocate diff command");
4567                                 break;
4568                         }
4569                         flags |= OPEN_PREPARED;
4570                 }
4572                 open_view(view, REQ_VIEW_DIFF, flags);
4573                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4574                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4575                 break;
4577         default:
4578                 return request;
4579         }
4581         return REQ_NONE;
4584 static bool
4585 blame_grep(struct view *view, struct line *line)
4587         struct blame *blame = line->data;
4588         struct blame_commit *commit = blame->commit;
4589         const char *text[] = {
4590                 blame->text,
4591                 commit ? commit->title : "",
4592                 commit ? commit->id : "",
4593                 commit && opt_author ? commit->author : "",
4594                 commit && opt_date ? mkdate(&commit->time) : "",
4595         };
4597         return grep_text(view, text);
4600 static void
4601 blame_select(struct view *view, struct line *line)
4603         struct blame *blame = line->data;
4604         struct blame_commit *commit = blame->commit;
4606         if (!commit)
4607                 return;
4609         if (!strcmp(commit->id, NULL_ID))
4610                 string_ncopy(ref_commit, "HEAD", 4);
4611         else
4612                 string_copy_rev(ref_commit, commit->id);
4615 static struct view_ops blame_ops = {
4616         "line",
4617         NULL,
4618         blame_open,
4619         blame_read,
4620         blame_draw,
4621         blame_request,
4622         blame_grep,
4623         blame_select,
4624 };
4626 /*
4627  * Status backend
4628  */
4630 struct status {
4631         char status;
4632         struct {
4633                 mode_t mode;
4634                 char rev[SIZEOF_REV];
4635                 char name[SIZEOF_STR];
4636         } old;
4637         struct {
4638                 mode_t mode;
4639                 char rev[SIZEOF_REV];
4640                 char name[SIZEOF_STR];
4641         } new;
4642 };
4644 static char status_onbranch[SIZEOF_STR];
4645 static struct status stage_status;
4646 static enum line_type stage_line_type;
4647 static size_t stage_chunks;
4648 static int *stage_chunk;
4650 /* This should work even for the "On branch" line. */
4651 static inline bool
4652 status_has_none(struct view *view, struct line *line)
4654         return line < view->line + view->lines && !line[1].data;
4657 /* Get fields from the diff line:
4658  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4659  */
4660 static inline bool
4661 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4663         const char *old_mode = buf +  1;
4664         const char *new_mode = buf +  8;
4665         const char *old_rev  = buf + 15;
4666         const char *new_rev  = buf + 56;
4667         const char *status   = buf + 97;
4669         if (bufsize < 98 ||
4670             old_mode[-1] != ':' ||
4671             new_mode[-1] != ' ' ||
4672             old_rev[-1]  != ' ' ||
4673             new_rev[-1]  != ' ' ||
4674             status[-1]   != ' ')
4675                 return FALSE;
4677         file->status = *status;
4679         string_copy_rev(file->old.rev, old_rev);
4680         string_copy_rev(file->new.rev, new_rev);
4682         file->old.mode = strtoul(old_mode, NULL, 8);
4683         file->new.mode = strtoul(new_mode, NULL, 8);
4685         file->old.name[0] = file->new.name[0] = 0;
4687         return TRUE;
4690 static bool
4691 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4693         struct status *unmerged = NULL;
4694         char *buf;
4695         struct io io = {};
4697         if (!run_io(&io, argv, NULL, IO_RD))
4698                 return FALSE;
4700         add_line_data(view, NULL, type);
4702         while ((buf = io_get(&io, 0, TRUE))) {
4703                 struct status *file = unmerged;
4705                 if (!file) {
4706                         file = calloc(1, sizeof(*file));
4707                         if (!file || !add_line_data(view, file, type))
4708                                 goto error_out;
4709                 }
4711                 /* Parse diff info part. */
4712                 if (status) {
4713                         file->status = status;
4714                         if (status == 'A')
4715                                 string_copy(file->old.rev, NULL_ID);
4717                 } else if (!file->status || file == unmerged) {
4718                         if (!status_get_diff(file, buf, strlen(buf)))
4719                                 goto error_out;
4721                         buf = io_get(&io, 0, TRUE);
4722                         if (!buf)
4723                                 break;
4725                         /* Collapse all modified entries that follow an
4726                          * associated unmerged entry. */
4727                         if (unmerged == file) {
4728                                 unmerged->status = 'U';
4729                                 unmerged = NULL;
4730                         } else if (file->status == 'U') {
4731                                 unmerged = file;
4732                         }
4733                 }
4735                 /* Grab the old name for rename/copy. */
4736                 if (!*file->old.name &&
4737                     (file->status == 'R' || file->status == 'C')) {
4738                         string_ncopy(file->old.name, buf, strlen(buf));
4740                         buf = io_get(&io, 0, TRUE);
4741                         if (!buf)
4742                                 break;
4743                 }
4745                 /* git-ls-files just delivers a NUL separated list of
4746                  * file names similar to the second half of the
4747                  * git-diff-* output. */
4748                 string_ncopy(file->new.name, buf, strlen(buf));
4749                 if (!*file->old.name)
4750                         string_copy(file->old.name, file->new.name);
4751                 file = NULL;
4752         }
4754         if (io_error(&io)) {
4755 error_out:
4756                 done_io(&io);
4757                 return FALSE;
4758         }
4760         if (!view->line[view->lines - 1].data)
4761                 add_line_data(view, NULL, LINE_STAT_NONE);
4763         done_io(&io);
4764         return TRUE;
4767 /* Don't show unmerged entries in the staged section. */
4768 static const char *status_diff_index_argv[] = {
4769         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4770                              "--cached", "-M", "HEAD", NULL
4771 };
4773 static const char *status_diff_files_argv[] = {
4774         "git", "diff-files", "-z", NULL
4775 };
4777 static const char *status_list_other_argv[] = {
4778         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4779 };
4781 static const char *status_list_no_head_argv[] = {
4782         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4783 };
4785 static const char *update_index_argv[] = {
4786         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4787 };
4789 /* Restore the previous line number to stay in the context or select a
4790  * line with something that can be updated. */
4791 static void
4792 status_restore(struct view *view)
4794         if (view->p_lineno >= view->lines)
4795                 view->p_lineno = view->lines - 1;
4796         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4797                 view->p_lineno++;
4798         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4799                 view->p_lineno--;
4801         /* If the above fails, always skip the "On branch" line. */
4802         if (view->p_lineno < view->lines)
4803                 view->lineno = view->p_lineno;
4804         else
4805                 view->lineno = 1;
4807         if (view->lineno < view->offset)
4808                 view->offset = view->lineno;
4809         else if (view->offset + view->height <= view->lineno)
4810                 view->offset = view->lineno - view->height + 1;
4812         view->p_restore = FALSE;
4815 static void
4816 status_update_onbranch(void)
4818         static const char *paths[][2] = {
4819                 { "rebase-apply/rebasing",      "Rebasing" },
4820                 { "rebase-apply/applying",      "Applying mailbox" },
4821                 { "rebase-apply/",              "Rebasing mailbox" },
4822                 { "rebase-merge/interactive",   "Interactive rebase" },
4823                 { "rebase-merge/",              "Rebase merge" },
4824                 { "MERGE_HEAD",                 "Merging" },
4825                 { "BISECT_LOG",                 "Bisecting" },
4826                 { "HEAD",                       "On branch" },
4827         };
4828         char buf[SIZEOF_STR];
4829         struct stat stat;
4830         int i;
4832         if (is_initial_commit()) {
4833                 string_copy(status_onbranch, "Initial commit");
4834                 return;
4835         }
4837         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4838                 char *head = opt_head;
4840                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4841                     lstat(buf, &stat) < 0)
4842                         continue;
4844                 if (!*opt_head) {
4845                         struct io io = {};
4847                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4848                             io_open(&io, buf) &&
4849                             io_read_buf(&io, buf, sizeof(buf))) {
4850                                 head = chomp_string(buf);
4851                                 if (!prefixcmp(head, "refs/heads/"))
4852                                         head += STRING_SIZE("refs/heads/");
4853                         }
4854                 }
4856                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4857                         string_copy(status_onbranch, opt_head);
4858                 return;
4859         }
4861         string_copy(status_onbranch, "Not currently on any branch");
4864 /* First parse staged info using git-diff-index(1), then parse unstaged
4865  * info using git-diff-files(1), and finally untracked files using
4866  * git-ls-files(1). */
4867 static bool
4868 status_open(struct view *view)
4870         reset_view(view);
4872         add_line_data(view, NULL, LINE_STAT_HEAD);
4873         status_update_onbranch();
4875         run_io_bg(update_index_argv);
4877         if (is_initial_commit()) {
4878                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4879                         return FALSE;
4880         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4881                 return FALSE;
4882         }
4884         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4885             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4886                 return FALSE;
4888         /* Restore the exact position or use the specialized restore
4889          * mode? */
4890         if (!view->p_restore)
4891                 status_restore(view);
4892         return TRUE;
4895 static bool
4896 status_draw(struct view *view, struct line *line, unsigned int lineno)
4898         struct status *status = line->data;
4899         enum line_type type;
4900         const char *text;
4902         if (!status) {
4903                 switch (line->type) {
4904                 case LINE_STAT_STAGED:
4905                         type = LINE_STAT_SECTION;
4906                         text = "Changes to be committed:";
4907                         break;
4909                 case LINE_STAT_UNSTAGED:
4910                         type = LINE_STAT_SECTION;
4911                         text = "Changed but not updated:";
4912                         break;
4914                 case LINE_STAT_UNTRACKED:
4915                         type = LINE_STAT_SECTION;
4916                         text = "Untracked files:";
4917                         break;
4919                 case LINE_STAT_NONE:
4920                         type = LINE_DEFAULT;
4921                         text = "  (no files)";
4922                         break;
4924                 case LINE_STAT_HEAD:
4925                         type = LINE_STAT_HEAD;
4926                         text = status_onbranch;
4927                         break;
4929                 default:
4930                         return FALSE;
4931                 }
4932         } else {
4933                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4935                 buf[0] = status->status;
4936                 if (draw_text(view, line->type, buf, TRUE))
4937                         return TRUE;
4938                 type = LINE_DEFAULT;
4939                 text = status->new.name;
4940         }
4942         draw_text(view, type, text, TRUE);
4943         return TRUE;
4946 static enum request
4947 status_load_error(struct view *view, struct view *stage, const char *path)
4949         if (displayed_views() == 2 || display[current_view] != view)
4950                 maximize_view(view);
4951         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4952         return REQ_NONE;
4955 static enum request
4956 status_enter(struct view *view, struct line *line)
4958         struct status *status = line->data;
4959         const char *oldpath = status ? status->old.name : NULL;
4960         /* Diffs for unmerged entries are empty when passing the new
4961          * path, so leave it empty. */
4962         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4963         const char *info;
4964         enum open_flags split;
4965         struct view *stage = VIEW(REQ_VIEW_STAGE);
4967         if (line->type == LINE_STAT_NONE ||
4968             (!status && line[1].type == LINE_STAT_NONE)) {
4969                 report("No file to diff");
4970                 return REQ_NONE;
4971         }
4973         switch (line->type) {
4974         case LINE_STAT_STAGED:
4975                 if (is_initial_commit()) {
4976                         const char *no_head_diff_argv[] = {
4977                                 "git", "diff", "--no-color", "--patch-with-stat",
4978                                         "--", "/dev/null", newpath, NULL
4979                         };
4981                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4982                                 return status_load_error(view, stage, newpath);
4983                 } else {
4984                         const char *index_show_argv[] = {
4985                                 "git", "diff-index", "--root", "--patch-with-stat",
4986                                         "-C", "-M", "--cached", "HEAD", "--",
4987                                         oldpath, newpath, NULL
4988                         };
4990                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4991                                 return status_load_error(view, stage, newpath);
4992                 }
4994                 if (status)
4995                         info = "Staged changes to %s";
4996                 else
4997                         info = "Staged changes";
4998                 break;
5000         case LINE_STAT_UNSTAGED:
5001         {
5002                 const char *files_show_argv[] = {
5003                         "git", "diff-files", "--root", "--patch-with-stat",
5004                                 "-C", "-M", "--", oldpath, newpath, NULL
5005                 };
5007                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5008                         return status_load_error(view, stage, newpath);
5009                 if (status)
5010                         info = "Unstaged changes to %s";
5011                 else
5012                         info = "Unstaged changes";
5013                 break;
5014         }
5015         case LINE_STAT_UNTRACKED:
5016                 if (!newpath) {
5017                         report("No file to show");
5018                         return REQ_NONE;
5019                 }
5021                 if (!suffixcmp(status->new.name, -1, "/")) {
5022                         report("Cannot display a directory");
5023                         return REQ_NONE;
5024                 }
5026                 if (!prepare_update_file(stage, newpath))
5027                         return status_load_error(view, stage, newpath);
5028                 info = "Untracked file %s";
5029                 break;
5031         case LINE_STAT_HEAD:
5032                 return REQ_NONE;
5034         default:
5035                 die("line type %d not handled in switch", line->type);
5036         }
5038         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5039         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5040         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5041                 if (status) {
5042                         stage_status = *status;
5043                 } else {
5044                         memset(&stage_status, 0, sizeof(stage_status));
5045                 }
5047                 stage_line_type = line->type;
5048                 stage_chunks = 0;
5049                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5050         }
5052         return REQ_NONE;
5055 static bool
5056 status_exists(struct status *status, enum line_type type)
5058         struct view *view = VIEW(REQ_VIEW_STATUS);
5059         unsigned long lineno;
5061         for (lineno = 0; lineno < view->lines; lineno++) {
5062                 struct line *line = &view->line[lineno];
5063                 struct status *pos = line->data;
5065                 if (line->type != type)
5066                         continue;
5067                 if (!pos && (!status || !status->status) && line[1].data) {
5068                         select_view_line(view, lineno);
5069                         return TRUE;
5070                 }
5071                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5072                         select_view_line(view, lineno);
5073                         return TRUE;
5074                 }
5075         }
5077         return FALSE;
5081 static bool
5082 status_update_prepare(struct io *io, enum line_type type)
5084         const char *staged_argv[] = {
5085                 "git", "update-index", "-z", "--index-info", NULL
5086         };
5087         const char *others_argv[] = {
5088                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5089         };
5091         switch (type) {
5092         case LINE_STAT_STAGED:
5093                 return run_io(io, staged_argv, opt_cdup, IO_WR);
5095         case LINE_STAT_UNSTAGED:
5096                 return run_io(io, others_argv, opt_cdup, IO_WR);
5098         case LINE_STAT_UNTRACKED:
5099                 return run_io(io, others_argv, NULL, IO_WR);
5101         default:
5102                 die("line type %d not handled in switch", type);
5103                 return FALSE;
5104         }
5107 static bool
5108 status_update_write(struct io *io, struct status *status, enum line_type type)
5110         char buf[SIZEOF_STR];
5111         size_t bufsize = 0;
5113         switch (type) {
5114         case LINE_STAT_STAGED:
5115                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5116                                         status->old.mode,
5117                                         status->old.rev,
5118                                         status->old.name, 0))
5119                         return FALSE;
5120                 break;
5122         case LINE_STAT_UNSTAGED:
5123         case LINE_STAT_UNTRACKED:
5124                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5125                         return FALSE;
5126                 break;
5128         default:
5129                 die("line type %d not handled in switch", type);
5130         }
5132         return io_write(io, buf, bufsize);
5135 static bool
5136 status_update_file(struct status *status, enum line_type type)
5138         struct io io = {};
5139         bool result;
5141         if (!status_update_prepare(&io, type))
5142                 return FALSE;
5144         result = status_update_write(&io, status, type);
5145         return done_io(&io) && result;
5148 static bool
5149 status_update_files(struct view *view, struct line *line)
5151         char buf[sizeof(view->ref)];
5152         struct io io = {};
5153         bool result = TRUE;
5154         struct line *pos = view->line + view->lines;
5155         int files = 0;
5156         int file, done;
5157         int cursor_y, cursor_x;
5159         if (!status_update_prepare(&io, line->type))
5160                 return FALSE;
5162         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5163                 files++;
5165         string_copy(buf, view->ref);
5166         getsyx(cursor_y, cursor_x);
5167         for (file = 0, done = 5; result && file < files; line++, file++) {
5168                 int almost_done = file * 100 / files;
5170                 if (almost_done > done) {
5171                         done = almost_done;
5172                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5173                                       file, files, done);
5174                         update_view_title(view);
5175                         setsyx(cursor_y, cursor_x);
5176                         doupdate();
5177                 }
5178                 result = status_update_write(&io, line->data, line->type);
5179         }
5180         string_copy(view->ref, buf);
5182         return done_io(&io) && result;
5185 static bool
5186 status_update(struct view *view)
5188         struct line *line = &view->line[view->lineno];
5190         assert(view->lines);
5192         if (!line->data) {
5193                 /* This should work even for the "On branch" line. */
5194                 if (line < view->line + view->lines && !line[1].data) {
5195                         report("Nothing to update");
5196                         return FALSE;
5197                 }
5199                 if (!status_update_files(view, line + 1)) {
5200                         report("Failed to update file status");
5201                         return FALSE;
5202                 }
5204         } else if (!status_update_file(line->data, line->type)) {
5205                 report("Failed to update file status");
5206                 return FALSE;
5207         }
5209         return TRUE;
5212 static bool
5213 status_revert(struct status *status, enum line_type type, bool has_none)
5215         if (!status || type != LINE_STAT_UNSTAGED) {
5216                 if (type == LINE_STAT_STAGED) {
5217                         report("Cannot revert changes to staged files");
5218                 } else if (type == LINE_STAT_UNTRACKED) {
5219                         report("Cannot revert changes to untracked files");
5220                 } else if (has_none) {
5221                         report("Nothing to revert");
5222                 } else {
5223                         report("Cannot revert changes to multiple files");
5224                 }
5225                 return FALSE;
5227         } else {
5228                 char mode[10] = "100644";
5229                 const char *reset_argv[] = {
5230                         "git", "update-index", "--cacheinfo", mode,
5231                                 status->old.rev, status->old.name, NULL
5232                 };
5233                 const char *checkout_argv[] = {
5234                         "git", "checkout", "--", status->old.name, NULL
5235                 };
5237                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5238                         return FALSE;
5239                 string_format(mode, "%o", status->old.mode);
5240                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5241                         run_io_fg(checkout_argv, opt_cdup);
5242         }
5245 static enum request
5246 status_request(struct view *view, enum request request, struct line *line)
5248         struct status *status = line->data;
5250         switch (request) {
5251         case REQ_STATUS_UPDATE:
5252                 if (!status_update(view))
5253                         return REQ_NONE;
5254                 break;
5256         case REQ_STATUS_REVERT:
5257                 if (!status_revert(status, line->type, status_has_none(view, line)))
5258                         return REQ_NONE;
5259                 break;
5261         case REQ_STATUS_MERGE:
5262                 if (!status || status->status != 'U') {
5263                         report("Merging only possible for files with unmerged status ('U').");
5264                         return REQ_NONE;
5265                 }
5266                 open_mergetool(status->new.name);
5267                 break;
5269         case REQ_EDIT:
5270                 if (!status)
5271                         return request;
5272                 if (status->status == 'D') {
5273                         report("File has been deleted.");
5274                         return REQ_NONE;
5275                 }
5277                 open_editor(status->status != '?', status->new.name);
5278                 break;
5280         case REQ_VIEW_BLAME:
5281                 if (status) {
5282                         string_copy(opt_file, status->new.name);
5283                         opt_ref[0] = 0;
5284                 }
5285                 return request;
5287         case REQ_ENTER:
5288                 /* After returning the status view has been split to
5289                  * show the stage view. No further reloading is
5290                  * necessary. */
5291                 return status_enter(view, line);
5293         case REQ_REFRESH:
5294                 /* Simply reload the view. */
5295                 break;
5297         default:
5298                 return request;
5299         }
5301         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5303         return REQ_NONE;
5306 static void
5307 status_select(struct view *view, struct line *line)
5309         struct status *status = line->data;
5310         char file[SIZEOF_STR] = "all files";
5311         const char *text;
5312         const char *key;
5314         if (status && !string_format(file, "'%s'", status->new.name))
5315                 return;
5317         if (!status && line[1].type == LINE_STAT_NONE)
5318                 line++;
5320         switch (line->type) {
5321         case LINE_STAT_STAGED:
5322                 text = "Press %s to unstage %s for commit";
5323                 break;
5325         case LINE_STAT_UNSTAGED:
5326                 text = "Press %s to stage %s for commit";
5327                 break;
5329         case LINE_STAT_UNTRACKED:
5330                 text = "Press %s to stage %s for addition";
5331                 break;
5333         case LINE_STAT_HEAD:
5334         case LINE_STAT_NONE:
5335                 text = "Nothing to update";
5336                 break;
5338         default:
5339                 die("line type %d not handled in switch", line->type);
5340         }
5342         if (status && status->status == 'U') {
5343                 text = "Press %s to resolve conflict in %s";
5344                 key = get_key(REQ_STATUS_MERGE);
5346         } else {
5347                 key = get_key(REQ_STATUS_UPDATE);
5348         }
5350         string_format(view->ref, text, key, file);
5353 static bool
5354 status_grep(struct view *view, struct line *line)
5356         struct status *status = line->data;
5358         if (status) {
5359                 const char buf[2] = { status->status, 0 };
5360                 const char *text[] = { status->new.name, buf, NULL };
5362                 return grep_text(view, text);
5363         }
5365         return FALSE;
5368 static struct view_ops status_ops = {
5369         "file",
5370         NULL,
5371         status_open,
5372         NULL,
5373         status_draw,
5374         status_request,
5375         status_grep,
5376         status_select,
5377 };
5380 static bool
5381 stage_diff_write(struct io *io, struct line *line, struct line *end)
5383         while (line < end) {
5384                 if (!io_write(io, line->data, strlen(line->data)) ||
5385                     !io_write(io, "\n", 1))
5386                         return FALSE;
5387                 line++;
5388                 if (line->type == LINE_DIFF_CHUNK ||
5389                     line->type == LINE_DIFF_HEADER)
5390                         break;
5391         }
5393         return TRUE;
5396 static struct line *
5397 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5399         for (; view->line < line; line--)
5400                 if (line->type == type)
5401                         return line;
5403         return NULL;
5406 static bool
5407 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5409         const char *apply_argv[SIZEOF_ARG] = {
5410                 "git", "apply", "--whitespace=nowarn", NULL
5411         };
5412         struct line *diff_hdr;
5413         struct io io = {};
5414         int argc = 3;
5416         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5417         if (!diff_hdr)
5418                 return FALSE;
5420         if (!revert)
5421                 apply_argv[argc++] = "--cached";
5422         if (revert || stage_line_type == LINE_STAT_STAGED)
5423                 apply_argv[argc++] = "-R";
5424         apply_argv[argc++] = "-";
5425         apply_argv[argc++] = NULL;
5426         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5427                 return FALSE;
5429         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5430             !stage_diff_write(&io, chunk, view->line + view->lines))
5431                 chunk = NULL;
5433         done_io(&io);
5434         run_io_bg(update_index_argv);
5436         return chunk ? TRUE : FALSE;
5439 static bool
5440 stage_update(struct view *view, struct line *line)
5442         struct line *chunk = NULL;
5444         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5445                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5447         if (chunk) {
5448                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5449                         report("Failed to apply chunk");
5450                         return FALSE;
5451                 }
5453         } else if (!stage_status.status) {
5454                 view = VIEW(REQ_VIEW_STATUS);
5456                 for (line = view->line; line < view->line + view->lines; line++)
5457                         if (line->type == stage_line_type)
5458                                 break;
5460                 if (!status_update_files(view, line + 1)) {
5461                         report("Failed to update files");
5462                         return FALSE;
5463                 }
5465         } else if (!status_update_file(&stage_status, stage_line_type)) {
5466                 report("Failed to update file");
5467                 return FALSE;
5468         }
5470         return TRUE;
5473 static bool
5474 stage_revert(struct view *view, struct line *line)
5476         struct line *chunk = NULL;
5478         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5479                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5481         if (chunk) {
5482                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5483                         return FALSE;
5485                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5486                         report("Failed to revert chunk");
5487                         return FALSE;
5488                 }
5489                 return TRUE;
5491         } else {
5492                 return status_revert(stage_status.status ? &stage_status : NULL,
5493                                      stage_line_type, FALSE);
5494         }
5498 static void
5499 stage_next(struct view *view, struct line *line)
5501         int i;
5503         if (!stage_chunks) {
5504                 static size_t alloc = 0;
5505                 int *tmp;
5507                 for (line = view->line; line < view->line + view->lines; line++) {
5508                         if (line->type != LINE_DIFF_CHUNK)
5509                                 continue;
5511                         tmp = realloc_items(stage_chunk, &alloc,
5512                                             stage_chunks, sizeof(*tmp));
5513                         if (!tmp) {
5514                                 report("Allocation failure");
5515                                 return;
5516                         }
5518                         stage_chunk = tmp;
5519                         stage_chunk[stage_chunks++] = line - view->line;
5520                 }
5521         }
5523         for (i = 0; i < stage_chunks; i++) {
5524                 if (stage_chunk[i] > view->lineno) {
5525                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5526                         report("Chunk %d of %d", i + 1, stage_chunks);
5527                         return;
5528                 }
5529         }
5531         report("No next chunk found");
5534 static enum request
5535 stage_request(struct view *view, enum request request, struct line *line)
5537         switch (request) {
5538         case REQ_STATUS_UPDATE:
5539                 if (!stage_update(view, line))
5540                         return REQ_NONE;
5541                 break;
5543         case REQ_STATUS_REVERT:
5544                 if (!stage_revert(view, line))
5545                         return REQ_NONE;
5546                 break;
5548         case REQ_STAGE_NEXT:
5549                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5550                         report("File is untracked; press %s to add",
5551                                get_key(REQ_STATUS_UPDATE));
5552                         return REQ_NONE;
5553                 }
5554                 stage_next(view, line);
5555                 return REQ_NONE;
5557         case REQ_EDIT:
5558                 if (!stage_status.new.name[0])
5559                         return request;
5560                 if (stage_status.status == 'D') {
5561                         report("File has been deleted.");
5562                         return REQ_NONE;
5563                 }
5565                 open_editor(stage_status.status != '?', stage_status.new.name);
5566                 break;
5568         case REQ_REFRESH:
5569                 /* Reload everything ... */
5570                 break;
5572         case REQ_VIEW_BLAME:
5573                 if (stage_status.new.name[0]) {
5574                         string_copy(opt_file, stage_status.new.name);
5575                         opt_ref[0] = 0;
5576                 }
5577                 return request;
5579         case REQ_ENTER:
5580                 return pager_request(view, request, line);
5582         default:
5583                 return request;
5584         }
5586         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5587         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5589         /* Check whether the staged entry still exists, and close the
5590          * stage view if it doesn't. */
5591         if (!status_exists(&stage_status, stage_line_type)) {
5592                 status_restore(VIEW(REQ_VIEW_STATUS));
5593                 return REQ_VIEW_CLOSE;
5594         }
5596         if (stage_line_type == LINE_STAT_UNTRACKED) {
5597                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5598                         report("Cannot display a directory");
5599                         return REQ_NONE;
5600                 }
5602                 if (!prepare_update_file(view, stage_status.new.name)) {
5603                         report("Failed to open file: %s", strerror(errno));
5604                         return REQ_NONE;
5605                 }
5606         }
5607         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5609         return REQ_NONE;
5612 static struct view_ops stage_ops = {
5613         "line",
5614         NULL,
5615         NULL,
5616         pager_read,
5617         pager_draw,
5618         stage_request,
5619         pager_grep,
5620         pager_select,
5621 };
5624 /*
5625  * Revision graph
5626  */
5628 struct commit {
5629         char id[SIZEOF_REV];            /* SHA1 ID. */
5630         char title[128];                /* First line of the commit message. */
5631         const char *author;             /* Author of the commit. */
5632         time_t time;                    /* Date from the author ident. */
5633         struct ref **refs;              /* Repository references. */
5634         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5635         size_t graph_size;              /* The width of the graph array. */
5636         bool has_parents;               /* Rewritten --parents seen. */
5637 };
5639 /* Size of rev graph with no  "padding" columns */
5640 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5642 struct rev_graph {
5643         struct rev_graph *prev, *next, *parents;
5644         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5645         size_t size;
5646         struct commit *commit;
5647         size_t pos;
5648         unsigned int boundary:1;
5649 };
5651 /* Parents of the commit being visualized. */
5652 static struct rev_graph graph_parents[4];
5654 /* The current stack of revisions on the graph. */
5655 static struct rev_graph graph_stacks[4] = {
5656         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5657         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5658         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5659         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5660 };
5662 static inline bool
5663 graph_parent_is_merge(struct rev_graph *graph)
5665         return graph->parents->size > 1;
5668 static inline void
5669 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5671         struct commit *commit = graph->commit;
5673         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5674                 commit->graph[commit->graph_size++] = symbol;
5677 static void
5678 clear_rev_graph(struct rev_graph *graph)
5680         graph->boundary = 0;
5681         graph->size = graph->pos = 0;
5682         graph->commit = NULL;
5683         memset(graph->parents, 0, sizeof(*graph->parents));
5686 static void
5687 done_rev_graph(struct rev_graph *graph)
5689         if (graph_parent_is_merge(graph) &&
5690             graph->pos < graph->size - 1 &&
5691             graph->next->size == graph->size + graph->parents->size - 1) {
5692                 size_t i = graph->pos + graph->parents->size - 1;
5694                 graph->commit->graph_size = i * 2;
5695                 while (i < graph->next->size - 1) {
5696                         append_to_rev_graph(graph, ' ');
5697                         append_to_rev_graph(graph, '\\');
5698                         i++;
5699                 }
5700         }
5702         clear_rev_graph(graph);
5705 static void
5706 push_rev_graph(struct rev_graph *graph, const char *parent)
5708         int i;
5710         /* "Collapse" duplicate parents lines.
5711          *
5712          * FIXME: This needs to also update update the drawn graph but
5713          * for now it just serves as a method for pruning graph lines. */
5714         for (i = 0; i < graph->size; i++)
5715                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5716                         return;
5718         if (graph->size < SIZEOF_REVITEMS) {
5719                 string_copy_rev(graph->rev[graph->size++], parent);
5720         }
5723 static chtype
5724 get_rev_graph_symbol(struct rev_graph *graph)
5726         chtype symbol;
5728         if (graph->boundary)
5729                 symbol = REVGRAPH_BOUND;
5730         else if (graph->parents->size == 0)
5731                 symbol = REVGRAPH_INIT;
5732         else if (graph_parent_is_merge(graph))
5733                 symbol = REVGRAPH_MERGE;
5734         else if (graph->pos >= graph->size)
5735                 symbol = REVGRAPH_BRANCH;
5736         else
5737                 symbol = REVGRAPH_COMMIT;
5739         return symbol;
5742 static void
5743 draw_rev_graph(struct rev_graph *graph)
5745         struct rev_filler {
5746                 chtype separator, line;
5747         };
5748         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5749         static struct rev_filler fillers[] = {
5750                 { ' ',  '|' },
5751                 { '`',  '.' },
5752                 { '\'', ' ' },
5753                 { '/',  ' ' },
5754         };
5755         chtype symbol = get_rev_graph_symbol(graph);
5756         struct rev_filler *filler;
5757         size_t i;
5759         if (opt_line_graphics)
5760                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5762         filler = &fillers[DEFAULT];
5764         for (i = 0; i < graph->pos; i++) {
5765                 append_to_rev_graph(graph, filler->line);
5766                 if (graph_parent_is_merge(graph->prev) &&
5767                     graph->prev->pos == i)
5768                         filler = &fillers[RSHARP];
5770                 append_to_rev_graph(graph, filler->separator);
5771         }
5773         /* Place the symbol for this revision. */
5774         append_to_rev_graph(graph, symbol);
5776         if (graph->prev->size > graph->size)
5777                 filler = &fillers[RDIAG];
5778         else
5779                 filler = &fillers[DEFAULT];
5781         i++;
5783         for (; i < graph->size; i++) {
5784                 append_to_rev_graph(graph, filler->separator);
5785                 append_to_rev_graph(graph, filler->line);
5786                 if (graph_parent_is_merge(graph->prev) &&
5787                     i < graph->prev->pos + graph->parents->size)
5788                         filler = &fillers[RSHARP];
5789                 if (graph->prev->size > graph->size)
5790                         filler = &fillers[LDIAG];
5791         }
5793         if (graph->prev->size > graph->size) {
5794                 append_to_rev_graph(graph, filler->separator);
5795                 if (filler->line != ' ')
5796                         append_to_rev_graph(graph, filler->line);
5797         }
5800 /* Prepare the next rev graph */
5801 static void
5802 prepare_rev_graph(struct rev_graph *graph)
5804         size_t i;
5806         /* First, traverse all lines of revisions up to the active one. */
5807         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5808                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5809                         break;
5811                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5812         }
5814         /* Interleave the new revision parent(s). */
5815         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5816                 push_rev_graph(graph->next, graph->parents->rev[i]);
5818         /* Lastly, put any remaining revisions. */
5819         for (i = graph->pos + 1; i < graph->size; i++)
5820                 push_rev_graph(graph->next, graph->rev[i]);
5823 static void
5824 update_rev_graph(struct view *view, struct rev_graph *graph)
5826         /* If this is the finalizing update ... */
5827         if (graph->commit)
5828                 prepare_rev_graph(graph);
5830         /* Graph visualization needs a one rev look-ahead,
5831          * so the first update doesn't visualize anything. */
5832         if (!graph->prev->commit)
5833                 return;
5835         if (view->lines > 2)
5836                 view->line[view->lines - 3].dirty = 1;
5837         if (view->lines > 1)
5838                 view->line[view->lines - 2].dirty = 1;
5839         draw_rev_graph(graph->prev);
5840         done_rev_graph(graph->prev->prev);
5844 /*
5845  * Main view backend
5846  */
5848 static const char *main_argv[SIZEOF_ARG] = {
5849         "git", "log", "--no-color", "--pretty=raw", "--parents",
5850                       "--topo-order", "%(head)", NULL
5851 };
5853 static bool
5854 main_draw(struct view *view, struct line *line, unsigned int lineno)
5856         struct commit *commit = line->data;
5858         if (!commit->author)
5859                 return FALSE;
5861         if (opt_date && draw_date(view, &commit->time))
5862                 return TRUE;
5864         if (opt_author && draw_author(view, commit->author))
5865                 return TRUE;
5867         if (opt_rev_graph && commit->graph_size &&
5868             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5869                 return TRUE;
5871         if (opt_show_refs && commit->refs) {
5872                 size_t i = 0;
5874                 do {
5875                         enum line_type type;
5877                         if (commit->refs[i]->head)
5878                                 type = LINE_MAIN_HEAD;
5879                         else if (commit->refs[i]->ltag)
5880                                 type = LINE_MAIN_LOCAL_TAG;
5881                         else if (commit->refs[i]->tag)
5882                                 type = LINE_MAIN_TAG;
5883                         else if (commit->refs[i]->tracked)
5884                                 type = LINE_MAIN_TRACKED;
5885                         else if (commit->refs[i]->remote)
5886                                 type = LINE_MAIN_REMOTE;
5887                         else
5888                                 type = LINE_MAIN_REF;
5890                         if (draw_text(view, type, "[", TRUE) ||
5891                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5892                             draw_text(view, type, "]", TRUE))
5893                                 return TRUE;
5895                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5896                                 return TRUE;
5897                 } while (commit->refs[i++]->next);
5898         }
5900         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5901         return TRUE;
5904 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5905 static bool
5906 main_read(struct view *view, char *line)
5908         static struct rev_graph *graph = graph_stacks;
5909         enum line_type type;
5910         struct commit *commit;
5912         if (!line) {
5913                 int i;
5915                 if (!view->lines && !view->parent)
5916                         die("No revisions match the given arguments.");
5917                 if (view->lines > 0) {
5918                         commit = view->line[view->lines - 1].data;
5919                         view->line[view->lines - 1].dirty = 1;
5920                         if (!commit->author) {
5921                                 view->lines--;
5922                                 free(commit);
5923                                 graph->commit = NULL;
5924                         }
5925                 }
5926                 update_rev_graph(view, graph);
5928                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5929                         clear_rev_graph(&graph_stacks[i]);
5930                 return TRUE;
5931         }
5933         type = get_line_type(line);
5934         if (type == LINE_COMMIT) {
5935                 commit = calloc(1, sizeof(struct commit));
5936                 if (!commit)
5937                         return FALSE;
5939                 line += STRING_SIZE("commit ");
5940                 if (*line == '-') {
5941                         graph->boundary = 1;
5942                         line++;
5943                 }
5945                 string_copy_rev(commit->id, line);
5946                 commit->refs = get_refs(commit->id);
5947                 graph->commit = commit;
5948                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5950                 while ((line = strchr(line, ' '))) {
5951                         line++;
5952                         push_rev_graph(graph->parents, line);
5953                         commit->has_parents = TRUE;
5954                 }
5955                 return TRUE;
5956         }
5958         if (!view->lines)
5959                 return TRUE;
5960         commit = view->line[view->lines - 1].data;
5962         switch (type) {
5963         case LINE_PARENT:
5964                 if (commit->has_parents)
5965                         break;
5966                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5967                 break;
5969         case LINE_AUTHOR:
5970                 parse_author_line(line + STRING_SIZE("author "),
5971                                   &commit->author, &commit->time);
5972                 update_rev_graph(view, graph);
5973                 graph = graph->next;
5974                 break;
5976         default:
5977                 /* Fill in the commit title if it has not already been set. */
5978                 if (commit->title[0])
5979                         break;
5981                 /* Require titles to start with a non-space character at the
5982                  * offset used by git log. */
5983                 if (strncmp(line, "    ", 4))
5984                         break;
5985                 line += 4;
5986                 /* Well, if the title starts with a whitespace character,
5987                  * try to be forgiving.  Otherwise we end up with no title. */
5988                 while (isspace(*line))
5989                         line++;
5990                 if (*line == '\0')
5991                         break;
5992                 /* FIXME: More graceful handling of titles; append "..." to
5993                  * shortened titles, etc. */
5995                 string_expand(commit->title, sizeof(commit->title), line, 1);
5996                 view->line[view->lines - 1].dirty = 1;
5997         }
5999         return TRUE;
6002 static enum request
6003 main_request(struct view *view, enum request request, struct line *line)
6005         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6007         switch (request) {
6008         case REQ_ENTER:
6009                 open_view(view, REQ_VIEW_DIFF, flags);
6010                 break;
6011         case REQ_REFRESH:
6012                 load_refs();
6013                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6014                 break;
6015         default:
6016                 return request;
6017         }
6019         return REQ_NONE;
6022 static bool
6023 grep_refs(struct ref **refs, regex_t *regex)
6025         regmatch_t pmatch;
6026         size_t i = 0;
6028         if (!opt_show_refs || !refs)
6029                 return FALSE;
6030         do {
6031                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6032                         return TRUE;
6033         } while (refs[i++]->next);
6035         return FALSE;
6038 static bool
6039 main_grep(struct view *view, struct line *line)
6041         struct commit *commit = line->data;
6042         const char *text[] = {
6043                 commit->title,
6044                 opt_author ? commit->author : "",
6045                 opt_date ? mkdate(&commit->time) : "",
6046                 NULL
6047         };
6049         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6052 static void
6053 main_select(struct view *view, struct line *line)
6055         struct commit *commit = line->data;
6057         string_copy_rev(view->ref, commit->id);
6058         string_copy_rev(ref_commit, view->ref);
6061 static struct view_ops main_ops = {
6062         "commit",
6063         main_argv,
6064         NULL,
6065         main_read,
6066         main_draw,
6067         main_request,
6068         main_grep,
6069         main_select,
6070 };
6073 /*
6074  * Unicode / UTF-8 handling
6075  *
6076  * NOTE: Much of the following code for dealing with Unicode is derived from
6077  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6078  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6079  */
6081 static inline int
6082 unicode_width(unsigned long c)
6084         if (c >= 0x1100 &&
6085            (c <= 0x115f                         /* Hangul Jamo */
6086             || c == 0x2329
6087             || c == 0x232a
6088             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6089                                                 /* CJK ... Yi */
6090             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6091             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6092             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6093             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6094             || (c >= 0xffe0  && c <= 0xffe6)
6095             || (c >= 0x20000 && c <= 0x2fffd)
6096             || (c >= 0x30000 && c <= 0x3fffd)))
6097                 return 2;
6099         if (c == '\t')
6100                 return opt_tab_size;
6102         return 1;
6105 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6106  * Illegal bytes are set one. */
6107 static const unsigned char utf8_bytes[256] = {
6108         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,
6109         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,
6110         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,
6111         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6112         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6113         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6114         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,
6115         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,
6116 };
6118 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6119 static inline unsigned long
6120 utf8_to_unicode(const char *string, size_t length)
6122         unsigned long unicode;
6124         switch (length) {
6125         case 1:
6126                 unicode  =   string[0];
6127                 break;
6128         case 2:
6129                 unicode  =  (string[0] & 0x1f) << 6;
6130                 unicode +=  (string[1] & 0x3f);
6131                 break;
6132         case 3:
6133                 unicode  =  (string[0] & 0x0f) << 12;
6134                 unicode += ((string[1] & 0x3f) << 6);
6135                 unicode +=  (string[2] & 0x3f);
6136                 break;
6137         case 4:
6138                 unicode  =  (string[0] & 0x0f) << 18;
6139                 unicode += ((string[1] & 0x3f) << 12);
6140                 unicode += ((string[2] & 0x3f) << 6);
6141                 unicode +=  (string[3] & 0x3f);
6142                 break;
6143         case 5:
6144                 unicode  =  (string[0] & 0x0f) << 24;
6145                 unicode += ((string[1] & 0x3f) << 18);
6146                 unicode += ((string[2] & 0x3f) << 12);
6147                 unicode += ((string[3] & 0x3f) << 6);
6148                 unicode +=  (string[4] & 0x3f);
6149                 break;
6150         case 6:
6151                 unicode  =  (string[0] & 0x01) << 30;
6152                 unicode += ((string[1] & 0x3f) << 24);
6153                 unicode += ((string[2] & 0x3f) << 18);
6154                 unicode += ((string[3] & 0x3f) << 12);
6155                 unicode += ((string[4] & 0x3f) << 6);
6156                 unicode +=  (string[5] & 0x3f);
6157                 break;
6158         default:
6159                 die("Invalid Unicode length");
6160         }
6162         /* Invalid characters could return the special 0xfffd value but NUL
6163          * should be just as good. */
6164         return unicode > 0xffff ? 0 : unicode;
6167 /* Calculates how much of string can be shown within the given maximum width
6168  * and sets trimmed parameter to non-zero value if all of string could not be
6169  * shown. If the reserve flag is TRUE, it will reserve at least one
6170  * trailing character, which can be useful when drawing a delimiter.
6171  *
6172  * Returns the number of bytes to output from string to satisfy max_width. */
6173 static size_t
6174 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6176         const char *string = *start;
6177         const char *end = strchr(string, '\0');
6178         unsigned char last_bytes = 0;
6179         size_t last_ucwidth = 0;
6181         *width = 0;
6182         *trimmed = 0;
6184         while (string < end) {
6185                 int c = *(unsigned char *) string;
6186                 unsigned char bytes = utf8_bytes[c];
6187                 size_t ucwidth;
6188                 unsigned long unicode;
6190                 if (string + bytes > end)
6191                         break;
6193                 /* Change representation to figure out whether
6194                  * it is a single- or double-width character. */
6196                 unicode = utf8_to_unicode(string, bytes);
6197                 /* FIXME: Graceful handling of invalid Unicode character. */
6198                 if (!unicode)
6199                         break;
6201                 ucwidth = unicode_width(unicode);
6202                 if (skip > 0) {
6203                         skip -= ucwidth <= skip ? ucwidth : skip;
6204                         *start += bytes;
6205                 }
6206                 *width  += ucwidth;
6207                 if (*width > max_width) {
6208                         *trimmed = 1;
6209                         *width -= ucwidth;
6210                         if (reserve && *width == max_width) {
6211                                 string -= last_bytes;
6212                                 *width -= last_ucwidth;
6213                         }
6214                         break;
6215                 }
6217                 string  += bytes;
6218                 last_bytes = ucwidth ? bytes : 0;
6219                 last_ucwidth = ucwidth;
6220         }
6222         return string - *start;
6226 /*
6227  * Status management
6228  */
6230 /* Whether or not the curses interface has been initialized. */
6231 static bool cursed = FALSE;
6233 /* Terminal hacks and workarounds. */
6234 static bool use_scroll_redrawwin;
6235 static bool use_scroll_status_wclear;
6237 /* The status window is used for polling keystrokes. */
6238 static WINDOW *status_win;
6240 /* Reading from the prompt? */
6241 static bool input_mode = FALSE;
6243 static bool status_empty = FALSE;
6245 /* Update status and title window. */
6246 static void
6247 report(const char *msg, ...)
6249         struct view *view = display[current_view];
6251         if (input_mode)
6252                 return;
6254         if (!view) {
6255                 char buf[SIZEOF_STR];
6256                 va_list args;
6258                 va_start(args, msg);
6259                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6260                         buf[sizeof(buf) - 1] = 0;
6261                         buf[sizeof(buf) - 2] = '.';
6262                         buf[sizeof(buf) - 3] = '.';
6263                         buf[sizeof(buf) - 4] = '.';
6264                 }
6265                 va_end(args);
6266                 die("%s", buf);
6267         }
6269         if (!status_empty || *msg) {
6270                 va_list args;
6272                 va_start(args, msg);
6274                 wmove(status_win, 0, 0);
6275                 if (view->has_scrolled && use_scroll_status_wclear)
6276                         wclear(status_win);
6277                 if (*msg) {
6278                         vwprintw(status_win, msg, args);
6279                         status_empty = FALSE;
6280                 } else {
6281                         status_empty = TRUE;
6282                 }
6283                 wclrtoeol(status_win);
6284                 wnoutrefresh(status_win);
6286                 va_end(args);
6287         }
6289         update_view_title(view);
6292 /* Controls when nodelay should be in effect when polling user input. */
6293 static void
6294 set_nonblocking_input(bool loading)
6296         static unsigned int loading_views;
6298         if ((loading == FALSE && loading_views-- == 1) ||
6299             (loading == TRUE  && loading_views++ == 0))
6300                 nodelay(status_win, loading);
6303 static void
6304 init_display(void)
6306         const char *term;
6307         int x, y;
6309         /* Initialize the curses library */
6310         if (isatty(STDIN_FILENO)) {
6311                 cursed = !!initscr();
6312                 opt_tty = stdin;
6313         } else {
6314                 /* Leave stdin and stdout alone when acting as a pager. */
6315                 opt_tty = fopen("/dev/tty", "r+");
6316                 if (!opt_tty)
6317                         die("Failed to open /dev/tty");
6318                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6319         }
6321         if (!cursed)
6322                 die("Failed to initialize curses");
6324         nonl();         /* Disable conversion and detect newlines from input. */
6325         cbreak();       /* Take input chars one at a time, no wait for \n */
6326         noecho();       /* Don't echo input */
6327         leaveok(stdscr, FALSE);
6329         if (has_colors())
6330                 init_colors();
6332         getmaxyx(stdscr, y, x);
6333         status_win = newwin(1, 0, y - 1, 0);
6334         if (!status_win)
6335                 die("Failed to create status window");
6337         /* Enable keyboard mapping */
6338         keypad(status_win, TRUE);
6339         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6341         TABSIZE = opt_tab_size;
6342         if (opt_line_graphics) {
6343                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6344         }
6346         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6347         if (term && !strcmp(term, "gnome-terminal")) {
6348                 /* In the gnome-terminal-emulator, the message from
6349                  * scrolling up one line when impossible followed by
6350                  * scrolling down one line causes corruption of the
6351                  * status line. This is fixed by calling wclear. */
6352                 use_scroll_status_wclear = TRUE;
6353                 use_scroll_redrawwin = FALSE;
6355         } else if (term && !strcmp(term, "xrvt-xpm")) {
6356                 /* No problems with full optimizations in xrvt-(unicode)
6357                  * and aterm. */
6358                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6360         } else {
6361                 /* When scrolling in (u)xterm the last line in the
6362                  * scrolling direction will update slowly. */
6363                 use_scroll_redrawwin = TRUE;
6364                 use_scroll_status_wclear = FALSE;
6365         }
6368 static int
6369 get_input(int prompt_position)
6371         struct view *view;
6372         int i, key, cursor_y, cursor_x;
6374         if (prompt_position)
6375                 input_mode = TRUE;
6377         while (TRUE) {
6378                 foreach_view (view, i) {
6379                         update_view(view);
6380                         if (view_is_displayed(view) && view->has_scrolled &&
6381                             use_scroll_redrawwin)
6382                                 redrawwin(view->win);
6383                         view->has_scrolled = FALSE;
6384                 }
6386                 /* Update the cursor position. */
6387                 if (prompt_position) {
6388                         getbegyx(status_win, cursor_y, cursor_x);
6389                         cursor_x = prompt_position;
6390                 } else {
6391                         view = display[current_view];
6392                         getbegyx(view->win, cursor_y, cursor_x);
6393                         cursor_x = view->width - 1;
6394                         cursor_y += view->lineno - view->offset;
6395                 }
6396                 setsyx(cursor_y, cursor_x);
6398                 /* Refresh, accept single keystroke of input */
6399                 doupdate();
6400                 key = wgetch(status_win);
6402                 /* wgetch() with nodelay() enabled returns ERR when
6403                  * there's no input. */
6404                 if (key == ERR) {
6406                 } else if (key == KEY_RESIZE) {
6407                         int height, width;
6409                         getmaxyx(stdscr, height, width);
6411                         wresize(status_win, 1, width);
6412                         mvwin(status_win, height - 1, 0);
6413                         wnoutrefresh(status_win);
6414                         resize_display();
6415                         redraw_display(TRUE);
6417                 } else {
6418                         input_mode = FALSE;
6419                         return key;
6420                 }
6421         }
6424 static char *
6425 prompt_input(const char *prompt, input_handler handler, void *data)
6427         enum input_status status = INPUT_OK;
6428         static char buf[SIZEOF_STR];
6429         size_t pos = 0;
6431         buf[pos] = 0;
6433         while (status == INPUT_OK || status == INPUT_SKIP) {
6434                 int key;
6436                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6437                 wclrtoeol(status_win);
6439                 key = get_input(pos + 1);
6440                 switch (key) {
6441                 case KEY_RETURN:
6442                 case KEY_ENTER:
6443                 case '\n':
6444                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6445                         break;
6447                 case KEY_BACKSPACE:
6448                         if (pos > 0)
6449                                 buf[--pos] = 0;
6450                         else
6451                                 status = INPUT_CANCEL;
6452                         break;
6454                 case KEY_ESC:
6455                         status = INPUT_CANCEL;
6456                         break;
6458                 default:
6459                         if (pos >= sizeof(buf)) {
6460                                 report("Input string too long");
6461                                 return NULL;
6462                         }
6464                         status = handler(data, buf, key);
6465                         if (status == INPUT_OK)
6466                                 buf[pos++] = (char) key;
6467                 }
6468         }
6470         /* Clear the status window */
6471         status_empty = FALSE;
6472         report("");
6474         if (status == INPUT_CANCEL)
6475                 return NULL;
6477         buf[pos++] = 0;
6479         return buf;
6482 static enum input_status
6483 prompt_yesno_handler(void *data, char *buf, int c)
6485         if (c == 'y' || c == 'Y')
6486                 return INPUT_STOP;
6487         if (c == 'n' || c == 'N')
6488                 return INPUT_CANCEL;
6489         return INPUT_SKIP;
6492 static bool
6493 prompt_yesno(const char *prompt)
6495         char prompt2[SIZEOF_STR];
6497         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6498                 return FALSE;
6500         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6503 static enum input_status
6504 read_prompt_handler(void *data, char *buf, int c)
6506         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6509 static char *
6510 read_prompt(const char *prompt)
6512         return prompt_input(prompt, read_prompt_handler, NULL);
6515 /*
6516  * Repository properties
6517  */
6519 static struct ref *refs = NULL;
6520 static size_t refs_alloc = 0;
6521 static size_t refs_size = 0;
6523 /* Id <-> ref store */
6524 static struct ref ***id_refs = NULL;
6525 static size_t id_refs_alloc = 0;
6526 static size_t id_refs_size = 0;
6528 static int
6529 compare_refs(const void *ref1_, const void *ref2_)
6531         const struct ref *ref1 = *(const struct ref **)ref1_;
6532         const struct ref *ref2 = *(const struct ref **)ref2_;
6534         if (ref1->tag != ref2->tag)
6535                 return ref2->tag - ref1->tag;
6536         if (ref1->ltag != ref2->ltag)
6537                 return ref2->ltag - ref2->ltag;
6538         if (ref1->head != ref2->head)
6539                 return ref2->head - ref1->head;
6540         if (ref1->tracked != ref2->tracked)
6541                 return ref2->tracked - ref1->tracked;
6542         if (ref1->remote != ref2->remote)
6543                 return ref2->remote - ref1->remote;
6544         return strcmp(ref1->name, ref2->name);
6547 static struct ref **
6548 get_refs(const char *id)
6550         struct ref ***tmp_id_refs;
6551         struct ref **ref_list = NULL;
6552         size_t ref_list_alloc = 0;
6553         size_t ref_list_size = 0;
6554         size_t i;
6556         for (i = 0; i < id_refs_size; i++)
6557                 if (!strcmp(id, id_refs[i][0]->id))
6558                         return id_refs[i];
6560         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6561                                     sizeof(*id_refs));
6562         if (!tmp_id_refs)
6563                 return NULL;
6565         id_refs = tmp_id_refs;
6567         for (i = 0; i < refs_size; i++) {
6568                 struct ref **tmp;
6570                 if (strcmp(id, refs[i].id))
6571                         continue;
6573                 tmp = realloc_items(ref_list, &ref_list_alloc,
6574                                     ref_list_size + 1, sizeof(*ref_list));
6575                 if (!tmp) {
6576                         if (ref_list)
6577                                 free(ref_list);
6578                         return NULL;
6579                 }
6581                 ref_list = tmp;
6582                 ref_list[ref_list_size] = &refs[i];
6583                 /* XXX: The properties of the commit chains ensures that we can
6584                  * safely modify the shared ref. The repo references will
6585                  * always be similar for the same id. */
6586                 ref_list[ref_list_size]->next = 1;
6588                 ref_list_size++;
6589         }
6591         if (ref_list) {
6592                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6593                 ref_list[ref_list_size - 1]->next = 0;
6594                 id_refs[id_refs_size++] = ref_list;
6595         }
6597         return ref_list;
6600 static int
6601 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6603         struct ref *ref;
6604         bool tag = FALSE;
6605         bool ltag = FALSE;
6606         bool remote = FALSE;
6607         bool tracked = FALSE;
6608         bool check_replace = FALSE;
6609         bool head = FALSE;
6611         if (!prefixcmp(name, "refs/tags/")) {
6612                 if (!suffixcmp(name, namelen, "^{}")) {
6613                         namelen -= 3;
6614                         name[namelen] = 0;
6615                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6616                                 check_replace = TRUE;
6617                 } else {
6618                         ltag = TRUE;
6619                 }
6621                 tag = TRUE;
6622                 namelen -= STRING_SIZE("refs/tags/");
6623                 name    += STRING_SIZE("refs/tags/");
6625         } else if (!prefixcmp(name, "refs/remotes/")) {
6626                 remote = TRUE;
6627                 namelen -= STRING_SIZE("refs/remotes/");
6628                 name    += STRING_SIZE("refs/remotes/");
6629                 tracked  = !strcmp(opt_remote, name);
6631         } else if (!prefixcmp(name, "refs/heads/")) {
6632                 namelen -= STRING_SIZE("refs/heads/");
6633                 name    += STRING_SIZE("refs/heads/");
6634                 head     = !strncmp(opt_head, name, namelen);
6636         } else if (!strcmp(name, "HEAD")) {
6637                 string_ncopy(opt_head_rev, id, idlen);
6638                 return OK;
6639         }
6641         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6642                 /* it's an annotated tag, replace the previous SHA1 with the
6643                  * resolved commit id; relies on the fact git-ls-remote lists
6644                  * the commit id of an annotated tag right before the commit id
6645                  * it points to. */
6646                 refs[refs_size - 1].ltag = ltag;
6647                 string_copy_rev(refs[refs_size - 1].id, id);
6649                 return OK;
6650         }
6651         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6652         if (!refs)
6653                 return ERR;
6655         ref = &refs[refs_size++];
6656         ref->name = malloc(namelen + 1);
6657         if (!ref->name)
6658                 return ERR;
6660         strncpy(ref->name, name, namelen);
6661         ref->name[namelen] = 0;
6662         ref->head = head;
6663         ref->tag = tag;
6664         ref->ltag = ltag;
6665         ref->remote = remote;
6666         ref->tracked = tracked;
6667         string_copy_rev(ref->id, id);
6669         return OK;
6672 static int
6673 load_refs(void)
6675         static const char *ls_remote_argv[SIZEOF_ARG] = {
6676                 "git", "ls-remote", opt_git_dir, NULL
6677         };
6678         static bool init = FALSE;
6680         if (!init) {
6681                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6682                 init = TRUE;
6683         }
6685         if (!*opt_git_dir)
6686                 return OK;
6688         while (refs_size > 0)
6689                 free(refs[--refs_size].name);
6690         while (id_refs_size > 0)
6691                 free(id_refs[--id_refs_size]);
6693         return run_io_load(ls_remote_argv, "\t", read_ref);
6696 static void
6697 set_remote_branch(const char *name, const char *value, size_t valuelen)
6699         if (!strcmp(name, ".remote")) {
6700                 string_ncopy(opt_remote, value, valuelen);
6702         } else if (*opt_remote && !strcmp(name, ".merge")) {
6703                 size_t from = strlen(opt_remote);
6705                 if (!prefixcmp(value, "refs/heads/"))
6706                         value += STRING_SIZE("refs/heads/");
6708                 if (!string_format_from(opt_remote, &from, "/%s", value))
6709                         opt_remote[0] = 0;
6710         }
6713 static void
6714 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6716         const char *argv[SIZEOF_ARG] = { name, "=" };
6717         int argc = 1 + (cmd == option_set_command);
6718         int error = ERR;
6720         if (!argv_from_string(argv, &argc, value))
6721                 config_msg = "Too many option arguments";
6722         else
6723                 error = cmd(argc, argv);
6725         if (error == ERR)
6726                 warn("Option 'tig.%s': %s", name, config_msg);
6729 static bool
6730 set_environment_variable(const char *name, const char *value)
6732         size_t len = strlen(name) + 1 + strlen(value) + 1;
6733         char *env = malloc(len);
6735         if (env &&
6736             string_nformat(env, len, NULL, "%s=%s", name, value) &&
6737             putenv(env) == 0)
6738                 return TRUE;
6739         free(env);
6740         return FALSE;
6743 static void
6744 set_work_tree(const char *value)
6746         char cwd[SIZEOF_STR];
6748         if (!getcwd(cwd, sizeof(cwd)))
6749                 die("Failed to get cwd path: %s", strerror(errno));
6750         if (chdir(opt_git_dir) < 0)
6751                 die("Failed to chdir(%s): %s", strerror(errno));
6752         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6753                 die("Failed to get git path: %s", strerror(errno));
6754         if (chdir(cwd) < 0)
6755                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6756         if (chdir(value) < 0)
6757                 die("Failed to chdir(%s): %s", value, strerror(errno));
6758         if (!getcwd(cwd, sizeof(cwd)))
6759                 die("Failed to get cwd path: %s", strerror(errno));
6760         if (!set_environment_variable("GIT_WORK_TREE", cwd))
6761                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6762         if (!set_environment_variable("GIT_DIR", opt_git_dir))
6763                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6764         opt_is_inside_work_tree = TRUE;
6767 static int
6768 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6770         if (!strcmp(name, "i18n.commitencoding"))
6771                 string_ncopy(opt_encoding, value, valuelen);
6773         else if (!strcmp(name, "core.editor"))
6774                 string_ncopy(opt_editor, value, valuelen);
6776         else if (!strcmp(name, "core.worktree"))
6777                 set_work_tree(value);
6779         else if (!prefixcmp(name, "tig.color."))
6780                 set_repo_config_option(name + 10, value, option_color_command);
6782         else if (!prefixcmp(name, "tig.bind."))
6783                 set_repo_config_option(name + 9, value, option_bind_command);
6785         else if (!prefixcmp(name, "tig."))
6786                 set_repo_config_option(name + 4, value, option_set_command);
6788         else if (*opt_head && !prefixcmp(name, "branch.") &&
6789                  !strncmp(name + 7, opt_head, strlen(opt_head)))
6790                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6792         return OK;
6795 static int
6796 load_git_config(void)
6798         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6800         return run_io_load(config_list_argv, "=", read_repo_config_option);
6803 static int
6804 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6806         if (!opt_git_dir[0]) {
6807                 string_ncopy(opt_git_dir, name, namelen);
6809         } else if (opt_is_inside_work_tree == -1) {
6810                 /* This can be 3 different values depending on the
6811                  * version of git being used. If git-rev-parse does not
6812                  * understand --is-inside-work-tree it will simply echo
6813                  * the option else either "true" or "false" is printed.
6814                  * Default to true for the unknown case. */
6815                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6817         } else if (*name == '.') {
6818                 string_ncopy(opt_cdup, name, namelen);
6820         } else {
6821                 string_ncopy(opt_prefix, name, namelen);
6822         }
6824         return OK;
6827 static int
6828 load_repo_info(void)
6830         const char *head_argv[] = {
6831                 "git", "symbolic-ref", "HEAD", NULL
6832         };
6833         const char *rev_parse_argv[] = {
6834                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6835                         "--show-cdup", "--show-prefix", NULL
6836         };
6838         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6839                 chomp_string(opt_head);
6840                 if (!prefixcmp(opt_head, "refs/heads/")) {
6841                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6843                         memmove(opt_head, offset, strlen(offset) + 1);
6844                 }
6845         }
6847         return run_io_load(rev_parse_argv, "=", read_repo_info);
6851 /*
6852  * Main
6853  */
6855 static const char usage[] =
6856 "tig " TIG_VERSION " (" __DATE__ ")\n"
6857 "\n"
6858 "Usage: tig        [options] [revs] [--] [paths]\n"
6859 "   or: tig show   [options] [revs] [--] [paths]\n"
6860 "   or: tig blame  [rev] path\n"
6861 "   or: tig status\n"
6862 "   or: tig <      [git command output]\n"
6863 "\n"
6864 "Options:\n"
6865 "  -v, --version   Show version and exit\n"
6866 "  -h, --help      Show help message and exit";
6868 static void __NORETURN
6869 quit(int sig)
6871         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6872         if (cursed)
6873                 endwin();
6874         exit(0);
6877 static void __NORETURN
6878 die(const char *err, ...)
6880         va_list args;
6882         endwin();
6884         va_start(args, err);
6885         fputs("tig: ", stderr);
6886         vfprintf(stderr, err, args);
6887         fputs("\n", stderr);
6888         va_end(args);
6890         exit(1);
6893 static void
6894 warn(const char *msg, ...)
6896         va_list args;
6898         va_start(args, msg);
6899         fputs("tig warning: ", stderr);
6900         vfprintf(stderr, msg, args);
6901         fputs("\n", stderr);
6902         va_end(args);
6905 static enum request
6906 parse_options(int argc, const char *argv[])
6908         enum request request = REQ_VIEW_MAIN;
6909         const char *subcommand;
6910         bool seen_dashdash = FALSE;
6911         /* XXX: This is vulnerable to the user overriding options
6912          * required for the main view parser. */
6913         const char *custom_argv[SIZEOF_ARG] = {
6914                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6915                         "--topo-order", NULL
6916         };
6917         int i, j = 6;
6919         if (!isatty(STDIN_FILENO)) {
6920                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6921                 return REQ_VIEW_PAGER;
6922         }
6924         if (argc <= 1)
6925                 return REQ_NONE;
6927         subcommand = argv[1];
6928         if (!strcmp(subcommand, "status")) {
6929                 if (argc > 2)
6930                         warn("ignoring arguments after `%s'", subcommand);
6931                 return REQ_VIEW_STATUS;
6933         } else if (!strcmp(subcommand, "blame")) {
6934                 if (argc <= 2 || argc > 4)
6935                         die("invalid number of options to blame\n\n%s", usage);
6937                 i = 2;
6938                 if (argc == 4) {
6939                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6940                         i++;
6941                 }
6943                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6944                 return REQ_VIEW_BLAME;
6946         } else if (!strcmp(subcommand, "show")) {
6947                 request = REQ_VIEW_DIFF;
6949         } else {
6950                 subcommand = NULL;
6951         }
6953         if (subcommand) {
6954                 custom_argv[1] = subcommand;
6955                 j = 2;
6956         }
6958         for (i = 1 + !!subcommand; i < argc; i++) {
6959                 const char *opt = argv[i];
6961                 if (seen_dashdash || !strcmp(opt, "--")) {
6962                         seen_dashdash = TRUE;
6964                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6965                         printf("tig version %s\n", TIG_VERSION);
6966                         quit(0);
6968                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6969                         printf("%s\n", usage);
6970                         quit(0);
6971                 }
6973                 custom_argv[j++] = opt;
6974                 if (j >= ARRAY_SIZE(custom_argv))
6975                         die("command too long");
6976         }
6978         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6979                 die("Failed to format arguments"); 
6981         return request;
6984 int
6985 main(int argc, const char *argv[])
6987         enum request request = parse_options(argc, argv);
6988         struct view *view;
6989         size_t i;
6991         signal(SIGINT, quit);
6992         signal(SIGPIPE, SIG_IGN);
6994         if (setlocale(LC_ALL, "")) {
6995                 char *codeset = nl_langinfo(CODESET);
6997                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6998         }
7000         if (load_repo_info() == ERR)
7001                 die("Failed to load repo info.");
7003         if (load_options() == ERR)
7004                 die("Failed to load user config.");
7006         if (load_git_config() == ERR)
7007                 die("Failed to load repo config.");
7009         /* Require a git repository unless when running in pager mode. */
7010         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7011                 die("Not a git repository");
7013         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7014                 opt_utf8 = FALSE;
7016         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7017                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7018                 if (opt_iconv == ICONV_NONE)
7019                         die("Failed to initialize character set conversion");
7020         }
7022         if (load_refs() == ERR)
7023                 die("Failed to load refs.");
7025         foreach_view (view, i)
7026                 argv_from_env(view->ops->argv, view->cmd_env);
7028         init_display();
7030         if (request != REQ_NONE)
7031                 open_view(NULL, request, OPEN_PREPARED);
7032         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7034         while (view_driver(display[current_view], request)) {
7035                 int key = get_input(0);
7037                 view = display[current_view];
7038                 request = get_keybinding(view->keymap, key);
7040                 /* Some low-level request handling. This keeps access to
7041                  * status_win restricted. */
7042                 switch (request) {
7043                 case REQ_PROMPT:
7044                 {
7045                         char *cmd = read_prompt(":");
7047                         if (cmd && isdigit(*cmd)) {
7048                                 int lineno = view->lineno + 1;
7050                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7051                                         select_view_line(view, lineno - 1);
7052                                         report("");
7053                                 } else {
7054                                         report("Unable to parse '%s' as a line number", cmd);
7055                                 }
7057                         } else if (cmd) {
7058                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7059                                 const char *argv[SIZEOF_ARG] = { "git" };
7060                                 int argc = 1;
7062                                 /* When running random commands, initially show the
7063                                  * command in the title. However, it maybe later be
7064                                  * overwritten if a commit line is selected. */
7065                                 string_ncopy(next->ref, cmd, strlen(cmd));
7067                                 if (!argv_from_string(argv, &argc, cmd)) {
7068                                         report("Too many arguments");
7069                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7070                                         report("Failed to format command");
7071                                 } else {
7072                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7073                                 }
7074                         }
7076                         request = REQ_NONE;
7077                         break;
7078                 }
7079                 case REQ_SEARCH:
7080                 case REQ_SEARCH_BACK:
7081                 {
7082                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7083                         char *search = read_prompt(prompt);
7085                         if (search)
7086                                 string_ncopy(opt_search, search, strlen(search));
7087                         else if (*opt_search)
7088                                 request = request == REQ_SEARCH ?
7089                                         REQ_FIND_NEXT :
7090                                         REQ_FIND_PREV;
7091                         else
7092                                 request = REQ_NONE;
7093                         break;
7094                 }
7095                 default:
7096                         break;
7097                 }
7098         }
7100         quit(0);
7102         return 0;