Code

Treat empty '/' as "find next"
[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 size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static int load_refs(void);
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 #ifndef GIT_CONFIG
120 #define GIT_CONFIG "config"
121 #endif
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_TAB         '\t'
125 #define KEY_RETURN      '\r'
126 #define KEY_ESC         27
129 struct ref {
130         char *name;             /* Ref name; tag or head names are shortened. */
131         char id[SIZEOF_REV];    /* Commit SHA1 ID */
132         unsigned int head:1;    /* Is it the current HEAD? */
133         unsigned int tag:1;     /* Is it a tag? */
134         unsigned int ltag:1;    /* If so, is the tag local? */
135         unsigned int remote:1;  /* Is it a remote ref? */
136         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137         unsigned int next:1;    /* For ref lists: are there more refs? */
138 };
140 static struct ref **get_refs(const char *id);
142 enum format_flags {
143         FORMAT_ALL,             /* Perform replacement in all arguments. */
144         FORMAT_DASH,            /* Perform replacement up until "--". */
145         FORMAT_NONE             /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
173 enum input_status {
174         INPUT_OK,
175         INPUT_SKIP,
176         INPUT_STOP,
177         INPUT_CANCEL
178 };
180 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
182 static char *prompt_input(const char *prompt, input_handler handler, void *data);
183 static bool prompt_yesno(const char *prompt);
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
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 #define prefixcmp(str1, str2) \
273         strncmp(str1, str2, STRING_SIZE(str2))
275 static inline int
276 suffixcmp(const char *str, int slen, const char *suffix)
278         size_t len = slen >= 0 ? slen : strlen(str);
279         size_t suffixlen = strlen(suffix);
281         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
285 static bool
286 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
288         int valuelen;
290         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
291                 bool advance = cmd[valuelen] != 0;
293                 cmd[valuelen] = 0;
294                 argv[(*argc)++] = chomp_string(cmd);
295                 cmd = chomp_string(cmd + valuelen + advance);
296         }
298         if (*argc < SIZEOF_ARG)
299                 argv[*argc] = NULL;
300         return *argc < SIZEOF_ARG;
303 static void
304 argv_from_env(const char **argv, const char *name)
306         char *env = argv ? getenv(name) : NULL;
307         int argc = 0;
309         if (env && *env)
310                 env = strdup(env);
311         if (env && !argv_from_string(argv, &argc, env))
312                 die("Too many arguments in the `%s` environment variable", name);
316 /*
317  * Executing external commands.
318  */
320 enum io_type {
321         IO_FD,                  /* File descriptor based IO. */
322         IO_BG,                  /* Execute command in the background. */
323         IO_FG,                  /* Execute command with same std{in,out,err}. */
324         IO_RD,                  /* Read only fork+exec IO. */
325         IO_WR,                  /* Write only fork+exec IO. */
326         IO_AP,                  /* Append fork+exec output to file. */
327 };
329 struct io {
330         enum io_type type;      /* The requested type of pipe. */
331         const char *dir;        /* Directory from which to execute. */
332         pid_t pid;              /* Pipe for reading or writing. */
333         int pipe;               /* Pipe end for reading or writing. */
334         int error;              /* Error status. */
335         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
336         char *buf;              /* Read buffer. */
337         size_t bufalloc;        /* Allocated buffer size. */
338         size_t bufsize;         /* Buffer content size. */
339         char *bufpos;           /* Current buffer position. */
340         unsigned int eof:1;     /* Has end of file been reached. */
341 };
343 static void
344 reset_io(struct io *io)
346         io->pipe = -1;
347         io->pid = 0;
348         io->buf = io->bufpos = NULL;
349         io->bufalloc = io->bufsize = 0;
350         io->error = 0;
351         io->eof = 0;
354 static void
355 init_io(struct io *io, const char *dir, enum io_type type)
357         reset_io(io);
358         io->type = type;
359         io->dir = dir;
362 static bool
363 init_io_rd(struct io *io, const char *argv[], const char *dir,
364                 enum format_flags flags)
366         init_io(io, dir, IO_RD);
367         return format_argv(io->argv, argv, flags);
370 static bool
371 io_open(struct io *io, const char *name)
373         init_io(io, NULL, IO_FD);
374         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
375         return io->pipe != -1;
378 static bool
379 kill_io(struct io *io)
381         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
384 static bool
385 done_io(struct io *io)
387         pid_t pid = io->pid;
389         if (io->pipe != -1)
390                 close(io->pipe);
391         free(io->buf);
392         reset_io(io);
394         while (pid > 0) {
395                 int status;
396                 pid_t waiting = waitpid(pid, &status, 0);
398                 if (waiting < 0) {
399                         if (errno == EINTR)
400                                 continue;
401                         report("waitpid failed (%s)", strerror(errno));
402                         return FALSE;
403                 }
405                 return waiting == pid &&
406                        !WIFSIGNALED(status) &&
407                        WIFEXITED(status) &&
408                        !WEXITSTATUS(status);
409         }
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         int pipefds[2] = { -1, -1 };
419         if (io->type == IO_FD)
420                 return TRUE;
422         if ((io->type == IO_RD || io->type == IO_WR) &&
423             pipe(pipefds) < 0)
424                 return FALSE;
425         else if (io->type == IO_AP)
426                 pipefds[1] = io->pipe;
428         if ((io->pid = fork())) {
429                 if (pipefds[!(io->type == IO_WR)] != -1)
430                         close(pipefds[!(io->type == IO_WR)]);
431                 if (io->pid != -1) {
432                         io->pipe = pipefds[!!(io->type == IO_WR)];
433                         return TRUE;
434                 }
436         } else {
437                 if (io->type != IO_FG) {
438                         int devnull = open("/dev/null", O_RDWR);
439                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
440                         int writefd = (io->type == IO_RD || io->type == IO_AP)
441                                                         ? pipefds[1] : devnull;
443                         dup2(readfd,  STDIN_FILENO);
444                         dup2(writefd, STDOUT_FILENO);
445                         dup2(devnull, STDERR_FILENO);
447                         close(devnull);
448                         if (pipefds[0] != -1)
449                                 close(pipefds[0]);
450                         if (pipefds[1] != -1)
451                                 close(pipefds[1]);
452                 }
454                 if (io->dir && *io->dir && chdir(io->dir) == -1)
455                         die("Failed to change directory: %s", strerror(errno));
457                 execvp(io->argv[0], (char *const*) io->argv);
458                 die("Failed to execute program: %s", strerror(errno));
459         }
461         if (pipefds[!!(io->type == IO_WR)] != -1)
462                 close(pipefds[!!(io->type == IO_WR)]);
463         return FALSE;
466 static bool
467 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
469         init_io(io, dir, type);
470         if (!format_argv(io->argv, argv, FORMAT_NONE))
471                 return FALSE;
472         return start_io(io);
475 static int
476 run_io_do(struct io *io)
478         return start_io(io) && done_io(io);
481 static int
482 run_io_bg(const char **argv)
484         struct io io = {};
486         init_io(&io, NULL, IO_BG);
487         if (!format_argv(io.argv, argv, FORMAT_NONE))
488                 return FALSE;
489         return run_io_do(&io);
492 static bool
493 run_io_fg(const char **argv, const char *dir)
495         struct io io = {};
497         init_io(&io, dir, IO_FG);
498         if (!format_argv(io.argv, argv, FORMAT_NONE))
499                 return FALSE;
500         return run_io_do(&io);
503 static bool
504 run_io_append(const char **argv, enum format_flags flags, int fd)
506         struct io io = {};
508         init_io(&io, NULL, IO_AP);
509         io.pipe = fd;
510         if (format_argv(io.argv, argv, flags))
511                 return run_io_do(&io);
512         close(fd);
513         return FALSE;
516 static bool
517 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
519         return init_io_rd(io, argv, NULL, flags) && start_io(io);
522 static bool
523 io_eof(struct io *io)
525         return io->eof;
528 static int
529 io_error(struct io *io)
531         return io->error;
534 static bool
535 io_strerror(struct io *io)
537         return strerror(io->error);
540 static bool
541 io_can_read(struct io *io)
543         struct timeval tv = { 0, 500 };
544         fd_set fds;
546         FD_ZERO(&fds);
547         FD_SET(io->pipe, &fds);
549         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
552 static ssize_t
553 io_read(struct io *io, void *buf, size_t bufsize)
555         do {
556                 ssize_t readsize = read(io->pipe, buf, bufsize);
558                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
559                         continue;
560                 else if (readsize == -1)
561                         io->error = errno;
562                 else if (readsize == 0)
563                         io->eof = 1;
564                 return readsize;
565         } while (1);
568 static char *
569 io_get(struct io *io, int c, bool can_read)
571         char *eol;
572         ssize_t readsize;
574         if (!io->buf) {
575                 io->buf = io->bufpos = malloc(BUFSIZ);
576                 if (!io->buf)
577                         return NULL;
578                 io->bufalloc = BUFSIZ;
579                 io->bufsize = 0;
580         }
582         while (TRUE) {
583                 if (io->bufsize > 0) {
584                         eol = memchr(io->bufpos, c, io->bufsize);
585                         if (eol) {
586                                 char *line = io->bufpos;
588                                 *eol = 0;
589                                 io->bufpos = eol + 1;
590                                 io->bufsize -= io->bufpos - line;
591                                 return line;
592                         }
593                 }
595                 if (io_eof(io)) {
596                         if (io->bufsize) {
597                                 io->bufpos[io->bufsize] = 0;
598                                 io->bufsize = 0;
599                                 return io->bufpos;
600                         }
601                         return NULL;
602                 }
604                 if (!can_read)
605                         return NULL;
607                 if (io->bufsize > 0 && io->bufpos > io->buf)
608                         memmove(io->buf, io->bufpos, io->bufsize);
610                 io->bufpos = io->buf;
611                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
612                 if (io_error(io))
613                         return NULL;
614                 io->bufsize += readsize;
615         }
618 static bool
619 io_write(struct io *io, const void *buf, size_t bufsize)
621         size_t written = 0;
623         while (!io_error(io) && written < bufsize) {
624                 ssize_t size;
626                 size = write(io->pipe, buf + written, bufsize - written);
627                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
628                         continue;
629                 else if (size == -1)
630                         io->error = errno;
631                 else
632                         written += size;
633         }
635         return written == bufsize;
638 static bool
639 run_io_buf(const char **argv, char buf[], size_t bufsize)
641         struct io io = {};
642         bool error;
644         if (!run_io_rd(&io, argv, FORMAT_NONE))
645                 return FALSE;
647         io.buf = io.bufpos = buf;
648         io.bufalloc = bufsize;
649         error = !io_get(&io, '\n', TRUE) && io_error(&io);
650         io.buf = NULL;
652         return done_io(&io) || error;
655 static int
656 io_load(struct io *io, const char *separators,
657         int (*read_property)(char *, size_t, char *, size_t))
659         char *name;
660         int state = OK;
662         if (!start_io(io))
663                 return ERR;
665         while (state == OK && (name = io_get(io, '\n', TRUE))) {
666                 char *value;
667                 size_t namelen;
668                 size_t valuelen;
670                 name = chomp_string(name);
671                 namelen = strcspn(name, separators);
673                 if (name[namelen]) {
674                         name[namelen] = 0;
675                         value = chomp_string(name + namelen + 1);
676                         valuelen = strlen(value);
678                 } else {
679                         value = "";
680                         valuelen = 0;
681                 }
683                 state = read_property(name, namelen, value, valuelen);
684         }
686         if (state != ERR && io_error(io))
687                 state = ERR;
688         done_io(io);
690         return state;
693 static int
694 run_io_load(const char **argv, const char *separators,
695             int (*read_property)(char *, size_t, char *, size_t))
697         struct io io = {};
699         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
700                 ? io_load(&io, separators, read_property) : ERR;
704 /*
705  * User requests
706  */
708 #define REQ_INFO \
709         /* XXX: Keep the view request first and in sync with views[]. */ \
710         REQ_GROUP("View switching") \
711         REQ_(VIEW_MAIN,         "Show main view"), \
712         REQ_(VIEW_DIFF,         "Show diff view"), \
713         REQ_(VIEW_LOG,          "Show log view"), \
714         REQ_(VIEW_TREE,         "Show tree view"), \
715         REQ_(VIEW_BLOB,         "Show blob view"), \
716         REQ_(VIEW_BLAME,        "Show blame view"), \
717         REQ_(VIEW_HELP,         "Show help page"), \
718         REQ_(VIEW_PAGER,        "Show pager view"), \
719         REQ_(VIEW_STATUS,       "Show status view"), \
720         REQ_(VIEW_STAGE,        "Show stage view"), \
721         \
722         REQ_GROUP("View manipulation") \
723         REQ_(ENTER,             "Enter current line and scroll"), \
724         REQ_(NEXT,              "Move to next"), \
725         REQ_(PREVIOUS,          "Move to previous"), \
726         REQ_(PARENT,            "Move to parent"), \
727         REQ_(VIEW_NEXT,         "Move focus to next view"), \
728         REQ_(REFRESH,           "Reload and refresh"), \
729         REQ_(MAXIMIZE,          "Maximize the current view"), \
730         REQ_(VIEW_CLOSE,        "Close the current view"), \
731         REQ_(QUIT,              "Close all views and quit"), \
732         \
733         REQ_GROUP("View specific requests") \
734         REQ_(STATUS_UPDATE,     "Update file status"), \
735         REQ_(STATUS_REVERT,     "Revert file changes"), \
736         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
737         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
738         \
739         REQ_GROUP("Cursor navigation") \
740         REQ_(MOVE_UP,           "Move cursor one line up"), \
741         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
742         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
743         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
744         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
745         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
746         \
747         REQ_GROUP("Scrolling") \
748         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
749         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
750         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
751         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
752         \
753         REQ_GROUP("Searching") \
754         REQ_(SEARCH,            "Search the view"), \
755         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
756         REQ_(FIND_NEXT,         "Find next search match"), \
757         REQ_(FIND_PREV,         "Find previous search match"), \
758         \
759         REQ_GROUP("Option manipulation") \
760         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
761         REQ_(TOGGLE_DATE,       "Toggle date display"), \
762         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
763         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
764         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
765         \
766         REQ_GROUP("Misc") \
767         REQ_(PROMPT,            "Bring up the prompt"), \
768         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
769         REQ_(SHOW_VERSION,      "Show version information"), \
770         REQ_(STOP_LOADING,      "Stop all loading views"), \
771         REQ_(EDIT,              "Open in editor"), \
772         REQ_(NONE,              "Do nothing")
775 /* User action requests. */
776 enum request {
777 #define REQ_GROUP(help)
778 #define REQ_(req, help) REQ_##req
780         /* Offset all requests to avoid conflicts with ncurses getch values. */
781         REQ_OFFSET = KEY_MAX + 1,
782         REQ_INFO
784 #undef  REQ_GROUP
785 #undef  REQ_
786 };
788 struct request_info {
789         enum request request;
790         const char *name;
791         int namelen;
792         const char *help;
793 };
795 static struct request_info req_info[] = {
796 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
797 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
798         REQ_INFO
799 #undef  REQ_GROUP
800 #undef  REQ_
801 };
803 static enum request
804 get_request(const char *name)
806         int namelen = strlen(name);
807         int i;
809         for (i = 0; i < ARRAY_SIZE(req_info); i++)
810                 if (req_info[i].namelen == namelen &&
811                     !string_enum_compare(req_info[i].name, name, namelen))
812                         return req_info[i].request;
814         return REQ_NONE;
818 /*
819  * Options
820  */
822 static const char usage[] =
823 "tig " TIG_VERSION " (" __DATE__ ")\n"
824 "\n"
825 "Usage: tig        [options] [revs] [--] [paths]\n"
826 "   or: tig show   [options] [revs] [--] [paths]\n"
827 "   or: tig blame  [rev] path\n"
828 "   or: tig status\n"
829 "   or: tig <      [git command output]\n"
830 "\n"
831 "Options:\n"
832 "  -v, --version   Show version and exit\n"
833 "  -h, --help      Show help message and exit";
835 /* Option and state variables. */
836 static bool opt_date                    = TRUE;
837 static bool opt_author                  = TRUE;
838 static bool opt_line_number             = FALSE;
839 static bool opt_line_graphics           = TRUE;
840 static bool opt_rev_graph               = FALSE;
841 static bool opt_show_refs               = TRUE;
842 static int opt_num_interval             = NUMBER_INTERVAL;
843 static int opt_tab_size                 = TAB_SIZE;
844 static int opt_author_cols              = AUTHOR_COLS-1;
845 static char opt_path[SIZEOF_STR]        = "";
846 static char opt_file[SIZEOF_STR]        = "";
847 static char opt_ref[SIZEOF_REF]         = "";
848 static char opt_head[SIZEOF_REF]        = "";
849 static char opt_head_rev[SIZEOF_REV]    = "";
850 static char opt_remote[SIZEOF_REF]      = "";
851 static char opt_encoding[20]            = "UTF-8";
852 static bool opt_utf8                    = TRUE;
853 static char opt_codeset[20]             = "UTF-8";
854 static iconv_t opt_iconv                = ICONV_NONE;
855 static char opt_search[SIZEOF_STR]      = "";
856 static char opt_cdup[SIZEOF_STR]        = "";
857 static char opt_prefix[SIZEOF_STR]      = "";
858 static char opt_git_dir[SIZEOF_STR]     = "";
859 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
860 static char opt_editor[SIZEOF_STR]      = "";
861 static FILE *opt_tty                    = NULL;
863 #define is_initial_commit()     (!*opt_head_rev)
864 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
867 /*
868  * Line-oriented content detection.
869  */
871 #define LINE_INFO \
872 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
873 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
874 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
875 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
876 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
877 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
878 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
879 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
880 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
881 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
882 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
883 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
884 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
885 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
886 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
887 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
888 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
889 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
890 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
891 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
892 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
893 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
894 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
895 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
896 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
897 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
898 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
899 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
900 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
901 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
902 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
903 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
904 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
905 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
906 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
907 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
908 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
909 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
910 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
911 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
912 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
913 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
914 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
915 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
916 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
917 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
918 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
919 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
920 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
921 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
922 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
923 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
924 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
925 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
926 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
927 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
929 enum line_type {
930 #define LINE(type, line, fg, bg, attr) \
931         LINE_##type
932         LINE_INFO,
933         LINE_NONE
934 #undef  LINE
935 };
937 struct line_info {
938         const char *name;       /* Option name. */
939         int namelen;            /* Size of option name. */
940         const char *line;       /* The start of line to match. */
941         int linelen;            /* Size of string to match. */
942         int fg, bg, attr;       /* Color and text attributes for the lines. */
943 };
945 static struct line_info line_info[] = {
946 #define LINE(type, line, fg, bg, attr) \
947         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
948         LINE_INFO
949 #undef  LINE
950 };
952 static enum line_type
953 get_line_type(const char *line)
955         int linelen = strlen(line);
956         enum line_type type;
958         for (type = 0; type < ARRAY_SIZE(line_info); type++)
959                 /* Case insensitive search matches Signed-off-by lines better. */
960                 if (linelen >= line_info[type].linelen &&
961                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
962                         return type;
964         return LINE_DEFAULT;
967 static inline int
968 get_line_attr(enum line_type type)
970         assert(type < ARRAY_SIZE(line_info));
971         return COLOR_PAIR(type) | line_info[type].attr;
974 static struct line_info *
975 get_line_info(const char *name)
977         size_t namelen = strlen(name);
978         enum line_type type;
980         for (type = 0; type < ARRAY_SIZE(line_info); type++)
981                 if (namelen == line_info[type].namelen &&
982                     !string_enum_compare(line_info[type].name, name, namelen))
983                         return &line_info[type];
985         return NULL;
988 static void
989 init_colors(void)
991         int default_bg = line_info[LINE_DEFAULT].bg;
992         int default_fg = line_info[LINE_DEFAULT].fg;
993         enum line_type type;
995         start_color();
997         if (assume_default_colors(default_fg, default_bg) == ERR) {
998                 default_bg = COLOR_BLACK;
999                 default_fg = COLOR_WHITE;
1000         }
1002         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1003                 struct line_info *info = &line_info[type];
1004                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1005                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1007                 init_pair(type, fg, bg);
1008         }
1011 struct line {
1012         enum line_type type;
1014         /* State flags */
1015         unsigned int selected:1;
1016         unsigned int dirty:1;
1017         unsigned int cleareol:1;
1019         void *data;             /* User data */
1020 };
1023 /*
1024  * Keys
1025  */
1027 struct keybinding {
1028         int alias;
1029         enum request request;
1030 };
1032 static struct keybinding default_keybindings[] = {
1033         /* View switching */
1034         { 'm',          REQ_VIEW_MAIN },
1035         { 'd',          REQ_VIEW_DIFF },
1036         { 'l',          REQ_VIEW_LOG },
1037         { 't',          REQ_VIEW_TREE },
1038         { 'f',          REQ_VIEW_BLOB },
1039         { 'B',          REQ_VIEW_BLAME },
1040         { 'p',          REQ_VIEW_PAGER },
1041         { 'h',          REQ_VIEW_HELP },
1042         { 'S',          REQ_VIEW_STATUS },
1043         { 'c',          REQ_VIEW_STAGE },
1045         /* View manipulation */
1046         { 'q',          REQ_VIEW_CLOSE },
1047         { KEY_TAB,      REQ_VIEW_NEXT },
1048         { KEY_RETURN,   REQ_ENTER },
1049         { KEY_UP,       REQ_PREVIOUS },
1050         { KEY_DOWN,     REQ_NEXT },
1051         { 'R',          REQ_REFRESH },
1052         { KEY_F(5),     REQ_REFRESH },
1053         { 'O',          REQ_MAXIMIZE },
1055         /* Cursor navigation */
1056         { 'k',          REQ_MOVE_UP },
1057         { 'j',          REQ_MOVE_DOWN },
1058         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1059         { KEY_END,      REQ_MOVE_LAST_LINE },
1060         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1061         { ' ',          REQ_MOVE_PAGE_DOWN },
1062         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1063         { 'b',          REQ_MOVE_PAGE_UP },
1064         { '-',          REQ_MOVE_PAGE_UP },
1066         /* Scrolling */
1067         { KEY_IC,       REQ_SCROLL_LINE_UP },
1068         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1069         { 'w',          REQ_SCROLL_PAGE_UP },
1070         { 's',          REQ_SCROLL_PAGE_DOWN },
1072         /* Searching */
1073         { '/',          REQ_SEARCH },
1074         { '?',          REQ_SEARCH_BACK },
1075         { 'n',          REQ_FIND_NEXT },
1076         { 'N',          REQ_FIND_PREV },
1078         /* Misc */
1079         { 'Q',          REQ_QUIT },
1080         { 'z',          REQ_STOP_LOADING },
1081         { 'v',          REQ_SHOW_VERSION },
1082         { 'r',          REQ_SCREEN_REDRAW },
1083         { '.',          REQ_TOGGLE_LINENO },
1084         { 'D',          REQ_TOGGLE_DATE },
1085         { 'A',          REQ_TOGGLE_AUTHOR },
1086         { 'g',          REQ_TOGGLE_REV_GRAPH },
1087         { 'F',          REQ_TOGGLE_REFS },
1088         { ':',          REQ_PROMPT },
1089         { 'u',          REQ_STATUS_UPDATE },
1090         { '!',          REQ_STATUS_REVERT },
1091         { 'M',          REQ_STATUS_MERGE },
1092         { '@',          REQ_STAGE_NEXT },
1093         { ',',          REQ_PARENT },
1094         { 'e',          REQ_EDIT },
1095 };
1097 #define KEYMAP_INFO \
1098         KEYMAP_(GENERIC), \
1099         KEYMAP_(MAIN), \
1100         KEYMAP_(DIFF), \
1101         KEYMAP_(LOG), \
1102         KEYMAP_(TREE), \
1103         KEYMAP_(BLOB), \
1104         KEYMAP_(BLAME), \
1105         KEYMAP_(PAGER), \
1106         KEYMAP_(HELP), \
1107         KEYMAP_(STATUS), \
1108         KEYMAP_(STAGE)
1110 enum keymap {
1111 #define KEYMAP_(name) KEYMAP_##name
1112         KEYMAP_INFO
1113 #undef  KEYMAP_
1114 };
1116 static struct int_map keymap_table[] = {
1117 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1118         KEYMAP_INFO
1119 #undef  KEYMAP_
1120 };
1122 #define set_keymap(map, name) \
1123         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1125 struct keybinding_table {
1126         struct keybinding *data;
1127         size_t size;
1128 };
1130 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1132 static void
1133 add_keybinding(enum keymap keymap, enum request request, int key)
1135         struct keybinding_table *table = &keybindings[keymap];
1137         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1138         if (!table->data)
1139                 die("Failed to allocate keybinding");
1140         table->data[table->size].alias = key;
1141         table->data[table->size++].request = request;
1144 /* Looks for a key binding first in the given map, then in the generic map, and
1145  * lastly in the default keybindings. */
1146 static enum request
1147 get_keybinding(enum keymap keymap, int key)
1149         size_t i;
1151         for (i = 0; i < keybindings[keymap].size; i++)
1152                 if (keybindings[keymap].data[i].alias == key)
1153                         return keybindings[keymap].data[i].request;
1155         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1156                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1157                         return keybindings[KEYMAP_GENERIC].data[i].request;
1159         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1160                 if (default_keybindings[i].alias == key)
1161                         return default_keybindings[i].request;
1163         return (enum request) key;
1167 struct key {
1168         const char *name;
1169         int value;
1170 };
1172 static struct key key_table[] = {
1173         { "Enter",      KEY_RETURN },
1174         { "Space",      ' ' },
1175         { "Backspace",  KEY_BACKSPACE },
1176         { "Tab",        KEY_TAB },
1177         { "Escape",     KEY_ESC },
1178         { "Left",       KEY_LEFT },
1179         { "Right",      KEY_RIGHT },
1180         { "Up",         KEY_UP },
1181         { "Down",       KEY_DOWN },
1182         { "Insert",     KEY_IC },
1183         { "Delete",     KEY_DC },
1184         { "Hash",       '#' },
1185         { "Home",       KEY_HOME },
1186         { "End",        KEY_END },
1187         { "PageUp",     KEY_PPAGE },
1188         { "PageDown",   KEY_NPAGE },
1189         { "F1",         KEY_F(1) },
1190         { "F2",         KEY_F(2) },
1191         { "F3",         KEY_F(3) },
1192         { "F4",         KEY_F(4) },
1193         { "F5",         KEY_F(5) },
1194         { "F6",         KEY_F(6) },
1195         { "F7",         KEY_F(7) },
1196         { "F8",         KEY_F(8) },
1197         { "F9",         KEY_F(9) },
1198         { "F10",        KEY_F(10) },
1199         { "F11",        KEY_F(11) },
1200         { "F12",        KEY_F(12) },
1201 };
1203 static int
1204 get_key_value(const char *name)
1206         int i;
1208         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1209                 if (!strcasecmp(key_table[i].name, name))
1210                         return key_table[i].value;
1212         if (strlen(name) == 1 && isprint(*name))
1213                 return (int) *name;
1215         return ERR;
1218 static const char *
1219 get_key_name(int key_value)
1221         static char key_char[] = "'X'";
1222         const char *seq = NULL;
1223         int key;
1225         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1226                 if (key_table[key].value == key_value)
1227                         seq = key_table[key].name;
1229         if (seq == NULL &&
1230             key_value < 127 &&
1231             isprint(key_value)) {
1232                 key_char[1] = (char) key_value;
1233                 seq = key_char;
1234         }
1236         return seq ? seq : "(no key)";
1239 static const char *
1240 get_key(enum request request)
1242         static char buf[BUFSIZ];
1243         size_t pos = 0;
1244         char *sep = "";
1245         int i;
1247         buf[pos] = 0;
1249         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1250                 struct keybinding *keybinding = &default_keybindings[i];
1252                 if (keybinding->request != request)
1253                         continue;
1255                 if (!string_format_from(buf, &pos, "%s%s", sep,
1256                                         get_key_name(keybinding->alias)))
1257                         return "Too many keybindings!";
1258                 sep = ", ";
1259         }
1261         return buf;
1264 struct run_request {
1265         enum keymap keymap;
1266         int key;
1267         const char *argv[SIZEOF_ARG];
1268 };
1270 static struct run_request *run_request;
1271 static size_t run_requests;
1273 static enum request
1274 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1276         struct run_request *req;
1278         if (argc >= ARRAY_SIZE(req->argv) - 1)
1279                 return REQ_NONE;
1281         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1282         if (!req)
1283                 return REQ_NONE;
1285         run_request = req;
1286         req = &run_request[run_requests];
1287         req->keymap = keymap;
1288         req->key = key;
1289         req->argv[0] = NULL;
1291         if (!format_argv(req->argv, argv, FORMAT_NONE))
1292                 return REQ_NONE;
1294         return REQ_NONE + ++run_requests;
1297 static struct run_request *
1298 get_run_request(enum request request)
1300         if (request <= REQ_NONE)
1301                 return NULL;
1302         return &run_request[request - REQ_NONE - 1];
1305 static void
1306 add_builtin_run_requests(void)
1308         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1309         const char *gc[] = { "git", "gc", NULL };
1310         struct {
1311                 enum keymap keymap;
1312                 int key;
1313                 int argc;
1314                 const char **argv;
1315         } reqs[] = {
1316                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1317                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1318         };
1319         int i;
1321         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1322                 enum request req;
1324                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1325                 if (req != REQ_NONE)
1326                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1327         }
1330 /*
1331  * User config file handling.
1332  */
1334 static struct int_map color_map[] = {
1335 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1336         COLOR_MAP(DEFAULT),
1337         COLOR_MAP(BLACK),
1338         COLOR_MAP(BLUE),
1339         COLOR_MAP(CYAN),
1340         COLOR_MAP(GREEN),
1341         COLOR_MAP(MAGENTA),
1342         COLOR_MAP(RED),
1343         COLOR_MAP(WHITE),
1344         COLOR_MAP(YELLOW),
1345 };
1347 #define set_color(color, name) \
1348         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1350 static struct int_map attr_map[] = {
1351 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1352         ATTR_MAP(NORMAL),
1353         ATTR_MAP(BLINK),
1354         ATTR_MAP(BOLD),
1355         ATTR_MAP(DIM),
1356         ATTR_MAP(REVERSE),
1357         ATTR_MAP(STANDOUT),
1358         ATTR_MAP(UNDERLINE),
1359 };
1361 #define set_attribute(attr, name) \
1362         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1364 static int   config_lineno;
1365 static bool  config_errors;
1366 static const char *config_msg;
1368 /* Wants: object fgcolor bgcolor [attr] */
1369 static int
1370 option_color_command(int argc, const char *argv[])
1372         struct line_info *info;
1374         if (argc != 3 && argc != 4) {
1375                 config_msg = "Wrong number of arguments given to color command";
1376                 return ERR;
1377         }
1379         info = get_line_info(argv[0]);
1380         if (!info) {
1381                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1382                         info = get_line_info("delimiter");
1384                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1385                         info = get_line_info("date");
1387                 } else {
1388                         config_msg = "Unknown color name";
1389                         return ERR;
1390                 }
1391         }
1393         if (set_color(&info->fg, argv[1]) == ERR ||
1394             set_color(&info->bg, argv[2]) == ERR) {
1395                 config_msg = "Unknown color";
1396                 return ERR;
1397         }
1399         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1400                 config_msg = "Unknown attribute";
1401                 return ERR;
1402         }
1404         return OK;
1407 static bool parse_bool(const char *s)
1409         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1410                 !strcmp(s, "yes")) ? TRUE : FALSE;
1413 static int
1414 parse_int(const char *s, int default_value, int min, int max)
1416         int value = atoi(s);
1418         return (value < min || value > max) ? default_value : value;
1421 /* Wants: name = value */
1422 static int
1423 option_set_command(int argc, const char *argv[])
1425         if (argc != 3) {
1426                 config_msg = "Wrong number of arguments given to set command";
1427                 return ERR;
1428         }
1430         if (strcmp(argv[1], "=")) {
1431                 config_msg = "No value assigned";
1432                 return ERR;
1433         }
1435         if (!strcmp(argv[0], "show-author")) {
1436                 opt_author = parse_bool(argv[2]);
1437                 return OK;
1438         }
1440         if (!strcmp(argv[0], "show-date")) {
1441                 opt_date = parse_bool(argv[2]);
1442                 return OK;
1443         }
1445         if (!strcmp(argv[0], "show-rev-graph")) {
1446                 opt_rev_graph = parse_bool(argv[2]);
1447                 return OK;
1448         }
1450         if (!strcmp(argv[0], "show-refs")) {
1451                 opt_show_refs = parse_bool(argv[2]);
1452                 return OK;
1453         }
1455         if (!strcmp(argv[0], "show-line-numbers")) {
1456                 opt_line_number = parse_bool(argv[2]);
1457                 return OK;
1458         }
1460         if (!strcmp(argv[0], "line-graphics")) {
1461                 opt_line_graphics = parse_bool(argv[2]);
1462                 return OK;
1463         }
1465         if (!strcmp(argv[0], "line-number-interval")) {
1466                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1467                 return OK;
1468         }
1470         if (!strcmp(argv[0], "author-width")) {
1471                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1472                 return OK;
1473         }
1475         if (!strcmp(argv[0], "tab-size")) {
1476                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1477                 return OK;
1478         }
1480         if (!strcmp(argv[0], "commit-encoding")) {
1481                 const char *arg = argv[2];
1482                 int arglen = strlen(arg);
1484                 switch (arg[0]) {
1485                 case '"':
1486                 case '\'':
1487                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1488                                 config_msg = "Unmatched quotation";
1489                                 return ERR;
1490                         }
1491                         arg += 1; arglen -= 2;
1492                 default:
1493                         string_ncopy(opt_encoding, arg, strlen(arg));
1494                         return OK;
1495                 }
1496         }
1498         config_msg = "Unknown variable name";
1499         return ERR;
1502 /* Wants: mode request key */
1503 static int
1504 option_bind_command(int argc, const char *argv[])
1506         enum request request;
1507         int keymap;
1508         int key;
1510         if (argc < 3) {
1511                 config_msg = "Wrong number of arguments given to bind command";
1512                 return ERR;
1513         }
1515         if (set_keymap(&keymap, argv[0]) == ERR) {
1516                 config_msg = "Unknown key map";
1517                 return ERR;
1518         }
1520         key = get_key_value(argv[1]);
1521         if (key == ERR) {
1522                 config_msg = "Unknown key";
1523                 return ERR;
1524         }
1526         request = get_request(argv[2]);
1527         if (request == REQ_NONE) {
1528                 struct {
1529                         const char *name;
1530                         enum request request;
1531                 } obsolete[] = {
1532                         { "cherry-pick",        REQ_NONE },
1533                         { "screen-resize",      REQ_NONE },
1534                         { "tree-parent",        REQ_PARENT },
1535                 };
1536                 size_t namelen = strlen(argv[2]);
1537                 int i;
1539                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1540                         if (namelen != strlen(obsolete[i].name) ||
1541                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1542                                 continue;
1543                         if (obsolete[i].request != REQ_NONE)
1544                                 add_keybinding(keymap, obsolete[i].request, key);
1545                         config_msg = "Obsolete request name";
1546                         return ERR;
1547                 }
1548         }
1549         if (request == REQ_NONE && *argv[2]++ == '!')
1550                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1551         if (request == REQ_NONE) {
1552                 config_msg = "Unknown request name";
1553                 return ERR;
1554         }
1556         add_keybinding(keymap, request, key);
1558         return OK;
1561 static int
1562 set_option(const char *opt, char *value)
1564         const char *argv[SIZEOF_ARG];
1565         int argc = 0;
1567         if (!argv_from_string(argv, &argc, value)) {
1568                 config_msg = "Too many option arguments";
1569                 return ERR;
1570         }
1572         if (!strcmp(opt, "color"))
1573                 return option_color_command(argc, argv);
1575         if (!strcmp(opt, "set"))
1576                 return option_set_command(argc, argv);
1578         if (!strcmp(opt, "bind"))
1579                 return option_bind_command(argc, argv);
1581         config_msg = "Unknown option command";
1582         return ERR;
1585 static int
1586 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1588         int status = OK;
1590         config_lineno++;
1591         config_msg = "Internal error";
1593         /* Check for comment markers, since read_properties() will
1594          * only ensure opt and value are split at first " \t". */
1595         optlen = strcspn(opt, "#");
1596         if (optlen == 0)
1597                 return OK;
1599         if (opt[optlen] != 0) {
1600                 config_msg = "No option value";
1601                 status = ERR;
1603         }  else {
1604                 /* Look for comment endings in the value. */
1605                 size_t len = strcspn(value, "#");
1607                 if (len < valuelen) {
1608                         valuelen = len;
1609                         value[valuelen] = 0;
1610                 }
1612                 status = set_option(opt, value);
1613         }
1615         if (status == ERR) {
1616                 warn("Error on line %d, near '%.*s': %s",
1617                      config_lineno, (int) optlen, opt, config_msg);
1618                 config_errors = TRUE;
1619         }
1621         /* Always keep going if errors are encountered. */
1622         return OK;
1625 static void
1626 load_option_file(const char *path)
1628         struct io io = {};
1630         /* It's ok that the file doesn't exist. */
1631         if (!io_open(&io, path))
1632                 return;
1634         config_lineno = 0;
1635         config_errors = FALSE;
1637         if (io_load(&io, " \t", read_option) == ERR ||
1638             config_errors == TRUE)
1639                 warn("Errors while loading %s.", path);
1642 static int
1643 load_options(void)
1645         const char *home = getenv("HOME");
1646         const char *tigrc_user = getenv("TIGRC_USER");
1647         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1648         char buf[SIZEOF_STR];
1650         add_builtin_run_requests();
1652         if (!tigrc_system) {
1653                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1654                         return ERR;
1655                 tigrc_system = buf;
1656         }
1657         load_option_file(tigrc_system);
1659         if (!tigrc_user) {
1660                 if (!home || !string_format(buf, "%s/.tigrc", home))
1661                         return ERR;
1662                 tigrc_user = buf;
1663         }
1664         load_option_file(tigrc_user);
1666         return OK;
1670 /*
1671  * The viewer
1672  */
1674 struct view;
1675 struct view_ops;
1677 /* The display array of active views and the index of the current view. */
1678 static struct view *display[2];
1679 static unsigned int current_view;
1681 #define foreach_displayed_view(view, i) \
1682         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1684 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1686 /* Current head and commit ID */
1687 static char ref_blob[SIZEOF_REF]        = "";
1688 static char ref_commit[SIZEOF_REF]      = "HEAD";
1689 static char ref_head[SIZEOF_REF]        = "HEAD";
1691 struct view {
1692         const char *name;       /* View name */
1693         const char *cmd_env;    /* Command line set via environment */
1694         const char *id;         /* Points to either of ref_{head,commit,blob} */
1696         struct view_ops *ops;   /* View operations */
1698         enum keymap keymap;     /* What keymap does this view have */
1699         bool git_dir;           /* Whether the view requires a git directory. */
1701         char ref[SIZEOF_REF];   /* Hovered commit reference */
1702         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1704         int height, width;      /* The width and height of the main window */
1705         WINDOW *win;            /* The main window */
1706         WINDOW *title;          /* The title window living below the main window */
1708         /* Navigation */
1709         unsigned long offset;   /* Offset of the window top */
1710         unsigned long lineno;   /* Current line number */
1711         unsigned long p_offset; /* Previous offset of the window top */
1712         unsigned long p_lineno; /* Previous current line number */
1713         bool p_restore;         /* Should the previous position be restored. */
1715         /* Searching */
1716         char grep[SIZEOF_STR];  /* Search string */
1717         regex_t *regex;         /* Pre-compiled regex */
1719         /* If non-NULL, points to the view that opened this view. If this view
1720          * is closed tig will switch back to the parent view. */
1721         struct view *parent;
1723         /* Buffering */
1724         size_t lines;           /* Total number of lines */
1725         struct line *line;      /* Line index */
1726         size_t line_alloc;      /* Total number of allocated lines */
1727         unsigned int digits;    /* Number of digits in the lines member. */
1729         /* Drawing */
1730         struct line *curline;   /* Line currently being drawn. */
1731         enum line_type curtype; /* Attribute currently used for drawing. */
1732         unsigned long col;      /* Column when drawing. */
1733         bool has_scrolled;      /* View was scrolled. */
1735         /* Loading */
1736         struct io io;
1737         struct io *pipe;
1738         time_t start_time;
1739         time_t update_secs;
1740 };
1742 struct view_ops {
1743         /* What type of content being displayed. Used in the title bar. */
1744         const char *type;
1745         /* Default command arguments. */
1746         const char **argv;
1747         /* Open and reads in all view content. */
1748         bool (*open)(struct view *view);
1749         /* Read one line; updates view->line. */
1750         bool (*read)(struct view *view, char *data);
1751         /* Draw one line; @lineno must be < view->height. */
1752         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1753         /* Depending on view handle a special requests. */
1754         enum request (*request)(struct view *view, enum request request, struct line *line);
1755         /* Search for regex in a line. */
1756         bool (*grep)(struct view *view, struct line *line);
1757         /* Select line */
1758         void (*select)(struct view *view, struct line *line);
1759 };
1761 static struct view_ops blame_ops;
1762 static struct view_ops blob_ops;
1763 static struct view_ops diff_ops;
1764 static struct view_ops help_ops;
1765 static struct view_ops log_ops;
1766 static struct view_ops main_ops;
1767 static struct view_ops pager_ops;
1768 static struct view_ops stage_ops;
1769 static struct view_ops status_ops;
1770 static struct view_ops tree_ops;
1772 #define VIEW_STR(name, env, ref, ops, map, git) \
1773         { name, #env, ref, ops, map, git }
1775 #define VIEW_(id, name, ops, git, ref) \
1776         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1779 static struct view views[] = {
1780         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1781         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1782         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1783         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1784         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1785         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1786         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1787         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1788         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1789         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1790 };
1792 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1793 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1795 #define foreach_view(view, i) \
1796         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1798 #define view_is_displayed(view) \
1799         (view == display[0] || view == display[1])
1802 enum line_graphic {
1803         LINE_GRAPHIC_VLINE
1804 };
1806 static int line_graphics[] = {
1807         /* LINE_GRAPHIC_VLINE: */ '|'
1808 };
1810 static inline void
1811 set_view_attr(struct view *view, enum line_type type)
1813         if (!view->curline->selected && view->curtype != type) {
1814                 wattrset(view->win, get_line_attr(type));
1815                 wchgat(view->win, -1, 0, type, NULL);
1816                 view->curtype = type;
1817         }
1820 static int
1821 draw_chars(struct view *view, enum line_type type, const char *string,
1822            int max_len, bool use_tilde)
1824         int len = 0;
1825         int col = 0;
1826         int trimmed = FALSE;
1828         if (max_len <= 0)
1829                 return 0;
1831         if (opt_utf8) {
1832                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1833         } else {
1834                 col = len = strlen(string);
1835                 if (len > max_len) {
1836                         if (use_tilde) {
1837                                 max_len -= 1;
1838                         }
1839                         col = len = max_len;
1840                         trimmed = TRUE;
1841                 }
1842         }
1844         set_view_attr(view, type);
1845         waddnstr(view->win, string, len);
1846         if (trimmed && use_tilde) {
1847                 set_view_attr(view, LINE_DELIMITER);
1848                 waddch(view->win, '~');
1849                 col++;
1850         }
1852         return col;
1855 static int
1856 draw_space(struct view *view, enum line_type type, int max, int spaces)
1858         static char space[] = "                    ";
1859         int col = 0;
1861         spaces = MIN(max, spaces);
1863         while (spaces > 0) {
1864                 int len = MIN(spaces, sizeof(space) - 1);
1866                 col += draw_chars(view, type, space, spaces, FALSE);
1867                 spaces -= len;
1868         }
1870         return col;
1873 static bool
1874 draw_lineno(struct view *view, unsigned int lineno)
1876         char number[10];
1877         int digits3 = view->digits < 3 ? 3 : view->digits;
1878         int max_number = MIN(digits3, STRING_SIZE(number));
1879         int max = view->width - view->col;
1880         int col;
1882         if (max < max_number)
1883                 max_number = max;
1885         lineno += view->offset + 1;
1886         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1887                 static char fmt[] = "%1ld";
1889                 if (view->digits <= 9)
1890                         fmt[1] = '0' + digits3;
1892                 if (!string_format(number, fmt, lineno))
1893                         number[0] = 0;
1894                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1895         } else {
1896                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1897         }
1899         if (col < max) {
1900                 set_view_attr(view, LINE_DEFAULT);
1901                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1902                 col++;
1903         }
1905         if (col < max)
1906                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1907         view->col += col;
1909         return view->width - view->col <= 0;
1912 static bool
1913 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1915         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1916         return view->width - view->col <= 0;
1919 static bool
1920 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1922         int max = view->width - view->col;
1923         int i;
1925         if (max < size)
1926                 size = max;
1928         set_view_attr(view, type);
1929         /* Using waddch() instead of waddnstr() ensures that
1930          * they'll be rendered correctly for the cursor line. */
1931         for (i = 0; i < size; i++)
1932                 waddch(view->win, graphic[i]);
1934         view->col += size;
1935         if (size < max) {
1936                 waddch(view->win, ' ');
1937                 view->col++;
1938         }
1940         return view->width - view->col <= 0;
1943 static bool
1944 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1946         int max = MIN(view->width - view->col, len);
1947         int col;
1949         if (text)
1950                 col = draw_chars(view, type, text, max - 1, trim);
1951         else
1952                 col = draw_space(view, type, max - 1, max - 1);
1954         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1955         return view->width - view->col <= 0;
1958 static bool
1959 draw_date(struct view *view, struct tm *time)
1961         char buf[DATE_COLS];
1962         char *date;
1963         int timelen = 0;
1965         if (time)
1966                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1967         date = timelen ? buf : NULL;
1969         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1972 static bool
1973 draw_author(struct view *view, const char *author)
1975         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1977         if (!trim) {
1978                 static char initials[10];
1979                 size_t pos;
1981 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
1983                 memset(initials, 0, sizeof(initials));
1984                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
1985                         while (is_initial_sep(*author))
1986                                 author++;
1987                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
1988                         while (*author && !is_initial_sep(author[1]))
1989                                 author++;
1990                 }
1992                 author = initials;
1993         }
1995         return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
1998 static bool
1999 draw_view_line(struct view *view, unsigned int lineno)
2001         struct line *line;
2002         bool selected = (view->offset + lineno == view->lineno);
2004         assert(view_is_displayed(view));
2006         if (view->offset + lineno >= view->lines)
2007                 return FALSE;
2009         line = &view->line[view->offset + lineno];
2011         wmove(view->win, lineno, 0);
2012         if (line->cleareol)
2013                 wclrtoeol(view->win);
2014         view->col = 0;
2015         view->curline = line;
2016         view->curtype = LINE_NONE;
2017         line->selected = FALSE;
2018         line->dirty = line->cleareol = 0;
2020         if (selected) {
2021                 set_view_attr(view, LINE_CURSOR);
2022                 line->selected = TRUE;
2023                 view->ops->select(view, line);
2024         }
2026         return view->ops->draw(view, line, lineno);
2029 static void
2030 redraw_view_dirty(struct view *view)
2032         bool dirty = FALSE;
2033         int lineno;
2035         for (lineno = 0; lineno < view->height; lineno++) {
2036                 if (view->offset + lineno >= view->lines)
2037                         break;
2038                 if (!view->line[view->offset + lineno].dirty)
2039                         continue;
2040                 dirty = TRUE;
2041                 if (!draw_view_line(view, lineno))
2042                         break;
2043         }
2045         if (!dirty)
2046                 return;
2047         wnoutrefresh(view->win);
2050 static void
2051 redraw_view_from(struct view *view, int lineno)
2053         assert(0 <= lineno && lineno < view->height);
2055         for (; lineno < view->height; lineno++) {
2056                 if (!draw_view_line(view, lineno))
2057                         break;
2058         }
2060         wnoutrefresh(view->win);
2063 static void
2064 redraw_view(struct view *view)
2066         werase(view->win);
2067         redraw_view_from(view, 0);
2071 static void
2072 update_view_title(struct view *view)
2074         char buf[SIZEOF_STR];
2075         char state[SIZEOF_STR];
2076         size_t bufpos = 0, statelen = 0;
2078         assert(view_is_displayed(view));
2080         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2081                 unsigned int view_lines = view->offset + view->height;
2082                 unsigned int lines = view->lines
2083                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2084                                    : 0;
2086                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2087                                    view->ops->type,
2088                                    view->lineno + 1,
2089                                    view->lines,
2090                                    lines);
2092         }
2094         if (view->pipe) {
2095                 time_t secs = time(NULL) - view->start_time;
2097                 /* Three git seconds are a long time ... */
2098                 if (secs > 2)
2099                         string_format_from(state, &statelen, " loading %lds", secs);
2100         }
2102         string_format_from(buf, &bufpos, "[%s]", view->name);
2103         if (*view->ref && bufpos < view->width) {
2104                 size_t refsize = strlen(view->ref);
2105                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2107                 if (minsize < view->width)
2108                         refsize = view->width - minsize + 7;
2109                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2110         }
2112         if (statelen && bufpos < view->width) {
2113                 string_format_from(buf, &bufpos, "%s", state);
2114         }
2116         if (view == display[current_view])
2117                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2118         else
2119                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2121         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2122         wclrtoeol(view->title);
2123         wnoutrefresh(view->title);
2126 static void
2127 resize_display(void)
2129         int offset, i;
2130         struct view *base = display[0];
2131         struct view *view = display[1] ? display[1] : display[0];
2133         /* Setup window dimensions */
2135         getmaxyx(stdscr, base->height, base->width);
2137         /* Make room for the status window. */
2138         base->height -= 1;
2140         if (view != base) {
2141                 /* Horizontal split. */
2142                 view->width   = base->width;
2143                 view->height  = SCALE_SPLIT_VIEW(base->height);
2144                 base->height -= view->height;
2146                 /* Make room for the title bar. */
2147                 view->height -= 1;
2148         }
2150         /* Make room for the title bar. */
2151         base->height -= 1;
2153         offset = 0;
2155         foreach_displayed_view (view, i) {
2156                 if (!view->win) {
2157                         view->win = newwin(view->height, 0, offset, 0);
2158                         if (!view->win)
2159                                 die("Failed to create %s view", view->name);
2161                         scrollok(view->win, FALSE);
2163                         view->title = newwin(1, 0, offset + view->height, 0);
2164                         if (!view->title)
2165                                 die("Failed to create title window");
2167                 } else {
2168                         wresize(view->win, view->height, view->width);
2169                         mvwin(view->win,   offset, 0);
2170                         mvwin(view->title, offset + view->height, 0);
2171                 }
2173                 offset += view->height + 1;
2174         }
2177 static void
2178 redraw_display(bool clear)
2180         struct view *view;
2181         int i;
2183         foreach_displayed_view (view, i) {
2184                 if (clear)
2185                         wclear(view->win);
2186                 redraw_view(view);
2187                 update_view_title(view);
2188         }
2191 static void
2192 toggle_view_option(bool *option, const char *help)
2194         *option = !*option;
2195         redraw_display(FALSE);
2196         report("%sabling %s", *option ? "En" : "Dis", help);
2199 /*
2200  * Navigation
2201  */
2203 /* Scrolling backend */
2204 static void
2205 do_scroll_view(struct view *view, int lines)
2207         bool redraw_current_line = FALSE;
2209         /* The rendering expects the new offset. */
2210         view->offset += lines;
2212         assert(0 <= view->offset && view->offset < view->lines);
2213         assert(lines);
2215         /* Move current line into the view. */
2216         if (view->lineno < view->offset) {
2217                 view->lineno = view->offset;
2218                 redraw_current_line = TRUE;
2219         } else if (view->lineno >= view->offset + view->height) {
2220                 view->lineno = view->offset + view->height - 1;
2221                 redraw_current_line = TRUE;
2222         }
2224         assert(view->offset <= view->lineno && view->lineno < view->lines);
2226         /* Redraw the whole screen if scrolling is pointless. */
2227         if (view->height < ABS(lines)) {
2228                 redraw_view(view);
2230         } else {
2231                 int line = lines > 0 ? view->height - lines : 0;
2232                 int end = line + ABS(lines);
2234                 scrollok(view->win, TRUE);
2235                 wscrl(view->win, lines);
2236                 scrollok(view->win, FALSE);
2238                 while (line < end && draw_view_line(view, line))
2239                         line++;
2241                 if (redraw_current_line)
2242                         draw_view_line(view, view->lineno - view->offset);
2243                 wnoutrefresh(view->win);
2244         }
2246         view->has_scrolled = TRUE;
2247         report("");
2250 /* Scroll frontend */
2251 static void
2252 scroll_view(struct view *view, enum request request)
2254         int lines = 1;
2256         assert(view_is_displayed(view));
2258         switch (request) {
2259         case REQ_SCROLL_PAGE_DOWN:
2260                 lines = view->height;
2261         case REQ_SCROLL_LINE_DOWN:
2262                 if (view->offset + lines > view->lines)
2263                         lines = view->lines - view->offset;
2265                 if (lines == 0 || view->offset + view->height >= view->lines) {
2266                         report("Cannot scroll beyond the last line");
2267                         return;
2268                 }
2269                 break;
2271         case REQ_SCROLL_PAGE_UP:
2272                 lines = view->height;
2273         case REQ_SCROLL_LINE_UP:
2274                 if (lines > view->offset)
2275                         lines = view->offset;
2277                 if (lines == 0) {
2278                         report("Cannot scroll beyond the first line");
2279                         return;
2280                 }
2282                 lines = -lines;
2283                 break;
2285         default:
2286                 die("request %d not handled in switch", request);
2287         }
2289         do_scroll_view(view, lines);
2292 /* Cursor moving */
2293 static void
2294 move_view(struct view *view, enum request request)
2296         int scroll_steps = 0;
2297         int steps;
2299         switch (request) {
2300         case REQ_MOVE_FIRST_LINE:
2301                 steps = -view->lineno;
2302                 break;
2304         case REQ_MOVE_LAST_LINE:
2305                 steps = view->lines - view->lineno - 1;
2306                 break;
2308         case REQ_MOVE_PAGE_UP:
2309                 steps = view->height > view->lineno
2310                       ? -view->lineno : -view->height;
2311                 break;
2313         case REQ_MOVE_PAGE_DOWN:
2314                 steps = view->lineno + view->height >= view->lines
2315                       ? view->lines - view->lineno - 1 : view->height;
2316                 break;
2318         case REQ_MOVE_UP:
2319                 steps = -1;
2320                 break;
2322         case REQ_MOVE_DOWN:
2323                 steps = 1;
2324                 break;
2326         default:
2327                 die("request %d not handled in switch", request);
2328         }
2330         if (steps <= 0 && view->lineno == 0) {
2331                 report("Cannot move beyond the first line");
2332                 return;
2334         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2335                 report("Cannot move beyond the last line");
2336                 return;
2337         }
2339         /* Move the current line */
2340         view->lineno += steps;
2341         assert(0 <= view->lineno && view->lineno < view->lines);
2343         /* Check whether the view needs to be scrolled */
2344         if (view->lineno < view->offset ||
2345             view->lineno >= view->offset + view->height) {
2346                 scroll_steps = steps;
2347                 if (steps < 0 && -steps > view->offset) {
2348                         scroll_steps = -view->offset;
2350                 } else if (steps > 0) {
2351                         if (view->lineno == view->lines - 1 &&
2352                             view->lines > view->height) {
2353                                 scroll_steps = view->lines - view->offset - 1;
2354                                 if (scroll_steps >= view->height)
2355                                         scroll_steps -= view->height - 1;
2356                         }
2357                 }
2358         }
2360         if (!view_is_displayed(view)) {
2361                 view->offset += scroll_steps;
2362                 assert(0 <= view->offset && view->offset < view->lines);
2363                 view->ops->select(view, &view->line[view->lineno]);
2364                 return;
2365         }
2367         /* Repaint the old "current" line if we be scrolling */
2368         if (ABS(steps) < view->height)
2369                 draw_view_line(view, view->lineno - steps - view->offset);
2371         if (scroll_steps) {
2372                 do_scroll_view(view, scroll_steps);
2373                 return;
2374         }
2376         /* Draw the current line */
2377         draw_view_line(view, view->lineno - view->offset);
2379         wnoutrefresh(view->win);
2380         report("");
2384 /*
2385  * Searching
2386  */
2388 static void search_view(struct view *view, enum request request);
2390 static void
2391 select_view_line(struct view *view, unsigned long lineno)
2393         if (lineno - view->offset >= view->height) {
2394                 view->offset = lineno;
2395                 view->lineno = lineno;
2396                 if (view_is_displayed(view))
2397                         redraw_view(view);
2399         } else {
2400                 unsigned long old_lineno = view->lineno - view->offset;
2402                 view->lineno = lineno;
2403                 if (view_is_displayed(view)) {
2404                         draw_view_line(view, old_lineno);
2405                         draw_view_line(view, view->lineno - view->offset);
2406                         wnoutrefresh(view->win);
2407                 } else {
2408                         view->ops->select(view, &view->line[view->lineno]);
2409                 }
2410         }
2413 static void
2414 find_next(struct view *view, enum request request)
2416         unsigned long lineno = view->lineno;
2417         int direction;
2419         if (!*view->grep) {
2420                 if (!*opt_search)
2421                         report("No previous search");
2422                 else
2423                         search_view(view, request);
2424                 return;
2425         }
2427         switch (request) {
2428         case REQ_SEARCH:
2429         case REQ_FIND_NEXT:
2430                 direction = 1;
2431                 break;
2433         case REQ_SEARCH_BACK:
2434         case REQ_FIND_PREV:
2435                 direction = -1;
2436                 break;
2438         default:
2439                 return;
2440         }
2442         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2443                 lineno += direction;
2445         /* Note, lineno is unsigned long so will wrap around in which case it
2446          * will become bigger than view->lines. */
2447         for (; lineno < view->lines; lineno += direction) {
2448                 if (view->ops->grep(view, &view->line[lineno])) {
2449                         select_view_line(view, lineno);
2450                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2451                         return;
2452                 }
2453         }
2455         report("No match found for '%s'", view->grep);
2458 static void
2459 search_view(struct view *view, enum request request)
2461         int regex_err;
2463         if (view->regex) {
2464                 regfree(view->regex);
2465                 *view->grep = 0;
2466         } else {
2467                 view->regex = calloc(1, sizeof(*view->regex));
2468                 if (!view->regex)
2469                         return;
2470         }
2472         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2473         if (regex_err != 0) {
2474                 char buf[SIZEOF_STR] = "unknown error";
2476                 regerror(regex_err, view->regex, buf, sizeof(buf));
2477                 report("Search failed: %s", buf);
2478                 return;
2479         }
2481         string_copy(view->grep, opt_search);
2483         find_next(view, request);
2486 /*
2487  * Incremental updating
2488  */
2490 static void
2491 reset_view(struct view *view)
2493         int i;
2495         for (i = 0; i < view->lines; i++)
2496                 free(view->line[i].data);
2497         free(view->line);
2499         view->p_offset = view->offset;
2500         view->p_lineno = view->lineno;
2502         view->line = NULL;
2503         view->offset = 0;
2504         view->lines  = 0;
2505         view->lineno = 0;
2506         view->line_alloc = 0;
2507         view->vid[0] = 0;
2508         view->update_secs = 0;
2511 static void
2512 free_argv(const char *argv[])
2514         int argc;
2516         for (argc = 0; argv[argc]; argc++)
2517                 free((void *) argv[argc]);
2520 static bool
2521 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2523         char buf[SIZEOF_STR];
2524         int argc;
2525         bool noreplace = flags == FORMAT_NONE;
2527         free_argv(dst_argv);
2529         for (argc = 0; src_argv[argc]; argc++) {
2530                 const char *arg = src_argv[argc];
2531                 size_t bufpos = 0;
2533                 while (arg) {
2534                         char *next = strstr(arg, "%(");
2535                         int len = next - arg;
2536                         const char *value;
2538                         if (!next || noreplace) {
2539                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2540                                         noreplace = TRUE;
2541                                 len = strlen(arg);
2542                                 value = "";
2544                         } else if (!prefixcmp(next, "%(directory)")) {
2545                                 value = opt_path;
2547                         } else if (!prefixcmp(next, "%(file)")) {
2548                                 value = opt_file;
2550                         } else if (!prefixcmp(next, "%(ref)")) {
2551                                 value = *opt_ref ? opt_ref : "HEAD";
2553                         } else if (!prefixcmp(next, "%(head)")) {
2554                                 value = ref_head;
2556                         } else if (!prefixcmp(next, "%(commit)")) {
2557                                 value = ref_commit;
2559                         } else if (!prefixcmp(next, "%(blob)")) {
2560                                 value = ref_blob;
2562                         } else {
2563                                 report("Unknown replacement: `%s`", next);
2564                                 return FALSE;
2565                         }
2567                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2568                                 return FALSE;
2570                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2571                 }
2573                 dst_argv[argc] = strdup(buf);
2574                 if (!dst_argv[argc])
2575                         break;
2576         }
2578         dst_argv[argc] = NULL;
2580         return src_argv[argc] == NULL;
2583 static bool
2584 restore_view_position(struct view *view)
2586         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2587                 return FALSE;
2589         /* Changing the view position cancels the restoring. */
2590         /* FIXME: Changing back to the first line is not detected. */
2591         if (view->offset != 0 || view->lineno != 0) {
2592                 view->p_restore = FALSE;
2593                 return FALSE;
2594         }
2596         if (view->p_lineno >= view->lines) {
2597                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2598                 if (view->p_offset >= view->p_lineno) {
2599                         unsigned long half = view->height / 2;
2601                         if (view->p_lineno > half)
2602                                 view->p_offset = view->p_lineno - half;
2603                         else
2604                                 view->p_offset = 0;
2605                 }
2606         }
2608         if (view_is_displayed(view) &&
2609             view->offset != view->p_offset &&
2610             view->lineno != view->p_lineno)
2611                 werase(view->win);
2613         view->offset = view->p_offset;
2614         view->lineno = view->p_lineno;
2615         view->p_restore = FALSE;
2617         return TRUE;
2620 static void
2621 end_update(struct view *view, bool force)
2623         if (!view->pipe)
2624                 return;
2625         while (!view->ops->read(view, NULL))
2626                 if (!force)
2627                         return;
2628         set_nonblocking_input(FALSE);
2629         if (force)
2630                 kill_io(view->pipe);
2631         done_io(view->pipe);
2632         view->pipe = NULL;
2635 static void
2636 setup_update(struct view *view, const char *vid)
2638         set_nonblocking_input(TRUE);
2639         reset_view(view);
2640         string_copy_rev(view->vid, vid);
2641         view->pipe = &view->io;
2642         view->start_time = time(NULL);
2645 static bool
2646 prepare_update(struct view *view, const char *argv[], const char *dir,
2647                enum format_flags flags)
2649         if (view->pipe)
2650                 end_update(view, TRUE);
2651         return init_io_rd(&view->io, argv, dir, flags);
2654 static bool
2655 prepare_update_file(struct view *view, const char *name)
2657         if (view->pipe)
2658                 end_update(view, TRUE);
2659         return io_open(&view->io, name);
2662 static bool
2663 begin_update(struct view *view, bool refresh)
2665         if (view->pipe)
2666                 end_update(view, TRUE);
2668         if (refresh) {
2669                 if (!start_io(&view->io))
2670                         return FALSE;
2672         } else {
2673                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2674                         opt_path[0] = 0;
2676                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2677                         return FALSE;
2679                 /* Put the current ref_* value to the view title ref
2680                  * member. This is needed by the blob view. Most other
2681                  * views sets it automatically after loading because the
2682                  * first line is a commit line. */
2683                 string_copy_rev(view->ref, view->id);
2684         }
2686         setup_update(view, view->id);
2688         return TRUE;
2691 #define ITEM_CHUNK_SIZE 256
2692 static void *
2693 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2695         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2696         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2698         if (mem == NULL || num_chunks != num_chunks_new) {
2699                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2700                 mem = realloc(mem, *size * item_size);
2701         }
2703         return mem;
2706 static struct line *
2707 realloc_lines(struct view *view, size_t line_size)
2709         size_t alloc = view->line_alloc;
2710         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2711                                          sizeof(*view->line));
2713         if (!tmp)
2714                 return NULL;
2716         view->line = tmp;
2717         view->line_alloc = alloc;
2718         return view->line;
2721 static bool
2722 update_view(struct view *view)
2724         char out_buffer[BUFSIZ * 2];
2725         char *line;
2726         /* Clear the view and redraw everything since the tree sorting
2727          * might have rearranged things. */
2728         bool redraw = view->lines == 0;
2729         bool can_read = TRUE;
2731         if (!view->pipe)
2732                 return TRUE;
2734         if (!io_can_read(view->pipe)) {
2735                 if (view->lines == 0) {
2736                         time_t secs = time(NULL) - view->start_time;
2738                         if (secs > 1 && secs > view->update_secs) {
2739                                 if (view->update_secs == 0)
2740                                         redraw_view(view);
2741                                 update_view_title(view);
2742                                 view->update_secs = secs;
2743                         }
2744                 }
2745                 return TRUE;
2746         }
2748         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2749                 if (opt_iconv != ICONV_NONE) {
2750                         ICONV_CONST char *inbuf = line;
2751                         size_t inlen = strlen(line) + 1;
2753                         char *outbuf = out_buffer;
2754                         size_t outlen = sizeof(out_buffer);
2756                         size_t ret;
2758                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2759                         if (ret != (size_t) -1)
2760                                 line = out_buffer;
2761                 }
2763                 if (!view->ops->read(view, line)) {
2764                         report("Allocation failure");
2765                         end_update(view, TRUE);
2766                         return FALSE;
2767                 }
2768         }
2770         {
2771                 unsigned long lines = view->lines;
2772                 int digits;
2774                 for (digits = 0; lines; digits++)
2775                         lines /= 10;
2777                 /* Keep the displayed view in sync with line number scaling. */
2778                 if (digits != view->digits) {
2779                         view->digits = digits;
2780                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2781                                 redraw = TRUE;
2782                 }
2783         }
2785         if (io_error(view->pipe)) {
2786                 report("Failed to read: %s", io_strerror(view->pipe));
2787                 end_update(view, TRUE);
2789         } else if (io_eof(view->pipe)) {
2790                 report("");
2791                 end_update(view, FALSE);
2792         }
2794         if (restore_view_position(view))
2795                 redraw = TRUE;
2797         if (!view_is_displayed(view))
2798                 return TRUE;
2800         if (redraw)
2801                 redraw_view_from(view, 0);
2802         else
2803                 redraw_view_dirty(view);
2805         /* Update the title _after_ the redraw so that if the redraw picks up a
2806          * commit reference in view->ref it'll be available here. */
2807         update_view_title(view);
2808         return TRUE;
2811 static struct line *
2812 add_line_data(struct view *view, void *data, enum line_type type)
2814         struct line *line;
2816         if (!realloc_lines(view, view->lines + 1))
2817                 return NULL;
2819         line = &view->line[view->lines++];
2820         memset(line, 0, sizeof(*line));
2821         line->type = type;
2822         line->data = data;
2823         line->dirty = 1;
2825         return line;
2828 static struct line *
2829 add_line_text(struct view *view, const char *text, enum line_type type)
2831         char *data = text ? strdup(text) : NULL;
2833         return data ? add_line_data(view, data, type) : NULL;
2836 static struct line *
2837 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2839         char buf[SIZEOF_STR];
2840         va_list args;
2842         va_start(args, fmt);
2843         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2844                 buf[0] = 0;
2845         va_end(args);
2847         return buf[0] ? add_line_text(view, buf, type) : NULL;
2850 /*
2851  * View opening
2852  */
2854 enum open_flags {
2855         OPEN_DEFAULT = 0,       /* Use default view switching. */
2856         OPEN_SPLIT = 1,         /* Split current view. */
2857         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2858         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2859         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2860         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2861         OPEN_PREPARED = 32,     /* Open already prepared command. */
2862 };
2864 static void
2865 open_view(struct view *prev, enum request request, enum open_flags flags)
2867         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2868         bool split = !!(flags & OPEN_SPLIT);
2869         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2870         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2871         struct view *view = VIEW(request);
2872         int nviews = displayed_views();
2873         struct view *base_view = display[0];
2875         if (view == prev && nviews == 1 && !reload) {
2876                 report("Already in %s view", view->name);
2877                 return;
2878         }
2880         if (view->git_dir && !opt_git_dir[0]) {
2881                 report("The %s view is disabled in pager view", view->name);
2882                 return;
2883         }
2885         if (split) {
2886                 display[1] = view;
2887                 if (!backgrounded)
2888                         current_view = 1;
2889         } else if (!nomaximize) {
2890                 /* Maximize the current view. */
2891                 memset(display, 0, sizeof(display));
2892                 current_view = 0;
2893                 display[current_view] = view;
2894         }
2896         /* Resize the view when switching between split- and full-screen,
2897          * or when switching between two different full-screen views. */
2898         if (nviews != displayed_views() ||
2899             (nviews == 1 && base_view != display[0]))
2900                 resize_display();
2902         if (view->ops->open) {
2903                 if (view->pipe)
2904                         end_update(view, TRUE);
2905                 if (!view->ops->open(view)) {
2906                         report("Failed to load %s view", view->name);
2907                         return;
2908                 }
2909                 restore_view_position(view);
2911         } else if ((reload || strcmp(view->vid, view->id)) &&
2912                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2913                 report("Failed to load %s view", view->name);
2914                 return;
2915         }
2917         if (split && prev->lineno - prev->offset >= prev->height) {
2918                 /* Take the title line into account. */
2919                 int lines = prev->lineno - prev->offset - prev->height + 1;
2921                 /* Scroll the view that was split if the current line is
2922                  * outside the new limited view. */
2923                 do_scroll_view(prev, lines);
2924         }
2926         if (prev && view != prev) {
2927                 if (split && !backgrounded) {
2928                         /* "Blur" the previous view. */
2929                         update_view_title(prev);
2930                 }
2932                 view->parent = prev;
2933         }
2935         if (view->pipe && view->lines == 0) {
2936                 /* Clear the old view and let the incremental updating refill
2937                  * the screen. */
2938                 werase(view->win);
2939                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2940                 report("");
2941         } else if (view_is_displayed(view)) {
2942                 redraw_view(view);
2943                 report("");
2944         }
2946         /* If the view is backgrounded the above calls to report()
2947          * won't redraw the view title. */
2948         if (backgrounded)
2949                 update_view_title(view);
2952 static void
2953 open_external_viewer(const char *argv[], const char *dir)
2955         def_prog_mode();           /* save current tty modes */
2956         endwin();                  /* restore original tty modes */
2957         run_io_fg(argv, dir);
2958         fprintf(stderr, "Press Enter to continue");
2959         getc(opt_tty);
2960         reset_prog_mode();
2961         redraw_display(TRUE);
2964 static void
2965 open_mergetool(const char *file)
2967         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2969         open_external_viewer(mergetool_argv, opt_cdup);
2972 static void
2973 open_editor(bool from_root, const char *file)
2975         const char *editor_argv[] = { "vi", file, NULL };
2976         const char *editor;
2978         editor = getenv("GIT_EDITOR");
2979         if (!editor && *opt_editor)
2980                 editor = opt_editor;
2981         if (!editor)
2982                 editor = getenv("VISUAL");
2983         if (!editor)
2984                 editor = getenv("EDITOR");
2985         if (!editor)
2986                 editor = "vi";
2988         editor_argv[0] = editor;
2989         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2992 static void
2993 open_run_request(enum request request)
2995         struct run_request *req = get_run_request(request);
2996         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2998         if (!req) {
2999                 report("Unknown run request");
3000                 return;
3001         }
3003         if (format_argv(argv, req->argv, FORMAT_ALL))
3004                 open_external_viewer(argv, NULL);
3005         free_argv(argv);
3008 /*
3009  * User request switch noodle
3010  */
3012 static int
3013 view_driver(struct view *view, enum request request)
3015         int i;
3017         if (request == REQ_NONE) {
3018                 doupdate();
3019                 return TRUE;
3020         }
3022         if (request > REQ_NONE) {
3023                 open_run_request(request);
3024                 /* FIXME: When all views can refresh always do this. */
3025                 if (view == VIEW(REQ_VIEW_STATUS) ||
3026                     view == VIEW(REQ_VIEW_MAIN) ||
3027                     view == VIEW(REQ_VIEW_LOG) ||
3028                     view == VIEW(REQ_VIEW_STAGE))
3029                         request = REQ_REFRESH;
3030                 else
3031                         return TRUE;
3032         }
3034         if (view && view->lines) {
3035                 request = view->ops->request(view, request, &view->line[view->lineno]);
3036                 if (request == REQ_NONE)
3037                         return TRUE;
3038         }
3040         switch (request) {
3041         case REQ_MOVE_UP:
3042         case REQ_MOVE_DOWN:
3043         case REQ_MOVE_PAGE_UP:
3044         case REQ_MOVE_PAGE_DOWN:
3045         case REQ_MOVE_FIRST_LINE:
3046         case REQ_MOVE_LAST_LINE:
3047                 move_view(view, request);
3048                 break;
3050         case REQ_SCROLL_LINE_DOWN:
3051         case REQ_SCROLL_LINE_UP:
3052         case REQ_SCROLL_PAGE_DOWN:
3053         case REQ_SCROLL_PAGE_UP:
3054                 scroll_view(view, request);
3055                 break;
3057         case REQ_VIEW_BLAME:
3058                 if (!opt_file[0]) {
3059                         report("No file chosen, press %s to open tree view",
3060                                get_key(REQ_VIEW_TREE));
3061                         break;
3062                 }
3063                 open_view(view, request, OPEN_DEFAULT);
3064                 break;
3066         case REQ_VIEW_BLOB:
3067                 if (!ref_blob[0]) {
3068                         report("No file chosen, press %s to open tree view",
3069                                get_key(REQ_VIEW_TREE));
3070                         break;
3071                 }
3072                 open_view(view, request, OPEN_DEFAULT);
3073                 break;
3075         case REQ_VIEW_PAGER:
3076                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3077                         report("No pager content, press %s to run command from prompt",
3078                                get_key(REQ_PROMPT));
3079                         break;
3080                 }
3081                 open_view(view, request, OPEN_DEFAULT);
3082                 break;
3084         case REQ_VIEW_STAGE:
3085                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3086                         report("No stage content, press %s to open the status view and choose file",
3087                                get_key(REQ_VIEW_STATUS));
3088                         break;
3089                 }
3090                 open_view(view, request, OPEN_DEFAULT);
3091                 break;
3093         case REQ_VIEW_STATUS:
3094                 if (opt_is_inside_work_tree == FALSE) {
3095                         report("The status view requires a working tree");
3096                         break;
3097                 }
3098                 open_view(view, request, OPEN_DEFAULT);
3099                 break;
3101         case REQ_VIEW_MAIN:
3102         case REQ_VIEW_DIFF:
3103         case REQ_VIEW_LOG:
3104         case REQ_VIEW_TREE:
3105         case REQ_VIEW_HELP:
3106                 open_view(view, request, OPEN_DEFAULT);
3107                 break;
3109         case REQ_NEXT:
3110         case REQ_PREVIOUS:
3111                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3113                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3114                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3115                    (view == VIEW(REQ_VIEW_DIFF) &&
3116                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3117                    (view == VIEW(REQ_VIEW_STAGE) &&
3118                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3119                    (view == VIEW(REQ_VIEW_BLOB) &&
3120                      view->parent == VIEW(REQ_VIEW_TREE))) {
3121                         int line;
3123                         view = view->parent;
3124                         line = view->lineno;
3125                         move_view(view, request);
3126                         if (view_is_displayed(view))
3127                                 update_view_title(view);
3128                         if (line != view->lineno)
3129                                 view->ops->request(view, REQ_ENTER,
3130                                                    &view->line[view->lineno]);
3132                 } else {
3133                         move_view(view, request);
3134                 }
3135                 break;
3137         case REQ_VIEW_NEXT:
3138         {
3139                 int nviews = displayed_views();
3140                 int next_view = (current_view + 1) % nviews;
3142                 if (next_view == current_view) {
3143                         report("Only one view is displayed");
3144                         break;
3145                 }
3147                 current_view = next_view;
3148                 /* Blur out the title of the previous view. */
3149                 update_view_title(view);
3150                 report("");
3151                 break;
3152         }
3153         case REQ_REFRESH:
3154                 report("Refreshing is not yet supported for the %s view", view->name);
3155                 break;
3157         case REQ_MAXIMIZE:
3158                 if (displayed_views() == 2)
3159                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3160                 break;
3162         case REQ_TOGGLE_LINENO:
3163                 toggle_view_option(&opt_line_number, "line numbers");
3164                 break;
3166         case REQ_TOGGLE_DATE:
3167                 toggle_view_option(&opt_date, "date display");
3168                 break;
3170         case REQ_TOGGLE_AUTHOR:
3171                 toggle_view_option(&opt_author, "author display");
3172                 break;
3174         case REQ_TOGGLE_REV_GRAPH:
3175                 toggle_view_option(&opt_rev_graph, "revision graph display");
3176                 break;
3178         case REQ_TOGGLE_REFS:
3179                 toggle_view_option(&opt_show_refs, "reference display");
3180                 break;
3182         case REQ_SEARCH:
3183         case REQ_SEARCH_BACK:
3184                 search_view(view, request);
3185                 break;
3187         case REQ_FIND_NEXT:
3188         case REQ_FIND_PREV:
3189                 find_next(view, request);
3190                 break;
3192         case REQ_STOP_LOADING:
3193                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3194                         view = &views[i];
3195                         if (view->pipe)
3196                                 report("Stopped loading the %s view", view->name),
3197                         end_update(view, TRUE);
3198                 }
3199                 break;
3201         case REQ_SHOW_VERSION:
3202                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3203                 return TRUE;
3205         case REQ_SCREEN_REDRAW:
3206                 redraw_display(TRUE);
3207                 break;
3209         case REQ_EDIT:
3210                 report("Nothing to edit");
3211                 break;
3213         case REQ_ENTER:
3214                 report("Nothing to enter");
3215                 break;
3217         case REQ_VIEW_CLOSE:
3218                 /* XXX: Mark closed views by letting view->parent point to the
3219                  * view itself. Parents to closed view should never be
3220                  * followed. */
3221                 if (view->parent &&
3222                     view->parent->parent != view->parent) {
3223                         memset(display, 0, sizeof(display));
3224                         current_view = 0;
3225                         display[current_view] = view->parent;
3226                         view->parent = view;
3227                         resize_display();
3228                         redraw_display(FALSE);
3229                         report("");
3230                         break;
3231                 }
3232                 /* Fall-through */
3233         case REQ_QUIT:
3234                 return FALSE;
3236         default:
3237                 report("Unknown key, press 'h' for help");
3238                 return TRUE;
3239         }
3241         return TRUE;
3245 /*
3246  * View backend utilities
3247  */
3249 /* Parse author lines where the name may be empty:
3250  *      author  <email@address.tld> 1138474660 +0100
3251  */
3252 static void
3253 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3255         char *nameend = strchr(ident, '<');
3256         char *emailend = strchr(ident, '>');
3258         if (nameend && emailend)
3259                 *nameend = *emailend = 0;
3260         ident = chomp_string(ident);
3261         if (!*ident) {
3262                 if (nameend)
3263                         ident = chomp_string(nameend + 1);
3264                 if (!*ident)
3265                         ident = "Unknown";
3266         }
3268         string_ncopy_do(author, authorsize, ident, strlen(ident));
3270         /* Parse epoch and timezone */
3271         if (emailend && emailend[1] == ' ') {
3272                 char *secs = emailend + 2;
3273                 char *zone = strchr(secs, ' ');
3274                 time_t time = (time_t) atol(secs);
3276                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3277                         long tz;
3279                         zone++;
3280                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3281                         tz += ('0' - zone[2]) * 60 * 60;
3282                         tz += ('0' - zone[3]) * 60;
3283                         tz += ('0' - zone[4]) * 60;
3285                         if (zone[0] == '-')
3286                                 tz = -tz;
3288                         time -= tz;
3289                 }
3291                 gmtime_r(&time, tm);
3292         }
3295 static enum input_status
3296 select_commit_parent_handler(void *data, char *buf, int c)
3298         size_t parents = *(size_t *) data;
3299         int parent = 0;
3301         if (!isdigit(c))
3302                 return INPUT_SKIP;
3304         if (*buf)
3305                 parent = atoi(buf) * 10;
3306         parent += c - '0';
3308         if (parent > parents)
3309                 return INPUT_SKIP;
3310         return INPUT_OK;
3313 static bool
3314 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3316         char buf[SIZEOF_STR * 4];
3317         const char *revlist_argv[] = {
3318                 "git", "rev-list", "-1", "--parents", id, NULL
3319         };
3320         int parents;
3322         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3323             !*chomp_string(buf) ||
3324             (parents = (strlen(buf) / 40) - 1) < 0) {
3325                 report("Failed to get parent information");
3326                 return FALSE;
3328         } else if (parents == 0) {
3329                 report("The selected commit has no parents");
3330                 return FALSE;
3331         }
3333         if (parents > 1) {
3334                 char prompt[SIZEOF_STR];
3335                 char *result;
3337                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3338                         return FALSE;
3339                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3340                 if (!result)
3341                         return FALSE;
3342                 parents = atoi(result);
3343         }
3345         string_copy_rev(rev, &buf[41 * parents]);
3346         return TRUE;
3349 /*
3350  * Pager backend
3351  */
3353 static bool
3354 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3356         char *text = line->data;
3358         if (opt_line_number && draw_lineno(view, lineno))
3359                 return TRUE;
3361         draw_text(view, line->type, text, TRUE);
3362         return TRUE;
3365 static bool
3366 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3368         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3369         char refbuf[SIZEOF_STR];
3370         char *ref = NULL;
3372         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3373                 ref = chomp_string(refbuf);
3375         if (!ref || !*ref)
3376                 return TRUE;
3378         /* This is the only fatal call, since it can "corrupt" the buffer. */
3379         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3380                 return FALSE;
3382         return TRUE;
3385 static void
3386 add_pager_refs(struct view *view, struct line *line)
3388         char buf[SIZEOF_STR];
3389         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3390         struct ref **refs;
3391         size_t bufpos = 0, refpos = 0;
3392         const char *sep = "Refs: ";
3393         bool is_tag = FALSE;
3395         assert(line->type == LINE_COMMIT);
3397         refs = get_refs(commit_id);
3398         if (!refs) {
3399                 if (view == VIEW(REQ_VIEW_DIFF))
3400                         goto try_add_describe_ref;
3401                 return;
3402         }
3404         do {
3405                 struct ref *ref = refs[refpos];
3406                 const char *fmt = ref->tag    ? "%s[%s]" :
3407                                   ref->remote ? "%s<%s>" : "%s%s";
3409                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3410                         return;
3411                 sep = ", ";
3412                 if (ref->tag)
3413                         is_tag = TRUE;
3414         } while (refs[refpos++]->next);
3416         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3417 try_add_describe_ref:
3418                 /* Add <tag>-g<commit_id> "fake" reference. */
3419                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3420                         return;
3421         }
3423         if (bufpos == 0)
3424                 return;
3426         add_line_text(view, buf, LINE_PP_REFS);
3429 static bool
3430 pager_read(struct view *view, char *data)
3432         struct line *line;
3434         if (!data)
3435                 return TRUE;
3437         line = add_line_text(view, data, get_line_type(data));
3438         if (!line)
3439                 return FALSE;
3441         if (line->type == LINE_COMMIT &&
3442             (view == VIEW(REQ_VIEW_DIFF) ||
3443              view == VIEW(REQ_VIEW_LOG)))
3444                 add_pager_refs(view, line);
3446         return TRUE;
3449 static enum request
3450 pager_request(struct view *view, enum request request, struct line *line)
3452         int split = 0;
3454         if (request != REQ_ENTER)
3455                 return request;
3457         if (line->type == LINE_COMMIT &&
3458            (view == VIEW(REQ_VIEW_LOG) ||
3459             view == VIEW(REQ_VIEW_PAGER))) {
3460                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3461                 split = 1;
3462         }
3464         /* Always scroll the view even if it was split. That way
3465          * you can use Enter to scroll through the log view and
3466          * split open each commit diff. */
3467         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3469         /* FIXME: A minor workaround. Scrolling the view will call report("")
3470          * but if we are scrolling a non-current view this won't properly
3471          * update the view title. */
3472         if (split)
3473                 update_view_title(view);
3475         return REQ_NONE;
3478 static bool
3479 pager_grep(struct view *view, struct line *line)
3481         regmatch_t pmatch;
3482         char *text = line->data;
3484         if (!*text)
3485                 return FALSE;
3487         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3488                 return FALSE;
3490         return TRUE;
3493 static void
3494 pager_select(struct view *view, struct line *line)
3496         if (line->type == LINE_COMMIT) {
3497                 char *text = (char *)line->data + STRING_SIZE("commit ");
3499                 if (view != VIEW(REQ_VIEW_PAGER))
3500                         string_copy_rev(view->ref, text);
3501                 string_copy_rev(ref_commit, text);
3502         }
3505 static struct view_ops pager_ops = {
3506         "line",
3507         NULL,
3508         NULL,
3509         pager_read,
3510         pager_draw,
3511         pager_request,
3512         pager_grep,
3513         pager_select,
3514 };
3516 static const char *log_argv[SIZEOF_ARG] = {
3517         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3518 };
3520 static enum request
3521 log_request(struct view *view, enum request request, struct line *line)
3523         switch (request) {
3524         case REQ_REFRESH:
3525                 load_refs();
3526                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3527                 return REQ_NONE;
3528         default:
3529                 return pager_request(view, request, line);
3530         }
3533 static struct view_ops log_ops = {
3534         "line",
3535         log_argv,
3536         NULL,
3537         pager_read,
3538         pager_draw,
3539         log_request,
3540         pager_grep,
3541         pager_select,
3542 };
3544 static const char *diff_argv[SIZEOF_ARG] = {
3545         "git", "show", "--pretty=fuller", "--no-color", "--root",
3546                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3547 };
3549 static struct view_ops diff_ops = {
3550         "line",
3551         diff_argv,
3552         NULL,
3553         pager_read,
3554         pager_draw,
3555         pager_request,
3556         pager_grep,
3557         pager_select,
3558 };
3560 /*
3561  * Help backend
3562  */
3564 static bool
3565 help_open(struct view *view)
3567         char buf[SIZEOF_STR];
3568         size_t bufpos;
3569         int i;
3571         if (view->lines > 0)
3572                 return TRUE;
3574         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3576         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3577                 const char *key;
3579                 if (req_info[i].request == REQ_NONE)
3580                         continue;
3582                 if (!req_info[i].request) {
3583                         add_line_text(view, "", LINE_DEFAULT);
3584                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3585                         continue;
3586                 }
3588                 key = get_key(req_info[i].request);
3589                 if (!*key)
3590                         key = "(no key defined)";
3592                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3593                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3594                         if (buf[bufpos] == '_')
3595                                 buf[bufpos] = '-';
3596                 }
3598                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3599                                 key, buf, req_info[i].help);
3600         }
3602         if (run_requests) {
3603                 add_line_text(view, "", LINE_DEFAULT);
3604                 add_line_text(view, "External commands:", LINE_DEFAULT);
3605         }
3607         for (i = 0; i < run_requests; i++) {
3608                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3609                 const char *key;
3610                 int argc;
3612                 if (!req)
3613                         continue;
3615                 key = get_key_name(req->key);
3616                 if (!*key)
3617                         key = "(no key defined)";
3619                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3620                         if (!string_format_from(buf, &bufpos, "%s%s",
3621                                                 argc ? " " : "", req->argv[argc]))
3622                                 return REQ_NONE;
3624                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3625                                 keymap_table[req->keymap].name, key, buf);
3626         }
3628         return TRUE;
3631 static struct view_ops help_ops = {
3632         "line",
3633         NULL,
3634         help_open,
3635         NULL,
3636         pager_draw,
3637         pager_request,
3638         pager_grep,
3639         pager_select,
3640 };
3643 /*
3644  * Tree backend
3645  */
3647 struct tree_stack_entry {
3648         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3649         unsigned long lineno;           /* Line number to restore */
3650         char *name;                     /* Position of name in opt_path */
3651 };
3653 /* The top of the path stack. */
3654 static struct tree_stack_entry *tree_stack = NULL;
3655 unsigned long tree_lineno = 0;
3657 static void
3658 pop_tree_stack_entry(void)
3660         struct tree_stack_entry *entry = tree_stack;
3662         tree_lineno = entry->lineno;
3663         entry->name[0] = 0;
3664         tree_stack = entry->prev;
3665         free(entry);
3668 static void
3669 push_tree_stack_entry(const char *name, unsigned long lineno)
3671         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3672         size_t pathlen = strlen(opt_path);
3674         if (!entry)
3675                 return;
3677         entry->prev = tree_stack;
3678         entry->name = opt_path + pathlen;
3679         tree_stack = entry;
3681         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3682                 pop_tree_stack_entry();
3683                 return;
3684         }
3686         /* Move the current line to the first tree entry. */
3687         tree_lineno = 1;
3688         entry->lineno = lineno;
3691 /* Parse output from git-ls-tree(1):
3692  *
3693  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3694  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3695  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3696  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3697  */
3699 #define SIZEOF_TREE_ATTR \
3700         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3702 #define SIZEOF_TREE_MODE \
3703         STRING_SIZE("100644 ")
3705 #define TREE_ID_OFFSET \
3706         STRING_SIZE("100644 blob ")
3708 struct tree_entry {
3709         char id[SIZEOF_REV];
3710         mode_t mode;
3711         struct tm time;                 /* Date from the author ident. */
3712         char author[75];                /* Author of the commit. */
3713         char name[1];
3714 };
3716 static const char *
3717 tree_path(struct line *line)
3719         return ((struct tree_entry *) line->data)->name;
3723 static int
3724 tree_compare_entry(struct line *line1, struct line *line2)
3726         if (line1->type != line2->type)
3727                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3728         return strcmp(tree_path(line1), tree_path(line2));
3731 static struct line *
3732 tree_entry(struct view *view, enum line_type type, const char *path,
3733            const char *mode, const char *id)
3735         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3736         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3738         if (!entry || !line) {
3739                 free(entry);
3740                 return NULL;
3741         }
3743         strncpy(entry->name, path, strlen(path));
3744         if (mode)
3745                 entry->mode = strtoul(mode, NULL, 8);
3746         if (id)
3747                 string_copy_rev(entry->id, id);
3749         return line;
3752 static bool
3753 tree_read_date(struct view *view, char *text, bool *read_date)
3755         static char author_name[SIZEOF_STR];
3756         static struct tm author_time;
3758         if (!text && *read_date) {
3759                 *read_date = FALSE;
3760                 return TRUE;
3762         } else if (!text) {
3763                 char *path = *opt_path ? opt_path : ".";
3764                 /* Find next entry to process */
3765                 const char *log_file[] = {
3766                         "git", "log", "--no-color", "--pretty=raw",
3767                                 "--cc", "--raw", view->id, "--", path, NULL
3768                 };
3769                 struct io io = {};
3771                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3772                         report("Failed to load tree data");
3773                         return TRUE;
3774                 }
3776                 done_io(view->pipe);
3777                 view->io = io;
3778                 *read_date = TRUE;
3779                 return FALSE;
3781         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3782                 parse_author_line(text + STRING_SIZE("author "),
3783                                   author_name, sizeof(author_name), &author_time);
3785         } else if (*text == ':') {
3786                 char *pos;
3787                 size_t annotated = 1;
3788                 size_t i;
3790                 pos = strchr(text, '\t');
3791                 if (!pos)
3792                         return TRUE;
3793                 text = pos + 1;
3794                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3795                         text += strlen(opt_prefix);
3796                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3797                         text += strlen(opt_path);
3798                 pos = strchr(text, '/');
3799                 if (pos)
3800                         *pos = 0;
3802                 for (i = 1; i < view->lines; i++) {
3803                         struct line *line = &view->line[i];
3804                         struct tree_entry *entry = line->data;
3806                         annotated += !!*entry->author;
3807                         if (*entry->author || strcmp(entry->name, text))
3808                                 continue;
3810                         string_copy(entry->author, author_name);
3811                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3812                         line->dirty = 1;
3813                         break;
3814                 }
3816                 if (annotated == view->lines)
3817                         kill_io(view->pipe);
3818         }
3819         return TRUE;
3822 static bool
3823 tree_read(struct view *view, char *text)
3825         static bool read_date = FALSE;
3826         struct tree_entry *data;
3827         struct line *entry, *line;
3828         enum line_type type;
3829         size_t textlen = text ? strlen(text) : 0;
3830         char *path = text + SIZEOF_TREE_ATTR;
3832         if (read_date || !text)
3833                 return tree_read_date(view, text, &read_date);
3835         if (textlen <= SIZEOF_TREE_ATTR)
3836                 return FALSE;
3837         if (view->lines == 0 &&
3838             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3839                 return FALSE;
3841         /* Strip the path part ... */
3842         if (*opt_path) {
3843                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3844                 size_t striplen = strlen(opt_path);
3846                 if (pathlen > striplen)
3847                         memmove(path, path + striplen,
3848                                 pathlen - striplen + 1);
3850                 /* Insert "link" to parent directory. */
3851                 if (view->lines == 1 &&
3852                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3853                         return FALSE;
3854         }
3856         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3857         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3858         if (!entry)
3859                 return FALSE;
3860         data = entry->data;
3862         /* Skip "Directory ..." and ".." line. */
3863         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3864                 if (tree_compare_entry(line, entry) <= 0)
3865                         continue;
3867                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3869                 line->data = data;
3870                 line->type = type;
3871                 for (; line <= entry; line++)
3872                         line->dirty = line->cleareol = 1;
3873                 return TRUE;
3874         }
3876         if (tree_lineno > view->lineno) {
3877                 view->lineno = tree_lineno;
3878                 tree_lineno = 0;
3879         }
3881         return TRUE;
3884 static bool
3885 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3887         struct tree_entry *entry = line->data;
3889         if (line->type == LINE_TREE_PARENT) {
3890                 if (draw_text(view, line->type, "Directory path /", TRUE))
3891                         return TRUE;
3892         } else {
3893                 char mode[11] = "-r--r--r--";
3895                 if (S_ISDIR(entry->mode)) {
3896                         mode[3] = mode[6] = mode[9] = 'x';
3897                         mode[0] = 'd';
3898                 }
3899                 if (S_ISLNK(entry->mode))
3900                         mode[0] = 'l';
3901                 if (entry->mode & S_IWUSR)
3902                         mode[2] = 'w';
3903                 if (entry->mode & S_IXUSR)
3904                         mode[3] = 'x';
3905                 if (entry->mode & S_IXGRP)
3906                         mode[6] = 'x';
3907                 if (entry->mode & S_IXOTH)
3908                         mode[9] = 'x';
3909                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3910                         return TRUE;
3912                 if (opt_author && draw_author(view, entry->author))
3913                         return TRUE;
3915                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3916                         return TRUE;
3917         }
3918         if (draw_text(view, line->type, entry->name, TRUE))
3919                 return TRUE;
3920         return TRUE;
3923 static void
3924 open_blob_editor()
3926         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3927         int fd = mkstemp(file);
3929         if (fd == -1)
3930                 report("Failed to create temporary file");
3931         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3932                 report("Failed to save blob data to file");
3933         else
3934                 open_editor(FALSE, file);
3935         if (fd != -1)
3936                 unlink(file);
3939 static enum request
3940 tree_request(struct view *view, enum request request, struct line *line)
3942         enum open_flags flags;
3944         switch (request) {
3945         case REQ_VIEW_BLAME:
3946                 if (line->type != LINE_TREE_FILE) {
3947                         report("Blame only supported for files");
3948                         return REQ_NONE;
3949                 }
3951                 string_copy(opt_ref, view->vid);
3952                 return request;
3954         case REQ_EDIT:
3955                 if (line->type != LINE_TREE_FILE) {
3956                         report("Edit only supported for files");
3957                 } else if (!is_head_commit(view->vid)) {
3958                         open_blob_editor();
3959                 } else {
3960                         open_editor(TRUE, opt_file);
3961                 }
3962                 return REQ_NONE;
3964         case REQ_PARENT:
3965                 if (!*opt_path) {
3966                         /* quit view if at top of tree */
3967                         return REQ_VIEW_CLOSE;
3968                 }
3969                 /* fake 'cd  ..' */
3970                 line = &view->line[1];
3971                 break;
3973         case REQ_ENTER:
3974                 break;
3976         default:
3977                 return request;
3978         }
3980         /* Cleanup the stack if the tree view is at a different tree. */
3981         while (!*opt_path && tree_stack)
3982                 pop_tree_stack_entry();
3984         switch (line->type) {
3985         case LINE_TREE_DIR:
3986                 /* Depending on whether it is a subdir or parent (updir?) link
3987                  * mangle the path buffer. */
3988                 if (line == &view->line[1] && *opt_path) {
3989                         pop_tree_stack_entry();
3991                 } else {
3992                         const char *basename = tree_path(line);
3994                         push_tree_stack_entry(basename, view->lineno);
3995                 }
3997                 /* Trees and subtrees share the same ID, so they are not not
3998                  * unique like blobs. */
3999                 flags = OPEN_RELOAD;
4000                 request = REQ_VIEW_TREE;
4001                 break;
4003         case LINE_TREE_FILE:
4004                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4005                 request = REQ_VIEW_BLOB;
4006                 break;
4008         default:
4009                 return REQ_NONE;
4010         }
4012         open_view(view, request, flags);
4013         if (request == REQ_VIEW_TREE)
4014                 view->lineno = tree_lineno;
4016         return REQ_NONE;
4019 static void
4020 tree_select(struct view *view, struct line *line)
4022         struct tree_entry *entry = line->data;
4024         if (line->type == LINE_TREE_FILE) {
4025                 string_copy_rev(ref_blob, entry->id);
4026                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4028         } else if (line->type != LINE_TREE_DIR) {
4029                 return;
4030         }
4032         string_copy_rev(view->ref, entry->id);
4035 static const char *tree_argv[SIZEOF_ARG] = {
4036         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4037 };
4039 static struct view_ops tree_ops = {
4040         "file",
4041         tree_argv,
4042         NULL,
4043         tree_read,
4044         tree_draw,
4045         tree_request,
4046         pager_grep,
4047         tree_select,
4048 };
4050 static bool
4051 blob_read(struct view *view, char *line)
4053         if (!line)
4054                 return TRUE;
4055         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4058 static enum request
4059 blob_request(struct view *view, enum request request, struct line *line)
4061         switch (request) {
4062         case REQ_EDIT:
4063                 open_blob_editor();
4064                 return REQ_NONE;
4065         default:
4066                 return pager_request(view, request, line);
4067         }
4070 static const char *blob_argv[SIZEOF_ARG] = {
4071         "git", "cat-file", "blob", "%(blob)", NULL
4072 };
4074 static struct view_ops blob_ops = {
4075         "line",
4076         blob_argv,
4077         NULL,
4078         blob_read,
4079         pager_draw,
4080         blob_request,
4081         pager_grep,
4082         pager_select,
4083 };
4085 /*
4086  * Blame backend
4087  *
4088  * Loading the blame view is a two phase job:
4089  *
4090  *  1. File content is read either using opt_file from the
4091  *     filesystem or using git-cat-file.
4092  *  2. Then blame information is incrementally added by
4093  *     reading output from git-blame.
4094  */
4096 static const char *blame_head_argv[] = {
4097         "git", "blame", "--incremental", "--", "%(file)", NULL
4098 };
4100 static const char *blame_ref_argv[] = {
4101         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4102 };
4104 static const char *blame_cat_file_argv[] = {
4105         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4106 };
4108 struct blame_commit {
4109         char id[SIZEOF_REV];            /* SHA1 ID. */
4110         char title[128];                /* First line of the commit message. */
4111         char author[75];                /* Author of the commit. */
4112         struct tm time;                 /* Date from the author ident. */
4113         char filename[128];             /* Name of file. */
4114         bool has_previous;              /* Was a "previous" line detected. */
4115 };
4117 struct blame {
4118         struct blame_commit *commit;
4119         char text[1];
4120 };
4122 static bool
4123 blame_open(struct view *view)
4125         if (*opt_ref || !io_open(&view->io, opt_file)) {
4126                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4127                         return FALSE;
4128         }
4130         setup_update(view, opt_file);
4131         string_format(view->ref, "%s ...", opt_file);
4133         return TRUE;
4136 static struct blame_commit *
4137 get_blame_commit(struct view *view, const char *id)
4139         size_t i;
4141         for (i = 0; i < view->lines; i++) {
4142                 struct blame *blame = view->line[i].data;
4144                 if (!blame->commit)
4145                         continue;
4147                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4148                         return blame->commit;
4149         }
4151         {
4152                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4154                 if (commit)
4155                         string_ncopy(commit->id, id, SIZEOF_REV);
4156                 return commit;
4157         }
4160 static bool
4161 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4163         const char *pos = *posref;
4165         *posref = NULL;
4166         pos = strchr(pos + 1, ' ');
4167         if (!pos || !isdigit(pos[1]))
4168                 return FALSE;
4169         *number = atoi(pos + 1);
4170         if (*number < min || *number > max)
4171                 return FALSE;
4173         *posref = pos;
4174         return TRUE;
4177 static struct blame_commit *
4178 parse_blame_commit(struct view *view, const char *text, int *blamed)
4180         struct blame_commit *commit;
4181         struct blame *blame;
4182         const char *pos = text + SIZEOF_REV - 1;
4183         size_t lineno;
4184         size_t group;
4186         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4187                 return NULL;
4189         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4190             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4191                 return NULL;
4193         commit = get_blame_commit(view, text);
4194         if (!commit)
4195                 return NULL;
4197         *blamed += group;
4198         while (group--) {
4199                 struct line *line = &view->line[lineno + group - 1];
4201                 blame = line->data;
4202                 blame->commit = commit;
4203                 line->dirty = 1;
4204         }
4206         return commit;
4209 static bool
4210 blame_read_file(struct view *view, const char *line, bool *read_file)
4212         if (!line) {
4213                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4214                 struct io io = {};
4216                 if (view->lines == 0 && !view->parent)
4217                         die("No blame exist for %s", view->vid);
4219                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4220                         report("Failed to load blame data");
4221                         return TRUE;
4222                 }
4224                 done_io(view->pipe);
4225                 view->io = io;
4226                 *read_file = FALSE;
4227                 return FALSE;
4229         } else {
4230                 size_t linelen = strlen(line);
4231                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4233                 blame->commit = NULL;
4234                 strncpy(blame->text, line, linelen);
4235                 blame->text[linelen] = 0;
4236                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4237         }
4240 static bool
4241 match_blame_header(const char *name, char **line)
4243         size_t namelen = strlen(name);
4244         bool matched = !strncmp(name, *line, namelen);
4246         if (matched)
4247                 *line += namelen;
4249         return matched;
4252 static bool
4253 blame_read(struct view *view, char *line)
4255         static struct blame_commit *commit = NULL;
4256         static int blamed = 0;
4257         static time_t author_time;
4258         static bool read_file = TRUE;
4260         if (read_file)
4261                 return blame_read_file(view, line, &read_file);
4263         if (!line) {
4264                 /* Reset all! */
4265                 commit = NULL;
4266                 blamed = 0;
4267                 read_file = TRUE;
4268                 string_format(view->ref, "%s", view->vid);
4269                 if (view_is_displayed(view)) {
4270                         update_view_title(view);
4271                         redraw_view_from(view, 0);
4272                 }
4273                 return TRUE;
4274         }
4276         if (!commit) {
4277                 commit = parse_blame_commit(view, line, &blamed);
4278                 string_format(view->ref, "%s %2d%%", view->vid,
4279                               view->lines ? blamed * 100 / view->lines : 0);
4281         } else if (match_blame_header("author ", &line)) {
4282                 string_ncopy(commit->author, line, strlen(line));
4284         } else if (match_blame_header("author-time ", &line)) {
4285                 author_time = (time_t) atol(line);
4287         } else if (match_blame_header("author-tz ", &line)) {
4288                 long tz;
4290                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4291                 tz += ('0' - line[2]) * 60 * 60;
4292                 tz += ('0' - line[3]) * 60;
4293                 tz += ('0' - line[4]) * 60;
4295                 if (line[0] == '-')
4296                         tz = -tz;
4298                 author_time -= tz;
4299                 gmtime_r(&author_time, &commit->time);
4301         } else if (match_blame_header("summary ", &line)) {
4302                 string_ncopy(commit->title, line, strlen(line));
4304         } else if (match_blame_header("previous ", &line)) {
4305                 commit->has_previous = TRUE;
4307         } else if (match_blame_header("filename ", &line)) {
4308                 string_ncopy(commit->filename, line, strlen(line));
4309                 commit = NULL;
4310         }
4312         return TRUE;
4315 static bool
4316 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4318         struct blame *blame = line->data;
4319         struct tm *time = NULL;
4320         const char *id = NULL, *author = NULL;
4322         if (blame->commit && *blame->commit->filename) {
4323                 id = blame->commit->id;
4324                 author = blame->commit->author;
4325                 time = &blame->commit->time;
4326         }
4328         if (opt_date && draw_date(view, time))
4329                 return TRUE;
4331         if (opt_author && draw_author(view, author))
4332                 return TRUE;
4334         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4335                 return TRUE;
4337         if (draw_lineno(view, lineno))
4338                 return TRUE;
4340         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4341         return TRUE;
4344 static bool
4345 check_blame_commit(struct blame *blame)
4347         if (!blame->commit)
4348                 report("Commit data not loaded yet");
4349         else if (!strcmp(blame->commit->id, NULL_ID))
4350                 report("No commit exist for the selected line");
4351         else
4352                 return TRUE;
4353         return FALSE;
4356 static enum request
4357 blame_request(struct view *view, enum request request, struct line *line)
4359         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4360         struct blame *blame = line->data;
4362         switch (request) {
4363         case REQ_VIEW_BLAME:
4364                 if (check_blame_commit(blame)) {
4365                         string_copy(opt_ref, blame->commit->id);
4366                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4367                 }
4368                 break;
4370         case REQ_PARENT:
4371                 if (check_blame_commit(blame) &&
4372                     select_commit_parent(blame->commit->id, opt_ref))
4373                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4374                 break;
4376         case REQ_ENTER:
4377                 if (!blame->commit) {
4378                         report("No commit loaded yet");
4379                         break;
4380                 }
4382                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4383                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4384                         break;
4386                 if (!strcmp(blame->commit->id, NULL_ID)) {
4387                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4388                         const char *diff_index_argv[] = {
4389                                 "git", "diff-index", "--root", "--patch-with-stat",
4390                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4391                         };
4393                         if (!blame->commit->has_previous) {
4394                                 diff_index_argv[1] = "diff";
4395                                 diff_index_argv[2] = "--no-color";
4396                                 diff_index_argv[6] = "--";
4397                                 diff_index_argv[7] = "/dev/null";
4398                         }
4400                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4401                                 report("Failed to allocate diff command");
4402                                 break;
4403                         }
4404                         flags |= OPEN_PREPARED;
4405                 }
4407                 open_view(view, REQ_VIEW_DIFF, flags);
4408                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4409                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4410                 break;
4412         default:
4413                 return request;
4414         }
4416         return REQ_NONE;
4419 static bool
4420 blame_grep(struct view *view, struct line *line)
4422         struct blame *blame = line->data;
4423         struct blame_commit *commit = blame->commit;
4424         regmatch_t pmatch;
4426 #define MATCH(text, on)                                                 \
4427         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4429         if (commit) {
4430                 char buf[DATE_COLS + 1];
4432                 if (MATCH(commit->title, 1) ||
4433                     MATCH(commit->author, opt_author) ||
4434                     MATCH(commit->id, opt_date))
4435                         return TRUE;
4437                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4438                     MATCH(buf, 1))
4439                         return TRUE;
4440         }
4442         return MATCH(blame->text, 1);
4444 #undef MATCH
4447 static void
4448 blame_select(struct view *view, struct line *line)
4450         struct blame *blame = line->data;
4451         struct blame_commit *commit = blame->commit;
4453         if (!commit)
4454                 return;
4456         if (!strcmp(commit->id, NULL_ID))
4457                 string_ncopy(ref_commit, "HEAD", 4);
4458         else
4459                 string_copy_rev(ref_commit, commit->id);
4462 static struct view_ops blame_ops = {
4463         "line",
4464         NULL,
4465         blame_open,
4466         blame_read,
4467         blame_draw,
4468         blame_request,
4469         blame_grep,
4470         blame_select,
4471 };
4473 /*
4474  * Status backend
4475  */
4477 struct status {
4478         char status;
4479         struct {
4480                 mode_t mode;
4481                 char rev[SIZEOF_REV];
4482                 char name[SIZEOF_STR];
4483         } old;
4484         struct {
4485                 mode_t mode;
4486                 char rev[SIZEOF_REV];
4487                 char name[SIZEOF_STR];
4488         } new;
4489 };
4491 static char status_onbranch[SIZEOF_STR];
4492 static struct status stage_status;
4493 static enum line_type stage_line_type;
4494 static size_t stage_chunks;
4495 static int *stage_chunk;
4497 /* This should work even for the "On branch" line. */
4498 static inline bool
4499 status_has_none(struct view *view, struct line *line)
4501         return line < view->line + view->lines && !line[1].data;
4504 /* Get fields from the diff line:
4505  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4506  */
4507 static inline bool
4508 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4510         const char *old_mode = buf +  1;
4511         const char *new_mode = buf +  8;
4512         const char *old_rev  = buf + 15;
4513         const char *new_rev  = buf + 56;
4514         const char *status   = buf + 97;
4516         if (bufsize < 98 ||
4517             old_mode[-1] != ':' ||
4518             new_mode[-1] != ' ' ||
4519             old_rev[-1]  != ' ' ||
4520             new_rev[-1]  != ' ' ||
4521             status[-1]   != ' ')
4522                 return FALSE;
4524         file->status = *status;
4526         string_copy_rev(file->old.rev, old_rev);
4527         string_copy_rev(file->new.rev, new_rev);
4529         file->old.mode = strtoul(old_mode, NULL, 8);
4530         file->new.mode = strtoul(new_mode, NULL, 8);
4532         file->old.name[0] = file->new.name[0] = 0;
4534         return TRUE;
4537 static bool
4538 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4540         struct status *file = NULL;
4541         struct status *unmerged = NULL;
4542         char *buf;
4543         struct io io = {};
4545         if (!run_io(&io, argv, NULL, IO_RD))
4546                 return FALSE;
4548         add_line_data(view, NULL, type);
4550         while ((buf = io_get(&io, 0, TRUE))) {
4551                 if (!file) {
4552                         file = calloc(1, sizeof(*file));
4553                         if (!file || !add_line_data(view, file, type))
4554                                 goto error_out;
4555                 }
4557                 /* Parse diff info part. */
4558                 if (status) {
4559                         file->status = status;
4560                         if (status == 'A')
4561                                 string_copy(file->old.rev, NULL_ID);
4563                 } else if (!file->status) {
4564                         if (!status_get_diff(file, buf, strlen(buf)))
4565                                 goto error_out;
4567                         buf = io_get(&io, 0, TRUE);
4568                         if (!buf)
4569                                 break;
4571                         /* Collapse all 'M'odified entries that follow a
4572                          * associated 'U'nmerged entry. */
4573                         if (file->status == 'U') {
4574                                 unmerged = file;
4576                         } else if (unmerged) {
4577                                 int collapse = !strcmp(buf, unmerged->new.name);
4579                                 unmerged = NULL;
4580                                 if (collapse) {
4581                                         free(file);
4582                                         file = NULL;
4583                                         view->lines--;
4584                                         continue;
4585                                 }
4586                         }
4587                 }
4589                 /* Grab the old name for rename/copy. */
4590                 if (!*file->old.name &&
4591                     (file->status == 'R' || file->status == 'C')) {
4592                         string_ncopy(file->old.name, buf, strlen(buf));
4594                         buf = io_get(&io, 0, TRUE);
4595                         if (!buf)
4596                                 break;
4597                 }
4599                 /* git-ls-files just delivers a NUL separated list of
4600                  * file names similar to the second half of the
4601                  * git-diff-* output. */
4602                 string_ncopy(file->new.name, buf, strlen(buf));
4603                 if (!*file->old.name)
4604                         string_copy(file->old.name, file->new.name);
4605                 file = NULL;
4606         }
4608         if (io_error(&io)) {
4609 error_out:
4610                 done_io(&io);
4611                 return FALSE;
4612         }
4614         if (!view->line[view->lines - 1].data)
4615                 add_line_data(view, NULL, LINE_STAT_NONE);
4617         done_io(&io);
4618         return TRUE;
4621 /* Don't show unmerged entries in the staged section. */
4622 static const char *status_diff_index_argv[] = {
4623         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4624                              "--cached", "-M", "HEAD", NULL
4625 };
4627 static const char *status_diff_files_argv[] = {
4628         "git", "diff-files", "-z", NULL
4629 };
4631 static const char *status_list_other_argv[] = {
4632         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4633 };
4635 static const char *status_list_no_head_argv[] = {
4636         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4637 };
4639 static const char *update_index_argv[] = {
4640         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4641 };
4643 /* Restore the previous line number to stay in the context or select a
4644  * line with something that can be updated. */
4645 static void
4646 status_restore(struct view *view)
4648         if (view->p_lineno >= view->lines)
4649                 view->p_lineno = view->lines - 1;
4650         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4651                 view->p_lineno++;
4652         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4653                 view->p_lineno--;
4655         /* If the above fails, always skip the "On branch" line. */
4656         if (view->p_lineno < view->lines)
4657                 view->lineno = view->p_lineno;
4658         else
4659                 view->lineno = 1;
4661         if (view->lineno < view->offset)
4662                 view->offset = view->lineno;
4663         else if (view->offset + view->height <= view->lineno)
4664                 view->offset = view->lineno - view->height + 1;
4666         view->p_restore = FALSE;
4669 /* First parse staged info using git-diff-index(1), then parse unstaged
4670  * info using git-diff-files(1), and finally untracked files using
4671  * git-ls-files(1). */
4672 static bool
4673 status_open(struct view *view)
4675         reset_view(view);
4677         add_line_data(view, NULL, LINE_STAT_HEAD);
4678         if (is_initial_commit())
4679                 string_copy(status_onbranch, "Initial commit");
4680         else if (!*opt_head)
4681                 string_copy(status_onbranch, "Not currently on any branch");
4682         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4683                 return FALSE;
4685         run_io_bg(update_index_argv);
4687         if (is_initial_commit()) {
4688                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4689                         return FALSE;
4690         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4691                 return FALSE;
4692         }
4694         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4695             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4696                 return FALSE;
4698         /* Restore the exact position or use the specialized restore
4699          * mode? */
4700         if (!view->p_restore)
4701                 status_restore(view);
4702         return TRUE;
4705 static bool
4706 status_draw(struct view *view, struct line *line, unsigned int lineno)
4708         struct status *status = line->data;
4709         enum line_type type;
4710         const char *text;
4712         if (!status) {
4713                 switch (line->type) {
4714                 case LINE_STAT_STAGED:
4715                         type = LINE_STAT_SECTION;
4716                         text = "Changes to be committed:";
4717                         break;
4719                 case LINE_STAT_UNSTAGED:
4720                         type = LINE_STAT_SECTION;
4721                         text = "Changed but not updated:";
4722                         break;
4724                 case LINE_STAT_UNTRACKED:
4725                         type = LINE_STAT_SECTION;
4726                         text = "Untracked files:";
4727                         break;
4729                 case LINE_STAT_NONE:
4730                         type = LINE_DEFAULT;
4731                         text = "    (no files)";
4732                         break;
4734                 case LINE_STAT_HEAD:
4735                         type = LINE_STAT_HEAD;
4736                         text = status_onbranch;
4737                         break;
4739                 default:
4740                         return FALSE;
4741                 }
4742         } else {
4743                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4745                 buf[0] = status->status;
4746                 if (draw_text(view, line->type, buf, TRUE))
4747                         return TRUE;
4748                 type = LINE_DEFAULT;
4749                 text = status->new.name;
4750         }
4752         draw_text(view, type, text, TRUE);
4753         return TRUE;
4756 static enum request
4757 status_enter(struct view *view, struct line *line)
4759         struct status *status = line->data;
4760         const char *oldpath = status ? status->old.name : NULL;
4761         /* Diffs for unmerged entries are empty when passing the new
4762          * path, so leave it empty. */
4763         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4764         const char *info;
4765         enum open_flags split;
4766         struct view *stage = VIEW(REQ_VIEW_STAGE);
4768         if (line->type == LINE_STAT_NONE ||
4769             (!status && line[1].type == LINE_STAT_NONE)) {
4770                 report("No file to diff");
4771                 return REQ_NONE;
4772         }
4774         switch (line->type) {
4775         case LINE_STAT_STAGED:
4776                 if (is_initial_commit()) {
4777                         const char *no_head_diff_argv[] = {
4778                                 "git", "diff", "--no-color", "--patch-with-stat",
4779                                         "--", "/dev/null", newpath, NULL
4780                         };
4782                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4783                                 return REQ_QUIT;
4784                 } else {
4785                         const char *index_show_argv[] = {
4786                                 "git", "diff-index", "--root", "--patch-with-stat",
4787                                         "-C", "-M", "--cached", "HEAD", "--",
4788                                         oldpath, newpath, NULL
4789                         };
4791                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4792                                 return REQ_QUIT;
4793                 }
4795                 if (status)
4796                         info = "Staged changes to %s";
4797                 else
4798                         info = "Staged changes";
4799                 break;
4801         case LINE_STAT_UNSTAGED:
4802         {
4803                 const char *files_show_argv[] = {
4804                         "git", "diff-files", "--root", "--patch-with-stat",
4805                                 "-C", "-M", "--", oldpath, newpath, NULL
4806                 };
4808                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4809                         return REQ_QUIT;
4810                 if (status)
4811                         info = "Unstaged changes to %s";
4812                 else
4813                         info = "Unstaged changes";
4814                 break;
4815         }
4816         case LINE_STAT_UNTRACKED:
4817                 if (!newpath) {
4818                         report("No file to show");
4819                         return REQ_NONE;
4820                 }
4822                 if (!suffixcmp(status->new.name, -1, "/")) {
4823                         report("Cannot display a directory");
4824                         return REQ_NONE;
4825                 }
4827                 if (!prepare_update_file(stage, newpath))
4828                         return REQ_QUIT;
4829                 info = "Untracked file %s";
4830                 break;
4832         case LINE_STAT_HEAD:
4833                 return REQ_NONE;
4835         default:
4836                 die("line type %d not handled in switch", line->type);
4837         }
4839         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4840         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4841         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4842                 if (status) {
4843                         stage_status = *status;
4844                 } else {
4845                         memset(&stage_status, 0, sizeof(stage_status));
4846                 }
4848                 stage_line_type = line->type;
4849                 stage_chunks = 0;
4850                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4851         }
4853         return REQ_NONE;
4856 static bool
4857 status_exists(struct status *status, enum line_type type)
4859         struct view *view = VIEW(REQ_VIEW_STATUS);
4860         unsigned long lineno;
4862         for (lineno = 0; lineno < view->lines; lineno++) {
4863                 struct line *line = &view->line[lineno];
4864                 struct status *pos = line->data;
4866                 if (line->type != type)
4867                         continue;
4868                 if (!pos && (!status || !status->status) && line[1].data) {
4869                         select_view_line(view, lineno);
4870                         return TRUE;
4871                 }
4872                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4873                         select_view_line(view, lineno);
4874                         return TRUE;
4875                 }
4876         }
4878         return FALSE;
4882 static bool
4883 status_update_prepare(struct io *io, enum line_type type)
4885         const char *staged_argv[] = {
4886                 "git", "update-index", "-z", "--index-info", NULL
4887         };
4888         const char *others_argv[] = {
4889                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4890         };
4892         switch (type) {
4893         case LINE_STAT_STAGED:
4894                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4896         case LINE_STAT_UNSTAGED:
4897                 return run_io(io, others_argv, opt_cdup, IO_WR);
4899         case LINE_STAT_UNTRACKED:
4900                 return run_io(io, others_argv, NULL, IO_WR);
4902         default:
4903                 die("line type %d not handled in switch", type);
4904                 return FALSE;
4905         }
4908 static bool
4909 status_update_write(struct io *io, struct status *status, enum line_type type)
4911         char buf[SIZEOF_STR];
4912         size_t bufsize = 0;
4914         switch (type) {
4915         case LINE_STAT_STAGED:
4916                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4917                                         status->old.mode,
4918                                         status->old.rev,
4919                                         status->old.name, 0))
4920                         return FALSE;
4921                 break;
4923         case LINE_STAT_UNSTAGED:
4924         case LINE_STAT_UNTRACKED:
4925                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4926                         return FALSE;
4927                 break;
4929         default:
4930                 die("line type %d not handled in switch", type);
4931         }
4933         return io_write(io, buf, bufsize);
4936 static bool
4937 status_update_file(struct status *status, enum line_type type)
4939         struct io io = {};
4940         bool result;
4942         if (!status_update_prepare(&io, type))
4943                 return FALSE;
4945         result = status_update_write(&io, status, type);
4946         done_io(&io);
4947         return result;
4950 static bool
4951 status_update_files(struct view *view, struct line *line)
4953         struct io io = {};
4954         bool result = TRUE;
4955         struct line *pos = view->line + view->lines;
4956         int files = 0;
4957         int file, done;
4959         if (!status_update_prepare(&io, line->type))
4960                 return FALSE;
4962         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4963                 files++;
4965         for (file = 0, done = 0; result && file < files; line++, file++) {
4966                 int almost_done = file * 100 / files;
4968                 if (almost_done > done) {
4969                         done = almost_done;
4970                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4971                                       file, files, done);
4972                         update_view_title(view);
4973                 }
4974                 result = status_update_write(&io, line->data, line->type);
4975         }
4977         done_io(&io);
4978         return result;
4981 static bool
4982 status_update(struct view *view)
4984         struct line *line = &view->line[view->lineno];
4986         assert(view->lines);
4988         if (!line->data) {
4989                 /* This should work even for the "On branch" line. */
4990                 if (line < view->line + view->lines && !line[1].data) {
4991                         report("Nothing to update");
4992                         return FALSE;
4993                 }
4995                 if (!status_update_files(view, line + 1)) {
4996                         report("Failed to update file status");
4997                         return FALSE;
4998                 }
5000         } else if (!status_update_file(line->data, line->type)) {
5001                 report("Failed to update file status");
5002                 return FALSE;
5003         }
5005         return TRUE;
5008 static bool
5009 status_revert(struct status *status, enum line_type type, bool has_none)
5011         if (!status || type != LINE_STAT_UNSTAGED) {
5012                 if (type == LINE_STAT_STAGED) {
5013                         report("Cannot revert changes to staged files");
5014                 } else if (type == LINE_STAT_UNTRACKED) {
5015                         report("Cannot revert changes to untracked files");
5016                 } else if (has_none) {
5017                         report("Nothing to revert");
5018                 } else {
5019                         report("Cannot revert changes to multiple files");
5020                 }
5021                 return FALSE;
5023         } else {
5024                 const char *checkout_argv[] = {
5025                         "git", "checkout", "--", status->old.name, NULL
5026                 };
5028                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5029                         return FALSE;
5030                 return run_io_fg(checkout_argv, opt_cdup);
5031         }
5034 static enum request
5035 status_request(struct view *view, enum request request, struct line *line)
5037         struct status *status = line->data;
5039         switch (request) {
5040         case REQ_STATUS_UPDATE:
5041                 if (!status_update(view))
5042                         return REQ_NONE;
5043                 break;
5045         case REQ_STATUS_REVERT:
5046                 if (!status_revert(status, line->type, status_has_none(view, line)))
5047                         return REQ_NONE;
5048                 break;
5050         case REQ_STATUS_MERGE:
5051                 if (!status || status->status != 'U') {
5052                         report("Merging only possible for files with unmerged status ('U').");
5053                         return REQ_NONE;
5054                 }
5055                 open_mergetool(status->new.name);
5056                 break;
5058         case REQ_EDIT:
5059                 if (!status)
5060                         return request;
5061                 if (status->status == 'D') {
5062                         report("File has been deleted.");
5063                         return REQ_NONE;
5064                 }
5066                 open_editor(status->status != '?', status->new.name);
5067                 break;
5069         case REQ_VIEW_BLAME:
5070                 if (status) {
5071                         string_copy(opt_file, status->new.name);
5072                         opt_ref[0] = 0;
5073                 }
5074                 return request;
5076         case REQ_ENTER:
5077                 /* After returning the status view has been split to
5078                  * show the stage view. No further reloading is
5079                  * necessary. */
5080                 status_enter(view, line);
5081                 return REQ_NONE;
5083         case REQ_REFRESH:
5084                 /* Simply reload the view. */
5085                 break;
5087         default:
5088                 return request;
5089         }
5091         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5093         return REQ_NONE;
5096 static void
5097 status_select(struct view *view, struct line *line)
5099         struct status *status = line->data;
5100         char file[SIZEOF_STR] = "all files";
5101         const char *text;
5102         const char *key;
5104         if (status && !string_format(file, "'%s'", status->new.name))
5105                 return;
5107         if (!status && line[1].type == LINE_STAT_NONE)
5108                 line++;
5110         switch (line->type) {
5111         case LINE_STAT_STAGED:
5112                 text = "Press %s to unstage %s for commit";
5113                 break;
5115         case LINE_STAT_UNSTAGED:
5116                 text = "Press %s to stage %s for commit";
5117                 break;
5119         case LINE_STAT_UNTRACKED:
5120                 text = "Press %s to stage %s for addition";
5121                 break;
5123         case LINE_STAT_HEAD:
5124         case LINE_STAT_NONE:
5125                 text = "Nothing to update";
5126                 break;
5128         default:
5129                 die("line type %d not handled in switch", line->type);
5130         }
5132         if (status && status->status == 'U') {
5133                 text = "Press %s to resolve conflict in %s";
5134                 key = get_key(REQ_STATUS_MERGE);
5136         } else {
5137                 key = get_key(REQ_STATUS_UPDATE);
5138         }
5140         string_format(view->ref, text, key, file);
5143 static bool
5144 status_grep(struct view *view, struct line *line)
5146         struct status *status = line->data;
5147         enum { S_STATUS, S_NAME, S_END } state;
5148         char buf[2] = "?";
5149         regmatch_t pmatch;
5151         if (!status)
5152                 return FALSE;
5154         for (state = S_STATUS; state < S_END; state++) {
5155                 const char *text;
5157                 switch (state) {
5158                 case S_NAME:    text = status->new.name;        break;
5159                 case S_STATUS:
5160                         buf[0] = status->status;
5161                         text = buf;
5162                         break;
5164                 default:
5165                         return FALSE;
5166                 }
5168                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5169                         return TRUE;
5170         }
5172         return FALSE;
5175 static struct view_ops status_ops = {
5176         "file",
5177         NULL,
5178         status_open,
5179         NULL,
5180         status_draw,
5181         status_request,
5182         status_grep,
5183         status_select,
5184 };
5187 static bool
5188 stage_diff_write(struct io *io, struct line *line, struct line *end)
5190         while (line < end) {
5191                 if (!io_write(io, line->data, strlen(line->data)) ||
5192                     !io_write(io, "\n", 1))
5193                         return FALSE;
5194                 line++;
5195                 if (line->type == LINE_DIFF_CHUNK ||
5196                     line->type == LINE_DIFF_HEADER)
5197                         break;
5198         }
5200         return TRUE;
5203 static struct line *
5204 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5206         for (; view->line < line; line--)
5207                 if (line->type == type)
5208                         return line;
5210         return NULL;
5213 static bool
5214 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5216         const char *apply_argv[SIZEOF_ARG] = {
5217                 "git", "apply", "--whitespace=nowarn", NULL
5218         };
5219         struct line *diff_hdr;
5220         struct io io = {};
5221         int argc = 3;
5223         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5224         if (!diff_hdr)
5225                 return FALSE;
5227         if (!revert)
5228                 apply_argv[argc++] = "--cached";
5229         if (revert || stage_line_type == LINE_STAT_STAGED)
5230                 apply_argv[argc++] = "-R";
5231         apply_argv[argc++] = "-";
5232         apply_argv[argc++] = NULL;
5233         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5234                 return FALSE;
5236         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5237             !stage_diff_write(&io, chunk, view->line + view->lines))
5238                 chunk = NULL;
5240         done_io(&io);
5241         run_io_bg(update_index_argv);
5243         return chunk ? TRUE : FALSE;
5246 static bool
5247 stage_update(struct view *view, struct line *line)
5249         struct line *chunk = NULL;
5251         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5252                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5254         if (chunk) {
5255                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5256                         report("Failed to apply chunk");
5257                         return FALSE;
5258                 }
5260         } else if (!stage_status.status) {
5261                 view = VIEW(REQ_VIEW_STATUS);
5263                 for (line = view->line; line < view->line + view->lines; line++)
5264                         if (line->type == stage_line_type)
5265                                 break;
5267                 if (!status_update_files(view, line + 1)) {
5268                         report("Failed to update files");
5269                         return FALSE;
5270                 }
5272         } else if (!status_update_file(&stage_status, stage_line_type)) {
5273                 report("Failed to update file");
5274                 return FALSE;
5275         }
5277         return TRUE;
5280 static bool
5281 stage_revert(struct view *view, struct line *line)
5283         struct line *chunk = NULL;
5285         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5286                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5288         if (chunk) {
5289                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5290                         return FALSE;
5292                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5293                         report("Failed to revert chunk");
5294                         return FALSE;
5295                 }
5296                 return TRUE;
5298         } else {
5299                 return status_revert(stage_status.status ? &stage_status : NULL,
5300                                      stage_line_type, FALSE);
5301         }
5305 static void
5306 stage_next(struct view *view, struct line *line)
5308         int i;
5310         if (!stage_chunks) {
5311                 static size_t alloc = 0;
5312                 int *tmp;
5314                 for (line = view->line; line < view->line + view->lines; line++) {
5315                         if (line->type != LINE_DIFF_CHUNK)
5316                                 continue;
5318                         tmp = realloc_items(stage_chunk, &alloc,
5319                                             stage_chunks, sizeof(*tmp));
5320                         if (!tmp) {
5321                                 report("Allocation failure");
5322                                 return;
5323                         }
5325                         stage_chunk = tmp;
5326                         stage_chunk[stage_chunks++] = line - view->line;
5327                 }
5328         }
5330         for (i = 0; i < stage_chunks; i++) {
5331                 if (stage_chunk[i] > view->lineno) {
5332                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5333                         report("Chunk %d of %d", i + 1, stage_chunks);
5334                         return;
5335                 }
5336         }
5338         report("No next chunk found");
5341 static enum request
5342 stage_request(struct view *view, enum request request, struct line *line)
5344         switch (request) {
5345         case REQ_STATUS_UPDATE:
5346                 if (!stage_update(view, line))
5347                         return REQ_NONE;
5348                 break;
5350         case REQ_STATUS_REVERT:
5351                 if (!stage_revert(view, line))
5352                         return REQ_NONE;
5353                 break;
5355         case REQ_STAGE_NEXT:
5356                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5357                         report("File is untracked; press %s to add",
5358                                get_key(REQ_STATUS_UPDATE));
5359                         return REQ_NONE;
5360                 }
5361                 stage_next(view, line);
5362                 return REQ_NONE;
5364         case REQ_EDIT:
5365                 if (!stage_status.new.name[0])
5366                         return request;
5367                 if (stage_status.status == 'D') {
5368                         report("File has been deleted.");
5369                         return REQ_NONE;
5370                 }
5372                 open_editor(stage_status.status != '?', stage_status.new.name);
5373                 break;
5375         case REQ_REFRESH:
5376                 /* Reload everything ... */
5377                 break;
5379         case REQ_VIEW_BLAME:
5380                 if (stage_status.new.name[0]) {
5381                         string_copy(opt_file, stage_status.new.name);
5382                         opt_ref[0] = 0;
5383                 }
5384                 return request;
5386         case REQ_ENTER:
5387                 return pager_request(view, request, line);
5389         default:
5390                 return request;
5391         }
5393         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5394         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5396         /* Check whether the staged entry still exists, and close the
5397          * stage view if it doesn't. */
5398         if (!status_exists(&stage_status, stage_line_type)) {
5399                 status_restore(VIEW(REQ_VIEW_STATUS));
5400                 return REQ_VIEW_CLOSE;
5401         }
5403         if (stage_line_type == LINE_STAT_UNTRACKED) {
5404                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5405                         report("Cannot display a directory");
5406                         return REQ_NONE;
5407                 }
5409                 if (!prepare_update_file(view, stage_status.new.name)) {
5410                         report("Failed to open file: %s", strerror(errno));
5411                         return REQ_NONE;
5412                 }
5413         }
5414         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5416         return REQ_NONE;
5419 static struct view_ops stage_ops = {
5420         "line",
5421         NULL,
5422         NULL,
5423         pager_read,
5424         pager_draw,
5425         stage_request,
5426         pager_grep,
5427         pager_select,
5428 };
5431 /*
5432  * Revision graph
5433  */
5435 struct commit {
5436         char id[SIZEOF_REV];            /* SHA1 ID. */
5437         char title[128];                /* First line of the commit message. */
5438         char author[75];                /* Author of the commit. */
5439         struct tm time;                 /* Date from the author ident. */
5440         struct ref **refs;              /* Repository references. */
5441         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5442         size_t graph_size;              /* The width of the graph array. */
5443         bool has_parents;               /* Rewritten --parents seen. */
5444 };
5446 /* Size of rev graph with no  "padding" columns */
5447 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5449 struct rev_graph {
5450         struct rev_graph *prev, *next, *parents;
5451         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5452         size_t size;
5453         struct commit *commit;
5454         size_t pos;
5455         unsigned int boundary:1;
5456 };
5458 /* Parents of the commit being visualized. */
5459 static struct rev_graph graph_parents[4];
5461 /* The current stack of revisions on the graph. */
5462 static struct rev_graph graph_stacks[4] = {
5463         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5464         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5465         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5466         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5467 };
5469 static inline bool
5470 graph_parent_is_merge(struct rev_graph *graph)
5472         return graph->parents->size > 1;
5475 static inline void
5476 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5478         struct commit *commit = graph->commit;
5480         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5481                 commit->graph[commit->graph_size++] = symbol;
5484 static void
5485 clear_rev_graph(struct rev_graph *graph)
5487         graph->boundary = 0;
5488         graph->size = graph->pos = 0;
5489         graph->commit = NULL;
5490         memset(graph->parents, 0, sizeof(*graph->parents));
5493 static void
5494 done_rev_graph(struct rev_graph *graph)
5496         if (graph_parent_is_merge(graph) &&
5497             graph->pos < graph->size - 1 &&
5498             graph->next->size == graph->size + graph->parents->size - 1) {
5499                 size_t i = graph->pos + graph->parents->size - 1;
5501                 graph->commit->graph_size = i * 2;
5502                 while (i < graph->next->size - 1) {
5503                         append_to_rev_graph(graph, ' ');
5504                         append_to_rev_graph(graph, '\\');
5505                         i++;
5506                 }
5507         }
5509         clear_rev_graph(graph);
5512 static void
5513 push_rev_graph(struct rev_graph *graph, const char *parent)
5515         int i;
5517         /* "Collapse" duplicate parents lines.
5518          *
5519          * FIXME: This needs to also update update the drawn graph but
5520          * for now it just serves as a method for pruning graph lines. */
5521         for (i = 0; i < graph->size; i++)
5522                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5523                         return;
5525         if (graph->size < SIZEOF_REVITEMS) {
5526                 string_copy_rev(graph->rev[graph->size++], parent);
5527         }
5530 static chtype
5531 get_rev_graph_symbol(struct rev_graph *graph)
5533         chtype symbol;
5535         if (graph->boundary)
5536                 symbol = REVGRAPH_BOUND;
5537         else if (graph->parents->size == 0)
5538                 symbol = REVGRAPH_INIT;
5539         else if (graph_parent_is_merge(graph))
5540                 symbol = REVGRAPH_MERGE;
5541         else if (graph->pos >= graph->size)
5542                 symbol = REVGRAPH_BRANCH;
5543         else
5544                 symbol = REVGRAPH_COMMIT;
5546         return symbol;
5549 static void
5550 draw_rev_graph(struct rev_graph *graph)
5552         struct rev_filler {
5553                 chtype separator, line;
5554         };
5555         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5556         static struct rev_filler fillers[] = {
5557                 { ' ',  '|' },
5558                 { '`',  '.' },
5559                 { '\'', ' ' },
5560                 { '/',  ' ' },
5561         };
5562         chtype symbol = get_rev_graph_symbol(graph);
5563         struct rev_filler *filler;
5564         size_t i;
5566         if (opt_line_graphics)
5567                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5569         filler = &fillers[DEFAULT];
5571         for (i = 0; i < graph->pos; i++) {
5572                 append_to_rev_graph(graph, filler->line);
5573                 if (graph_parent_is_merge(graph->prev) &&
5574                     graph->prev->pos == i)
5575                         filler = &fillers[RSHARP];
5577                 append_to_rev_graph(graph, filler->separator);
5578         }
5580         /* Place the symbol for this revision. */
5581         append_to_rev_graph(graph, symbol);
5583         if (graph->prev->size > graph->size)
5584                 filler = &fillers[RDIAG];
5585         else
5586                 filler = &fillers[DEFAULT];
5588         i++;
5590         for (; i < graph->size; i++) {
5591                 append_to_rev_graph(graph, filler->separator);
5592                 append_to_rev_graph(graph, filler->line);
5593                 if (graph_parent_is_merge(graph->prev) &&
5594                     i < graph->prev->pos + graph->parents->size)
5595                         filler = &fillers[RSHARP];
5596                 if (graph->prev->size > graph->size)
5597                         filler = &fillers[LDIAG];
5598         }
5600         if (graph->prev->size > graph->size) {
5601                 append_to_rev_graph(graph, filler->separator);
5602                 if (filler->line != ' ')
5603                         append_to_rev_graph(graph, filler->line);
5604         }
5607 /* Prepare the next rev graph */
5608 static void
5609 prepare_rev_graph(struct rev_graph *graph)
5611         size_t i;
5613         /* First, traverse all lines of revisions up to the active one. */
5614         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5615                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5616                         break;
5618                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5619         }
5621         /* Interleave the new revision parent(s). */
5622         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5623                 push_rev_graph(graph->next, graph->parents->rev[i]);
5625         /* Lastly, put any remaining revisions. */
5626         for (i = graph->pos + 1; i < graph->size; i++)
5627                 push_rev_graph(graph->next, graph->rev[i]);
5630 static void
5631 update_rev_graph(struct view *view, struct rev_graph *graph)
5633         /* If this is the finalizing update ... */
5634         if (graph->commit)
5635                 prepare_rev_graph(graph);
5637         /* Graph visualization needs a one rev look-ahead,
5638          * so the first update doesn't visualize anything. */
5639         if (!graph->prev->commit)
5640                 return;
5642         if (view->lines > 2)
5643                 view->line[view->lines - 3].dirty = 1;
5644         if (view->lines > 1)
5645                 view->line[view->lines - 2].dirty = 1;
5646         draw_rev_graph(graph->prev);
5647         done_rev_graph(graph->prev->prev);
5651 /*
5652  * Main view backend
5653  */
5655 static const char *main_argv[SIZEOF_ARG] = {
5656         "git", "log", "--no-color", "--pretty=raw", "--parents",
5657                       "--topo-order", "%(head)", NULL
5658 };
5660 static bool
5661 main_draw(struct view *view, struct line *line, unsigned int lineno)
5663         struct commit *commit = line->data;
5665         if (!*commit->author)
5666                 return FALSE;
5668         if (opt_date && draw_date(view, &commit->time))
5669                 return TRUE;
5671         if (opt_author && draw_author(view, commit->author))
5672                 return TRUE;
5674         if (opt_rev_graph && commit->graph_size &&
5675             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5676                 return TRUE;
5678         if (opt_show_refs && commit->refs) {
5679                 size_t i = 0;
5681                 do {
5682                         enum line_type type;
5684                         if (commit->refs[i]->head)
5685                                 type = LINE_MAIN_HEAD;
5686                         else if (commit->refs[i]->ltag)
5687                                 type = LINE_MAIN_LOCAL_TAG;
5688                         else if (commit->refs[i]->tag)
5689                                 type = LINE_MAIN_TAG;
5690                         else if (commit->refs[i]->tracked)
5691                                 type = LINE_MAIN_TRACKED;
5692                         else if (commit->refs[i]->remote)
5693                                 type = LINE_MAIN_REMOTE;
5694                         else
5695                                 type = LINE_MAIN_REF;
5697                         if (draw_text(view, type, "[", TRUE) ||
5698                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5699                             draw_text(view, type, "]", TRUE))
5700                                 return TRUE;
5702                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5703                                 return TRUE;
5704                 } while (commit->refs[i++]->next);
5705         }
5707         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5708         return TRUE;
5711 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5712 static bool
5713 main_read(struct view *view, char *line)
5715         static struct rev_graph *graph = graph_stacks;
5716         enum line_type type;
5717         struct commit *commit;
5719         if (!line) {
5720                 int i;
5722                 if (!view->lines && !view->parent)
5723                         die("No revisions match the given arguments.");
5724                 if (view->lines > 0) {
5725                         commit = view->line[view->lines - 1].data;
5726                         view->line[view->lines - 1].dirty = 1;
5727                         if (!*commit->author) {
5728                                 view->lines--;
5729                                 free(commit);
5730                                 graph->commit = NULL;
5731                         }
5732                 }
5733                 update_rev_graph(view, graph);
5735                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5736                         clear_rev_graph(&graph_stacks[i]);
5737                 return TRUE;
5738         }
5740         type = get_line_type(line);
5741         if (type == LINE_COMMIT) {
5742                 commit = calloc(1, sizeof(struct commit));
5743                 if (!commit)
5744                         return FALSE;
5746                 line += STRING_SIZE("commit ");
5747                 if (*line == '-') {
5748                         graph->boundary = 1;
5749                         line++;
5750                 }
5752                 string_copy_rev(commit->id, line);
5753                 commit->refs = get_refs(commit->id);
5754                 graph->commit = commit;
5755                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5757                 while ((line = strchr(line, ' '))) {
5758                         line++;
5759                         push_rev_graph(graph->parents, line);
5760                         commit->has_parents = TRUE;
5761                 }
5762                 return TRUE;
5763         }
5765         if (!view->lines)
5766                 return TRUE;
5767         commit = view->line[view->lines - 1].data;
5769         switch (type) {
5770         case LINE_PARENT:
5771                 if (commit->has_parents)
5772                         break;
5773                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5774                 break;
5776         case LINE_AUTHOR:
5777                 parse_author_line(line + STRING_SIZE("author "),
5778                                   commit->author, sizeof(commit->author),
5779                                   &commit->time);
5780                 update_rev_graph(view, graph);
5781                 graph = graph->next;
5782                 break;
5784         default:
5785                 /* Fill in the commit title if it has not already been set. */
5786                 if (commit->title[0])
5787                         break;
5789                 /* Require titles to start with a non-space character at the
5790                  * offset used by git log. */
5791                 if (strncmp(line, "    ", 4))
5792                         break;
5793                 line += 4;
5794                 /* Well, if the title starts with a whitespace character,
5795                  * try to be forgiving.  Otherwise we end up with no title. */
5796                 while (isspace(*line))
5797                         line++;
5798                 if (*line == '\0')
5799                         break;
5800                 /* FIXME: More graceful handling of titles; append "..." to
5801                  * shortened titles, etc. */
5803                 string_ncopy(commit->title, line, strlen(line));
5804                 view->line[view->lines - 1].dirty = 1;
5805         }
5807         return TRUE;
5810 static enum request
5811 main_request(struct view *view, enum request request, struct line *line)
5813         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5815         switch (request) {
5816         case REQ_ENTER:
5817                 open_view(view, REQ_VIEW_DIFF, flags);
5818                 break;
5819         case REQ_REFRESH:
5820                 load_refs();
5821                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5822                 break;
5823         default:
5824                 return request;
5825         }
5827         return REQ_NONE;
5830 static bool
5831 grep_refs(struct ref **refs, regex_t *regex)
5833         regmatch_t pmatch;
5834         size_t i = 0;
5836         if (!refs)
5837                 return FALSE;
5838         do {
5839                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5840                         return TRUE;
5841         } while (refs[i++]->next);
5843         return FALSE;
5846 static bool
5847 main_grep(struct view *view, struct line *line)
5849         struct commit *commit = line->data;
5850         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5851         char buf[DATE_COLS + 1];
5852         regmatch_t pmatch;
5854         for (state = S_TITLE; state < S_END; state++) {
5855                 char *text;
5857                 switch (state) {
5858                 case S_TITLE:   text = commit->title;   break;
5859                 case S_AUTHOR:
5860                         if (!opt_author)
5861                                 continue;
5862                         text = commit->author;
5863                         break;
5864                 case S_DATE:
5865                         if (!opt_date)
5866                                 continue;
5867                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5868                                 continue;
5869                         text = buf;
5870                         break;
5871                 case S_REFS:
5872                         if (!opt_show_refs)
5873                                 continue;
5874                         if (grep_refs(commit->refs, view->regex) == TRUE)
5875                                 return TRUE;
5876                         continue;
5877                 default:
5878                         return FALSE;
5879                 }
5881                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5882                         return TRUE;
5883         }
5885         return FALSE;
5888 static void
5889 main_select(struct view *view, struct line *line)
5891         struct commit *commit = line->data;
5893         string_copy_rev(view->ref, commit->id);
5894         string_copy_rev(ref_commit, view->ref);
5897 static struct view_ops main_ops = {
5898         "commit",
5899         main_argv,
5900         NULL,
5901         main_read,
5902         main_draw,
5903         main_request,
5904         main_grep,
5905         main_select,
5906 };
5909 /*
5910  * Unicode / UTF-8 handling
5911  *
5912  * NOTE: Much of the following code for dealing with unicode is derived from
5913  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5914  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5915  */
5917 /* I've (over)annotated a lot of code snippets because I am not entirely
5918  * confident that the approach taken by this small UTF-8 interface is correct.
5919  * --jonas */
5921 static inline int
5922 unicode_width(unsigned long c)
5924         if (c >= 0x1100 &&
5925            (c <= 0x115f                         /* Hangul Jamo */
5926             || c == 0x2329
5927             || c == 0x232a
5928             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5929                                                 /* CJK ... Yi */
5930             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5931             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5932             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5933             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5934             || (c >= 0xffe0  && c <= 0xffe6)
5935             || (c >= 0x20000 && c <= 0x2fffd)
5936             || (c >= 0x30000 && c <= 0x3fffd)))
5937                 return 2;
5939         if (c == '\t')
5940                 return opt_tab_size;
5942         return 1;
5945 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5946  * Illegal bytes are set one. */
5947 static const unsigned char utf8_bytes[256] = {
5948         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,
5949         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,
5950         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,
5951         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,
5952         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,
5953         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,
5954         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,
5955         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,
5956 };
5958 /* Decode UTF-8 multi-byte representation into a unicode character. */
5959 static inline unsigned long
5960 utf8_to_unicode(const char *string, size_t length)
5962         unsigned long unicode;
5964         switch (length) {
5965         case 1:
5966                 unicode  =   string[0];
5967                 break;
5968         case 2:
5969                 unicode  =  (string[0] & 0x1f) << 6;
5970                 unicode +=  (string[1] & 0x3f);
5971                 break;
5972         case 3:
5973                 unicode  =  (string[0] & 0x0f) << 12;
5974                 unicode += ((string[1] & 0x3f) << 6);
5975                 unicode +=  (string[2] & 0x3f);
5976                 break;
5977         case 4:
5978                 unicode  =  (string[0] & 0x0f) << 18;
5979                 unicode += ((string[1] & 0x3f) << 12);
5980                 unicode += ((string[2] & 0x3f) << 6);
5981                 unicode +=  (string[3] & 0x3f);
5982                 break;
5983         case 5:
5984                 unicode  =  (string[0] & 0x0f) << 24;
5985                 unicode += ((string[1] & 0x3f) << 18);
5986                 unicode += ((string[2] & 0x3f) << 12);
5987                 unicode += ((string[3] & 0x3f) << 6);
5988                 unicode +=  (string[4] & 0x3f);
5989                 break;
5990         case 6:
5991                 unicode  =  (string[0] & 0x01) << 30;
5992                 unicode += ((string[1] & 0x3f) << 24);
5993                 unicode += ((string[2] & 0x3f) << 18);
5994                 unicode += ((string[3] & 0x3f) << 12);
5995                 unicode += ((string[4] & 0x3f) << 6);
5996                 unicode +=  (string[5] & 0x3f);
5997                 break;
5998         default:
5999                 die("Invalid unicode length");
6000         }
6002         /* Invalid characters could return the special 0xfffd value but NUL
6003          * should be just as good. */
6004         return unicode > 0xffff ? 0 : unicode;
6007 /* Calculates how much of string can be shown within the given maximum width
6008  * and sets trimmed parameter to non-zero value if all of string could not be
6009  * shown. If the reserve flag is TRUE, it will reserve at least one
6010  * trailing character, which can be useful when drawing a delimiter.
6011  *
6012  * Returns the number of bytes to output from string to satisfy max_width. */
6013 static size_t
6014 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6016         const char *start = string;
6017         const char *end = strchr(string, '\0');
6018         unsigned char last_bytes = 0;
6019         size_t last_ucwidth = 0;
6021         *width = 0;
6022         *trimmed = 0;
6024         while (string < end) {
6025                 int c = *(unsigned char *) string;
6026                 unsigned char bytes = utf8_bytes[c];
6027                 size_t ucwidth;
6028                 unsigned long unicode;
6030                 if (string + bytes > end)
6031                         break;
6033                 /* Change representation to figure out whether
6034                  * it is a single- or double-width character. */
6036                 unicode = utf8_to_unicode(string, bytes);
6037                 /* FIXME: Graceful handling of invalid unicode character. */
6038                 if (!unicode)
6039                         break;
6041                 ucwidth = unicode_width(unicode);
6042                 *width  += ucwidth;
6043                 if (*width > max_width) {
6044                         *trimmed = 1;
6045                         *width -= ucwidth;
6046                         if (reserve && *width == max_width) {
6047                                 string -= last_bytes;
6048                                 *width -= last_ucwidth;
6049                         }
6050                         break;
6051                 }
6053                 string  += bytes;
6054                 last_bytes = bytes;
6055                 last_ucwidth = ucwidth;
6056         }
6058         return string - start;
6062 /*
6063  * Status management
6064  */
6066 /* Whether or not the curses interface has been initialized. */
6067 static bool cursed = FALSE;
6069 /* Terminal hacks and workarounds. */
6070 static bool use_scroll_redrawwin;
6071 static bool use_scroll_status_wclear;
6073 /* The status window is used for polling keystrokes. */
6074 static WINDOW *status_win;
6076 /* Reading from the prompt? */
6077 static bool input_mode = FALSE;
6079 static bool status_empty = FALSE;
6081 /* Update status and title window. */
6082 static void
6083 report(const char *msg, ...)
6085         struct view *view = display[current_view];
6087         if (input_mode)
6088                 return;
6090         if (!view) {
6091                 char buf[SIZEOF_STR];
6092                 va_list args;
6094                 va_start(args, msg);
6095                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6096                         buf[sizeof(buf) - 1] = 0;
6097                         buf[sizeof(buf) - 2] = '.';
6098                         buf[sizeof(buf) - 3] = '.';
6099                         buf[sizeof(buf) - 4] = '.';
6100                 }
6101                 va_end(args);
6102                 die("%s", buf);
6103         }
6105         if (!status_empty || *msg) {
6106                 va_list args;
6108                 va_start(args, msg);
6110                 wmove(status_win, 0, 0);
6111                 if (view->has_scrolled && use_scroll_status_wclear)
6112                         wclear(status_win);
6113                 if (*msg) {
6114                         vwprintw(status_win, msg, args);
6115                         status_empty = FALSE;
6116                 } else {
6117                         status_empty = TRUE;
6118                 }
6119                 wclrtoeol(status_win);
6120                 wnoutrefresh(status_win);
6122                 va_end(args);
6123         }
6125         update_view_title(view);
6128 /* Controls when nodelay should be in effect when polling user input. */
6129 static void
6130 set_nonblocking_input(bool loading)
6132         static unsigned int loading_views;
6134         if ((loading == FALSE && loading_views-- == 1) ||
6135             (loading == TRUE  && loading_views++ == 0))
6136                 nodelay(status_win, loading);
6139 static void
6140 init_display(void)
6142         const char *term;
6143         int x, y;
6145         /* Initialize the curses library */
6146         if (isatty(STDIN_FILENO)) {
6147                 cursed = !!initscr();
6148                 opt_tty = stdin;
6149         } else {
6150                 /* Leave stdin and stdout alone when acting as a pager. */
6151                 opt_tty = fopen("/dev/tty", "r+");
6152                 if (!opt_tty)
6153                         die("Failed to open /dev/tty");
6154                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6155         }
6157         if (!cursed)
6158                 die("Failed to initialize curses");
6160         nonl();         /* Tell curses not to do NL->CR/NL on output */
6161         cbreak();       /* Take input chars one at a time, no wait for \n */
6162         noecho();       /* Don't echo input */
6163         leaveok(stdscr, FALSE);
6165         if (has_colors())
6166                 init_colors();
6168         getmaxyx(stdscr, y, x);
6169         status_win = newwin(1, 0, y - 1, 0);
6170         if (!status_win)
6171                 die("Failed to create status window");
6173         /* Enable keyboard mapping */
6174         keypad(status_win, TRUE);
6175         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6177         TABSIZE = opt_tab_size;
6178         if (opt_line_graphics) {
6179                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6180         }
6182         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6183         if (term && !strcmp(term, "gnome-terminal")) {
6184                 /* In the gnome-terminal-emulator, the message from
6185                  * scrolling up one line when impossible followed by
6186                  * scrolling down one line causes corruption of the
6187                  * status line. This is fixed by calling wclear. */
6188                 use_scroll_status_wclear = TRUE;
6189                 use_scroll_redrawwin = FALSE;
6191         } else if (term && !strcmp(term, "xrvt-xpm")) {
6192                 /* No problems with full optimizations in xrvt-(unicode)
6193                  * and aterm. */
6194                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6196         } else {
6197                 /* When scrolling in (u)xterm the last line in the
6198                  * scrolling direction will update slowly. */
6199                 use_scroll_redrawwin = TRUE;
6200                 use_scroll_status_wclear = FALSE;
6201         }
6204 static int
6205 get_input(int prompt_position)
6207         struct view *view;
6208         int i, key, cursor_y, cursor_x;
6210         if (prompt_position)
6211                 input_mode = TRUE;
6213         while (TRUE) {
6214                 foreach_view (view, i) {
6215                         update_view(view);
6216                         if (view_is_displayed(view) && view->has_scrolled &&
6217                             use_scroll_redrawwin)
6218                                 redrawwin(view->win);
6219                         view->has_scrolled = FALSE;
6220                 }
6222                 /* Update the cursor position. */
6223                 if (prompt_position) {
6224                         getbegyx(status_win, cursor_y, cursor_x);
6225                         cursor_x = prompt_position;
6226                 } else {
6227                         view = display[current_view];
6228                         getbegyx(view->win, cursor_y, cursor_x);
6229                         cursor_x = view->width - 1;
6230                         cursor_y += view->lineno - view->offset;
6231                 }
6232                 setsyx(cursor_y, cursor_x);
6234                 /* Refresh, accept single keystroke of input */
6235                 doupdate();
6236                 key = wgetch(status_win);
6238                 /* wgetch() with nodelay() enabled returns ERR when
6239                  * there's no input. */
6240                 if (key == ERR) {
6242                 } else if (key == KEY_RESIZE) {
6243                         int height, width;
6245                         getmaxyx(stdscr, height, width);
6247                         wresize(status_win, 1, width);
6248                         mvwin(status_win, height - 1, 0);
6249                         wnoutrefresh(status_win);
6250                         resize_display();
6251                         redraw_display(TRUE);
6253                 } else {
6254                         input_mode = FALSE;
6255                         return key;
6256                 }
6257         }
6260 static char *
6261 prompt_input(const char *prompt, input_handler handler, void *data)
6263         enum input_status status = INPUT_OK;
6264         static char buf[SIZEOF_STR];
6265         size_t pos = 0;
6267         buf[pos] = 0;
6269         while (status == INPUT_OK || status == INPUT_SKIP) {
6270                 int key;
6272                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6273                 wclrtoeol(status_win);
6275                 key = get_input(pos + 1);
6276                 switch (key) {
6277                 case KEY_RETURN:
6278                 case KEY_ENTER:
6279                 case '\n':
6280                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6281                         break;
6283                 case KEY_BACKSPACE:
6284                         if (pos > 0)
6285                                 buf[--pos] = 0;
6286                         else
6287                                 status = INPUT_CANCEL;
6288                         break;
6290                 case KEY_ESC:
6291                         status = INPUT_CANCEL;
6292                         break;
6294                 default:
6295                         if (pos >= sizeof(buf)) {
6296                                 report("Input string too long");
6297                                 return NULL;
6298                         }
6300                         status = handler(data, buf, key);
6301                         if (status == INPUT_OK)
6302                                 buf[pos++] = (char) key;
6303                 }
6304         }
6306         /* Clear the status window */
6307         status_empty = FALSE;
6308         report("");
6310         if (status == INPUT_CANCEL)
6311                 return NULL;
6313         buf[pos++] = 0;
6315         return buf;
6318 static enum input_status
6319 prompt_yesno_handler(void *data, char *buf, int c)
6321         if (c == 'y' || c == 'Y')
6322                 return INPUT_STOP;
6323         if (c == 'n' || c == 'N')
6324                 return INPUT_CANCEL;
6325         return INPUT_SKIP;
6328 static bool
6329 prompt_yesno(const char *prompt)
6331         char prompt2[SIZEOF_STR];
6333         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6334                 return FALSE;
6336         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6339 static enum input_status
6340 read_prompt_handler(void *data, char *buf, int c)
6342         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6345 static char *
6346 read_prompt(const char *prompt)
6348         return prompt_input(prompt, read_prompt_handler, NULL);
6351 /*
6352  * Repository properties
6353  */
6355 static struct ref *refs = NULL;
6356 static size_t refs_alloc = 0;
6357 static size_t refs_size = 0;
6359 /* Id <-> ref store */
6360 static struct ref ***id_refs = NULL;
6361 static size_t id_refs_alloc = 0;
6362 static size_t id_refs_size = 0;
6364 static int
6365 compare_refs(const void *ref1_, const void *ref2_)
6367         const struct ref *ref1 = *(const struct ref **)ref1_;
6368         const struct ref *ref2 = *(const struct ref **)ref2_;
6370         if (ref1->tag != ref2->tag)
6371                 return ref2->tag - ref1->tag;
6372         if (ref1->ltag != ref2->ltag)
6373                 return ref2->ltag - ref2->ltag;
6374         if (ref1->head != ref2->head)
6375                 return ref2->head - ref1->head;
6376         if (ref1->tracked != ref2->tracked)
6377                 return ref2->tracked - ref1->tracked;
6378         if (ref1->remote != ref2->remote)
6379                 return ref2->remote - ref1->remote;
6380         return strcmp(ref1->name, ref2->name);
6383 static struct ref **
6384 get_refs(const char *id)
6386         struct ref ***tmp_id_refs;
6387         struct ref **ref_list = NULL;
6388         size_t ref_list_alloc = 0;
6389         size_t ref_list_size = 0;
6390         size_t i;
6392         for (i = 0; i < id_refs_size; i++)
6393                 if (!strcmp(id, id_refs[i][0]->id))
6394                         return id_refs[i];
6396         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6397                                     sizeof(*id_refs));
6398         if (!tmp_id_refs)
6399                 return NULL;
6401         id_refs = tmp_id_refs;
6403         for (i = 0; i < refs_size; i++) {
6404                 struct ref **tmp;
6406                 if (strcmp(id, refs[i].id))
6407                         continue;
6409                 tmp = realloc_items(ref_list, &ref_list_alloc,
6410                                     ref_list_size + 1, sizeof(*ref_list));
6411                 if (!tmp) {
6412                         if (ref_list)
6413                                 free(ref_list);
6414                         return NULL;
6415                 }
6417                 ref_list = tmp;
6418                 ref_list[ref_list_size] = &refs[i];
6419                 /* XXX: The properties of the commit chains ensures that we can
6420                  * safely modify the shared ref. The repo references will
6421                  * always be similar for the same id. */
6422                 ref_list[ref_list_size]->next = 1;
6424                 ref_list_size++;
6425         }
6427         if (ref_list) {
6428                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6429                 ref_list[ref_list_size - 1]->next = 0;
6430                 id_refs[id_refs_size++] = ref_list;
6431         }
6433         return ref_list;
6436 static int
6437 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6439         struct ref *ref;
6440         bool tag = FALSE;
6441         bool ltag = FALSE;
6442         bool remote = FALSE;
6443         bool tracked = FALSE;
6444         bool check_replace = FALSE;
6445         bool head = FALSE;
6447         if (!prefixcmp(name, "refs/tags/")) {
6448                 if (!suffixcmp(name, namelen, "^{}")) {
6449                         namelen -= 3;
6450                         name[namelen] = 0;
6451                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6452                                 check_replace = TRUE;
6453                 } else {
6454                         ltag = TRUE;
6455                 }
6457                 tag = TRUE;
6458                 namelen -= STRING_SIZE("refs/tags/");
6459                 name    += STRING_SIZE("refs/tags/");
6461         } else if (!prefixcmp(name, "refs/remotes/")) {
6462                 remote = TRUE;
6463                 namelen -= STRING_SIZE("refs/remotes/");
6464                 name    += STRING_SIZE("refs/remotes/");
6465                 tracked  = !strcmp(opt_remote, name);
6467         } else if (!prefixcmp(name, "refs/heads/")) {
6468                 namelen -= STRING_SIZE("refs/heads/");
6469                 name    += STRING_SIZE("refs/heads/");
6470                 head     = !strncmp(opt_head, name, namelen);
6472         } else if (!strcmp(name, "HEAD")) {
6473                 string_ncopy(opt_head_rev, id, idlen);
6474                 return OK;
6475         }
6477         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6478                 /* it's an annotated tag, replace the previous sha1 with the
6479                  * resolved commit id; relies on the fact git-ls-remote lists
6480                  * the commit id of an annotated tag right before the commit id
6481                  * it points to. */
6482                 refs[refs_size - 1].ltag = ltag;
6483                 string_copy_rev(refs[refs_size - 1].id, id);
6485                 return OK;
6486         }
6487         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6488         if (!refs)
6489                 return ERR;
6491         ref = &refs[refs_size++];
6492         ref->name = malloc(namelen + 1);
6493         if (!ref->name)
6494                 return ERR;
6496         strncpy(ref->name, name, namelen);
6497         ref->name[namelen] = 0;
6498         ref->head = head;
6499         ref->tag = tag;
6500         ref->ltag = ltag;
6501         ref->remote = remote;
6502         ref->tracked = tracked;
6503         string_copy_rev(ref->id, id);
6505         return OK;
6508 static int
6509 load_refs(void)
6511         static const char *ls_remote_argv[SIZEOF_ARG] = {
6512                 "git", "ls-remote", ".", NULL
6513         };
6514         static bool init = FALSE;
6516         if (!init) {
6517                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6518                 init = TRUE;
6519         }
6521         if (!*opt_git_dir)
6522                 return OK;
6524         while (refs_size > 0)
6525                 free(refs[--refs_size].name);
6526         while (id_refs_size > 0)
6527                 free(id_refs[--id_refs_size]);
6529         return run_io_load(ls_remote_argv, "\t", read_ref);
6532 static int
6533 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6535         if (!strcmp(name, "i18n.commitencoding"))
6536                 string_ncopy(opt_encoding, value, valuelen);
6538         if (!strcmp(name, "core.editor"))
6539                 string_ncopy(opt_editor, value, valuelen);
6541         /* branch.<head>.remote */
6542         if (*opt_head &&
6543             !strncmp(name, "branch.", 7) &&
6544             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6545             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6546                 string_ncopy(opt_remote, value, valuelen);
6548         if (*opt_head && *opt_remote &&
6549             !strncmp(name, "branch.", 7) &&
6550             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6551             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6552                 size_t from = strlen(opt_remote);
6554                 if (!prefixcmp(value, "refs/heads/")) {
6555                         value += STRING_SIZE("refs/heads/");
6556                         valuelen -= STRING_SIZE("refs/heads/");
6557                 }
6559                 if (!string_format_from(opt_remote, &from, "/%s", value))
6560                         opt_remote[0] = 0;
6561         }
6563         return OK;
6566 static int
6567 load_git_config(void)
6569         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6571         return run_io_load(config_list_argv, "=", read_repo_config_option);
6574 static int
6575 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6577         if (!opt_git_dir[0]) {
6578                 string_ncopy(opt_git_dir, name, namelen);
6580         } else if (opt_is_inside_work_tree == -1) {
6581                 /* This can be 3 different values depending on the
6582                  * version of git being used. If git-rev-parse does not
6583                  * understand --is-inside-work-tree it will simply echo
6584                  * the option else either "true" or "false" is printed.
6585                  * Default to true for the unknown case. */
6586                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6588         } else if (*name == '.') {
6589                 string_ncopy(opt_cdup, name, namelen);
6591         } else {
6592                 string_ncopy(opt_prefix, name, namelen);
6593         }
6595         return OK;
6598 static int
6599 load_repo_info(void)
6601         const char *head_argv[] = {
6602                 "git", "symbolic-ref", "HEAD", NULL
6603         };
6604         const char *rev_parse_argv[] = {
6605                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6606                         "--show-cdup", "--show-prefix", NULL
6607         };
6609         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6610                 chomp_string(opt_head);
6611                 if (!prefixcmp(opt_head, "refs/heads/")) {
6612                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6614                         memmove(opt_head, offset, strlen(offset) + 1);
6615                 }
6616         }
6618         return run_io_load(rev_parse_argv, "=", read_repo_info);
6622 /*
6623  * Main
6624  */
6626 static void __NORETURN
6627 quit(int sig)
6629         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6630         if (cursed)
6631                 endwin();
6632         exit(0);
6635 static void __NORETURN
6636 die(const char *err, ...)
6638         va_list args;
6640         endwin();
6642         va_start(args, err);
6643         fputs("tig: ", stderr);
6644         vfprintf(stderr, err, args);
6645         fputs("\n", stderr);
6646         va_end(args);
6648         exit(1);
6651 static void
6652 warn(const char *msg, ...)
6654         va_list args;
6656         va_start(args, msg);
6657         fputs("tig warning: ", stderr);
6658         vfprintf(stderr, msg, args);
6659         fputs("\n", stderr);
6660         va_end(args);
6663 static enum request
6664 parse_options(int argc, const char *argv[])
6666         enum request request = REQ_VIEW_MAIN;
6667         const char *subcommand;
6668         bool seen_dashdash = FALSE;
6669         /* XXX: This is vulnerable to the user overriding options
6670          * required for the main view parser. */
6671         const char *custom_argv[SIZEOF_ARG] = {
6672                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6673                         "--topo-order", NULL
6674         };
6675         int i, j = 6;
6677         if (!isatty(STDIN_FILENO)) {
6678                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6679                 return REQ_VIEW_PAGER;
6680         }
6682         if (argc <= 1)
6683                 return REQ_NONE;
6685         subcommand = argv[1];
6686         if (!strcmp(subcommand, "status")) {
6687                 if (argc > 2)
6688                         warn("ignoring arguments after `%s'", subcommand);
6689                 return REQ_VIEW_STATUS;
6691         } else if (!strcmp(subcommand, "blame")) {
6692                 if (argc <= 2 || argc > 4)
6693                         die("invalid number of options to blame\n\n%s", usage);
6695                 i = 2;
6696                 if (argc == 4) {
6697                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6698                         i++;
6699                 }
6701                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6702                 return REQ_VIEW_BLAME;
6704         } else if (!strcmp(subcommand, "show")) {
6705                 request = REQ_VIEW_DIFF;
6707         } else {
6708                 subcommand = NULL;
6709         }
6711         if (subcommand) {
6712                 custom_argv[1] = subcommand;
6713                 j = 2;
6714         }
6716         for (i = 1 + !!subcommand; i < argc; i++) {
6717                 const char *opt = argv[i];
6719                 if (seen_dashdash || !strcmp(opt, "--")) {
6720                         seen_dashdash = TRUE;
6722                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6723                         printf("tig version %s\n", TIG_VERSION);
6724                         quit(0);
6726                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6727                         printf("%s\n", usage);
6728                         quit(0);
6729                 }
6731                 custom_argv[j++] = opt;
6732                 if (j >= ARRAY_SIZE(custom_argv))
6733                         die("command too long");
6734         }
6736         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6737                 die("Failed to format arguments"); 
6739         return request;
6742 int
6743 main(int argc, const char *argv[])
6745         enum request request = parse_options(argc, argv);
6746         struct view *view;
6747         size_t i;
6749         signal(SIGINT, quit);
6751         if (setlocale(LC_ALL, "")) {
6752                 char *codeset = nl_langinfo(CODESET);
6754                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6755         }
6757         if (load_repo_info() == ERR)
6758                 die("Failed to load repo info.");
6760         if (load_options() == ERR)
6761                 die("Failed to load user config.");
6763         if (load_git_config() == ERR)
6764                 die("Failed to load repo config.");
6766         /* Require a git repository unless when running in pager mode. */
6767         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6768                 die("Not a git repository");
6770         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6771                 opt_utf8 = FALSE;
6773         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6774                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6775                 if (opt_iconv == ICONV_NONE)
6776                         die("Failed to initialize character set conversion");
6777         }
6779         if (load_refs() == ERR)
6780                 die("Failed to load refs.");
6782         foreach_view (view, i)
6783                 argv_from_env(view->ops->argv, view->cmd_env);
6785         init_display();
6787         if (request != REQ_NONE)
6788                 open_view(NULL, request, OPEN_PREPARED);
6789         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6791         while (view_driver(display[current_view], request)) {
6792                 int key = get_input(0);
6794                 view = display[current_view];
6795                 request = get_keybinding(view->keymap, key);
6797                 /* Some low-level request handling. This keeps access to
6798                  * status_win restricted. */
6799                 switch (request) {
6800                 case REQ_PROMPT:
6801                 {
6802                         char *cmd = read_prompt(":");
6804                         if (cmd) {
6805                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6806                                 const char *argv[SIZEOF_ARG] = { "git" };
6807                                 int argc = 1;
6809                                 /* When running random commands, initially show the
6810                                  * command in the title. However, it maybe later be
6811                                  * overwritten if a commit line is selected. */
6812                                 string_ncopy(next->ref, cmd, strlen(cmd));
6814                                 if (!argv_from_string(argv, &argc, cmd)) {
6815                                         report("Too many arguments");
6816                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6817                                         report("Failed to format command");
6818                                 } else {
6819                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6820                                 }
6821                         }
6823                         request = REQ_NONE;
6824                         break;
6825                 }
6826                 case REQ_SEARCH:
6827                 case REQ_SEARCH_BACK:
6828                 {
6829                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6830                         char *search = read_prompt(prompt);
6832                         if (search)
6833                                 string_ncopy(opt_search, search, strlen(search));
6834                         else if (*opt_search)
6835                                 request = request == REQ_SEARCH ?
6836                                         REQ_FIND_NEXT :
6837                                         REQ_FIND_PREV;
6838                         else
6839                                 request = REQ_NONE;
6840                         break;
6841                 }
6842                 default:
6843                         break;
6844                 }
6845         }
6847         quit(0);
6849         return 0;