Code

Make the blame view expand tabs at drawing time
[tig.git] / tig.c
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
42 #include <regex.h>
44 #include <locale.h>
45 #include <langinfo.h>
46 #include <iconv.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
53 #else
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
56 #else
57 #include <ncurses.h>
58 #endif
59 #endif
61 #if __GNUC__ >= 3
62 #define __NORETURN __attribute__((__noreturn__))
63 #else
64 #define __NORETURN
65 #endif
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
75 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
77 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x)  (sizeof(x) - 1)
80 #define SIZEOF_STR      1024    /* Default string size. */
81 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG      32      /* Default argument array size. */
85 /* Revision graph */
87 #define REVGRAPH_INIT   'I'
88 #define REVGRAPH_MERGE  'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND  '^'
93 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT   (-1)
98 #define ICONV_NONE      ((iconv_t) -1)
99 #ifndef ICONV_CONST
100 #define ICONV_CONST     /* nothing */
101 #endif
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
105 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS     20
108 #define ID_COLS         8
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
114 #define TAB_SIZE        8
116 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
118 #define NULL_ID         "0000000000000000000000000000000000000000"
120 #ifndef GIT_CONFIG
121 #define GIT_CONFIG "config"
122 #endif
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
125 #define KEY_TAB         '\t'
126 #define KEY_RETURN      '\r'
127 #define KEY_ESC         27
130 struct ref {
131         char *name;             /* Ref name; tag or head names are shortened. */
132         char id[SIZEOF_REV];    /* Commit SHA1 ID */
133         unsigned int head:1;    /* Is it the current HEAD? */
134         unsigned int tag:1;     /* Is it a tag? */
135         unsigned int ltag:1;    /* If so, is the tag local? */
136         unsigned int remote:1;  /* Is it a remote ref? */
137         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138         unsigned int next:1;    /* For ref lists: are there more refs? */
139 };
141 static struct ref **get_refs(const char *id);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 /*
164  * String helpers
165  */
167 static inline void
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170         if (srclen > dstlen - 1)
171                 srclen = dstlen - 1;
173         strncpy(dst, src, srclen);
174         dst[srclen] = 0;
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183         string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
191 static void
192 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
194         size_t size, pos;
196         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
197                 if (src[pos] == '\t') {
198                         size_t expanded = tabsize - (size % tabsize);
200                         if (expanded + size >= dstlen - 1)
201                                 expanded = dstlen - size - 1;
202                         memcpy(dst + size, "        ", expanded);
203                         size += expanded;
204                 } else {
205                         dst[size++] = src[pos];
206                 }
207         }
209         dst[size] = 0;
212 static char *
213 chomp_string(char *name)
215         int namelen;
217         while (isspace(*name))
218                 name++;
220         namelen = strlen(name) - 1;
221         while (namelen > 0 && isspace(name[namelen]))
222                 name[namelen--] = 0;
224         return name;
227 static bool
228 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
230         va_list args;
231         size_t pos = bufpos ? *bufpos : 0;
233         va_start(args, fmt);
234         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
235         va_end(args);
237         if (bufpos)
238                 *bufpos = pos;
240         return pos >= bufsize ? FALSE : TRUE;
243 #define string_format(buf, fmt, args...) \
244         string_nformat(buf, sizeof(buf), NULL, fmt, args)
246 #define string_format_from(buf, from, fmt, args...) \
247         string_nformat(buf, sizeof(buf), from, fmt, args)
249 static int
250 string_enum_compare(const char *str1, const char *str2, int len)
252         size_t i;
254 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
256         /* Diff-Header == DIFF_HEADER */
257         for (i = 0; i < len; i++) {
258                 if (toupper(str1[i]) == toupper(str2[i]))
259                         continue;
261                 if (string_enum_sep(str1[i]) &&
262                     string_enum_sep(str2[i]))
263                         continue;
265                 return str1[i] - str2[i];
266         }
268         return 0;
271 struct enum_map {
272         const char *name;
273         int namelen;
274         int value;
275 };
277 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
279 static bool
280 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
282         size_t namelen = strlen(name);
283         int i;
285         for (i = 0; i < map_size; i++)
286                 if (namelen == map[i].namelen &&
287                     !string_enum_compare(name, map[i].name, namelen)) {
288                         *value = map[i].value;
289                         return TRUE;
290                 }
292         return FALSE;
295 #define map_enum(attr, map, name) \
296         map_enum_do(map, ARRAY_SIZE(map), attr, name)
298 #define prefixcmp(str1, str2) \
299         strncmp(str1, str2, STRING_SIZE(str2))
301 static inline int
302 suffixcmp(const char *str, int slen, const char *suffix)
304         size_t len = slen >= 0 ? slen : strlen(str);
305         size_t suffixlen = strlen(suffix);
307         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
311 static bool
312 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
314         int valuelen;
316         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
317                 bool advance = cmd[valuelen] != 0;
319                 cmd[valuelen] = 0;
320                 argv[(*argc)++] = chomp_string(cmd);
321                 cmd = chomp_string(cmd + valuelen + advance);
322         }
324         if (*argc < SIZEOF_ARG)
325                 argv[*argc] = NULL;
326         return *argc < SIZEOF_ARG;
329 static void
330 argv_from_env(const char **argv, const char *name)
332         char *env = argv ? getenv(name) : NULL;
333         int argc = 0;
335         if (env && *env)
336                 env = strdup(env);
337         if (env && !argv_from_string(argv, &argc, env))
338                 die("Too many arguments in the `%s` environment variable", name);
342 /*
343  * Executing external commands.
344  */
346 enum io_type {
347         IO_FD,                  /* File descriptor based IO. */
348         IO_BG,                  /* Execute command in the background. */
349         IO_FG,                  /* Execute command with same std{in,out,err}. */
350         IO_RD,                  /* Read only fork+exec IO. */
351         IO_WR,                  /* Write only fork+exec IO. */
352         IO_AP,                  /* Append fork+exec output to file. */
353 };
355 struct io {
356         enum io_type type;      /* The requested type of pipe. */
357         const char *dir;        /* Directory from which to execute. */
358         pid_t pid;              /* Pipe for reading or writing. */
359         int pipe;               /* Pipe end for reading or writing. */
360         int error;              /* Error status. */
361         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
362         char *buf;              /* Read buffer. */
363         size_t bufalloc;        /* Allocated buffer size. */
364         size_t bufsize;         /* Buffer content size. */
365         char *bufpos;           /* Current buffer position. */
366         unsigned int eof:1;     /* Has end of file been reached. */
367 };
369 static void
370 reset_io(struct io *io)
372         io->pipe = -1;
373         io->pid = 0;
374         io->buf = io->bufpos = NULL;
375         io->bufalloc = io->bufsize = 0;
376         io->error = 0;
377         io->eof = 0;
380 static void
381 init_io(struct io *io, const char *dir, enum io_type type)
383         reset_io(io);
384         io->type = type;
385         io->dir = dir;
388 static bool
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390                 enum format_flags flags)
392         init_io(io, dir, IO_RD);
393         return format_argv(io->argv, argv, flags);
396 static bool
397 io_open(struct io *io, const char *name)
399         init_io(io, NULL, IO_FD);
400         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
401         return io->pipe != -1;
404 static bool
405 kill_io(struct io *io)
407         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
410 static bool
411 done_io(struct io *io)
413         pid_t pid = io->pid;
415         if (io->pipe != -1)
416                 close(io->pipe);
417         free(io->buf);
418         reset_io(io);
420         while (pid > 0) {
421                 int status;
422                 pid_t waiting = waitpid(pid, &status, 0);
424                 if (waiting < 0) {
425                         if (errno == EINTR)
426                                 continue;
427                         report("waitpid failed (%s)", strerror(errno));
428                         return FALSE;
429                 }
431                 return waiting == pid &&
432                        !WIFSIGNALED(status) &&
433                        WIFEXITED(status) &&
434                        !WEXITSTATUS(status);
435         }
437         return TRUE;
440 static bool
441 start_io(struct io *io)
443         int pipefds[2] = { -1, -1 };
445         if (io->type == IO_FD)
446                 return TRUE;
448         if ((io->type == IO_RD || io->type == IO_WR) &&
449             pipe(pipefds) < 0)
450                 return FALSE;
451         else if (io->type == IO_AP)
452                 pipefds[1] = io->pipe;
454         if ((io->pid = fork())) {
455                 if (pipefds[!(io->type == IO_WR)] != -1)
456                         close(pipefds[!(io->type == IO_WR)]);
457                 if (io->pid != -1) {
458                         io->pipe = pipefds[!!(io->type == IO_WR)];
459                         return TRUE;
460                 }
462         } else {
463                 if (io->type != IO_FG) {
464                         int devnull = open("/dev/null", O_RDWR);
465                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
466                         int writefd = (io->type == IO_RD || io->type == IO_AP)
467                                                         ? pipefds[1] : devnull;
469                         dup2(readfd,  STDIN_FILENO);
470                         dup2(writefd, STDOUT_FILENO);
471                         dup2(devnull, STDERR_FILENO);
473                         close(devnull);
474                         if (pipefds[0] != -1)
475                                 close(pipefds[0]);
476                         if (pipefds[1] != -1)
477                                 close(pipefds[1]);
478                 }
480                 if (io->dir && *io->dir && chdir(io->dir) == -1)
481                         die("Failed to change directory: %s", strerror(errno));
483                 execvp(io->argv[0], (char *const*) io->argv);
484                 die("Failed to execute program: %s", strerror(errno));
485         }
487         if (pipefds[!!(io->type == IO_WR)] != -1)
488                 close(pipefds[!!(io->type == IO_WR)]);
489         return FALSE;
492 static bool
493 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
495         init_io(io, dir, type);
496         if (!format_argv(io->argv, argv, FORMAT_NONE))
497                 return FALSE;
498         return start_io(io);
501 static int
502 run_io_do(struct io *io)
504         return start_io(io) && done_io(io);
507 static int
508 run_io_bg(const char **argv)
510         struct io io = {};
512         init_io(&io, NULL, IO_BG);
513         if (!format_argv(io.argv, argv, FORMAT_NONE))
514                 return FALSE;
515         return run_io_do(&io);
518 static bool
519 run_io_fg(const char **argv, const char *dir)
521         struct io io = {};
523         init_io(&io, dir, IO_FG);
524         if (!format_argv(io.argv, argv, FORMAT_NONE))
525                 return FALSE;
526         return run_io_do(&io);
529 static bool
530 run_io_append(const char **argv, enum format_flags flags, int fd)
532         struct io io = {};
534         init_io(&io, NULL, IO_AP);
535         io.pipe = fd;
536         if (format_argv(io.argv, argv, flags))
537                 return run_io_do(&io);
538         close(fd);
539         return FALSE;
542 static bool
543 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
545         return init_io_rd(io, argv, NULL, flags) && start_io(io);
548 static bool
549 io_eof(struct io *io)
551         return io->eof;
554 static int
555 io_error(struct io *io)
557         return io->error;
560 static bool
561 io_strerror(struct io *io)
563         return strerror(io->error);
566 static bool
567 io_can_read(struct io *io)
569         struct timeval tv = { 0, 500 };
570         fd_set fds;
572         FD_ZERO(&fds);
573         FD_SET(io->pipe, &fds);
575         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
578 static ssize_t
579 io_read(struct io *io, void *buf, size_t bufsize)
581         do {
582                 ssize_t readsize = read(io->pipe, buf, bufsize);
584                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
585                         continue;
586                 else if (readsize == -1)
587                         io->error = errno;
588                 else if (readsize == 0)
589                         io->eof = 1;
590                 return readsize;
591         } while (1);
594 static char *
595 io_get(struct io *io, int c, bool can_read)
597         char *eol;
598         ssize_t readsize;
600         if (!io->buf) {
601                 io->buf = io->bufpos = malloc(BUFSIZ);
602                 if (!io->buf)
603                         return NULL;
604                 io->bufalloc = BUFSIZ;
605                 io->bufsize = 0;
606         }
608         while (TRUE) {
609                 if (io->bufsize > 0) {
610                         eol = memchr(io->bufpos, c, io->bufsize);
611                         if (eol) {
612                                 char *line = io->bufpos;
614                                 *eol = 0;
615                                 io->bufpos = eol + 1;
616                                 io->bufsize -= io->bufpos - line;
617                                 return line;
618                         }
619                 }
621                 if (io_eof(io)) {
622                         if (io->bufsize) {
623                                 io->bufpos[io->bufsize] = 0;
624                                 io->bufsize = 0;
625                                 return io->bufpos;
626                         }
627                         return NULL;
628                 }
630                 if (!can_read)
631                         return NULL;
633                 if (io->bufsize > 0 && io->bufpos > io->buf)
634                         memmove(io->buf, io->bufpos, io->bufsize);
636                 io->bufpos = io->buf;
637                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
638                 if (io_error(io))
639                         return NULL;
640                 io->bufsize += readsize;
641         }
644 static bool
645 io_write(struct io *io, const void *buf, size_t bufsize)
647         size_t written = 0;
649         while (!io_error(io) && written < bufsize) {
650                 ssize_t size;
652                 size = write(io->pipe, buf + written, bufsize - written);
653                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
654                         continue;
655                 else if (size == -1)
656                         io->error = errno;
657                 else
658                         written += size;
659         }
661         return written == bufsize;
664 static bool
665 io_read_buf(struct io *io, char buf[], size_t bufsize)
667         bool error;
669         io->buf = io->bufpos = buf;
670         io->bufalloc = bufsize;
671         error = !io_get(io, '\n', TRUE) && io_error(io);
672         io->buf = NULL;
674         return done_io(io) || error;
677 static bool
678 run_io_buf(const char **argv, char buf[], size_t bufsize)
680         struct io io = {};
682         return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
685 static int
686 io_load(struct io *io, const char *separators,
687         int (*read_property)(char *, size_t, char *, size_t))
689         char *name;
690         int state = OK;
692         if (!start_io(io))
693                 return ERR;
695         while (state == OK && (name = io_get(io, '\n', TRUE))) {
696                 char *value;
697                 size_t namelen;
698                 size_t valuelen;
700                 name = chomp_string(name);
701                 namelen = strcspn(name, separators);
703                 if (name[namelen]) {
704                         name[namelen] = 0;
705                         value = chomp_string(name + namelen + 1);
706                         valuelen = strlen(value);
708                 } else {
709                         value = "";
710                         valuelen = 0;
711                 }
713                 state = read_property(name, namelen, value, valuelen);
714         }
716         if (state != ERR && io_error(io))
717                 state = ERR;
718         done_io(io);
720         return state;
723 static int
724 run_io_load(const char **argv, const char *separators,
725             int (*read_property)(char *, size_t, char *, size_t))
727         struct io io = {};
729         return init_io_rd(&io, argv, NULL, FORMAT_NONE)
730                 ? io_load(&io, separators, read_property) : ERR;
734 /*
735  * User requests
736  */
738 #define REQ_INFO \
739         /* XXX: Keep the view request first and in sync with views[]. */ \
740         REQ_GROUP("View switching") \
741         REQ_(VIEW_MAIN,         "Show main view"), \
742         REQ_(VIEW_DIFF,         "Show diff view"), \
743         REQ_(VIEW_LOG,          "Show log view"), \
744         REQ_(VIEW_TREE,         "Show tree view"), \
745         REQ_(VIEW_BLOB,         "Show blob view"), \
746         REQ_(VIEW_BLAME,        "Show blame view"), \
747         REQ_(VIEW_HELP,         "Show help page"), \
748         REQ_(VIEW_PAGER,        "Show pager view"), \
749         REQ_(VIEW_STATUS,       "Show status view"), \
750         REQ_(VIEW_STAGE,        "Show stage view"), \
751         \
752         REQ_GROUP("View manipulation") \
753         REQ_(ENTER,             "Enter current line and scroll"), \
754         REQ_(NEXT,              "Move to next"), \
755         REQ_(PREVIOUS,          "Move to previous"), \
756         REQ_(PARENT,            "Move to parent"), \
757         REQ_(VIEW_NEXT,         "Move focus to next view"), \
758         REQ_(REFRESH,           "Reload and refresh"), \
759         REQ_(MAXIMIZE,          "Maximize the current view"), \
760         REQ_(VIEW_CLOSE,        "Close the current view"), \
761         REQ_(QUIT,              "Close all views and quit"), \
762         \
763         REQ_GROUP("View specific requests") \
764         REQ_(STATUS_UPDATE,     "Update file status"), \
765         REQ_(STATUS_REVERT,     "Revert file changes"), \
766         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
767         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
768         \
769         REQ_GROUP("Cursor navigation") \
770         REQ_(MOVE_UP,           "Move cursor one line up"), \
771         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
772         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
773         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
774         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
775         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
776         \
777         REQ_GROUP("Scrolling") \
778         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
779         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
780         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
781         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
782         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
783         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
784         \
785         REQ_GROUP("Searching") \
786         REQ_(SEARCH,            "Search the view"), \
787         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
788         REQ_(FIND_NEXT,         "Find next search match"), \
789         REQ_(FIND_PREV,         "Find previous search match"), \
790         \
791         REQ_GROUP("Option manipulation") \
792         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
793         REQ_(TOGGLE_DATE,       "Toggle date display"), \
794         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
795         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
796         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
797         \
798         REQ_GROUP("Misc") \
799         REQ_(PROMPT,            "Bring up the prompt"), \
800         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
801         REQ_(SHOW_VERSION,      "Show version information"), \
802         REQ_(STOP_LOADING,      "Stop all loading views"), \
803         REQ_(EDIT,              "Open in editor"), \
804         REQ_(NONE,              "Do nothing")
807 /* User action requests. */
808 enum request {
809 #define REQ_GROUP(help)
810 #define REQ_(req, help) REQ_##req
812         /* Offset all requests to avoid conflicts with ncurses getch values. */
813         REQ_OFFSET = KEY_MAX + 1,
814         REQ_INFO
816 #undef  REQ_GROUP
817 #undef  REQ_
818 };
820 struct request_info {
821         enum request request;
822         const char *name;
823         int namelen;
824         const char *help;
825 };
827 static struct request_info req_info[] = {
828 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
829 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
830         REQ_INFO
831 #undef  REQ_GROUP
832 #undef  REQ_
833 };
835 static enum request
836 get_request(const char *name)
838         int namelen = strlen(name);
839         int i;
841         for (i = 0; i < ARRAY_SIZE(req_info); i++)
842                 if (req_info[i].namelen == namelen &&
843                     !string_enum_compare(req_info[i].name, name, namelen))
844                         return req_info[i].request;
846         return REQ_NONE;
850 /*
851  * Options
852  */
854 /* Option and state variables. */
855 static bool opt_date                    = TRUE;
856 static bool opt_author                  = TRUE;
857 static bool opt_line_number             = FALSE;
858 static bool opt_line_graphics           = TRUE;
859 static bool opt_rev_graph               = FALSE;
860 static bool opt_show_refs               = TRUE;
861 static int opt_num_interval             = NUMBER_INTERVAL;
862 static int opt_tab_size                 = TAB_SIZE;
863 static int opt_author_cols              = AUTHOR_COLS-1;
864 static char opt_path[SIZEOF_STR]        = "";
865 static char opt_file[SIZEOF_STR]        = "";
866 static char opt_ref[SIZEOF_REF]         = "";
867 static char opt_head[SIZEOF_REF]        = "";
868 static char opt_head_rev[SIZEOF_REV]    = "";
869 static char opt_remote[SIZEOF_REF]      = "";
870 static char opt_encoding[20]            = "UTF-8";
871 static bool opt_utf8                    = TRUE;
872 static char opt_codeset[20]             = "UTF-8";
873 static iconv_t opt_iconv                = ICONV_NONE;
874 static char opt_search[SIZEOF_STR]      = "";
875 static char opt_cdup[SIZEOF_STR]        = "";
876 static char opt_prefix[SIZEOF_STR]      = "";
877 static char opt_git_dir[SIZEOF_STR]     = "";
878 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
879 static char opt_editor[SIZEOF_STR]      = "";
880 static FILE *opt_tty                    = NULL;
882 #define is_initial_commit()     (!*opt_head_rev)
883 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
886 /*
887  * Line-oriented content detection.
888  */
890 #define LINE_INFO \
891 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
892 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
893 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
894 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
895 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
896 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
897 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
898 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
899 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
900 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
901 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
902 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
903 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
904 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
905 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
906 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
907 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
908 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
909 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
910 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
911 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
912 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
913 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
914 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
915 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
916 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
917 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
918 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
919 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
920 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
921 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
922 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
923 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
924 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
925 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
926 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
927 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
928 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
929 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
930 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
931 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
932 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
933 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
934 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
935 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
936 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
937 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
938 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
939 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
940 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
941 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
942 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
943 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
944 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
945 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
947 enum line_type {
948 #define LINE(type, line, fg, bg, attr) \
949         LINE_##type
950         LINE_INFO,
951         LINE_NONE
952 #undef  LINE
953 };
955 struct line_info {
956         const char *name;       /* Option name. */
957         int namelen;            /* Size of option name. */
958         const char *line;       /* The start of line to match. */
959         int linelen;            /* Size of string to match. */
960         int fg, bg, attr;       /* Color and text attributes for the lines. */
961 };
963 static struct line_info line_info[] = {
964 #define LINE(type, line, fg, bg, attr) \
965         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
966         LINE_INFO
967 #undef  LINE
968 };
970 static enum line_type
971 get_line_type(const char *line)
973         int linelen = strlen(line);
974         enum line_type type;
976         for (type = 0; type < ARRAY_SIZE(line_info); type++)
977                 /* Case insensitive search matches Signed-off-by lines better. */
978                 if (linelen >= line_info[type].linelen &&
979                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
980                         return type;
982         return LINE_DEFAULT;
985 static inline int
986 get_line_attr(enum line_type type)
988         assert(type < ARRAY_SIZE(line_info));
989         return COLOR_PAIR(type) | line_info[type].attr;
992 static struct line_info *
993 get_line_info(const char *name)
995         size_t namelen = strlen(name);
996         enum line_type type;
998         for (type = 0; type < ARRAY_SIZE(line_info); type++)
999                 if (namelen == line_info[type].namelen &&
1000                     !string_enum_compare(line_info[type].name, name, namelen))
1001                         return &line_info[type];
1003         return NULL;
1006 static void
1007 init_colors(void)
1009         int default_bg = line_info[LINE_DEFAULT].bg;
1010         int default_fg = line_info[LINE_DEFAULT].fg;
1011         enum line_type type;
1013         start_color();
1015         if (assume_default_colors(default_fg, default_bg) == ERR) {
1016                 default_bg = COLOR_BLACK;
1017                 default_fg = COLOR_WHITE;
1018         }
1020         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1021                 struct line_info *info = &line_info[type];
1022                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1023                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1025                 init_pair(type, fg, bg);
1026         }
1029 struct line {
1030         enum line_type type;
1032         /* State flags */
1033         unsigned int selected:1;
1034         unsigned int dirty:1;
1035         unsigned int cleareol:1;
1037         void *data;             /* User data */
1038 };
1041 /*
1042  * Keys
1043  */
1045 struct keybinding {
1046         int alias;
1047         enum request request;
1048 };
1050 static struct keybinding default_keybindings[] = {
1051         /* View switching */
1052         { 'm',          REQ_VIEW_MAIN },
1053         { 'd',          REQ_VIEW_DIFF },
1054         { 'l',          REQ_VIEW_LOG },
1055         { 't',          REQ_VIEW_TREE },
1056         { 'f',          REQ_VIEW_BLOB },
1057         { 'B',          REQ_VIEW_BLAME },
1058         { 'p',          REQ_VIEW_PAGER },
1059         { 'h',          REQ_VIEW_HELP },
1060         { 'S',          REQ_VIEW_STATUS },
1061         { 'c',          REQ_VIEW_STAGE },
1063         /* View manipulation */
1064         { 'q',          REQ_VIEW_CLOSE },
1065         { KEY_TAB,      REQ_VIEW_NEXT },
1066         { KEY_RETURN,   REQ_ENTER },
1067         { KEY_UP,       REQ_PREVIOUS },
1068         { KEY_DOWN,     REQ_NEXT },
1069         { 'R',          REQ_REFRESH },
1070         { KEY_F(5),     REQ_REFRESH },
1071         { 'O',          REQ_MAXIMIZE },
1073         /* Cursor navigation */
1074         { 'k',          REQ_MOVE_UP },
1075         { 'j',          REQ_MOVE_DOWN },
1076         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1077         { KEY_END,      REQ_MOVE_LAST_LINE },
1078         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1079         { ' ',          REQ_MOVE_PAGE_DOWN },
1080         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1081         { 'b',          REQ_MOVE_PAGE_UP },
1082         { '-',          REQ_MOVE_PAGE_UP },
1084         /* Scrolling */
1085         { KEY_LEFT,     REQ_SCROLL_LEFT },
1086         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1087         { KEY_IC,       REQ_SCROLL_LINE_UP },
1088         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1089         { 'w',          REQ_SCROLL_PAGE_UP },
1090         { 's',          REQ_SCROLL_PAGE_DOWN },
1092         /* Searching */
1093         { '/',          REQ_SEARCH },
1094         { '?',          REQ_SEARCH_BACK },
1095         { 'n',          REQ_FIND_NEXT },
1096         { 'N',          REQ_FIND_PREV },
1098         /* Misc */
1099         { 'Q',          REQ_QUIT },
1100         { 'z',          REQ_STOP_LOADING },
1101         { 'v',          REQ_SHOW_VERSION },
1102         { 'r',          REQ_SCREEN_REDRAW },
1103         { '.',          REQ_TOGGLE_LINENO },
1104         { 'D',          REQ_TOGGLE_DATE },
1105         { 'A',          REQ_TOGGLE_AUTHOR },
1106         { 'g',          REQ_TOGGLE_REV_GRAPH },
1107         { 'F',          REQ_TOGGLE_REFS },
1108         { ':',          REQ_PROMPT },
1109         { 'u',          REQ_STATUS_UPDATE },
1110         { '!',          REQ_STATUS_REVERT },
1111         { 'M',          REQ_STATUS_MERGE },
1112         { '@',          REQ_STAGE_NEXT },
1113         { ',',          REQ_PARENT },
1114         { 'e',          REQ_EDIT },
1115 };
1117 #define KEYMAP_INFO \
1118         KEYMAP_(GENERIC), \
1119         KEYMAP_(MAIN), \
1120         KEYMAP_(DIFF), \
1121         KEYMAP_(LOG), \
1122         KEYMAP_(TREE), \
1123         KEYMAP_(BLOB), \
1124         KEYMAP_(BLAME), \
1125         KEYMAP_(PAGER), \
1126         KEYMAP_(HELP), \
1127         KEYMAP_(STATUS), \
1128         KEYMAP_(STAGE)
1130 enum keymap {
1131 #define KEYMAP_(name) KEYMAP_##name
1132         KEYMAP_INFO
1133 #undef  KEYMAP_
1134 };
1136 static struct enum_map keymap_table[] = {
1137 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1138         KEYMAP_INFO
1139 #undef  KEYMAP_
1140 };
1142 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1144 struct keybinding_table {
1145         struct keybinding *data;
1146         size_t size;
1147 };
1149 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1151 static void
1152 add_keybinding(enum keymap keymap, enum request request, int key)
1154         struct keybinding_table *table = &keybindings[keymap];
1156         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1157         if (!table->data)
1158                 die("Failed to allocate keybinding");
1159         table->data[table->size].alias = key;
1160         table->data[table->size++].request = request;
1163 /* Looks for a key binding first in the given map, then in the generic map, and
1164  * lastly in the default keybindings. */
1165 static enum request
1166 get_keybinding(enum keymap keymap, int key)
1168         size_t i;
1170         for (i = 0; i < keybindings[keymap].size; i++)
1171                 if (keybindings[keymap].data[i].alias == key)
1172                         return keybindings[keymap].data[i].request;
1174         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1175                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1176                         return keybindings[KEYMAP_GENERIC].data[i].request;
1178         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1179                 if (default_keybindings[i].alias == key)
1180                         return default_keybindings[i].request;
1182         return (enum request) key;
1186 struct key {
1187         const char *name;
1188         int value;
1189 };
1191 static struct key key_table[] = {
1192         { "Enter",      KEY_RETURN },
1193         { "Space",      ' ' },
1194         { "Backspace",  KEY_BACKSPACE },
1195         { "Tab",        KEY_TAB },
1196         { "Escape",     KEY_ESC },
1197         { "Left",       KEY_LEFT },
1198         { "Right",      KEY_RIGHT },
1199         { "Up",         KEY_UP },
1200         { "Down",       KEY_DOWN },
1201         { "Insert",     KEY_IC },
1202         { "Delete",     KEY_DC },
1203         { "Hash",       '#' },
1204         { "Home",       KEY_HOME },
1205         { "End",        KEY_END },
1206         { "PageUp",     KEY_PPAGE },
1207         { "PageDown",   KEY_NPAGE },
1208         { "F1",         KEY_F(1) },
1209         { "F2",         KEY_F(2) },
1210         { "F3",         KEY_F(3) },
1211         { "F4",         KEY_F(4) },
1212         { "F5",         KEY_F(5) },
1213         { "F6",         KEY_F(6) },
1214         { "F7",         KEY_F(7) },
1215         { "F8",         KEY_F(8) },
1216         { "F9",         KEY_F(9) },
1217         { "F10",        KEY_F(10) },
1218         { "F11",        KEY_F(11) },
1219         { "F12",        KEY_F(12) },
1220 };
1222 static int
1223 get_key_value(const char *name)
1225         int i;
1227         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1228                 if (!strcasecmp(key_table[i].name, name))
1229                         return key_table[i].value;
1231         if (strlen(name) == 1 && isprint(*name))
1232                 return (int) *name;
1234         return ERR;
1237 static const char *
1238 get_key_name(int key_value)
1240         static char key_char[] = "'X'";
1241         const char *seq = NULL;
1242         int key;
1244         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1245                 if (key_table[key].value == key_value)
1246                         seq = key_table[key].name;
1248         if (seq == NULL &&
1249             key_value < 127 &&
1250             isprint(key_value)) {
1251                 key_char[1] = (char) key_value;
1252                 seq = key_char;
1253         }
1255         return seq ? seq : "(no key)";
1258 static const char *
1259 get_key(enum request request)
1261         static char buf[BUFSIZ];
1262         size_t pos = 0;
1263         char *sep = "";
1264         int i;
1266         buf[pos] = 0;
1268         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1269                 struct keybinding *keybinding = &default_keybindings[i];
1271                 if (keybinding->request != request)
1272                         continue;
1274                 if (!string_format_from(buf, &pos, "%s%s", sep,
1275                                         get_key_name(keybinding->alias)))
1276                         return "Too many keybindings!";
1277                 sep = ", ";
1278         }
1280         return buf;
1283 struct run_request {
1284         enum keymap keymap;
1285         int key;
1286         const char *argv[SIZEOF_ARG];
1287 };
1289 static struct run_request *run_request;
1290 static size_t run_requests;
1292 static enum request
1293 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1295         struct run_request *req;
1297         if (argc >= ARRAY_SIZE(req->argv) - 1)
1298                 return REQ_NONE;
1300         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1301         if (!req)
1302                 return REQ_NONE;
1304         run_request = req;
1305         req = &run_request[run_requests];
1306         req->keymap = keymap;
1307         req->key = key;
1308         req->argv[0] = NULL;
1310         if (!format_argv(req->argv, argv, FORMAT_NONE))
1311                 return REQ_NONE;
1313         return REQ_NONE + ++run_requests;
1316 static struct run_request *
1317 get_run_request(enum request request)
1319         if (request <= REQ_NONE)
1320                 return NULL;
1321         return &run_request[request - REQ_NONE - 1];
1324 static void
1325 add_builtin_run_requests(void)
1327         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1328         const char *gc[] = { "git", "gc", NULL };
1329         struct {
1330                 enum keymap keymap;
1331                 int key;
1332                 int argc;
1333                 const char **argv;
1334         } reqs[] = {
1335                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1336                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1337         };
1338         int i;
1340         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1341                 enum request req;
1343                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1344                 if (req != REQ_NONE)
1345                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1346         }
1349 /*
1350  * User config file handling.
1351  */
1353 static int   config_lineno;
1354 static bool  config_errors;
1355 static const char *config_msg;
1357 static struct enum_map color_map[] = {
1358 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1359         COLOR_MAP(DEFAULT),
1360         COLOR_MAP(BLACK),
1361         COLOR_MAP(BLUE),
1362         COLOR_MAP(CYAN),
1363         COLOR_MAP(GREEN),
1364         COLOR_MAP(MAGENTA),
1365         COLOR_MAP(RED),
1366         COLOR_MAP(WHITE),
1367         COLOR_MAP(YELLOW),
1368 };
1370 static struct enum_map attr_map[] = {
1371 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1372         ATTR_MAP(NORMAL),
1373         ATTR_MAP(BLINK),
1374         ATTR_MAP(BOLD),
1375         ATTR_MAP(DIM),
1376         ATTR_MAP(REVERSE),
1377         ATTR_MAP(STANDOUT),
1378         ATTR_MAP(UNDERLINE),
1379 };
1381 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1383 static int
1384 parse_int(int *opt, const char *arg, int min, int max)
1386         int value = atoi(arg);
1388         if (min <= value && value <= max) {
1389                 *opt = value;
1390                 return OK;
1391         }
1393         config_msg = "Integer value out of bound";
1394         return ERR;
1397 static bool
1398 set_color(int *color, const char *name)
1400         if (map_enum(color, color_map, name))
1401                 return TRUE;
1402         if (!prefixcmp(name, "color"))
1403                 return parse_int(color, name + 5, 0, 255) == OK;
1404         return FALSE;
1407 /* Wants: object fgcolor bgcolor [attribute] */
1408 static int
1409 option_color_command(int argc, const char *argv[])
1411         struct line_info *info;
1413         if (argc != 3 && argc != 4) {
1414                 config_msg = "Wrong number of arguments given to color command";
1415                 return ERR;
1416         }
1418         info = get_line_info(argv[0]);
1419         if (!info) {
1420                 static struct enum_map obsolete[] = {
1421                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1422                         ENUM_MAP("main-date",   LINE_DATE),
1423                         ENUM_MAP("main-author", LINE_AUTHOR),
1424                 };
1425                 int index;
1427                 if (!map_enum(&index, obsolete, argv[0])) {
1428                         config_msg = "Unknown color name";
1429                         return ERR;
1430                 }
1431                 info = &line_info[index];
1432         }
1434         if (!set_color(&info->fg, argv[1]) ||
1435             !set_color(&info->bg, argv[2])) {
1436                 config_msg = "Unknown color";
1437                 return ERR;
1438         }
1440         if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1441                 config_msg = "Unknown attribute";
1442                 return ERR;
1443         }
1445         return OK;
1448 static int parse_bool(bool *opt, const char *arg)
1450         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1451                 ? TRUE : FALSE;
1452         return OK;
1455 static int
1456 parse_string(char *opt, const char *arg, size_t optsize)
1458         int arglen = strlen(arg);
1460         switch (arg[0]) {
1461         case '\"':
1462         case '\'':
1463                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1464                         config_msg = "Unmatched quotation";
1465                         return ERR;
1466                 }
1467                 arg += 1; arglen -= 2;
1468         default:
1469                 string_ncopy_do(opt, optsize, arg, strlen(arg));
1470                 return OK;
1471         }
1474 /* Wants: name = value */
1475 static int
1476 option_set_command(int argc, const char *argv[])
1478         if (argc != 3) {
1479                 config_msg = "Wrong number of arguments given to set command";
1480                 return ERR;
1481         }
1483         if (strcmp(argv[1], "=")) {
1484                 config_msg = "No value assigned";
1485                 return ERR;
1486         }
1488         if (!strcmp(argv[0], "show-author"))
1489                 return parse_bool(&opt_author, argv[2]);
1491         if (!strcmp(argv[0], "show-date"))
1492                 return parse_bool(&opt_date, argv[2]);
1494         if (!strcmp(argv[0], "show-rev-graph"))
1495                 return parse_bool(&opt_rev_graph, argv[2]);
1497         if (!strcmp(argv[0], "show-refs"))
1498                 return parse_bool(&opt_show_refs, argv[2]);
1500         if (!strcmp(argv[0], "show-line-numbers"))
1501                 return parse_bool(&opt_line_number, argv[2]);
1503         if (!strcmp(argv[0], "line-graphics"))
1504                 return parse_bool(&opt_line_graphics, argv[2]);
1506         if (!strcmp(argv[0], "line-number-interval"))
1507                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1509         if (!strcmp(argv[0], "author-width"))
1510                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1512         if (!strcmp(argv[0], "tab-size"))
1513                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1515         if (!strcmp(argv[0], "commit-encoding"))
1516                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1518         config_msg = "Unknown variable name";
1519         return ERR;
1522 /* Wants: mode request key */
1523 static int
1524 option_bind_command(int argc, const char *argv[])
1526         enum request request;
1527         int keymap;
1528         int key;
1530         if (argc < 3) {
1531                 config_msg = "Wrong number of arguments given to bind command";
1532                 return ERR;
1533         }
1535         if (set_keymap(&keymap, argv[0]) == ERR) {
1536                 config_msg = "Unknown key map";
1537                 return ERR;
1538         }
1540         key = get_key_value(argv[1]);
1541         if (key == ERR) {
1542                 config_msg = "Unknown key";
1543                 return ERR;
1544         }
1546         request = get_request(argv[2]);
1547         if (request == REQ_NONE) {
1548                 static struct enum_map obsolete[] = {
1549                         ENUM_MAP("cherry-pick",         REQ_NONE),
1550                         ENUM_MAP("screen-resize",       REQ_NONE),
1551                         ENUM_MAP("tree-parent",         REQ_PARENT),
1552                 };
1553                 int alias;
1555                 if (map_enum(&alias, obsolete, argv[2])) {
1556                         if (alias != REQ_NONE)
1557                                 add_keybinding(keymap, alias, key);
1558                         config_msg = "Obsolete request name";
1559                         return ERR;
1560                 }
1561         }
1562         if (request == REQ_NONE && *argv[2]++ == '!')
1563                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1564         if (request == REQ_NONE) {
1565                 config_msg = "Unknown request name";
1566                 return ERR;
1567         }
1569         add_keybinding(keymap, request, key);
1571         return OK;
1574 static int
1575 set_option(const char *opt, char *value)
1577         const char *argv[SIZEOF_ARG];
1578         int argc = 0;
1580         if (!argv_from_string(argv, &argc, value)) {
1581                 config_msg = "Too many option arguments";
1582                 return ERR;
1583         }
1585         if (!strcmp(opt, "color"))
1586                 return option_color_command(argc, argv);
1588         if (!strcmp(opt, "set"))
1589                 return option_set_command(argc, argv);
1591         if (!strcmp(opt, "bind"))
1592                 return option_bind_command(argc, argv);
1594         config_msg = "Unknown option command";
1595         return ERR;
1598 static int
1599 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1601         int status = OK;
1603         config_lineno++;
1604         config_msg = "Internal error";
1606         /* Check for comment markers, since read_properties() will
1607          * only ensure opt and value are split at first " \t". */
1608         optlen = strcspn(opt, "#");
1609         if (optlen == 0)
1610                 return OK;
1612         if (opt[optlen] != 0) {
1613                 config_msg = "No option value";
1614                 status = ERR;
1616         }  else {
1617                 /* Look for comment endings in the value. */
1618                 size_t len = strcspn(value, "#");
1620                 if (len < valuelen) {
1621                         valuelen = len;
1622                         value[valuelen] = 0;
1623                 }
1625                 status = set_option(opt, value);
1626         }
1628         if (status == ERR) {
1629                 warn("Error on line %d, near '%.*s': %s",
1630                      config_lineno, (int) optlen, opt, config_msg);
1631                 config_errors = TRUE;
1632         }
1634         /* Always keep going if errors are encountered. */
1635         return OK;
1638 static void
1639 load_option_file(const char *path)
1641         struct io io = {};
1643         /* It's OK that the file doesn't exist. */
1644         if (!io_open(&io, path))
1645                 return;
1647         config_lineno = 0;
1648         config_errors = FALSE;
1650         if (io_load(&io, " \t", read_option) == ERR ||
1651             config_errors == TRUE)
1652                 warn("Errors while loading %s.", path);
1655 static int
1656 load_options(void)
1658         const char *home = getenv("HOME");
1659         const char *tigrc_user = getenv("TIGRC_USER");
1660         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1661         char buf[SIZEOF_STR];
1663         add_builtin_run_requests();
1665         if (!tigrc_system)
1666                 tigrc_system = SYSCONFDIR "/tigrc";
1667         load_option_file(tigrc_system);
1669         if (!tigrc_user) {
1670                 if (!home || !string_format(buf, "%s/.tigrc", home))
1671                         return ERR;
1672                 tigrc_user = buf;
1673         }
1674         load_option_file(tigrc_user);
1676         return OK;
1680 /*
1681  * The viewer
1682  */
1684 struct view;
1685 struct view_ops;
1687 /* The display array of active views and the index of the current view. */
1688 static struct view *display[2];
1689 static unsigned int current_view;
1691 #define foreach_displayed_view(view, i) \
1692         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1694 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1696 /* Current head and commit ID */
1697 static char ref_blob[SIZEOF_REF]        = "";
1698 static char ref_commit[SIZEOF_REF]      = "HEAD";
1699 static char ref_head[SIZEOF_REF]        = "HEAD";
1701 struct view {
1702         const char *name;       /* View name */
1703         const char *cmd_env;    /* Command line set via environment */
1704         const char *id;         /* Points to either of ref_{head,commit,blob} */
1706         struct view_ops *ops;   /* View operations */
1708         enum keymap keymap;     /* What keymap does this view have */
1709         bool git_dir;           /* Whether the view requires a git directory. */
1711         char ref[SIZEOF_REF];   /* Hovered commit reference */
1712         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1714         int height, width;      /* The width and height of the main window */
1715         WINDOW *win;            /* The main window */
1716         WINDOW *title;          /* The title window living below the main window */
1718         /* Navigation */
1719         unsigned long offset;   /* Offset of the window top */
1720         unsigned long yoffset;  /* Offset from the window side. */
1721         unsigned long lineno;   /* Current line number */
1722         unsigned long p_offset; /* Previous offset of the window top */
1723         unsigned long p_yoffset;/* Previous offset from the window side */
1724         unsigned long p_lineno; /* Previous current line number */
1725         bool p_restore;         /* Should the previous position be restored. */
1727         /* Searching */
1728         char grep[SIZEOF_STR];  /* Search string */
1729         regex_t *regex;         /* Pre-compiled regexp */
1731         /* If non-NULL, points to the view that opened this view. If this view
1732          * is closed tig will switch back to the parent view. */
1733         struct view *parent;
1735         /* Buffering */
1736         size_t lines;           /* Total number of lines */
1737         struct line *line;      /* Line index */
1738         size_t line_alloc;      /* Total number of allocated lines */
1739         unsigned int digits;    /* Number of digits in the lines member. */
1741         /* Drawing */
1742         struct line *curline;   /* Line currently being drawn. */
1743         enum line_type curtype; /* Attribute currently used for drawing. */
1744         unsigned long col;      /* Column when drawing. */
1745         bool has_scrolled;      /* View was scrolled. */
1746         bool can_hscroll;       /* View can be scrolled horizontally. */
1748         /* Loading */
1749         struct io io;
1750         struct io *pipe;
1751         time_t start_time;
1752         time_t update_secs;
1753 };
1755 struct view_ops {
1756         /* What type of content being displayed. Used in the title bar. */
1757         const char *type;
1758         /* Default command arguments. */
1759         const char **argv;
1760         /* Open and reads in all view content. */
1761         bool (*open)(struct view *view);
1762         /* Read one line; updates view->line. */
1763         bool (*read)(struct view *view, char *data);
1764         /* Draw one line; @lineno must be < view->height. */
1765         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1766         /* Depending on view handle a special requests. */
1767         enum request (*request)(struct view *view, enum request request, struct line *line);
1768         /* Search for regexp in a line. */
1769         bool (*grep)(struct view *view, struct line *line);
1770         /* Select line */
1771         void (*select)(struct view *view, struct line *line);
1772 };
1774 static struct view_ops blame_ops;
1775 static struct view_ops blob_ops;
1776 static struct view_ops diff_ops;
1777 static struct view_ops help_ops;
1778 static struct view_ops log_ops;
1779 static struct view_ops main_ops;
1780 static struct view_ops pager_ops;
1781 static struct view_ops stage_ops;
1782 static struct view_ops status_ops;
1783 static struct view_ops tree_ops;
1785 #define VIEW_STR(name, env, ref, ops, map, git) \
1786         { name, #env, ref, ops, map, git }
1788 #define VIEW_(id, name, ops, git, ref) \
1789         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1792 static struct view views[] = {
1793         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1794         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1795         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1796         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1797         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1798         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1799         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1800         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1801         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1802         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1803 };
1805 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1806 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1808 #define foreach_view(view, i) \
1809         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1811 #define view_is_displayed(view) \
1812         (view == display[0] || view == display[1])
1815 enum line_graphic {
1816         LINE_GRAPHIC_VLINE
1817 };
1819 static int line_graphics[] = {
1820         /* LINE_GRAPHIC_VLINE: */ '|'
1821 };
1823 static inline void
1824 set_view_attr(struct view *view, enum line_type type)
1826         if (!view->curline->selected && view->curtype != type) {
1827                 wattrset(view->win, get_line_attr(type));
1828                 wchgat(view->win, -1, 0, type, NULL);
1829                 view->curtype = type;
1830         }
1833 static int
1834 draw_chars(struct view *view, enum line_type type, const char *string,
1835            int max_len, bool use_tilde)
1837         int len = 0;
1838         int col = 0;
1839         int trimmed = FALSE;
1840         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1842         if (max_len <= 0)
1843                 return 0;
1845         if (opt_utf8) {
1846                 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1847         } else {
1848                 col = len = strlen(string);
1849                 if (len > max_len) {
1850                         if (use_tilde) {
1851                                 max_len -= 1;
1852                         }
1853                         col = len = max_len;
1854                         trimmed = TRUE;
1855                 }
1856         }
1858         set_view_attr(view, type);
1859         if (len > 0)
1860                 waddnstr(view->win, string, len);
1861         if (trimmed && use_tilde) {
1862                 set_view_attr(view, LINE_DELIMITER);
1863                 waddch(view->win, '~');
1864                 col++;
1865         }
1867         if (view->col + col >= view->width + view->yoffset)
1868                 view->can_hscroll = TRUE;
1870         return col;
1873 static int
1874 draw_space(struct view *view, enum line_type type, int max, int spaces)
1876         static char space[] = "                    ";
1877         int col = 0;
1879         spaces = MIN(max, spaces);
1881         while (spaces > 0) {
1882                 int len = MIN(spaces, sizeof(space) - 1);
1884                 col += draw_chars(view, type, space, spaces, FALSE);
1885                 spaces -= len;
1886         }
1888         return col;
1891 static bool
1892 draw_lineno(struct view *view, unsigned int lineno)
1894         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1895         char number[10];
1896         int digits3 = view->digits < 3 ? 3 : view->digits;
1897         int max_number = MIN(digits3, STRING_SIZE(number));
1898         int max = view->width - view->col;
1899         int col;
1901         if (max < max_number)
1902                 max_number = max;
1904         lineno += view->offset + 1;
1905         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1906                 static char fmt[] = "%1ld";
1908                 if (view->digits <= 9)
1909                         fmt[1] = '0' + digits3;
1911                 if (!string_format(number, fmt, lineno))
1912                         number[0] = 0;
1913                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1914         } else {
1915                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1916         }
1918         if (col < max && skip <= col) {
1919                 set_view_attr(view, LINE_DEFAULT);
1920                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1921         }
1922         col++;
1924         view->col += col;
1925         if (col < max && skip <= col)
1926                 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1927         view->col++;
1929         return view->width + view->yoffset <= view->col;
1932 static bool
1933 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1935         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1936         return view->width - view->col <= 0;
1939 static bool
1940 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1942         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1943         int max = view->width - view->col;
1944         int i;
1946         if (max < size)
1947                 size = max;
1949         set_view_attr(view, type);
1950         /* Using waddch() instead of waddnstr() ensures that
1951          * they'll be rendered correctly for the cursor line. */
1952         for (i = skip; i < size; i++)
1953                 waddch(view->win, graphic[i]);
1955         view->col += size;
1956         if (size < max && skip <= size)
1957                 waddch(view->win, ' ');
1958         view->col++;
1960         return view->width - view->col <= 0;
1963 static bool
1964 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1966         int max = MIN(view->width - view->col, len);
1967         int col;
1969         if (text)
1970                 col = draw_chars(view, type, text, max - 1, trim);
1971         else
1972                 col = draw_space(view, type, max - 1, max - 1);
1974         view->col += col;
1975         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1976         return view->width + view->yoffset <= view->col;
1979 static bool
1980 draw_date(struct view *view, struct tm *time)
1982         char buf[DATE_COLS];
1983         char *date;
1984         int timelen = 0;
1986         if (time)
1987                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1988         date = timelen ? buf : NULL;
1990         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1993 static bool
1994 draw_author(struct view *view, const char *author)
1996         bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1998         if (!trim) {
1999                 static char initials[10];
2000                 size_t pos;
2002 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2004                 memset(initials, 0, sizeof(initials));
2005                 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2006                         while (is_initial_sep(*author))
2007                                 author++;
2008                         strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2009                         while (*author && !is_initial_sep(author[1]))
2010                                 author++;
2011                 }
2013                 author = initials;
2014         }
2016         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2019 static bool
2020 draw_mode(struct view *view, mode_t mode)
2022         static const char dir_mode[]    = "drwxr-xr-x";
2023         static const char link_mode[]   = "lrwxrwxrwx";
2024         static const char exe_mode[]    = "-rwxr-xr-x";
2025         static const char file_mode[]   = "-rw-r--r--";
2026         const char *str;
2028         if (S_ISDIR(mode))
2029                 str = dir_mode;
2030         else if (S_ISLNK(mode))
2031                 str = link_mode;
2032         else if (mode & S_IXUSR)
2033                 str = exe_mode;
2034         else
2035                 str = file_mode;
2037         return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2040 static bool
2041 draw_view_line(struct view *view, unsigned int lineno)
2043         struct line *line;
2044         bool selected = (view->offset + lineno == view->lineno);
2046         assert(view_is_displayed(view));
2048         if (view->offset + lineno >= view->lines)
2049                 return FALSE;
2051         line = &view->line[view->offset + lineno];
2053         wmove(view->win, lineno, 0);
2054         if (line->cleareol)
2055                 wclrtoeol(view->win);
2056         view->col = 0;
2057         view->curline = line;
2058         view->curtype = LINE_NONE;
2059         line->selected = FALSE;
2060         line->dirty = line->cleareol = 0;
2062         if (selected) {
2063                 set_view_attr(view, LINE_CURSOR);
2064                 line->selected = TRUE;
2065                 view->ops->select(view, line);
2066         }
2068         return view->ops->draw(view, line, lineno);
2071 static void
2072 redraw_view_dirty(struct view *view)
2074         bool dirty = FALSE;
2075         int lineno;
2077         for (lineno = 0; lineno < view->height; lineno++) {
2078                 if (view->offset + lineno >= view->lines)
2079                         break;
2080                 if (!view->line[view->offset + lineno].dirty)
2081                         continue;
2082                 dirty = TRUE;
2083                 if (!draw_view_line(view, lineno))
2084                         break;
2085         }
2087         if (!dirty)
2088                 return;
2089         wnoutrefresh(view->win);
2092 static void
2093 redraw_view_from(struct view *view, int lineno)
2095         assert(0 <= lineno && lineno < view->height);
2097         if (lineno == 0)
2098                 view->can_hscroll = FALSE;
2100         for (; lineno < view->height; lineno++) {
2101                 if (!draw_view_line(view, lineno))
2102                         break;
2103         }
2105         wnoutrefresh(view->win);
2108 static void
2109 redraw_view(struct view *view)
2111         werase(view->win);
2112         redraw_view_from(view, 0);
2116 static void
2117 update_view_title(struct view *view)
2119         char buf[SIZEOF_STR];
2120         char state[SIZEOF_STR];
2121         size_t bufpos = 0, statelen = 0;
2123         assert(view_is_displayed(view));
2125         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2126                 unsigned int view_lines = view->offset + view->height;
2127                 unsigned int lines = view->lines
2128                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2129                                    : 0;
2131                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2132                                    view->ops->type,
2133                                    view->lineno + 1,
2134                                    view->lines,
2135                                    lines);
2137         }
2139         if (view->pipe) {
2140                 time_t secs = time(NULL) - view->start_time;
2142                 /* Three git seconds are a long time ... */
2143                 if (secs > 2)
2144                         string_format_from(state, &statelen, " loading %lds", secs);
2145         }
2147         string_format_from(buf, &bufpos, "[%s]", view->name);
2148         if (*view->ref && bufpos < view->width) {
2149                 size_t refsize = strlen(view->ref);
2150                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2152                 if (minsize < view->width)
2153                         refsize = view->width - minsize + 7;
2154                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2155         }
2157         if (statelen && bufpos < view->width) {
2158                 string_format_from(buf, &bufpos, "%s", state);
2159         }
2161         if (view == display[current_view])
2162                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2163         else
2164                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2166         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2167         wclrtoeol(view->title);
2168         wnoutrefresh(view->title);
2171 static void
2172 resize_display(void)
2174         int offset, i;
2175         struct view *base = display[0];
2176         struct view *view = display[1] ? display[1] : display[0];
2178         /* Setup window dimensions */
2180         getmaxyx(stdscr, base->height, base->width);
2182         /* Make room for the status window. */
2183         base->height -= 1;
2185         if (view != base) {
2186                 /* Horizontal split. */
2187                 view->width   = base->width;
2188                 view->height  = SCALE_SPLIT_VIEW(base->height);
2189                 base->height -= view->height;
2191                 /* Make room for the title bar. */
2192                 view->height -= 1;
2193         }
2195         /* Make room for the title bar. */
2196         base->height -= 1;
2198         offset = 0;
2200         foreach_displayed_view (view, i) {
2201                 if (!view->win) {
2202                         view->win = newwin(view->height, 0, offset, 0);
2203                         if (!view->win)
2204                                 die("Failed to create %s view", view->name);
2206                         scrollok(view->win, FALSE);
2208                         view->title = newwin(1, 0, offset + view->height, 0);
2209                         if (!view->title)
2210                                 die("Failed to create title window");
2212                 } else {
2213                         wresize(view->win, view->height, view->width);
2214                         mvwin(view->win,   offset, 0);
2215                         mvwin(view->title, offset + view->height, 0);
2216                 }
2218                 offset += view->height + 1;
2219         }
2222 static void
2223 redraw_display(bool clear)
2225         struct view *view;
2226         int i;
2228         foreach_displayed_view (view, i) {
2229                 if (clear)
2230                         wclear(view->win);
2231                 redraw_view(view);
2232                 update_view_title(view);
2233         }
2236 static void
2237 toggle_view_option(bool *option, const char *help)
2239         *option = !*option;
2240         redraw_display(FALSE);
2241         report("%sabling %s", *option ? "En" : "Dis", help);
2244 /*
2245  * Navigation
2246  */
2248 static bool
2249 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2251         if (lineno >= view->lines)
2252                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2254         if (offset > lineno || offset + view->height <= lineno) {
2255                 unsigned long half = view->height / 2;
2257                 if (lineno > half)
2258                         offset = lineno - half;
2259                 else
2260                         offset = 0;
2261         }
2263         if (offset != view->offset || lineno != view->lineno) {
2264                 view->offset = offset;
2265                 view->lineno = lineno;
2266                 return TRUE;
2267         }
2269         return FALSE;
2272 /* Scrolling backend */
2273 static void
2274 do_scroll_view(struct view *view, int lines)
2276         bool redraw_current_line = FALSE;
2278         /* The rendering expects the new offset. */
2279         view->offset += lines;
2281         assert(0 <= view->offset && view->offset < view->lines);
2282         assert(lines);
2284         /* Move current line into the view. */
2285         if (view->lineno < view->offset) {
2286                 view->lineno = view->offset;
2287                 redraw_current_line = TRUE;
2288         } else if (view->lineno >= view->offset + view->height) {
2289                 view->lineno = view->offset + view->height - 1;
2290                 redraw_current_line = TRUE;
2291         }
2293         assert(view->offset <= view->lineno && view->lineno < view->lines);
2295         /* Redraw the whole screen if scrolling is pointless. */
2296         if (view->height < ABS(lines)) {
2297                 redraw_view(view);
2299         } else {
2300                 int line = lines > 0 ? view->height - lines : 0;
2301                 int end = line + ABS(lines);
2303                 scrollok(view->win, TRUE);
2304                 wscrl(view->win, lines);
2305                 scrollok(view->win, FALSE);
2307                 while (line < end && draw_view_line(view, line))
2308                         line++;
2310                 if (redraw_current_line)
2311                         draw_view_line(view, view->lineno - view->offset);
2312                 wnoutrefresh(view->win);
2313         }
2315         view->has_scrolled = TRUE;
2316         report("");
2319 /* Scroll frontend */
2320 static void
2321 scroll_view(struct view *view, enum request request)
2323         int lines = 1;
2325         assert(view_is_displayed(view));
2327         switch (request) {
2328         case REQ_SCROLL_LEFT:
2329                 if (view->yoffset == 0) {
2330                         report("Cannot scroll beyond the first column");
2331                         return;
2332                 }
2333                 if (view->yoffset <= SCROLL_INTERVAL)
2334                         view->yoffset = 0;
2335                 else
2336                         view->yoffset -= SCROLL_INTERVAL;
2337                 redraw_view_from(view, 0);
2338                 report("");
2339                 return;
2340         case REQ_SCROLL_RIGHT:
2341                 if (!view->can_hscroll) {
2342                         report("Cannot scroll beyond the last column");
2343                         return;
2344                 }
2345                 view->yoffset += SCROLL_INTERVAL;
2346                 redraw_view(view);
2347                 report("");
2348                 return;
2349         case REQ_SCROLL_PAGE_DOWN:
2350                 lines = view->height;
2351         case REQ_SCROLL_LINE_DOWN:
2352                 if (view->offset + lines > view->lines)
2353                         lines = view->lines - view->offset;
2355                 if (lines == 0 || view->offset + view->height >= view->lines) {
2356                         report("Cannot scroll beyond the last line");
2357                         return;
2358                 }
2359                 break;
2361         case REQ_SCROLL_PAGE_UP:
2362                 lines = view->height;
2363         case REQ_SCROLL_LINE_UP:
2364                 if (lines > view->offset)
2365                         lines = view->offset;
2367                 if (lines == 0) {
2368                         report("Cannot scroll beyond the first line");
2369                         return;
2370                 }
2372                 lines = -lines;
2373                 break;
2375         default:
2376                 die("request %d not handled in switch", request);
2377         }
2379         do_scroll_view(view, lines);
2382 /* Cursor moving */
2383 static void
2384 move_view(struct view *view, enum request request)
2386         int scroll_steps = 0;
2387         int steps;
2389         switch (request) {
2390         case REQ_MOVE_FIRST_LINE:
2391                 steps = -view->lineno;
2392                 break;
2394         case REQ_MOVE_LAST_LINE:
2395                 steps = view->lines - view->lineno - 1;
2396                 break;
2398         case REQ_MOVE_PAGE_UP:
2399                 steps = view->height > view->lineno
2400                       ? -view->lineno : -view->height;
2401                 break;
2403         case REQ_MOVE_PAGE_DOWN:
2404                 steps = view->lineno + view->height >= view->lines
2405                       ? view->lines - view->lineno - 1 : view->height;
2406                 break;
2408         case REQ_MOVE_UP:
2409                 steps = -1;
2410                 break;
2412         case REQ_MOVE_DOWN:
2413                 steps = 1;
2414                 break;
2416         default:
2417                 die("request %d not handled in switch", request);
2418         }
2420         if (steps <= 0 && view->lineno == 0) {
2421                 report("Cannot move beyond the first line");
2422                 return;
2424         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2425                 report("Cannot move beyond the last line");
2426                 return;
2427         }
2429         /* Move the current line */
2430         view->lineno += steps;
2431         assert(0 <= view->lineno && view->lineno < view->lines);
2433         /* Check whether the view needs to be scrolled */
2434         if (view->lineno < view->offset ||
2435             view->lineno >= view->offset + view->height) {
2436                 scroll_steps = steps;
2437                 if (steps < 0 && -steps > view->offset) {
2438                         scroll_steps = -view->offset;
2440                 } else if (steps > 0) {
2441                         if (view->lineno == view->lines - 1 &&
2442                             view->lines > view->height) {
2443                                 scroll_steps = view->lines - view->offset - 1;
2444                                 if (scroll_steps >= view->height)
2445                                         scroll_steps -= view->height - 1;
2446                         }
2447                 }
2448         }
2450         if (!view_is_displayed(view)) {
2451                 view->offset += scroll_steps;
2452                 assert(0 <= view->offset && view->offset < view->lines);
2453                 view->ops->select(view, &view->line[view->lineno]);
2454                 return;
2455         }
2457         /* Repaint the old "current" line if we be scrolling */
2458         if (ABS(steps) < view->height)
2459                 draw_view_line(view, view->lineno - steps - view->offset);
2461         if (scroll_steps) {
2462                 do_scroll_view(view, scroll_steps);
2463                 return;
2464         }
2466         /* Draw the current line */
2467         draw_view_line(view, view->lineno - view->offset);
2469         wnoutrefresh(view->win);
2470         report("");
2474 /*
2475  * Searching
2476  */
2478 static void search_view(struct view *view, enum request request);
2480 static void
2481 select_view_line(struct view *view, unsigned long lineno)
2483         unsigned long old_lineno = view->lineno;
2484         unsigned long old_offset = view->offset;
2486         if (goto_view_line(view, view->offset, lineno)) {
2487                 if (view_is_displayed(view)) {
2488                         if (old_offset != view->offset) {
2489                                 redraw_view(view);
2490                         } else {
2491                                 draw_view_line(view, old_lineno - view->offset);
2492                                 draw_view_line(view, view->lineno - view->offset);
2493                                 wnoutrefresh(view->win);
2494                         }
2495                 } else {
2496                         view->ops->select(view, &view->line[view->lineno]);
2497                 }
2498         }
2501 static void
2502 find_next(struct view *view, enum request request)
2504         unsigned long lineno = view->lineno;
2505         int direction;
2507         if (!*view->grep) {
2508                 if (!*opt_search)
2509                         report("No previous search");
2510                 else
2511                         search_view(view, request);
2512                 return;
2513         }
2515         switch (request) {
2516         case REQ_SEARCH:
2517         case REQ_FIND_NEXT:
2518                 direction = 1;
2519                 break;
2521         case REQ_SEARCH_BACK:
2522         case REQ_FIND_PREV:
2523                 direction = -1;
2524                 break;
2526         default:
2527                 return;
2528         }
2530         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2531                 lineno += direction;
2533         /* Note, lineno is unsigned long so will wrap around in which case it
2534          * will become bigger than view->lines. */
2535         for (; lineno < view->lines; lineno += direction) {
2536                 if (view->ops->grep(view, &view->line[lineno])) {
2537                         select_view_line(view, lineno);
2538                         report("Line %ld matches '%s'", lineno + 1, view->grep);
2539                         return;
2540                 }
2541         }
2543         report("No match found for '%s'", view->grep);
2546 static void
2547 search_view(struct view *view, enum request request)
2549         int regex_err;
2551         if (view->regex) {
2552                 regfree(view->regex);
2553                 *view->grep = 0;
2554         } else {
2555                 view->regex = calloc(1, sizeof(*view->regex));
2556                 if (!view->regex)
2557                         return;
2558         }
2560         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2561         if (regex_err != 0) {
2562                 char buf[SIZEOF_STR] = "unknown error";
2564                 regerror(regex_err, view->regex, buf, sizeof(buf));
2565                 report("Search failed: %s", buf);
2566                 return;
2567         }
2569         string_copy(view->grep, opt_search);
2571         find_next(view, request);
2574 /*
2575  * Incremental updating
2576  */
2578 static void
2579 reset_view(struct view *view)
2581         int i;
2583         for (i = 0; i < view->lines; i++)
2584                 free(view->line[i].data);
2585         free(view->line);
2587         view->p_offset = view->offset;
2588         view->p_yoffset = view->yoffset;
2589         view->p_lineno = view->lineno;
2591         view->line = NULL;
2592         view->offset = 0;
2593         view->yoffset = 0;
2594         view->lines  = 0;
2595         view->lineno = 0;
2596         view->line_alloc = 0;
2597         view->vid[0] = 0;
2598         view->update_secs = 0;
2601 static void
2602 free_argv(const char *argv[])
2604         int argc;
2606         for (argc = 0; argv[argc]; argc++)
2607                 free((void *) argv[argc]);
2610 static bool
2611 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2613         char buf[SIZEOF_STR];
2614         int argc;
2615         bool noreplace = flags == FORMAT_NONE;
2617         free_argv(dst_argv);
2619         for (argc = 0; src_argv[argc]; argc++) {
2620                 const char *arg = src_argv[argc];
2621                 size_t bufpos = 0;
2623                 while (arg) {
2624                         char *next = strstr(arg, "%(");
2625                         int len = next - arg;
2626                         const char *value;
2628                         if (!next || noreplace) {
2629                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2630                                         noreplace = TRUE;
2631                                 len = strlen(arg);
2632                                 value = "";
2634                         } else if (!prefixcmp(next, "%(directory)")) {
2635                                 value = opt_path;
2637                         } else if (!prefixcmp(next, "%(file)")) {
2638                                 value = opt_file;
2640                         } else if (!prefixcmp(next, "%(ref)")) {
2641                                 value = *opt_ref ? opt_ref : "HEAD";
2643                         } else if (!prefixcmp(next, "%(head)")) {
2644                                 value = ref_head;
2646                         } else if (!prefixcmp(next, "%(commit)")) {
2647                                 value = ref_commit;
2649                         } else if (!prefixcmp(next, "%(blob)")) {
2650                                 value = ref_blob;
2652                         } else {
2653                                 report("Unknown replacement: `%s`", next);
2654                                 return FALSE;
2655                         }
2657                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2658                                 return FALSE;
2660                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2661                 }
2663                 dst_argv[argc] = strdup(buf);
2664                 if (!dst_argv[argc])
2665                         break;
2666         }
2668         dst_argv[argc] = NULL;
2670         return src_argv[argc] == NULL;
2673 static bool
2674 restore_view_position(struct view *view)
2676         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2677                 return FALSE;
2679         /* Changing the view position cancels the restoring. */
2680         /* FIXME: Changing back to the first line is not detected. */
2681         if (view->offset != 0 || view->lineno != 0) {
2682                 view->p_restore = FALSE;
2683                 return FALSE;
2684         }
2686         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2687             view_is_displayed(view))
2688                 werase(view->win);
2690         view->yoffset = view->p_yoffset;
2691         view->p_restore = FALSE;
2693         return TRUE;
2696 static void
2697 end_update(struct view *view, bool force)
2699         if (!view->pipe)
2700                 return;
2701         while (!view->ops->read(view, NULL))
2702                 if (!force)
2703                         return;
2704         set_nonblocking_input(FALSE);
2705         if (force)
2706                 kill_io(view->pipe);
2707         done_io(view->pipe);
2708         view->pipe = NULL;
2711 static void
2712 setup_update(struct view *view, const char *vid)
2714         set_nonblocking_input(TRUE);
2715         reset_view(view);
2716         string_copy_rev(view->vid, vid);
2717         view->pipe = &view->io;
2718         view->start_time = time(NULL);
2721 static bool
2722 prepare_update(struct view *view, const char *argv[], const char *dir,
2723                enum format_flags flags)
2725         if (view->pipe)
2726                 end_update(view, TRUE);
2727         return init_io_rd(&view->io, argv, dir, flags);
2730 static bool
2731 prepare_update_file(struct view *view, const char *name)
2733         if (view->pipe)
2734                 end_update(view, TRUE);
2735         return io_open(&view->io, name);
2738 static bool
2739 begin_update(struct view *view, bool refresh)
2741         if (view->pipe)
2742                 end_update(view, TRUE);
2744         if (refresh) {
2745                 if (!start_io(&view->io))
2746                         return FALSE;
2748         } else {
2749                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2750                         opt_path[0] = 0;
2752                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2753                         return FALSE;
2755                 /* Put the current ref_* value to the view title ref
2756                  * member. This is needed by the blob view. Most other
2757                  * views sets it automatically after loading because the
2758                  * first line is a commit line. */
2759                 string_copy_rev(view->ref, view->id);
2760         }
2762         setup_update(view, view->id);
2764         return TRUE;
2767 #define ITEM_CHUNK_SIZE 256
2768 static void *
2769 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2771         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2772         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2774         if (mem == NULL || num_chunks != num_chunks_new) {
2775                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2776                 mem = realloc(mem, *size * item_size);
2777         }
2779         return mem;
2782 static struct line *
2783 realloc_lines(struct view *view, size_t line_size)
2785         size_t alloc = view->line_alloc;
2786         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2787                                          sizeof(*view->line));
2789         if (!tmp)
2790                 return NULL;
2792         view->line = tmp;
2793         view->line_alloc = alloc;
2794         return view->line;
2797 static bool
2798 update_view(struct view *view)
2800         char out_buffer[BUFSIZ * 2];
2801         char *line;
2802         /* Clear the view and redraw everything since the tree sorting
2803          * might have rearranged things. */
2804         bool redraw = view->lines == 0;
2805         bool can_read = TRUE;
2807         if (!view->pipe)
2808                 return TRUE;
2810         if (!io_can_read(view->pipe)) {
2811                 if (view->lines == 0) {
2812                         time_t secs = time(NULL) - view->start_time;
2814                         if (secs > 1 && secs > view->update_secs) {
2815                                 if (view->update_secs == 0)
2816                                         redraw_view(view);
2817                                 update_view_title(view);
2818                                 view->update_secs = secs;
2819                         }
2820                 }
2821                 return TRUE;
2822         }
2824         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2825                 if (opt_iconv != ICONV_NONE) {
2826                         ICONV_CONST char *inbuf = line;
2827                         size_t inlen = strlen(line) + 1;
2829                         char *outbuf = out_buffer;
2830                         size_t outlen = sizeof(out_buffer);
2832                         size_t ret;
2834                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2835                         if (ret != (size_t) -1)
2836                                 line = out_buffer;
2837                 }
2839                 if (!view->ops->read(view, line)) {
2840                         report("Allocation failure");
2841                         end_update(view, TRUE);
2842                         return FALSE;
2843                 }
2844         }
2846         {
2847                 unsigned long lines = view->lines;
2848                 int digits;
2850                 for (digits = 0; lines; digits++)
2851                         lines /= 10;
2853                 /* Keep the displayed view in sync with line number scaling. */
2854                 if (digits != view->digits) {
2855                         view->digits = digits;
2856                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2857                                 redraw = TRUE;
2858                 }
2859         }
2861         if (io_error(view->pipe)) {
2862                 report("Failed to read: %s", io_strerror(view->pipe));
2863                 end_update(view, TRUE);
2865         } else if (io_eof(view->pipe)) {
2866                 report("");
2867                 end_update(view, FALSE);
2868         }
2870         if (restore_view_position(view))
2871                 redraw = TRUE;
2873         if (!view_is_displayed(view))
2874                 return TRUE;
2876         if (redraw)
2877                 redraw_view_from(view, 0);
2878         else
2879                 redraw_view_dirty(view);
2881         /* Update the title _after_ the redraw so that if the redraw picks up a
2882          * commit reference in view->ref it'll be available here. */
2883         update_view_title(view);
2884         return TRUE;
2887 static struct line *
2888 add_line_data(struct view *view, void *data, enum line_type type)
2890         struct line *line;
2892         if (!realloc_lines(view, view->lines + 1))
2893                 return NULL;
2895         line = &view->line[view->lines++];
2896         memset(line, 0, sizeof(*line));
2897         line->type = type;
2898         line->data = data;
2899         line->dirty = 1;
2901         return line;
2904 static struct line *
2905 add_line_text(struct view *view, const char *text, enum line_type type)
2907         char *data = text ? strdup(text) : NULL;
2909         return data ? add_line_data(view, data, type) : NULL;
2912 static struct line *
2913 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2915         char buf[SIZEOF_STR];
2916         va_list args;
2918         va_start(args, fmt);
2919         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2920                 buf[0] = 0;
2921         va_end(args);
2923         return buf[0] ? add_line_text(view, buf, type) : NULL;
2926 /*
2927  * View opening
2928  */
2930 enum open_flags {
2931         OPEN_DEFAULT = 0,       /* Use default view switching. */
2932         OPEN_SPLIT = 1,         /* Split current view. */
2933         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2934         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2935         OPEN_PREPARED = 32,     /* Open already prepared command. */
2936 };
2938 static void
2939 open_view(struct view *prev, enum request request, enum open_flags flags)
2941         bool split = !!(flags & OPEN_SPLIT);
2942         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2943         bool nomaximize = !!(flags & OPEN_REFRESH);
2944         struct view *view = VIEW(request);
2945         int nviews = displayed_views();
2946         struct view *base_view = display[0];
2948         if (view == prev && nviews == 1 && !reload) {
2949                 report("Already in %s view", view->name);
2950                 return;
2951         }
2953         if (view->git_dir && !opt_git_dir[0]) {
2954                 report("The %s view is disabled in pager view", view->name);
2955                 return;
2956         }
2958         if (split) {
2959                 display[1] = view;
2960                 current_view = 1;
2961         } else if (!nomaximize) {
2962                 /* Maximize the current view. */
2963                 memset(display, 0, sizeof(display));
2964                 current_view = 0;
2965                 display[current_view] = view;
2966         }
2968         /* Resize the view when switching between split- and full-screen,
2969          * or when switching between two different full-screen views. */
2970         if (nviews != displayed_views() ||
2971             (nviews == 1 && base_view != display[0]))
2972                 resize_display();
2974         if (view->ops->open) {
2975                 if (view->pipe)
2976                         end_update(view, TRUE);
2977                 if (!view->ops->open(view)) {
2978                         report("Failed to load %s view", view->name);
2979                         return;
2980                 }
2981                 restore_view_position(view);
2983         } else if ((reload || strcmp(view->vid, view->id)) &&
2984                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2985                 report("Failed to load %s view", view->name);
2986                 return;
2987         }
2989         if (split && prev->lineno - prev->offset >= prev->height) {
2990                 /* Take the title line into account. */
2991                 int lines = prev->lineno - prev->offset - prev->height + 1;
2993                 /* Scroll the view that was split if the current line is
2994                  * outside the new limited view. */
2995                 do_scroll_view(prev, lines);
2996         }
2998         if (prev && view != prev) {
2999                 if (split) {
3000                         /* "Blur" the previous view. */
3001                         update_view_title(prev);
3002                 }
3004                 view->parent = prev;
3005         }
3007         if (view->pipe && view->lines == 0) {
3008                 /* Clear the old view and let the incremental updating refill
3009                  * the screen. */
3010                 werase(view->win);
3011                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3012                 report("");
3013         } else if (view_is_displayed(view)) {
3014                 redraw_view(view);
3015                 report("");
3016         }
3019 static void
3020 open_external_viewer(const char *argv[], const char *dir)
3022         def_prog_mode();           /* save current tty modes */
3023         endwin();                  /* restore original tty modes */
3024         run_io_fg(argv, dir);
3025         fprintf(stderr, "Press Enter to continue");
3026         getc(opt_tty);
3027         reset_prog_mode();
3028         redraw_display(TRUE);
3031 static void
3032 open_mergetool(const char *file)
3034         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3036         open_external_viewer(mergetool_argv, opt_cdup);
3039 static void
3040 open_editor(bool from_root, const char *file)
3042         const char *editor_argv[] = { "vi", file, NULL };
3043         const char *editor;
3045         editor = getenv("GIT_EDITOR");
3046         if (!editor && *opt_editor)
3047                 editor = opt_editor;
3048         if (!editor)
3049                 editor = getenv("VISUAL");
3050         if (!editor)
3051                 editor = getenv("EDITOR");
3052         if (!editor)
3053                 editor = "vi";
3055         editor_argv[0] = editor;
3056         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3059 static void
3060 open_run_request(enum request request)
3062         struct run_request *req = get_run_request(request);
3063         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3065         if (!req) {
3066                 report("Unknown run request");
3067                 return;
3068         }
3070         if (format_argv(argv, req->argv, FORMAT_ALL))
3071                 open_external_viewer(argv, NULL);
3072         free_argv(argv);
3075 /*
3076  * User request switch noodle
3077  */
3079 static int
3080 view_driver(struct view *view, enum request request)
3082         int i;
3084         if (request == REQ_NONE) {
3085                 doupdate();
3086                 return TRUE;
3087         }
3089         if (request > REQ_NONE) {
3090                 open_run_request(request);
3091                 /* FIXME: When all views can refresh always do this. */
3092                 if (view == VIEW(REQ_VIEW_STATUS) ||
3093                     view == VIEW(REQ_VIEW_MAIN) ||
3094                     view == VIEW(REQ_VIEW_LOG) ||
3095                     view == VIEW(REQ_VIEW_STAGE))
3096                         request = REQ_REFRESH;
3097                 else
3098                         return TRUE;
3099         }
3101         if (view && view->lines) {
3102                 request = view->ops->request(view, request, &view->line[view->lineno]);
3103                 if (request == REQ_NONE)
3104                         return TRUE;
3105         }
3107         switch (request) {
3108         case REQ_MOVE_UP:
3109         case REQ_MOVE_DOWN:
3110         case REQ_MOVE_PAGE_UP:
3111         case REQ_MOVE_PAGE_DOWN:
3112         case REQ_MOVE_FIRST_LINE:
3113         case REQ_MOVE_LAST_LINE:
3114                 move_view(view, request);
3115                 break;
3117         case REQ_SCROLL_LEFT:
3118         case REQ_SCROLL_RIGHT:
3119         case REQ_SCROLL_LINE_DOWN:
3120         case REQ_SCROLL_LINE_UP:
3121         case REQ_SCROLL_PAGE_DOWN:
3122         case REQ_SCROLL_PAGE_UP:
3123                 scroll_view(view, request);
3124                 break;
3126         case REQ_VIEW_BLAME:
3127                 if (!opt_file[0]) {
3128                         report("No file chosen, press %s to open tree view",
3129                                get_key(REQ_VIEW_TREE));
3130                         break;
3131                 }
3132                 open_view(view, request, OPEN_DEFAULT);
3133                 break;
3135         case REQ_VIEW_BLOB:
3136                 if (!ref_blob[0]) {
3137                         report("No file chosen, press %s to open tree view",
3138                                get_key(REQ_VIEW_TREE));
3139                         break;
3140                 }
3141                 open_view(view, request, OPEN_DEFAULT);
3142                 break;
3144         case REQ_VIEW_PAGER:
3145                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3146                         report("No pager content, press %s to run command from prompt",
3147                                get_key(REQ_PROMPT));
3148                         break;
3149                 }
3150                 open_view(view, request, OPEN_DEFAULT);
3151                 break;
3153         case REQ_VIEW_STAGE:
3154                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3155                         report("No stage content, press %s to open the status view and choose file",
3156                                get_key(REQ_VIEW_STATUS));
3157                         break;
3158                 }
3159                 open_view(view, request, OPEN_DEFAULT);
3160                 break;
3162         case REQ_VIEW_STATUS:
3163                 if (opt_is_inside_work_tree == FALSE) {
3164                         report("The status view requires a working tree");
3165                         break;
3166                 }
3167                 open_view(view, request, OPEN_DEFAULT);
3168                 break;
3170         case REQ_VIEW_MAIN:
3171         case REQ_VIEW_DIFF:
3172         case REQ_VIEW_LOG:
3173         case REQ_VIEW_TREE:
3174         case REQ_VIEW_HELP:
3175                 open_view(view, request, OPEN_DEFAULT);
3176                 break;
3178         case REQ_NEXT:
3179         case REQ_PREVIOUS:
3180                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3182                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3183                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3184                    (view == VIEW(REQ_VIEW_DIFF) &&
3185                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3186                    (view == VIEW(REQ_VIEW_STAGE) &&
3187                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3188                    (view == VIEW(REQ_VIEW_BLOB) &&
3189                      view->parent == VIEW(REQ_VIEW_TREE))) {
3190                         int line;
3192                         view = view->parent;
3193                         line = view->lineno;
3194                         move_view(view, request);
3195                         if (view_is_displayed(view))
3196                                 update_view_title(view);
3197                         if (line != view->lineno)
3198                                 view->ops->request(view, REQ_ENTER,
3199                                                    &view->line[view->lineno]);
3201                 } else {
3202                         move_view(view, request);
3203                 }
3204                 break;
3206         case REQ_VIEW_NEXT:
3207         {
3208                 int nviews = displayed_views();
3209                 int next_view = (current_view + 1) % nviews;
3211                 if (next_view == current_view) {
3212                         report("Only one view is displayed");
3213                         break;
3214                 }
3216                 current_view = next_view;
3217                 /* Blur out the title of the previous view. */
3218                 update_view_title(view);
3219                 report("");
3220                 break;
3221         }
3222         case REQ_REFRESH:
3223                 report("Refreshing is not yet supported for the %s view", view->name);
3224                 break;
3226         case REQ_MAXIMIZE:
3227                 if (displayed_views() == 2)
3228                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3229                 break;
3231         case REQ_TOGGLE_LINENO:
3232                 toggle_view_option(&opt_line_number, "line numbers");
3233                 break;
3235         case REQ_TOGGLE_DATE:
3236                 toggle_view_option(&opt_date, "date display");
3237                 break;
3239         case REQ_TOGGLE_AUTHOR:
3240                 toggle_view_option(&opt_author, "author display");
3241                 break;
3243         case REQ_TOGGLE_REV_GRAPH:
3244                 toggle_view_option(&opt_rev_graph, "revision graph display");
3245                 break;
3247         case REQ_TOGGLE_REFS:
3248                 toggle_view_option(&opt_show_refs, "reference display");
3249                 break;
3251         case REQ_SEARCH:
3252         case REQ_SEARCH_BACK:
3253                 search_view(view, request);
3254                 break;
3256         case REQ_FIND_NEXT:
3257         case REQ_FIND_PREV:
3258                 find_next(view, request);
3259                 break;
3261         case REQ_STOP_LOADING:
3262                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3263                         view = &views[i];
3264                         if (view->pipe)
3265                                 report("Stopped loading the %s view", view->name),
3266                         end_update(view, TRUE);
3267                 }
3268                 break;
3270         case REQ_SHOW_VERSION:
3271                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3272                 return TRUE;
3274         case REQ_SCREEN_REDRAW:
3275                 redraw_display(TRUE);
3276                 break;
3278         case REQ_EDIT:
3279                 report("Nothing to edit");
3280                 break;
3282         case REQ_ENTER:
3283                 report("Nothing to enter");
3284                 break;
3286         case REQ_VIEW_CLOSE:
3287                 /* XXX: Mark closed views by letting view->parent point to the
3288                  * view itself. Parents to closed view should never be
3289                  * followed. */
3290                 if (view->parent &&
3291                     view->parent->parent != view->parent) {
3292                         memset(display, 0, sizeof(display));
3293                         current_view = 0;
3294                         display[current_view] = view->parent;
3295                         view->parent = view;
3296                         resize_display();
3297                         redraw_display(FALSE);
3298                         report("");
3299                         break;
3300                 }
3301                 /* Fall-through */
3302         case REQ_QUIT:
3303                 return FALSE;
3305         default:
3306                 report("Unknown key, press 'h' for help");
3307                 return TRUE;
3308         }
3310         return TRUE;
3314 /*
3315  * View backend utilities
3316  */
3318 static void
3319 parse_timezone(time_t *time, const char *zone)
3321         long tz;
3323         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3324         tz += ('0' - zone[2]) * 60 * 60;
3325         tz += ('0' - zone[3]) * 60;
3326         tz += ('0' - zone[4]);
3328         if (zone[0] == '-')
3329                 tz = -tz;
3331         *time -= tz;
3334 /* Parse author lines where the name may be empty:
3335  *      author  <email@address.tld> 1138474660 +0100
3336  */
3337 static void
3338 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3340         char *nameend = strchr(ident, '<');
3341         char *emailend = strchr(ident, '>');
3343         if (nameend && emailend)
3344                 *nameend = *emailend = 0;
3345         ident = chomp_string(ident);
3346         if (!*ident) {
3347                 if (nameend)
3348                         ident = chomp_string(nameend + 1);
3349                 if (!*ident)
3350                         ident = "Unknown";
3351         }
3353         string_ncopy_do(author, authorsize, ident, strlen(ident));
3355         /* Parse epoch and timezone */
3356         if (emailend && emailend[1] == ' ') {
3357                 char *secs = emailend + 2;
3358                 char *zone = strchr(secs, ' ');
3359                 time_t time = (time_t) atol(secs);
3361                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3362                         parse_timezone(&time, zone + 1);
3364                 gmtime_r(&time, tm);
3365         }
3368 static enum input_status
3369 select_commit_parent_handler(void *data, char *buf, int c)
3371         size_t parents = *(size_t *) data;
3372         int parent = 0;
3374         if (!isdigit(c))
3375                 return INPUT_SKIP;
3377         if (*buf)
3378                 parent = atoi(buf) * 10;
3379         parent += c - '0';
3381         if (parent > parents)
3382                 return INPUT_SKIP;
3383         return INPUT_OK;
3386 static bool
3387 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3389         char buf[SIZEOF_STR * 4];
3390         const char *revlist_argv[] = {
3391                 "git", "rev-list", "-1", "--parents", id, NULL
3392         };
3393         int parents;
3395         if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3396             !*chomp_string(buf) ||
3397             (parents = (strlen(buf) / 40) - 1) < 0) {
3398                 report("Failed to get parent information");
3399                 return FALSE;
3401         } else if (parents == 0) {
3402                 report("The selected commit has no parents");
3403                 return FALSE;
3404         }
3406         if (parents > 1) {
3407                 char prompt[SIZEOF_STR];
3408                 char *result;
3410                 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3411                         return FALSE;
3412                 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3413                 if (!result)
3414                         return FALSE;
3415                 parents = atoi(result);
3416         }
3418         string_copy_rev(rev, &buf[41 * parents]);
3419         return TRUE;
3422 /*
3423  * Pager backend
3424  */
3426 static bool
3427 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3429         char text[SIZEOF_STR];
3431         if (opt_line_number && draw_lineno(view, lineno))
3432                 return TRUE;
3434         string_expand(text, sizeof(text), line->data, opt_tab_size);
3435         draw_text(view, line->type, text, TRUE);
3436         return TRUE;
3439 static bool
3440 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3442         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3443         char refbuf[SIZEOF_STR];
3444         char *ref = NULL;
3446         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3447                 ref = chomp_string(refbuf);
3449         if (!ref || !*ref)
3450                 return TRUE;
3452         /* This is the only fatal call, since it can "corrupt" the buffer. */
3453         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3454                 return FALSE;
3456         return TRUE;
3459 static void
3460 add_pager_refs(struct view *view, struct line *line)
3462         char buf[SIZEOF_STR];
3463         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3464         struct ref **refs;
3465         size_t bufpos = 0, refpos = 0;
3466         const char *sep = "Refs: ";
3467         bool is_tag = FALSE;
3469         assert(line->type == LINE_COMMIT);
3471         refs = get_refs(commit_id);
3472         if (!refs) {
3473                 if (view == VIEW(REQ_VIEW_DIFF))
3474                         goto try_add_describe_ref;
3475                 return;
3476         }
3478         do {
3479                 struct ref *ref = refs[refpos];
3480                 const char *fmt = ref->tag    ? "%s[%s]" :
3481                                   ref->remote ? "%s<%s>" : "%s%s";
3483                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3484                         return;
3485                 sep = ", ";
3486                 if (ref->tag)
3487                         is_tag = TRUE;
3488         } while (refs[refpos++]->next);
3490         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3491 try_add_describe_ref:
3492                 /* Add <tag>-g<commit_id> "fake" reference. */
3493                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3494                         return;
3495         }
3497         if (bufpos == 0)
3498                 return;
3500         add_line_text(view, buf, LINE_PP_REFS);
3503 static bool
3504 pager_read(struct view *view, char *data)
3506         struct line *line;
3508         if (!data)
3509                 return TRUE;
3511         line = add_line_text(view, data, get_line_type(data));
3512         if (!line)
3513                 return FALSE;
3515         if (line->type == LINE_COMMIT &&
3516             (view == VIEW(REQ_VIEW_DIFF) ||
3517              view == VIEW(REQ_VIEW_LOG)))
3518                 add_pager_refs(view, line);
3520         return TRUE;
3523 static enum request
3524 pager_request(struct view *view, enum request request, struct line *line)
3526         int split = 0;
3528         if (request != REQ_ENTER)
3529                 return request;
3531         if (line->type == LINE_COMMIT &&
3532            (view == VIEW(REQ_VIEW_LOG) ||
3533             view == VIEW(REQ_VIEW_PAGER))) {
3534                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3535                 split = 1;
3536         }
3538         /* Always scroll the view even if it was split. That way
3539          * you can use Enter to scroll through the log view and
3540          * split open each commit diff. */
3541         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3543         /* FIXME: A minor workaround. Scrolling the view will call report("")
3544          * but if we are scrolling a non-current view this won't properly
3545          * update the view title. */
3546         if (split)
3547                 update_view_title(view);
3549         return REQ_NONE;
3552 static bool
3553 pager_grep(struct view *view, struct line *line)
3555         regmatch_t pmatch;
3556         char *text = line->data;
3558         if (!*text)
3559                 return FALSE;
3561         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3562                 return FALSE;
3564         return TRUE;
3567 static void
3568 pager_select(struct view *view, struct line *line)
3570         if (line->type == LINE_COMMIT) {
3571                 char *text = (char *)line->data + STRING_SIZE("commit ");
3573                 if (view != VIEW(REQ_VIEW_PAGER))
3574                         string_copy_rev(view->ref, text);
3575                 string_copy_rev(ref_commit, text);
3576         }
3579 static struct view_ops pager_ops = {
3580         "line",
3581         NULL,
3582         NULL,
3583         pager_read,
3584         pager_draw,
3585         pager_request,
3586         pager_grep,
3587         pager_select,
3588 };
3590 static const char *log_argv[SIZEOF_ARG] = {
3591         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3592 };
3594 static enum request
3595 log_request(struct view *view, enum request request, struct line *line)
3597         switch (request) {
3598         case REQ_REFRESH:
3599                 load_refs();
3600                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3601                 return REQ_NONE;
3602         default:
3603                 return pager_request(view, request, line);
3604         }
3607 static struct view_ops log_ops = {
3608         "line",
3609         log_argv,
3610         NULL,
3611         pager_read,
3612         pager_draw,
3613         log_request,
3614         pager_grep,
3615         pager_select,
3616 };
3618 static const char *diff_argv[SIZEOF_ARG] = {
3619         "git", "show", "--pretty=fuller", "--no-color", "--root",
3620                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3621 };
3623 static struct view_ops diff_ops = {
3624         "line",
3625         diff_argv,
3626         NULL,
3627         pager_read,
3628         pager_draw,
3629         pager_request,
3630         pager_grep,
3631         pager_select,
3632 };
3634 /*
3635  * Help backend
3636  */
3638 static bool
3639 help_open(struct view *view)
3641         char buf[SIZEOF_STR];
3642         size_t bufpos;
3643         int i;
3645         if (view->lines > 0)
3646                 return TRUE;
3648         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3650         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3651                 const char *key;
3653                 if (req_info[i].request == REQ_NONE)
3654                         continue;
3656                 if (!req_info[i].request) {
3657                         add_line_text(view, "", LINE_DEFAULT);
3658                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3659                         continue;
3660                 }
3662                 key = get_key(req_info[i].request);
3663                 if (!*key)
3664                         key = "(no key defined)";
3666                 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3667                         buf[bufpos] = tolower(req_info[i].name[bufpos]);
3668                         if (buf[bufpos] == '_')
3669                                 buf[bufpos] = '-';
3670                 }
3672                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s",
3673                                 key, buf, req_info[i].help);
3674         }
3676         if (run_requests) {
3677                 add_line_text(view, "", LINE_DEFAULT);
3678                 add_line_text(view, "External commands:", LINE_DEFAULT);
3679         }
3681         for (i = 0; i < run_requests; i++) {
3682                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3683                 const char *key;
3684                 int argc;
3686                 if (!req)
3687                         continue;
3689                 key = get_key_name(req->key);
3690                 if (!*key)
3691                         key = "(no key defined)";
3693                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3694                         if (!string_format_from(buf, &bufpos, "%s%s",
3695                                                 argc ? " " : "", req->argv[argc]))
3696                                 return REQ_NONE;
3698                 add_line_format(view, LINE_DEFAULT, "    %-10s %-14s `%s`",
3699                                 keymap_table[req->keymap].name, key, buf);
3700         }
3702         return TRUE;
3705 static struct view_ops help_ops = {
3706         "line",
3707         NULL,
3708         help_open,
3709         NULL,
3710         pager_draw,
3711         pager_request,
3712         pager_grep,
3713         pager_select,
3714 };
3717 /*
3718  * Tree backend
3719  */
3721 struct tree_stack_entry {
3722         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3723         unsigned long lineno;           /* Line number to restore */
3724         char *name;                     /* Position of name in opt_path */
3725 };
3727 /* The top of the path stack. */
3728 static struct tree_stack_entry *tree_stack = NULL;
3729 unsigned long tree_lineno = 0;
3731 static void
3732 pop_tree_stack_entry(void)
3734         struct tree_stack_entry *entry = tree_stack;
3736         tree_lineno = entry->lineno;
3737         entry->name[0] = 0;
3738         tree_stack = entry->prev;
3739         free(entry);
3742 static void
3743 push_tree_stack_entry(const char *name, unsigned long lineno)
3745         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3746         size_t pathlen = strlen(opt_path);
3748         if (!entry)
3749                 return;
3751         entry->prev = tree_stack;
3752         entry->name = opt_path + pathlen;
3753         tree_stack = entry;
3755         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3756                 pop_tree_stack_entry();
3757                 return;
3758         }
3760         /* Move the current line to the first tree entry. */
3761         tree_lineno = 1;
3762         entry->lineno = lineno;
3765 /* Parse output from git-ls-tree(1):
3766  *
3767  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3768  */
3770 #define SIZEOF_TREE_ATTR \
3771         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3773 #define SIZEOF_TREE_MODE \
3774         STRING_SIZE("100644 ")
3776 #define TREE_ID_OFFSET \
3777         STRING_SIZE("100644 blob ")
3779 struct tree_entry {
3780         char id[SIZEOF_REV];
3781         mode_t mode;
3782         struct tm time;                 /* Date from the author ident. */
3783         char author[75];                /* Author of the commit. */
3784         char name[1];
3785 };
3787 static const char *
3788 tree_path(struct line *line)
3790         return ((struct tree_entry *) line->data)->name;
3794 static int
3795 tree_compare_entry(struct line *line1, struct line *line2)
3797         if (line1->type != line2->type)
3798                 return line1->type == LINE_TREE_DIR ? -1 : 1;
3799         return strcmp(tree_path(line1), tree_path(line2));
3802 static struct line *
3803 tree_entry(struct view *view, enum line_type type, const char *path,
3804            const char *mode, const char *id)
3806         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3807         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3809         if (!entry || !line) {
3810                 free(entry);
3811                 return NULL;
3812         }
3814         strncpy(entry->name, path, strlen(path));
3815         if (mode)
3816                 entry->mode = strtoul(mode, NULL, 8);
3817         if (id)
3818                 string_copy_rev(entry->id, id);
3820         return line;
3823 static bool
3824 tree_read_date(struct view *view, char *text, bool *read_date)
3826         static char author_name[SIZEOF_STR];
3827         static struct tm author_time;
3829         if (!text && *read_date) {
3830                 *read_date = FALSE;
3831                 return TRUE;
3833         } else if (!text) {
3834                 char *path = *opt_path ? opt_path : ".";
3835                 /* Find next entry to process */
3836                 const char *log_file[] = {
3837                         "git", "log", "--no-color", "--pretty=raw",
3838                                 "--cc", "--raw", view->id, "--", path, NULL
3839                 };
3840                 struct io io = {};
3842                 if (!view->lines) {
3843                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3844                         report("Tree is empty");
3845                         return TRUE;
3846                 }
3848                 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3849                         report("Failed to load tree data");
3850                         return TRUE;
3851                 }
3853                 done_io(view->pipe);
3854                 view->io = io;
3855                 *read_date = TRUE;
3856                 return FALSE;
3858         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3859                 parse_author_line(text + STRING_SIZE("author "),
3860                                   author_name, sizeof(author_name), &author_time);
3862         } else if (*text == ':') {
3863                 char *pos;
3864                 size_t annotated = 1;
3865                 size_t i;
3867                 pos = strchr(text, '\t');
3868                 if (!pos)
3869                         return TRUE;
3870                 text = pos + 1;
3871                 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3872                         text += strlen(opt_prefix);
3873                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3874                         text += strlen(opt_path);
3875                 pos = strchr(text, '/');
3876                 if (pos)
3877                         *pos = 0;
3879                 for (i = 1; i < view->lines; i++) {
3880                         struct line *line = &view->line[i];
3881                         struct tree_entry *entry = line->data;
3883                         annotated += !!*entry->author;
3884                         if (*entry->author || strcmp(entry->name, text))
3885                                 continue;
3887                         string_copy(entry->author, author_name);
3888                         memcpy(&entry->time, &author_time, sizeof(entry->time));
3889                         line->dirty = 1;
3890                         break;
3891                 }
3893                 if (annotated == view->lines)
3894                         kill_io(view->pipe);
3895         }
3896         return TRUE;
3899 static bool
3900 tree_read(struct view *view, char *text)
3902         static bool read_date = FALSE;
3903         struct tree_entry *data;
3904         struct line *entry, *line;
3905         enum line_type type;
3906         size_t textlen = text ? strlen(text) : 0;
3907         char *path = text + SIZEOF_TREE_ATTR;
3909         if (read_date || !text)
3910                 return tree_read_date(view, text, &read_date);
3912         if (textlen <= SIZEOF_TREE_ATTR)
3913                 return FALSE;
3914         if (view->lines == 0 &&
3915             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3916                 return FALSE;
3918         /* Strip the path part ... */
3919         if (*opt_path) {
3920                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3921                 size_t striplen = strlen(opt_path);
3923                 if (pathlen > striplen)
3924                         memmove(path, path + striplen,
3925                                 pathlen - striplen + 1);
3927                 /* Insert "link" to parent directory. */
3928                 if (view->lines == 1 &&
3929                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3930                         return FALSE;
3931         }
3933         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3934         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3935         if (!entry)
3936                 return FALSE;
3937         data = entry->data;
3939         /* Skip "Directory ..." and ".." line. */
3940         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3941                 if (tree_compare_entry(line, entry) <= 0)
3942                         continue;
3944                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3946                 line->data = data;
3947                 line->type = type;
3948                 for (; line <= entry; line++)
3949                         line->dirty = line->cleareol = 1;
3950                 return TRUE;
3951         }
3953         if (tree_lineno > view->lineno) {
3954                 view->lineno = tree_lineno;
3955                 tree_lineno = 0;
3956         }
3958         return TRUE;
3961 static bool
3962 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3964         struct tree_entry *entry = line->data;
3966         if (line->type == LINE_TREE_HEAD) {
3967                 if (draw_text(view, line->type, "Directory path /", TRUE))
3968                         return TRUE;
3969         } else {
3970                 if (draw_mode(view, entry->mode))
3971                         return TRUE;
3973                 if (opt_author && draw_author(view, entry->author))
3974                         return TRUE;
3976                 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3977                         return TRUE;
3978         }
3979         if (draw_text(view, line->type, entry->name, TRUE))
3980                 return TRUE;
3981         return TRUE;
3984 static void
3985 open_blob_editor()
3987         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3988         int fd = mkstemp(file);
3990         if (fd == -1)
3991                 report("Failed to create temporary file");
3992         else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3993                 report("Failed to save blob data to file");
3994         else
3995                 open_editor(FALSE, file);
3996         if (fd != -1)
3997                 unlink(file);
4000 static enum request
4001 tree_request(struct view *view, enum request request, struct line *line)
4003         enum open_flags flags;
4005         switch (request) {
4006         case REQ_VIEW_BLAME:
4007                 if (line->type != LINE_TREE_FILE) {
4008                         report("Blame only supported for files");
4009                         return REQ_NONE;
4010                 }
4012                 string_copy(opt_ref, view->vid);
4013                 return request;
4015         case REQ_EDIT:
4016                 if (line->type != LINE_TREE_FILE) {
4017                         report("Edit only supported for files");
4018                 } else if (!is_head_commit(view->vid)) {
4019                         open_blob_editor();
4020                 } else {
4021                         open_editor(TRUE, opt_file);
4022                 }
4023                 return REQ_NONE;
4025         case REQ_PARENT:
4026                 if (!*opt_path) {
4027                         /* quit view if at top of tree */
4028                         return REQ_VIEW_CLOSE;
4029                 }
4030                 /* fake 'cd  ..' */
4031                 line = &view->line[1];
4032                 break;
4034         case REQ_ENTER:
4035                 break;
4037         default:
4038                 return request;
4039         }
4041         /* Cleanup the stack if the tree view is at a different tree. */
4042         while (!*opt_path && tree_stack)
4043                 pop_tree_stack_entry();
4045         switch (line->type) {
4046         case LINE_TREE_DIR:
4047                 /* Depending on whether it is a subdirectory or parent link
4048                  * mangle the path buffer. */
4049                 if (line == &view->line[1] && *opt_path) {
4050                         pop_tree_stack_entry();
4052                 } else {
4053                         const char *basename = tree_path(line);
4055                         push_tree_stack_entry(basename, view->lineno);
4056                 }
4058                 /* Trees and subtrees share the same ID, so they are not not
4059                  * unique like blobs. */
4060                 flags = OPEN_RELOAD;
4061                 request = REQ_VIEW_TREE;
4062                 break;
4064         case LINE_TREE_FILE:
4065                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4066                 request = REQ_VIEW_BLOB;
4067                 break;
4069         default:
4070                 return REQ_NONE;
4071         }
4073         open_view(view, request, flags);
4074         if (request == REQ_VIEW_TREE)
4075                 view->lineno = tree_lineno;
4077         return REQ_NONE;
4080 static void
4081 tree_select(struct view *view, struct line *line)
4083         struct tree_entry *entry = line->data;
4085         if (line->type == LINE_TREE_FILE) {
4086                 string_copy_rev(ref_blob, entry->id);
4087                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4089         } else if (line->type != LINE_TREE_DIR) {
4090                 return;
4091         }
4093         string_copy_rev(view->ref, entry->id);
4096 static const char *tree_argv[SIZEOF_ARG] = {
4097         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4098 };
4100 static struct view_ops tree_ops = {
4101         "file",
4102         tree_argv,
4103         NULL,
4104         tree_read,
4105         tree_draw,
4106         tree_request,
4107         pager_grep,
4108         tree_select,
4109 };
4111 static bool
4112 blob_read(struct view *view, char *line)
4114         if (!line)
4115                 return TRUE;
4116         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4119 static enum request
4120 blob_request(struct view *view, enum request request, struct line *line)
4122         switch (request) {
4123         case REQ_EDIT:
4124                 open_blob_editor();
4125                 return REQ_NONE;
4126         default:
4127                 return pager_request(view, request, line);
4128         }
4131 static const char *blob_argv[SIZEOF_ARG] = {
4132         "git", "cat-file", "blob", "%(blob)", NULL
4133 };
4135 static struct view_ops blob_ops = {
4136         "line",
4137         blob_argv,
4138         NULL,
4139         blob_read,
4140         pager_draw,
4141         blob_request,
4142         pager_grep,
4143         pager_select,
4144 };
4146 /*
4147  * Blame backend
4148  *
4149  * Loading the blame view is a two phase job:
4150  *
4151  *  1. File content is read either using opt_file from the
4152  *     filesystem or using git-cat-file.
4153  *  2. Then blame information is incrementally added by
4154  *     reading output from git-blame.
4155  */
4157 static const char *blame_head_argv[] = {
4158         "git", "blame", "--incremental", "--", "%(file)", NULL
4159 };
4161 static const char *blame_ref_argv[] = {
4162         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4163 };
4165 static const char *blame_cat_file_argv[] = {
4166         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4167 };
4169 struct blame_commit {
4170         char id[SIZEOF_REV];            /* SHA1 ID. */
4171         char title[128];                /* First line of the commit message. */
4172         char author[75];                /* Author of the commit. */
4173         struct tm time;                 /* Date from the author ident. */
4174         char filename[128];             /* Name of file. */
4175         bool has_previous;              /* Was a "previous" line detected. */
4176 };
4178 struct blame {
4179         struct blame_commit *commit;
4180         char text[1];
4181 };
4183 static bool
4184 blame_open(struct view *view)
4186         if (*opt_ref || !io_open(&view->io, opt_file)) {
4187                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4188                         return FALSE;
4189         }
4191         setup_update(view, opt_file);
4192         string_format(view->ref, "%s ...", opt_file);
4194         return TRUE;
4197 static struct blame_commit *
4198 get_blame_commit(struct view *view, const char *id)
4200         size_t i;
4202         for (i = 0; i < view->lines; i++) {
4203                 struct blame *blame = view->line[i].data;
4205                 if (!blame->commit)
4206                         continue;
4208                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4209                         return blame->commit;
4210         }
4212         {
4213                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4215                 if (commit)
4216                         string_ncopy(commit->id, id, SIZEOF_REV);
4217                 return commit;
4218         }
4221 static bool
4222 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4224         const char *pos = *posref;
4226         *posref = NULL;
4227         pos = strchr(pos + 1, ' ');
4228         if (!pos || !isdigit(pos[1]))
4229                 return FALSE;
4230         *number = atoi(pos + 1);
4231         if (*number < min || *number > max)
4232                 return FALSE;
4234         *posref = pos;
4235         return TRUE;
4238 static struct blame_commit *
4239 parse_blame_commit(struct view *view, const char *text, int *blamed)
4241         struct blame_commit *commit;
4242         struct blame *blame;
4243         const char *pos = text + SIZEOF_REV - 1;
4244         size_t lineno;
4245         size_t group;
4247         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4248                 return NULL;
4250         if (!parse_number(&pos, &lineno, 1, view->lines) ||
4251             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4252                 return NULL;
4254         commit = get_blame_commit(view, text);
4255         if (!commit)
4256                 return NULL;
4258         *blamed += group;
4259         while (group--) {
4260                 struct line *line = &view->line[lineno + group - 1];
4262                 blame = line->data;
4263                 blame->commit = commit;
4264                 line->dirty = 1;
4265         }
4267         return commit;
4270 static bool
4271 blame_read_file(struct view *view, const char *line, bool *read_file)
4273         if (!line) {
4274                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4275                 struct io io = {};
4277                 if (view->lines == 0 && !view->parent)
4278                         die("No blame exist for %s", view->vid);
4280                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4281                         report("Failed to load blame data");
4282                         return TRUE;
4283                 }
4285                 done_io(view->pipe);
4286                 view->io = io;
4287                 *read_file = FALSE;
4288                 return FALSE;
4290         } else {
4291                 size_t linelen = strlen(line);
4292                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4294                 if (!blame)
4295                         return FALSE;
4297                 blame->commit = NULL;
4298                 strncpy(blame->text, line, linelen);
4299                 blame->text[linelen] = 0;
4300                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4301         }
4304 static bool
4305 match_blame_header(const char *name, char **line)
4307         size_t namelen = strlen(name);
4308         bool matched = !strncmp(name, *line, namelen);
4310         if (matched)
4311                 *line += namelen;
4313         return matched;
4316 static bool
4317 blame_read(struct view *view, char *line)
4319         static struct blame_commit *commit = NULL;
4320         static int blamed = 0;
4321         static time_t author_time;
4322         static bool read_file = TRUE;
4324         if (read_file)
4325                 return blame_read_file(view, line, &read_file);
4327         if (!line) {
4328                 /* Reset all! */
4329                 commit = NULL;
4330                 blamed = 0;
4331                 read_file = TRUE;
4332                 string_format(view->ref, "%s", view->vid);
4333                 if (view_is_displayed(view)) {
4334                         update_view_title(view);
4335                         redraw_view_from(view, 0);
4336                 }
4337                 return TRUE;
4338         }
4340         if (!commit) {
4341                 commit = parse_blame_commit(view, line, &blamed);
4342                 string_format(view->ref, "%s %2d%%", view->vid,
4343                               view->lines ? blamed * 100 / view->lines : 0);
4345         } else if (match_blame_header("author ", &line)) {
4346                 string_ncopy(commit->author, line, strlen(line));
4348         } else if (match_blame_header("author-time ", &line)) {
4349                 author_time = (time_t) atol(line);
4351         } else if (match_blame_header("author-tz ", &line)) {
4352                 parse_timezone(&author_time, line);
4353                 gmtime_r(&author_time, &commit->time);
4355         } else if (match_blame_header("summary ", &line)) {
4356                 string_ncopy(commit->title, line, strlen(line));
4358         } else if (match_blame_header("previous ", &line)) {
4359                 commit->has_previous = TRUE;
4361         } else if (match_blame_header("filename ", &line)) {
4362                 string_ncopy(commit->filename, line, strlen(line));
4363                 commit = NULL;
4364         }
4366         return TRUE;
4369 static bool
4370 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4372         struct blame *blame = line->data;
4373         struct tm *time = NULL;
4374         const char *id = NULL, *author = NULL;
4375         char text[SIZEOF_STR];
4377         if (blame->commit && *blame->commit->filename) {
4378                 id = blame->commit->id;
4379                 author = blame->commit->author;
4380                 time = &blame->commit->time;
4381         }
4383         if (opt_date && draw_date(view, time))
4384                 return TRUE;
4386         if (opt_author && draw_author(view, author))
4387                 return TRUE;
4389         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4390                 return TRUE;
4392         if (draw_lineno(view, lineno))
4393                 return TRUE;
4395         string_expand(text, sizeof(text), blame->text, opt_tab_size);
4396         draw_text(view, LINE_DEFAULT, text, TRUE);
4397         return TRUE;
4400 static bool
4401 check_blame_commit(struct blame *blame)
4403         if (!blame->commit)
4404                 report("Commit data not loaded yet");
4405         else if (!strcmp(blame->commit->id, NULL_ID))
4406                 report("No commit exist for the selected line");
4407         else
4408                 return TRUE;
4409         return FALSE;
4412 static enum request
4413 blame_request(struct view *view, enum request request, struct line *line)
4415         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4416         struct blame *blame = line->data;
4418         switch (request) {
4419         case REQ_VIEW_BLAME:
4420                 if (check_blame_commit(blame)) {
4421                         string_copy(opt_ref, blame->commit->id);
4422                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4423                 }
4424                 break;
4426         case REQ_PARENT:
4427                 if (check_blame_commit(blame) &&
4428                     select_commit_parent(blame->commit->id, opt_ref))
4429                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4430                 break;
4432         case REQ_ENTER:
4433                 if (!blame->commit) {
4434                         report("No commit loaded yet");
4435                         break;
4436                 }
4438                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4439                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4440                         break;
4442                 if (!strcmp(blame->commit->id, NULL_ID)) {
4443                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4444                         const char *diff_index_argv[] = {
4445                                 "git", "diff-index", "--root", "--patch-with-stat",
4446                                         "-C", "-M", "HEAD", "--", view->vid, NULL
4447                         };
4449                         if (!blame->commit->has_previous) {
4450                                 diff_index_argv[1] = "diff";
4451                                 diff_index_argv[2] = "--no-color";
4452                                 diff_index_argv[6] = "--";
4453                                 diff_index_argv[7] = "/dev/null";
4454                         }
4456                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4457                                 report("Failed to allocate diff command");
4458                                 break;
4459                         }
4460                         flags |= OPEN_PREPARED;
4461                 }
4463                 open_view(view, REQ_VIEW_DIFF, flags);
4464                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4465                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4466                 break;
4468         default:
4469                 return request;
4470         }
4472         return REQ_NONE;
4475 static bool
4476 blame_grep(struct view *view, struct line *line)
4478         struct blame *blame = line->data;
4479         struct blame_commit *commit = blame->commit;
4480         regmatch_t pmatch;
4482 #define MATCH(text, on)                                                 \
4483         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4485         if (commit) {
4486                 char buf[DATE_COLS + 1];
4488                 if (MATCH(commit->title, 1) ||
4489                     MATCH(commit->author, opt_author) ||
4490                     MATCH(commit->id, opt_date))
4491                         return TRUE;
4493                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4494                     MATCH(buf, 1))
4495                         return TRUE;
4496         }
4498         return MATCH(blame->text, 1);
4500 #undef MATCH
4503 static void
4504 blame_select(struct view *view, struct line *line)
4506         struct blame *blame = line->data;
4507         struct blame_commit *commit = blame->commit;
4509         if (!commit)
4510                 return;
4512         if (!strcmp(commit->id, NULL_ID))
4513                 string_ncopy(ref_commit, "HEAD", 4);
4514         else
4515                 string_copy_rev(ref_commit, commit->id);
4518 static struct view_ops blame_ops = {
4519         "line",
4520         NULL,
4521         blame_open,
4522         blame_read,
4523         blame_draw,
4524         blame_request,
4525         blame_grep,
4526         blame_select,
4527 };
4529 /*
4530  * Status backend
4531  */
4533 struct status {
4534         char status;
4535         struct {
4536                 mode_t mode;
4537                 char rev[SIZEOF_REV];
4538                 char name[SIZEOF_STR];
4539         } old;
4540         struct {
4541                 mode_t mode;
4542                 char rev[SIZEOF_REV];
4543                 char name[SIZEOF_STR];
4544         } new;
4545 };
4547 static char status_onbranch[SIZEOF_STR];
4548 static struct status stage_status;
4549 static enum line_type stage_line_type;
4550 static size_t stage_chunks;
4551 static int *stage_chunk;
4553 /* This should work even for the "On branch" line. */
4554 static inline bool
4555 status_has_none(struct view *view, struct line *line)
4557         return line < view->line + view->lines && !line[1].data;
4560 /* Get fields from the diff line:
4561  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4562  */
4563 static inline bool
4564 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4566         const char *old_mode = buf +  1;
4567         const char *new_mode = buf +  8;
4568         const char *old_rev  = buf + 15;
4569         const char *new_rev  = buf + 56;
4570         const char *status   = buf + 97;
4572         if (bufsize < 98 ||
4573             old_mode[-1] != ':' ||
4574             new_mode[-1] != ' ' ||
4575             old_rev[-1]  != ' ' ||
4576             new_rev[-1]  != ' ' ||
4577             status[-1]   != ' ')
4578                 return FALSE;
4580         file->status = *status;
4582         string_copy_rev(file->old.rev, old_rev);
4583         string_copy_rev(file->new.rev, new_rev);
4585         file->old.mode = strtoul(old_mode, NULL, 8);
4586         file->new.mode = strtoul(new_mode, NULL, 8);
4588         file->old.name[0] = file->new.name[0] = 0;
4590         return TRUE;
4593 static bool
4594 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4596         struct status *unmerged = NULL;
4597         char *buf;
4598         struct io io = {};
4600         if (!run_io(&io, argv, NULL, IO_RD))
4601                 return FALSE;
4603         add_line_data(view, NULL, type);
4605         while ((buf = io_get(&io, 0, TRUE))) {
4606                 struct status *file = unmerged;
4608                 if (!file) {
4609                         file = calloc(1, sizeof(*file));
4610                         if (!file || !add_line_data(view, file, type))
4611                                 goto error_out;
4612                 }
4614                 /* Parse diff info part. */
4615                 if (status) {
4616                         file->status = status;
4617                         if (status == 'A')
4618                                 string_copy(file->old.rev, NULL_ID);
4620                 } else if (!file->status || file == unmerged) {
4621                         if (!status_get_diff(file, buf, strlen(buf)))
4622                                 goto error_out;
4624                         buf = io_get(&io, 0, TRUE);
4625                         if (!buf)
4626                                 break;
4628                         /* Collapse all modified entries that follow an
4629                          * associated unmerged entry. */
4630                         if (unmerged == file) {
4631                                 unmerged->status = 'U';
4632                                 unmerged = NULL;
4633                         } else if (file->status == 'U') {
4634                                 unmerged = file;
4635                         }
4636                 }
4638                 /* Grab the old name for rename/copy. */
4639                 if (!*file->old.name &&
4640                     (file->status == 'R' || file->status == 'C')) {
4641                         string_ncopy(file->old.name, buf, strlen(buf));
4643                         buf = io_get(&io, 0, TRUE);
4644                         if (!buf)
4645                                 break;
4646                 }
4648                 /* git-ls-files just delivers a NUL separated list of
4649                  * file names similar to the second half of the
4650                  * git-diff-* output. */
4651                 string_ncopy(file->new.name, buf, strlen(buf));
4652                 if (!*file->old.name)
4653                         string_copy(file->old.name, file->new.name);
4654                 file = NULL;
4655         }
4657         if (io_error(&io)) {
4658 error_out:
4659                 done_io(&io);
4660                 return FALSE;
4661         }
4663         if (!view->line[view->lines - 1].data)
4664                 add_line_data(view, NULL, LINE_STAT_NONE);
4666         done_io(&io);
4667         return TRUE;
4670 /* Don't show unmerged entries in the staged section. */
4671 static const char *status_diff_index_argv[] = {
4672         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4673                              "--cached", "-M", "HEAD", NULL
4674 };
4676 static const char *status_diff_files_argv[] = {
4677         "git", "diff-files", "-z", NULL
4678 };
4680 static const char *status_list_other_argv[] = {
4681         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4682 };
4684 static const char *status_list_no_head_argv[] = {
4685         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4686 };
4688 static const char *update_index_argv[] = {
4689         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4690 };
4692 /* Restore the previous line number to stay in the context or select a
4693  * line with something that can be updated. */
4694 static void
4695 status_restore(struct view *view)
4697         if (view->p_lineno >= view->lines)
4698                 view->p_lineno = view->lines - 1;
4699         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4700                 view->p_lineno++;
4701         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4702                 view->p_lineno--;
4704         /* If the above fails, always skip the "On branch" line. */
4705         if (view->p_lineno < view->lines)
4706                 view->lineno = view->p_lineno;
4707         else
4708                 view->lineno = 1;
4710         if (view->lineno < view->offset)
4711                 view->offset = view->lineno;
4712         else if (view->offset + view->height <= view->lineno)
4713                 view->offset = view->lineno - view->height + 1;
4715         view->p_restore = FALSE;
4718 static void
4719 status_update_onbranch(void)
4721         static const char *paths[][2] = {
4722                 { "rebase-apply/rebasing",      "Rebasing" },
4723                 { "rebase-apply/applying",      "Applying mailbox" },
4724                 { "rebase-apply/",              "Rebasing mailbox" },
4725                 { "rebase-merge/interactive",   "Interactive rebase" },
4726                 { "rebase-merge/",              "Rebase merge" },
4727                 { "MERGE_HEAD",                 "Merging" },
4728                 { "BISECT_LOG",                 "Bisecting" },
4729                 { "HEAD",                       "On branch" },
4730         };
4731         char buf[SIZEOF_STR];
4732         struct stat stat;
4733         int i;
4735         if (is_initial_commit()) {
4736                 string_copy(status_onbranch, "Initial commit");
4737                 return;
4738         }
4740         for (i = 0; i < ARRAY_SIZE(paths); i++) {
4741                 char *head = opt_head;
4743                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4744                     lstat(buf, &stat) < 0)
4745                         continue;
4747                 if (!*opt_head) {
4748                         struct io io = {};
4750                         if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4751                             io_open(&io, buf) &&
4752                             io_read_buf(&io, buf, sizeof(buf))) {
4753                                 head = chomp_string(buf);
4754                                 if (!prefixcmp(head, "refs/heads/"))
4755                                         head += STRING_SIZE("refs/heads/");
4756                         }
4757                 }
4759                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4760                         string_copy(status_onbranch, opt_head);
4761                 return;
4762         }
4764         string_copy(status_onbranch, "Not currently on any branch");
4767 /* First parse staged info using git-diff-index(1), then parse unstaged
4768  * info using git-diff-files(1), and finally untracked files using
4769  * git-ls-files(1). */
4770 static bool
4771 status_open(struct view *view)
4773         reset_view(view);
4775         add_line_data(view, NULL, LINE_STAT_HEAD);
4776         status_update_onbranch();
4778         run_io_bg(update_index_argv);
4780         if (is_initial_commit()) {
4781                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4782                         return FALSE;
4783         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4784                 return FALSE;
4785         }
4787         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4788             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4789                 return FALSE;
4791         /* Restore the exact position or use the specialized restore
4792          * mode? */
4793         if (!view->p_restore)
4794                 status_restore(view);
4795         return TRUE;
4798 static bool
4799 status_draw(struct view *view, struct line *line, unsigned int lineno)
4801         struct status *status = line->data;
4802         enum line_type type;
4803         const char *text;
4805         if (!status) {
4806                 switch (line->type) {
4807                 case LINE_STAT_STAGED:
4808                         type = LINE_STAT_SECTION;
4809                         text = "Changes to be committed:";
4810                         break;
4812                 case LINE_STAT_UNSTAGED:
4813                         type = LINE_STAT_SECTION;
4814                         text = "Changed but not updated:";
4815                         break;
4817                 case LINE_STAT_UNTRACKED:
4818                         type = LINE_STAT_SECTION;
4819                         text = "Untracked files:";
4820                         break;
4822                 case LINE_STAT_NONE:
4823                         type = LINE_DEFAULT;
4824                         text = "  (no files)";
4825                         break;
4827                 case LINE_STAT_HEAD:
4828                         type = LINE_STAT_HEAD;
4829                         text = status_onbranch;
4830                         break;
4832                 default:
4833                         return FALSE;
4834                 }
4835         } else {
4836                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4838                 buf[0] = status->status;
4839                 if (draw_text(view, line->type, buf, TRUE))
4840                         return TRUE;
4841                 type = LINE_DEFAULT;
4842                 text = status->new.name;
4843         }
4845         draw_text(view, type, text, TRUE);
4846         return TRUE;
4849 static enum request
4850 status_enter(struct view *view, struct line *line)
4852         struct status *status = line->data;
4853         const char *oldpath = status ? status->old.name : NULL;
4854         /* Diffs for unmerged entries are empty when passing the new
4855          * path, so leave it empty. */
4856         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4857         const char *info;
4858         enum open_flags split;
4859         struct view *stage = VIEW(REQ_VIEW_STAGE);
4861         if (line->type == LINE_STAT_NONE ||
4862             (!status && line[1].type == LINE_STAT_NONE)) {
4863                 report("No file to diff");
4864                 return REQ_NONE;
4865         }
4867         switch (line->type) {
4868         case LINE_STAT_STAGED:
4869                 if (is_initial_commit()) {
4870                         const char *no_head_diff_argv[] = {
4871                                 "git", "diff", "--no-color", "--patch-with-stat",
4872                                         "--", "/dev/null", newpath, NULL
4873                         };
4875                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4876                                 return REQ_QUIT;
4877                 } else {
4878                         const char *index_show_argv[] = {
4879                                 "git", "diff-index", "--root", "--patch-with-stat",
4880                                         "-C", "-M", "--cached", "HEAD", "--",
4881                                         oldpath, newpath, NULL
4882                         };
4884                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4885                                 return REQ_QUIT;
4886                 }
4888                 if (status)
4889                         info = "Staged changes to %s";
4890                 else
4891                         info = "Staged changes";
4892                 break;
4894         case LINE_STAT_UNSTAGED:
4895         {
4896                 const char *files_show_argv[] = {
4897                         "git", "diff-files", "--root", "--patch-with-stat",
4898                                 "-C", "-M", "--", oldpath, newpath, NULL
4899                 };
4901                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4902                         return REQ_QUIT;
4903                 if (status)
4904                         info = "Unstaged changes to %s";
4905                 else
4906                         info = "Unstaged changes";
4907                 break;
4908         }
4909         case LINE_STAT_UNTRACKED:
4910                 if (!newpath) {
4911                         report("No file to show");
4912                         return REQ_NONE;
4913                 }
4915                 if (!suffixcmp(status->new.name, -1, "/")) {
4916                         report("Cannot display a directory");
4917                         return REQ_NONE;
4918                 }
4920                 if (!prepare_update_file(stage, newpath))
4921                         return REQ_QUIT;
4922                 info = "Untracked file %s";
4923                 break;
4925         case LINE_STAT_HEAD:
4926                 return REQ_NONE;
4928         default:
4929                 die("line type %d not handled in switch", line->type);
4930         }
4932         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4933         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4934         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4935                 if (status) {
4936                         stage_status = *status;
4937                 } else {
4938                         memset(&stage_status, 0, sizeof(stage_status));
4939                 }
4941                 stage_line_type = line->type;
4942                 stage_chunks = 0;
4943                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4944         }
4946         return REQ_NONE;
4949 static bool
4950 status_exists(struct status *status, enum line_type type)
4952         struct view *view = VIEW(REQ_VIEW_STATUS);
4953         unsigned long lineno;
4955         for (lineno = 0; lineno < view->lines; lineno++) {
4956                 struct line *line = &view->line[lineno];
4957                 struct status *pos = line->data;
4959                 if (line->type != type)
4960                         continue;
4961                 if (!pos && (!status || !status->status) && line[1].data) {
4962                         select_view_line(view, lineno);
4963                         return TRUE;
4964                 }
4965                 if (pos && !strcmp(status->new.name, pos->new.name)) {
4966                         select_view_line(view, lineno);
4967                         return TRUE;
4968                 }
4969         }
4971         return FALSE;
4975 static bool
4976 status_update_prepare(struct io *io, enum line_type type)
4978         const char *staged_argv[] = {
4979                 "git", "update-index", "-z", "--index-info", NULL
4980         };
4981         const char *others_argv[] = {
4982                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4983         };
4985         switch (type) {
4986         case LINE_STAT_STAGED:
4987                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4989         case LINE_STAT_UNSTAGED:
4990                 return run_io(io, others_argv, opt_cdup, IO_WR);
4992         case LINE_STAT_UNTRACKED:
4993                 return run_io(io, others_argv, NULL, IO_WR);
4995         default:
4996                 die("line type %d not handled in switch", type);
4997                 return FALSE;
4998         }
5001 static bool
5002 status_update_write(struct io *io, struct status *status, enum line_type type)
5004         char buf[SIZEOF_STR];
5005         size_t bufsize = 0;
5007         switch (type) {
5008         case LINE_STAT_STAGED:
5009                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5010                                         status->old.mode,
5011                                         status->old.rev,
5012                                         status->old.name, 0))
5013                         return FALSE;
5014                 break;
5016         case LINE_STAT_UNSTAGED:
5017         case LINE_STAT_UNTRACKED:
5018                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5019                         return FALSE;
5020                 break;
5022         default:
5023                 die("line type %d not handled in switch", type);
5024         }
5026         return io_write(io, buf, bufsize);
5029 static bool
5030 status_update_file(struct status *status, enum line_type type)
5032         struct io io = {};
5033         bool result;
5035         if (!status_update_prepare(&io, type))
5036                 return FALSE;
5038         result = status_update_write(&io, status, type);
5039         done_io(&io);
5040         return result;
5043 static bool
5044 status_update_files(struct view *view, struct line *line)
5046         struct io io = {};
5047         bool result = TRUE;
5048         struct line *pos = view->line + view->lines;
5049         int files = 0;
5050         int file, done;
5052         if (!status_update_prepare(&io, line->type))
5053                 return FALSE;
5055         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5056                 files++;
5058         for (file = 0, done = 0; result && file < files; line++, file++) {
5059                 int almost_done = file * 100 / files;
5061                 if (almost_done > done) {
5062                         done = almost_done;
5063                         string_format(view->ref, "updating file %u of %u (%d%% done)",
5064                                       file, files, done);
5065                         update_view_title(view);
5066                 }
5067                 result = status_update_write(&io, line->data, line->type);
5068         }
5070         done_io(&io);
5071         return result;
5074 static bool
5075 status_update(struct view *view)
5077         struct line *line = &view->line[view->lineno];
5079         assert(view->lines);
5081         if (!line->data) {
5082                 /* This should work even for the "On branch" line. */
5083                 if (line < view->line + view->lines && !line[1].data) {
5084                         report("Nothing to update");
5085                         return FALSE;
5086                 }
5088                 if (!status_update_files(view, line + 1)) {
5089                         report("Failed to update file status");
5090                         return FALSE;
5091                 }
5093         } else if (!status_update_file(line->data, line->type)) {
5094                 report("Failed to update file status");
5095                 return FALSE;
5096         }
5098         return TRUE;
5101 static bool
5102 status_revert(struct status *status, enum line_type type, bool has_none)
5104         if (!status || type != LINE_STAT_UNSTAGED) {
5105                 if (type == LINE_STAT_STAGED) {
5106                         report("Cannot revert changes to staged files");
5107                 } else if (type == LINE_STAT_UNTRACKED) {
5108                         report("Cannot revert changes to untracked files");
5109                 } else if (has_none) {
5110                         report("Nothing to revert");
5111                 } else {
5112                         report("Cannot revert changes to multiple files");
5113                 }
5114                 return FALSE;
5116         } else {
5117                 char mode[10] = "100644";
5118                 const char *reset_argv[] = {
5119                         "git", "update-index", "--cacheinfo", mode,
5120                                 status->old.rev, status->old.name, NULL
5121                 };
5122                 const char *checkout_argv[] = {
5123                         "git", "checkout", "--", status->old.name, NULL
5124                 };
5126                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5127                         return FALSE;
5128                 string_format(mode, "%o", status->old.mode);
5129                 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5130                         run_io_fg(checkout_argv, opt_cdup);
5131         }
5134 static enum request
5135 status_request(struct view *view, enum request request, struct line *line)
5137         struct status *status = line->data;
5139         switch (request) {
5140         case REQ_STATUS_UPDATE:
5141                 if (!status_update(view))
5142                         return REQ_NONE;
5143                 break;
5145         case REQ_STATUS_REVERT:
5146                 if (!status_revert(status, line->type, status_has_none(view, line)))
5147                         return REQ_NONE;
5148                 break;
5150         case REQ_STATUS_MERGE:
5151                 if (!status || status->status != 'U') {
5152                         report("Merging only possible for files with unmerged status ('U').");
5153                         return REQ_NONE;
5154                 }
5155                 open_mergetool(status->new.name);
5156                 break;
5158         case REQ_EDIT:
5159                 if (!status)
5160                         return request;
5161                 if (status->status == 'D') {
5162                         report("File has been deleted.");
5163                         return REQ_NONE;
5164                 }
5166                 open_editor(status->status != '?', status->new.name);
5167                 break;
5169         case REQ_VIEW_BLAME:
5170                 if (status) {
5171                         string_copy(opt_file, status->new.name);
5172                         opt_ref[0] = 0;
5173                 }
5174                 return request;
5176         case REQ_ENTER:
5177                 /* After returning the status view has been split to
5178                  * show the stage view. No further reloading is
5179                  * necessary. */
5180                 status_enter(view, line);
5181                 return REQ_NONE;
5183         case REQ_REFRESH:
5184                 /* Simply reload the view. */
5185                 break;
5187         default:
5188                 return request;
5189         }
5191         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5193         return REQ_NONE;
5196 static void
5197 status_select(struct view *view, struct line *line)
5199         struct status *status = line->data;
5200         char file[SIZEOF_STR] = "all files";
5201         const char *text;
5202         const char *key;
5204         if (status && !string_format(file, "'%s'", status->new.name))
5205                 return;
5207         if (!status && line[1].type == LINE_STAT_NONE)
5208                 line++;
5210         switch (line->type) {
5211         case LINE_STAT_STAGED:
5212                 text = "Press %s to unstage %s for commit";
5213                 break;
5215         case LINE_STAT_UNSTAGED:
5216                 text = "Press %s to stage %s for commit";
5217                 break;
5219         case LINE_STAT_UNTRACKED:
5220                 text = "Press %s to stage %s for addition";
5221                 break;
5223         case LINE_STAT_HEAD:
5224         case LINE_STAT_NONE:
5225                 text = "Nothing to update";
5226                 break;
5228         default:
5229                 die("line type %d not handled in switch", line->type);
5230         }
5232         if (status && status->status == 'U') {
5233                 text = "Press %s to resolve conflict in %s";
5234                 key = get_key(REQ_STATUS_MERGE);
5236         } else {
5237                 key = get_key(REQ_STATUS_UPDATE);
5238         }
5240         string_format(view->ref, text, key, file);
5243 static bool
5244 status_grep(struct view *view, struct line *line)
5246         struct status *status = line->data;
5247         enum { S_STATUS, S_NAME, S_END } state;
5248         char buf[2] = "?";
5249         regmatch_t pmatch;
5251         if (!status)
5252                 return FALSE;
5254         for (state = S_STATUS; state < S_END; state++) {
5255                 const char *text;
5257                 switch (state) {
5258                 case S_NAME:    text = status->new.name;        break;
5259                 case S_STATUS:
5260                         buf[0] = status->status;
5261                         text = buf;
5262                         break;
5264                 default:
5265                         return FALSE;
5266                 }
5268                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5269                         return TRUE;
5270         }
5272         return FALSE;
5275 static struct view_ops status_ops = {
5276         "file",
5277         NULL,
5278         status_open,
5279         NULL,
5280         status_draw,
5281         status_request,
5282         status_grep,
5283         status_select,
5284 };
5287 static bool
5288 stage_diff_write(struct io *io, struct line *line, struct line *end)
5290         while (line < end) {
5291                 if (!io_write(io, line->data, strlen(line->data)) ||
5292                     !io_write(io, "\n", 1))
5293                         return FALSE;
5294                 line++;
5295                 if (line->type == LINE_DIFF_CHUNK ||
5296                     line->type == LINE_DIFF_HEADER)
5297                         break;
5298         }
5300         return TRUE;
5303 static struct line *
5304 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5306         for (; view->line < line; line--)
5307                 if (line->type == type)
5308                         return line;
5310         return NULL;
5313 static bool
5314 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5316         const char *apply_argv[SIZEOF_ARG] = {
5317                 "git", "apply", "--whitespace=nowarn", NULL
5318         };
5319         struct line *diff_hdr;
5320         struct io io = {};
5321         int argc = 3;
5323         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5324         if (!diff_hdr)
5325                 return FALSE;
5327         if (!revert)
5328                 apply_argv[argc++] = "--cached";
5329         if (revert || stage_line_type == LINE_STAT_STAGED)
5330                 apply_argv[argc++] = "-R";
5331         apply_argv[argc++] = "-";
5332         apply_argv[argc++] = NULL;
5333         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5334                 return FALSE;
5336         if (!stage_diff_write(&io, diff_hdr, chunk) ||
5337             !stage_diff_write(&io, chunk, view->line + view->lines))
5338                 chunk = NULL;
5340         done_io(&io);
5341         run_io_bg(update_index_argv);
5343         return chunk ? TRUE : FALSE;
5346 static bool
5347 stage_update(struct view *view, struct line *line)
5349         struct line *chunk = NULL;
5351         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5352                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5354         if (chunk) {
5355                 if (!stage_apply_chunk(view, chunk, FALSE)) {
5356                         report("Failed to apply chunk");
5357                         return FALSE;
5358                 }
5360         } else if (!stage_status.status) {
5361                 view = VIEW(REQ_VIEW_STATUS);
5363                 for (line = view->line; line < view->line + view->lines; line++)
5364                         if (line->type == stage_line_type)
5365                                 break;
5367                 if (!status_update_files(view, line + 1)) {
5368                         report("Failed to update files");
5369                         return FALSE;
5370                 }
5372         } else if (!status_update_file(&stage_status, stage_line_type)) {
5373                 report("Failed to update file");
5374                 return FALSE;
5375         }
5377         return TRUE;
5380 static bool
5381 stage_revert(struct view *view, struct line *line)
5383         struct line *chunk = NULL;
5385         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5386                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5388         if (chunk) {
5389                 if (!prompt_yesno("Are you sure you want to revert changes?"))
5390                         return FALSE;
5392                 if (!stage_apply_chunk(view, chunk, TRUE)) {
5393                         report("Failed to revert chunk");
5394                         return FALSE;
5395                 }
5396                 return TRUE;
5398         } else {
5399                 return status_revert(stage_status.status ? &stage_status : NULL,
5400                                      stage_line_type, FALSE);
5401         }
5405 static void
5406 stage_next(struct view *view, struct line *line)
5408         int i;
5410         if (!stage_chunks) {
5411                 static size_t alloc = 0;
5412                 int *tmp;
5414                 for (line = view->line; line < view->line + view->lines; line++) {
5415                         if (line->type != LINE_DIFF_CHUNK)
5416                                 continue;
5418                         tmp = realloc_items(stage_chunk, &alloc,
5419                                             stage_chunks, sizeof(*tmp));
5420                         if (!tmp) {
5421                                 report("Allocation failure");
5422                                 return;
5423                         }
5425                         stage_chunk = tmp;
5426                         stage_chunk[stage_chunks++] = line - view->line;
5427                 }
5428         }
5430         for (i = 0; i < stage_chunks; i++) {
5431                 if (stage_chunk[i] > view->lineno) {
5432                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5433                         report("Chunk %d of %d", i + 1, stage_chunks);
5434                         return;
5435                 }
5436         }
5438         report("No next chunk found");
5441 static enum request
5442 stage_request(struct view *view, enum request request, struct line *line)
5444         switch (request) {
5445         case REQ_STATUS_UPDATE:
5446                 if (!stage_update(view, line))
5447                         return REQ_NONE;
5448                 break;
5450         case REQ_STATUS_REVERT:
5451                 if (!stage_revert(view, line))
5452                         return REQ_NONE;
5453                 break;
5455         case REQ_STAGE_NEXT:
5456                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5457                         report("File is untracked; press %s to add",
5458                                get_key(REQ_STATUS_UPDATE));
5459                         return REQ_NONE;
5460                 }
5461                 stage_next(view, line);
5462                 return REQ_NONE;
5464         case REQ_EDIT:
5465                 if (!stage_status.new.name[0])
5466                         return request;
5467                 if (stage_status.status == 'D') {
5468                         report("File has been deleted.");
5469                         return REQ_NONE;
5470                 }
5472                 open_editor(stage_status.status != '?', stage_status.new.name);
5473                 break;
5475         case REQ_REFRESH:
5476                 /* Reload everything ... */
5477                 break;
5479         case REQ_VIEW_BLAME:
5480                 if (stage_status.new.name[0]) {
5481                         string_copy(opt_file, stage_status.new.name);
5482                         opt_ref[0] = 0;
5483                 }
5484                 return request;
5486         case REQ_ENTER:
5487                 return pager_request(view, request, line);
5489         default:
5490                 return request;
5491         }
5493         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5494         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5496         /* Check whether the staged entry still exists, and close the
5497          * stage view if it doesn't. */
5498         if (!status_exists(&stage_status, stage_line_type)) {
5499                 status_restore(VIEW(REQ_VIEW_STATUS));
5500                 return REQ_VIEW_CLOSE;
5501         }
5503         if (stage_line_type == LINE_STAT_UNTRACKED) {
5504                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5505                         report("Cannot display a directory");
5506                         return REQ_NONE;
5507                 }
5509                 if (!prepare_update_file(view, stage_status.new.name)) {
5510                         report("Failed to open file: %s", strerror(errno));
5511                         return REQ_NONE;
5512                 }
5513         }
5514         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5516         return REQ_NONE;
5519 static struct view_ops stage_ops = {
5520         "line",
5521         NULL,
5522         NULL,
5523         pager_read,
5524         pager_draw,
5525         stage_request,
5526         pager_grep,
5527         pager_select,
5528 };
5531 /*
5532  * Revision graph
5533  */
5535 struct commit {
5536         char id[SIZEOF_REV];            /* SHA1 ID. */
5537         char title[128];                /* First line of the commit message. */
5538         char author[75];                /* Author of the commit. */
5539         struct tm time;                 /* Date from the author ident. */
5540         struct ref **refs;              /* Repository references. */
5541         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5542         size_t graph_size;              /* The width of the graph array. */
5543         bool has_parents;               /* Rewritten --parents seen. */
5544 };
5546 /* Size of rev graph with no  "padding" columns */
5547 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5549 struct rev_graph {
5550         struct rev_graph *prev, *next, *parents;
5551         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5552         size_t size;
5553         struct commit *commit;
5554         size_t pos;
5555         unsigned int boundary:1;
5556 };
5558 /* Parents of the commit being visualized. */
5559 static struct rev_graph graph_parents[4];
5561 /* The current stack of revisions on the graph. */
5562 static struct rev_graph graph_stacks[4] = {
5563         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5564         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5565         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5566         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5567 };
5569 static inline bool
5570 graph_parent_is_merge(struct rev_graph *graph)
5572         return graph->parents->size > 1;
5575 static inline void
5576 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5578         struct commit *commit = graph->commit;
5580         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5581                 commit->graph[commit->graph_size++] = symbol;
5584 static void
5585 clear_rev_graph(struct rev_graph *graph)
5587         graph->boundary = 0;
5588         graph->size = graph->pos = 0;
5589         graph->commit = NULL;
5590         memset(graph->parents, 0, sizeof(*graph->parents));
5593 static void
5594 done_rev_graph(struct rev_graph *graph)
5596         if (graph_parent_is_merge(graph) &&
5597             graph->pos < graph->size - 1 &&
5598             graph->next->size == graph->size + graph->parents->size - 1) {
5599                 size_t i = graph->pos + graph->parents->size - 1;
5601                 graph->commit->graph_size = i * 2;
5602                 while (i < graph->next->size - 1) {
5603                         append_to_rev_graph(graph, ' ');
5604                         append_to_rev_graph(graph, '\\');
5605                         i++;
5606                 }
5607         }
5609         clear_rev_graph(graph);
5612 static void
5613 push_rev_graph(struct rev_graph *graph, const char *parent)
5615         int i;
5617         /* "Collapse" duplicate parents lines.
5618          *
5619          * FIXME: This needs to also update update the drawn graph but
5620          * for now it just serves as a method for pruning graph lines. */
5621         for (i = 0; i < graph->size; i++)
5622                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5623                         return;
5625         if (graph->size < SIZEOF_REVITEMS) {
5626                 string_copy_rev(graph->rev[graph->size++], parent);
5627         }
5630 static chtype
5631 get_rev_graph_symbol(struct rev_graph *graph)
5633         chtype symbol;
5635         if (graph->boundary)
5636                 symbol = REVGRAPH_BOUND;
5637         else if (graph->parents->size == 0)
5638                 symbol = REVGRAPH_INIT;
5639         else if (graph_parent_is_merge(graph))
5640                 symbol = REVGRAPH_MERGE;
5641         else if (graph->pos >= graph->size)
5642                 symbol = REVGRAPH_BRANCH;
5643         else
5644                 symbol = REVGRAPH_COMMIT;
5646         return symbol;
5649 static void
5650 draw_rev_graph(struct rev_graph *graph)
5652         struct rev_filler {
5653                 chtype separator, line;
5654         };
5655         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5656         static struct rev_filler fillers[] = {
5657                 { ' ',  '|' },
5658                 { '`',  '.' },
5659                 { '\'', ' ' },
5660                 { '/',  ' ' },
5661         };
5662         chtype symbol = get_rev_graph_symbol(graph);
5663         struct rev_filler *filler;
5664         size_t i;
5666         if (opt_line_graphics)
5667                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5669         filler = &fillers[DEFAULT];
5671         for (i = 0; i < graph->pos; i++) {
5672                 append_to_rev_graph(graph, filler->line);
5673                 if (graph_parent_is_merge(graph->prev) &&
5674                     graph->prev->pos == i)
5675                         filler = &fillers[RSHARP];
5677                 append_to_rev_graph(graph, filler->separator);
5678         }
5680         /* Place the symbol for this revision. */
5681         append_to_rev_graph(graph, symbol);
5683         if (graph->prev->size > graph->size)
5684                 filler = &fillers[RDIAG];
5685         else
5686                 filler = &fillers[DEFAULT];
5688         i++;
5690         for (; i < graph->size; i++) {
5691                 append_to_rev_graph(graph, filler->separator);
5692                 append_to_rev_graph(graph, filler->line);
5693                 if (graph_parent_is_merge(graph->prev) &&
5694                     i < graph->prev->pos + graph->parents->size)
5695                         filler = &fillers[RSHARP];
5696                 if (graph->prev->size > graph->size)
5697                         filler = &fillers[LDIAG];
5698         }
5700         if (graph->prev->size > graph->size) {
5701                 append_to_rev_graph(graph, filler->separator);
5702                 if (filler->line != ' ')
5703                         append_to_rev_graph(graph, filler->line);
5704         }
5707 /* Prepare the next rev graph */
5708 static void
5709 prepare_rev_graph(struct rev_graph *graph)
5711         size_t i;
5713         /* First, traverse all lines of revisions up to the active one. */
5714         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5715                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5716                         break;
5718                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5719         }
5721         /* Interleave the new revision parent(s). */
5722         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5723                 push_rev_graph(graph->next, graph->parents->rev[i]);
5725         /* Lastly, put any remaining revisions. */
5726         for (i = graph->pos + 1; i < graph->size; i++)
5727                 push_rev_graph(graph->next, graph->rev[i]);
5730 static void
5731 update_rev_graph(struct view *view, struct rev_graph *graph)
5733         /* If this is the finalizing update ... */
5734         if (graph->commit)
5735                 prepare_rev_graph(graph);
5737         /* Graph visualization needs a one rev look-ahead,
5738          * so the first update doesn't visualize anything. */
5739         if (!graph->prev->commit)
5740                 return;
5742         if (view->lines > 2)
5743                 view->line[view->lines - 3].dirty = 1;
5744         if (view->lines > 1)
5745                 view->line[view->lines - 2].dirty = 1;
5746         draw_rev_graph(graph->prev);
5747         done_rev_graph(graph->prev->prev);
5751 /*
5752  * Main view backend
5753  */
5755 static const char *main_argv[SIZEOF_ARG] = {
5756         "git", "log", "--no-color", "--pretty=raw", "--parents",
5757                       "--topo-order", "%(head)", NULL
5758 };
5760 static bool
5761 main_draw(struct view *view, struct line *line, unsigned int lineno)
5763         struct commit *commit = line->data;
5765         if (!*commit->author)
5766                 return FALSE;
5768         if (opt_date && draw_date(view, &commit->time))
5769                 return TRUE;
5771         if (opt_author && draw_author(view, commit->author))
5772                 return TRUE;
5774         if (opt_rev_graph && commit->graph_size &&
5775             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5776                 return TRUE;
5778         if (opt_show_refs && commit->refs) {
5779                 size_t i = 0;
5781                 do {
5782                         enum line_type type;
5784                         if (commit->refs[i]->head)
5785                                 type = LINE_MAIN_HEAD;
5786                         else if (commit->refs[i]->ltag)
5787                                 type = LINE_MAIN_LOCAL_TAG;
5788                         else if (commit->refs[i]->tag)
5789                                 type = LINE_MAIN_TAG;
5790                         else if (commit->refs[i]->tracked)
5791                                 type = LINE_MAIN_TRACKED;
5792                         else if (commit->refs[i]->remote)
5793                                 type = LINE_MAIN_REMOTE;
5794                         else
5795                                 type = LINE_MAIN_REF;
5797                         if (draw_text(view, type, "[", TRUE) ||
5798                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5799                             draw_text(view, type, "]", TRUE))
5800                                 return TRUE;
5802                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5803                                 return TRUE;
5804                 } while (commit->refs[i++]->next);
5805         }
5807         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5808         return TRUE;
5811 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5812 static bool
5813 main_read(struct view *view, char *line)
5815         static struct rev_graph *graph = graph_stacks;
5816         enum line_type type;
5817         struct commit *commit;
5819         if (!line) {
5820                 int i;
5822                 if (!view->lines && !view->parent)
5823                         die("No revisions match the given arguments.");
5824                 if (view->lines > 0) {
5825                         commit = view->line[view->lines - 1].data;
5826                         view->line[view->lines - 1].dirty = 1;
5827                         if (!*commit->author) {
5828                                 view->lines--;
5829                                 free(commit);
5830                                 graph->commit = NULL;
5831                         }
5832                 }
5833                 update_rev_graph(view, graph);
5835                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5836                         clear_rev_graph(&graph_stacks[i]);
5837                 return TRUE;
5838         }
5840         type = get_line_type(line);
5841         if (type == LINE_COMMIT) {
5842                 commit = calloc(1, sizeof(struct commit));
5843                 if (!commit)
5844                         return FALSE;
5846                 line += STRING_SIZE("commit ");
5847                 if (*line == '-') {
5848                         graph->boundary = 1;
5849                         line++;
5850                 }
5852                 string_copy_rev(commit->id, line);
5853                 commit->refs = get_refs(commit->id);
5854                 graph->commit = commit;
5855                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5857                 while ((line = strchr(line, ' '))) {
5858                         line++;
5859                         push_rev_graph(graph->parents, line);
5860                         commit->has_parents = TRUE;
5861                 }
5862                 return TRUE;
5863         }
5865         if (!view->lines)
5866                 return TRUE;
5867         commit = view->line[view->lines - 1].data;
5869         switch (type) {
5870         case LINE_PARENT:
5871                 if (commit->has_parents)
5872                         break;
5873                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5874                 break;
5876         case LINE_AUTHOR:
5877                 parse_author_line(line + STRING_SIZE("author "),
5878                                   commit->author, sizeof(commit->author),
5879                                   &commit->time);
5880                 update_rev_graph(view, graph);
5881                 graph = graph->next;
5882                 break;
5884         default:
5885                 /* Fill in the commit title if it has not already been set. */
5886                 if (commit->title[0])
5887                         break;
5889                 /* Require titles to start with a non-space character at the
5890                  * offset used by git log. */
5891                 if (strncmp(line, "    ", 4))
5892                         break;
5893                 line += 4;
5894                 /* Well, if the title starts with a whitespace character,
5895                  * try to be forgiving.  Otherwise we end up with no title. */
5896                 while (isspace(*line))
5897                         line++;
5898                 if (*line == '\0')
5899                         break;
5900                 /* FIXME: More graceful handling of titles; append "..." to
5901                  * shortened titles, etc. */
5903                 string_expand(commit->title, sizeof(commit->title), line, 1);
5904                 view->line[view->lines - 1].dirty = 1;
5905         }
5907         return TRUE;
5910 static enum request
5911 main_request(struct view *view, enum request request, struct line *line)
5913         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5915         switch (request) {
5916         case REQ_ENTER:
5917                 open_view(view, REQ_VIEW_DIFF, flags);
5918                 break;
5919         case REQ_REFRESH:
5920                 load_refs();
5921                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5922                 break;
5923         default:
5924                 return request;
5925         }
5927         return REQ_NONE;
5930 static bool
5931 grep_refs(struct ref **refs, regex_t *regex)
5933         regmatch_t pmatch;
5934         size_t i = 0;
5936         if (!refs)
5937                 return FALSE;
5938         do {
5939                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5940                         return TRUE;
5941         } while (refs[i++]->next);
5943         return FALSE;
5946 static bool
5947 main_grep(struct view *view, struct line *line)
5949         struct commit *commit = line->data;
5950         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5951         char buf[DATE_COLS + 1];
5952         regmatch_t pmatch;
5954         for (state = S_TITLE; state < S_END; state++) {
5955                 char *text;
5957                 switch (state) {
5958                 case S_TITLE:   text = commit->title;   break;
5959                 case S_AUTHOR:
5960                         if (!opt_author)
5961                                 continue;
5962                         text = commit->author;
5963                         break;
5964                 case S_DATE:
5965                         if (!opt_date)
5966                                 continue;
5967                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5968                                 continue;
5969                         text = buf;
5970                         break;
5971                 case S_REFS:
5972                         if (!opt_show_refs)
5973                                 continue;
5974                         if (grep_refs(commit->refs, view->regex) == TRUE)
5975                                 return TRUE;
5976                         continue;
5977                 default:
5978                         return FALSE;
5979                 }
5981                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5982                         return TRUE;
5983         }
5985         return FALSE;
5988 static void
5989 main_select(struct view *view, struct line *line)
5991         struct commit *commit = line->data;
5993         string_copy_rev(view->ref, commit->id);
5994         string_copy_rev(ref_commit, view->ref);
5997 static struct view_ops main_ops = {
5998         "commit",
5999         main_argv,
6000         NULL,
6001         main_read,
6002         main_draw,
6003         main_request,
6004         main_grep,
6005         main_select,
6006 };
6009 /*
6010  * Unicode / UTF-8 handling
6011  *
6012  * NOTE: Much of the following code for dealing with Unicode is derived from
6013  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6014  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6015  */
6017 static inline int
6018 unicode_width(unsigned long c)
6020         if (c >= 0x1100 &&
6021            (c <= 0x115f                         /* Hangul Jamo */
6022             || c == 0x2329
6023             || c == 0x232a
6024             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
6025                                                 /* CJK ... Yi */
6026             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
6027             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
6028             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
6029             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
6030             || (c >= 0xffe0  && c <= 0xffe6)
6031             || (c >= 0x20000 && c <= 0x2fffd)
6032             || (c >= 0x30000 && c <= 0x3fffd)))
6033                 return 2;
6035         if (c == '\t')
6036                 return opt_tab_size;
6038         return 1;
6041 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6042  * Illegal bytes are set one. */
6043 static const unsigned char utf8_bytes[256] = {
6044         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,
6045         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,
6046         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,
6047         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,
6048         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,
6049         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6050         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,
6051         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,
6052 };
6054 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6055 static inline unsigned long
6056 utf8_to_unicode(const char *string, size_t length)
6058         unsigned long unicode;
6060         switch (length) {
6061         case 1:
6062                 unicode  =   string[0];
6063                 break;
6064         case 2:
6065                 unicode  =  (string[0] & 0x1f) << 6;
6066                 unicode +=  (string[1] & 0x3f);
6067                 break;
6068         case 3:
6069                 unicode  =  (string[0] & 0x0f) << 12;
6070                 unicode += ((string[1] & 0x3f) << 6);
6071                 unicode +=  (string[2] & 0x3f);
6072                 break;
6073         case 4:
6074                 unicode  =  (string[0] & 0x0f) << 18;
6075                 unicode += ((string[1] & 0x3f) << 12);
6076                 unicode += ((string[2] & 0x3f) << 6);
6077                 unicode +=  (string[3] & 0x3f);
6078                 break;
6079         case 5:
6080                 unicode  =  (string[0] & 0x0f) << 24;
6081                 unicode += ((string[1] & 0x3f) << 18);
6082                 unicode += ((string[2] & 0x3f) << 12);
6083                 unicode += ((string[3] & 0x3f) << 6);
6084                 unicode +=  (string[4] & 0x3f);
6085                 break;
6086         case 6:
6087                 unicode  =  (string[0] & 0x01) << 30;
6088                 unicode += ((string[1] & 0x3f) << 24);
6089                 unicode += ((string[2] & 0x3f) << 18);
6090                 unicode += ((string[3] & 0x3f) << 12);
6091                 unicode += ((string[4] & 0x3f) << 6);
6092                 unicode +=  (string[5] & 0x3f);
6093                 break;
6094         default:
6095                 die("Invalid Unicode length");
6096         }
6098         /* Invalid characters could return the special 0xfffd value but NUL
6099          * should be just as good. */
6100         return unicode > 0xffff ? 0 : unicode;
6103 /* Calculates how much of string can be shown within the given maximum width
6104  * and sets trimmed parameter to non-zero value if all of string could not be
6105  * shown. If the reserve flag is TRUE, it will reserve at least one
6106  * trailing character, which can be useful when drawing a delimiter.
6107  *
6108  * Returns the number of bytes to output from string to satisfy max_width. */
6109 static size_t
6110 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6112         const char *string = *start;
6113         const char *end = strchr(string, '\0');
6114         unsigned char last_bytes = 0;
6115         size_t last_ucwidth = 0;
6117         *width = 0;
6118         *trimmed = 0;
6120         while (string < end) {
6121                 int c = *(unsigned char *) string;
6122                 unsigned char bytes = utf8_bytes[c];
6123                 size_t ucwidth;
6124                 unsigned long unicode;
6126                 if (string + bytes > end)
6127                         break;
6129                 /* Change representation to figure out whether
6130                  * it is a single- or double-width character. */
6132                 unicode = utf8_to_unicode(string, bytes);
6133                 /* FIXME: Graceful handling of invalid Unicode character. */
6134                 if (!unicode)
6135                         break;
6137                 ucwidth = unicode_width(unicode);
6138                 if (skip > 0) {
6139                         skip -= ucwidth <= skip ? ucwidth : skip;
6140                         *start += bytes;
6141                 }
6142                 *width  += ucwidth;
6143                 if (*width > max_width) {
6144                         *trimmed = 1;
6145                         *width -= ucwidth;
6146                         if (reserve && *width == max_width) {
6147                                 string -= last_bytes;
6148                                 *width -= last_ucwidth;
6149                         }
6150                         break;
6151                 }
6153                 string  += bytes;
6154                 last_bytes = ucwidth ? bytes : 0;
6155                 last_ucwidth = ucwidth;
6156         }
6158         return string - *start;
6162 /*
6163  * Status management
6164  */
6166 /* Whether or not the curses interface has been initialized. */
6167 static bool cursed = FALSE;
6169 /* Terminal hacks and workarounds. */
6170 static bool use_scroll_redrawwin;
6171 static bool use_scroll_status_wclear;
6173 /* The status window is used for polling keystrokes. */
6174 static WINDOW *status_win;
6176 /* Reading from the prompt? */
6177 static bool input_mode = FALSE;
6179 static bool status_empty = FALSE;
6181 /* Update status and title window. */
6182 static void
6183 report(const char *msg, ...)
6185         struct view *view = display[current_view];
6187         if (input_mode)
6188                 return;
6190         if (!view) {
6191                 char buf[SIZEOF_STR];
6192                 va_list args;
6194                 va_start(args, msg);
6195                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6196                         buf[sizeof(buf) - 1] = 0;
6197                         buf[sizeof(buf) - 2] = '.';
6198                         buf[sizeof(buf) - 3] = '.';
6199                         buf[sizeof(buf) - 4] = '.';
6200                 }
6201                 va_end(args);
6202                 die("%s", buf);
6203         }
6205         if (!status_empty || *msg) {
6206                 va_list args;
6208                 va_start(args, msg);
6210                 wmove(status_win, 0, 0);
6211                 if (view->has_scrolled && use_scroll_status_wclear)
6212                         wclear(status_win);
6213                 if (*msg) {
6214                         vwprintw(status_win, msg, args);
6215                         status_empty = FALSE;
6216                 } else {
6217                         status_empty = TRUE;
6218                 }
6219                 wclrtoeol(status_win);
6220                 wnoutrefresh(status_win);
6222                 va_end(args);
6223         }
6225         update_view_title(view);
6228 /* Controls when nodelay should be in effect when polling user input. */
6229 static void
6230 set_nonblocking_input(bool loading)
6232         static unsigned int loading_views;
6234         if ((loading == FALSE && loading_views-- == 1) ||
6235             (loading == TRUE  && loading_views++ == 0))
6236                 nodelay(status_win, loading);
6239 static void
6240 init_display(void)
6242         const char *term;
6243         int x, y;
6245         /* Initialize the curses library */
6246         if (isatty(STDIN_FILENO)) {
6247                 cursed = !!initscr();
6248                 opt_tty = stdin;
6249         } else {
6250                 /* Leave stdin and stdout alone when acting as a pager. */
6251                 opt_tty = fopen("/dev/tty", "r+");
6252                 if (!opt_tty)
6253                         die("Failed to open /dev/tty");
6254                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6255         }
6257         if (!cursed)
6258                 die("Failed to initialize curses");
6260         nonl();         /* Disable conversion and detect newlines from input. */
6261         cbreak();       /* Take input chars one at a time, no wait for \n */
6262         noecho();       /* Don't echo input */
6263         leaveok(stdscr, FALSE);
6265         if (has_colors())
6266                 init_colors();
6268         getmaxyx(stdscr, y, x);
6269         status_win = newwin(1, 0, y - 1, 0);
6270         if (!status_win)
6271                 die("Failed to create status window");
6273         /* Enable keyboard mapping */
6274         keypad(status_win, TRUE);
6275         wbkgdset(status_win, get_line_attr(LINE_STATUS));
6277         TABSIZE = opt_tab_size;
6278         if (opt_line_graphics) {
6279                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6280         }
6282         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6283         if (term && !strcmp(term, "gnome-terminal")) {
6284                 /* In the gnome-terminal-emulator, the message from
6285                  * scrolling up one line when impossible followed by
6286                  * scrolling down one line causes corruption of the
6287                  * status line. This is fixed by calling wclear. */
6288                 use_scroll_status_wclear = TRUE;
6289                 use_scroll_redrawwin = FALSE;
6291         } else if (term && !strcmp(term, "xrvt-xpm")) {
6292                 /* No problems with full optimizations in xrvt-(unicode)
6293                  * and aterm. */
6294                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6296         } else {
6297                 /* When scrolling in (u)xterm the last line in the
6298                  * scrolling direction will update slowly. */
6299                 use_scroll_redrawwin = TRUE;
6300                 use_scroll_status_wclear = FALSE;
6301         }
6304 static int
6305 get_input(int prompt_position)
6307         struct view *view;
6308         int i, key, cursor_y, cursor_x;
6310         if (prompt_position)
6311                 input_mode = TRUE;
6313         while (TRUE) {
6314                 foreach_view (view, i) {
6315                         update_view(view);
6316                         if (view_is_displayed(view) && view->has_scrolled &&
6317                             use_scroll_redrawwin)
6318                                 redrawwin(view->win);
6319                         view->has_scrolled = FALSE;
6320                 }
6322                 /* Update the cursor position. */
6323                 if (prompt_position) {
6324                         getbegyx(status_win, cursor_y, cursor_x);
6325                         cursor_x = prompt_position;
6326                 } else {
6327                         view = display[current_view];
6328                         getbegyx(view->win, cursor_y, cursor_x);
6329                         cursor_x = view->width - 1;
6330                         cursor_y += view->lineno - view->offset;
6331                 }
6332                 setsyx(cursor_y, cursor_x);
6334                 /* Refresh, accept single keystroke of input */
6335                 doupdate();
6336                 key = wgetch(status_win);
6338                 /* wgetch() with nodelay() enabled returns ERR when
6339                  * there's no input. */
6340                 if (key == ERR) {
6342                 } else if (key == KEY_RESIZE) {
6343                         int height, width;
6345                         getmaxyx(stdscr, height, width);
6347                         wresize(status_win, 1, width);
6348                         mvwin(status_win, height - 1, 0);
6349                         wnoutrefresh(status_win);
6350                         resize_display();
6351                         redraw_display(TRUE);
6353                 } else {
6354                         input_mode = FALSE;
6355                         return key;
6356                 }
6357         }
6360 static char *
6361 prompt_input(const char *prompt, input_handler handler, void *data)
6363         enum input_status status = INPUT_OK;
6364         static char buf[SIZEOF_STR];
6365         size_t pos = 0;
6367         buf[pos] = 0;
6369         while (status == INPUT_OK || status == INPUT_SKIP) {
6370                 int key;
6372                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6373                 wclrtoeol(status_win);
6375                 key = get_input(pos + 1);
6376                 switch (key) {
6377                 case KEY_RETURN:
6378                 case KEY_ENTER:
6379                 case '\n':
6380                         status = pos ? INPUT_STOP : INPUT_CANCEL;
6381                         break;
6383                 case KEY_BACKSPACE:
6384                         if (pos > 0)
6385                                 buf[--pos] = 0;
6386                         else
6387                                 status = INPUT_CANCEL;
6388                         break;
6390                 case KEY_ESC:
6391                         status = INPUT_CANCEL;
6392                         break;
6394                 default:
6395                         if (pos >= sizeof(buf)) {
6396                                 report("Input string too long");
6397                                 return NULL;
6398                         }
6400                         status = handler(data, buf, key);
6401                         if (status == INPUT_OK)
6402                                 buf[pos++] = (char) key;
6403                 }
6404         }
6406         /* Clear the status window */
6407         status_empty = FALSE;
6408         report("");
6410         if (status == INPUT_CANCEL)
6411                 return NULL;
6413         buf[pos++] = 0;
6415         return buf;
6418 static enum input_status
6419 prompt_yesno_handler(void *data, char *buf, int c)
6421         if (c == 'y' || c == 'Y')
6422                 return INPUT_STOP;
6423         if (c == 'n' || c == 'N')
6424                 return INPUT_CANCEL;
6425         return INPUT_SKIP;
6428 static bool
6429 prompt_yesno(const char *prompt)
6431         char prompt2[SIZEOF_STR];
6433         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6434                 return FALSE;
6436         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6439 static enum input_status
6440 read_prompt_handler(void *data, char *buf, int c)
6442         return isprint(c) ? INPUT_OK : INPUT_SKIP;
6445 static char *
6446 read_prompt(const char *prompt)
6448         return prompt_input(prompt, read_prompt_handler, NULL);
6451 /*
6452  * Repository properties
6453  */
6455 static struct ref *refs = NULL;
6456 static size_t refs_alloc = 0;
6457 static size_t refs_size = 0;
6459 /* Id <-> ref store */
6460 static struct ref ***id_refs = NULL;
6461 static size_t id_refs_alloc = 0;
6462 static size_t id_refs_size = 0;
6464 static int
6465 compare_refs(const void *ref1_, const void *ref2_)
6467         const struct ref *ref1 = *(const struct ref **)ref1_;
6468         const struct ref *ref2 = *(const struct ref **)ref2_;
6470         if (ref1->tag != ref2->tag)
6471                 return ref2->tag - ref1->tag;
6472         if (ref1->ltag != ref2->ltag)
6473                 return ref2->ltag - ref2->ltag;
6474         if (ref1->head != ref2->head)
6475                 return ref2->head - ref1->head;
6476         if (ref1->tracked != ref2->tracked)
6477                 return ref2->tracked - ref1->tracked;
6478         if (ref1->remote != ref2->remote)
6479                 return ref2->remote - ref1->remote;
6480         return strcmp(ref1->name, ref2->name);
6483 static struct ref **
6484 get_refs(const char *id)
6486         struct ref ***tmp_id_refs;
6487         struct ref **ref_list = NULL;
6488         size_t ref_list_alloc = 0;
6489         size_t ref_list_size = 0;
6490         size_t i;
6492         for (i = 0; i < id_refs_size; i++)
6493                 if (!strcmp(id, id_refs[i][0]->id))
6494                         return id_refs[i];
6496         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6497                                     sizeof(*id_refs));
6498         if (!tmp_id_refs)
6499                 return NULL;
6501         id_refs = tmp_id_refs;
6503         for (i = 0; i < refs_size; i++) {
6504                 struct ref **tmp;
6506                 if (strcmp(id, refs[i].id))
6507                         continue;
6509                 tmp = realloc_items(ref_list, &ref_list_alloc,
6510                                     ref_list_size + 1, sizeof(*ref_list));
6511                 if (!tmp) {
6512                         if (ref_list)
6513                                 free(ref_list);
6514                         return NULL;
6515                 }
6517                 ref_list = tmp;
6518                 ref_list[ref_list_size] = &refs[i];
6519                 /* XXX: The properties of the commit chains ensures that we can
6520                  * safely modify the shared ref. The repo references will
6521                  * always be similar for the same id. */
6522                 ref_list[ref_list_size]->next = 1;
6524                 ref_list_size++;
6525         }
6527         if (ref_list) {
6528                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6529                 ref_list[ref_list_size - 1]->next = 0;
6530                 id_refs[id_refs_size++] = ref_list;
6531         }
6533         return ref_list;
6536 static int
6537 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6539         struct ref *ref;
6540         bool tag = FALSE;
6541         bool ltag = FALSE;
6542         bool remote = FALSE;
6543         bool tracked = FALSE;
6544         bool check_replace = FALSE;
6545         bool head = FALSE;
6547         if (!prefixcmp(name, "refs/tags/")) {
6548                 if (!suffixcmp(name, namelen, "^{}")) {
6549                         namelen -= 3;
6550                         name[namelen] = 0;
6551                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6552                                 check_replace = TRUE;
6553                 } else {
6554                         ltag = TRUE;
6555                 }
6557                 tag = TRUE;
6558                 namelen -= STRING_SIZE("refs/tags/");
6559                 name    += STRING_SIZE("refs/tags/");
6561         } else if (!prefixcmp(name, "refs/remotes/")) {
6562                 remote = TRUE;
6563                 namelen -= STRING_SIZE("refs/remotes/");
6564                 name    += STRING_SIZE("refs/remotes/");
6565                 tracked  = !strcmp(opt_remote, name);
6567         } else if (!prefixcmp(name, "refs/heads/")) {
6568                 namelen -= STRING_SIZE("refs/heads/");
6569                 name    += STRING_SIZE("refs/heads/");
6570                 head     = !strncmp(opt_head, name, namelen);
6572         } else if (!strcmp(name, "HEAD")) {
6573                 string_ncopy(opt_head_rev, id, idlen);
6574                 return OK;
6575         }
6577         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6578                 /* it's an annotated tag, replace the previous SHA1 with the
6579                  * resolved commit id; relies on the fact git-ls-remote lists
6580                  * the commit id of an annotated tag right before the commit id
6581                  * it points to. */
6582                 refs[refs_size - 1].ltag = ltag;
6583                 string_copy_rev(refs[refs_size - 1].id, id);
6585                 return OK;
6586         }
6587         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6588         if (!refs)
6589                 return ERR;
6591         ref = &refs[refs_size++];
6592         ref->name = malloc(namelen + 1);
6593         if (!ref->name)
6594                 return ERR;
6596         strncpy(ref->name, name, namelen);
6597         ref->name[namelen] = 0;
6598         ref->head = head;
6599         ref->tag = tag;
6600         ref->ltag = ltag;
6601         ref->remote = remote;
6602         ref->tracked = tracked;
6603         string_copy_rev(ref->id, id);
6605         return OK;
6608 static int
6609 load_refs(void)
6611         static const char *ls_remote_argv[SIZEOF_ARG] = {
6612                 "git", "ls-remote", ".", NULL
6613         };
6614         static bool init = FALSE;
6616         if (!init) {
6617                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6618                 init = TRUE;
6619         }
6621         if (!*opt_git_dir)
6622                 return OK;
6624         while (refs_size > 0)
6625                 free(refs[--refs_size].name);
6626         while (id_refs_size > 0)
6627                 free(id_refs[--id_refs_size]);
6629         return run_io_load(ls_remote_argv, "\t", read_ref);
6632 static void
6633 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6635         const char *argv[SIZEOF_ARG] = { name, "=" };
6636         int argc = 1 + (cmd == option_set_command);
6637         int error = ERR;
6639         if (!argv_from_string(argv, &argc, value))
6640                 config_msg = "Too many option arguments";
6641         else
6642                 error = cmd(argc, argv);
6644         if (error == ERR)
6645                 warn("Option 'tig.%s': %s", name, config_msg);
6648 static int
6649 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6651         if (!strcmp(name, "i18n.commitencoding"))
6652                 string_ncopy(opt_encoding, value, valuelen);
6654         if (!strcmp(name, "core.editor"))
6655                 string_ncopy(opt_editor, value, valuelen);
6657         if (!prefixcmp(name, "tig.color."))
6658                 set_repo_config_option(name + 10, value, option_color_command);
6660         else if (!prefixcmp(name, "tig.bind."))
6661                 set_repo_config_option(name + 9, value, option_bind_command);
6663         else if (!prefixcmp(name, "tig."))
6664                 set_repo_config_option(name + 4, value, option_set_command);
6666         /* branch.<head>.remote */
6667         if (*opt_head &&
6668             !strncmp(name, "branch.", 7) &&
6669             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6670             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6671                 string_ncopy(opt_remote, value, valuelen);
6673         if (*opt_head && *opt_remote &&
6674             !strncmp(name, "branch.", 7) &&
6675             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6676             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6677                 size_t from = strlen(opt_remote);
6679                 if (!prefixcmp(value, "refs/heads/")) {
6680                         value += STRING_SIZE("refs/heads/");
6681                         valuelen -= STRING_SIZE("refs/heads/");
6682                 }
6684                 if (!string_format_from(opt_remote, &from, "/%s", value))
6685                         opt_remote[0] = 0;
6686         }
6688         return OK;
6691 static int
6692 load_git_config(void)
6694         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6696         return run_io_load(config_list_argv, "=", read_repo_config_option);
6699 static int
6700 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6702         if (!opt_git_dir[0]) {
6703                 string_ncopy(opt_git_dir, name, namelen);
6705         } else if (opt_is_inside_work_tree == -1) {
6706                 /* This can be 3 different values depending on the
6707                  * version of git being used. If git-rev-parse does not
6708                  * understand --is-inside-work-tree it will simply echo
6709                  * the option else either "true" or "false" is printed.
6710                  * Default to true for the unknown case. */
6711                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6713         } else if (*name == '.') {
6714                 string_ncopy(opt_cdup, name, namelen);
6716         } else {
6717                 string_ncopy(opt_prefix, name, namelen);
6718         }
6720         return OK;
6723 static int
6724 load_repo_info(void)
6726         const char *head_argv[] = {
6727                 "git", "symbolic-ref", "HEAD", NULL
6728         };
6729         const char *rev_parse_argv[] = {
6730                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6731                         "--show-cdup", "--show-prefix", NULL
6732         };
6734         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6735                 chomp_string(opt_head);
6736                 if (!prefixcmp(opt_head, "refs/heads/")) {
6737                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6739                         memmove(opt_head, offset, strlen(offset) + 1);
6740                 }
6741         }
6743         return run_io_load(rev_parse_argv, "=", read_repo_info);
6747 /*
6748  * Main
6749  */
6751 static const char usage[] =
6752 "tig " TIG_VERSION " (" __DATE__ ")\n"
6753 "\n"
6754 "Usage: tig        [options] [revs] [--] [paths]\n"
6755 "   or: tig show   [options] [revs] [--] [paths]\n"
6756 "   or: tig blame  [rev] path\n"
6757 "   or: tig status\n"
6758 "   or: tig <      [git command output]\n"
6759 "\n"
6760 "Options:\n"
6761 "  -v, --version   Show version and exit\n"
6762 "  -h, --help      Show help message and exit";
6764 static void __NORETURN
6765 quit(int sig)
6767         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6768         if (cursed)
6769                 endwin();
6770         exit(0);
6773 static void __NORETURN
6774 die(const char *err, ...)
6776         va_list args;
6778         endwin();
6780         va_start(args, err);
6781         fputs("tig: ", stderr);
6782         vfprintf(stderr, err, args);
6783         fputs("\n", stderr);
6784         va_end(args);
6786         exit(1);
6789 static void
6790 warn(const char *msg, ...)
6792         va_list args;
6794         va_start(args, msg);
6795         fputs("tig warning: ", stderr);
6796         vfprintf(stderr, msg, args);
6797         fputs("\n", stderr);
6798         va_end(args);
6801 static enum request
6802 parse_options(int argc, const char *argv[])
6804         enum request request = REQ_VIEW_MAIN;
6805         const char *subcommand;
6806         bool seen_dashdash = FALSE;
6807         /* XXX: This is vulnerable to the user overriding options
6808          * required for the main view parser. */
6809         const char *custom_argv[SIZEOF_ARG] = {
6810                 "git", "log", "--no-color", "--pretty=raw", "--parents",
6811                         "--topo-order", NULL
6812         };
6813         int i, j = 6;
6815         if (!isatty(STDIN_FILENO)) {
6816                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6817                 return REQ_VIEW_PAGER;
6818         }
6820         if (argc <= 1)
6821                 return REQ_NONE;
6823         subcommand = argv[1];
6824         if (!strcmp(subcommand, "status")) {
6825                 if (argc > 2)
6826                         warn("ignoring arguments after `%s'", subcommand);
6827                 return REQ_VIEW_STATUS;
6829         } else if (!strcmp(subcommand, "blame")) {
6830                 if (argc <= 2 || argc > 4)
6831                         die("invalid number of options to blame\n\n%s", usage);
6833                 i = 2;
6834                 if (argc == 4) {
6835                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6836                         i++;
6837                 }
6839                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6840                 return REQ_VIEW_BLAME;
6842         } else if (!strcmp(subcommand, "show")) {
6843                 request = REQ_VIEW_DIFF;
6845         } else {
6846                 subcommand = NULL;
6847         }
6849         if (subcommand) {
6850                 custom_argv[1] = subcommand;
6851                 j = 2;
6852         }
6854         for (i = 1 + !!subcommand; i < argc; i++) {
6855                 const char *opt = argv[i];
6857                 if (seen_dashdash || !strcmp(opt, "--")) {
6858                         seen_dashdash = TRUE;
6860                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6861                         printf("tig version %s\n", TIG_VERSION);
6862                         quit(0);
6864                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6865                         printf("%s\n", usage);
6866                         quit(0);
6867                 }
6869                 custom_argv[j++] = opt;
6870                 if (j >= ARRAY_SIZE(custom_argv))
6871                         die("command too long");
6872         }
6874         if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))                                                                        
6875                 die("Failed to format arguments"); 
6877         return request;
6880 int
6881 main(int argc, const char *argv[])
6883         enum request request = parse_options(argc, argv);
6884         struct view *view;
6885         size_t i;
6887         signal(SIGINT, quit);
6889         if (setlocale(LC_ALL, "")) {
6890                 char *codeset = nl_langinfo(CODESET);
6892                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6893         }
6895         if (load_repo_info() == ERR)
6896                 die("Failed to load repo info.");
6898         if (load_options() == ERR)
6899                 die("Failed to load user config.");
6901         if (load_git_config() == ERR)
6902                 die("Failed to load repo config.");
6904         /* Require a git repository unless when running in pager mode. */
6905         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6906                 die("Not a git repository");
6908         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6909                 opt_utf8 = FALSE;
6911         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6912                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6913                 if (opt_iconv == ICONV_NONE)
6914                         die("Failed to initialize character set conversion");
6915         }
6917         if (load_refs() == ERR)
6918                 die("Failed to load refs.");
6920         foreach_view (view, i)
6921                 argv_from_env(view->ops->argv, view->cmd_env);
6923         init_display();
6925         if (request != REQ_NONE)
6926                 open_view(NULL, request, OPEN_PREPARED);
6927         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6929         while (view_driver(display[current_view], request)) {
6930                 int key = get_input(0);
6932                 view = display[current_view];
6933                 request = get_keybinding(view->keymap, key);
6935                 /* Some low-level request handling. This keeps access to
6936                  * status_win restricted. */
6937                 switch (request) {
6938                 case REQ_PROMPT:
6939                 {
6940                         char *cmd = read_prompt(":");
6942                         if (cmd && isdigit(*cmd)) {
6943                                 int lineno = view->lineno + 1;
6945                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
6946                                         select_view_line(view, lineno - 1);
6947                                         report("");
6948                                 } else {
6949                                         report("Unable to parse '%s' as a line number", cmd);
6950                                 }
6952                         } else if (cmd) {
6953                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6954                                 const char *argv[SIZEOF_ARG] = { "git" };
6955                                 int argc = 1;
6957                                 /* When running random commands, initially show the
6958                                  * command in the title. However, it maybe later be
6959                                  * overwritten if a commit line is selected. */
6960                                 string_ncopy(next->ref, cmd, strlen(cmd));
6962                                 if (!argv_from_string(argv, &argc, cmd)) {
6963                                         report("Too many arguments");
6964                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6965                                         report("Failed to format command");
6966                                 } else {
6967                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6968                                 }
6969                         }
6971                         request = REQ_NONE;
6972                         break;
6973                 }
6974                 case REQ_SEARCH:
6975                 case REQ_SEARCH_BACK:
6976                 {
6977                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6978                         char *search = read_prompt(prompt);
6980                         if (search)
6981                                 string_ncopy(opt_search, search, strlen(search));
6982                         else if (*opt_search)
6983                                 request = request == REQ_SEARCH ?
6984                                         REQ_FIND_NEXT :
6985                                         REQ_FIND_PREV;
6986                         else
6987                                 request = REQ_NONE;
6988                         break;
6989                 }
6990                 default:
6991                         break;
6992                 }
6993         }
6995         quit(0);
6997         return 0;