Code

Expand tabs in displayed lines to not rely on ncurses to expand them
[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 size_t
214 string_expand_length(const char *line, int tabsize)
216         size_t size, pos;
218         for (pos = 0; line[pos]; pos++) {
219                 if (line[pos] == '\t' && tabsize > 0)
220                         size += tabsize - (size % tabsize);
221                 else
222                         size++;
223         }
224         return size;
227 static void
228 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
230         size_t size, pos;
232         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
233                 if (src[pos] == '\t') {
234                         size_t expanded = tabsize - (size % tabsize);
236                         if (expanded + size >= dstlen - 1)
237                                 expanded = dstlen - size - 1;
238                         memcpy(dst + size, "        ", expanded);
239                         size += expanded;
240                 } else {
241                         dst[size++] = src[pos];
242                 }
243         }
245         dst[size] = 0;
248 static char *
249 chomp_string(char *name)
251         int namelen;
253         while (isspace(*name))
254                 name++;
256         namelen = strlen(name) - 1;
257         while (namelen > 0 && isspace(name[namelen]))
258                 name[namelen--] = 0;
260         return name;
263 static bool
264 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
266         va_list args;
267         size_t pos = bufpos ? *bufpos : 0;
269         va_start(args, fmt);
270         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
271         va_end(args);
273         if (bufpos)
274                 *bufpos = pos;
276         return pos >= bufsize ? FALSE : TRUE;
279 #define string_format(buf, fmt, args...) \
280         string_nformat(buf, sizeof(buf), NULL, fmt, args)
282 #define string_format_from(buf, from, fmt, args...) \
283         string_nformat(buf, sizeof(buf), from, fmt, args)
285 static int
286 string_enum_compare(const char *str1, const char *str2, int len)
288         size_t i;
290 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
292         /* Diff-Header == DIFF_HEADER */
293         for (i = 0; i < len; i++) {
294                 if (toupper(str1[i]) == toupper(str2[i]))
295                         continue;
297                 if (string_enum_sep(str1[i]) &&
298                     string_enum_sep(str2[i]))
299                         continue;
301                 return str1[i] - str2[i];
302         }
304         return 0;
307 #define prefixcmp(str1, str2) \
308         strncmp(str1, str2, STRING_SIZE(str2))
310 static inline int
311 suffixcmp(const char *str, int slen, const char *suffix)
313         size_t len = slen >= 0 ? slen : strlen(str);
314         size_t suffixlen = strlen(suffix);
316         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
320 static bool
321 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
323         int valuelen;
325         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
326                 bool advance = cmd[valuelen] != 0;
328                 cmd[valuelen] = 0;
329                 argv[(*argc)++] = chomp_string(cmd);
330                 cmd = chomp_string(cmd + valuelen + advance);
331         }
333         if (*argc < SIZEOF_ARG)
334                 argv[*argc] = NULL;
335         return *argc < SIZEOF_ARG;
338 static void
339 argv_from_env(const char **argv, const char *name)
341         char *env = argv ? getenv(name) : NULL;
342         int argc = 0;
344         if (env && *env)
345                 env = strdup(env);
346         if (env && !argv_from_string(argv, &argc, env))
347                 die("Too many arguments in the `%s` environment variable", name);
351 /*
352  * Executing external commands.
353  */
355 enum io_type {
356         IO_FD,                  /* File descriptor based IO. */
357         IO_BG,                  /* Execute command in the background. */
358         IO_FG,                  /* Execute command with same std{in,out,err}. */
359         IO_RD,                  /* Read only fork+exec IO. */
360         IO_WR,                  /* Write only fork+exec IO. */
361         IO_AP,                  /* Append fork+exec output to file. */
362 };
364 struct io {
365         enum io_type type;      /* The requested type of pipe. */
366         const char *dir;        /* Directory from which to execute. */
367         pid_t pid;              /* Pipe for reading or writing. */
368         int pipe;               /* Pipe end for reading or writing. */
369         int error;              /* Error status. */
370         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
371         char *buf;              /* Read buffer. */
372         size_t bufalloc;        /* Allocated buffer size. */
373         size_t bufsize;         /* Buffer content size. */
374         char *bufpos;           /* Current buffer position. */
375         unsigned int eof:1;     /* Has end of file been reached. */
376 };
378 static void
379 reset_io(struct io *io)
381         io->pipe = -1;
382         io->pid = 0;
383         io->buf = io->bufpos = NULL;
384         io->bufalloc = io->bufsize = 0;
385         io->error = 0;
386         io->eof = 0;
389 static void
390 init_io(struct io *io, const char *dir, enum io_type type)
392         reset_io(io);
393         io->type = type;
394         io->dir = dir;
397 static bool
398 init_io_rd(struct io *io, const char *argv[], const char *dir,
399                 enum format_flags flags)
401         init_io(io, dir, IO_RD);
402         return format_argv(io->argv, argv, flags);
405 static bool
406 io_open(struct io *io, const char *name)
408         init_io(io, NULL, IO_FD);
409         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
410         return io->pipe != -1;
413 static bool
414 kill_io(struct io *io)
416         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
419 static bool
420 done_io(struct io *io)
422         pid_t pid = io->pid;
424         if (io->pipe != -1)
425                 close(io->pipe);
426         free(io->buf);
427         reset_io(io);
429         while (pid > 0) {
430                 int status;
431                 pid_t waiting = waitpid(pid, &status, 0);
433                 if (waiting < 0) {
434                         if (errno == EINTR)
435                                 continue;
436                         report("waitpid failed (%s)", strerror(errno));
437                         return FALSE;
438                 }
440                 return waiting == pid &&
441                        !WIFSIGNALED(status) &&
442                        WIFEXITED(status) &&
443                        !WEXITSTATUS(status);
444         }
446         return TRUE;
449 static bool
450 start_io(struct io *io)
452         int pipefds[2] = { -1, -1 };
454         if (io->type == IO_FD)
455                 return TRUE;
457         if ((io->type == IO_RD || io->type == IO_WR) &&
458             pipe(pipefds) < 0)
459                 return FALSE;
460         else if (io->type == IO_AP)
461                 pipefds[1] = io->pipe;
463         if ((io->pid = fork())) {
464                 if (pipefds[!(io->type == IO_WR)] != -1)
465                         close(pipefds[!(io->type == IO_WR)]);
466                 if (io->pid != -1) {
467                         io->pipe = pipefds[!!(io->type == IO_WR)];
468                         return TRUE;
469                 }
471         } else {
472                 if (io->type != IO_FG) {
473                         int devnull = open("/dev/null", O_RDWR);
474                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
475                         int writefd = (io->type == IO_RD || io->type == IO_AP)
476                                                         ? pipefds[1] : devnull;
478                         dup2(readfd,  STDIN_FILENO);
479                         dup2(writefd, STDOUT_FILENO);
480                         dup2(devnull, STDERR_FILENO);
482                         close(devnull);
483                         if (pipefds[0] != -1)
484                                 close(pipefds[0]);
485                         if (pipefds[1] != -1)
486                                 close(pipefds[1]);
487                 }
489                 if (io->dir && *io->dir && chdir(io->dir) == -1)
490                         die("Failed to change directory: %s", strerror(errno));
492                 execvp(io->argv[0], (char *const*) io->argv);
493                 die("Failed to execute program: %s", strerror(errno));
494         }
496         if (pipefds[!!(io->type == IO_WR)] != -1)
497                 close(pipefds[!!(io->type == IO_WR)]);
498         return FALSE;
501 static bool
502 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
504         init_io(io, dir, type);
505         if (!format_argv(io->argv, argv, FORMAT_NONE))
506                 return FALSE;
507         return start_io(io);
510 static int
511 run_io_do(struct io *io)
513         return start_io(io) && done_io(io);
516 static int
517 run_io_bg(const char **argv)
519         struct io io = {};
521         init_io(&io, NULL, IO_BG);
522         if (!format_argv(io.argv, argv, FORMAT_NONE))
523                 return FALSE;
524         return run_io_do(&io);
527 static bool
528 run_io_fg(const char **argv, const char *dir)
530         struct io io = {};
532         init_io(&io, dir, IO_FG);
533         if (!format_argv(io.argv, argv, FORMAT_NONE))
534                 return FALSE;
535         return run_io_do(&io);
538 static bool
539 run_io_append(const char **argv, enum format_flags flags, int fd)
541         struct io io = {};
543         init_io(&io, NULL, IO_AP);
544         io.pipe = fd;
545         if (format_argv(io.argv, argv, flags))
546                 return run_io_do(&io);
547         close(fd);
548         return FALSE;
551 static bool
552 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
554         return init_io_rd(io, argv, NULL, flags) && start_io(io);
557 static bool
558 io_eof(struct io *io)
560         return io->eof;
563 static int
564 io_error(struct io *io)
566         return io->error;
569 static bool
570 io_strerror(struct io *io)
572         return strerror(io->error);
575 static bool
576 io_can_read(struct io *io)
578         struct timeval tv = { 0, 500 };
579         fd_set fds;
581         FD_ZERO(&fds);
582         FD_SET(io->pipe, &fds);
584         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
587 static ssize_t
588 io_read(struct io *io, void *buf, size_t bufsize)
590         do {
591                 ssize_t readsize = read(io->pipe, buf, bufsize);
593                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
594                         continue;
595                 else if (readsize == -1)
596                         io->error = errno;
597                 else if (readsize == 0)
598                         io->eof = 1;
599                 return readsize;
600         } while (1);
603 static char *
604 io_get(struct io *io, int c, bool can_read)
606         char *eol;
607         ssize_t readsize;
609         if (!io->buf) {
610                 io->buf = io->bufpos = malloc(BUFSIZ);
611                 if (!io->buf)
612                         return NULL;
613                 io->bufalloc = BUFSIZ;
614                 io->bufsize = 0;
615         }
617         while (TRUE) {
618                 if (io->bufsize > 0) {
619                         eol = memchr(io->bufpos, c, io->bufsize);
620                         if (eol) {
621                                 char *line = io->bufpos;
623                                 *eol = 0;
624                                 io->bufpos = eol + 1;
625                                 io->bufsize -= io->bufpos - line;
626                                 return line;
627                         }
628                 }
630                 if (io_eof(io)) {
631                         if (io->bufsize) {
632                                 io->bufpos[io->bufsize] = 0;
633                                 io->bufsize = 0;
634                                 return io->bufpos;
635                         }
636                         return NULL;
637                 }
639                 if (!can_read)
640                         return NULL;
642                 if (io->bufsize > 0 && io->bufpos > io->buf)
643                         memmove(io->buf, io->bufpos, io->bufsize);
645                 io->bufpos = io->buf;
646                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
647                 if (io_error(io))
648                         return NULL;
649                 io->bufsize += readsize;
650         }
653 static bool
654 io_write(struct io *io, const void *buf, size_t bufsize)
656         size_t written = 0;
658         while (!io_error(io) && written < bufsize) {
659                 ssize_t size;
661                 size = write(io->pipe, buf + written, bufsize - written);
662                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
663                         continue;
664                 else if (size == -1)
665                         io->error = errno;
666                 else
667                         written += size;
668         }
670         return written == bufsize;
673 static bool
674 run_io_buf(const char **argv, char buf[], size_t bufsize)
676         struct io io = {};
677         bool error;
679         if (!run_io_rd(&io, argv, FORMAT_NONE))
680                 return FALSE;
682         io.buf = io.bufpos = buf;
683         io.bufalloc = bufsize;
684         error = !io_get(&io, '\n', TRUE) && io_error(&io);
685         io.buf = NULL;
687         return done_io(&io) || error;
690 static int
691 io_load(struct io *io, const char *separators,
692         int (*read_property)(char *, size_t, char *, size_t))
694         char *name;
695         int state = OK;
697         if (!start_io(io))
698                 return ERR;
700         while (state == OK && (name = io_get(io, '\n', TRUE))) {
701                 char *value;
702                 size_t namelen;
703                 size_t valuelen;
705                 name = chomp_string(name);
706                 namelen = strcspn(name, separators);
708                 if (name[namelen]) {
709                         name[namelen] = 0;
710                         value = chomp_string(name + namelen + 1);
711                         valuelen = strlen(value);
713                 } else {
714                         value = "";
715                         valuelen = 0;
716                 }
718                 state = read_property(name, namelen, value, valuelen);
719         }
721         if (state != ERR && io_error(io))
722                 state = ERR;
723         done_io(io);
725         return state;
728 static int
729 run_io_load(const char **argv, const char *separators,
730             int (*read_property)(char *, size_t, char *, size_t))
732         struct io io = {};
734         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
735                 ? io_load(&io, separators, read_property) : ERR;
739 /*
740  * User requests
741  */
743 #define REQ_INFO \
744         /* XXX: Keep the view request first and in sync with views[]. */ \
745         REQ_GROUP("View switching") \
746         REQ_(VIEW_MAIN,         "Show main view"), \
747         REQ_(VIEW_DIFF,         "Show diff view"), \
748         REQ_(VIEW_LOG,          "Show log view"), \
749         REQ_(VIEW_TREE,         "Show tree view"), \
750         REQ_(VIEW_BLOB,         "Show blob view"), \
751         REQ_(VIEW_BLAME,        "Show blame view"), \
752         REQ_(VIEW_HELP,         "Show help page"), \
753         REQ_(VIEW_PAGER,        "Show pager view"), \
754         REQ_(VIEW_STATUS,       "Show status view"), \
755         REQ_(VIEW_STAGE,        "Show stage view"), \
756         \
757         REQ_GROUP("View manipulation") \
758         REQ_(ENTER,             "Enter current line and scroll"), \
759         REQ_(NEXT,              "Move to next"), \
760         REQ_(PREVIOUS,          "Move to previous"), \
761         REQ_(PARENT,            "Move to parent"), \
762         REQ_(VIEW_NEXT,         "Move focus to next view"), \
763         REQ_(REFRESH,           "Reload and refresh"), \
764         REQ_(MAXIMIZE,          "Maximize the current view"), \
765         REQ_(VIEW_CLOSE,        "Close the current view"), \
766         REQ_(QUIT,              "Close all views and quit"), \
767         \
768         REQ_GROUP("View specific requests") \
769         REQ_(STATUS_UPDATE,     "Update file status"), \
770         REQ_(STATUS_REVERT,     "Revert file changes"), \
771         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
772         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
773         \
774         REQ_GROUP("Cursor navigation") \
775         REQ_(MOVE_UP,           "Move cursor one line up"), \
776         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
777         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
778         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
779         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
780         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
781         \
782         REQ_GROUP("Scrolling") \
783         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
784         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
785         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
786         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
787         \
788         REQ_GROUP("Searching") \
789         REQ_(SEARCH,            "Search the view"), \
790         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
791         REQ_(FIND_NEXT,         "Find next search match"), \
792         REQ_(FIND_PREV,         "Find previous search match"), \
793         \
794         REQ_GROUP("Option manipulation") \
795         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
796         REQ_(TOGGLE_DATE,       "Toggle date display"), \
797         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
798         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
799         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
800         \
801         REQ_GROUP("Misc") \
802         REQ_(PROMPT,            "Bring up the prompt"), \
803         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
804         REQ_(SHOW_VERSION,      "Show version information"), \
805         REQ_(STOP_LOADING,      "Stop all loading views"), \
806         REQ_(EDIT,              "Open in editor"), \
807         REQ_(NONE,              "Do nothing")
810 /* User action requests. */
811 enum request {
812 #define REQ_GROUP(help)
813 #define REQ_(req, help) REQ_##req
815         /* Offset all requests to avoid conflicts with ncurses getch values. */
816         REQ_OFFSET = KEY_MAX + 1,
817         REQ_INFO
819 #undef  REQ_GROUP
820 #undef  REQ_
821 };
823 struct request_info {
824         enum request request;
825         const char *name;
826         int namelen;
827         const char *help;
828 };
830 static struct request_info req_info[] = {
831 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
832 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
833         REQ_INFO
834 #undef  REQ_GROUP
835 #undef  REQ_
836 };
838 static enum request
839 get_request(const char *name)
841         int namelen = strlen(name);
842         int i;
844         for (i = 0; i < ARRAY_SIZE(req_info); i++)
845                 if (req_info[i].namelen == namelen &&
846                     !string_enum_compare(req_info[i].name, name, namelen))
847                         return req_info[i].request;
849         return REQ_NONE;
853 /*
854  * Options
855  */
857 static const char usage[] =
858 "tig " TIG_VERSION " (" __DATE__ ")\n"
859 "\n"
860 "Usage: tig        [options] [revs] [--] [paths]\n"
861 "   or: tig show   [options] [revs] [--] [paths]\n"
862 "   or: tig blame  [rev] path\n"
863 "   or: tig status\n"
864 "   or: tig <      [git command output]\n"
865 "\n"
866 "Options:\n"
867 "  -v, --version   Show version and exit\n"
868 "  -h, --help      Show help message and exit";
870 /* Option and state variables. */
871 static bool opt_date                    = TRUE;
872 static bool opt_author                  = TRUE;
873 static bool opt_line_number             = FALSE;
874 static bool opt_line_graphics           = TRUE;
875 static bool opt_rev_graph               = FALSE;
876 static bool opt_show_refs               = TRUE;
877 static int opt_num_interval             = NUMBER_INTERVAL;
878 static int opt_tab_size                 = TAB_SIZE;
879 static int opt_author_cols              = AUTHOR_COLS-1;
880 static char opt_path[SIZEOF_STR]        = "";
881 static char opt_file[SIZEOF_STR]        = "";
882 static char opt_ref[SIZEOF_REF]         = "";
883 static char opt_head[SIZEOF_REF]        = "";
884 static char opt_head_rev[SIZEOF_REV]    = "";
885 static char opt_remote[SIZEOF_REF]      = "";
886 static char opt_encoding[20]            = "UTF-8";
887 static bool opt_utf8                    = TRUE;
888 static char opt_codeset[20]             = "UTF-8";
889 static iconv_t opt_iconv                = ICONV_NONE;
890 static char opt_search[SIZEOF_STR]      = "";
891 static char opt_cdup[SIZEOF_STR]        = "";
892 static char opt_prefix[SIZEOF_STR]      = "";
893 static char opt_git_dir[SIZEOF_STR]     = "";
894 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
895 static char opt_editor[SIZEOF_STR]      = "";
896 static FILE *opt_tty                    = NULL;
898 #define is_initial_commit()     (!*opt_head_rev)
899 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
902 /*
903  * Line-oriented content detection.
904  */
906 #define LINE_INFO \
907 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
908 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
909 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
910 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
911 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
912 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
913 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
914 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
915 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
916 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
917 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
918 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
919 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
920 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
921 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
922 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
923 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
924 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
925 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
926 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
927 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
928 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
929 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
930 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
931 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
932 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
933 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
934 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
935 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
936 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
937 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
938 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
939 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
940 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
941 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
942 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
943 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
944 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
945 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
946 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
947 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
948 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
949 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
950 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
951 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
952 LINE(TREE_PARENT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
953 LINE(TREE_MODE,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
954 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
955 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
956 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
957 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
958 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
959 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
960 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
961 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
962 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
964 enum line_type {
965 #define LINE(type, line, fg, bg, attr) \
966         LINE_##type
967         LINE_INFO,
968         LINE_NONE
969 #undef  LINE
970 };
972 struct line_info {
973         const char *name;       /* Option name. */
974         int namelen;            /* Size of option name. */
975         const char *line;       /* The start of line to match. */
976         int linelen;            /* Size of string to match. */
977         int fg, bg, attr;       /* Color and text attributes for the lines. */
978 };
980 static struct line_info line_info[] = {
981 #define LINE(type, line, fg, bg, attr) \
982         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
983         LINE_INFO
984 #undef  LINE
985 };
987 static enum line_type
988 get_line_type(const char *line)
990         int linelen = strlen(line);
991         enum line_type type;
993         for (type = 0; type < ARRAY_SIZE(line_info); type++)
994                 /* Case insensitive search matches Signed-off-by lines better. */
995                 if (linelen >= line_info[type].linelen &&
996                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
997                         return type;
999         return LINE_DEFAULT;
1002 static inline int
1003 get_line_attr(enum line_type type)
1005         assert(type < ARRAY_SIZE(line_info));
1006         return COLOR_PAIR(type) | line_info[type].attr;
1009 static struct line_info *
1010 get_line_info(const char *name)
1012         size_t namelen = strlen(name);
1013         enum line_type type;
1015         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1016                 if (namelen == line_info[type].namelen &&
1017                     !string_enum_compare(line_info[type].name, name, namelen))
1018                         return &line_info[type];
1020         return NULL;
1023 static void
1024 init_colors(void)
1026         int default_bg = line_info[LINE_DEFAULT].bg;
1027         int default_fg = line_info[LINE_DEFAULT].fg;
1028         enum line_type type;
1030         start_color();
1032         if (assume_default_colors(default_fg, default_bg) == ERR) {
1033                 default_bg = COLOR_BLACK;
1034                 default_fg = COLOR_WHITE;
1035         }
1037         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1038                 struct line_info *info = &line_info[type];
1039                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1040                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1042                 init_pair(type, fg, bg);
1043         }
1046 struct line {
1047         enum line_type type;
1049         /* State flags */
1050         unsigned int selected:1;
1051         unsigned int dirty:1;
1052         unsigned int cleareol:1;
1054         void *data;             /* User data */
1055 };
1058 /*
1059  * Keys
1060  */
1062 struct keybinding {
1063         int alias;
1064         enum request request;
1065 };
1067 static struct keybinding default_keybindings[] = {
1068         /* View switching */
1069         { 'm',          REQ_VIEW_MAIN },
1070         { 'd',          REQ_VIEW_DIFF },
1071         { 'l',          REQ_VIEW_LOG },
1072         { 't',          REQ_VIEW_TREE },
1073         { 'f',          REQ_VIEW_BLOB },
1074         { 'B',          REQ_VIEW_BLAME },
1075         { 'p',          REQ_VIEW_PAGER },
1076         { 'h',          REQ_VIEW_HELP },
1077         { 'S',          REQ_VIEW_STATUS },
1078         { 'c',          REQ_VIEW_STAGE },
1080         /* View manipulation */
1081         { 'q',          REQ_VIEW_CLOSE },
1082         { KEY_TAB,      REQ_VIEW_NEXT },
1083         { KEY_RETURN,   REQ_ENTER },
1084         { KEY_UP,       REQ_PREVIOUS },
1085         { KEY_DOWN,     REQ_NEXT },
1086         { 'R',          REQ_REFRESH },
1087         { KEY_F(5),     REQ_REFRESH },
1088         { 'O',          REQ_MAXIMIZE },
1090         /* Cursor navigation */
1091         { 'k',          REQ_MOVE_UP },
1092         { 'j',          REQ_MOVE_DOWN },
1093         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1094         { KEY_END,      REQ_MOVE_LAST_LINE },
1095         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1096         { ' ',          REQ_MOVE_PAGE_DOWN },
1097         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1098         { 'b',          REQ_MOVE_PAGE_UP },
1099         { '-',          REQ_MOVE_PAGE_UP },
1101         /* Scrolling */
1102         { KEY_IC,       REQ_SCROLL_LINE_UP },
1103         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1104         { 'w',          REQ_SCROLL_PAGE_UP },
1105         { 's',          REQ_SCROLL_PAGE_DOWN },
1107         /* Searching */
1108         { '/',          REQ_SEARCH },
1109         { '?',          REQ_SEARCH_BACK },
1110         { 'n',          REQ_FIND_NEXT },
1111         { 'N',          REQ_FIND_PREV },
1113         /* Misc */
1114         { 'Q',          REQ_QUIT },
1115         { 'z',          REQ_STOP_LOADING },
1116         { 'v',          REQ_SHOW_VERSION },
1117         { 'r',          REQ_SCREEN_REDRAW },
1118         { '.',          REQ_TOGGLE_LINENO },
1119         { 'D',          REQ_TOGGLE_DATE },
1120         { 'A',          REQ_TOGGLE_AUTHOR },
1121         { 'g',          REQ_TOGGLE_REV_GRAPH },
1122         { 'F',          REQ_TOGGLE_REFS },
1123         { ':',          REQ_PROMPT },
1124         { 'u',          REQ_STATUS_UPDATE },
1125         { '!',          REQ_STATUS_REVERT },
1126         { 'M',          REQ_STATUS_MERGE },
1127         { '@',          REQ_STAGE_NEXT },
1128         { ',',          REQ_PARENT },
1129         { 'e',          REQ_EDIT },
1130 };
1132 #define KEYMAP_INFO \
1133         KEYMAP_(GENERIC), \
1134         KEYMAP_(MAIN), \
1135         KEYMAP_(DIFF), \
1136         KEYMAP_(LOG), \
1137         KEYMAP_(TREE), \
1138         KEYMAP_(BLOB), \
1139         KEYMAP_(BLAME), \
1140         KEYMAP_(PAGER), \
1141         KEYMAP_(HELP), \
1142         KEYMAP_(STATUS), \
1143         KEYMAP_(STAGE)
1145 enum keymap {
1146 #define KEYMAP_(name) KEYMAP_##name
1147         KEYMAP_INFO
1148 #undef  KEYMAP_
1149 };
1151 static struct int_map keymap_table[] = {
1152 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1153         KEYMAP_INFO
1154 #undef  KEYMAP_
1155 };
1157 #define set_keymap(map, name) \
1158         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1160 struct keybinding_table {
1161         struct keybinding *data;
1162         size_t size;
1163 };
1165 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1167 static void
1168 add_keybinding(enum keymap keymap, enum request request, int key)
1170         struct keybinding_table *table = &keybindings[keymap];
1172         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1173         if (!table->data)
1174                 die("Failed to allocate keybinding");
1175         table->data[table->size].alias = key;
1176         table->data[table->size++].request = request;
1179 /* Looks for a key binding first in the given map, then in the generic map, and
1180  * lastly in the default keybindings. */
1181 static enum request
1182 get_keybinding(enum keymap keymap, int key)
1184         size_t i;
1186         for (i = 0; i < keybindings[keymap].size; i++)
1187                 if (keybindings[keymap].data[i].alias == key)
1188                         return keybindings[keymap].data[i].request;
1190         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1191                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1192                         return keybindings[KEYMAP_GENERIC].data[i].request;
1194         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1195                 if (default_keybindings[i].alias == key)
1196                         return default_keybindings[i].request;
1198         return (enum request) key;
1202 struct key {
1203         const char *name;
1204         int value;
1205 };
1207 static struct key key_table[] = {
1208         { "Enter",      KEY_RETURN },
1209         { "Space",      ' ' },
1210         { "Backspace",  KEY_BACKSPACE },
1211         { "Tab",        KEY_TAB },
1212         { "Escape",     KEY_ESC },
1213         { "Left",       KEY_LEFT },
1214         { "Right",      KEY_RIGHT },
1215         { "Up",         KEY_UP },
1216         { "Down",       KEY_DOWN },
1217         { "Insert",     KEY_IC },
1218         { "Delete",     KEY_DC },
1219         { "Hash",       '#' },
1220         { "Home",       KEY_HOME },
1221         { "End",        KEY_END },
1222         { "PageUp",     KEY_PPAGE },
1223         { "PageDown",   KEY_NPAGE },
1224         { "F1",         KEY_F(1) },
1225         { "F2",         KEY_F(2) },
1226         { "F3",         KEY_F(3) },
1227         { "F4",         KEY_F(4) },
1228         { "F5",         KEY_F(5) },
1229         { "F6",         KEY_F(6) },
1230         { "F7",         KEY_F(7) },
1231         { "F8",         KEY_F(8) },
1232         { "F9",         KEY_F(9) },
1233         { "F10",        KEY_F(10) },
1234         { "F11",        KEY_F(11) },
1235         { "F12",        KEY_F(12) },
1236 };
1238 static int
1239 get_key_value(const char *name)
1241         int i;
1243         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1244                 if (!strcasecmp(key_table[i].name, name))
1245                         return key_table[i].value;
1247         if (strlen(name) == 1 && isprint(*name))
1248                 return (int) *name;
1250         return ERR;
1253 static const char *
1254 get_key_name(int key_value)
1256         static char key_char[] = "'X'";
1257         const char *seq = NULL;
1258         int key;
1260         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1261                 if (key_table[key].value == key_value)
1262                         seq = key_table[key].name;
1264         if (seq == NULL &&
1265             key_value < 127 &&
1266             isprint(key_value)) {
1267                 key_char[1] = (char) key_value;
1268                 seq = key_char;
1269         }
1271         return seq ? seq : "(no key)";
1274 static const char *
1275 get_key(enum request request)
1277         static char buf[BUFSIZ];
1278         size_t pos = 0;
1279         char *sep = "";
1280         int i;
1282         buf[pos] = 0;
1284         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1285                 struct keybinding *keybinding = &default_keybindings[i];
1287                 if (keybinding->request != request)
1288                         continue;
1290                 if (!string_format_from(buf, &pos, "%s%s", sep,
1291                                         get_key_name(keybinding->alias)))
1292                         return "Too many keybindings!";
1293                 sep = ", ";
1294         }
1296         return buf;
1299 struct run_request {
1300         enum keymap keymap;
1301         int key;
1302         const char *argv[SIZEOF_ARG];
1303 };
1305 static struct run_request *run_request;
1306 static size_t run_requests;
1308 static enum request
1309 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1311         struct run_request *req;
1313         if (argc >= ARRAY_SIZE(req->argv) - 1)
1314                 return REQ_NONE;
1316         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1317         if (!req)
1318                 return REQ_NONE;
1320         run_request = req;
1321         req = &run_request[run_requests];
1322         req->keymap = keymap;
1323         req->key = key;
1324         req->argv[0] = NULL;
1326         if (!format_argv(req->argv, argv, FORMAT_NONE))
1327                 return REQ_NONE;
1329         return REQ_NONE + ++run_requests;
1332 static struct run_request *
1333 get_run_request(enum request request)
1335         if (request <= REQ_NONE)
1336                 return NULL;
1337         return &run_request[request - REQ_NONE - 1];
1340 static void
1341 add_builtin_run_requests(void)
1343         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1344         const char *gc[] = { "git", "gc", NULL };
1345         struct {
1346                 enum keymap keymap;
1347                 int key;
1348                 int argc;
1349                 const char **argv;
1350         } reqs[] = {
1351                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1352                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1353         };
1354         int i;
1356         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1357                 enum request req;
1359                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1360                 if (req != REQ_NONE)
1361                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1362         }
1365 /*
1366  * User config file handling.
1367  */
1369 static struct int_map color_map[] = {
1370 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1371         COLOR_MAP(DEFAULT),
1372         COLOR_MAP(BLACK),
1373         COLOR_MAP(BLUE),
1374         COLOR_MAP(CYAN),
1375         COLOR_MAP(GREEN),
1376         COLOR_MAP(MAGENTA),
1377         COLOR_MAP(RED),
1378         COLOR_MAP(WHITE),
1379         COLOR_MAP(YELLOW),
1380 };
1382 #define set_color(color, name) \
1383         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1385 static struct int_map attr_map[] = {
1386 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1387         ATTR_MAP(NORMAL),
1388         ATTR_MAP(BLINK),
1389         ATTR_MAP(BOLD),
1390         ATTR_MAP(DIM),
1391         ATTR_MAP(REVERSE),
1392         ATTR_MAP(STANDOUT),
1393         ATTR_MAP(UNDERLINE),
1394 };
1396 #define set_attribute(attr, name) \
1397         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1399 static int   config_lineno;
1400 static bool  config_errors;
1401 static const char *config_msg;
1403 /* Wants: object fgcolor bgcolor [attr] */
1404 static int
1405 option_color_command(int argc, const char *argv[])
1407         struct line_info *info;
1409         if (argc != 3 && argc != 4) {
1410                 config_msg = "Wrong number of arguments given to color command";
1411                 return ERR;
1412         }
1414         info = get_line_info(argv[0]);
1415         if (!info) {
1416                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1417                         info = get_line_info("delimiter");
1419                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1420                         info = get_line_info("date");
1422                 } else {
1423                         config_msg = "Unknown color name";
1424                         return ERR;
1425                 }
1426         }
1428         if (set_color(&info->fg, argv[1]) == ERR ||
1429             set_color(&info->bg, argv[2]) == ERR) {
1430                 config_msg = "Unknown color";
1431                 return ERR;
1432         }
1434         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1435                 config_msg = "Unknown attribute";
1436                 return ERR;
1437         }
1439         return OK;
1442 static bool parse_bool(const char *s)
1444         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1445                 !strcmp(s, "yes")) ? TRUE : FALSE;
1448 static int
1449 parse_int(const char *s, int default_value, int min, int max)
1451         int value = atoi(s);
1453         return (value < min || value > max) ? default_value : value;
1456 /* Wants: name = value */
1457 static int
1458 option_set_command(int argc, const char *argv[])
1460         if (argc != 3) {
1461                 config_msg = "Wrong number of arguments given to set command";
1462                 return ERR;
1463         }
1465         if (strcmp(argv[1], "=")) {
1466                 config_msg = "No value assigned";
1467                 return ERR;
1468         }
1470         if (!strcmp(argv[0], "show-author")) {
1471                 opt_author = parse_bool(argv[2]);
1472                 return OK;
1473         }
1475         if (!strcmp(argv[0], "show-date")) {
1476                 opt_date = parse_bool(argv[2]);
1477                 return OK;
1478         }
1480         if (!strcmp(argv[0], "show-rev-graph")) {
1481                 opt_rev_graph = parse_bool(argv[2]);
1482                 return OK;
1483         }
1485         if (!strcmp(argv[0], "show-refs")) {
1486                 opt_show_refs = parse_bool(argv[2]);
1487                 return OK;
1488         }
1490         if (!strcmp(argv[0], "show-line-numbers")) {
1491                 opt_line_number = parse_bool(argv[2]);
1492                 return OK;
1493         }
1495         if (!strcmp(argv[0], "line-graphics")) {
1496                 opt_line_graphics = parse_bool(argv[2]);
1497                 return OK;
1498         }
1500         if (!strcmp(argv[0], "line-number-interval")) {
1501                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1502                 return OK;
1503         }
1505         if (!strcmp(argv[0], "author-width")) {
1506                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1507                 return OK;
1508         }
1510         if (!strcmp(argv[0], "tab-size")) {
1511                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1512                 return OK;
1513         }
1515         if (!strcmp(argv[0], "commit-encoding")) {
1516                 const char *arg = argv[2];
1517                 int arglen = strlen(arg);
1519                 switch (arg[0]) {
1520                 case '"':
1521                 case '\'':
1522                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1523                                 config_msg = "Unmatched quotation";
1524                                 return ERR;
1525                         }
1526                         arg += 1; arglen -= 2;
1527                 default:
1528                         string_ncopy(opt_encoding, arg, strlen(arg));
1529                         return OK;
1530                 }
1531         }
1533         config_msg = "Unknown variable name";
1534         return ERR;
1537 /* Wants: mode request key */
1538 static int
1539 option_bind_command(int argc, const char *argv[])
1541         enum request request;
1542         int keymap;
1543         int key;
1545         if (argc < 3) {
1546                 config_msg = "Wrong number of arguments given to bind command";
1547                 return ERR;
1548         }
1550         if (set_keymap(&keymap, argv[0]) == ERR) {
1551                 config_msg = "Unknown key map";
1552                 return ERR;
1553         }
1555         key = get_key_value(argv[1]);
1556         if (key == ERR) {
1557                 config_msg = "Unknown key";
1558                 return ERR;
1559         }
1561         request = get_request(argv[2]);
1562         if (request == REQ_NONE) {
1563                 struct {
1564                         const char *name;
1565                         enum request request;
1566                 } obsolete[] = {
1567                         { "cherry-pick",        REQ_NONE },
1568                         { "screen-resize",      REQ_NONE },
1569                         { "tree-parent",        REQ_PARENT },
1570                 };
1571                 size_t namelen = strlen(argv[2]);
1572                 int i;
1574                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1575                         if (namelen != strlen(obsolete[i].name) ||
1576                             string_enum_compare(obsolete[i].name, argv[2], namelen))
1577                                 continue;
1578                         if (obsolete[i].request != REQ_NONE)
1579                                 add_keybinding(keymap, obsolete[i].request, key);
1580                         config_msg = "Obsolete request name";
1581                         return ERR;
1582                 }
1583         }
1584         if (request == REQ_NONE && *argv[2]++ == '!')
1585                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1586         if (request == REQ_NONE) {
1587                 config_msg = "Unknown request name";
1588                 return ERR;
1589         }
1591         add_keybinding(keymap, request, key);
1593         return OK;
1596 static int
1597 set_option(const char *opt, char *value)
1599         const char *argv[SIZEOF_ARG];
1600         int argc = 0;
1602         if (!argv_from_string(argv, &argc, value)) {
1603                 config_msg = "Too many option arguments";
1604                 return ERR;
1605         }
1607         if (!strcmp(opt, "color"))
1608                 return option_color_command(argc, argv);
1610         if (!strcmp(opt, "set"))
1611                 return option_set_command(argc, argv);
1613         if (!strcmp(opt, "bind"))
1614                 return option_bind_command(argc, argv);
1616         config_msg = "Unknown option command";
1617         return ERR;
1620 static int
1621 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1623         int status = OK;
1625         config_lineno++;
1626         config_msg = "Internal error";
1628         /* Check for comment markers, since read_properties() will
1629          * only ensure opt and value are split at first " \t". */
1630         optlen = strcspn(opt, "#");
1631         if (optlen == 0)
1632                 return OK;
1634         if (opt[optlen] != 0) {
1635                 config_msg = "No option value";
1636                 status = ERR;
1638         }  else {
1639                 /* Look for comment endings in the value. */
1640                 size_t len = strcspn(value, "#");
1642                 if (len < valuelen) {
1643                         valuelen = len;
1644                         value[valuelen] = 0;
1645                 }
1647                 status = set_option(opt, value);
1648         }
1650         if (status == ERR) {
1651                 warn("Error on line %d, near '%.*s': %s",
1652                      config_lineno, (int) optlen, opt, config_msg);
1653                 config_errors = TRUE;
1654         }
1656         /* Always keep going if errors are encountered. */
1657         return OK;
1660 static void
1661 load_option_file(const char *path)
1663         struct io io = {};
1665         /* It's ok that the file doesn't exist. */
1666         if (!io_open(&io, path))
1667                 return;
1669         config_lineno = 0;
1670         config_errors = FALSE;
1672         if (io_load(&io, " \t", read_option) == ERR ||
1673             config_errors == TRUE)
1674                 warn("Errors while loading %s.", path);
1677 static int
1678 load_options(void)
1680         const char *home = getenv("HOME");
1681         const char *tigrc_user = getenv("TIGRC_USER");
1682         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1683         char buf[SIZEOF_STR];
1685         add_builtin_run_requests();
1687         if (!tigrc_system) {
1688                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1689                         return ERR;
1690                 tigrc_system = buf;
1691         }
1692         load_option_file(tigrc_system);
1694         if (!tigrc_user) {
1695                 if (!home || !string_format(buf, "%s/.tigrc", home))
1696                         return ERR;
1697                 tigrc_user = buf;
1698         }
1699         load_option_file(tigrc_user);
1701         return OK;
1705 /*
1706  * The viewer
1707  */
1709 struct view;
1710 struct view_ops;
1712 /* The display array of active views and the index of the current view. */
1713 static struct view *display[2];
1714 static unsigned int current_view;
1716 #define foreach_displayed_view(view, i) \
1717         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1719 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1721 /* Current head and commit ID */
1722 static char ref_blob[SIZEOF_REF]        = "";
1723 static char ref_commit[SIZEOF_REF]      = "HEAD";
1724 static char ref_head[SIZEOF_REF]        = "HEAD";
1726 struct view {
1727         const char *name;       /* View name */
1728         const char *cmd_env;    /* Command line set via environment */
1729         const char *id;         /* Points to either of ref_{head,commit,blob} */
1731         struct view_ops *ops;   /* View operations */
1733         enum keymap keymap;     /* What keymap does this view have */
1734         bool git_dir;           /* Whether the view requires a git directory. */
1736         char ref[SIZEOF_REF];   /* Hovered commit reference */
1737         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1739         int height, width;      /* The width and height of the main window */
1740         WINDOW *win;            /* The main window */
1741         WINDOW *title;          /* The title window living below the main window */
1743         /* Navigation */
1744         unsigned long offset;   /* Offset of the window top */
1745         unsigned long lineno;   /* Current line number */
1746         unsigned long p_offset; /* Previous offset of the window top */
1747         unsigned long p_lineno; /* Previous current line number */
1748         bool p_restore;         /* Should the previous position be restored. */
1750         /* Searching */
1751         char grep[SIZEOF_STR];  /* Search string */
1752         regex_t *regex;         /* Pre-compiled regex */
1754         /* If non-NULL, points to the view that opened this view. If this view
1755          * is closed tig will switch back to the parent view. */
1756         struct view *parent;
1758         /* Buffering */
1759         size_t lines;           /* Total number of lines */
1760         struct line *line;      /* Line index */
1761         size_t line_alloc;      /* Total number of allocated lines */
1762         unsigned int digits;    /* Number of digits in the lines member. */
1764         /* Drawing */
1765         struct line *curline;   /* Line currently being drawn. */
1766         enum line_type curtype; /* Attribute currently used for drawing. */
1767         unsigned long col;      /* Column when drawing. */
1768         bool has_scrolled;      /* View was scrolled. */
1770         /* Loading */
1771         struct io io;
1772         struct io *pipe;
1773         time_t start_time;
1774         time_t update_secs;
1775 };
1777 struct view_ops {
1778         /* What type of content being displayed. Used in the title bar. */
1779         const char *type;
1780         /* Default command arguments. */
1781         const char **argv;
1782         /* Open and reads in all view content. */
1783         bool (*open)(struct view *view);
1784         /* Read one line; updates view->line. */
1785         bool (*read)(struct view *view, char *data);
1786         /* Draw one line; @lineno must be < view->height. */
1787         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1788         /* Depending on view handle a special requests. */
1789         enum request (*request)(struct view *view, enum request request, struct line *line);
1790         /* Search for regex in a line. */
1791         bool (*grep)(struct view *view, struct line *line);
1792         /* Select line */
1793         void (*select)(struct view *view, struct line *line);
1794 };
1796 static struct view_ops blame_ops;
1797 static struct view_ops blob_ops;
1798 static struct view_ops diff_ops;
1799 static struct view_ops help_ops;
1800 static struct view_ops log_ops;
1801 static struct view_ops main_ops;
1802 static struct view_ops pager_ops;
1803 static struct view_ops stage_ops;
1804 static struct view_ops status_ops;
1805 static struct view_ops tree_ops;
1807 #define VIEW_STR(name, env, ref, ops, map, git) \
1808         { name, #env, ref, ops, map, git }
1810 #define VIEW_(id, name, ops, git, ref) \
1811         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1814 static struct view views[] = {
1815         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1816         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1817         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1818         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1819         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1820         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1821         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1822         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1823         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1824         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1825 };
1827 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1828 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1830 #define foreach_view(view, i) \
1831         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1833 #define view_is_displayed(view) \
1834         (view == display[0] || view == display[1])
1837 enum line_graphic {
1838         LINE_GRAPHIC_VLINE
1839 };
1841 static int line_graphics[] = {
1842         /* LINE_GRAPHIC_VLINE: */ '|'
1843 };
1845 static inline void
1846 set_view_attr(struct view *view, enum line_type type)
1848         if (!view->curline->selected && view->curtype != type) {
1849                 wattrset(view->win, get_line_attr(type));
1850                 wchgat(view->win, -1, 0, type, NULL);
1851                 view->curtype = type;
1852         }
1855 static int
1856 draw_chars(struct view *view, enum line_type type, const char *string,
1857            int max_len, bool use_tilde)
1859         int len = 0;
1860         int col = 0;
1861         int trimmed = FALSE;
1863         if (max_len <= 0)
1864                 return 0;
1866         if (opt_utf8) {
1867                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1868         } else {
1869                 col = len = strlen(string);
1870                 if (len > max_len) {
1871                         if (use_tilde) {
1872                                 max_len -= 1;
1873                         }
1874                         col = len = max_len;
1875                         trimmed = TRUE;
1876                 }
1877         }
1879         set_view_attr(view, type);
1880         waddnstr(view->win, string, len);
1881         if (trimmed && use_tilde) {
1882                 set_view_attr(view, LINE_DELIMITER);
1883                 waddch(view->win, '~');
1884                 col++;
1885         }
1887         return col;
1890 static int
1891 draw_space(struct view *view, enum line_type type, int max, int spaces)
1893         static char space[] = "                    ";
1894         int col = 0;
1896         spaces = MIN(max, spaces);
1898         while (spaces > 0) {
1899                 int len = MIN(spaces, sizeof(space) - 1);
1901                 col += draw_chars(view, type, space, spaces, FALSE);
1902                 spaces -= len;
1903         }
1905         return col;
1908 static bool
1909 draw_lineno(struct view *view, unsigned int lineno)
1911         char number[10];
1912         int digits3 = view->digits < 3 ? 3 : view->digits;
1913         int max_number = MIN(digits3, STRING_SIZE(number));
1914         int max = view->width - view->col;
1915         int col;
1917         if (max < max_number)
1918                 max_number = max;
1920         lineno += view->offset + 1;
1921         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1922                 static char fmt[] = "%1ld";
1924                 if (view->digits <= 9)
1925                         fmt[1] = '0' + digits3;
1927                 if (!string_format(number, fmt, lineno))
1928                         number[0] = 0;
1929                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1930         } else {
1931                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1932         }
1934         if (col < max) {
1935                 set_view_attr(view, LINE_DEFAULT);
1936                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1937                 col++;
1938         }
1940         if (col < max)
1941                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1942         view->col += col;
1944         return view->width - view->col <= 0;
1947 static bool
1948 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1950         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1951         return view->width - view->col <= 0;
1954 static bool
1955 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1957         int max = view->width - view->col;
1958         int i;
1960         if (max < size)
1961                 size = max;
1963         set_view_attr(view, type);
1964         /* Using waddch() instead of waddnstr() ensures that
1965          * they'll be rendered correctly for the cursor line. */
1966         for (i = 0; i < size; i++)
1967                 waddch(view->win, graphic[i]);
1969         view->col += size;
1970         if (size < max) {
1971                 waddch(view->win, ' ');
1972                 view->col++;
1973         }
1975         return view->width - view->col <= 0;
1978 static bool
1979 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1981         int max = MIN(view->width - view->col, len);
1982         int col;
1984         if (text)
1985                 col = draw_chars(view, type, text, max - 1, trim);
1986         else
1987                 col = draw_space(view, type, max - 1, max - 1);
1989         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1990         return view->width - view->col <= 0;
1993 static bool
1994 draw_date(struct view *view, struct tm *time)
1996         char buf[DATE_COLS];
1997         char *date;
1998         int timelen = 0;
2000         if (time)
2001                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2002         date = timelen ? buf : NULL;
2004         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2007 static bool
2008 draw_author(struct view *view, const char *author)
2010         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2012         if (!trim) {
2013                 static char initials[10];
2014                 size_t pos;
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018                 memset(initials, 0, sizeof(initials));
2019                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020                         while (is_initial_sep(*author))
2021                                 author++;
2022                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023                         while (*author && !is_initial_sep(author[1]))
2024                                 author++;
2025                 }
2027                 author = initials;
2028         }
2030         return draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, trim);
2033 static bool
2034 draw_view_line(struct view *view, unsigned int lineno)
2036         struct line *line;
2037         bool selected = (view->offset + lineno == view->lineno);
2039         assert(view_is_displayed(view));
2041         if (view->offset + lineno >= view->lines)
2042                 return FALSE;
2044         line = &view->line[view->offset + lineno];
2046         wmove(view->win, lineno, 0);
2047         if (line->cleareol)
2048                 wclrtoeol(view->win);
2049         view->col = 0;
2050         view->curline = line;
2051         view->curtype = LINE_NONE;
2052         line->selected = FALSE;
2053         line->dirty = line->cleareol = 0;
2055         if (selected) {
2056                 set_view_attr(view, LINE_CURSOR);
2057                 line->selected = TRUE;
2058                 view->ops->select(view, line);
2059         }
2061         return view->ops->draw(view, line, lineno);
2064 static void
2065 redraw_view_dirty(struct view *view)
2067         bool dirty = FALSE;
2068         int lineno;
2070         for (lineno = 0; lineno < view->height; lineno++) {
2071                 if (view->offset + lineno >= view->lines)
2072                         break;
2073                 if (!view->line[view->offset + lineno].dirty)
2074                         continue;
2075                 dirty = TRUE;
2076                 if (!draw_view_line(view, lineno))
2077                         break;
2078         }
2080         if (!dirty)
2081                 return;
2082         wnoutrefresh(view->win);
2085 static void
2086 redraw_view_from(struct view *view, int lineno)
2088         assert(0 <= lineno && lineno < view->height);
2090         for (; lineno < view->height; lineno++) {
2091                 if (!draw_view_line(view, lineno))
2092                         break;
2093         }
2095         wnoutrefresh(view->win);
2098 static void
2099 redraw_view(struct view *view)
2101         werase(view->win);
2102         redraw_view_from(view, 0);
2106 static void
2107 update_view_title(struct view *view)
2109         char buf[SIZEOF_STR];
2110         char state[SIZEOF_STR];
2111         size_t bufpos = 0, statelen = 0;
2113         assert(view_is_displayed(view));
2115         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2116                 unsigned int view_lines = view->offset + view->height;
2117                 unsigned int lines = view->lines
2118                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2119                                    : 0;
2121                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2122                                    view->ops->type,
2123                                    view->lineno + 1,
2124                                    view->lines,
2125                                    lines);
2127         }
2129         if (view->pipe) {
2130                 time_t secs = time(NULL) - view->start_time;
2132                 /* Three git seconds are a long time ... */
2133                 if (secs > 2)
2134                         string_format_from(state, &statelen, " loading %lds", secs);
2135         }
2137         string_format_from(buf, &bufpos, "[%s]", view->name);
2138         if (*view->ref && bufpos < view->width) {
2139                 size_t refsize = strlen(view->ref);
2140                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2142                 if (minsize < view->width)
2143                         refsize = view->width - minsize + 7;
2144                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2145         }
2147         if (statelen && bufpos < view->width) {
2148                 string_format_from(buf, &bufpos, "%s", state);
2149         }
2151         if (view == display[current_view])
2152                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2153         else
2154                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2156         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2157         wclrtoeol(view->title);
2158         wnoutrefresh(view->title);
2161 static void
2162 resize_display(void)
2164         int offset, i;
2165         struct view *base = display[0];
2166         struct view *view = display[1] ? display[1] : display[0];
2168         /* Setup window dimensions */
2170         getmaxyx(stdscr, base->height, base->width);
2172         /* Make room for the status window. */
2173         base->height -= 1;
2175         if (view != base) {
2176                 /* Horizontal split. */
2177                 view->width   = base->width;
2178                 view->height  = SCALE_SPLIT_VIEW(base->height);
2179                 base->height -= view->height;
2181                 /* Make room for the title bar. */
2182                 view->height -= 1;
2183         }
2185         /* Make room for the title bar. */
2186         base->height -= 1;
2188         offset = 0;
2190         foreach_displayed_view (view, i) {
2191                 if (!view->win) {
2192                         view->win = newwin(view->height, 0, offset, 0);
2193                         if (!view->win)
2194                                 die("Failed to create %s view", view->name);
2196                         scrollok(view->win, FALSE);
2198                         view->title = newwin(1, 0, offset + view->height, 0);
2199                         if (!view->title)
2200                                 die("Failed to create title window");
2202                 } else {
2203                         wresize(view->win, view->height, view->width);
2204                         mvwin(view->win,   offset, 0);
2205                         mvwin(view->title, offset + view->height, 0);
2206                 }
2208                 offset += view->height + 1;
2209         }
2212 static void
2213 redraw_display(bool clear)
2215         struct view *view;
2216         int i;
2218         foreach_displayed_view (view, i) {
2219                 if (clear)
2220                         wclear(view->win);
2221                 redraw_view(view);
2222                 update_view_title(view);
2223         }
2226 static void
2227 toggle_view_option(bool *option, const char *help)
2229         *option = !*option;
2230         redraw_display(FALSE);
2231         report("%sabling %s", *option ? "En" : "Dis", help);
2234 /*
2235  * Navigation
2236  */
2238 /* Scrolling backend */
2239 static void
2240 do_scroll_view(struct view *view, int lines)
2242         bool redraw_current_line = FALSE;
2244         /* The rendering expects the new offset. */
2245         view->offset += lines;
2247         assert(0 <= view->offset && view->offset < view->lines);
2248         assert(lines);
2250         /* Move current line into the view. */
2251         if (view->lineno < view->offset) {
2252                 view->lineno = view->offset;
2253                 redraw_current_line = TRUE;
2254         } else if (view->lineno >= view->offset + view->height) {
2255                 view->lineno = view->offset + view->height - 1;
2256                 redraw_current_line = TRUE;
2257         }
2259         assert(view->offset <= view->lineno && view->lineno < view->lines);
2261         /* Redraw the whole screen if scrolling is pointless. */
2262         if (view->height < ABS(lines)) {
2263                 redraw_view(view);
2265         } else {
2266                 int line = lines > 0 ? view->height - lines : 0;
2267                 int end = line + ABS(lines);
2269                 scrollok(view->win, TRUE);
2270                 wscrl(view->win, lines);
2271                 scrollok(view->win, FALSE);
2273                 while (line < end && draw_view_line(view, line))
2274                         line++;
2276                 if (redraw_current_line)
2277                         draw_view_line(view, view->lineno - view->offset);
2278                 wnoutrefresh(view->win);
2279         }
2281         view->has_scrolled = TRUE;
2282         report("");
2285 /* Scroll frontend */
2286 static void
2287 scroll_view(struct view *view, enum request request)
2289         int lines = 1;
2291         assert(view_is_displayed(view));
2293         switch (request) {
2294         case REQ_SCROLL_PAGE_DOWN:
2295                 lines = view->height;
2296         case REQ_SCROLL_LINE_DOWN:
2297                 if (view->offset + lines > view->lines)
2298                         lines = view->lines - view->offset;
2300                 if (lines == 0 || view->offset + view->height >= view->lines) {
2301                         report("Cannot scroll beyond the last line");
2302                         return;
2303                 }
2304                 break;
2306         case REQ_SCROLL_PAGE_UP:
2307                 lines = view->height;
2308         case REQ_SCROLL_LINE_UP:
2309                 if (lines > view->offset)
2310                         lines = view->offset;
2312                 if (lines == 0) {
2313                         report("Cannot scroll beyond the first line");
2314                         return;
2315                 }
2317                 lines = -lines;
2318                 break;
2320         default:
2321                 die("request %d not handled in switch", request);
2322         }
2324         do_scroll_view(view, lines);
2327 /* Cursor moving */
2328 static void
2329 move_view(struct view *view, enum request request)
2331         int scroll_steps = 0;
2332         int steps;
2334         switch (request) {
2335         case REQ_MOVE_FIRST_LINE:
2336                 steps = -view->lineno;
2337                 break;
2339         case REQ_MOVE_LAST_LINE:
2340                 steps = view->lines - view->lineno - 1;
2341                 break;
2343         case REQ_MOVE_PAGE_UP:
2344                 steps = view->height > view->lineno
2345                       ? -view->lineno : -view->height;
2346                 break;
2348         case REQ_MOVE_PAGE_DOWN:
2349                 steps = view->lineno + view->height >= view->lines
2350                       ? view->lines - view->lineno - 1 : view->height;
2351                 break;
2353         case REQ_MOVE_UP:
2354                 steps = -1;
2355                 break;
2357         case REQ_MOVE_DOWN:
2358                 steps = 1;
2359                 break;
2361         default:
2362                 die("request %d not handled in switch", request);
2363         }
2365         if (steps <= 0 && view->lineno == 0) {
2366                 report("Cannot move beyond the first line");
2367                 return;
2369         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2370                 report("Cannot move beyond the last line");
2371                 return;
2372         }
2374         /* Move the current line */
2375         view->lineno += steps;
2376         assert(0 <= view->lineno && view->lineno < view->lines);
2378         /* Check whether the view needs to be scrolled */
2379         if (view->lineno < view->offset ||
2380             view->lineno >= view->offset + view->height) {
2381                 scroll_steps = steps;
2382                 if (steps < 0 && -steps > view->offset) {
2383                         scroll_steps = -view->offset;
2385                 } else if (steps > 0) {
2386                         if (view->lineno == view->lines - 1 &&
2387                             view->lines > view->height) {
2388                                 scroll_steps = view->lines - view->offset - 1;
2389                                 if (scroll_steps >= view->height)
2390                                         scroll_steps -= view->height - 1;
2391                         }
2392                 }
2393         }
2395         if (!view_is_displayed(view)) {
2396                 view->offset += scroll_steps;
2397                 assert(0 <= view->offset && view->offset < view->lines);
2398                 view->ops->select(view, &view->line[view->lineno]);
2399                 return;
2400         }
2402         /* Repaint the old "current" line if we be scrolling */
2403         if (ABS(steps) < view->height)
2404                 draw_view_line(view, view->lineno - steps - view->offset);
2406         if (scroll_steps) {
2407                 do_scroll_view(view, scroll_steps);
2408                 return;
2409         }
2411         /* Draw the current line */
2412         draw_view_line(view, view->lineno - view->offset);
2414         wnoutrefresh(view->win);
2415         report("");
2419 /*
2420  * Searching
2421  */
2423 static void search_view(struct view *view, enum request request);
2425 static void
2426 select_view_line(struct view *view, unsigned long lineno)
2428         if (lineno - view->offset >= view->height) {
2429                 view->offset = lineno;
2430                 view->lineno = lineno;
2431                 if (view_is_displayed(view))
2432                         redraw_view(view);
2434         } else {
2435                 unsigned long old_lineno = view->lineno - view->offset;
2437                 view->lineno = lineno;
2438                 if (view_is_displayed(view)) {
2439                         draw_view_line(view, old_lineno);
2440                         draw_view_line(view, view->lineno - view->offset);
2441                         wnoutrefresh(view->win);
2442                 } else {
2443                         view->ops->select(view, &view->line[view->lineno]);
2444                 }
2445         }
2448 static void
2449 find_next(struct view *view, enum request request)
2451         unsigned long lineno = view->lineno;
2452         int direction;
2454         if (!*view->grep) {
2455                 if (!*opt_search)
2456                         report("No previous search");
2457                 else
2458                         search_view(view, request);
2459                 return;
2460         }
2462         switch (request) {
2463         case REQ_SEARCH:
2464         case REQ_FIND_NEXT:
2465                 direction = 1;
2466                 break;
2468         case REQ_SEARCH_BACK:
2469         case REQ_FIND_PREV:
2470                 direction = -1;
2471                 break;
2473         default:
2474                 return;
2475         }
2477         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2478                 lineno += direction;
2480         /* Note, lineno is unsigned long so will wrap around in which case it
2481          * will become bigger than view->lines. */
2482         for (; lineno < view->lines; lineno += direction) {
2483                 if (view->ops->grep(view, &view->line[lineno])) {
2484                         select_view_line(view, lineno);
2485                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2486                         return;
2487                 }
2488         }
2490         report("No match found for '%s'", view->grep);
2493 static void
2494 search_view(struct view *view, enum request request)
2496         int regex_err;
2498         if (view->regex) {
2499                 regfree(view->regex);
2500                 *view->grep = 0;
2501         } else {
2502                 view->regex = calloc(1, sizeof(*view->regex));
2503                 if (!view->regex)
2504                         return;
2505         }
2507         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2508         if (regex_err != 0) {
2509                 char buf[SIZEOF_STR] = "unknown error";
2511                 regerror(regex_err, view->regex, buf, sizeof(buf));
2512                 report("Search failed: %s", buf);
2513                 return;
2514         }
2516         string_copy(view->grep, opt_search);
2518         find_next(view, request);
2521 /*
2522  * Incremental updating
2523  */
2525 static void
2526 reset_view(struct view *view)
2528         int i;
2530         for (i = 0; i < view->lines; i++)
2531                 free(view->line[i].data);
2532         free(view->line);
2534         view->p_offset = view->offset;
2535         view->p_lineno = view->lineno;
2537         view->line = NULL;
2538         view->offset = 0;
2539         view->lines  = 0;
2540         view->lineno = 0;
2541         view->line_alloc = 0;
2542         view->vid[0] = 0;
2543         view->update_secs = 0;
2546 static void
2547 free_argv(const char *argv[])
2549         int argc;
2551         for (argc = 0; argv[argc]; argc++)
2552                 free((void *) argv[argc]);
2555 static bool
2556 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2558         char buf[SIZEOF_STR];
2559         int argc;
2560         bool noreplace = flags == FORMAT_NONE;
2562         free_argv(dst_argv);
2564         for (argc = 0; src_argv[argc]; argc++) {
2565                 const char *arg = src_argv[argc];
2566                 size_t bufpos = 0;
2568                 while (arg) {
2569                         char *next = strstr(arg, "%(");
2570                         int len = next - arg;
2571                         const char *value;
2573                         if (!next || noreplace) {
2574                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2575                                         noreplace = TRUE;
2576                                 len = strlen(arg);
2577                                 value = "";
2579                         } else if (!prefixcmp(next, "%(directory)")) {
2580                                 value = opt_path;
2582                         } else if (!prefixcmp(next, "%(file)")) {
2583                                 value = opt_file;
2585                         } else if (!prefixcmp(next, "%(ref)")) {
2586                                 value = *opt_ref ? opt_ref : "HEAD";
2588                         } else if (!prefixcmp(next, "%(head)")) {
2589                                 value = ref_head;
2591                         } else if (!prefixcmp(next, "%(commit)")) {
2592                                 value = ref_commit;
2594                         } else if (!prefixcmp(next, "%(blob)")) {
2595                                 value = ref_blob;
2597                         } else {
2598                                 report("Unknown replacement: `%s`", next);
2599                                 return FALSE;
2600                         }
2602                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2603                                 return FALSE;
2605                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2606                 }
2608                 dst_argv[argc] = strdup(buf);
2609                 if (!dst_argv[argc])
2610                         break;
2611         }
2613         dst_argv[argc] = NULL;
2615         return src_argv[argc] == NULL;
2618 static bool
2619 restore_view_position(struct view *view)
2621         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2622                 return FALSE;
2624         /* Changing the view position cancels the restoring. */
2625         /* FIXME: Changing back to the first line is not detected. */
2626         if (view->offset != 0 || view->lineno != 0) {
2627                 view->p_restore = FALSE;
2628                 return FALSE;
2629         }
2631         if (view->p_lineno >= view->lines) {
2632                 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2633                 if (view->p_offset >= view->p_lineno) {
2634                         unsigned long half = view->height / 2;
2636                         if (view->p_lineno > half)
2637                                 view->p_offset = view->p_lineno - half;
2638                         else
2639                                 view->p_offset = 0;
2640                 }
2641         }
2643         if (view_is_displayed(view) &&
2644             view->offset != view->p_offset &&
2645             view->lineno != view->p_lineno)
2646                 werase(view->win);
2648         view->offset = view->p_offset;
2649         view->lineno = view->p_lineno;
2650         view->p_restore = FALSE;
2652         return TRUE;
2655 static void
2656 end_update(struct view *view, bool force)
2658         if (!view->pipe)
2659                 return;
2660         while (!view->ops->read(view, NULL))
2661                 if (!force)
2662                         return;
2663         set_nonblocking_input(FALSE);
2664         if (force)
2665                 kill_io(view->pipe);
2666         done_io(view->pipe);
2667         view->pipe = NULL;
2670 static void
2671 setup_update(struct view *view, const char *vid)
2673         set_nonblocking_input(TRUE);
2674         reset_view(view);
2675         string_copy_rev(view->vid, vid);
2676         view->pipe = &view->io;
2677         view->start_time = time(NULL);
2680 static bool
2681 prepare_update(struct view *view, const char *argv[], const char *dir,
2682                enum format_flags flags)
2684         if (view->pipe)
2685                 end_update(view, TRUE);
2686         return init_io_rd(&view->io, argv, dir, flags);
2689 static bool
2690 prepare_update_file(struct view *view, const char *name)
2692         if (view->pipe)
2693                 end_update(view, TRUE);
2694         return io_open(&view->io, name);
2697 static bool
2698 begin_update(struct view *view, bool refresh)
2700         if (view->pipe)
2701                 end_update(view, TRUE);
2703         if (refresh) {
2704                 if (!start_io(&view->io))
2705                         return FALSE;
2707         } else {
2708                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2709                         opt_path[0] = 0;
2711                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2712                         return FALSE;
2714                 /* Put the current ref_* value to the view title ref
2715                  * member. This is needed by the blob view. Most other
2716                  * views sets it automatically after loading because the
2717                  * first line is a commit line. */
2718                 string_copy_rev(view->ref, view->id);
2719         }
2721         setup_update(view, view->id);
2723         return TRUE;
2726 #define ITEM_CHUNK_SIZE 256
2727 static void *
2728 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2730         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2731         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2733         if (mem == NULL || num_chunks != num_chunks_new) {
2734                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2735                 mem = realloc(mem, *size * item_size);
2736         }
2738         return mem;
2741 static struct line *
2742 realloc_lines(struct view *view, size_t line_size)
2744         size_t alloc = view->line_alloc;
2745         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2746                                          sizeof(*view->line));
2748         if (!tmp)
2749                 return NULL;
2751         view->line = tmp;
2752         view->line_alloc = alloc;
2753         return view->line;
2756 static bool
2757 update_view(struct view *view)
2759         char out_buffer[BUFSIZ * 2];
2760         char *line;
2761         /* Clear the view and redraw everything since the tree sorting
2762          * might have rearranged things. */
2763         bool redraw = view->lines == 0;
2764         bool can_read = TRUE;
2766         if (!view->pipe)
2767                 return TRUE;
2769         if (!io_can_read(view->pipe)) {
2770                 if (view->lines == 0) {
2771                         time_t secs = time(NULL) - view->start_time;
2773                         if (secs > 1 && secs > view->update_secs) {
2774                                 if (view->update_secs == 0)
2775                                         redraw_view(view);
2776                                 update_view_title(view);
2777                                 view->update_secs = secs;
2778                         }
2779                 }
2780                 return TRUE;
2781         }
2783         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2784                 if (opt_iconv != ICONV_NONE) {
2785                         ICONV_CONST char *inbuf = line;
2786                         size_t inlen = strlen(line) + 1;
2788                         char *outbuf = out_buffer;
2789                         size_t outlen = sizeof(out_buffer);
2791                         size_t ret;
2793                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2794                         if (ret != (size_t) -1)
2795                                 line = out_buffer;
2796                 }
2798                 if (!view->ops->read(view, line)) {
2799                         report("Allocation failure");
2800                         end_update(view, TRUE);
2801                         return FALSE;
2802                 }
2803         }
2805         {
2806                 unsigned long lines = view->lines;
2807                 int digits;
2809                 for (digits = 0; lines; digits++)
2810                         lines /= 10;
2812                 /* Keep the displayed view in sync with line number scaling. */
2813                 if (digits != view->digits) {
2814                         view->digits = digits;
2815                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2816                                 redraw = TRUE;
2817                 }
2818         }
2820         if (io_error(view->pipe)) {
2821                 report("Failed to read: %s", io_strerror(view->pipe));
2822                 end_update(view, TRUE);
2824         } else if (io_eof(view->pipe)) {
2825                 report("");
2826                 end_update(view, FALSE);
2827         }
2829         if (restore_view_position(view))
2830                 redraw = TRUE;
2832         if (!view_is_displayed(view))
2833                 return TRUE;
2835         if (redraw)
2836                 redraw_view_from(view, 0);
2837         else
2838                 redraw_view_dirty(view);
2840         /* Update the title _after_ the redraw so that if the redraw picks up a
2841          * commit reference in view->ref it'll be available here. */
2842         update_view_title(view);
2843         return TRUE;
2846 static struct line *
2847 add_line_data(struct view *view, void *data, enum line_type type)
2849         struct line *line;
2851         if (!realloc_lines(view, view->lines + 1))
2852                 return NULL;
2854         line = &view->line[view->lines++];
2855         memset(line, 0, sizeof(*line));
2856         line->type = type;
2857         line->data = data;
2858         line->dirty = 1;
2860         return line;
2863 static struct line *
2864 add_line_text(struct view *view, const char *text, enum line_type type)
2866         char *data = text ? strdup(text) : NULL;
2868         return data ? add_line_data(view, data, type) : NULL;
2871 static struct line *
2872 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2874         char buf[SIZEOF_STR];
2875         va_list args;
2877         va_start(args, fmt);
2878         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2879                 buf[0] = 0;
2880         va_end(args);
2882         return buf[0] ? add_line_text(view, buf, type) : NULL;
2885 /*
2886  * View opening
2887  */
2889 enum open_flags {
2890         OPEN_DEFAULT = 0,       /* Use default view switching. */
2891         OPEN_SPLIT = 1,         /* Split current view. */
2892         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2893         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2894         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2895         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2896         OPEN_PREPARED = 32,     /* Open already prepared command. */
2897 };
2899 static void
2900 open_view(struct view *prev, enum request request, enum open_flags flags)
2902         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2903         bool split = !!(flags & OPEN_SPLIT);
2904         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2905         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2906         struct view *view = VIEW(request);
2907         int nviews = displayed_views();
2908         struct view *base_view = display[0];
2910         if (view == prev && nviews == 1 && !reload) {
2911                 report("Already in %s view", view->name);
2912                 return;
2913         }
2915         if (view->git_dir && !opt_git_dir[0]) {
2916                 report("The %s view is disabled in pager view", view->name);
2917                 return;
2918         }
2920         if (split) {
2921                 display[1] = view;
2922                 if (!backgrounded)
2923                         current_view = 1;
2924         } else if (!nomaximize) {
2925                 /* Maximize the current view. */
2926                 memset(display, 0, sizeof(display));
2927                 current_view = 0;
2928                 display[current_view] = view;
2929         }
2931         /* Resize the view when switching between split- and full-screen,
2932          * or when switching between two different full-screen views. */
2933         if (nviews != displayed_views() ||
2934             (nviews == 1 && base_view != display[0]))
2935                 resize_display();
2937         if (view->ops->open) {
2938                 if (view->pipe)
2939                         end_update(view, TRUE);
2940                 if (!view->ops->open(view)) {
2941                         report("Failed to load %s view", view->name);
2942                         return;
2943                 }
2944                 restore_view_position(view);
2946         } else if ((reload || strcmp(view->vid, view->id)) &&
2947                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2948                 report("Failed to load %s view", view->name);
2949                 return;
2950         }
2952         if (split && prev->lineno - prev->offset >= prev->height) {
2953                 /* Take the title line into account. */
2954                 int lines = prev->lineno - prev->offset - prev->height + 1;
2956                 /* Scroll the view that was split if the current line is
2957                  * outside the new limited view. */
2958                 do_scroll_view(prev, lines);
2959         }
2961         if (prev && view != prev) {
2962                 if (split && !backgrounded) {
2963                         /* "Blur" the previous view. */
2964                         update_view_title(prev);
2965                 }
2967                 view->parent = prev;
2968         }
2970         if (view->pipe && view->lines == 0) {
2971                 /* Clear the old view and let the incremental updating refill
2972                  * the screen. */
2973                 werase(view->win);
2974                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
2975                 report("");
2976         } else if (view_is_displayed(view)) {
2977                 redraw_view(view);
2978                 report("");
2979         }
2981         /* If the view is backgrounded the above calls to report()
2982          * won't redraw the view title. */
2983         if (backgrounded)
2984                 update_view_title(view);
2987 static void
2988 open_external_viewer(const char *argv[], const char *dir)
2990         def_prog_mode();           /* save current tty modes */
2991         endwin();                  /* restore original tty modes */
2992         run_io_fg(argv, dir);
2993         fprintf(stderr, "Press Enter to continue");
2994         getc(opt_tty);
2995         reset_prog_mode();
2996         redraw_display(TRUE);
2999 static void
3000 open_mergetool(const char *file)
3002         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3004         open_external_viewer(mergetool_argv, opt_cdup);
3007 static void
3008 open_editor(bool from_root, const char *file)
3010         const char *editor_argv[] = { "vi", file, NULL };
3011         const char *editor;
3013         editor = getenv("GIT_EDITOR");
3014         if (!editor && *opt_editor)
3015                 editor = opt_editor;
3016         if (!editor)
3017                 editor = getenv("VISUAL");
3018         if (!editor)
3019                 editor = getenv("EDITOR");
3020         if (!editor)
3021                 editor = "vi";
3023         editor_argv[0] = editor;
3024         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3027 static void
3028 open_run_request(enum request request)
3030         struct run_request *req = get_run_request(request);
3031         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3033         if (!req) {
3034                 report("Unknown run request");
3035                 return;
3036         }
3038         if (format_argv(argv, req->argv, FORMAT_ALL))
3039                 open_external_viewer(argv, NULL);
3040         free_argv(argv);
3043 /*
3044  * User request switch noodle
3045  */
3047 static int
3048 view_driver(struct view *view, enum request request)
3050         int i;
3052         if (request == REQ_NONE) {
3053                 doupdate();
3054                 return TRUE;
3055         }
3057         if (request > REQ_NONE) {
3058                 open_run_request(request);
3059                 /* FIXME: When all views can refresh always do this. */
3060                 if (view == VIEW(REQ_VIEW_STATUS) ||
3061                     view == VIEW(REQ_VIEW_MAIN) ||
3062                     view == VIEW(REQ_VIEW_LOG) ||
3063                     view == VIEW(REQ_VIEW_STAGE))
3064                         request = REQ_REFRESH;
3065                 else
3066                         return TRUE;
3067         }
3069         if (view && view->lines) {
3070                 request = view->ops->request(view, request, &view->line[view->lineno]);
3071                 if (request == REQ_NONE)
3072                         return TRUE;
3073         }
3075         switch (request) {
3076         case REQ_MOVE_UP:
3077         case REQ_MOVE_DOWN:
3078         case REQ_MOVE_PAGE_UP:
3079         case REQ_MOVE_PAGE_DOWN:
3080         case REQ_MOVE_FIRST_LINE:
3081         case REQ_MOVE_LAST_LINE:
3082                 move_view(view, request);
3083                 break;
3085         case REQ_SCROLL_LINE_DOWN:
3086         case REQ_SCROLL_LINE_UP:
3087         case REQ_SCROLL_PAGE_DOWN:
3088         case REQ_SCROLL_PAGE_UP:
3089                 scroll_view(view, request);
3090                 break;
3092         case REQ_VIEW_BLAME:
3093                 if (!opt_file[0]) {
3094                         report("No file chosen, press %s to open tree view",
3095                                get_key(REQ_VIEW_TREE));
3096                         break;
3097                 }
3098                 open_view(view, request, OPEN_DEFAULT);
3099                 break;
3101         case REQ_VIEW_BLOB:
3102                 if (!ref_blob[0]) {
3103                         report("No file chosen, press %s to open tree view",
3104                                get_key(REQ_VIEW_TREE));
3105                         break;
3106                 }
3107                 open_view(view, request, OPEN_DEFAULT);
3108                 break;
3110         case REQ_VIEW_PAGER:
3111                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3112                         report("No pager content, press %s to run command from prompt",
3113                                get_key(REQ_PROMPT));
3114                         break;
3115                 }
3116                 open_view(view, request, OPEN_DEFAULT);
3117                 break;
3119         case REQ_VIEW_STAGE:
3120                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3121                         report("No stage content, press %s to open the status view and choose file",
3122                                get_key(REQ_VIEW_STATUS));
3123                         break;
3124                 }
3125                 open_view(view, request, OPEN_DEFAULT);
3126                 break;
3128         case REQ_VIEW_STATUS:
3129                 if (opt_is_inside_work_tree == FALSE) {
3130                         report("The status view requires a working tree");
3131                         break;
3132                 }
3133                 open_view(view, request, OPEN_DEFAULT);
3134                 break;
3136         case REQ_VIEW_MAIN:
3137         case REQ_VIEW_DIFF:
3138         case REQ_VIEW_LOG:
3139         case REQ_VIEW_TREE:
3140         case REQ_VIEW_HELP:
3141                 open_view(view, request, OPEN_DEFAULT);
3142                 break;
3144         case REQ_NEXT:
3145         case REQ_PREVIOUS:
3146                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3148                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3149                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3150                    (view == VIEW(REQ_VIEW_DIFF) &&
3151                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3152                    (view == VIEW(REQ_VIEW_STAGE) &&
3153                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3154                    (view == VIEW(REQ_VIEW_BLOB) &&
3155                      view->parent == VIEW(REQ_VIEW_TREE))) {
3156                         int line;
3158                         view = view->parent;
3159                         line = view->lineno;
3160                         move_view(view, request);
3161                         if (view_is_displayed(view))
3162                                 update_view_title(view);
3163                         if (line != view->lineno)
3164                                 view->ops->request(view, REQ_ENTER,
3165                                                    &view->line[view->lineno]);
3167                 } else {
3168                         move_view(view, request);
3169                 }
3170                 break;
3172         case REQ_VIEW_NEXT:
3173         {
3174                 int nviews = displayed_views();
3175                 int next_view = (current_view + 1) % nviews;
3177                 if (next_view == current_view) {
3178                         report("Only one view is displayed");
3179                         break;
3180                 }
3182                 current_view = next_view;
3183                 /* Blur out the title of the previous view. */
3184                 update_view_title(view);
3185                 report("");
3186                 break;
3187         }
3188         case REQ_REFRESH:
3189                 report("Refreshing is not yet supported for the %s view", view->name);
3190                 break;
3192         case REQ_MAXIMIZE:
3193                 if (displayed_views() == 2)
3194                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3195                 break;
3197         case REQ_TOGGLE_LINENO:
3198                 toggle_view_option(&opt_line_number, "line numbers");
3199                 break;
3201         case REQ_TOGGLE_DATE:
3202                 toggle_view_option(&opt_date, "date display");
3203                 break;
3205         case REQ_TOGGLE_AUTHOR:
3206                 toggle_view_option(&opt_author, "author display");
3207                 break;
3209         case REQ_TOGGLE_REV_GRAPH:
3210                 toggle_view_option(&opt_rev_graph, "revision graph display");
3211                 break;
3213         case REQ_TOGGLE_REFS:
3214                 toggle_view_option(&opt_show_refs, "reference display");
3215                 break;
3217         case REQ_SEARCH:
3218         case REQ_SEARCH_BACK:
3219                 search_view(view, request);
3220                 break;
3222         case REQ_FIND_NEXT:
3223         case REQ_FIND_PREV:
3224                 find_next(view, request);
3225                 break;
3227         case REQ_STOP_LOADING:
3228                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3229                         view = &views[i];
3230                         if (view->pipe)
3231                                 report("Stopped loading the %s view", view->name),
3232                         end_update(view, TRUE);
3233                 }
3234                 break;
3236         case REQ_SHOW_VERSION:
3237                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3238                 return TRUE;
3240         case REQ_SCREEN_REDRAW:
3241                 redraw_display(TRUE);
3242                 break;
3244         case REQ_EDIT:
3245                 report("Nothing to edit");
3246                 break;
3248         case REQ_ENTER:
3249                 report("Nothing to enter");
3250                 break;
3252         case REQ_VIEW_CLOSE:
3253                 /* XXX: Mark closed views by letting view->parent point to the
3254                  * view itself. Parents to closed view should never be
3255                  * followed. */
3256                 if (view->parent &&
3257                     view->parent->parent != view->parent) {
3258                         memset(display, 0, sizeof(display));
3259                         current_view = 0;
3260                         display[current_view] = view->parent;
3261                         view->parent = view;
3262                         resize_display();
3263                         redraw_display(FALSE);
3264                         report("");
3265                         break;
3266                 }
3267                 /* Fall-through */
3268         case REQ_QUIT:
3269                 return FALSE;
3271         default:
3272                 report("Unknown key, press 'h' for help");
3273                 return TRUE;
3274         }
3276         return TRUE;
3280 /*
3281  * View backend utilities
3282  */
3284 /* Parse author lines where the name may be empty:
3285  *      author  <email@address.tld> 1138474660 +0100
3286  */
3287 static void
3288 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3290         char *nameend = strchr(ident, '<');
3291         char *emailend = strchr(ident, '>');
3293         if (nameend && emailend)
3294                 *nameend = *emailend = 0;
3295         ident = chomp_string(ident);
3296         if (!*ident) {
3297                 if (nameend)
3298                         ident = chomp_string(nameend + 1);
3299                 if (!*ident)
3300                         ident = "Unknown";
3301         }
3303         string_ncopy_do(author, authorsize, ident, strlen(ident));
3305         /* Parse epoch and timezone */
3306         if (emailend && emailend[1] == ' ') {
3307                 char *secs = emailend + 2;
3308                 char *zone = strchr(secs, ' ');
3309                 time_t time = (time_t) atol(secs);
3311                 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3312                         long tz;
3314                         zone++;
3315                         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3316                         tz += ('0' - zone[2]) * 60 * 60;
3317                         tz += ('0' - zone[3]) * 60;
3318                         tz += ('0' - zone[4]) * 60;
3320                         if (zone[0] == '-')
3321                                 tz = -tz;
3323                         time -= tz;
3324                 }
3326                 gmtime_r(&time, tm);
3327         }
3330 static enum input_status
3331 select_commit_parent_handler(void *data, char *buf, int c)
3333         size_t parents = *(size_t *) data;
3334         int parent = 0;
3336         if (!isdigit(c))
3337                 return INPUT_SKIP;
3339         if (*buf)
3340                 parent = atoi(buf) * 10;
3341         parent += c - '0';
3343         if (parent > parents)
3344                 return INPUT_SKIP;
3345         return INPUT_OK;
3348 static bool
3349 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3351         char buf[SIZEOF_STR * 4];
3352         const char *revlist_argv[] = {
3353                 "git", "rev-list", "-1", "--parents", id, NULL
3354         };
3355         int parents;
3357         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3358             !*chomp_string(buf) ||
3359             (parents = (strlen(buf) / 40) - 1) < 0) {
3360                 report("Failed to get parent information");
3361                 return FALSE;
3363         } else if (parents == 0) {
3364                 report("The selected commit has no parents");
3365                 return FALSE;
3366         }
3368         if (parents > 1) {
3369                 char prompt[SIZEOF_STR];
3370                 char *result;
3372                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3373                         return FALSE;
3374                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3375                 if (!result)
3376                         return FALSE;
3377                 parents = atoi(result);
3378         }
3380         string_copy_rev(rev, &buf[41 * parents]);
3381         return TRUE;
3384 /*
3385  * Pager backend
3386  */
3388 static bool
3389 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3391         char text[SIZEOF_STR];
3393         if (opt_line_number && draw_lineno(view, lineno))
3394                 return TRUE;
3396         string_expand(text, sizeof(text), line->data, opt_tab_size);
3397         draw_text(view, line->type, text, TRUE);
3398         return TRUE;
3401 static bool
3402 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3404         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3405         char refbuf[SIZEOF_STR];
3406         char *ref = NULL;
3408         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3409                 ref = chomp_string(refbuf);
3411         if (!ref || !*ref)
3412                 return TRUE;
3414         /* This is the only fatal call, since it can "corrupt" the buffer. */
3415         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3416                 return FALSE;
3418         return TRUE;
3421 static void
3422 add_pager_refs(struct view *view, struct line *line)
3424         char buf[SIZEOF_STR];
3425         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3426         struct ref **refs;
3427         size_t bufpos = 0, refpos = 0;
3428         const char *sep = "Refs: ";
3429         bool is_tag = FALSE;
3431         assert(line->type == LINE_COMMIT);
3433         refs = get_refs(commit_id);
3434         if (!refs) {
3435                 if (view == VIEW(REQ_VIEW_DIFF))
3436                         goto try_add_describe_ref;
3437                 return;
3438         }
3440         do {
3441                 struct ref *ref = refs[refpos];
3442                 const char *fmt = ref->tag    ? "%s[%s]" :
3443                                   ref->remote ? "%s<%s>" : "%s%s";
3445                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3446                         return;
3447                 sep = ", ";
3448                 if (ref->tag)
3449                         is_tag = TRUE;
3450         } while (refs[refpos++]->next);
3452         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3453 try_add_describe_ref:
3454                 /* Add <tag>-g<commit_id> "fake" reference. */
3455                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3456                         return;
3457         }
3459         if (bufpos == 0)
3460                 return;
3462         add_line_text(view, buf, LINE_PP_REFS);
3465 static bool
3466 pager_read(struct view *view, char *data)
3468         struct line *line;
3470         if (!data)
3471                 return TRUE;
3473         line = add_line_text(view, data, get_line_type(data));
3474         if (!line)
3475                 return FALSE;
3477         if (line->type == LINE_COMMIT &&
3478             (view == VIEW(REQ_VIEW_DIFF) ||
3479              view == VIEW(REQ_VIEW_LOG)))
3480                 add_pager_refs(view, line);
3482         return TRUE;
3485 static enum request
3486 pager_request(struct view *view, enum request request, struct line *line)
3488         int split = 0;
3490         if (request != REQ_ENTER)
3491                 return request;
3493         if (line->type == LINE_COMMIT &&
3494            (view == VIEW(REQ_VIEW_LOG) ||
3495             view == VIEW(REQ_VIEW_PAGER))) {
3496                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3497                 split = 1;
3498         }
3500         /* Always scroll the view even if it was split. That way
3501          * you can use Enter to scroll through the log view and
3502          * split open each commit diff. */
3503         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3505         /* FIXME: A minor workaround. Scrolling the view will call report("")
3506          * but if we are scrolling a non-current view this won't properly
3507          * update the view title. */
3508         if (split)
3509                 update_view_title(view);
3511         return REQ_NONE;
3514 static bool
3515 pager_grep(struct view *view, struct line *line)
3517         regmatch_t pmatch;
3518         char *text = line->data;
3520         if (!*text)
3521                 return FALSE;
3523         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3524                 return FALSE;
3526         return TRUE;
3529 static void
3530 pager_select(struct view *view, struct line *line)
3532         if (line->type == LINE_COMMIT) {
3533                 char *text = (char *)line->data + STRING_SIZE("commit ");
3535                 if (view != VIEW(REQ_VIEW_PAGER))
3536                         string_copy_rev(view->ref, text);
3537                 string_copy_rev(ref_commit, text);
3538         }
3541 static struct view_ops pager_ops = {
3542         "line",
3543         NULL,
3544         NULL,
3545         pager_read,
3546         pager_draw,
3547         pager_request,
3548         pager_grep,
3549         pager_select,
3550 };
3552 static const char *log_argv[SIZEOF_ARG] = {
3553         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3554 };
3556 static enum request
3557 log_request(struct view *view, enum request request, struct line *line)
3559         switch (request) {
3560         case REQ_REFRESH:
3561                 load_refs();
3562                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3563                 return REQ_NONE;
3564         default:
3565                 return pager_request(view, request, line);
3566         }
3569 static struct view_ops log_ops = {
3570         "line",
3571         log_argv,
3572         NULL,
3573         pager_read,
3574         pager_draw,
3575         log_request,
3576         pager_grep,
3577         pager_select,
3578 };
3580 static const char *diff_argv[SIZEOF_ARG] = {
3581         "git", "show", "--pretty=fuller", "--no-color", "--root",
3582                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3583 };
3585 static struct view_ops diff_ops = {
3586         "line",
3587         diff_argv,
3588         NULL,
3589         pager_read,
3590         pager_draw,
3591         pager_request,
3592         pager_grep,
3593         pager_select,
3594 };
3596 /*
3597  * Help backend
3598  */
3600 static bool
3601 help_open(struct view *view)
3603         char buf[SIZEOF_STR];
3604         size_t bufpos;
3605         int i;
3607         if (view->lines > 0)
3608                 return TRUE;
3610         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3612         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3613                 const char *key;
3615                 if (req_info[i].request == REQ_NONE)
3616                         continue;
3618                 if (!req_info[i].request) {
3619                         add_line_text(view, "", LINE_DEFAULT);
3620                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3621                         continue;
3622                 }
3624                 key = get_key(req_info[i].request);
3625                 if (!*key)
3626                         key = "(no key defined)";
3628                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3629                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3630                         if (buf[bufpos] == '_')
3631                                 buf[bufpos] = '-';
3632                 }
3634                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3635                                 key, buf, req_info[i].help);
3636         }
3638         if (run_requests) {
3639                 add_line_text(view, "", LINE_DEFAULT);
3640                 add_line_text(view, "External commands:", LINE_DEFAULT);
3641         }
3643         for (i = 0; i < run_requests; i++) {
3644                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3645                 const char *key;
3646                 int argc;
3648                 if (!req)
3649                         continue;
3651                 key = get_key_name(req->key);
3652                 if (!*key)
3653                         key = "(no key defined)";
3655                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3656                         if (!string_format_from(buf, &bufpos, "%s%s",
3657                                                 argc ? " " : "", req->argv[argc]))
3658                                 return REQ_NONE;
3660                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3661                                 keymap_table[req->keymap].name, key, buf);
3662         }
3664         return TRUE;
3667 static struct view_ops help_ops = {
3668         "line",
3669         NULL,
3670         help_open,
3671         NULL,
3672         pager_draw,
3673         pager_request,
3674         pager_grep,
3675         pager_select,
3676 };
3679 /*
3680  * Tree backend
3681  */
3683 struct tree_stack_entry {
3684         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3685         unsigned long lineno;           /* Line number to restore */
3686         char *name;                     /* Position of name in opt_path */
3687 };
3689 /* The top of the path stack. */
3690 static struct tree_stack_entry *tree_stack = NULL;
3691 unsigned long tree_lineno = 0;
3693 static void
3694 pop_tree_stack_entry(void)
3696         struct tree_stack_entry *entry = tree_stack;
3698         tree_lineno = entry->lineno;
3699         entry->name[0] = 0;
3700         tree_stack = entry->prev;
3701         free(entry);
3704 static void
3705 push_tree_stack_entry(const char *name, unsigned long lineno)
3707         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3708         size_t pathlen = strlen(opt_path);
3710         if (!entry)
3711                 return;
3713         entry->prev = tree_stack;
3714         entry->name = opt_path + pathlen;
3715         tree_stack = entry;
3717         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3718                 pop_tree_stack_entry();
3719                 return;
3720         }
3722         /* Move the current line to the first tree entry. */
3723         tree_lineno = 1;
3724         entry->lineno = lineno;
3727 /* Parse output from git-ls-tree(1):
3728  *
3729  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3730  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3731  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3732  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3733  */
3735 #define SIZEOF_TREE_ATTR \
3736         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3738 #define SIZEOF_TREE_MODE \
3739         STRING_SIZE("100644 ")
3741 #define TREE_ID_OFFSET \
3742         STRING_SIZE("100644 blob ")
3744 struct tree_entry {
3745         char id[SIZEOF_REV];
3746         mode_t mode;
3747         struct tm time;                 /* Date from the author ident. */
3748         char author[75];                /* Author of the commit. */
3749         char name[1];
3750 };
3752 static const char *
3753 tree_path(struct line *line)
3755         return ((struct tree_entry *) line->data)->name;
3759 static int
3760 tree_compare_entry(struct line *line1, struct line *line2)
3762         if (line1->type != line2->type)
3763                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3764         return strcmp(tree_path(line1), tree_path(line2));
3767 static struct line *
3768 tree_entry(struct view *view, enum line_type type, const char *path,
3769            const char *mode, const char *id)
3771         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3772         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3774         if (!entry || !line) {
3775                 free(entry);
3776                 return NULL;
3777         }
3779         strncpy(entry->name, path, strlen(path));
3780         if (mode)
3781                 entry->mode = strtoul(mode, NULL, 8);
3782         if (id)
3783                 string_copy_rev(entry->id, id);
3785         return line;
3788 static bool
3789 tree_read_date(struct view *view, char *text, bool *read_date)
3791         static char author_name[SIZEOF_STR];
3792         static struct tm author_time;
3794         if (!text && *read_date) {
3795                 *read_date = FALSE;
3796                 return TRUE;
3798         } else if (!text) {
3799                 char *path = *opt_path ? opt_path : ".";
3800                 /* Find next entry to process */
3801                 const char *log_file[] = {
3802                         "git", "log", "--no-color", "--pretty=raw",
3803                                 "--cc", "--raw", view->id, "--", path, NULL
3804                 };
3805                 struct io io = {};
3807                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3808                         report("Failed to load tree data");
3809                         return TRUE;
3810                 }
3812                 done_io(view->pipe);
3813                 view->io = io;
3814                 *read_date = TRUE;
3815                 return FALSE;
3817         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3818                 parse_author_line(text + STRING_SIZE("author "),
3819                                   author_name, sizeof(author_name), &author_time);
3821         } else if (*text == ':') {
3822                 char *pos;
3823                 size_t annotated = 1;
3824                 size_t i;
3826                 pos = strchr(text, '\t');
3827                 if (!pos)
3828                         return TRUE;
3829                 text = pos + 1;
3830                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3831                         text += strlen(opt_prefix);
3832                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3833                         text += strlen(opt_path);
3834                 pos = strchr(text, '/');
3835                 if (pos)
3836                         *pos = 0;
3838                 for (i = 1; i < view->lines; i++) {
3839                         struct line *line = &view->line[i];
3840                         struct tree_entry *entry = line->data;
3842                         annotated += !!*entry->author;
3843                         if (*entry->author || strcmp(entry->name, text))
3844                                 continue;
3846                         string_copy(entry->author, author_name);
3847                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3848                         line->dirty = 1;
3849                         break;
3850                 }
3852                 if (annotated == view->lines)
3853                         kill_io(view->pipe);
3854         }
3855         return TRUE;
3858 static bool
3859 tree_read(struct view *view, char *text)
3861         static bool read_date = FALSE;
3862         struct tree_entry *data;
3863         struct line *entry, *line;
3864         enum line_type type;
3865         size_t textlen = text ? strlen(text) : 0;
3866         char *path = text + SIZEOF_TREE_ATTR;
3868         if (read_date || !text)
3869                 return tree_read_date(view, text, &read_date);
3871         if (textlen <= SIZEOF_TREE_ATTR)
3872                 return FALSE;
3873         if (view->lines == 0 &&
3874             !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
3875                 return FALSE;
3877         /* Strip the path part ... */
3878         if (*opt_path) {
3879                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3880                 size_t striplen = strlen(opt_path);
3882                 if (pathlen > striplen)
3883                         memmove(path, path + striplen,
3884                                 pathlen - striplen + 1);
3886                 /* Insert "link" to parent directory. */
3887                 if (view->lines == 1 &&
3888                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3889                         return FALSE;
3890         }
3892         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3893         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3894         if (!entry)
3895                 return FALSE;
3896         data = entry->data;
3898         /* Skip "Directory ..." and ".." line. */
3899         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3900                 if (tree_compare_entry(line, entry) <= 0)
3901                         continue;
3903                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3905                 line->data = data;
3906                 line->type = type;
3907                 for (; line <= entry; line++)
3908                         line->dirty = line->cleareol = 1;
3909                 return TRUE;
3910         }
3912         if (tree_lineno > view->lineno) {
3913                 view->lineno = tree_lineno;
3914                 tree_lineno = 0;
3915         }
3917         return TRUE;
3920 static bool
3921 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3923         struct tree_entry *entry = line->data;
3925         if (line->type == LINE_TREE_PARENT) {
3926                 if (draw_text(view, line->type, "Directory path /", TRUE))
3927                         return TRUE;
3928         } else {
3929                 char mode[11] = "-r--r--r--";
3931                 if (S_ISDIR(entry->mode)) {
3932                         mode[3] = mode[6] = mode[9] = 'x';
3933                         mode[0] = 'd';
3934                 }
3935                 if (S_ISLNK(entry->mode))
3936                         mode[0] = 'l';
3937                 if (entry->mode & S_IWUSR)
3938                         mode[2] = 'w';
3939                 if (entry->mode & S_IXUSR)
3940                         mode[3] = 'x';
3941                 if (entry->mode & S_IXGRP)
3942                         mode[6] = 'x';
3943                 if (entry->mode & S_IXOTH)
3944                         mode[9] = 'x';
3945                 if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
3946                         return TRUE;
3948                 if (opt_author && draw_author(view, entry->author))
3949                         return TRUE;
3951                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3952                         return TRUE;
3953         }
3954         if (draw_text(view, line->type, entry->name, TRUE))
3955                 return TRUE;
3956         return TRUE;
3959 static void
3960 open_blob_editor()
3962         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3963         int fd = mkstemp(file);
3965         if (fd == -1)
3966                 report("Failed to create temporary file");
3967         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3968                 report("Failed to save blob data to file");
3969         else
3970                 open_editor(FALSE, file);
3971         if (fd != -1)
3972                 unlink(file);
3975 static enum request
3976 tree_request(struct view *view, enum request request, struct line *line)
3978         enum open_flags flags;
3980         switch (request) {
3981         case REQ_VIEW_BLAME:
3982                 if (line->type != LINE_TREE_FILE) {
3983                         report("Blame only supported for files");
3984                         return REQ_NONE;
3985                 }
3987                 string_copy(opt_ref, view->vid);
3988                 return request;
3990         case REQ_EDIT:
3991                 if (line->type != LINE_TREE_FILE) {
3992                         report("Edit only supported for files");
3993                 } else if (!is_head_commit(view->vid)) {
3994                         open_blob_editor();
3995                 } else {
3996                         open_editor(TRUE, opt_file);
3997                 }
3998                 return REQ_NONE;
4000         case REQ_PARENT:
4001                 if (!*opt_path) {
4002                         /* quit view if at top of tree */
4003                         return REQ_VIEW_CLOSE;
4004                 }
4005                 /* fake 'cd  ..' */
4006                 line = &view->line[1];
4007                 break;
4009         case REQ_ENTER:
4010                 break;
4012         default:
4013                 return request;
4014         }
4016         /* Cleanup the stack if the tree view is at a different tree. */
4017         while (!*opt_path && tree_stack)
4018                 pop_tree_stack_entry();
4020         switch (line->type) {
4021         case LINE_TREE_DIR:
4022                 /* Depending on whether it is a subdir or parent (updir?) link
4023                  * mangle the path buffer. */
4024                 if (line == &view->line[1] && *opt_path) {
4025                         pop_tree_stack_entry();
4027                 } else {
4028                         const char *basename = tree_path(line);
4030                         push_tree_stack_entry(basename, view->lineno);
4031                 }
4033                 /* Trees and subtrees share the same ID, so they are not not
4034                  * unique like blobs. */
4035                 flags = OPEN_RELOAD;
4036                 request = REQ_VIEW_TREE;
4037                 break;
4039         case LINE_TREE_FILE:
4040                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4041                 request = REQ_VIEW_BLOB;
4042                 break;
4044         default:
4045                 return REQ_NONE;
4046         }
4048         open_view(view, request, flags);
4049         if (request == REQ_VIEW_TREE)
4050                 view->lineno = tree_lineno;
4052         return REQ_NONE;
4055 static void
4056 tree_select(struct view *view, struct line *line)
4058         struct tree_entry *entry = line->data;
4060         if (line->type == LINE_TREE_FILE) {
4061                 string_copy_rev(ref_blob, entry->id);
4062                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4064         } else if (line->type != LINE_TREE_DIR) {
4065                 return;
4066         }
4068         string_copy_rev(view->ref, entry->id);
4071 static const char *tree_argv[SIZEOF_ARG] = {
4072         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4073 };
4075 static struct view_ops tree_ops = {
4076         "file",
4077         tree_argv,
4078         NULL,
4079         tree_read,
4080         tree_draw,
4081         tree_request,
4082         pager_grep,
4083         tree_select,
4084 };
4086 static bool
4087 blob_read(struct view *view, char *line)
4089         if (!line)
4090                 return TRUE;
4091         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4094 static enum request
4095 blob_request(struct view *view, enum request request, struct line *line)
4097         switch (request) {
4098         case REQ_EDIT:
4099                 open_blob_editor();
4100                 return REQ_NONE;
4101         default:
4102                 return pager_request(view, request, line);
4103         }
4106 static const char *blob_argv[SIZEOF_ARG] = {
4107         "git", "cat-file", "blob", "%(blob)", NULL
4108 };
4110 static struct view_ops blob_ops = {
4111         "line",
4112         blob_argv,
4113         NULL,
4114         blob_read,
4115         pager_draw,
4116         blob_request,
4117         pager_grep,
4118         pager_select,
4119 };
4121 /*
4122  * Blame backend
4123  *
4124  * Loading the blame view is a two phase job:
4125  *
4126  *  1. File content is read either using opt_file from the
4127  *     filesystem or using git-cat-file.
4128  *  2. Then blame information is incrementally added by
4129  *     reading output from git-blame.
4130  */
4132 static const char *blame_head_argv[] = {
4133         "git", "blame", "--incremental", "--", "%(file)", NULL
4134 };
4136 static const char *blame_ref_argv[] = {
4137         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4138 };
4140 static const char *blame_cat_file_argv[] = {
4141         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4142 };
4144 struct blame_commit {
4145         char id[SIZEOF_REV];            /* SHA1 ID. */
4146         char title[128];                /* First line of the commit message. */
4147         char author[75];                /* Author of the commit. */
4148         struct tm time;                 /* Date from the author ident. */
4149         char filename[128];             /* Name of file. */
4150         bool has_previous;              /* Was a "previous" line detected. */
4151 };
4153 struct blame {
4154         struct blame_commit *commit;
4155         char text[1];
4156 };
4158 static bool
4159 blame_open(struct view *view)
4161         if (*opt_ref || !io_open(&view->io, opt_file)) {
4162                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4163                         return FALSE;
4164         }
4166         setup_update(view, opt_file);
4167         string_format(view->ref, "%s ...", opt_file);
4169         return TRUE;
4172 static struct blame_commit *
4173 get_blame_commit(struct view *view, const char *id)
4175         size_t i;
4177         for (i = 0; i < view->lines; i++) {
4178                 struct blame *blame = view->line[i].data;
4180                 if (!blame->commit)
4181                         continue;
4183                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4184                         return blame->commit;
4185         }
4187         {
4188                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4190                 if (commit)
4191                         string_ncopy(commit->id, id, SIZEOF_REV);
4192                 return commit;
4193         }
4196 static bool
4197 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4199         const char *pos = *posref;
4201         *posref = NULL;
4202         pos = strchr(pos + 1, ' ');
4203         if (!pos || !isdigit(pos[1]))
4204                 return FALSE;
4205         *number = atoi(pos + 1);
4206         if (*number < min || *number > max)
4207                 return FALSE;
4209         *posref = pos;
4210         return TRUE;
4213 static struct blame_commit *
4214 parse_blame_commit(struct view *view, const char *text, int *blamed)
4216         struct blame_commit *commit;
4217         struct blame *blame;
4218         const char *pos = text + SIZEOF_REV - 1;
4219         size_t lineno;
4220         size_t group;
4222         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4223                 return NULL;
4225         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4226             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4227                 return NULL;
4229         commit = get_blame_commit(view, text);
4230         if (!commit)
4231                 return NULL;
4233         *blamed += group;
4234         while (group--) {
4235                 struct line *line = &view->line[lineno + group - 1];
4237                 blame = line->data;
4238                 blame->commit = commit;
4239                 line->dirty = 1;
4240         }
4242         return commit;
4245 static bool
4246 blame_read_file(struct view *view, const char *line, bool *read_file)
4248         if (!line) {
4249                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4250                 struct io io = {};
4252                 if (view->lines == 0 && !view->parent)
4253                         die("No blame exist for %s", view->vid);
4255                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4256                         report("Failed to load blame data");
4257                         return TRUE;
4258                 }
4260                 done_io(view->pipe);
4261                 view->io = io;
4262                 *read_file = FALSE;
4263                 return FALSE;
4265         } else {
4266                 size_t linelen = string_expand_length(line, opt_tab_size);
4267                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4269                 if (!blame)
4270                         return FALSE;
4272                 blame->commit = NULL;
4273                 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4274                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4275         }
4278 static bool
4279 match_blame_header(const char *name, char **line)
4281         size_t namelen = strlen(name);
4282         bool matched = !strncmp(name, *line, namelen);
4284         if (matched)
4285                 *line += namelen;
4287         return matched;
4290 static bool
4291 blame_read(struct view *view, char *line)
4293         static struct blame_commit *commit = NULL;
4294         static int blamed = 0;
4295         static time_t author_time;
4296         static bool read_file = TRUE;
4298         if (read_file)
4299                 return blame_read_file(view, line, &read_file);
4301         if (!line) {
4302                 /* Reset all! */
4303                 commit = NULL;
4304                 blamed = 0;
4305                 read_file = TRUE;
4306                 string_format(view->ref, "%s", view->vid);
4307                 if (view_is_displayed(view)) {
4308                         update_view_title(view);
4309                         redraw_view_from(view, 0);
4310                 }
4311                 return TRUE;
4312         }
4314         if (!commit) {
4315                 commit = parse_blame_commit(view, line, &blamed);
4316                 string_format(view->ref, "%s %2d%%", view->vid,
4317                               view->lines ? blamed * 100 / view->lines : 0);
4319         } else if (match_blame_header("author ", &line)) {
4320                 string_ncopy(commit->author, line, strlen(line));
4322         } else if (match_blame_header("author-time ", &line)) {
4323                 author_time = (time_t) atol(line);
4325         } else if (match_blame_header("author-tz ", &line)) {
4326                 long tz;
4328                 tz  = ('0' - line[1]) * 60 * 60 * 10;
4329                 tz += ('0' - line[2]) * 60 * 60;
4330                 tz += ('0' - line[3]) * 60;
4331                 tz += ('0' - line[4]) * 60;
4333                 if (line[0] == '-')
4334                         tz = -tz;
4336                 author_time -= tz;
4337                 gmtime_r(&author_time, &commit->time);
4339         } else if (match_blame_header("summary ", &line)) {
4340                 string_ncopy(commit->title, line, strlen(line));
4342         } else if (match_blame_header("previous ", &line)) {
4343                 commit->has_previous = TRUE;
4345         } else if (match_blame_header("filename ", &line)) {
4346                 string_ncopy(commit->filename, line, strlen(line));
4347                 commit = NULL;
4348         }
4350         return TRUE;
4353 static bool
4354 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4356         struct blame *blame = line->data;
4357         struct tm *time = NULL;
4358         const char *id = NULL, *author = NULL;
4360         if (blame->commit && *blame->commit->filename) {
4361                 id = blame->commit->id;
4362                 author = blame->commit->author;
4363                 time = &blame->commit->time;
4364         }
4366         if (opt_date && draw_date(view, time))
4367                 return TRUE;
4369         if (opt_author && draw_author(view, author))
4370                 return TRUE;
4372         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4373                 return TRUE;
4375         if (draw_lineno(view, lineno))
4376                 return TRUE;
4378         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4379         return TRUE;
4382 static bool
4383 check_blame_commit(struct blame *blame)
4385         if (!blame->commit)
4386                 report("Commit data not loaded yet");
4387         else if (!strcmp(blame->commit->id, NULL_ID))
4388                 report("No commit exist for the selected line");
4389         else
4390                 return TRUE;
4391         return FALSE;
4394 static enum request
4395 blame_request(struct view *view, enum request request, struct line *line)
4397         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4398         struct blame *blame = line->data;
4400         switch (request) {
4401         case REQ_VIEW_BLAME:
4402                 if (check_blame_commit(blame)) {
4403                         string_copy(opt_ref, blame->commit->id);
4404                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4405                 }
4406                 break;
4408         case REQ_PARENT:
4409                 if (check_blame_commit(blame) &&
4410                     select_commit_parent(blame->commit->id, opt_ref))
4411                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4412                 break;
4414         case REQ_ENTER:
4415                 if (!blame->commit) {
4416                         report("No commit loaded yet");
4417                         break;
4418                 }
4420                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4421                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4422                         break;
4424                 if (!strcmp(blame->commit->id, NULL_ID)) {
4425                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4426                         const char *diff_index_argv[] = {
4427                                 "git", "diff-index", "--root", "--patch-with-stat",
4428                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4429                         };
4431                         if (!blame->commit->has_previous) {
4432                                 diff_index_argv[1] = "diff";
4433                                 diff_index_argv[2] = "--no-color";
4434                                 diff_index_argv[6] = "--";
4435                                 diff_index_argv[7] = "/dev/null";
4436                         }
4438                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4439                                 report("Failed to allocate diff command");
4440                                 break;
4441                         }
4442                         flags |= OPEN_PREPARED;
4443                 }
4445                 open_view(view, REQ_VIEW_DIFF, flags);
4446                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4447                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4448                 break;
4450         default:
4451                 return request;
4452         }
4454         return REQ_NONE;
4457 static bool
4458 blame_grep(struct view *view, struct line *line)
4460         struct blame *blame = line->data;
4461         struct blame_commit *commit = blame->commit;
4462         regmatch_t pmatch;
4464 #define MATCH(text, on)                                                 \
4465         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4467         if (commit) {
4468                 char buf[DATE_COLS + 1];
4470                 if (MATCH(commit->title, 1) ||
4471                     MATCH(commit->author, opt_author) ||
4472                     MATCH(commit->id, opt_date))
4473                         return TRUE;
4475                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4476                     MATCH(buf, 1))
4477                         return TRUE;
4478         }
4480         return MATCH(blame->text, 1);
4482 #undef MATCH
4485 static void
4486 blame_select(struct view *view, struct line *line)
4488         struct blame *blame = line->data;
4489         struct blame_commit *commit = blame->commit;
4491         if (!commit)
4492                 return;
4494         if (!strcmp(commit->id, NULL_ID))
4495                 string_ncopy(ref_commit, "HEAD", 4);
4496         else
4497                 string_copy_rev(ref_commit, commit->id);
4500 static struct view_ops blame_ops = {
4501         "line",
4502         NULL,
4503         blame_open,
4504         blame_read,
4505         blame_draw,
4506         blame_request,
4507         blame_grep,
4508         blame_select,
4509 };
4511 /*
4512  * Status backend
4513  */
4515 struct status {
4516         char status;
4517         struct {
4518                 mode_t mode;
4519                 char rev[SIZEOF_REV];
4520                 char name[SIZEOF_STR];
4521         } old;
4522         struct {
4523                 mode_t mode;
4524                 char rev[SIZEOF_REV];
4525                 char name[SIZEOF_STR];
4526         } new;
4527 };
4529 static char status_onbranch[SIZEOF_STR];
4530 static struct status stage_status;
4531 static enum line_type stage_line_type;
4532 static size_t stage_chunks;
4533 static int *stage_chunk;
4535 /* This should work even for the "On branch" line. */
4536 static inline bool
4537 status_has_none(struct view *view, struct line *line)
4539         return line < view->line + view->lines && !line[1].data;
4542 /* Get fields from the diff line:
4543  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4544  */
4545 static inline bool
4546 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4548         const char *old_mode = buf +  1;
4549         const char *new_mode = buf +  8;
4550         const char *old_rev  = buf + 15;
4551         const char *new_rev  = buf + 56;
4552         const char *status   = buf + 97;
4554         if (bufsize < 98 ||
4555             old_mode[-1] != ':' ||
4556             new_mode[-1] != ' ' ||
4557             old_rev[-1]  != ' ' ||
4558             new_rev[-1]  != ' ' ||
4559             status[-1]   != ' ')
4560                 return FALSE;
4562         file->status = *status;
4564         string_copy_rev(file->old.rev, old_rev);
4565         string_copy_rev(file->new.rev, new_rev);
4567         file->old.mode = strtoul(old_mode, NULL, 8);
4568         file->new.mode = strtoul(new_mode, NULL, 8);
4570         file->old.name[0] = file->new.name[0] = 0;
4572         return TRUE;
4575 static bool
4576 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4578         struct status *file = NULL;
4579         struct status *unmerged = NULL;
4580         char *buf;
4581         struct io io = {};
4583         if (!run_io(&io, argv, NULL, IO_RD))
4584                 return FALSE;
4586         add_line_data(view, NULL, type);
4588         while ((buf = io_get(&io, 0, TRUE))) {
4589                 if (!file) {
4590                         file = calloc(1, sizeof(*file));
4591                         if (!file || !add_line_data(view, file, type))
4592                                 goto error_out;
4593                 }
4595                 /* Parse diff info part. */
4596                 if (status) {
4597                         file->status = status;
4598                         if (status == 'A')
4599                                 string_copy(file->old.rev, NULL_ID);
4601                 } else if (!file->status) {
4602                         if (!status_get_diff(file, buf, strlen(buf)))
4603                                 goto error_out;
4605                         buf = io_get(&io, 0, TRUE);
4606                         if (!buf)
4607                                 break;
4609                         /* Collapse all 'M'odified entries that follow a
4610                          * associated 'U'nmerged entry. */
4611                         if (file->status == 'U') {
4612                                 unmerged = file;
4614                         } else if (unmerged) {
4615                                 int collapse = !strcmp(buf, unmerged->new.name);
4617                                 unmerged = NULL;
4618                                 if (collapse) {
4619                                         free(file);
4620                                         file = NULL;
4621                                         view->lines--;
4622                                         continue;
4623                                 }
4624                         }
4625                 }
4627                 /* Grab the old name for rename/copy. */
4628                 if (!*file->old.name &&
4629                     (file->status == 'R' || file->status == 'C')) {
4630                         string_ncopy(file->old.name, buf, strlen(buf));
4632                         buf = io_get(&io, 0, TRUE);
4633                         if (!buf)
4634                                 break;
4635                 }
4637                 /* git-ls-files just delivers a NUL separated list of
4638                  * file names similar to the second half of the
4639                  * git-diff-* output. */
4640                 string_ncopy(file->new.name, buf, strlen(buf));
4641                 if (!*file->old.name)
4642                         string_copy(file->old.name, file->new.name);
4643                 file = NULL;
4644         }
4646         if (io_error(&io)) {
4647 error_out:
4648                 done_io(&io);
4649                 return FALSE;
4650         }
4652         if (!view->line[view->lines - 1].data)
4653                 add_line_data(view, NULL, LINE_STAT_NONE);
4655         done_io(&io);
4656         return TRUE;
4659 /* Don't show unmerged entries in the staged section. */
4660 static const char *status_diff_index_argv[] = {
4661         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4662                              "--cached", "-M", "HEAD", NULL
4663 };
4665 static const char *status_diff_files_argv[] = {
4666         "git", "diff-files", "-z", NULL
4667 };
4669 static const char *status_list_other_argv[] = {
4670         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4671 };
4673 static const char *status_list_no_head_argv[] = {
4674         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4675 };
4677 static const char *update_index_argv[] = {
4678         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4679 };
4681 /* Restore the previous line number to stay in the context or select a
4682  * line with something that can be updated. */
4683 static void
4684 status_restore(struct view *view)
4686         if (view->p_lineno >= view->lines)
4687                 view->p_lineno = view->lines - 1;
4688         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4689                 view->p_lineno++;
4690         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4691                 view->p_lineno--;
4693         /* If the above fails, always skip the "On branch" line. */
4694         if (view->p_lineno < view->lines)
4695                 view->lineno = view->p_lineno;
4696         else
4697                 view->lineno = 1;
4699         if (view->lineno < view->offset)
4700                 view->offset = view->lineno;
4701         else if (view->offset + view->height <= view->lineno)
4702                 view->offset = view->lineno - view->height + 1;
4704         view->p_restore = FALSE;
4707 /* First parse staged info using git-diff-index(1), then parse unstaged
4708  * info using git-diff-files(1), and finally untracked files using
4709  * git-ls-files(1). */
4710 static bool
4711 status_open(struct view *view)
4713         reset_view(view);
4715         add_line_data(view, NULL, LINE_STAT_HEAD);
4716         if (is_initial_commit())
4717                 string_copy(status_onbranch, "Initial commit");
4718         else if (!*opt_head)
4719                 string_copy(status_onbranch, "Not currently on any branch");
4720         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4721                 return FALSE;
4723         run_io_bg(update_index_argv);
4725         if (is_initial_commit()) {
4726                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4727                         return FALSE;
4728         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4729                 return FALSE;
4730         }
4732         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4733             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4734                 return FALSE;
4736         /* Restore the exact position or use the specialized restore
4737          * mode? */
4738         if (!view->p_restore)
4739                 status_restore(view);
4740         return TRUE;
4743 static bool
4744 status_draw(struct view *view, struct line *line, unsigned int lineno)
4746         struct status *status = line->data;
4747         enum line_type type;
4748         const char *text;
4750         if (!status) {
4751                 switch (line->type) {
4752                 case LINE_STAT_STAGED:
4753                         type = LINE_STAT_SECTION;
4754                         text = "Changes to be committed:";
4755                         break;
4757                 case LINE_STAT_UNSTAGED:
4758                         type = LINE_STAT_SECTION;
4759                         text = "Changed but not updated:";
4760                         break;
4762                 case LINE_STAT_UNTRACKED:
4763                         type = LINE_STAT_SECTION;
4764                         text = "Untracked files:";
4765                         break;
4767                 case LINE_STAT_NONE:
4768                         type = LINE_DEFAULT;
4769                         text = "    (no files)";
4770                         break;
4772                 case LINE_STAT_HEAD:
4773                         type = LINE_STAT_HEAD;
4774                         text = status_onbranch;
4775                         break;
4777                 default:
4778                         return FALSE;
4779                 }
4780         } else {
4781                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4783                 buf[0] = status->status;
4784                 if (draw_text(view, line->type, buf, TRUE))
4785                         return TRUE;
4786                 type = LINE_DEFAULT;
4787                 text = status->new.name;
4788         }
4790         draw_text(view, type, text, TRUE);
4791         return TRUE;
4794 static enum request
4795 status_enter(struct view *view, struct line *line)
4797         struct status *status = line->data;
4798         const char *oldpath = status ? status->old.name : NULL;
4799         /* Diffs for unmerged entries are empty when passing the new
4800          * path, so leave it empty. */
4801         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4802         const char *info;
4803         enum open_flags split;
4804         struct view *stage = VIEW(REQ_VIEW_STAGE);
4806         if (line->type == LINE_STAT_NONE ||
4807             (!status && line[1].type == LINE_STAT_NONE)) {
4808                 report("No file to diff");
4809                 return REQ_NONE;
4810         }
4812         switch (line->type) {
4813         case LINE_STAT_STAGED:
4814                 if (is_initial_commit()) {
4815                         const char *no_head_diff_argv[] = {
4816                                 "git", "diff", "--no-color", "--patch-with-stat",
4817                                         "--", "/dev/null", newpath, NULL
4818                         };
4820                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4821                                 return REQ_QUIT;
4822                 } else {
4823                         const char *index_show_argv[] = {
4824                                 "git", "diff-index", "--root", "--patch-with-stat",
4825                                         "-C", "-M", "--cached", "HEAD", "--",
4826                                         oldpath, newpath, NULL
4827                         };
4829                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4830                                 return REQ_QUIT;
4831                 }
4833                 if (status)
4834                         info = "Staged changes to %s";
4835                 else
4836                         info = "Staged changes";
4837                 break;
4839         case LINE_STAT_UNSTAGED:
4840         {
4841                 const char *files_show_argv[] = {
4842                         "git", "diff-files", "--root", "--patch-with-stat",
4843                                 "-C", "-M", "--", oldpath, newpath, NULL
4844                 };
4846                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4847                         return REQ_QUIT;
4848                 if (status)
4849                         info = "Unstaged changes to %s";
4850                 else
4851                         info = "Unstaged changes";
4852                 break;
4853         }
4854         case LINE_STAT_UNTRACKED:
4855                 if (!newpath) {
4856                         report("No file to show");
4857                         return REQ_NONE;
4858                 }
4860                 if (!suffixcmp(status->new.name, -1, "/")) {
4861                         report("Cannot display a directory");
4862                         return REQ_NONE;
4863                 }
4865                 if (!prepare_update_file(stage, newpath))
4866                         return REQ_QUIT;
4867                 info = "Untracked file %s";
4868                 break;
4870         case LINE_STAT_HEAD:
4871                 return REQ_NONE;
4873         default:
4874                 die("line type %d not handled in switch", line->type);
4875         }
4877         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4878         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4879         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4880                 if (status) {
4881                         stage_status = *status;
4882                 } else {
4883                         memset(&stage_status, 0, sizeof(stage_status));
4884                 }
4886                 stage_line_type = line->type;
4887                 stage_chunks = 0;
4888                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4889         }
4891         return REQ_NONE;
4894 static bool
4895 status_exists(struct status *status, enum line_type type)
4897         struct view *view = VIEW(REQ_VIEW_STATUS);
4898         unsigned long lineno;
4900         for (lineno = 0; lineno < view->lines; lineno++) {
4901                 struct line *line = &view->line[lineno];
4902                 struct status *pos = line->data;
4904                 if (line->type != type)
4905                         continue;
4906                 if (!pos && (!status || !status->status) && line[1].data) {
4907                         select_view_line(view, lineno);
4908                         return TRUE;
4909                 }
4910                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4911                         select_view_line(view, lineno);
4912                         return TRUE;
4913                 }
4914         }
4916         return FALSE;
4920 static bool
4921 status_update_prepare(struct io *io, enum line_type type)
4923         const char *staged_argv[] = {
4924                 "git", "update-index", "-z", "--index-info", NULL
4925         };
4926         const char *others_argv[] = {
4927                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4928         };
4930         switch (type) {
4931         case LINE_STAT_STAGED:
4932                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4934         case LINE_STAT_UNSTAGED:
4935                 return run_io(io, others_argv, opt_cdup, IO_WR);
4937         case LINE_STAT_UNTRACKED:
4938                 return run_io(io, others_argv, NULL, IO_WR);
4940         default:
4941                 die("line type %d not handled in switch", type);
4942                 return FALSE;
4943         }
4946 static bool
4947 status_update_write(struct io *io, struct status *status, enum line_type type)
4949         char buf[SIZEOF_STR];
4950         size_t bufsize = 0;
4952         switch (type) {
4953         case LINE_STAT_STAGED:
4954                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4955                                         status->old.mode,
4956                                         status->old.rev,
4957                                         status->old.name, 0))
4958                         return FALSE;
4959                 break;
4961         case LINE_STAT_UNSTAGED:
4962         case LINE_STAT_UNTRACKED:
4963                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4964                         return FALSE;
4965                 break;
4967         default:
4968                 die("line type %d not handled in switch", type);
4969         }
4971         return io_write(io, buf, bufsize);
4974 static bool
4975 status_update_file(struct status *status, enum line_type type)
4977         struct io io = {};
4978         bool result;
4980         if (!status_update_prepare(&io, type))
4981                 return FALSE;
4983         result = status_update_write(&io, status, type);
4984         done_io(&io);
4985         return result;
4988 static bool
4989 status_update_files(struct view *view, struct line *line)
4991         struct io io = {};
4992         bool result = TRUE;
4993         struct line *pos = view->line + view->lines;
4994         int files = 0;
4995         int file, done;
4997         if (!status_update_prepare(&io, line->type))
4998                 return FALSE;
5000         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5001                 files++;
5003         for (file = 0, done = 0; result && file < files; line++, file++) {
5004                 int almost_done = file * 100 / files;
5006                 if (almost_done > done) {
5007                         done = almost_done;
5008                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5009                                       file, files, done);
5010                         update_view_title(view);
5011                 }
5012                 result = status_update_write(&io, line->data, line->type);
5013         }
5015         done_io(&io);
5016         return result;
5019 static bool
5020 status_update(struct view *view)
5022         struct line *line = &view->line[view->lineno];
5024         assert(view->lines);
5026         if (!line->data) {
5027                 /* This should work even for the "On branch" line. */
5028                 if (line < view->line + view->lines && !line[1].data) {
5029                         report("Nothing to update");
5030                         return FALSE;
5031                 }
5033                 if (!status_update_files(view, line + 1)) {
5034                         report("Failed to update file status");
5035                         return FALSE;
5036                 }
5038         } else if (!status_update_file(line->data, line->type)) {
5039                 report("Failed to update file status");
5040                 return FALSE;
5041         }
5043         return TRUE;
5046 static bool
5047 status_revert(struct status *status, enum line_type type, bool has_none)
5049         if (!status || type != LINE_STAT_UNSTAGED) {
5050                 if (type == LINE_STAT_STAGED) {
5051                         report("Cannot revert changes to staged files");
5052                 } else if (type == LINE_STAT_UNTRACKED) {
5053                         report("Cannot revert changes to untracked files");
5054                 } else if (has_none) {
5055                         report("Nothing to revert");
5056                 } else {
5057                         report("Cannot revert changes to multiple files");
5058                 }
5059                 return FALSE;
5061         } else {
5062                 const char *checkout_argv[] = {
5063                         "git", "checkout", "--", status->old.name, NULL
5064                 };
5066                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5067                         return FALSE;
5068                 return run_io_fg(checkout_argv, opt_cdup);
5069         }
5072 static enum request
5073 status_request(struct view *view, enum request request, struct line *line)
5075         struct status *status = line->data;
5077         switch (request) {
5078         case REQ_STATUS_UPDATE:
5079                 if (!status_update(view))
5080                         return REQ_NONE;
5081                 break;
5083         case REQ_STATUS_REVERT:
5084                 if (!status_revert(status, line->type, status_has_none(view, line)))
5085                         return REQ_NONE;
5086                 break;
5088         case REQ_STATUS_MERGE:
5089                 if (!status || status->status != 'U') {
5090                         report("Merging only possible for files with unmerged status ('U').");
5091                         return REQ_NONE;
5092                 }
5093                 open_mergetool(status->new.name);
5094                 break;
5096         case REQ_EDIT:
5097                 if (!status)
5098                         return request;
5099                 if (status->status == 'D') {
5100                         report("File has been deleted.");
5101                         return REQ_NONE;
5102                 }
5104                 open_editor(status->status != '?', status->new.name);
5105                 break;
5107         case REQ_VIEW_BLAME:
5108                 if (status) {
5109                         string_copy(opt_file, status->new.name);
5110                         opt_ref[0] = 0;
5111                 }
5112                 return request;
5114         case REQ_ENTER:
5115                 /* After returning the status view has been split to
5116                  * show the stage view. No further reloading is
5117                  * necessary. */
5118                 status_enter(view, line);
5119                 return REQ_NONE;
5121         case REQ_REFRESH:
5122                 /* Simply reload the view. */
5123                 break;
5125         default:
5126                 return request;
5127         }
5129         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5131         return REQ_NONE;
5134 static void
5135 status_select(struct view *view, struct line *line)
5137         struct status *status = line->data;
5138         char file[SIZEOF_STR] = "all files";
5139         const char *text;
5140         const char *key;
5142         if (status && !string_format(file, "'%s'", status->new.name))
5143                 return;
5145         if (!status && line[1].type == LINE_STAT_NONE)
5146                 line++;
5148         switch (line->type) {
5149         case LINE_STAT_STAGED:
5150                 text = "Press %s to unstage %s for commit";
5151                 break;
5153         case LINE_STAT_UNSTAGED:
5154                 text = "Press %s to stage %s for commit";
5155                 break;
5157         case LINE_STAT_UNTRACKED:
5158                 text = "Press %s to stage %s for addition";
5159                 break;
5161         case LINE_STAT_HEAD:
5162         case LINE_STAT_NONE:
5163                 text = "Nothing to update";
5164                 break;
5166         default:
5167                 die("line type %d not handled in switch", line->type);
5168         }
5170         if (status && status->status == 'U') {
5171                 text = "Press %s to resolve conflict in %s";
5172                 key = get_key(REQ_STATUS_MERGE);
5174         } else {
5175                 key = get_key(REQ_STATUS_UPDATE);
5176         }
5178         string_format(view->ref, text, key, file);
5181 static bool
5182 status_grep(struct view *view, struct line *line)
5184         struct status *status = line->data;
5185         enum { S_STATUS, S_NAME, S_END } state;
5186         char buf[2] = "?";
5187         regmatch_t pmatch;
5189         if (!status)
5190                 return FALSE;
5192         for (state = S_STATUS; state < S_END; state++) {
5193                 const char *text;
5195                 switch (state) {
5196                 case S_NAME:    text = status->new.name;        break;
5197                 case S_STATUS:
5198                         buf[0] = status->status;
5199                         text = buf;
5200                         break;
5202                 default:
5203                         return FALSE;
5204                 }
5206                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5207                         return TRUE;
5208         }
5210         return FALSE;
5213 static struct view_ops status_ops = {
5214         "file",
5215         NULL,
5216         status_open,
5217         NULL,
5218         status_draw,
5219         status_request,
5220         status_grep,
5221         status_select,
5222 };
5225 static bool
5226 stage_diff_write(struct io *io, struct line *line, struct line *end)
5228         while (line < end) {
5229                 if (!io_write(io, line->data, strlen(line->data)) ||
5230                     !io_write(io, "\n", 1))
5231                         return FALSE;
5232                 line++;
5233                 if (line->type == LINE_DIFF_CHUNK ||
5234                     line->type == LINE_DIFF_HEADER)
5235                         break;
5236         }
5238         return TRUE;
5241 static struct line *
5242 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5244         for (; view->line < line; line--)
5245                 if (line->type == type)
5246                         return line;
5248         return NULL;
5251 static bool
5252 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5254         const char *apply_argv[SIZEOF_ARG] = {
5255                 "git", "apply", "--whitespace=nowarn", NULL
5256         };
5257         struct line *diff_hdr;
5258         struct io io = {};
5259         int argc = 3;
5261         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5262         if (!diff_hdr)
5263                 return FALSE;
5265         if (!revert)
5266                 apply_argv[argc++] = "--cached";
5267         if (revert || stage_line_type == LINE_STAT_STAGED)
5268                 apply_argv[argc++] = "-R";
5269         apply_argv[argc++] = "-";
5270         apply_argv[argc++] = NULL;
5271         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5272                 return FALSE;
5274         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5275             !stage_diff_write(&io, chunk, view->line + view->lines))
5276                 chunk = NULL;
5278         done_io(&io);
5279         run_io_bg(update_index_argv);
5281         return chunk ? TRUE : FALSE;
5284 static bool
5285 stage_update(struct view *view, struct line *line)
5287         struct line *chunk = NULL;
5289         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5290                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5292         if (chunk) {
5293                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5294                         report("Failed to apply chunk");
5295                         return FALSE;
5296                 }
5298         } else if (!stage_status.status) {
5299                 view = VIEW(REQ_VIEW_STATUS);
5301                 for (line = view->line; line < view->line + view->lines; line++)
5302                         if (line->type == stage_line_type)
5303                                 break;
5305                 if (!status_update_files(view, line + 1)) {
5306                         report("Failed to update files");
5307                         return FALSE;
5308                 }
5310         } else if (!status_update_file(&stage_status, stage_line_type)) {
5311                 report("Failed to update file");
5312                 return FALSE;
5313         }
5315         return TRUE;
5318 static bool
5319 stage_revert(struct view *view, struct line *line)
5321         struct line *chunk = NULL;
5323         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5324                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5326         if (chunk) {
5327                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5328                         return FALSE;
5330                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5331                         report("Failed to revert chunk");
5332                         return FALSE;
5333                 }
5334                 return TRUE;
5336         } else {
5337                 return status_revert(stage_status.status ? &stage_status : NULL,
5338                                      stage_line_type, FALSE);
5339         }
5343 static void
5344 stage_next(struct view *view, struct line *line)
5346         int i;
5348         if (!stage_chunks) {
5349                 static size_t alloc = 0;
5350                 int *tmp;
5352                 for (line = view->line; line < view->line + view->lines; line++) {
5353                         if (line->type != LINE_DIFF_CHUNK)
5354                                 continue;
5356                         tmp = realloc_items(stage_chunk, &alloc,
5357                                             stage_chunks, sizeof(*tmp));
5358                         if (!tmp) {
5359                                 report("Allocation failure");
5360                                 return;
5361                         }
5363                         stage_chunk = tmp;
5364                         stage_chunk[stage_chunks++] = line - view->line;
5365                 }
5366         }
5368         for (i = 0; i < stage_chunks; i++) {
5369                 if (stage_chunk[i] > view->lineno) {
5370                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5371                         report("Chunk %d of %d", i + 1, stage_chunks);
5372                         return;
5373                 }
5374         }
5376         report("No next chunk found");
5379 static enum request
5380 stage_request(struct view *view, enum request request, struct line *line)
5382         switch (request) {
5383         case REQ_STATUS_UPDATE:
5384                 if (!stage_update(view, line))
5385                         return REQ_NONE;
5386                 break;
5388         case REQ_STATUS_REVERT:
5389                 if (!stage_revert(view, line))
5390                         return REQ_NONE;
5391                 break;
5393         case REQ_STAGE_NEXT:
5394                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5395                         report("File is untracked; press %s to add",
5396                                get_key(REQ_STATUS_UPDATE));
5397                         return REQ_NONE;
5398                 }
5399                 stage_next(view, line);
5400                 return REQ_NONE;
5402         case REQ_EDIT:
5403                 if (!stage_status.new.name[0])
5404                         return request;
5405                 if (stage_status.status == 'D') {
5406                         report("File has been deleted.");
5407                         return REQ_NONE;
5408                 }
5410                 open_editor(stage_status.status != '?', stage_status.new.name);
5411                 break;
5413         case REQ_REFRESH:
5414                 /* Reload everything ... */
5415                 break;
5417         case REQ_VIEW_BLAME:
5418                 if (stage_status.new.name[0]) {
5419                         string_copy(opt_file, stage_status.new.name);
5420                         opt_ref[0] = 0;
5421                 }
5422                 return request;
5424         case REQ_ENTER:
5425                 return pager_request(view, request, line);
5427         default:
5428                 return request;
5429         }
5431         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5432         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5434         /* Check whether the staged entry still exists, and close the
5435          * stage view if it doesn't. */
5436         if (!status_exists(&stage_status, stage_line_type)) {
5437                 status_restore(VIEW(REQ_VIEW_STATUS));
5438                 return REQ_VIEW_CLOSE;
5439         }
5441         if (stage_line_type == LINE_STAT_UNTRACKED) {
5442                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5443                         report("Cannot display a directory");
5444                         return REQ_NONE;
5445                 }
5447                 if (!prepare_update_file(view, stage_status.new.name)) {
5448                         report("Failed to open file: %s", strerror(errno));
5449                         return REQ_NONE;
5450                 }
5451         }
5452         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5454         return REQ_NONE;
5457 static struct view_ops stage_ops = {
5458         "line",
5459         NULL,
5460         NULL,
5461         pager_read,
5462         pager_draw,
5463         stage_request,
5464         pager_grep,
5465         pager_select,
5466 };
5469 /*
5470  * Revision graph
5471  */
5473 struct commit {
5474         char id[SIZEOF_REV];            /* SHA1 ID. */
5475         char title[128];                /* First line of the commit message. */
5476         char author[75];                /* Author of the commit. */
5477         struct tm time;                 /* Date from the author ident. */
5478         struct ref **refs;              /* Repository references. */
5479         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5480         size_t graph_size;              /* The width of the graph array. */
5481         bool has_parents;               /* Rewritten --parents seen. */
5482 };
5484 /* Size of rev graph with no  "padding" columns */
5485 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5487 struct rev_graph {
5488         struct rev_graph *prev, *next, *parents;
5489         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5490         size_t size;
5491         struct commit *commit;
5492         size_t pos;
5493         unsigned int boundary:1;
5494 };
5496 /* Parents of the commit being visualized. */
5497 static struct rev_graph graph_parents[4];
5499 /* The current stack of revisions on the graph. */
5500 static struct rev_graph graph_stacks[4] = {
5501         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5502         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5503         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5504         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5505 };
5507 static inline bool
5508 graph_parent_is_merge(struct rev_graph *graph)
5510         return graph->parents->size > 1;
5513 static inline void
5514 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5516         struct commit *commit = graph->commit;
5518         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5519                 commit->graph[commit->graph_size++] = symbol;
5522 static void
5523 clear_rev_graph(struct rev_graph *graph)
5525         graph->boundary = 0;
5526         graph->size = graph->pos = 0;
5527         graph->commit = NULL;
5528         memset(graph->parents, 0, sizeof(*graph->parents));
5531 static void
5532 done_rev_graph(struct rev_graph *graph)
5534         if (graph_parent_is_merge(graph) &&
5535             graph->pos < graph->size - 1 &&
5536             graph->next->size == graph->size + graph->parents->size - 1) {
5537                 size_t i = graph->pos + graph->parents->size - 1;
5539                 graph->commit->graph_size = i * 2;
5540                 while (i < graph->next->size - 1) {
5541                         append_to_rev_graph(graph, ' ');
5542                         append_to_rev_graph(graph, '\\');
5543                         i++;
5544                 }
5545         }
5547         clear_rev_graph(graph);
5550 static void
5551 push_rev_graph(struct rev_graph *graph, const char *parent)
5553         int i;
5555         /* "Collapse" duplicate parents lines.
5556          *
5557          * FIXME: This needs to also update update the drawn graph but
5558          * for now it just serves as a method for pruning graph lines. */
5559         for (i = 0; i < graph->size; i++)
5560                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5561                         return;
5563         if (graph->size < SIZEOF_REVITEMS) {
5564                 string_copy_rev(graph->rev[graph->size++], parent);
5565         }
5568 static chtype
5569 get_rev_graph_symbol(struct rev_graph *graph)
5571         chtype symbol;
5573         if (graph->boundary)
5574                 symbol = REVGRAPH_BOUND;
5575         else if (graph->parents->size == 0)
5576                 symbol = REVGRAPH_INIT;
5577         else if (graph_parent_is_merge(graph))
5578                 symbol = REVGRAPH_MERGE;
5579         else if (graph->pos >= graph->size)
5580                 symbol = REVGRAPH_BRANCH;
5581         else
5582                 symbol = REVGRAPH_COMMIT;
5584         return symbol;
5587 static void
5588 draw_rev_graph(struct rev_graph *graph)
5590         struct rev_filler {
5591                 chtype separator, line;
5592         };
5593         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5594         static struct rev_filler fillers[] = {
5595                 { ' ',  '|' },
5596                 { '`',  '.' },
5597                 { '\'', ' ' },
5598                 { '/',  ' ' },
5599         };
5600         chtype symbol = get_rev_graph_symbol(graph);
5601         struct rev_filler *filler;
5602         size_t i;
5604         if (opt_line_graphics)
5605                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5607         filler = &fillers[DEFAULT];
5609         for (i = 0; i < graph->pos; i++) {
5610                 append_to_rev_graph(graph, filler->line);
5611                 if (graph_parent_is_merge(graph->prev) &&
5612                     graph->prev->pos == i)
5613                         filler = &fillers[RSHARP];
5615                 append_to_rev_graph(graph, filler->separator);
5616         }
5618         /* Place the symbol for this revision. */
5619         append_to_rev_graph(graph, symbol);
5621         if (graph->prev->size > graph->size)
5622                 filler = &fillers[RDIAG];
5623         else
5624                 filler = &fillers[DEFAULT];
5626         i++;
5628         for (; i < graph->size; i++) {
5629                 append_to_rev_graph(graph, filler->separator);
5630                 append_to_rev_graph(graph, filler->line);
5631                 if (graph_parent_is_merge(graph->prev) &&
5632                     i < graph->prev->pos + graph->parents->size)
5633                         filler = &fillers[RSHARP];
5634                 if (graph->prev->size > graph->size)
5635                         filler = &fillers[LDIAG];
5636         }
5638         if (graph->prev->size > graph->size) {
5639                 append_to_rev_graph(graph, filler->separator);
5640                 if (filler->line != ' ')
5641                         append_to_rev_graph(graph, filler->line);
5642         }
5645 /* Prepare the next rev graph */
5646 static void
5647 prepare_rev_graph(struct rev_graph *graph)
5649         size_t i;
5651         /* First, traverse all lines of revisions up to the active one. */
5652         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5653                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5654                         break;
5656                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5657         }
5659         /* Interleave the new revision parent(s). */
5660         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5661                 push_rev_graph(graph->next, graph->parents->rev[i]);
5663         /* Lastly, put any remaining revisions. */
5664         for (i = graph->pos + 1; i < graph->size; i++)
5665                 push_rev_graph(graph->next, graph->rev[i]);
5668 static void
5669 update_rev_graph(struct view *view, struct rev_graph *graph)
5671         /* If this is the finalizing update ... */
5672         if (graph->commit)
5673                 prepare_rev_graph(graph);
5675         /* Graph visualization needs a one rev look-ahead,
5676          * so the first update doesn't visualize anything. */
5677         if (!graph->prev->commit)
5678                 return;
5680         if (view->lines > 2)
5681                 view->line[view->lines - 3].dirty = 1;
5682         if (view->lines > 1)
5683                 view->line[view->lines - 2].dirty = 1;
5684         draw_rev_graph(graph->prev);
5685         done_rev_graph(graph->prev->prev);
5689 /*
5690  * Main view backend
5691  */
5693 static const char *main_argv[SIZEOF_ARG] = {
5694         "git", "log", "--no-color", "--pretty=raw", "--parents",
5695                       "--topo-order", "%(head)", NULL
5696 };
5698 static bool
5699 main_draw(struct view *view, struct line *line, unsigned int lineno)
5701         struct commit *commit = line->data;
5703         if (!*commit->author)
5704                 return FALSE;
5706         if (opt_date && draw_date(view, &commit->time))
5707                 return TRUE;
5709         if (opt_author && draw_author(view, commit->author))
5710                 return TRUE;
5712         if (opt_rev_graph && commit->graph_size &&
5713             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5714                 return TRUE;
5716         if (opt_show_refs && commit->refs) {
5717                 size_t i = 0;
5719                 do {
5720                         enum line_type type;
5722                         if (commit->refs[i]->head)
5723                                 type = LINE_MAIN_HEAD;
5724                         else if (commit->refs[i]->ltag)
5725                                 type = LINE_MAIN_LOCAL_TAG;
5726                         else if (commit->refs[i]->tag)
5727                                 type = LINE_MAIN_TAG;
5728                         else if (commit->refs[i]->tracked)
5729                                 type = LINE_MAIN_TRACKED;
5730                         else if (commit->refs[i]->remote)
5731                                 type = LINE_MAIN_REMOTE;
5732                         else
5733                                 type = LINE_MAIN_REF;
5735                         if (draw_text(view, type, "[", TRUE) ||
5736                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5737                             draw_text(view, type, "]", TRUE))
5738                                 return TRUE;
5740                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5741                                 return TRUE;
5742                 } while (commit->refs[i++]->next);
5743         }
5745         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5746         return TRUE;
5749 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5750 static bool
5751 main_read(struct view *view, char *line)
5753         static struct rev_graph *graph = graph_stacks;
5754         enum line_type type;
5755         struct commit *commit;
5757         if (!line) {
5758                 int i;
5760                 if (!view->lines && !view->parent)
5761                         die("No revisions match the given arguments.");
5762                 if (view->lines > 0) {
5763                         commit = view->line[view->lines - 1].data;
5764                         view->line[view->lines - 1].dirty = 1;
5765                         if (!*commit->author) {
5766                                 view->lines--;
5767                                 free(commit);
5768                                 graph->commit = NULL;
5769                         }
5770                 }
5771                 update_rev_graph(view, graph);
5773                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5774                         clear_rev_graph(&graph_stacks[i]);
5775                 return TRUE;
5776         }
5778         type = get_line_type(line);
5779         if (type == LINE_COMMIT) {
5780                 commit = calloc(1, sizeof(struct commit));
5781                 if (!commit)
5782                         return FALSE;
5784                 line += STRING_SIZE("commit ");
5785                 if (*line == '-') {
5786                         graph->boundary = 1;
5787                         line++;
5788                 }
5790                 string_copy_rev(commit->id, line);
5791                 commit->refs = get_refs(commit->id);
5792                 graph->commit = commit;
5793                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5795                 while ((line = strchr(line, ' '))) {
5796                         line++;
5797                         push_rev_graph(graph->parents, line);
5798                         commit->has_parents = TRUE;
5799                 }
5800                 return TRUE;
5801         }
5803         if (!view->lines)
5804                 return TRUE;
5805         commit = view->line[view->lines - 1].data;
5807         switch (type) {
5808         case LINE_PARENT:
5809                 if (commit->has_parents)
5810                         break;
5811                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5812                 break;
5814         case LINE_AUTHOR:
5815                 parse_author_line(line + STRING_SIZE("author "),
5816                                   commit->author, sizeof(commit->author),
5817                                   &commit->time);
5818                 update_rev_graph(view, graph);
5819                 graph = graph->next;
5820                 break;
5822         default:
5823                 /* Fill in the commit title if it has not already been set. */
5824                 if (commit->title[0])
5825                         break;
5827                 /* Require titles to start with a non-space character at the
5828                  * offset used by git log. */
5829                 if (strncmp(line, "    ", 4))
5830                         break;
5831                 line += 4;
5832                 /* Well, if the title starts with a whitespace character,
5833                  * try to be forgiving.  Otherwise we end up with no title. */
5834                 while (isspace(*line))
5835                         line++;
5836                 if (*line == '\0')
5837                         break;
5838                 /* FIXME: More graceful handling of titles; append "..." to
5839                  * shortened titles, etc. */
5841                 string_expand(commit->title, sizeof(commit->title), line, 1);
5842                 view->line[view->lines - 1].dirty = 1;
5843         }
5845         return TRUE;
5848 static enum request
5849 main_request(struct view *view, enum request request, struct line *line)
5851         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5853         switch (request) {
5854         case REQ_ENTER:
5855                 open_view(view, REQ_VIEW_DIFF, flags);
5856                 break;
5857         case REQ_REFRESH:
5858                 load_refs();
5859                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5860                 break;
5861         default:
5862                 return request;
5863         }
5865         return REQ_NONE;
5868 static bool
5869 grep_refs(struct ref **refs, regex_t *regex)
5871         regmatch_t pmatch;
5872         size_t i = 0;
5874         if (!refs)
5875                 return FALSE;
5876         do {
5877                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5878                         return TRUE;
5879         } while (refs[i++]->next);
5881         return FALSE;
5884 static bool
5885 main_grep(struct view *view, struct line *line)
5887         struct commit *commit = line->data;
5888         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5889         char buf[DATE_COLS + 1];
5890         regmatch_t pmatch;
5892         for (state = S_TITLE; state < S_END; state++) {
5893                 char *text;
5895                 switch (state) {
5896                 case S_TITLE:   text = commit->title;   break;
5897                 case S_AUTHOR:
5898                         if (!opt_author)
5899                                 continue;
5900                         text = commit->author;
5901                         break;
5902                 case S_DATE:
5903                         if (!opt_date)
5904                                 continue;
5905                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5906                                 continue;
5907                         text = buf;
5908                         break;
5909                 case S_REFS:
5910                         if (!opt_show_refs)
5911                                 continue;
5912                         if (grep_refs(commit->refs, view->regex) == TRUE)
5913                                 return TRUE;
5914                         continue;
5915                 default:
5916                         return FALSE;
5917                 }
5919                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5920                         return TRUE;
5921         }
5923         return FALSE;
5926 static void
5927 main_select(struct view *view, struct line *line)
5929         struct commit *commit = line->data;
5931         string_copy_rev(view->ref, commit->id);
5932         string_copy_rev(ref_commit, view->ref);
5935 static struct view_ops main_ops = {
5936         "commit",
5937         main_argv,
5938         NULL,
5939         main_read,
5940         main_draw,
5941         main_request,
5942         main_grep,
5943         main_select,
5944 };
5947 /*
5948  * Unicode / UTF-8 handling
5949  *
5950  * NOTE: Much of the following code for dealing with unicode is derived from
5951  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5952  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5953  */
5955 /* I've (over)annotated a lot of code snippets because I am not entirely
5956  * confident that the approach taken by this small UTF-8 interface is correct.
5957  * --jonas */
5959 static inline int
5960 unicode_width(unsigned long c)
5962         if (c >= 0x1100 &&
5963            (c <= 0x115f                         /* Hangul Jamo */
5964             || c == 0x2329
5965             || c == 0x232a
5966             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5967                                                 /* CJK ... Yi */
5968             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5969             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5970             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5971             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5972             || (c >= 0xffe0  && c <= 0xffe6)
5973             || (c >= 0x20000 && c <= 0x2fffd)
5974             || (c >= 0x30000 && c <= 0x3fffd)))
5975                 return 2;
5977         if (c == '\t')
5978                 return opt_tab_size;
5980         return 1;
5983 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5984  * Illegal bytes are set one. */
5985 static const unsigned char utf8_bytes[256] = {
5986         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,
5987         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,
5988         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,
5989         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,
5990         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,
5991         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,
5992         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,
5993         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,
5994 };
5996 /* Decode UTF-8 multi-byte representation into a unicode character. */
5997 static inline unsigned long
5998 utf8_to_unicode(const char *string, size_t length)
6000         unsigned long unicode;
6002         switch (length) {
6003         case 1:
6004                 unicode  =   string[0];
6005                 break;
6006         case 2:
6007                 unicode  =  (string[0] & 0x1f) << 6;
6008                 unicode +=  (string[1] & 0x3f);
6009                 break;
6010         case 3:
6011                 unicode  =  (string[0] & 0x0f) << 12;
6012                 unicode += ((string[1] & 0x3f) << 6);
6013                 unicode +=  (string[2] & 0x3f);
6014                 break;
6015         case 4:
6016                 unicode  =  (string[0] & 0x0f) << 18;
6017                 unicode += ((string[1] & 0x3f) << 12);
6018                 unicode += ((string[2] & 0x3f) << 6);
6019                 unicode +=  (string[3] & 0x3f);
6020                 break;
6021         case 5:
6022                 unicode  =  (string[0] & 0x0f) << 24;
6023                 unicode += ((string[1] & 0x3f) << 18);
6024                 unicode += ((string[2] & 0x3f) << 12);
6025                 unicode += ((string[3] & 0x3f) << 6);
6026                 unicode +=  (string[4] & 0x3f);
6027                 break;
6028         case 6:
6029                 unicode  =  (string[0] & 0x01) << 30;
6030                 unicode += ((string[1] & 0x3f) << 24);
6031                 unicode += ((string[2] & 0x3f) << 18);
6032                 unicode += ((string[3] & 0x3f) << 12);
6033                 unicode += ((string[4] & 0x3f) << 6);
6034                 unicode +=  (string[5] & 0x3f);
6035                 break;
6036         default:
6037                 die("Invalid unicode length");
6038         }
6040         /* Invalid characters could return the special 0xfffd value but NUL
6041          * should be just as good. */
6042         return unicode > 0xffff ? 0 : unicode;
6045 /* Calculates how much of string can be shown within the given maximum width
6046  * and sets trimmed parameter to non-zero value if all of string could not be
6047  * shown. If the reserve flag is TRUE, it will reserve at least one
6048  * trailing character, which can be useful when drawing a delimiter.
6049  *
6050  * Returns the number of bytes to output from string to satisfy max_width. */
6051 static size_t
6052 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
6054         const char *start = string;
6055         const char *end = strchr(string, '\0');
6056         unsigned char last_bytes = 0;
6057         size_t last_ucwidth = 0;
6059         *width = 0;
6060         *trimmed = 0;
6062         while (string < end) {
6063                 int c = *(unsigned char *) string;
6064                 unsigned char bytes = utf8_bytes[c];
6065                 size_t ucwidth;
6066                 unsigned long unicode;
6068                 if (string + bytes > end)
6069                         break;
6071                 /* Change representation to figure out whether
6072                  * it is a single- or double-width character. */
6074                 unicode = utf8_to_unicode(string, bytes);
6075                 /* FIXME: Graceful handling of invalid unicode character. */
6076                 if (!unicode)
6077                         break;
6079                 ucwidth = unicode_width(unicode);
6080                 *width  += ucwidth;
6081                 if (*width > max_width) {
6082                         *trimmed = 1;
6083                         *width -= ucwidth;
6084                         if (reserve && *width == max_width) {
6085                                 string -= last_bytes;
6086                                 *width -= last_ucwidth;
6087                         }
6088                         break;
6089                 }
6091                 string  += bytes;
6092                 last_bytes = bytes;
6093                 last_ucwidth = ucwidth;
6094         }
6096         return string - start;
6100 /*
6101  * Status management
6102  */
6104 /* Whether or not the curses interface has been initialized. */
6105 static bool cursed = FALSE;
6107 /* Terminal hacks and workarounds. */
6108 static bool use_scroll_redrawwin;
6109 static bool use_scroll_status_wclear;
6111 /* The status window is used for polling keystrokes. */
6112 static WINDOW *status_win;
6114 /* Reading from the prompt? */
6115 static bool input_mode = FALSE;
6117 static bool status_empty = FALSE;
6119 /* Update status and title window. */
6120 static void
6121 report(const char *msg, ...)
6123         struct view *view = display[current_view];
6125         if (input_mode)
6126                 return;
6128         if (!view) {
6129                 char buf[SIZEOF_STR];
6130                 va_list args;
6132                 va_start(args, msg);
6133                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6134                         buf[sizeof(buf) - 1] = 0;
6135                         buf[sizeof(buf) - 2] = '.';
6136                         buf[sizeof(buf) - 3] = '.';
6137                         buf[sizeof(buf) - 4] = '.';
6138                 }
6139                 va_end(args);
6140                 die("%s", buf);
6141         }
6143         if (!status_empty || *msg) {
6144                 va_list args;
6146                 va_start(args, msg);
6148                 wmove(status_win, 0, 0);
6149                 if (view->has_scrolled && use_scroll_status_wclear)
6150                         wclear(status_win);
6151                 if (*msg) {
6152                         vwprintw(status_win, msg, args);
6153                         status_empty = FALSE;
6154                 } else {
6155                         status_empty = TRUE;
6156                 }
6157                 wclrtoeol(status_win);
6158                 wnoutrefresh(status_win);
6160                 va_end(args);
6161         }
6163         update_view_title(view);
6166 /* Controls when nodelay should be in effect when polling user input. */
6167 static void
6168 set_nonblocking_input(bool loading)
6170         static unsigned int loading_views;
6172         if ((loading == FALSE && loading_views-- == 1) ||
6173             (loading == TRUE  && loading_views++ == 0))
6174                 nodelay(status_win, loading);
6177 static void
6178 init_display(void)
6180         const char *term;
6181         int x, y;
6183         /* Initialize the curses library */
6184         if (isatty(STDIN_FILENO)) {
6185                 cursed = !!initscr();
6186                 opt_tty = stdin;
6187         } else {
6188                 /* Leave stdin and stdout alone when acting as a pager. */
6189                 opt_tty = fopen("/dev/tty", "r+");
6190                 if (!opt_tty)
6191                         die("Failed to open /dev/tty");
6192                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6193         }
6195         if (!cursed)
6196                 die("Failed to initialize curses");
6198         nonl();         /* Tell curses not to do NL->CR/NL on output */
6199         cbreak();       /* Take input chars one at a time, no wait for \n */
6200         noecho();       /* Don't echo input */
6201         leaveok(stdscr, FALSE);
6203         if (has_colors())
6204                 init_colors();
6206         getmaxyx(stdscr, y, x);
6207         status_win = newwin(1, 0, y - 1, 0);
6208         if (!status_win)
6209                 die("Failed to create status window");
6211         /* Enable keyboard mapping */
6212         keypad(status_win, TRUE);
6213         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6215         TABSIZE = opt_tab_size;
6216         if (opt_line_graphics) {
6217                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6218         }
6220         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6221         if (term && !strcmp(term, "gnome-terminal")) {
6222                 /* In the gnome-terminal-emulator, the message from
6223                  * scrolling up one line when impossible followed by
6224                  * scrolling down one line causes corruption of the
6225                  * status line. This is fixed by calling wclear. */
6226                 use_scroll_status_wclear = TRUE;
6227                 use_scroll_redrawwin = FALSE;
6229         } else if (term && !strcmp(term, "xrvt-xpm")) {
6230                 /* No problems with full optimizations in xrvt-(unicode)
6231                  * and aterm. */
6232                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6234         } else {
6235                 /* When scrolling in (u)xterm the last line in the
6236                  * scrolling direction will update slowly. */
6237                 use_scroll_redrawwin = TRUE;
6238                 use_scroll_status_wclear = FALSE;
6239         }
6242 static int
6243 get_input(int prompt_position)
6245         struct view *view;
6246         int i, key, cursor_y, cursor_x;
6248         if (prompt_position)
6249                 input_mode = TRUE;
6251         while (TRUE) {
6252                 foreach_view (view, i) {
6253                         update_view(view);
6254                         if (view_is_displayed(view) && view->has_scrolled &&
6255                             use_scroll_redrawwin)
6256                                 redrawwin(view->win);
6257                         view->has_scrolled = FALSE;
6258                 }
6260                 /* Update the cursor position. */
6261                 if (prompt_position) {
6262                         getbegyx(status_win, cursor_y, cursor_x);
6263                         cursor_x = prompt_position;
6264                 } else {
6265                         view = display[current_view];
6266                         getbegyx(view->win, cursor_y, cursor_x);
6267                         cursor_x = view->width - 1;
6268                         cursor_y += view->lineno - view->offset;
6269                 }
6270                 setsyx(cursor_y, cursor_x);
6272                 /* Refresh, accept single keystroke of input */
6273                 doupdate();
6274                 key = wgetch(status_win);
6276                 /* wgetch() with nodelay() enabled returns ERR when
6277                  * there's no input. */
6278                 if (key == ERR) {
6280                 } else if (key == KEY_RESIZE) {
6281                         int height, width;
6283                         getmaxyx(stdscr, height, width);
6285                         wresize(status_win, 1, width);
6286                         mvwin(status_win, height - 1, 0);
6287                         wnoutrefresh(status_win);
6288                         resize_display();
6289                         redraw_display(TRUE);
6291                 } else {
6292                         input_mode = FALSE;
6293                         return key;
6294                 }
6295         }
6298 static char *
6299 prompt_input(const char *prompt, input_handler handler, void *data)
6301         enum input_status status = INPUT_OK;
6302         static char buf[SIZEOF_STR];
6303         size_t pos = 0;
6305         buf[pos] = 0;
6307         while (status == INPUT_OK || status == INPUT_SKIP) {
6308                 int key;
6310                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6311                 wclrtoeol(status_win);
6313                 key = get_input(pos + 1);
6314                 switch (key) {
6315                 case KEY_RETURN:
6316                 case KEY_ENTER:
6317                 case '\n':
6318                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6319                         break;
6321                 case KEY_BACKSPACE:
6322                         if (pos > 0)
6323                                 buf[--pos] = 0;
6324                         else
6325                                 status = INPUT_CANCEL;
6326                         break;
6328                 case KEY_ESC:
6329                         status = INPUT_CANCEL;
6330                         break;
6332                 default:
6333                         if (pos >= sizeof(buf)) {
6334                                 report("Input string too long");
6335                                 return NULL;
6336                         }
6338                         status = handler(data, buf, key);
6339                         if (status == INPUT_OK)
6340                                 buf[pos++] = (char) key;
6341                 }
6342         }
6344         /* Clear the status window */
6345         status_empty = FALSE;
6346         report("");
6348         if (status == INPUT_CANCEL)
6349                 return NULL;
6351         buf[pos++] = 0;
6353         return buf;
6356 static enum input_status
6357 prompt_yesno_handler(void *data, char *buf, int c)
6359         if (c == 'y' || c == 'Y')
6360                 return INPUT_STOP;
6361         if (c == 'n' || c == 'N')
6362                 return INPUT_CANCEL;
6363         return INPUT_SKIP;
6366 static bool
6367 prompt_yesno(const char *prompt)
6369         char prompt2[SIZEOF_STR];
6371         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6372                 return FALSE;
6374         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6377 static enum input_status
6378 read_prompt_handler(void *data, char *buf, int c)
6380         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6383 static char *
6384 read_prompt(const char *prompt)
6386         return prompt_input(prompt, read_prompt_handler, NULL);
6389 /*
6390  * Repository properties
6391  */
6393 static struct ref *refs = NULL;
6394 static size_t refs_alloc = 0;
6395 static size_t refs_size = 0;
6397 /* Id <-> ref store */
6398 static struct ref ***id_refs = NULL;
6399 static size_t id_refs_alloc = 0;
6400 static size_t id_refs_size = 0;
6402 static int
6403 compare_refs(const void *ref1_, const void *ref2_)
6405         const struct ref *ref1 = *(const struct ref **)ref1_;
6406         const struct ref *ref2 = *(const struct ref **)ref2_;
6408         if (ref1->tag != ref2->tag)
6409                 return ref2->tag - ref1->tag;
6410         if (ref1->ltag != ref2->ltag)
6411                 return ref2->ltag - ref2->ltag;
6412         if (ref1->head != ref2->head)
6413                 return ref2->head - ref1->head;
6414         if (ref1->tracked != ref2->tracked)
6415                 return ref2->tracked - ref1->tracked;
6416         if (ref1->remote != ref2->remote)
6417                 return ref2->remote - ref1->remote;
6418         return strcmp(ref1->name, ref2->name);
6421 static struct ref **
6422 get_refs(const char *id)
6424         struct ref ***tmp_id_refs;
6425         struct ref **ref_list = NULL;
6426         size_t ref_list_alloc = 0;
6427         size_t ref_list_size = 0;
6428         size_t i;
6430         for (i = 0; i < id_refs_size; i++)
6431                 if (!strcmp(id, id_refs[i][0]->id))
6432                         return id_refs[i];
6434         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6435                                     sizeof(*id_refs));
6436         if (!tmp_id_refs)
6437                 return NULL;
6439         id_refs = tmp_id_refs;
6441         for (i = 0; i < refs_size; i++) {
6442                 struct ref **tmp;
6444                 if (strcmp(id, refs[i].id))
6445                         continue;
6447                 tmp = realloc_items(ref_list, &ref_list_alloc,
6448                                     ref_list_size + 1, sizeof(*ref_list));
6449                 if (!tmp) {
6450                         if (ref_list)
6451                                 free(ref_list);
6452                         return NULL;
6453                 }
6455                 ref_list = tmp;
6456                 ref_list[ref_list_size] = &refs[i];
6457                 /* XXX: The properties of the commit chains ensures that we can
6458                  * safely modify the shared ref. The repo references will
6459                  * always be similar for the same id. */
6460                 ref_list[ref_list_size]->next = 1;
6462                 ref_list_size++;
6463         }
6465         if (ref_list) {
6466                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6467                 ref_list[ref_list_size - 1]->next = 0;
6468                 id_refs[id_refs_size++] = ref_list;
6469         }
6471         return ref_list;
6474 static int
6475 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6477         struct ref *ref;
6478         bool tag = FALSE;
6479         bool ltag = FALSE;
6480         bool remote = FALSE;
6481         bool tracked = FALSE;
6482         bool check_replace = FALSE;
6483         bool head = FALSE;
6485         if (!prefixcmp(name, "refs/tags/")) {
6486                 if (!suffixcmp(name, namelen, "^{}")) {
6487                         namelen -= 3;
6488                         name[namelen] = 0;
6489                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6490                                 check_replace = TRUE;
6491                 } else {
6492                         ltag = TRUE;
6493                 }
6495                 tag = TRUE;
6496                 namelen -= STRING_SIZE("refs/tags/");
6497                 name    += STRING_SIZE("refs/tags/");
6499         } else if (!prefixcmp(name, "refs/remotes/")) {
6500                 remote = TRUE;
6501                 namelen -= STRING_SIZE("refs/remotes/");
6502                 name    += STRING_SIZE("refs/remotes/");
6503                 tracked  = !strcmp(opt_remote, name);
6505         } else if (!prefixcmp(name, "refs/heads/")) {
6506                 namelen -= STRING_SIZE("refs/heads/");
6507                 name    += STRING_SIZE("refs/heads/");
6508                 head     = !strncmp(opt_head, name, namelen);
6510         } else if (!strcmp(name, "HEAD")) {
6511                 string_ncopy(opt_head_rev, id, idlen);
6512                 return OK;
6513         }
6515         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6516                 /* it's an annotated tag, replace the previous sha1 with the
6517                  * resolved commit id; relies on the fact git-ls-remote lists
6518                  * the commit id of an annotated tag right before the commit id
6519                  * it points to. */
6520                 refs[refs_size - 1].ltag = ltag;
6521                 string_copy_rev(refs[refs_size - 1].id, id);
6523                 return OK;
6524         }
6525         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6526         if (!refs)
6527                 return ERR;
6529         ref = &refs[refs_size++];
6530         ref->name = malloc(namelen + 1);
6531         if (!ref->name)
6532                 return ERR;
6534         strncpy(ref->name, name, namelen);
6535         ref->name[namelen] = 0;
6536         ref->head = head;
6537         ref->tag = tag;
6538         ref->ltag = ltag;
6539         ref->remote = remote;
6540         ref->tracked = tracked;
6541         string_copy_rev(ref->id, id);
6543         return OK;
6546 static int
6547 load_refs(void)
6549         static const char *ls_remote_argv[SIZEOF_ARG] = {
6550                 "git", "ls-remote", ".", NULL
6551         };
6552         static bool init = FALSE;
6554         if (!init) {
6555                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6556                 init = TRUE;
6557         }
6559         if (!*opt_git_dir)
6560                 return OK;
6562         while (refs_size > 0)
6563                 free(refs[--refs_size].name);
6564         while (id_refs_size > 0)
6565                 free(id_refs[--id_refs_size]);
6567         return run_io_load(ls_remote_argv, "\t", read_ref);
6570 static int
6571 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6573         if (!strcmp(name, "i18n.commitencoding"))
6574                 string_ncopy(opt_encoding, value, valuelen);
6576         if (!strcmp(name, "core.editor"))
6577                 string_ncopy(opt_editor, value, valuelen);
6579         /* branch.<head>.remote */
6580         if (*opt_head &&
6581             !strncmp(name, "branch.", 7) &&
6582             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6583             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6584                 string_ncopy(opt_remote, value, valuelen);
6586         if (*opt_head && *opt_remote &&
6587             !strncmp(name, "branch.", 7) &&
6588             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6589             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6590                 size_t from = strlen(opt_remote);
6592                 if (!prefixcmp(value, "refs/heads/")) {
6593                         value += STRING_SIZE("refs/heads/");
6594                         valuelen -= STRING_SIZE("refs/heads/");
6595                 }
6597                 if (!string_format_from(opt_remote, &from, "/%s", value))
6598                         opt_remote[0] = 0;
6599         }
6601         return OK;
6604 static int
6605 load_git_config(void)
6607         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6609         return run_io_load(config_list_argv, "=", read_repo_config_option);
6612 static int
6613 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6615         if (!opt_git_dir[0]) {
6616                 string_ncopy(opt_git_dir, name, namelen);
6618         } else if (opt_is_inside_work_tree == -1) {
6619                 /* This can be 3 different values depending on the
6620                  * version of git being used. If git-rev-parse does not
6621                  * understand --is-inside-work-tree it will simply echo
6622                  * the option else either "true" or "false" is printed.
6623                  * Default to true for the unknown case. */
6624                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6626         } else if (*name == '.') {
6627                 string_ncopy(opt_cdup, name, namelen);
6629         } else {
6630                 string_ncopy(opt_prefix, name, namelen);
6631         }
6633         return OK;
6636 static int
6637 load_repo_info(void)
6639         const char *head_argv[] = {
6640                 "git", "symbolic-ref", "HEAD", NULL
6641         };
6642         const char *rev_parse_argv[] = {
6643                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6644                         "--show-cdup", "--show-prefix", NULL
6645         };
6647         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6648                 chomp_string(opt_head);
6649                 if (!prefixcmp(opt_head, "refs/heads/")) {
6650                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6652                         memmove(opt_head, offset, strlen(offset) + 1);
6653                 }
6654         }
6656         return run_io_load(rev_parse_argv, "=", read_repo_info);
6660 /*
6661  * Main
6662  */
6664 static void __NORETURN
6665 quit(int sig)
6667         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6668         if (cursed)
6669                 endwin();
6670         exit(0);
6673 static void __NORETURN
6674 die(const char *err, ...)
6676         va_list args;
6678         endwin();
6680         va_start(args, err);
6681         fputs("tig: ", stderr);
6682         vfprintf(stderr, err, args);
6683         fputs("\n", stderr);
6684         va_end(args);
6686         exit(1);
6689 static void
6690 warn(const char *msg, ...)
6692         va_list args;
6694         va_start(args, msg);
6695         fputs("tig warning: ", stderr);
6696         vfprintf(stderr, msg, args);
6697         fputs("\n", stderr);
6698         va_end(args);
6701 static enum request
6702 parse_options(int argc, const char *argv[])
6704         enum request request = REQ_VIEW_MAIN;
6705         const char *subcommand;
6706         bool seen_dashdash = FALSE;
6707         /* XXX: This is vulnerable to the user overriding options
6708          * required for the main view parser. */
6709         const char *custom_argv[SIZEOF_ARG] = {
6710                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6711                         "--topo-order", NULL
6712         };
6713         int i, j = 6;
6715         if (!isatty(STDIN_FILENO)) {
6716                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6717                 return REQ_VIEW_PAGER;
6718         }
6720         if (argc <= 1)
6721                 return REQ_NONE;
6723         subcommand = argv[1];
6724         if (!strcmp(subcommand, "status")) {
6725                 if (argc > 2)
6726                         warn("ignoring arguments after `%s'", subcommand);
6727                 return REQ_VIEW_STATUS;
6729         } else if (!strcmp(subcommand, "blame")) {
6730                 if (argc <= 2 || argc > 4)
6731                         die("invalid number of options to blame\n\n%s", usage);
6733                 i = 2;
6734                 if (argc == 4) {
6735                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6736                         i++;
6737                 }
6739                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6740                 return REQ_VIEW_BLAME;
6742         } else if (!strcmp(subcommand, "show")) {
6743                 request = REQ_VIEW_DIFF;
6745         } else {
6746                 subcommand = NULL;
6747         }
6749         if (subcommand) {
6750                 custom_argv[1] = subcommand;
6751                 j = 2;
6752         }
6754         for (i = 1 + !!subcommand; i < argc; i++) {
6755                 const char *opt = argv[i];
6757                 if (seen_dashdash || !strcmp(opt, "--")) {
6758                         seen_dashdash = TRUE;
6760                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6761                         printf("tig version %s\n", TIG_VERSION);
6762                         quit(0);
6764                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6765                         printf("%s\n", usage);
6766                         quit(0);
6767                 }
6769                 custom_argv[j++] = opt;
6770                 if (j >= ARRAY_SIZE(custom_argv))
6771                         die("command too long");
6772         }
6774         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6775                 die("Failed to format arguments"); 
6777         return request;
6780 int
6781 main(int argc, const char *argv[])
6783         enum request request = parse_options(argc, argv);
6784         struct view *view;
6785         size_t i;
6787         signal(SIGINT, quit);
6789         if (setlocale(LC_ALL, "")) {
6790                 char *codeset = nl_langinfo(CODESET);
6792                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6793         }
6795         if (load_repo_info() == ERR)
6796                 die("Failed to load repo info.");
6798         if (load_options() == ERR)
6799                 die("Failed to load user config.");
6801         if (load_git_config() == ERR)
6802                 die("Failed to load repo config.");
6804         /* Require a git repository unless when running in pager mode. */
6805         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6806                 die("Not a git repository");
6808         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6809                 opt_utf8 = FALSE;
6811         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6812                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6813                 if (opt_iconv == ICONV_NONE)
6814                         die("Failed to initialize character set conversion");
6815         }
6817         if (load_refs() == ERR)
6818                 die("Failed to load refs.");
6820         foreach_view (view, i)
6821                 argv_from_env(view->ops->argv, view->cmd_env);
6823         init_display();
6825         if (request != REQ_NONE)
6826                 open_view(NULL, request, OPEN_PREPARED);
6827         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6829         while (view_driver(display[current_view], request)) {
6830                 int key = get_input(0);
6832                 view = display[current_view];
6833                 request = get_keybinding(view->keymap, key);
6835                 /* Some low-level request handling. This keeps access to
6836                  * status_win restricted. */
6837                 switch (request) {
6838                 case REQ_PROMPT:
6839                 {
6840                         char *cmd = read_prompt(":");
6842                         if (cmd) {
6843                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6844                                 const char *argv[SIZEOF_ARG] = { "git" };
6845                                 int argc = 1;
6847                                 /* When running random commands, initially show the
6848                                  * command in the title. However, it maybe later be
6849                                  * overwritten if a commit line is selected. */
6850                                 string_ncopy(next->ref, cmd, strlen(cmd));
6852                                 if (!argv_from_string(argv, &argc, cmd)) {
6853                                         report("Too many arguments");
6854                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6855                                         report("Failed to format command");
6856                                 } else {
6857                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6858                                 }
6859                         }
6861                         request = REQ_NONE;
6862                         break;
6863                 }
6864                 case REQ_SEARCH:
6865                 case REQ_SEARCH_BACK:
6866                 {
6867                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6868                         char *search = read_prompt(prompt);
6870                         if (search)
6871                                 string_ncopy(opt_search, search, strlen(search));
6872                         else if (*opt_search)
6873                                 request = request == REQ_SEARCH ?
6874                                         REQ_FIND_NEXT :
6875                                         REQ_FIND_PREV;
6876                         else
6877                                 request = REQ_NONE;
6878                         break;
6879                 }
6880                 default:
6881                         break;
6882                 }
6883         }
6885         quit(0);
6887         return 0;