begin_update: simplify control flow
[tig.git] / tig.c
blobaf8e19695425162eed687d2bdf14330137b4bdb3
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
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.
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.
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 <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
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))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS 8
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
141 static int load_refs(void);
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. */
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
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 struct menu_item {
164 int hotkey;
165 const char *text;
166 void *data;
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181 type *tmp = *mem; \
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185 if (tmp) \
186 *mem = tmp; \
189 return tmp; \
193 * String helpers
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223 size_t size, pos;
225 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226 if (src[pos] == '\t') {
227 size_t expanded = tabsize - (size % tabsize);
229 if (expanded + size >= dstlen - 1)
230 expanded = dstlen - size - 1;
231 memcpy(dst + size, " ", expanded);
232 size += expanded;
233 } else {
234 dst[size++] = src[pos];
238 dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244 int namelen;
246 while (isspace(*name))
247 name++;
249 namelen = strlen(name) - 1;
250 while (namelen > 0 && isspace(name[namelen]))
251 name[namelen--] = 0;
253 return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259 va_list args;
260 size_t pos = bufpos ? *bufpos : 0;
262 va_start(args, fmt);
263 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264 va_end(args);
266 if (bufpos)
267 *bufpos = pos;
269 return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281 size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i = 0; i < len; i++) {
287 if (toupper(str1[i]) == toupper(str2[i]))
288 continue;
290 if (string_enum_sep(str1[i]) &&
291 string_enum_sep(str2[i]))
292 continue;
294 return str1[i] - str2[i];
297 return 0;
300 struct enum_map {
301 const char *name;
302 int namelen;
303 int value;
306 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 static bool
309 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
311 size_t namelen = strlen(name);
312 int i;
314 for (i = 0; i < map_size; i++)
315 if (namelen == map[i].namelen &&
316 !string_enum_compare(name, map[i].name, namelen)) {
317 *value = map[i].value;
318 return TRUE;
321 return FALSE;
324 #define map_enum(attr, map, name) \
325 map_enum_do(map, ARRAY_SIZE(map), attr, name)
327 #define prefixcmp(str1, str2) \
328 strncmp(str1, str2, STRING_SIZE(str2))
330 static inline int
331 suffixcmp(const char *str, int slen, const char *suffix)
333 size_t len = slen >= 0 ? slen : strlen(str);
334 size_t suffixlen = strlen(suffix);
336 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
341 * What value of "tz" was in effect back then at "time" in the
342 * local timezone?
344 static int local_tzoffset(time_t time)
346 time_t t, t_local;
347 struct tm tm;
348 int offset, eastwest;
350 t = time;
351 localtime_r(&t, &tm);
352 t_local = mktime(&tm);
354 if (t_local < t) {
355 eastwest = -1;
356 offset = t - t_local;
357 } else {
358 eastwest = 1;
359 offset = t_local - t;
361 offset /= 60; /* in minutes */
362 offset = (offset % 60) + ((offset / 60) * 100);
363 return offset * eastwest;
366 enum date {
367 DATE_NONE = 0,
368 DATE_DEFAULT,
369 DATE_RELATIVE,
370 DATE_SHORT
373 static char *
374 string_date(const time_t *time, enum date date)
376 static char buf[DATE_COLS + 1];
377 static const struct enum_map reldate[] = {
378 { "second", 1, 60 * 2 },
379 { "minute", 60, 60 * 60 * 2 },
380 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
381 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
382 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
383 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
385 struct tm tm;
387 if (date == DATE_RELATIVE) {
388 struct timeval now;
389 time_t date = *time + local_tzoffset(*time);
390 time_t seconds;
391 int i;
393 gettimeofday(&now, NULL);
394 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
395 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
396 if (seconds >= reldate[i].value)
397 continue;
399 seconds /= reldate[i].namelen;
400 if (!string_format(buf, "%ld %s%s %s",
401 seconds, reldate[i].name,
402 seconds > 1 ? "s" : "",
403 now.tv_sec >= date ? "ago" : "ahead"))
404 break;
405 return buf;
409 gmtime_r(time, &tm);
410 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
414 static bool
415 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
417 int valuelen;
419 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
420 bool advance = cmd[valuelen] != 0;
422 cmd[valuelen] = 0;
423 argv[(*argc)++] = chomp_string(cmd);
424 cmd = chomp_string(cmd + valuelen + advance);
427 if (*argc < SIZEOF_ARG)
428 argv[*argc] = NULL;
429 return *argc < SIZEOF_ARG;
432 static void
433 argv_from_env(const char **argv, const char *name)
435 char *env = argv ? getenv(name) : NULL;
436 int argc = 0;
438 if (env && *env)
439 env = strdup(env);
440 if (env && !argv_from_string(argv, &argc, env))
441 die("Too many arguments in the `%s` environment variable", name);
446 * Executing external commands.
449 enum io_type {
450 IO_FD, /* File descriptor based IO. */
451 IO_BG, /* Execute command in the background. */
452 IO_FG, /* Execute command with same std{in,out,err}. */
453 IO_RD, /* Read only fork+exec IO. */
454 IO_WR, /* Write only fork+exec IO. */
455 IO_AP, /* Append fork+exec output to file. */
458 struct io {
459 enum io_type type; /* The requested type of pipe. */
460 const char *dir; /* Directory from which to execute. */
461 pid_t pid; /* Pipe for reading or writing. */
462 int pipe; /* Pipe end for reading or writing. */
463 int error; /* Error status. */
464 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
465 char *buf; /* Read buffer. */
466 size_t bufalloc; /* Allocated buffer size. */
467 size_t bufsize; /* Buffer content size. */
468 char *bufpos; /* Current buffer position. */
469 unsigned int eof:1; /* Has end of file been reached. */
472 static void
473 reset_io(struct io *io)
475 io->pipe = -1;
476 io->pid = 0;
477 io->buf = io->bufpos = NULL;
478 io->bufalloc = io->bufsize = 0;
479 io->error = 0;
480 io->eof = 0;
483 static void
484 init_io(struct io *io, const char *dir, enum io_type type)
486 reset_io(io);
487 io->type = type;
488 io->dir = dir;
491 static bool
492 init_io_rd(struct io *io, const char *argv[], const char *dir,
493 enum format_flags flags)
495 init_io(io, dir, IO_RD);
496 return format_argv(io->argv, argv, flags);
499 static bool
500 io_open(struct io *io, const char *name)
502 init_io(io, NULL, IO_FD);
503 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
504 if (io->pipe == -1)
505 io->error = errno;
506 return io->pipe != -1;
509 static bool
510 kill_io(struct io *io)
512 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
515 static bool
516 done_io(struct io *io)
518 pid_t pid = io->pid;
520 if (io->pipe != -1)
521 close(io->pipe);
522 free(io->buf);
523 reset_io(io);
525 while (pid > 0) {
526 int status;
527 pid_t waiting = waitpid(pid, &status, 0);
529 if (waiting < 0) {
530 if (errno == EINTR)
531 continue;
532 report("waitpid failed (%s)", strerror(errno));
533 return FALSE;
536 return waiting == pid &&
537 !WIFSIGNALED(status) &&
538 WIFEXITED(status) &&
539 !WEXITSTATUS(status);
542 return TRUE;
545 static bool
546 start_io(struct io *io)
548 int pipefds[2] = { -1, -1 };
550 if (io->type == IO_FD)
551 return TRUE;
553 if ((io->type == IO_RD || io->type == IO_WR) &&
554 pipe(pipefds) < 0)
555 return FALSE;
556 else if (io->type == IO_AP)
557 pipefds[1] = io->pipe;
559 if ((io->pid = fork())) {
560 if (pipefds[!(io->type == IO_WR)] != -1)
561 close(pipefds[!(io->type == IO_WR)]);
562 if (io->pid != -1) {
563 io->pipe = pipefds[!!(io->type == IO_WR)];
564 return TRUE;
567 } else {
568 if (io->type != IO_FG) {
569 int devnull = open("/dev/null", O_RDWR);
570 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
571 int writefd = (io->type == IO_RD || io->type == IO_AP)
572 ? pipefds[1] : devnull;
574 dup2(readfd, STDIN_FILENO);
575 dup2(writefd, STDOUT_FILENO);
576 dup2(devnull, STDERR_FILENO);
578 close(devnull);
579 if (pipefds[0] != -1)
580 close(pipefds[0]);
581 if (pipefds[1] != -1)
582 close(pipefds[1]);
585 if (io->dir && *io->dir && chdir(io->dir) == -1)
586 die("Failed to change directory: %s", strerror(errno));
588 execvp(io->argv[0], (char *const*) io->argv);
589 die("Failed to execute program: %s", strerror(errno));
592 if (pipefds[!!(io->type == IO_WR)] != -1)
593 close(pipefds[!!(io->type == IO_WR)]);
594 return FALSE;
597 static bool
598 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
600 init_io(io, dir, type);
601 if (!format_argv(io->argv, argv, FORMAT_NONE))
602 return FALSE;
603 return start_io(io);
606 static int
607 run_io_do(struct io *io)
609 return start_io(io) && done_io(io);
612 static int
613 run_io_bg(const char **argv)
615 struct io io = {};
617 init_io(&io, NULL, IO_BG);
618 if (!format_argv(io.argv, argv, FORMAT_NONE))
619 return FALSE;
620 return run_io_do(&io);
623 static bool
624 run_io_fg(const char **argv, const char *dir)
626 struct io io = {};
628 init_io(&io, dir, IO_FG);
629 if (!format_argv(io.argv, argv, FORMAT_NONE))
630 return FALSE;
631 return run_io_do(&io);
634 static bool
635 run_io_append(const char **argv, enum format_flags flags, int fd)
637 struct io io = {};
639 init_io(&io, NULL, IO_AP);
640 io.pipe = fd;
641 if (format_argv(io.argv, argv, flags))
642 return run_io_do(&io);
643 close(fd);
644 return FALSE;
647 static bool
648 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
650 return init_io_rd(io, argv, NULL, flags) && start_io(io);
653 static bool
654 run_io_rd_dir(struct io *io, const char **argv, const char *dir, enum format_flags flags)
656 return init_io_rd(io, argv, dir, flags) && start_io(io);
659 static bool
660 io_eof(struct io *io)
662 return io->eof;
665 static int
666 io_error(struct io *io)
668 return io->error;
671 static char *
672 io_strerror(struct io *io)
674 return strerror(io->error);
677 static bool
678 io_can_read(struct io *io)
680 struct timeval tv = { 0, 500 };
681 fd_set fds;
683 FD_ZERO(&fds);
684 FD_SET(io->pipe, &fds);
686 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
689 static ssize_t
690 io_read(struct io *io, void *buf, size_t bufsize)
692 do {
693 ssize_t readsize = read(io->pipe, buf, bufsize);
695 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
696 continue;
697 else if (readsize == -1)
698 io->error = errno;
699 else if (readsize == 0)
700 io->eof = 1;
701 return readsize;
702 } while (1);
705 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
707 static char *
708 io_get(struct io *io, int c, bool can_read)
710 char *eol;
711 ssize_t readsize;
713 while (TRUE) {
714 if (io->bufsize > 0) {
715 eol = memchr(io->bufpos, c, io->bufsize);
716 if (eol) {
717 char *line = io->bufpos;
719 *eol = 0;
720 io->bufpos = eol + 1;
721 io->bufsize -= io->bufpos - line;
722 return line;
726 if (io_eof(io)) {
727 if (io->bufsize) {
728 io->bufpos[io->bufsize] = 0;
729 io->bufsize = 0;
730 return io->bufpos;
732 return NULL;
735 if (!can_read)
736 return NULL;
738 if (io->bufsize > 0 && io->bufpos > io->buf)
739 memmove(io->buf, io->bufpos, io->bufsize);
741 if (io->bufalloc == io->bufsize) {
742 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
743 return NULL;
744 io->bufalloc += BUFSIZ;
747 io->bufpos = io->buf;
748 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
749 if (io_error(io))
750 return NULL;
751 io->bufsize += readsize;
755 static bool
756 io_write(struct io *io, const void *buf, size_t bufsize)
758 size_t written = 0;
760 while (!io_error(io) && written < bufsize) {
761 ssize_t size;
763 size = write(io->pipe, buf + written, bufsize - written);
764 if (size < 0 && (errno == EAGAIN || errno == EINTR))
765 continue;
766 else if (size == -1)
767 io->error = errno;
768 else
769 written += size;
772 return written == bufsize;
775 static bool
776 io_read_buf(struct io *io, char buf[], size_t bufsize)
778 char *result = io_get(io, '\n', TRUE);
780 if (result) {
781 result = chomp_string(result);
782 string_ncopy_do(buf, bufsize, result, strlen(result));
785 return done_io(io) && result;
788 static bool
789 run_io_buf(const char **argv, char buf[], size_t bufsize)
791 struct io io = {};
793 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
796 static int
797 io_load(struct io *io, const char *separators,
798 int (*read_property)(char *, size_t, char *, size_t))
800 char *name;
801 int state = OK;
803 if (!start_io(io))
804 return ERR;
806 while (state == OK && (name = io_get(io, '\n', TRUE))) {
807 char *value;
808 size_t namelen;
809 size_t valuelen;
811 name = chomp_string(name);
812 namelen = strcspn(name, separators);
814 if (name[namelen]) {
815 name[namelen] = 0;
816 value = chomp_string(name + namelen + 1);
817 valuelen = strlen(value);
819 } else {
820 value = "";
821 valuelen = 0;
824 state = read_property(name, namelen, value, valuelen);
827 if (state != ERR && io_error(io))
828 state = ERR;
829 done_io(io);
831 return state;
834 static int
835 run_io_load(const char **argv, const char *separators,
836 int (*read_property)(char *, size_t, char *, size_t))
838 struct io io = {};
840 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
841 ? io_load(&io, separators, read_property) : ERR;
846 * User requests
849 #define REQ_INFO \
850 /* XXX: Keep the view request first and in sync with views[]. */ \
851 REQ_GROUP("View switching") \
852 REQ_(VIEW_MAIN, "Show main view"), \
853 REQ_(VIEW_DIFF, "Show diff view"), \
854 REQ_(VIEW_LOG, "Show log view"), \
855 REQ_(VIEW_TREE, "Show tree view"), \
856 REQ_(VIEW_BLOB, "Show blob view"), \
857 REQ_(VIEW_BLAME, "Show blame view"), \
858 REQ_(VIEW_BRANCH, "Show branch view"), \
859 REQ_(VIEW_HELP, "Show help page"), \
860 REQ_(VIEW_PAGER, "Show pager view"), \
861 REQ_(VIEW_STATUS, "Show status view"), \
862 REQ_(VIEW_STAGE, "Show stage view"), \
864 REQ_GROUP("View manipulation") \
865 REQ_(ENTER, "Enter current line and scroll"), \
866 REQ_(NEXT, "Move to next"), \
867 REQ_(PREVIOUS, "Move to previous"), \
868 REQ_(PARENT, "Move to parent"), \
869 REQ_(VIEW_NEXT, "Move focus to next view"), \
870 REQ_(REFRESH, "Reload and refresh"), \
871 REQ_(MAXIMIZE, "Maximize the current view"), \
872 REQ_(VIEW_CLOSE, "Close the current view"), \
873 REQ_(QUIT, "Close all views and quit"), \
875 REQ_GROUP("View specific requests") \
876 REQ_(STATUS_UPDATE, "Update file status"), \
877 REQ_(STATUS_REVERT, "Revert file changes"), \
878 REQ_(STATUS_MERGE, "Merge file using external tool"), \
879 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
881 REQ_GROUP("Cursor navigation") \
882 REQ_(MOVE_UP, "Move cursor one line up"), \
883 REQ_(MOVE_DOWN, "Move cursor one line down"), \
884 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
885 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
886 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
887 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
889 REQ_GROUP("Scrolling") \
890 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
891 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
892 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
893 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
894 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
895 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
897 REQ_GROUP("Searching") \
898 REQ_(SEARCH, "Search the view"), \
899 REQ_(SEARCH_BACK, "Search backwards in the view"), \
900 REQ_(FIND_NEXT, "Find next search match"), \
901 REQ_(FIND_PREV, "Find previous search match"), \
903 REQ_GROUP("Option manipulation") \
904 REQ_(OPTIONS, "Open option menu"), \
905 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
906 REQ_(TOGGLE_DATE, "Toggle date display"), \
907 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
908 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
909 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
910 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
911 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
912 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
914 REQ_GROUP("Misc") \
915 REQ_(PROMPT, "Bring up the prompt"), \
916 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
917 REQ_(SHOW_VERSION, "Show version information"), \
918 REQ_(STOP_LOADING, "Stop all loading views"), \
919 REQ_(EDIT, "Open in editor"), \
920 REQ_(NONE, "Do nothing")
923 /* User action requests. */
924 enum request {
925 #define REQ_GROUP(help)
926 #define REQ_(req, help) REQ_##req
928 /* Offset all requests to avoid conflicts with ncurses getch values. */
929 REQ_OFFSET = KEY_MAX + 1,
930 REQ_INFO
932 #undef REQ_GROUP
933 #undef REQ_
936 struct request_info {
937 enum request request;
938 const char *name;
939 int namelen;
940 const char *help;
943 static const struct request_info req_info[] = {
944 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
945 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
946 REQ_INFO
947 #undef REQ_GROUP
948 #undef REQ_
951 static enum request
952 get_request(const char *name)
954 int namelen = strlen(name);
955 int i;
957 for (i = 0; i < ARRAY_SIZE(req_info); i++)
958 if (req_info[i].namelen == namelen &&
959 !string_enum_compare(req_info[i].name, name, namelen))
960 return req_info[i].request;
962 return REQ_NONE;
967 * Options
970 /* Option and state variables. */
971 static enum date opt_date = DATE_DEFAULT;
972 static bool opt_author = TRUE;
973 static bool opt_line_number = FALSE;
974 static bool opt_line_graphics = TRUE;
975 static bool opt_rev_graph = FALSE;
976 static bool opt_show_refs = TRUE;
977 static int opt_num_interval = 5;
978 static double opt_hscroll = 0.50;
979 static double opt_scale_split_view = 2.0 / 3.0;
980 static int opt_tab_size = 8;
981 static int opt_author_cols = 19;
982 static char opt_path[SIZEOF_STR] = "";
983 static char opt_file[SIZEOF_STR] = "";
984 static char opt_ref[SIZEOF_REF] = "";
985 static char opt_head[SIZEOF_REF] = "";
986 static char opt_head_rev[SIZEOF_REV] = "";
987 static char opt_remote[SIZEOF_REF] = "";
988 static char opt_encoding[20] = "UTF-8";
989 static bool opt_utf8 = TRUE;
990 static char opt_codeset[20] = "UTF-8";
991 static iconv_t opt_iconv = ICONV_NONE;
992 static char opt_search[SIZEOF_STR] = "";
993 static char opt_cdup[SIZEOF_STR] = "";
994 static char opt_prefix[SIZEOF_STR] = "";
995 static char opt_git_dir[SIZEOF_STR] = "";
996 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
997 static char opt_editor[SIZEOF_STR] = "";
998 static FILE *opt_tty = NULL;
1000 #define is_initial_commit() (!*opt_head_rev)
1001 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1002 #define mkdate(time) string_date(time, opt_date)
1006 * Line-oriented content detection.
1009 #define LINE_INFO \
1010 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1011 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1012 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1013 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1014 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1015 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1016 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1017 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1018 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1019 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1020 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1021 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1022 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1023 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1024 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1025 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1026 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1027 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1028 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1029 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1030 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1031 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1032 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1033 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1034 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1035 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1036 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1037 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1038 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1039 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1040 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1041 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1042 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1043 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1044 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1045 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1046 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1047 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1048 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1049 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1051 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1052 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1053 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1054 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1055 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1056 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1057 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1058 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1060 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1061 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1062 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1063 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1064 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1065 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1066 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1068 enum line_type {
1069 #define LINE(type, line, fg, bg, attr) \
1070 LINE_##type
1071 LINE_INFO,
1072 LINE_NONE
1073 #undef LINE
1076 struct line_info {
1077 const char *name; /* Option name. */
1078 int namelen; /* Size of option name. */
1079 const char *line; /* The start of line to match. */
1080 int linelen; /* Size of string to match. */
1081 int fg, bg, attr; /* Color and text attributes for the lines. */
1084 static struct line_info line_info[] = {
1085 #define LINE(type, line, fg, bg, attr) \
1086 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1087 LINE_INFO
1088 #undef LINE
1091 static enum line_type
1092 get_line_type(const char *line)
1094 int linelen = strlen(line);
1095 enum line_type type;
1097 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1098 /* Case insensitive search matches Signed-off-by lines better. */
1099 if (linelen >= line_info[type].linelen &&
1100 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1101 return type;
1103 return LINE_DEFAULT;
1106 static inline int
1107 get_line_attr(enum line_type type)
1109 assert(type < ARRAY_SIZE(line_info));
1110 return COLOR_PAIR(type) | line_info[type].attr;
1113 static struct line_info *
1114 get_line_info(const char *name)
1116 size_t namelen = strlen(name);
1117 enum line_type type;
1119 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1120 if (namelen == line_info[type].namelen &&
1121 !string_enum_compare(line_info[type].name, name, namelen))
1122 return &line_info[type];
1124 return NULL;
1127 static void
1128 init_colors(void)
1130 int default_bg = line_info[LINE_DEFAULT].bg;
1131 int default_fg = line_info[LINE_DEFAULT].fg;
1132 enum line_type type;
1134 start_color();
1136 if (assume_default_colors(default_fg, default_bg) == ERR) {
1137 default_bg = COLOR_BLACK;
1138 default_fg = COLOR_WHITE;
1141 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1142 struct line_info *info = &line_info[type];
1143 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1144 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1146 init_pair(type, fg, bg);
1150 struct line {
1151 enum line_type type;
1153 /* State flags */
1154 unsigned int selected:1;
1155 unsigned int dirty:1;
1156 unsigned int cleareol:1;
1157 unsigned int other:16;
1159 void *data; /* User data */
1164 * Keys
1167 struct keybinding {
1168 int alias;
1169 enum request request;
1172 static const struct keybinding default_keybindings[] = {
1173 /* View switching */
1174 { 'm', REQ_VIEW_MAIN },
1175 { 'd', REQ_VIEW_DIFF },
1176 { 'l', REQ_VIEW_LOG },
1177 { 't', REQ_VIEW_TREE },
1178 { 'f', REQ_VIEW_BLOB },
1179 { 'B', REQ_VIEW_BLAME },
1180 { 'H', REQ_VIEW_BRANCH },
1181 { 'p', REQ_VIEW_PAGER },
1182 { 'h', REQ_VIEW_HELP },
1183 { 'S', REQ_VIEW_STATUS },
1184 { 'c', REQ_VIEW_STAGE },
1186 /* View manipulation */
1187 { 'q', REQ_VIEW_CLOSE },
1188 { KEY_TAB, REQ_VIEW_NEXT },
1189 { KEY_RETURN, REQ_ENTER },
1190 { KEY_UP, REQ_PREVIOUS },
1191 { KEY_DOWN, REQ_NEXT },
1192 { 'R', REQ_REFRESH },
1193 { KEY_F(5), REQ_REFRESH },
1194 { 'O', REQ_MAXIMIZE },
1196 /* Cursor navigation */
1197 { 'k', REQ_MOVE_UP },
1198 { 'j', REQ_MOVE_DOWN },
1199 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1200 { KEY_END, REQ_MOVE_LAST_LINE },
1201 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1202 { ' ', REQ_MOVE_PAGE_DOWN },
1203 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1204 { 'b', REQ_MOVE_PAGE_UP },
1205 { '-', REQ_MOVE_PAGE_UP },
1207 /* Scrolling */
1208 { KEY_LEFT, REQ_SCROLL_LEFT },
1209 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1210 { KEY_IC, REQ_SCROLL_LINE_UP },
1211 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1212 { 'w', REQ_SCROLL_PAGE_UP },
1213 { 's', REQ_SCROLL_PAGE_DOWN },
1215 /* Searching */
1216 { '/', REQ_SEARCH },
1217 { '?', REQ_SEARCH_BACK },
1218 { 'n', REQ_FIND_NEXT },
1219 { 'N', REQ_FIND_PREV },
1221 /* Misc */
1222 { 'Q', REQ_QUIT },
1223 { 'z', REQ_STOP_LOADING },
1224 { 'v', REQ_SHOW_VERSION },
1225 { 'r', REQ_SCREEN_REDRAW },
1226 { 'o', REQ_OPTIONS },
1227 { '.', REQ_TOGGLE_LINENO },
1228 { 'D', REQ_TOGGLE_DATE },
1229 { 'A', REQ_TOGGLE_AUTHOR },
1230 { 'g', REQ_TOGGLE_REV_GRAPH },
1231 { 'F', REQ_TOGGLE_REFS },
1232 { 'I', REQ_TOGGLE_SORT_ORDER },
1233 { 'i', REQ_TOGGLE_SORT_FIELD },
1234 { ':', REQ_PROMPT },
1235 { 'u', REQ_STATUS_UPDATE },
1236 { '!', REQ_STATUS_REVERT },
1237 { 'M', REQ_STATUS_MERGE },
1238 { '@', REQ_STAGE_NEXT },
1239 { ',', REQ_PARENT },
1240 { 'e', REQ_EDIT },
1243 #define KEYMAP_INFO \
1244 KEYMAP_(GENERIC), \
1245 KEYMAP_(MAIN), \
1246 KEYMAP_(DIFF), \
1247 KEYMAP_(LOG), \
1248 KEYMAP_(TREE), \
1249 KEYMAP_(BLOB), \
1250 KEYMAP_(BLAME), \
1251 KEYMAP_(BRANCH), \
1252 KEYMAP_(PAGER), \
1253 KEYMAP_(HELP), \
1254 KEYMAP_(STATUS), \
1255 KEYMAP_(STAGE)
1257 enum keymap {
1258 #define KEYMAP_(name) KEYMAP_##name
1259 KEYMAP_INFO
1260 #undef KEYMAP_
1263 static const struct enum_map keymap_table[] = {
1264 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1265 KEYMAP_INFO
1266 #undef KEYMAP_
1269 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1271 struct keybinding_table {
1272 struct keybinding *data;
1273 size_t size;
1276 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1278 static void
1279 add_keybinding(enum keymap keymap, enum request request, int key)
1281 struct keybinding_table *table = &keybindings[keymap];
1283 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1284 if (!table->data)
1285 die("Failed to allocate keybinding");
1286 table->data[table->size].alias = key;
1287 table->data[table->size++].request = request;
1290 /* Looks for a key binding first in the given map, then in the generic map, and
1291 * lastly in the default keybindings. */
1292 static enum request
1293 get_keybinding(enum keymap keymap, int key)
1295 size_t i;
1297 for (i = 0; i < keybindings[keymap].size; i++)
1298 if (keybindings[keymap].data[i].alias == key)
1299 return keybindings[keymap].data[i].request;
1301 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1302 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1303 return keybindings[KEYMAP_GENERIC].data[i].request;
1305 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1306 if (default_keybindings[i].alias == key)
1307 return default_keybindings[i].request;
1309 return (enum request) key;
1313 struct key {
1314 const char *name;
1315 int value;
1318 static const struct key key_table[] = {
1319 { "Enter", KEY_RETURN },
1320 { "Space", ' ' },
1321 { "Backspace", KEY_BACKSPACE },
1322 { "Tab", KEY_TAB },
1323 { "Escape", KEY_ESC },
1324 { "Left", KEY_LEFT },
1325 { "Right", KEY_RIGHT },
1326 { "Up", KEY_UP },
1327 { "Down", KEY_DOWN },
1328 { "Insert", KEY_IC },
1329 { "Delete", KEY_DC },
1330 { "Hash", '#' },
1331 { "Home", KEY_HOME },
1332 { "End", KEY_END },
1333 { "PageUp", KEY_PPAGE },
1334 { "PageDown", KEY_NPAGE },
1335 { "F1", KEY_F(1) },
1336 { "F2", KEY_F(2) },
1337 { "F3", KEY_F(3) },
1338 { "F4", KEY_F(4) },
1339 { "F5", KEY_F(5) },
1340 { "F6", KEY_F(6) },
1341 { "F7", KEY_F(7) },
1342 { "F8", KEY_F(8) },
1343 { "F9", KEY_F(9) },
1344 { "F10", KEY_F(10) },
1345 { "F11", KEY_F(11) },
1346 { "F12", KEY_F(12) },
1349 static int
1350 get_key_value(const char *name)
1352 int i;
1354 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1355 if (!strcasecmp(key_table[i].name, name))
1356 return key_table[i].value;
1358 if (strlen(name) == 1 && isprint(*name))
1359 return (int) *name;
1361 return ERR;
1364 static const char *
1365 get_key_name(int key_value)
1367 static char key_char[] = "'X'";
1368 const char *seq = NULL;
1369 int key;
1371 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1372 if (key_table[key].value == key_value)
1373 seq = key_table[key].name;
1375 if (seq == NULL &&
1376 key_value < 127 &&
1377 isprint(key_value)) {
1378 key_char[1] = (char) key_value;
1379 seq = key_char;
1382 return seq ? seq : "(no key)";
1385 static bool
1386 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1388 const char *sep = *pos > 0 ? ", " : "";
1389 const char *keyname = get_key_name(keybinding->alias);
1391 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1394 static bool
1395 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1396 enum keymap keymap, bool all)
1398 int i;
1400 for (i = 0; i < keybindings[keymap].size; i++) {
1401 if (keybindings[keymap].data[i].request == request) {
1402 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1403 return FALSE;
1404 if (!all)
1405 break;
1409 return TRUE;
1412 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1414 static const char *
1415 get_keys(enum keymap keymap, enum request request, bool all)
1417 static char buf[BUFSIZ];
1418 size_t pos = 0;
1419 int i;
1421 buf[pos] = 0;
1423 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1424 return "Too many keybindings!";
1425 if (pos > 0 && !all)
1426 return buf;
1428 if (keymap != KEYMAP_GENERIC) {
1429 /* Only the generic keymap includes the default keybindings when
1430 * listing all keys. */
1431 if (all)
1432 return buf;
1434 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1435 return "Too many keybindings!";
1436 if (pos)
1437 return buf;
1440 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1441 if (default_keybindings[i].request == request) {
1442 if (!append_key(buf, &pos, &default_keybindings[i]))
1443 return "Too many keybindings!";
1444 if (!all)
1445 return buf;
1449 return buf;
1452 struct run_request {
1453 enum keymap keymap;
1454 int key;
1455 const char *argv[SIZEOF_ARG];
1458 static struct run_request *run_request;
1459 static size_t run_requests;
1461 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1463 static enum request
1464 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1466 struct run_request *req;
1468 if (argc >= ARRAY_SIZE(req->argv) - 1)
1469 return REQ_NONE;
1471 if (!realloc_run_requests(&run_request, run_requests, 1))
1472 return REQ_NONE;
1474 req = &run_request[run_requests];
1475 req->keymap = keymap;
1476 req->key = key;
1477 req->argv[0] = NULL;
1479 if (!format_argv(req->argv, argv, FORMAT_NONE))
1480 return REQ_NONE;
1482 return REQ_NONE + ++run_requests;
1485 static struct run_request *
1486 get_run_request(enum request request)
1488 if (request <= REQ_NONE)
1489 return NULL;
1490 return &run_request[request - REQ_NONE - 1];
1493 static void
1494 add_builtin_run_requests(void)
1496 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1497 const char *commit[] = { "git", "commit", NULL };
1498 const char *gc[] = { "git", "gc", NULL };
1499 struct {
1500 enum keymap keymap;
1501 int key;
1502 int argc;
1503 const char **argv;
1504 } reqs[] = {
1505 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1506 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1507 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1509 int i;
1511 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1512 enum request req;
1514 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1515 if (req != REQ_NONE)
1516 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1521 * User config file handling.
1524 static int config_lineno;
1525 static bool config_errors;
1526 static const char *config_msg;
1528 static const struct enum_map color_map[] = {
1529 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1530 COLOR_MAP(DEFAULT),
1531 COLOR_MAP(BLACK),
1532 COLOR_MAP(BLUE),
1533 COLOR_MAP(CYAN),
1534 COLOR_MAP(GREEN),
1535 COLOR_MAP(MAGENTA),
1536 COLOR_MAP(RED),
1537 COLOR_MAP(WHITE),
1538 COLOR_MAP(YELLOW),
1541 static const struct enum_map attr_map[] = {
1542 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1543 ATTR_MAP(NORMAL),
1544 ATTR_MAP(BLINK),
1545 ATTR_MAP(BOLD),
1546 ATTR_MAP(DIM),
1547 ATTR_MAP(REVERSE),
1548 ATTR_MAP(STANDOUT),
1549 ATTR_MAP(UNDERLINE),
1552 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1554 static int parse_step(double *opt, const char *arg)
1556 *opt = atoi(arg);
1557 if (!strchr(arg, '%'))
1558 return OK;
1560 /* "Shift down" so 100% and 1 does not conflict. */
1561 *opt = (*opt - 1) / 100;
1562 if (*opt >= 1.0) {
1563 *opt = 0.99;
1564 config_msg = "Step value larger than 100%";
1565 return ERR;
1567 if (*opt < 0.0) {
1568 *opt = 1;
1569 config_msg = "Invalid step value";
1570 return ERR;
1572 return OK;
1575 static int
1576 parse_int(int *opt, const char *arg, int min, int max)
1578 int value = atoi(arg);
1580 if (min <= value && value <= max) {
1581 *opt = value;
1582 return OK;
1585 config_msg = "Integer value out of bound";
1586 return ERR;
1589 static bool
1590 set_color(int *color, const char *name)
1592 if (map_enum(color, color_map, name))
1593 return TRUE;
1594 if (!prefixcmp(name, "color"))
1595 return parse_int(color, name + 5, 0, 255) == OK;
1596 return FALSE;
1599 /* Wants: object fgcolor bgcolor [attribute] */
1600 static int
1601 option_color_command(int argc, const char *argv[])
1603 struct line_info *info;
1605 if (argc < 3) {
1606 config_msg = "Wrong number of arguments given to color command";
1607 return ERR;
1610 info = get_line_info(argv[0]);
1611 if (!info) {
1612 static const struct enum_map obsolete[] = {
1613 ENUM_MAP("main-delim", LINE_DELIMITER),
1614 ENUM_MAP("main-date", LINE_DATE),
1615 ENUM_MAP("main-author", LINE_AUTHOR),
1617 int index;
1619 if (!map_enum(&index, obsolete, argv[0])) {
1620 config_msg = "Unknown color name";
1621 return ERR;
1623 info = &line_info[index];
1626 if (!set_color(&info->fg, argv[1]) ||
1627 !set_color(&info->bg, argv[2])) {
1628 config_msg = "Unknown color";
1629 return ERR;
1632 info->attr = 0;
1633 while (argc-- > 3) {
1634 int attr;
1636 if (!set_attribute(&attr, argv[argc])) {
1637 config_msg = "Unknown attribute";
1638 return ERR;
1640 info->attr |= attr;
1643 return OK;
1646 static int parse_bool(bool *opt, const char *arg)
1648 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1649 ? TRUE : FALSE;
1650 return OK;
1653 static int
1654 parse_string(char *opt, const char *arg, size_t optsize)
1656 int arglen = strlen(arg);
1658 switch (arg[0]) {
1659 case '\"':
1660 case '\'':
1661 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1662 config_msg = "Unmatched quotation";
1663 return ERR;
1665 arg += 1; arglen -= 2;
1666 default:
1667 string_ncopy_do(opt, optsize, arg, arglen);
1668 return OK;
1672 /* Wants: name = value */
1673 static int
1674 option_set_command(int argc, const char *argv[])
1676 if (argc != 3) {
1677 config_msg = "Wrong number of arguments given to set command";
1678 return ERR;
1681 if (strcmp(argv[1], "=")) {
1682 config_msg = "No value assigned";
1683 return ERR;
1686 if (!strcmp(argv[0], "show-author"))
1687 return parse_bool(&opt_author, argv[2]);
1689 if (!strcmp(argv[0], "show-date")) {
1690 bool show_date;
1692 if (!strcmp(argv[2], "relative")) {
1693 opt_date = DATE_RELATIVE;
1694 return OK;
1695 } else if (!strcmp(argv[2], "short")) {
1696 opt_date = DATE_SHORT;
1697 return OK;
1698 } else if (parse_bool(&show_date, argv[2])) {
1699 opt_date = show_date ? DATE_DEFAULT : DATE_NONE;
1701 return ERR;
1704 if (!strcmp(argv[0], "show-rev-graph"))
1705 return parse_bool(&opt_rev_graph, argv[2]);
1707 if (!strcmp(argv[0], "show-refs"))
1708 return parse_bool(&opt_show_refs, argv[2]);
1710 if (!strcmp(argv[0], "show-line-numbers"))
1711 return parse_bool(&opt_line_number, argv[2]);
1713 if (!strcmp(argv[0], "line-graphics"))
1714 return parse_bool(&opt_line_graphics, argv[2]);
1716 if (!strcmp(argv[0], "line-number-interval"))
1717 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1719 if (!strcmp(argv[0], "author-width"))
1720 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1722 if (!strcmp(argv[0], "horizontal-scroll"))
1723 return parse_step(&opt_hscroll, argv[2]);
1725 if (!strcmp(argv[0], "split-view-height"))
1726 return parse_step(&opt_scale_split_view, argv[2]);
1728 if (!strcmp(argv[0], "tab-size"))
1729 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1731 if (!strcmp(argv[0], "commit-encoding"))
1732 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1734 config_msg = "Unknown variable name";
1735 return ERR;
1738 /* Wants: mode request key */
1739 static int
1740 option_bind_command(int argc, const char *argv[])
1742 enum request request;
1743 int keymap = -1;
1744 int key;
1746 if (argc < 3) {
1747 config_msg = "Wrong number of arguments given to bind command";
1748 return ERR;
1751 if (set_keymap(&keymap, argv[0]) == ERR) {
1752 config_msg = "Unknown key map";
1753 return ERR;
1756 key = get_key_value(argv[1]);
1757 if (key == ERR) {
1758 config_msg = "Unknown key";
1759 return ERR;
1762 request = get_request(argv[2]);
1763 if (request == REQ_NONE) {
1764 static const struct enum_map obsolete[] = {
1765 ENUM_MAP("cherry-pick", REQ_NONE),
1766 ENUM_MAP("screen-resize", REQ_NONE),
1767 ENUM_MAP("tree-parent", REQ_PARENT),
1769 int alias;
1771 if (map_enum(&alias, obsolete, argv[2])) {
1772 if (alias != REQ_NONE)
1773 add_keybinding(keymap, alias, key);
1774 config_msg = "Obsolete request name";
1775 return ERR;
1778 if (request == REQ_NONE && *argv[2]++ == '!')
1779 request = add_run_request(keymap, key, argc - 2, argv + 2);
1780 if (request == REQ_NONE) {
1781 config_msg = "Unknown request name";
1782 return ERR;
1785 add_keybinding(keymap, request, key);
1787 return OK;
1790 static int
1791 set_option(const char *opt, char *value)
1793 const char *argv[SIZEOF_ARG];
1794 int argc = 0;
1796 if (!argv_from_string(argv, &argc, value)) {
1797 config_msg = "Too many option arguments";
1798 return ERR;
1801 if (!strcmp(opt, "color"))
1802 return option_color_command(argc, argv);
1804 if (!strcmp(opt, "set"))
1805 return option_set_command(argc, argv);
1807 if (!strcmp(opt, "bind"))
1808 return option_bind_command(argc, argv);
1810 config_msg = "Unknown option command";
1811 return ERR;
1814 static int
1815 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1817 int status = OK;
1819 config_lineno++;
1820 config_msg = "Internal error";
1822 /* Check for comment markers, since read_properties() will
1823 * only ensure opt and value are split at first " \t". */
1824 optlen = strcspn(opt, "#");
1825 if (optlen == 0)
1826 return OK;
1828 if (opt[optlen] != 0) {
1829 config_msg = "No option value";
1830 status = ERR;
1832 } else {
1833 /* Look for comment endings in the value. */
1834 size_t len = strcspn(value, "#");
1836 if (len < valuelen) {
1837 valuelen = len;
1838 value[valuelen] = 0;
1841 status = set_option(opt, value);
1844 if (status == ERR) {
1845 warn("Error on line %d, near '%.*s': %s",
1846 config_lineno, (int) optlen, opt, config_msg);
1847 config_errors = TRUE;
1850 /* Always keep going if errors are encountered. */
1851 return OK;
1854 static void
1855 load_option_file(const char *path)
1857 struct io io = {};
1859 /* It's OK that the file doesn't exist. */
1860 if (!io_open(&io, path))
1861 return;
1863 config_lineno = 0;
1864 config_errors = FALSE;
1866 if (io_load(&io, " \t", read_option) == ERR ||
1867 config_errors == TRUE)
1868 warn("Errors while loading %s.", path);
1871 static int
1872 load_options(void)
1874 const char *home = getenv("HOME");
1875 const char *tigrc_user = getenv("TIGRC_USER");
1876 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1877 char buf[SIZEOF_STR];
1879 add_builtin_run_requests();
1881 if (!tigrc_system)
1882 tigrc_system = SYSCONFDIR "/tigrc";
1883 load_option_file(tigrc_system);
1885 if (!tigrc_user) {
1886 if (!home || !string_format(buf, "%s/.tigrc", home))
1887 return ERR;
1888 tigrc_user = buf;
1890 load_option_file(tigrc_user);
1892 return OK;
1897 * The viewer
1900 struct view;
1901 struct view_ops;
1903 /* The display array of active views and the index of the current view. */
1904 static struct view *display[2];
1905 static unsigned int current_view;
1907 #define foreach_displayed_view(view, i) \
1908 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1910 #define displayed_views() (display[1] != NULL ? 2 : 1)
1912 /* Current head and commit ID */
1913 static char ref_blob[SIZEOF_REF] = "";
1914 static char ref_commit[SIZEOF_REF] = "HEAD";
1915 static char ref_head[SIZEOF_REF] = "HEAD";
1917 struct view {
1918 const char *name; /* View name */
1919 const char *cmd_env; /* Command line set via environment */
1920 const char *id; /* Points to either of ref_{head,commit,blob} */
1922 struct view_ops *ops; /* View operations */
1924 enum keymap keymap; /* What keymap does this view have */
1925 bool git_dir; /* Whether the view requires a git directory. */
1927 char ref[SIZEOF_REF]; /* Hovered commit reference */
1928 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1930 int height, width; /* The width and height of the main window */
1931 WINDOW *win; /* The main window */
1932 WINDOW *title; /* The title window living below the main window */
1934 /* Navigation */
1935 unsigned long offset; /* Offset of the window top */
1936 unsigned long yoffset; /* Offset from the window side. */
1937 unsigned long lineno; /* Current line number */
1938 unsigned long p_offset; /* Previous offset of the window top */
1939 unsigned long p_yoffset;/* Previous offset from the window side */
1940 unsigned long p_lineno; /* Previous current line number */
1941 bool p_restore; /* Should the previous position be restored. */
1943 /* Searching */
1944 char grep[SIZEOF_STR]; /* Search string */
1945 regex_t *regex; /* Pre-compiled regexp */
1947 /* If non-NULL, points to the view that opened this view. If this view
1948 * is closed tig will switch back to the parent view. */
1949 struct view *parent;
1951 /* Buffering */
1952 size_t lines; /* Total number of lines */
1953 struct line *line; /* Line index */
1954 unsigned int digits; /* Number of digits in the lines member. */
1956 /* Drawing */
1957 struct line *curline; /* Line currently being drawn. */
1958 enum line_type curtype; /* Attribute currently used for drawing. */
1959 unsigned long col; /* Column when drawing. */
1960 bool has_scrolled; /* View was scrolled. */
1962 /* Loading */
1963 struct io io;
1964 struct io *pipe;
1965 time_t start_time;
1966 time_t update_secs;
1969 struct view_ops {
1970 /* What type of content being displayed. Used in the title bar. */
1971 const char *type;
1972 /* Default command arguments. */
1973 const char **argv;
1974 /* Open and reads in all view content. */
1975 bool (*open)(struct view *view);
1976 /* Read one line; updates view->line. */
1977 bool (*read)(struct view *view, char *data);
1978 /* Draw one line; @lineno must be < view->height. */
1979 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1980 /* Depending on view handle a special requests. */
1981 enum request (*request)(struct view *view, enum request request, struct line *line);
1982 /* Search for regexp in a line. */
1983 bool (*grep)(struct view *view, struct line *line);
1984 /* Select line */
1985 void (*select)(struct view *view, struct line *line);
1986 /* Prepare view for loading */
1987 bool (*prepare)(struct view *view);
1990 static struct view_ops blame_ops;
1991 static struct view_ops blob_ops;
1992 static struct view_ops diff_ops;
1993 static struct view_ops help_ops;
1994 static struct view_ops log_ops;
1995 static struct view_ops main_ops;
1996 static struct view_ops pager_ops;
1997 static struct view_ops stage_ops;
1998 static struct view_ops status_ops;
1999 static struct view_ops tree_ops;
2000 static struct view_ops branch_ops;
2002 #define VIEW_STR(name, env, ref, ops, map, git) \
2003 { name, #env, ref, ops, map, git }
2005 #define VIEW_(id, name, ops, git, ref) \
2006 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2009 static struct view views[] = {
2010 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2011 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2012 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2013 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2014 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2015 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2016 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2017 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2018 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2019 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2020 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2023 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2024 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2026 #define foreach_view(view, i) \
2027 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2029 #define view_is_displayed(view) \
2030 (view == display[0] || view == display[1])
2033 enum line_graphic {
2034 LINE_GRAPHIC_VLINE
2037 static chtype line_graphics[] = {
2038 /* LINE_GRAPHIC_VLINE: */ '|'
2041 static inline void
2042 set_view_attr(struct view *view, enum line_type type)
2044 if (!view->curline->selected && view->curtype != type) {
2045 wattrset(view->win, get_line_attr(type));
2046 wchgat(view->win, -1, 0, type, NULL);
2047 view->curtype = type;
2051 static int
2052 draw_chars(struct view *view, enum line_type type, const char *string,
2053 int max_len, bool use_tilde)
2055 int len = 0;
2056 int col = 0;
2057 int trimmed = FALSE;
2058 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2060 if (max_len <= 0)
2061 return 0;
2063 if (opt_utf8) {
2064 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2065 } else {
2066 col = len = strlen(string);
2067 if (len > max_len) {
2068 if (use_tilde) {
2069 max_len -= 1;
2071 col = len = max_len;
2072 trimmed = TRUE;
2076 set_view_attr(view, type);
2077 if (len > 0)
2078 waddnstr(view->win, string, len);
2079 if (trimmed && use_tilde) {
2080 set_view_attr(view, LINE_DELIMITER);
2081 waddch(view->win, '~');
2082 col++;
2085 return col;
2088 static int
2089 draw_space(struct view *view, enum line_type type, int max, int spaces)
2091 static char space[] = " ";
2092 int col = 0;
2094 spaces = MIN(max, spaces);
2096 while (spaces > 0) {
2097 int len = MIN(spaces, sizeof(space) - 1);
2099 col += draw_chars(view, type, space, len, FALSE);
2100 spaces -= len;
2103 return col;
2106 static bool
2107 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2109 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2110 return view->width + view->yoffset <= view->col;
2113 static bool
2114 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2116 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2117 int max = view->width + view->yoffset - view->col;
2118 int i;
2120 if (max < size)
2121 size = max;
2123 set_view_attr(view, type);
2124 /* Using waddch() instead of waddnstr() ensures that
2125 * they'll be rendered correctly for the cursor line. */
2126 for (i = skip; i < size; i++)
2127 waddch(view->win, graphic[i]);
2129 view->col += size;
2130 if (size < max && skip <= size)
2131 waddch(view->win, ' ');
2132 view->col++;
2134 return view->width + view->yoffset <= view->col;
2137 static bool
2138 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2140 int max = MIN(view->width + view->yoffset - view->col, len);
2141 int col;
2143 if (text)
2144 col = draw_chars(view, type, text, max - 1, trim);
2145 else
2146 col = draw_space(view, type, max - 1, max - 1);
2148 view->col += col;
2149 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2150 return view->width + view->yoffset <= view->col;
2153 static bool
2154 draw_date(struct view *view, time_t *time)
2156 const char *date = time ? mkdate(time) : "";
2157 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2159 return draw_field(view, LINE_DATE, date, cols, FALSE);
2162 static bool
2163 draw_author(struct view *view, const char *author)
2165 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2167 if (!trim) {
2168 static char initials[10];
2169 size_t pos;
2171 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2173 memset(initials, 0, sizeof(initials));
2174 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2175 while (is_initial_sep(*author))
2176 author++;
2177 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2178 while (*author && !is_initial_sep(author[1]))
2179 author++;
2182 author = initials;
2185 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2188 static bool
2189 draw_mode(struct view *view, mode_t mode)
2191 const char *str;
2193 if (S_ISDIR(mode))
2194 str = "drwxr-xr-x";
2195 else if (S_ISLNK(mode))
2196 str = "lrwxrwxrwx";
2197 else if (S_ISGITLINK(mode))
2198 str = "m---------";
2199 else if (S_ISREG(mode) && mode & S_IXUSR)
2200 str = "-rwxr-xr-x";
2201 else if (S_ISREG(mode))
2202 str = "-rw-r--r--";
2203 else
2204 str = "----------";
2206 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2209 static bool
2210 draw_lineno(struct view *view, unsigned int lineno)
2212 char number[10];
2213 int digits3 = view->digits < 3 ? 3 : view->digits;
2214 int max = MIN(view->width + view->yoffset - view->col, digits3);
2215 char *text = NULL;
2217 lineno += view->offset + 1;
2218 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2219 static char fmt[] = "%1ld";
2221 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2222 if (string_format(number, fmt, lineno))
2223 text = number;
2225 if (text)
2226 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2227 else
2228 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2229 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2232 static bool
2233 draw_view_line(struct view *view, unsigned int lineno)
2235 struct line *line;
2236 bool selected = (view->offset + lineno == view->lineno);
2238 assert(view_is_displayed(view));
2240 if (view->offset + lineno >= view->lines)
2241 return FALSE;
2243 line = &view->line[view->offset + lineno];
2245 wmove(view->win, lineno, 0);
2246 if (line->cleareol)
2247 wclrtoeol(view->win);
2248 view->col = 0;
2249 view->curline = line;
2250 view->curtype = LINE_NONE;
2251 line->selected = FALSE;
2252 line->dirty = line->cleareol = 0;
2254 if (selected) {
2255 set_view_attr(view, LINE_CURSOR);
2256 line->selected = TRUE;
2257 view->ops->select(view, line);
2260 return view->ops->draw(view, line, lineno);
2263 static void
2264 redraw_view_dirty(struct view *view)
2266 bool dirty = FALSE;
2267 int lineno;
2269 for (lineno = 0; lineno < view->height; lineno++) {
2270 if (view->offset + lineno >= view->lines)
2271 break;
2272 if (!view->line[view->offset + lineno].dirty)
2273 continue;
2274 dirty = TRUE;
2275 if (!draw_view_line(view, lineno))
2276 break;
2279 if (!dirty)
2280 return;
2281 wnoutrefresh(view->win);
2284 static void
2285 redraw_view_from(struct view *view, int lineno)
2287 assert(0 <= lineno && lineno < view->height);
2289 for (; lineno < view->height; lineno++) {
2290 if (!draw_view_line(view, lineno))
2291 break;
2294 wnoutrefresh(view->win);
2297 static void
2298 redraw_view(struct view *view)
2300 werase(view->win);
2301 redraw_view_from(view, 0);
2305 static void
2306 update_view_title(struct view *view)
2308 char buf[SIZEOF_STR];
2309 char state[SIZEOF_STR];
2310 size_t bufpos = 0, statelen = 0;
2312 assert(view_is_displayed(view));
2314 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2315 unsigned int view_lines = view->offset + view->height;
2316 unsigned int lines = view->lines
2317 ? MIN(view_lines, view->lines) * 100 / view->lines
2318 : 0;
2320 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2321 view->ops->type,
2322 view->lineno + 1,
2323 view->lines,
2324 lines);
2328 if (view->pipe) {
2329 time_t secs = time(NULL) - view->start_time;
2331 /* Three git seconds are a long time ... */
2332 if (secs > 2)
2333 string_format_from(state, &statelen, " loading %lds", secs);
2336 string_format_from(buf, &bufpos, "[%s]", view->name);
2337 if (*view->ref && bufpos < view->width) {
2338 size_t refsize = strlen(view->ref);
2339 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2341 if (minsize < view->width)
2342 refsize = view->width - minsize + 7;
2343 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2346 if (statelen && bufpos < view->width) {
2347 string_format_from(buf, &bufpos, "%s", state);
2350 if (view == display[current_view])
2351 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2352 else
2353 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2355 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2356 wclrtoeol(view->title);
2357 wnoutrefresh(view->title);
2360 static int
2361 apply_step(double step, int value)
2363 if (step >= 1)
2364 return (int) step;
2365 value *= step + 0.01;
2366 return value ? value : 1;
2369 static void
2370 resize_display(void)
2372 int offset, i;
2373 struct view *base = display[0];
2374 struct view *view = display[1] ? display[1] : display[0];
2376 /* Setup window dimensions */
2378 getmaxyx(stdscr, base->height, base->width);
2380 /* Make room for the status window. */
2381 base->height -= 1;
2383 if (view != base) {
2384 /* Horizontal split. */
2385 view->width = base->width;
2386 view->height = apply_step(opt_scale_split_view, base->height);
2387 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2388 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2389 base->height -= view->height;
2391 /* Make room for the title bar. */
2392 view->height -= 1;
2395 /* Make room for the title bar. */
2396 base->height -= 1;
2398 offset = 0;
2400 foreach_displayed_view (view, i) {
2401 if (!view->win) {
2402 view->win = newwin(view->height, 0, offset, 0);
2403 if (!view->win)
2404 die("Failed to create %s view", view->name);
2406 scrollok(view->win, FALSE);
2408 view->title = newwin(1, 0, offset + view->height, 0);
2409 if (!view->title)
2410 die("Failed to create title window");
2412 } else {
2413 wresize(view->win, view->height, view->width);
2414 mvwin(view->win, offset, 0);
2415 mvwin(view->title, offset + view->height, 0);
2418 offset += view->height + 1;
2422 static void
2423 redraw_display(bool clear)
2425 struct view *view;
2426 int i;
2428 foreach_displayed_view (view, i) {
2429 if (clear)
2430 wclear(view->win);
2431 redraw_view(view);
2432 update_view_title(view);
2436 static void
2437 toggle_date_option(enum date *date)
2439 static const char *help[] = {
2440 "no",
2441 "default",
2442 "relative",
2443 "short"
2446 opt_date = (opt_date + 1) % ARRAY_SIZE(help);
2447 redraw_display(FALSE);
2448 report("Displaying %s dates", help[opt_date]);
2451 static void
2452 toggle_view_option(bool *option, const char *help)
2454 *option = !*option;
2455 redraw_display(FALSE);
2456 report("%sabling %s", *option ? "En" : "Dis", help);
2459 static void
2460 open_option_menu(void)
2462 const struct menu_item menu[] = {
2463 { '.', "line numbers", &opt_line_number },
2464 { 'D', "date display", &opt_date },
2465 { 'A', "author display", &opt_author },
2466 { 'g', "revision graph display", &opt_rev_graph },
2467 { 'F', "reference display", &opt_show_refs },
2468 { 0 }
2470 int selected = 0;
2472 if (prompt_menu("Toggle option", menu, &selected)) {
2473 if (menu[selected].data == &opt_date)
2474 toggle_date_option(menu[selected].data);
2475 else
2476 toggle_view_option(menu[selected].data, menu[selected].text);
2480 static void
2481 maximize_view(struct view *view)
2483 memset(display, 0, sizeof(display));
2484 current_view = 0;
2485 display[current_view] = view;
2486 resize_display();
2487 redraw_display(FALSE);
2488 report("");
2493 * Navigation
2496 static bool
2497 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2499 if (lineno >= view->lines)
2500 lineno = view->lines > 0 ? view->lines - 1 : 0;
2502 if (offset > lineno || offset + view->height <= lineno) {
2503 unsigned long half = view->height / 2;
2505 if (lineno > half)
2506 offset = lineno - half;
2507 else
2508 offset = 0;
2511 if (offset != view->offset || lineno != view->lineno) {
2512 view->offset = offset;
2513 view->lineno = lineno;
2514 return TRUE;
2517 return FALSE;
2520 /* Scrolling backend */
2521 static void
2522 do_scroll_view(struct view *view, int lines)
2524 bool redraw_current_line = FALSE;
2526 /* The rendering expects the new offset. */
2527 view->offset += lines;
2529 assert(0 <= view->offset && view->offset < view->lines);
2530 assert(lines);
2532 /* Move current line into the view. */
2533 if (view->lineno < view->offset) {
2534 view->lineno = view->offset;
2535 redraw_current_line = TRUE;
2536 } else if (view->lineno >= view->offset + view->height) {
2537 view->lineno = view->offset + view->height - 1;
2538 redraw_current_line = TRUE;
2541 assert(view->offset <= view->lineno && view->lineno < view->lines);
2543 /* Redraw the whole screen if scrolling is pointless. */
2544 if (view->height < ABS(lines)) {
2545 redraw_view(view);
2547 } else {
2548 int line = lines > 0 ? view->height - lines : 0;
2549 int end = line + ABS(lines);
2551 scrollok(view->win, TRUE);
2552 wscrl(view->win, lines);
2553 scrollok(view->win, FALSE);
2555 while (line < end && draw_view_line(view, line))
2556 line++;
2558 if (redraw_current_line)
2559 draw_view_line(view, view->lineno - view->offset);
2560 wnoutrefresh(view->win);
2563 view->has_scrolled = TRUE;
2564 report("");
2567 /* Scroll frontend */
2568 static void
2569 scroll_view(struct view *view, enum request request)
2571 int lines = 1;
2573 assert(view_is_displayed(view));
2575 switch (request) {
2576 case REQ_SCROLL_LEFT:
2577 if (view->yoffset == 0) {
2578 report("Cannot scroll beyond the first column");
2579 return;
2581 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2582 view->yoffset = 0;
2583 else
2584 view->yoffset -= apply_step(opt_hscroll, view->width);
2585 redraw_view_from(view, 0);
2586 report("");
2587 return;
2588 case REQ_SCROLL_RIGHT:
2589 view->yoffset += apply_step(opt_hscroll, view->width);
2590 redraw_view(view);
2591 report("");
2592 return;
2593 case REQ_SCROLL_PAGE_DOWN:
2594 lines = view->height;
2595 case REQ_SCROLL_LINE_DOWN:
2596 if (view->offset + lines > view->lines)
2597 lines = view->lines - view->offset;
2599 if (lines == 0 || view->offset + view->height >= view->lines) {
2600 report("Cannot scroll beyond the last line");
2601 return;
2603 break;
2605 case REQ_SCROLL_PAGE_UP:
2606 lines = view->height;
2607 case REQ_SCROLL_LINE_UP:
2608 if (lines > view->offset)
2609 lines = view->offset;
2611 if (lines == 0) {
2612 report("Cannot scroll beyond the first line");
2613 return;
2616 lines = -lines;
2617 break;
2619 default:
2620 die("request %d not handled in switch", request);
2623 do_scroll_view(view, lines);
2626 /* Cursor moving */
2627 static void
2628 move_view(struct view *view, enum request request)
2630 int scroll_steps = 0;
2631 int steps;
2633 switch (request) {
2634 case REQ_MOVE_FIRST_LINE:
2635 steps = -view->lineno;
2636 break;
2638 case REQ_MOVE_LAST_LINE:
2639 steps = view->lines - view->lineno - 1;
2640 break;
2642 case REQ_MOVE_PAGE_UP:
2643 steps = view->height > view->lineno
2644 ? -view->lineno : -view->height;
2645 break;
2647 case REQ_MOVE_PAGE_DOWN:
2648 steps = view->lineno + view->height >= view->lines
2649 ? view->lines - view->lineno - 1 : view->height;
2650 break;
2652 case REQ_MOVE_UP:
2653 steps = -1;
2654 break;
2656 case REQ_MOVE_DOWN:
2657 steps = 1;
2658 break;
2660 default:
2661 die("request %d not handled in switch", request);
2664 if (steps <= 0 && view->lineno == 0) {
2665 report("Cannot move beyond the first line");
2666 return;
2668 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2669 report("Cannot move beyond the last line");
2670 return;
2673 /* Move the current line */
2674 view->lineno += steps;
2675 assert(0 <= view->lineno && view->lineno < view->lines);
2677 /* Check whether the view needs to be scrolled */
2678 if (view->lineno < view->offset ||
2679 view->lineno >= view->offset + view->height) {
2680 scroll_steps = steps;
2681 if (steps < 0 && -steps > view->offset) {
2682 scroll_steps = -view->offset;
2684 } else if (steps > 0) {
2685 if (view->lineno == view->lines - 1 &&
2686 view->lines > view->height) {
2687 scroll_steps = view->lines - view->offset - 1;
2688 if (scroll_steps >= view->height)
2689 scroll_steps -= view->height - 1;
2694 if (!view_is_displayed(view)) {
2695 view->offset += scroll_steps;
2696 assert(0 <= view->offset && view->offset < view->lines);
2697 view->ops->select(view, &view->line[view->lineno]);
2698 return;
2701 /* Repaint the old "current" line if we be scrolling */
2702 if (ABS(steps) < view->height)
2703 draw_view_line(view, view->lineno - steps - view->offset);
2705 if (scroll_steps) {
2706 do_scroll_view(view, scroll_steps);
2707 return;
2710 /* Draw the current line */
2711 draw_view_line(view, view->lineno - view->offset);
2713 wnoutrefresh(view->win);
2714 report("");
2719 * Searching
2722 static void search_view(struct view *view, enum request request);
2724 static bool
2725 grep_text(struct view *view, const char *text[])
2727 regmatch_t pmatch;
2728 size_t i;
2730 for (i = 0; text[i]; i++)
2731 if (*text[i] &&
2732 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2733 return TRUE;
2734 return FALSE;
2737 static void
2738 select_view_line(struct view *view, unsigned long lineno)
2740 unsigned long old_lineno = view->lineno;
2741 unsigned long old_offset = view->offset;
2743 if (goto_view_line(view, view->offset, lineno)) {
2744 if (view_is_displayed(view)) {
2745 if (old_offset != view->offset) {
2746 redraw_view(view);
2747 } else {
2748 draw_view_line(view, old_lineno - view->offset);
2749 draw_view_line(view, view->lineno - view->offset);
2750 wnoutrefresh(view->win);
2752 } else {
2753 view->ops->select(view, &view->line[view->lineno]);
2758 static void
2759 find_next(struct view *view, enum request request)
2761 unsigned long lineno = view->lineno;
2762 int direction;
2764 if (!*view->grep) {
2765 if (!*opt_search)
2766 report("No previous search");
2767 else
2768 search_view(view, request);
2769 return;
2772 switch (request) {
2773 case REQ_SEARCH:
2774 case REQ_FIND_NEXT:
2775 direction = 1;
2776 break;
2778 case REQ_SEARCH_BACK:
2779 case REQ_FIND_PREV:
2780 direction = -1;
2781 break;
2783 default:
2784 return;
2787 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2788 lineno += direction;
2790 /* Note, lineno is unsigned long so will wrap around in which case it
2791 * will become bigger than view->lines. */
2792 for (; lineno < view->lines; lineno += direction) {
2793 if (view->ops->grep(view, &view->line[lineno])) {
2794 select_view_line(view, lineno);
2795 report("Line %ld matches '%s'", lineno + 1, view->grep);
2796 return;
2800 report("No match found for '%s'", view->grep);
2803 static void
2804 search_view(struct view *view, enum request request)
2806 int regex_err;
2808 if (view->regex) {
2809 regfree(view->regex);
2810 *view->grep = 0;
2811 } else {
2812 view->regex = calloc(1, sizeof(*view->regex));
2813 if (!view->regex)
2814 return;
2817 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2818 if (regex_err != 0) {
2819 char buf[SIZEOF_STR] = "unknown error";
2821 regerror(regex_err, view->regex, buf, sizeof(buf));
2822 report("Search failed: %s", buf);
2823 return;
2826 string_copy(view->grep, opt_search);
2828 find_next(view, request);
2832 * Incremental updating
2835 static void
2836 reset_view(struct view *view)
2838 int i;
2840 for (i = 0; i < view->lines; i++)
2841 free(view->line[i].data);
2842 free(view->line);
2844 view->p_offset = view->offset;
2845 view->p_yoffset = view->yoffset;
2846 view->p_lineno = view->lineno;
2848 view->line = NULL;
2849 view->offset = 0;
2850 view->yoffset = 0;
2851 view->lines = 0;
2852 view->lineno = 0;
2853 view->vid[0] = 0;
2854 view->update_secs = 0;
2857 static void
2858 free_argv(const char *argv[])
2860 int argc;
2862 for (argc = 0; argv[argc]; argc++)
2863 free((void *) argv[argc]);
2866 static bool
2867 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2869 char buf[SIZEOF_STR];
2870 int argc;
2871 bool noreplace = flags == FORMAT_NONE;
2873 free_argv(dst_argv);
2875 for (argc = 0; src_argv[argc]; argc++) {
2876 const char *arg = src_argv[argc];
2877 size_t bufpos = 0;
2879 while (arg) {
2880 char *next = strstr(arg, "%(");
2881 int len = next - arg;
2882 const char *value;
2884 if (!next || noreplace) {
2885 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2886 noreplace = TRUE;
2887 len = strlen(arg);
2888 value = "";
2890 } else if (!prefixcmp(next, "%(directory)")) {
2891 value = opt_path;
2893 } else if (!prefixcmp(next, "%(file)")) {
2894 value = opt_file;
2896 } else if (!prefixcmp(next, "%(ref)")) {
2897 value = *opt_ref ? opt_ref : "HEAD";
2899 } else if (!prefixcmp(next, "%(head)")) {
2900 value = ref_head;
2902 } else if (!prefixcmp(next, "%(commit)")) {
2903 value = ref_commit;
2905 } else if (!prefixcmp(next, "%(blob)")) {
2906 value = ref_blob;
2908 } else {
2909 report("Unknown replacement: `%s`", next);
2910 return FALSE;
2913 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2914 return FALSE;
2916 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2919 dst_argv[argc] = strdup(buf);
2920 if (!dst_argv[argc])
2921 break;
2924 dst_argv[argc] = NULL;
2926 return src_argv[argc] == NULL;
2929 static bool
2930 restore_view_position(struct view *view)
2932 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2933 return FALSE;
2935 /* Changing the view position cancels the restoring. */
2936 /* FIXME: Changing back to the first line is not detected. */
2937 if (view->offset != 0 || view->lineno != 0) {
2938 view->p_restore = FALSE;
2939 return FALSE;
2942 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2943 view_is_displayed(view))
2944 werase(view->win);
2946 view->yoffset = view->p_yoffset;
2947 view->p_restore = FALSE;
2949 return TRUE;
2952 static void
2953 end_update(struct view *view, bool force)
2955 if (!view->pipe)
2956 return;
2957 while (!view->ops->read(view, NULL))
2958 if (!force)
2959 return;
2960 set_nonblocking_input(FALSE);
2961 if (force)
2962 kill_io(view->pipe);
2963 done_io(view->pipe);
2964 view->pipe = NULL;
2967 static void
2968 setup_update(struct view *view, const char *vid)
2970 set_nonblocking_input(TRUE);
2971 reset_view(view);
2972 string_copy_rev(view->vid, vid);
2973 view->pipe = &view->io;
2974 view->start_time = time(NULL);
2977 static bool
2978 prepare_update(struct view *view, const char *argv[], const char *dir,
2979 enum format_flags flags)
2981 if (view->pipe)
2982 end_update(view, TRUE);
2983 return init_io_rd(&view->io, argv, dir, flags);
2986 static bool
2987 prepare_update_file(struct view *view, const char *name)
2989 if (view->pipe)
2990 end_update(view, TRUE);
2991 return io_open(&view->io, name);
2994 static bool
2995 begin_update(struct view *view, bool refresh)
2997 if (view->pipe)
2998 end_update(view, TRUE);
3000 if (!refresh) {
3001 if (view->ops->prepare) {
3002 if (!view->ops->prepare(view))
3003 return FALSE;
3004 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3005 return FALSE;
3008 /* Put the current ref_* value to the view title ref
3009 * member. This is needed by the blob view. Most other
3010 * views sets it automatically after loading because the
3011 * first line is a commit line. */
3012 string_copy_rev(view->ref, view->id);
3015 if (!start_io(&view->io))
3016 return FALSE;
3018 setup_update(view, view->id);
3020 return TRUE;
3023 static bool
3024 update_view(struct view *view)
3026 char out_buffer[BUFSIZ * 2];
3027 char *line;
3028 /* Clear the view and redraw everything since the tree sorting
3029 * might have rearranged things. */
3030 bool redraw = view->lines == 0;
3031 bool can_read = TRUE;
3033 if (!view->pipe)
3034 return TRUE;
3036 if (!io_can_read(view->pipe)) {
3037 if (view->lines == 0 && view_is_displayed(view)) {
3038 time_t secs = time(NULL) - view->start_time;
3040 if (secs > 1 && secs > view->update_secs) {
3041 if (view->update_secs == 0)
3042 redraw_view(view);
3043 update_view_title(view);
3044 view->update_secs = secs;
3047 return TRUE;
3050 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3051 if (opt_iconv != ICONV_NONE) {
3052 ICONV_CONST char *inbuf = line;
3053 size_t inlen = strlen(line) + 1;
3055 char *outbuf = out_buffer;
3056 size_t outlen = sizeof(out_buffer);
3058 size_t ret;
3060 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
3061 if (ret != (size_t) -1)
3062 line = out_buffer;
3065 if (!view->ops->read(view, line)) {
3066 report("Allocation failure");
3067 end_update(view, TRUE);
3068 return FALSE;
3073 unsigned long lines = view->lines;
3074 int digits;
3076 for (digits = 0; lines; digits++)
3077 lines /= 10;
3079 /* Keep the displayed view in sync with line number scaling. */
3080 if (digits != view->digits) {
3081 view->digits = digits;
3082 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3083 redraw = TRUE;
3087 if (io_error(view->pipe)) {
3088 report("Failed to read: %s", io_strerror(view->pipe));
3089 end_update(view, TRUE);
3091 } else if (io_eof(view->pipe)) {
3092 report("");
3093 end_update(view, FALSE);
3096 if (restore_view_position(view))
3097 redraw = TRUE;
3099 if (!view_is_displayed(view))
3100 return TRUE;
3102 if (redraw)
3103 redraw_view_from(view, 0);
3104 else
3105 redraw_view_dirty(view);
3107 /* Update the title _after_ the redraw so that if the redraw picks up a
3108 * commit reference in view->ref it'll be available here. */
3109 update_view_title(view);
3110 return TRUE;
3113 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3115 static struct line *
3116 add_line_data(struct view *view, void *data, enum line_type type)
3118 struct line *line;
3120 if (!realloc_lines(&view->line, view->lines, 1))
3121 return NULL;
3123 line = &view->line[view->lines++];
3124 memset(line, 0, sizeof(*line));
3125 line->type = type;
3126 line->data = data;
3127 line->dirty = 1;
3129 return line;
3132 static struct line *
3133 add_line_text(struct view *view, const char *text, enum line_type type)
3135 char *data = text ? strdup(text) : NULL;
3137 return data ? add_line_data(view, data, type) : NULL;
3140 static struct line *
3141 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3143 char buf[SIZEOF_STR];
3144 va_list args;
3146 va_start(args, fmt);
3147 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3148 buf[0] = 0;
3149 va_end(args);
3151 return buf[0] ? add_line_text(view, buf, type) : NULL;
3155 * View opening
3158 enum open_flags {
3159 OPEN_DEFAULT = 0, /* Use default view switching. */
3160 OPEN_SPLIT = 1, /* Split current view. */
3161 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3162 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3163 OPEN_PREPARED = 32, /* Open already prepared command. */
3166 static void
3167 open_view(struct view *prev, enum request request, enum open_flags flags)
3169 bool split = !!(flags & OPEN_SPLIT);
3170 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3171 bool nomaximize = !!(flags & OPEN_REFRESH);
3172 struct view *view = VIEW(request);
3173 int nviews = displayed_views();
3174 struct view *base_view = display[0];
3176 if (view == prev && nviews == 1 && !reload) {
3177 report("Already in %s view", view->name);
3178 return;
3181 if (view->git_dir && !opt_git_dir[0]) {
3182 report("The %s view is disabled in pager view", view->name);
3183 return;
3186 if (split) {
3187 display[1] = view;
3188 current_view = 1;
3189 } else if (!nomaximize) {
3190 /* Maximize the current view. */
3191 memset(display, 0, sizeof(display));
3192 current_view = 0;
3193 display[current_view] = view;
3196 /* No parent signals that this is the first loaded view. */
3197 if (prev && view != prev) {
3198 view->parent = prev;
3201 /* Resize the view when switching between split- and full-screen,
3202 * or when switching between two different full-screen views. */
3203 if (nviews != displayed_views() ||
3204 (nviews == 1 && base_view != display[0]))
3205 resize_display();
3207 if (view->ops->open) {
3208 if (view->pipe)
3209 end_update(view, TRUE);
3210 if (!view->ops->open(view)) {
3211 report("Failed to load %s view", view->name);
3212 return;
3214 restore_view_position(view);
3216 } else if ((reload || strcmp(view->vid, view->id)) &&
3217 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3218 report("Failed to load %s view", view->name);
3219 return;
3222 if (split && prev->lineno - prev->offset >= prev->height) {
3223 /* Take the title line into account. */
3224 int lines = prev->lineno - prev->offset - prev->height + 1;
3226 /* Scroll the view that was split if the current line is
3227 * outside the new limited view. */
3228 do_scroll_view(prev, lines);
3231 if (prev && view != prev) {
3232 if (split) {
3233 /* "Blur" the previous view. */
3234 update_view_title(prev);
3238 if (view->pipe && view->lines == 0) {
3239 /* Clear the old view and let the incremental updating refill
3240 * the screen. */
3241 werase(view->win);
3242 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3243 report("");
3244 } else if (view_is_displayed(view)) {
3245 redraw_view(view);
3246 report("");
3250 static void
3251 open_external_viewer(const char *argv[], const char *dir)
3253 def_prog_mode(); /* save current tty modes */
3254 endwin(); /* restore original tty modes */
3255 run_io_fg(argv, dir);
3256 fprintf(stderr, "Press Enter to continue");
3257 getc(opt_tty);
3258 reset_prog_mode();
3259 redraw_display(TRUE);
3262 static void
3263 open_mergetool(const char *file)
3265 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3267 open_external_viewer(mergetool_argv, opt_cdup);
3270 static void
3271 open_editor(bool from_root, const char *file)
3273 const char *editor_argv[] = { "vi", file, NULL };
3274 const char *editor;
3276 editor = getenv("GIT_EDITOR");
3277 if (!editor && *opt_editor)
3278 editor = opt_editor;
3279 if (!editor)
3280 editor = getenv("VISUAL");
3281 if (!editor)
3282 editor = getenv("EDITOR");
3283 if (!editor)
3284 editor = "vi";
3286 editor_argv[0] = editor;
3287 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3290 static void
3291 open_run_request(enum request request)
3293 struct run_request *req = get_run_request(request);
3294 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3296 if (!req) {
3297 report("Unknown run request");
3298 return;
3301 if (format_argv(argv, req->argv, FORMAT_ALL))
3302 open_external_viewer(argv, NULL);
3303 free_argv(argv);
3307 * User request switch noodle
3310 static int
3311 view_driver(struct view *view, enum request request)
3313 int i;
3315 if (request == REQ_NONE)
3316 return TRUE;
3318 if (request > REQ_NONE) {
3319 open_run_request(request);
3320 /* FIXME: When all views can refresh always do this. */
3321 if (view == VIEW(REQ_VIEW_STATUS) ||
3322 view == VIEW(REQ_VIEW_MAIN) ||
3323 view == VIEW(REQ_VIEW_LOG) ||
3324 view == VIEW(REQ_VIEW_BRANCH) ||
3325 view == VIEW(REQ_VIEW_STAGE))
3326 request = REQ_REFRESH;
3327 else
3328 return TRUE;
3331 if (view && view->lines) {
3332 request = view->ops->request(view, request, &view->line[view->lineno]);
3333 if (request == REQ_NONE)
3334 return TRUE;
3337 switch (request) {
3338 case REQ_MOVE_UP:
3339 case REQ_MOVE_DOWN:
3340 case REQ_MOVE_PAGE_UP:
3341 case REQ_MOVE_PAGE_DOWN:
3342 case REQ_MOVE_FIRST_LINE:
3343 case REQ_MOVE_LAST_LINE:
3344 move_view(view, request);
3345 break;
3347 case REQ_SCROLL_LEFT:
3348 case REQ_SCROLL_RIGHT:
3349 case REQ_SCROLL_LINE_DOWN:
3350 case REQ_SCROLL_LINE_UP:
3351 case REQ_SCROLL_PAGE_DOWN:
3352 case REQ_SCROLL_PAGE_UP:
3353 scroll_view(view, request);
3354 break;
3356 case REQ_VIEW_BLAME:
3357 if (!opt_file[0]) {
3358 report("No file chosen, press %s to open tree view",
3359 get_key(view->keymap, REQ_VIEW_TREE));
3360 break;
3362 open_view(view, request, OPEN_DEFAULT);
3363 break;
3365 case REQ_VIEW_BLOB:
3366 if (!ref_blob[0]) {
3367 report("No file chosen, press %s to open tree view",
3368 get_key(view->keymap, REQ_VIEW_TREE));
3369 break;
3371 open_view(view, request, OPEN_DEFAULT);
3372 break;
3374 case REQ_VIEW_PAGER:
3375 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3376 report("No pager content, press %s to run command from prompt",
3377 get_key(view->keymap, REQ_PROMPT));
3378 break;
3380 open_view(view, request, OPEN_DEFAULT);
3381 break;
3383 case REQ_VIEW_STAGE:
3384 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3385 report("No stage content, press %s to open the status view and choose file",
3386 get_key(view->keymap, REQ_VIEW_STATUS));
3387 break;
3389 open_view(view, request, OPEN_DEFAULT);
3390 break;
3392 case REQ_VIEW_STATUS:
3393 if (opt_is_inside_work_tree == FALSE) {
3394 report("The status view requires a working tree");
3395 break;
3397 open_view(view, request, OPEN_DEFAULT);
3398 break;
3400 case REQ_VIEW_MAIN:
3401 case REQ_VIEW_DIFF:
3402 case REQ_VIEW_LOG:
3403 case REQ_VIEW_TREE:
3404 case REQ_VIEW_HELP:
3405 case REQ_VIEW_BRANCH:
3406 open_view(view, request, OPEN_DEFAULT);
3407 break;
3409 case REQ_NEXT:
3410 case REQ_PREVIOUS:
3411 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3413 if ((view == VIEW(REQ_VIEW_DIFF) &&
3414 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3415 (view == VIEW(REQ_VIEW_DIFF) &&
3416 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3417 (view == VIEW(REQ_VIEW_STAGE) &&
3418 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3419 (view == VIEW(REQ_VIEW_BLOB) &&
3420 view->parent == VIEW(REQ_VIEW_TREE)) ||
3421 (view == VIEW(REQ_VIEW_MAIN) &&
3422 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3423 int line;
3425 view = view->parent;
3426 line = view->lineno;
3427 move_view(view, request);
3428 if (view_is_displayed(view))
3429 update_view_title(view);
3430 if (line != view->lineno)
3431 view->ops->request(view, REQ_ENTER,
3432 &view->line[view->lineno]);
3434 } else {
3435 move_view(view, request);
3437 break;
3439 case REQ_VIEW_NEXT:
3441 int nviews = displayed_views();
3442 int next_view = (current_view + 1) % nviews;
3444 if (next_view == current_view) {
3445 report("Only one view is displayed");
3446 break;
3449 current_view = next_view;
3450 /* Blur out the title of the previous view. */
3451 update_view_title(view);
3452 report("");
3453 break;
3455 case REQ_REFRESH:
3456 report("Refreshing is not yet supported for the %s view", view->name);
3457 break;
3459 case REQ_MAXIMIZE:
3460 if (displayed_views() == 2)
3461 maximize_view(view);
3462 break;
3464 case REQ_OPTIONS:
3465 open_option_menu();
3466 break;
3468 case REQ_TOGGLE_LINENO:
3469 toggle_view_option(&opt_line_number, "line numbers");
3470 break;
3472 case REQ_TOGGLE_DATE:
3473 toggle_date_option(&opt_date);
3474 break;
3476 case REQ_TOGGLE_AUTHOR:
3477 toggle_view_option(&opt_author, "author display");
3478 break;
3480 case REQ_TOGGLE_REV_GRAPH:
3481 toggle_view_option(&opt_rev_graph, "revision graph display");
3482 break;
3484 case REQ_TOGGLE_REFS:
3485 toggle_view_option(&opt_show_refs, "reference display");
3486 break;
3488 case REQ_TOGGLE_SORT_FIELD:
3489 case REQ_TOGGLE_SORT_ORDER:
3490 report("Sorting is not yet supported for the %s view", view->name);
3491 break;
3493 case REQ_SEARCH:
3494 case REQ_SEARCH_BACK:
3495 search_view(view, request);
3496 break;
3498 case REQ_FIND_NEXT:
3499 case REQ_FIND_PREV:
3500 find_next(view, request);
3501 break;
3503 case REQ_STOP_LOADING:
3504 for (i = 0; i < ARRAY_SIZE(views); i++) {
3505 view = &views[i];
3506 if (view->pipe)
3507 report("Stopped loading the %s view", view->name),
3508 end_update(view, TRUE);
3510 break;
3512 case REQ_SHOW_VERSION:
3513 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3514 return TRUE;
3516 case REQ_SCREEN_REDRAW:
3517 redraw_display(TRUE);
3518 break;
3520 case REQ_EDIT:
3521 report("Nothing to edit");
3522 break;
3524 case REQ_ENTER:
3525 report("Nothing to enter");
3526 break;
3528 case REQ_VIEW_CLOSE:
3529 /* XXX: Mark closed views by letting view->parent point to the
3530 * view itself. Parents to closed view should never be
3531 * followed. */
3532 if (view->parent &&
3533 view->parent->parent != view->parent) {
3534 maximize_view(view->parent);
3535 view->parent = view;
3536 break;
3538 /* Fall-through */
3539 case REQ_QUIT:
3540 return FALSE;
3542 default:
3543 report("Unknown key, press %s for help",
3544 get_key(view->keymap, REQ_VIEW_HELP));
3545 return TRUE;
3548 return TRUE;
3553 * View backend utilities
3556 enum sort_field {
3557 ORDERBY_NAME,
3558 ORDERBY_DATE,
3559 ORDERBY_AUTHOR,
3562 struct sort_state {
3563 const enum sort_field *fields;
3564 size_t size, current;
3565 bool reverse;
3568 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3569 #define get_sort_field(state) ((state).fields[(state).current])
3570 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3572 static void
3573 sort_view(struct view *view, enum request request, struct sort_state *state,
3574 int (*compare)(const void *, const void *))
3576 switch (request) {
3577 case REQ_TOGGLE_SORT_FIELD:
3578 state->current = (state->current + 1) % state->size;
3579 break;
3581 case REQ_TOGGLE_SORT_ORDER:
3582 state->reverse = !state->reverse;
3583 break;
3584 default:
3585 die("Not a sort request");
3588 qsort(view->line, view->lines, sizeof(*view->line), compare);
3589 redraw_view(view);
3592 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3594 /* Small author cache to reduce memory consumption. It uses binary
3595 * search to lookup or find place to position new entries. No entries
3596 * are ever freed. */
3597 static const char *
3598 get_author(const char *name)
3600 static const char **authors;
3601 static size_t authors_size;
3602 int from = 0, to = authors_size - 1;
3604 while (from <= to) {
3605 size_t pos = (to + from) / 2;
3606 int cmp = strcmp(name, authors[pos]);
3608 if (!cmp)
3609 return authors[pos];
3611 if (cmp < 0)
3612 to = pos - 1;
3613 else
3614 from = pos + 1;
3617 if (!realloc_authors(&authors, authors_size, 1))
3618 return NULL;
3619 name = strdup(name);
3620 if (!name)
3621 return NULL;
3623 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3624 authors[from] = name;
3625 authors_size++;
3627 return name;
3630 static void
3631 parse_timezone(time_t *time, const char *zone)
3633 long tz;
3635 tz = ('0' - zone[1]) * 60 * 60 * 10;
3636 tz += ('0' - zone[2]) * 60 * 60;
3637 tz += ('0' - zone[3]) * 60;
3638 tz += ('0' - zone[4]);
3640 if (zone[0] == '-')
3641 tz = -tz;
3643 *time -= tz;
3646 /* Parse author lines where the name may be empty:
3647 * author <email@address.tld> 1138474660 +0100
3649 static void
3650 parse_author_line(char *ident, const char **author, time_t *time)
3652 char *nameend = strchr(ident, '<');
3653 char *emailend = strchr(ident, '>');
3655 if (nameend && emailend)
3656 *nameend = *emailend = 0;
3657 ident = chomp_string(ident);
3658 if (!*ident) {
3659 if (nameend)
3660 ident = chomp_string(nameend + 1);
3661 if (!*ident)
3662 ident = "Unknown";
3665 *author = get_author(ident);
3667 /* Parse epoch and timezone */
3668 if (emailend && emailend[1] == ' ') {
3669 char *secs = emailend + 2;
3670 char *zone = strchr(secs, ' ');
3672 *time = (time_t) atol(secs);
3674 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3675 parse_timezone(time, zone + 1);
3679 static bool
3680 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3682 char rev[SIZEOF_REV];
3683 const char *revlist_argv[] = {
3684 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3686 struct menu_item *items;
3687 char text[SIZEOF_STR];
3688 bool ok = TRUE;
3689 int i;
3691 items = calloc(*parents + 1, sizeof(*items));
3692 if (!items)
3693 return FALSE;
3695 for (i = 0; i < *parents; i++) {
3696 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3697 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3698 !(items[i].text = strdup(text))) {
3699 ok = FALSE;
3700 break;
3704 if (ok) {
3705 *parents = 0;
3706 ok = prompt_menu("Select parent", items, parents);
3708 for (i = 0; items[i].text; i++)
3709 free((char *) items[i].text);
3710 free(items);
3711 return ok;
3714 static bool
3715 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3717 char buf[SIZEOF_STR * 4];
3718 const char *revlist_argv[] = {
3719 "git", "log", "--no-color", "-1",
3720 "--pretty=format:%P", id, "--", path, NULL
3722 int parents;
3724 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3725 (parents = strlen(buf) / 40) < 0) {
3726 report("Failed to get parent information");
3727 return FALSE;
3729 } else if (parents == 0) {
3730 if (path)
3731 report("Path '%s' does not exist in the parent", path);
3732 else
3733 report("The selected commit has no parents");
3734 return FALSE;
3737 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3738 return FALSE;
3740 string_copy_rev(rev, &buf[41 * parents]);
3741 return TRUE;
3745 * Pager backend
3748 static bool
3749 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3751 char text[SIZEOF_STR];
3753 if (opt_line_number && draw_lineno(view, lineno))
3754 return TRUE;
3756 string_expand(text, sizeof(text), line->data, opt_tab_size);
3757 draw_text(view, line->type, text, TRUE);
3758 return TRUE;
3761 static bool
3762 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3764 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3765 char ref[SIZEOF_STR];
3767 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3768 return TRUE;
3770 /* This is the only fatal call, since it can "corrupt" the buffer. */
3771 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3772 return FALSE;
3774 return TRUE;
3777 static void
3778 add_pager_refs(struct view *view, struct line *line)
3780 char buf[SIZEOF_STR];
3781 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3782 struct ref_list *list;
3783 size_t bufpos = 0, i;
3784 const char *sep = "Refs: ";
3785 bool is_tag = FALSE;
3787 assert(line->type == LINE_COMMIT);
3789 list = get_ref_list(commit_id);
3790 if (!list) {
3791 if (view == VIEW(REQ_VIEW_DIFF))
3792 goto try_add_describe_ref;
3793 return;
3796 for (i = 0; i < list->size; i++) {
3797 struct ref *ref = list->refs[i];
3798 const char *fmt = ref->tag ? "%s[%s]" :
3799 ref->remote ? "%s<%s>" : "%s%s";
3801 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3802 return;
3803 sep = ", ";
3804 if (ref->tag)
3805 is_tag = TRUE;
3808 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3809 try_add_describe_ref:
3810 /* Add <tag>-g<commit_id> "fake" reference. */
3811 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3812 return;
3815 if (bufpos == 0)
3816 return;
3818 add_line_text(view, buf, LINE_PP_REFS);
3821 static bool
3822 pager_read(struct view *view, char *data)
3824 struct line *line;
3826 if (!data)
3827 return TRUE;
3829 line = add_line_text(view, data, get_line_type(data));
3830 if (!line)
3831 return FALSE;
3833 if (line->type == LINE_COMMIT &&
3834 (view == VIEW(REQ_VIEW_DIFF) ||
3835 view == VIEW(REQ_VIEW_LOG)))
3836 add_pager_refs(view, line);
3838 return TRUE;
3841 static enum request
3842 pager_request(struct view *view, enum request request, struct line *line)
3844 int split = 0;
3846 if (request != REQ_ENTER)
3847 return request;
3849 if (line->type == LINE_COMMIT &&
3850 (view == VIEW(REQ_VIEW_LOG) ||
3851 view == VIEW(REQ_VIEW_PAGER))) {
3852 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3853 split = 1;
3856 /* Always scroll the view even if it was split. That way
3857 * you can use Enter to scroll through the log view and
3858 * split open each commit diff. */
3859 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3861 /* FIXME: A minor workaround. Scrolling the view will call report("")
3862 * but if we are scrolling a non-current view this won't properly
3863 * update the view title. */
3864 if (split)
3865 update_view_title(view);
3867 return REQ_NONE;
3870 static bool
3871 pager_grep(struct view *view, struct line *line)
3873 const char *text[] = { line->data, NULL };
3875 return grep_text(view, text);
3878 static void
3879 pager_select(struct view *view, struct line *line)
3881 if (line->type == LINE_COMMIT) {
3882 char *text = (char *)line->data + STRING_SIZE("commit ");
3884 if (view != VIEW(REQ_VIEW_PAGER))
3885 string_copy_rev(view->ref, text);
3886 string_copy_rev(ref_commit, text);
3890 static struct view_ops pager_ops = {
3891 "line",
3892 NULL,
3893 NULL,
3894 pager_read,
3895 pager_draw,
3896 pager_request,
3897 pager_grep,
3898 pager_select,
3901 static const char *log_argv[SIZEOF_ARG] = {
3902 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3905 static enum request
3906 log_request(struct view *view, enum request request, struct line *line)
3908 switch (request) {
3909 case REQ_REFRESH:
3910 load_refs();
3911 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3912 return REQ_NONE;
3913 default:
3914 return pager_request(view, request, line);
3918 static struct view_ops log_ops = {
3919 "line",
3920 log_argv,
3921 NULL,
3922 pager_read,
3923 pager_draw,
3924 log_request,
3925 pager_grep,
3926 pager_select,
3929 static const char *diff_argv[SIZEOF_ARG] = {
3930 "git", "show", "--pretty=fuller", "--no-color", "--root",
3931 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3934 static struct view_ops diff_ops = {
3935 "line",
3936 diff_argv,
3937 NULL,
3938 pager_read,
3939 pager_draw,
3940 pager_request,
3941 pager_grep,
3942 pager_select,
3946 * Help backend
3949 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
3951 static char *
3952 help_name(char buf[SIZEOF_STR], const char *name, size_t namelen)
3954 int bufpos;
3956 for (bufpos = 0; bufpos <= namelen; bufpos++) {
3957 buf[bufpos] = tolower(name[bufpos]);
3958 if (buf[bufpos] == '_')
3959 buf[bufpos] = '-';
3962 buf[bufpos] = 0;
3963 return buf;
3966 #define help_keymap_name(buf, keymap) \
3967 help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen)
3969 static bool
3970 help_open_keymap_title(struct view *view, enum keymap keymap)
3972 char buf[SIZEOF_STR];
3973 struct line *line;
3975 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
3976 help_keymap_hidden[keymap] ? '+' : '-',
3977 help_keymap_name(buf, keymap));
3978 if (line)
3979 line->other = keymap;
3981 return help_keymap_hidden[keymap];
3984 static void
3985 help_open_keymap(struct view *view, enum keymap keymap)
3987 const char *group = NULL;
3988 char buf[SIZEOF_STR];
3989 size_t bufpos;
3990 bool add_title = TRUE;
3991 int i;
3993 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3994 const char *key = NULL;
3996 if (req_info[i].request == REQ_NONE)
3997 continue;
3999 if (!req_info[i].request) {
4000 group = req_info[i].help;
4001 continue;
4004 key = get_keys(keymap, req_info[i].request, TRUE);
4005 if (!key || !*key)
4006 continue;
4008 if (add_title && help_open_keymap_title(view, keymap))
4009 return;
4010 add_title = false;
4012 if (group) {
4013 add_line_text(view, group, LINE_HELP_GROUP);
4014 group = NULL;
4017 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4018 help_name(buf, req_info[i].name, req_info[i].namelen),
4019 req_info[i].help);
4022 group = "External commands:";
4024 for (i = 0; i < run_requests; i++) {
4025 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4026 const char *key;
4027 int argc;
4029 if (!req || req->keymap != keymap)
4030 continue;
4032 key = get_key_name(req->key);
4033 if (!*key)
4034 key = "(no key defined)";
4036 if (add_title && help_open_keymap_title(view, keymap))
4037 return;
4038 if (group) {
4039 add_line_text(view, group, LINE_HELP_GROUP);
4040 group = NULL;
4043 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4044 if (!string_format_from(buf, &bufpos, "%s%s",
4045 argc ? " " : "", req->argv[argc]))
4046 return;
4048 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4052 static bool
4053 help_open(struct view *view)
4055 enum keymap keymap;
4057 reset_view(view);
4058 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4059 add_line_text(view, "", LINE_DEFAULT);
4061 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4062 help_open_keymap(view, keymap);
4064 return TRUE;
4067 static enum request
4068 help_request(struct view *view, enum request request, struct line *line)
4070 switch (request) {
4071 case REQ_ENTER:
4072 if (line->type == LINE_HELP_KEYMAP) {
4073 help_keymap_hidden[line->other] =
4074 !help_keymap_hidden[line->other];
4075 view->p_restore = TRUE;
4076 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4079 return REQ_NONE;
4080 default:
4081 return pager_request(view, request, line);
4085 static struct view_ops help_ops = {
4086 "line",
4087 NULL,
4088 help_open,
4089 NULL,
4090 pager_draw,
4091 help_request,
4092 pager_grep,
4093 pager_select,
4098 * Tree backend
4101 struct tree_stack_entry {
4102 struct tree_stack_entry *prev; /* Entry below this in the stack */
4103 unsigned long lineno; /* Line number to restore */
4104 char *name; /* Position of name in opt_path */
4107 /* The top of the path stack. */
4108 static struct tree_stack_entry *tree_stack = NULL;
4109 unsigned long tree_lineno = 0;
4111 static void
4112 pop_tree_stack_entry(void)
4114 struct tree_stack_entry *entry = tree_stack;
4116 tree_lineno = entry->lineno;
4117 entry->name[0] = 0;
4118 tree_stack = entry->prev;
4119 free(entry);
4122 static void
4123 push_tree_stack_entry(const char *name, unsigned long lineno)
4125 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4126 size_t pathlen = strlen(opt_path);
4128 if (!entry)
4129 return;
4131 entry->prev = tree_stack;
4132 entry->name = opt_path + pathlen;
4133 tree_stack = entry;
4135 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4136 pop_tree_stack_entry();
4137 return;
4140 /* Move the current line to the first tree entry. */
4141 tree_lineno = 1;
4142 entry->lineno = lineno;
4145 /* Parse output from git-ls-tree(1):
4147 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4150 #define SIZEOF_TREE_ATTR \
4151 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4153 #define SIZEOF_TREE_MODE \
4154 STRING_SIZE("100644 ")
4156 #define TREE_ID_OFFSET \
4157 STRING_SIZE("100644 blob ")
4159 struct tree_entry {
4160 char id[SIZEOF_REV];
4161 mode_t mode;
4162 time_t time; /* Date from the author ident. */
4163 const char *author; /* Author of the commit. */
4164 char name[1];
4167 static const char *
4168 tree_path(const struct line *line)
4170 return ((struct tree_entry *) line->data)->name;
4173 static int
4174 tree_compare_entry(const struct line *line1, const struct line *line2)
4176 if (line1->type != line2->type)
4177 return line1->type == LINE_TREE_DIR ? -1 : 1;
4178 return strcmp(tree_path(line1), tree_path(line2));
4181 static const enum sort_field tree_sort_fields[] = {
4182 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4184 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4186 static int
4187 tree_compare(const void *l1, const void *l2)
4189 const struct line *line1 = (const struct line *) l1;
4190 const struct line *line2 = (const struct line *) l2;
4191 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4192 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4194 if (line1->type == LINE_TREE_HEAD)
4195 return -1;
4196 if (line2->type == LINE_TREE_HEAD)
4197 return 1;
4199 switch (get_sort_field(tree_sort_state)) {
4200 case ORDERBY_DATE:
4201 return sort_order(tree_sort_state, entry1->time - entry2->time);
4203 case ORDERBY_AUTHOR:
4204 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4206 case ORDERBY_NAME:
4207 default:
4208 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4213 static struct line *
4214 tree_entry(struct view *view, enum line_type type, const char *path,
4215 const char *mode, const char *id)
4217 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4218 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4220 if (!entry || !line) {
4221 free(entry);
4222 return NULL;
4225 strncpy(entry->name, path, strlen(path));
4226 if (mode)
4227 entry->mode = strtoul(mode, NULL, 8);
4228 if (id)
4229 string_copy_rev(entry->id, id);
4231 return line;
4234 static bool
4235 tree_read_date(struct view *view, char *text, bool *read_date)
4237 static const char *author_name;
4238 static time_t author_time;
4240 if (!text && *read_date) {
4241 *read_date = FALSE;
4242 return TRUE;
4244 } else if (!text) {
4245 char *path = *opt_path ? opt_path : ".";
4246 /* Find next entry to process */
4247 const char *log_file[] = {
4248 "git", "log", "--no-color", "--pretty=raw",
4249 "--cc", "--raw", view->id, "--", path, NULL
4251 struct io io = {};
4253 if (!view->lines) {
4254 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4255 report("Tree is empty");
4256 return TRUE;
4259 if (!run_io_rd_dir(&io, log_file, opt_cdup, FORMAT_NONE)) {
4260 report("Failed to load tree data");
4261 return TRUE;
4264 done_io(view->pipe);
4265 view->io = io;
4266 *read_date = TRUE;
4267 return FALSE;
4269 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4270 parse_author_line(text + STRING_SIZE("author "),
4271 &author_name, &author_time);
4273 } else if (*text == ':') {
4274 char *pos;
4275 size_t annotated = 1;
4276 size_t i;
4278 pos = strchr(text, '\t');
4279 if (!pos)
4280 return TRUE;
4281 text = pos + 1;
4282 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4283 text += strlen(opt_path);
4284 pos = strchr(text, '/');
4285 if (pos)
4286 *pos = 0;
4288 for (i = 1; i < view->lines; i++) {
4289 struct line *line = &view->line[i];
4290 struct tree_entry *entry = line->data;
4292 annotated += !!entry->author;
4293 if (entry->author || strcmp(entry->name, text))
4294 continue;
4296 entry->author = author_name;
4297 entry->time = author_time;
4298 line->dirty = 1;
4299 break;
4302 if (annotated == view->lines)
4303 kill_io(view->pipe);
4305 return TRUE;
4308 static bool
4309 tree_read(struct view *view, char *text)
4311 static bool read_date = FALSE;
4312 struct tree_entry *data;
4313 struct line *entry, *line;
4314 enum line_type type;
4315 size_t textlen = text ? strlen(text) : 0;
4316 char *path = text + SIZEOF_TREE_ATTR;
4318 if (read_date || !text)
4319 return tree_read_date(view, text, &read_date);
4321 if (textlen <= SIZEOF_TREE_ATTR)
4322 return FALSE;
4323 if (view->lines == 0 &&
4324 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4325 return FALSE;
4327 /* Strip the path part ... */
4328 if (*opt_path) {
4329 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4330 size_t striplen = strlen(opt_path);
4332 if (pathlen > striplen)
4333 memmove(path, path + striplen,
4334 pathlen - striplen + 1);
4336 /* Insert "link" to parent directory. */
4337 if (view->lines == 1 &&
4338 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4339 return FALSE;
4342 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4343 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4344 if (!entry)
4345 return FALSE;
4346 data = entry->data;
4348 /* Skip "Directory ..." and ".." line. */
4349 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4350 if (tree_compare_entry(line, entry) <= 0)
4351 continue;
4353 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4355 line->data = data;
4356 line->type = type;
4357 for (; line <= entry; line++)
4358 line->dirty = line->cleareol = 1;
4359 return TRUE;
4362 if (tree_lineno > view->lineno) {
4363 view->lineno = tree_lineno;
4364 tree_lineno = 0;
4367 return TRUE;
4370 static bool
4371 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4373 struct tree_entry *entry = line->data;
4375 if (line->type == LINE_TREE_HEAD) {
4376 if (draw_text(view, line->type, "Directory path /", TRUE))
4377 return TRUE;
4378 } else {
4379 if (draw_mode(view, entry->mode))
4380 return TRUE;
4382 if (opt_author && draw_author(view, entry->author))
4383 return TRUE;
4385 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4386 return TRUE;
4388 if (draw_text(view, line->type, entry->name, TRUE))
4389 return TRUE;
4390 return TRUE;
4393 static void
4394 open_blob_editor()
4396 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4397 int fd = mkstemp(file);
4399 if (fd == -1)
4400 report("Failed to create temporary file");
4401 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4402 report("Failed to save blob data to file");
4403 else
4404 open_editor(FALSE, file);
4405 if (fd != -1)
4406 unlink(file);
4409 static enum request
4410 tree_request(struct view *view, enum request request, struct line *line)
4412 enum open_flags flags;
4414 switch (request) {
4415 case REQ_VIEW_BLAME:
4416 if (line->type != LINE_TREE_FILE) {
4417 report("Blame only supported for files");
4418 return REQ_NONE;
4421 string_copy(opt_ref, view->vid);
4422 return request;
4424 case REQ_EDIT:
4425 if (line->type != LINE_TREE_FILE) {
4426 report("Edit only supported for files");
4427 } else if (!is_head_commit(view->vid)) {
4428 open_blob_editor();
4429 } else {
4430 open_editor(TRUE, opt_file);
4432 return REQ_NONE;
4434 case REQ_TOGGLE_SORT_FIELD:
4435 case REQ_TOGGLE_SORT_ORDER:
4436 sort_view(view, request, &tree_sort_state, tree_compare);
4437 return REQ_NONE;
4439 case REQ_PARENT:
4440 if (!*opt_path) {
4441 /* quit view if at top of tree */
4442 return REQ_VIEW_CLOSE;
4444 /* fake 'cd ..' */
4445 line = &view->line[1];
4446 break;
4448 case REQ_ENTER:
4449 break;
4451 default:
4452 return request;
4455 /* Cleanup the stack if the tree view is at a different tree. */
4456 while (!*opt_path && tree_stack)
4457 pop_tree_stack_entry();
4459 switch (line->type) {
4460 case LINE_TREE_DIR:
4461 /* Depending on whether it is a subdirectory or parent link
4462 * mangle the path buffer. */
4463 if (line == &view->line[1] && *opt_path) {
4464 pop_tree_stack_entry();
4466 } else {
4467 const char *basename = tree_path(line);
4469 push_tree_stack_entry(basename, view->lineno);
4472 /* Trees and subtrees share the same ID, so they are not not
4473 * unique like blobs. */
4474 flags = OPEN_RELOAD;
4475 request = REQ_VIEW_TREE;
4476 break;
4478 case LINE_TREE_FILE:
4479 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4480 request = REQ_VIEW_BLOB;
4481 break;
4483 default:
4484 return REQ_NONE;
4487 open_view(view, request, flags);
4488 if (request == REQ_VIEW_TREE)
4489 view->lineno = tree_lineno;
4491 return REQ_NONE;
4494 static bool
4495 tree_grep(struct view *view, struct line *line)
4497 struct tree_entry *entry = line->data;
4498 const char *text[] = {
4499 entry->name,
4500 opt_author ? entry->author : "",
4501 opt_date ? mkdate(&entry->time) : "",
4502 NULL
4505 return grep_text(view, text);
4508 static void
4509 tree_select(struct view *view, struct line *line)
4511 struct tree_entry *entry = line->data;
4513 if (line->type == LINE_TREE_FILE) {
4514 string_copy_rev(ref_blob, entry->id);
4515 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4517 } else if (line->type != LINE_TREE_DIR) {
4518 return;
4521 string_copy_rev(view->ref, entry->id);
4524 static bool
4525 tree_prepare(struct view *view)
4527 if (view->lines == 0 && opt_prefix[0]) {
4528 char *pos = opt_prefix;
4530 while (pos && *pos) {
4531 char *end = strchr(pos, '/');
4533 if (end)
4534 *end = 0;
4535 push_tree_stack_entry(pos, 0);
4536 pos = end;
4537 if (end) {
4538 *end = '/';
4539 pos++;
4543 } else if (strcmp(view->vid, view->id)) {
4544 opt_path[0] = 0;
4547 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4550 static const char *tree_argv[SIZEOF_ARG] = {
4551 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4554 static struct view_ops tree_ops = {
4555 "file",
4556 tree_argv,
4557 NULL,
4558 tree_read,
4559 tree_draw,
4560 tree_request,
4561 tree_grep,
4562 tree_select,
4563 tree_prepare,
4566 static bool
4567 blob_read(struct view *view, char *line)
4569 if (!line)
4570 return TRUE;
4571 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4574 static enum request
4575 blob_request(struct view *view, enum request request, struct line *line)
4577 switch (request) {
4578 case REQ_EDIT:
4579 open_blob_editor();
4580 return REQ_NONE;
4581 default:
4582 return pager_request(view, request, line);
4586 static const char *blob_argv[SIZEOF_ARG] = {
4587 "git", "cat-file", "blob", "%(blob)", NULL
4590 static struct view_ops blob_ops = {
4591 "line",
4592 blob_argv,
4593 NULL,
4594 blob_read,
4595 pager_draw,
4596 blob_request,
4597 pager_grep,
4598 pager_select,
4602 * Blame backend
4604 * Loading the blame view is a two phase job:
4606 * 1. File content is read either using opt_file from the
4607 * filesystem or using git-cat-file.
4608 * 2. Then blame information is incrementally added by
4609 * reading output from git-blame.
4612 static const char *blame_head_argv[] = {
4613 "git", "blame", "--incremental", "--", "%(file)", NULL
4616 static const char *blame_ref_argv[] = {
4617 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4620 static const char *blame_cat_file_argv[] = {
4621 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4624 struct blame_commit {
4625 char id[SIZEOF_REV]; /* SHA1 ID. */
4626 char title[128]; /* First line of the commit message. */
4627 const char *author; /* Author of the commit. */
4628 time_t time; /* Date from the author ident. */
4629 char filename[128]; /* Name of file. */
4630 bool has_previous; /* Was a "previous" line detected. */
4633 struct blame {
4634 struct blame_commit *commit;
4635 unsigned long lineno;
4636 char text[1];
4639 static bool
4640 blame_open(struct view *view)
4642 char path[SIZEOF_STR];
4644 if (!view->parent && *opt_prefix) {
4645 string_copy(path, opt_file);
4646 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4647 return FALSE;
4650 if (!string_format(path, "%s%s", opt_cdup, opt_file))
4651 return FALSE;
4653 if (*opt_ref || !io_open(&view->io, path)) {
4654 if (!run_io_rd_dir(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4655 return FALSE;
4658 setup_update(view, opt_file);
4659 string_format(view->ref, "%s ...", opt_file);
4661 return TRUE;
4664 static struct blame_commit *
4665 get_blame_commit(struct view *view, const char *id)
4667 size_t i;
4669 for (i = 0; i < view->lines; i++) {
4670 struct blame *blame = view->line[i].data;
4672 if (!blame->commit)
4673 continue;
4675 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4676 return blame->commit;
4680 struct blame_commit *commit = calloc(1, sizeof(*commit));
4682 if (commit)
4683 string_ncopy(commit->id, id, SIZEOF_REV);
4684 return commit;
4688 static bool
4689 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4691 const char *pos = *posref;
4693 *posref = NULL;
4694 pos = strchr(pos + 1, ' ');
4695 if (!pos || !isdigit(pos[1]))
4696 return FALSE;
4697 *number = atoi(pos + 1);
4698 if (*number < min || *number > max)
4699 return FALSE;
4701 *posref = pos;
4702 return TRUE;
4705 static struct blame_commit *
4706 parse_blame_commit(struct view *view, const char *text, int *blamed)
4708 struct blame_commit *commit;
4709 struct blame *blame;
4710 const char *pos = text + SIZEOF_REV - 2;
4711 size_t orig_lineno = 0;
4712 size_t lineno;
4713 size_t group;
4715 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4716 return NULL;
4718 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4719 !parse_number(&pos, &lineno, 1, view->lines) ||
4720 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4721 return NULL;
4723 commit = get_blame_commit(view, text);
4724 if (!commit)
4725 return NULL;
4727 *blamed += group;
4728 while (group--) {
4729 struct line *line = &view->line[lineno + group - 1];
4731 blame = line->data;
4732 blame->commit = commit;
4733 blame->lineno = orig_lineno + group - 1;
4734 line->dirty = 1;
4737 return commit;
4740 static bool
4741 blame_read_file(struct view *view, const char *line, bool *read_file)
4743 if (!line) {
4744 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4745 struct io io = {};
4747 if (view->lines == 0 && !view->parent)
4748 die("No blame exist for %s", view->vid);
4750 if (view->lines == 0 || !run_io_rd_dir(&io, argv, opt_cdup, FORMAT_ALL)) {
4751 report("Failed to load blame data");
4752 return TRUE;
4755 done_io(view->pipe);
4756 view->io = io;
4757 *read_file = FALSE;
4758 return FALSE;
4760 } else {
4761 size_t linelen = strlen(line);
4762 struct blame *blame = malloc(sizeof(*blame) + linelen);
4764 if (!blame)
4765 return FALSE;
4767 blame->commit = NULL;
4768 strncpy(blame->text, line, linelen);
4769 blame->text[linelen] = 0;
4770 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4774 static bool
4775 match_blame_header(const char *name, char **line)
4777 size_t namelen = strlen(name);
4778 bool matched = !strncmp(name, *line, namelen);
4780 if (matched)
4781 *line += namelen;
4783 return matched;
4786 static bool
4787 blame_read(struct view *view, char *line)
4789 static struct blame_commit *commit = NULL;
4790 static int blamed = 0;
4791 static bool read_file = TRUE;
4793 if (read_file)
4794 return blame_read_file(view, line, &read_file);
4796 if (!line) {
4797 /* Reset all! */
4798 commit = NULL;
4799 blamed = 0;
4800 read_file = TRUE;
4801 string_format(view->ref, "%s", view->vid);
4802 if (view_is_displayed(view)) {
4803 update_view_title(view);
4804 redraw_view_from(view, 0);
4806 return TRUE;
4809 if (!commit) {
4810 commit = parse_blame_commit(view, line, &blamed);
4811 string_format(view->ref, "%s %2d%%", view->vid,
4812 view->lines ? blamed * 100 / view->lines : 0);
4814 } else if (match_blame_header("author ", &line)) {
4815 commit->author = get_author(line);
4817 } else if (match_blame_header("author-time ", &line)) {
4818 commit->time = (time_t) atol(line);
4820 } else if (match_blame_header("author-tz ", &line)) {
4821 parse_timezone(&commit->time, line);
4823 } else if (match_blame_header("summary ", &line)) {
4824 string_ncopy(commit->title, line, strlen(line));
4826 } else if (match_blame_header("previous ", &line)) {
4827 commit->has_previous = TRUE;
4829 } else if (match_blame_header("filename ", &line)) {
4830 string_ncopy(commit->filename, line, strlen(line));
4831 commit = NULL;
4834 return TRUE;
4837 static bool
4838 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4840 struct blame *blame = line->data;
4841 time_t *time = NULL;
4842 const char *id = NULL, *author = NULL;
4843 char text[SIZEOF_STR];
4845 if (blame->commit && *blame->commit->filename) {
4846 id = blame->commit->id;
4847 author = blame->commit->author;
4848 time = &blame->commit->time;
4851 if (opt_date && draw_date(view, time))
4852 return TRUE;
4854 if (opt_author && draw_author(view, author))
4855 return TRUE;
4857 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4858 return TRUE;
4860 if (draw_lineno(view, lineno))
4861 return TRUE;
4863 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4864 draw_text(view, LINE_DEFAULT, text, TRUE);
4865 return TRUE;
4868 static bool
4869 check_blame_commit(struct blame *blame, bool check_null_id)
4871 if (!blame->commit)
4872 report("Commit data not loaded yet");
4873 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4874 report("No commit exist for the selected line");
4875 else
4876 return TRUE;
4877 return FALSE;
4880 static void
4881 setup_blame_parent_line(struct view *view, struct blame *blame)
4883 const char *diff_tree_argv[] = {
4884 "git", "diff-tree", "-U0", blame->commit->id,
4885 "--", blame->commit->filename, NULL
4887 struct io io = {};
4888 int parent_lineno = -1;
4889 int blamed_lineno = -1;
4890 char *line;
4892 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4893 return;
4895 while ((line = io_get(&io, '\n', TRUE))) {
4896 if (*line == '@') {
4897 char *pos = strchr(line, '+');
4899 parent_lineno = atoi(line + 4);
4900 if (pos)
4901 blamed_lineno = atoi(pos + 1);
4903 } else if (*line == '+' && parent_lineno != -1) {
4904 if (blame->lineno == blamed_lineno - 1 &&
4905 !strcmp(blame->text, line + 1)) {
4906 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4907 break;
4909 blamed_lineno++;
4913 done_io(&io);
4916 static enum request
4917 blame_request(struct view *view, enum request request, struct line *line)
4919 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4920 struct blame *blame = line->data;
4922 switch (request) {
4923 case REQ_VIEW_BLAME:
4924 if (check_blame_commit(blame, TRUE)) {
4925 string_copy(opt_ref, blame->commit->id);
4926 string_copy(opt_file, blame->commit->filename);
4927 if (blame->lineno)
4928 view->lineno = blame->lineno;
4929 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4931 break;
4933 case REQ_PARENT:
4934 if (check_blame_commit(blame, TRUE) &&
4935 select_commit_parent(blame->commit->id, opt_ref,
4936 blame->commit->filename)) {
4937 string_copy(opt_file, blame->commit->filename);
4938 setup_blame_parent_line(view, blame);
4939 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4941 break;
4943 case REQ_ENTER:
4944 if (!check_blame_commit(blame, FALSE))
4945 break;
4947 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4948 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4949 break;
4951 if (!strcmp(blame->commit->id, NULL_ID)) {
4952 struct view *diff = VIEW(REQ_VIEW_DIFF);
4953 const char *diff_index_argv[] = {
4954 "git", "diff-index", "--root", "--patch-with-stat",
4955 "-C", "-M", "HEAD", "--", view->vid, NULL
4958 if (!blame->commit->has_previous) {
4959 diff_index_argv[1] = "diff";
4960 diff_index_argv[2] = "--no-color";
4961 diff_index_argv[6] = "--";
4962 diff_index_argv[7] = "/dev/null";
4965 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4966 report("Failed to allocate diff command");
4967 break;
4969 flags |= OPEN_PREPARED;
4972 open_view(view, REQ_VIEW_DIFF, flags);
4973 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4974 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4975 break;
4977 default:
4978 return request;
4981 return REQ_NONE;
4984 static bool
4985 blame_grep(struct view *view, struct line *line)
4987 struct blame *blame = line->data;
4988 struct blame_commit *commit = blame->commit;
4989 const char *text[] = {
4990 blame->text,
4991 commit ? commit->title : "",
4992 commit ? commit->id : "",
4993 commit && opt_author ? commit->author : "",
4994 commit && opt_date ? mkdate(&commit->time) : "",
4995 NULL
4998 return grep_text(view, text);
5001 static void
5002 blame_select(struct view *view, struct line *line)
5004 struct blame *blame = line->data;
5005 struct blame_commit *commit = blame->commit;
5007 if (!commit)
5008 return;
5010 if (!strcmp(commit->id, NULL_ID))
5011 string_ncopy(ref_commit, "HEAD", 4);
5012 else
5013 string_copy_rev(ref_commit, commit->id);
5016 static struct view_ops blame_ops = {
5017 "line",
5018 NULL,
5019 blame_open,
5020 blame_read,
5021 blame_draw,
5022 blame_request,
5023 blame_grep,
5024 blame_select,
5028 * Branch backend
5031 struct branch {
5032 const char *author; /* Author of the last commit. */
5033 time_t time; /* Date of the last activity. */
5034 struct ref *ref; /* Name and commit ID information. */
5037 static const enum sort_field branch_sort_fields[] = {
5038 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5040 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5042 static int
5043 branch_compare(const void *l1, const void *l2)
5045 const struct branch *branch1 = ((const struct line *) l1)->data;
5046 const struct branch *branch2 = ((const struct line *) l2)->data;
5048 switch (get_sort_field(branch_sort_state)) {
5049 case ORDERBY_DATE:
5050 return sort_order(branch_sort_state, branch1->time - branch2->time);
5052 case ORDERBY_AUTHOR:
5053 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5055 case ORDERBY_NAME:
5056 default:
5057 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5061 static bool
5062 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5064 struct branch *branch = line->data;
5065 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5067 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5068 return TRUE;
5070 if (opt_author && draw_author(view, branch->author))
5071 return TRUE;
5073 draw_text(view, type, branch->ref->name, TRUE);
5074 return TRUE;
5077 static enum request
5078 branch_request(struct view *view, enum request request, struct line *line)
5080 switch (request) {
5081 case REQ_REFRESH:
5082 load_refs();
5083 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5084 return REQ_NONE;
5086 case REQ_TOGGLE_SORT_FIELD:
5087 case REQ_TOGGLE_SORT_ORDER:
5088 sort_view(view, request, &branch_sort_state, branch_compare);
5089 return REQ_NONE;
5091 case REQ_ENTER:
5092 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5093 return REQ_NONE;
5095 default:
5096 return request;
5100 static bool
5101 branch_read(struct view *view, char *line)
5103 static char id[SIZEOF_REV];
5104 struct branch *reference;
5105 size_t i;
5107 if (!line)
5108 return TRUE;
5110 switch (get_line_type(line)) {
5111 case LINE_COMMIT:
5112 string_copy_rev(id, line + STRING_SIZE("commit "));
5113 return TRUE;
5115 case LINE_AUTHOR:
5116 for (i = 0, reference = NULL; i < view->lines; i++) {
5117 struct branch *branch = view->line[i].data;
5119 if (strcmp(branch->ref->id, id))
5120 continue;
5122 view->line[i].dirty = TRUE;
5123 if (reference) {
5124 branch->author = reference->author;
5125 branch->time = reference->time;
5126 continue;
5129 parse_author_line(line + STRING_SIZE("author "),
5130 &branch->author, &branch->time);
5131 reference = branch;
5133 return TRUE;
5135 default:
5136 return TRUE;
5141 static bool
5142 branch_open_visitor(void *data, struct ref *ref)
5144 struct view *view = data;
5145 struct branch *branch;
5147 if (ref->tag || ref->ltag || ref->remote)
5148 return TRUE;
5150 branch = calloc(1, sizeof(*branch));
5151 if (!branch)
5152 return FALSE;
5154 branch->ref = ref;
5155 return !!add_line_data(view, branch, LINE_DEFAULT);
5158 static bool
5159 branch_open(struct view *view)
5161 const char *branch_log[] = {
5162 "git", "log", "--no-color", "--pretty=raw",
5163 "--simplify-by-decoration", "--all", NULL
5166 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
5167 report("Failed to load branch data");
5168 return TRUE;
5171 setup_update(view, view->id);
5172 foreach_ref(branch_open_visitor, view);
5173 view->p_restore = TRUE;
5175 return TRUE;
5178 static bool
5179 branch_grep(struct view *view, struct line *line)
5181 struct branch *branch = line->data;
5182 const char *text[] = {
5183 branch->ref->name,
5184 branch->author,
5185 NULL
5188 return grep_text(view, text);
5191 static void
5192 branch_select(struct view *view, struct line *line)
5194 struct branch *branch = line->data;
5196 string_copy_rev(view->ref, branch->ref->id);
5197 string_copy_rev(ref_commit, branch->ref->id);
5198 string_copy_rev(ref_head, branch->ref->id);
5201 static struct view_ops branch_ops = {
5202 "branch",
5203 NULL,
5204 branch_open,
5205 branch_read,
5206 branch_draw,
5207 branch_request,
5208 branch_grep,
5209 branch_select,
5213 * Status backend
5216 struct status {
5217 char status;
5218 struct {
5219 mode_t mode;
5220 char rev[SIZEOF_REV];
5221 char name[SIZEOF_STR];
5222 } old;
5223 struct {
5224 mode_t mode;
5225 char rev[SIZEOF_REV];
5226 char name[SIZEOF_STR];
5227 } new;
5230 static char status_onbranch[SIZEOF_STR];
5231 static struct status stage_status;
5232 static enum line_type stage_line_type;
5233 static size_t stage_chunks;
5234 static int *stage_chunk;
5236 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5238 /* This should work even for the "On branch" line. */
5239 static inline bool
5240 status_has_none(struct view *view, struct line *line)
5242 return line < view->line + view->lines && !line[1].data;
5245 /* Get fields from the diff line:
5246 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5248 static inline bool
5249 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5251 const char *old_mode = buf + 1;
5252 const char *new_mode = buf + 8;
5253 const char *old_rev = buf + 15;
5254 const char *new_rev = buf + 56;
5255 const char *status = buf + 97;
5257 if (bufsize < 98 ||
5258 old_mode[-1] != ':' ||
5259 new_mode[-1] != ' ' ||
5260 old_rev[-1] != ' ' ||
5261 new_rev[-1] != ' ' ||
5262 status[-1] != ' ')
5263 return FALSE;
5265 file->status = *status;
5267 string_copy_rev(file->old.rev, old_rev);
5268 string_copy_rev(file->new.rev, new_rev);
5270 file->old.mode = strtoul(old_mode, NULL, 8);
5271 file->new.mode = strtoul(new_mode, NULL, 8);
5273 file->old.name[0] = file->new.name[0] = 0;
5275 return TRUE;
5278 static bool
5279 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5281 struct status *unmerged = NULL;
5282 char *buf;
5283 struct io io = {};
5285 if (!run_io(&io, argv, NULL, IO_RD))
5286 return FALSE;
5288 add_line_data(view, NULL, type);
5290 while ((buf = io_get(&io, 0, TRUE))) {
5291 struct status *file = unmerged;
5293 if (!file) {
5294 file = calloc(1, sizeof(*file));
5295 if (!file || !add_line_data(view, file, type))
5296 goto error_out;
5299 /* Parse diff info part. */
5300 if (status) {
5301 file->status = status;
5302 if (status == 'A')
5303 string_copy(file->old.rev, NULL_ID);
5305 } else if (!file->status || file == unmerged) {
5306 if (!status_get_diff(file, buf, strlen(buf)))
5307 goto error_out;
5309 buf = io_get(&io, 0, TRUE);
5310 if (!buf)
5311 break;
5313 /* Collapse all modified entries that follow an
5314 * associated unmerged entry. */
5315 if (unmerged == file) {
5316 unmerged->status = 'U';
5317 unmerged = NULL;
5318 } else if (file->status == 'U') {
5319 unmerged = file;
5323 /* Grab the old name for rename/copy. */
5324 if (!*file->old.name &&
5325 (file->status == 'R' || file->status == 'C')) {
5326 string_ncopy(file->old.name, buf, strlen(buf));
5328 buf = io_get(&io, 0, TRUE);
5329 if (!buf)
5330 break;
5333 /* git-ls-files just delivers a NUL separated list of
5334 * file names similar to the second half of the
5335 * git-diff-* output. */
5336 string_ncopy(file->new.name, buf, strlen(buf));
5337 if (!*file->old.name)
5338 string_copy(file->old.name, file->new.name);
5339 file = NULL;
5342 if (io_error(&io)) {
5343 error_out:
5344 done_io(&io);
5345 return FALSE;
5348 if (!view->line[view->lines - 1].data)
5349 add_line_data(view, NULL, LINE_STAT_NONE);
5351 done_io(&io);
5352 return TRUE;
5355 /* Don't show unmerged entries in the staged section. */
5356 static const char *status_diff_index_argv[] = {
5357 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5358 "--cached", "-M", "HEAD", NULL
5361 static const char *status_diff_files_argv[] = {
5362 "git", "diff-files", "-z", NULL
5365 static const char *status_list_other_argv[] = {
5366 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5369 static const char *status_list_no_head_argv[] = {
5370 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5373 static const char *update_index_argv[] = {
5374 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5377 /* Restore the previous line number to stay in the context or select a
5378 * line with something that can be updated. */
5379 static void
5380 status_restore(struct view *view)
5382 if (view->p_lineno >= view->lines)
5383 view->p_lineno = view->lines - 1;
5384 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5385 view->p_lineno++;
5386 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5387 view->p_lineno--;
5389 /* If the above fails, always skip the "On branch" line. */
5390 if (view->p_lineno < view->lines)
5391 view->lineno = view->p_lineno;
5392 else
5393 view->lineno = 1;
5395 if (view->lineno < view->offset)
5396 view->offset = view->lineno;
5397 else if (view->offset + view->height <= view->lineno)
5398 view->offset = view->lineno - view->height + 1;
5400 view->p_restore = FALSE;
5403 static void
5404 status_update_onbranch(void)
5406 static const char *paths[][2] = {
5407 { "rebase-apply/rebasing", "Rebasing" },
5408 { "rebase-apply/applying", "Applying mailbox" },
5409 { "rebase-apply/", "Rebasing mailbox" },
5410 { "rebase-merge/interactive", "Interactive rebase" },
5411 { "rebase-merge/", "Rebase merge" },
5412 { "MERGE_HEAD", "Merging" },
5413 { "BISECT_LOG", "Bisecting" },
5414 { "HEAD", "On branch" },
5416 char buf[SIZEOF_STR];
5417 struct stat stat;
5418 int i;
5420 if (is_initial_commit()) {
5421 string_copy(status_onbranch, "Initial commit");
5422 return;
5425 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5426 char *head = opt_head;
5428 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5429 lstat(buf, &stat) < 0)
5430 continue;
5432 if (!*opt_head) {
5433 struct io io = {};
5435 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5436 io_open(&io, buf) &&
5437 io_read_buf(&io, buf, sizeof(buf))) {
5438 head = buf;
5439 if (!prefixcmp(head, "refs/heads/"))
5440 head += STRING_SIZE("refs/heads/");
5444 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5445 string_copy(status_onbranch, opt_head);
5446 return;
5449 string_copy(status_onbranch, "Not currently on any branch");
5452 /* First parse staged info using git-diff-index(1), then parse unstaged
5453 * info using git-diff-files(1), and finally untracked files using
5454 * git-ls-files(1). */
5455 static bool
5456 status_open(struct view *view)
5458 reset_view(view);
5460 add_line_data(view, NULL, LINE_STAT_HEAD);
5461 status_update_onbranch();
5463 run_io_bg(update_index_argv);
5465 if (is_initial_commit()) {
5466 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5467 return FALSE;
5468 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5469 return FALSE;
5472 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5473 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5474 return FALSE;
5476 /* Restore the exact position or use the specialized restore
5477 * mode? */
5478 if (!view->p_restore)
5479 status_restore(view);
5480 return TRUE;
5483 static bool
5484 status_draw(struct view *view, struct line *line, unsigned int lineno)
5486 struct status *status = line->data;
5487 enum line_type type;
5488 const char *text;
5490 if (!status) {
5491 switch (line->type) {
5492 case LINE_STAT_STAGED:
5493 type = LINE_STAT_SECTION;
5494 text = "Changes to be committed:";
5495 break;
5497 case LINE_STAT_UNSTAGED:
5498 type = LINE_STAT_SECTION;
5499 text = "Changed but not updated:";
5500 break;
5502 case LINE_STAT_UNTRACKED:
5503 type = LINE_STAT_SECTION;
5504 text = "Untracked files:";
5505 break;
5507 case LINE_STAT_NONE:
5508 type = LINE_DEFAULT;
5509 text = " (no files)";
5510 break;
5512 case LINE_STAT_HEAD:
5513 type = LINE_STAT_HEAD;
5514 text = status_onbranch;
5515 break;
5517 default:
5518 return FALSE;
5520 } else {
5521 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5523 buf[0] = status->status;
5524 if (draw_text(view, line->type, buf, TRUE))
5525 return TRUE;
5526 type = LINE_DEFAULT;
5527 text = status->new.name;
5530 draw_text(view, type, text, TRUE);
5531 return TRUE;
5534 static enum request
5535 status_load_error(struct view *view, struct view *stage, const char *path)
5537 if (displayed_views() == 2 || display[current_view] != view)
5538 maximize_view(view);
5539 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5540 return REQ_NONE;
5543 static enum request
5544 status_enter(struct view *view, struct line *line)
5546 struct status *status = line->data;
5547 const char *oldpath = status ? status->old.name : NULL;
5548 /* Diffs for unmerged entries are empty when passing the new
5549 * path, so leave it empty. */
5550 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5551 const char *info;
5552 enum open_flags split;
5553 struct view *stage = VIEW(REQ_VIEW_STAGE);
5555 if (line->type == LINE_STAT_NONE ||
5556 (!status && line[1].type == LINE_STAT_NONE)) {
5557 report("No file to diff");
5558 return REQ_NONE;
5561 switch (line->type) {
5562 case LINE_STAT_STAGED:
5563 if (is_initial_commit()) {
5564 const char *no_head_diff_argv[] = {
5565 "git", "diff", "--no-color", "--patch-with-stat",
5566 "--", "/dev/null", newpath, NULL
5569 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5570 return status_load_error(view, stage, newpath);
5571 } else {
5572 const char *index_show_argv[] = {
5573 "git", "diff-index", "--root", "--patch-with-stat",
5574 "-C", "-M", "--cached", "HEAD", "--",
5575 oldpath, newpath, NULL
5578 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5579 return status_load_error(view, stage, newpath);
5582 if (status)
5583 info = "Staged changes to %s";
5584 else
5585 info = "Staged changes";
5586 break;
5588 case LINE_STAT_UNSTAGED:
5590 const char *files_show_argv[] = {
5591 "git", "diff-files", "--root", "--patch-with-stat",
5592 "-C", "-M", "--", oldpath, newpath, NULL
5595 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5596 return status_load_error(view, stage, newpath);
5597 if (status)
5598 info = "Unstaged changes to %s";
5599 else
5600 info = "Unstaged changes";
5601 break;
5603 case LINE_STAT_UNTRACKED:
5604 if (!newpath) {
5605 report("No file to show");
5606 return REQ_NONE;
5609 if (!suffixcmp(status->new.name, -1, "/")) {
5610 report("Cannot display a directory");
5611 return REQ_NONE;
5614 if (!prepare_update_file(stage, newpath))
5615 return status_load_error(view, stage, newpath);
5616 info = "Untracked file %s";
5617 break;
5619 case LINE_STAT_HEAD:
5620 return REQ_NONE;
5622 default:
5623 die("line type %d not handled in switch", line->type);
5626 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5627 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5628 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5629 if (status) {
5630 stage_status = *status;
5631 } else {
5632 memset(&stage_status, 0, sizeof(stage_status));
5635 stage_line_type = line->type;
5636 stage_chunks = 0;
5637 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5640 return REQ_NONE;
5643 static bool
5644 status_exists(struct status *status, enum line_type type)
5646 struct view *view = VIEW(REQ_VIEW_STATUS);
5647 unsigned long lineno;
5649 for (lineno = 0; lineno < view->lines; lineno++) {
5650 struct line *line = &view->line[lineno];
5651 struct status *pos = line->data;
5653 if (line->type != type)
5654 continue;
5655 if (!pos && (!status || !status->status) && line[1].data) {
5656 select_view_line(view, lineno);
5657 return TRUE;
5659 if (pos && !strcmp(status->new.name, pos->new.name)) {
5660 select_view_line(view, lineno);
5661 return TRUE;
5665 return FALSE;
5669 static bool
5670 status_update_prepare(struct io *io, enum line_type type)
5672 const char *staged_argv[] = {
5673 "git", "update-index", "-z", "--index-info", NULL
5675 const char *others_argv[] = {
5676 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5679 switch (type) {
5680 case LINE_STAT_STAGED:
5681 return run_io(io, staged_argv, opt_cdup, IO_WR);
5683 case LINE_STAT_UNSTAGED:
5684 return run_io(io, others_argv, opt_cdup, IO_WR);
5686 case LINE_STAT_UNTRACKED:
5687 return run_io(io, others_argv, NULL, IO_WR);
5689 default:
5690 die("line type %d not handled in switch", type);
5691 return FALSE;
5695 static bool
5696 status_update_write(struct io *io, struct status *status, enum line_type type)
5698 char buf[SIZEOF_STR];
5699 size_t bufsize = 0;
5701 switch (type) {
5702 case LINE_STAT_STAGED:
5703 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5704 status->old.mode,
5705 status->old.rev,
5706 status->old.name, 0))
5707 return FALSE;
5708 break;
5710 case LINE_STAT_UNSTAGED:
5711 case LINE_STAT_UNTRACKED:
5712 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5713 return FALSE;
5714 break;
5716 default:
5717 die("line type %d not handled in switch", type);
5720 return io_write(io, buf, bufsize);
5723 static bool
5724 status_update_file(struct status *status, enum line_type type)
5726 struct io io = {};
5727 bool result;
5729 if (!status_update_prepare(&io, type))
5730 return FALSE;
5732 result = status_update_write(&io, status, type);
5733 return done_io(&io) && result;
5736 static bool
5737 status_update_files(struct view *view, struct line *line)
5739 char buf[sizeof(view->ref)];
5740 struct io io = {};
5741 bool result = TRUE;
5742 struct line *pos = view->line + view->lines;
5743 int files = 0;
5744 int file, done;
5745 int cursor_y = -1, cursor_x = -1;
5747 if (!status_update_prepare(&io, line->type))
5748 return FALSE;
5750 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5751 files++;
5753 string_copy(buf, view->ref);
5754 getsyx(cursor_y, cursor_x);
5755 for (file = 0, done = 5; result && file < files; line++, file++) {
5756 int almost_done = file * 100 / files;
5758 if (almost_done > done) {
5759 done = almost_done;
5760 string_format(view->ref, "updating file %u of %u (%d%% done)",
5761 file, files, done);
5762 update_view_title(view);
5763 setsyx(cursor_y, cursor_x);
5764 doupdate();
5766 result = status_update_write(&io, line->data, line->type);
5768 string_copy(view->ref, buf);
5770 return done_io(&io) && result;
5773 static bool
5774 status_update(struct view *view)
5776 struct line *line = &view->line[view->lineno];
5778 assert(view->lines);
5780 if (!line->data) {
5781 /* This should work even for the "On branch" line. */
5782 if (line < view->line + view->lines && !line[1].data) {
5783 report("Nothing to update");
5784 return FALSE;
5787 if (!status_update_files(view, line + 1)) {
5788 report("Failed to update file status");
5789 return FALSE;
5792 } else if (!status_update_file(line->data, line->type)) {
5793 report("Failed to update file status");
5794 return FALSE;
5797 return TRUE;
5800 static bool
5801 status_revert(struct status *status, enum line_type type, bool has_none)
5803 if (!status || type != LINE_STAT_UNSTAGED) {
5804 if (type == LINE_STAT_STAGED) {
5805 report("Cannot revert changes to staged files");
5806 } else if (type == LINE_STAT_UNTRACKED) {
5807 report("Cannot revert changes to untracked files");
5808 } else if (has_none) {
5809 report("Nothing to revert");
5810 } else {
5811 report("Cannot revert changes to multiple files");
5813 return FALSE;
5815 } else {
5816 char mode[10] = "100644";
5817 const char *reset_argv[] = {
5818 "git", "update-index", "--cacheinfo", mode,
5819 status->old.rev, status->old.name, NULL
5821 const char *checkout_argv[] = {
5822 "git", "checkout", "--", status->old.name, NULL
5825 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5826 return FALSE;
5827 string_format(mode, "%o", status->old.mode);
5828 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5829 run_io_fg(checkout_argv, opt_cdup);
5833 static enum request
5834 status_request(struct view *view, enum request request, struct line *line)
5836 struct status *status = line->data;
5838 switch (request) {
5839 case REQ_STATUS_UPDATE:
5840 if (!status_update(view))
5841 return REQ_NONE;
5842 break;
5844 case REQ_STATUS_REVERT:
5845 if (!status_revert(status, line->type, status_has_none(view, line)))
5846 return REQ_NONE;
5847 break;
5849 case REQ_STATUS_MERGE:
5850 if (!status || status->status != 'U') {
5851 report("Merging only possible for files with unmerged status ('U').");
5852 return REQ_NONE;
5854 open_mergetool(status->new.name);
5855 break;
5857 case REQ_EDIT:
5858 if (!status)
5859 return request;
5860 if (status->status == 'D') {
5861 report("File has been deleted.");
5862 return REQ_NONE;
5865 open_editor(status->status != '?', status->new.name);
5866 break;
5868 case REQ_VIEW_BLAME:
5869 if (status) {
5870 string_copy(opt_file, status->new.name);
5871 opt_ref[0] = 0;
5873 return request;
5875 case REQ_ENTER:
5876 /* After returning the status view has been split to
5877 * show the stage view. No further reloading is
5878 * necessary. */
5879 return status_enter(view, line);
5881 case REQ_REFRESH:
5882 /* Simply reload the view. */
5883 break;
5885 default:
5886 return request;
5889 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5891 return REQ_NONE;
5894 static void
5895 status_select(struct view *view, struct line *line)
5897 struct status *status = line->data;
5898 char file[SIZEOF_STR] = "all files";
5899 const char *text;
5900 const char *key;
5902 if (status && !string_format(file, "'%s'", status->new.name))
5903 return;
5905 if (!status && line[1].type == LINE_STAT_NONE)
5906 line++;
5908 switch (line->type) {
5909 case LINE_STAT_STAGED:
5910 text = "Press %s to unstage %s for commit";
5911 break;
5913 case LINE_STAT_UNSTAGED:
5914 text = "Press %s to stage %s for commit";
5915 break;
5917 case LINE_STAT_UNTRACKED:
5918 text = "Press %s to stage %s for addition";
5919 break;
5921 case LINE_STAT_HEAD:
5922 case LINE_STAT_NONE:
5923 text = "Nothing to update";
5924 break;
5926 default:
5927 die("line type %d not handled in switch", line->type);
5930 if (status && status->status == 'U') {
5931 text = "Press %s to resolve conflict in %s";
5932 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5934 } else {
5935 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
5938 string_format(view->ref, text, key, file);
5941 static bool
5942 status_grep(struct view *view, struct line *line)
5944 struct status *status = line->data;
5946 if (status) {
5947 const char buf[2] = { status->status, 0 };
5948 const char *text[] = { status->new.name, buf, NULL };
5950 return grep_text(view, text);
5953 return FALSE;
5956 static struct view_ops status_ops = {
5957 "file",
5958 NULL,
5959 status_open,
5960 NULL,
5961 status_draw,
5962 status_request,
5963 status_grep,
5964 status_select,
5968 static bool
5969 stage_diff_write(struct io *io, struct line *line, struct line *end)
5971 while (line < end) {
5972 if (!io_write(io, line->data, strlen(line->data)) ||
5973 !io_write(io, "\n", 1))
5974 return FALSE;
5975 line++;
5976 if (line->type == LINE_DIFF_CHUNK ||
5977 line->type == LINE_DIFF_HEADER)
5978 break;
5981 return TRUE;
5984 static struct line *
5985 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5987 for (; view->line < line; line--)
5988 if (line->type == type)
5989 return line;
5991 return NULL;
5994 static bool
5995 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5997 const char *apply_argv[SIZEOF_ARG] = {
5998 "git", "apply", "--whitespace=nowarn", NULL
6000 struct line *diff_hdr;
6001 struct io io = {};
6002 int argc = 3;
6004 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6005 if (!diff_hdr)
6006 return FALSE;
6008 if (!revert)
6009 apply_argv[argc++] = "--cached";
6010 if (revert || stage_line_type == LINE_STAT_STAGED)
6011 apply_argv[argc++] = "-R";
6012 apply_argv[argc++] = "-";
6013 apply_argv[argc++] = NULL;
6014 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6015 return FALSE;
6017 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6018 !stage_diff_write(&io, chunk, view->line + view->lines))
6019 chunk = NULL;
6021 done_io(&io);
6022 run_io_bg(update_index_argv);
6024 return chunk ? TRUE : FALSE;
6027 static bool
6028 stage_update(struct view *view, struct line *line)
6030 struct line *chunk = NULL;
6032 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6033 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6035 if (chunk) {
6036 if (!stage_apply_chunk(view, chunk, FALSE)) {
6037 report("Failed to apply chunk");
6038 return FALSE;
6041 } else if (!stage_status.status) {
6042 view = VIEW(REQ_VIEW_STATUS);
6044 for (line = view->line; line < view->line + view->lines; line++)
6045 if (line->type == stage_line_type)
6046 break;
6048 if (!status_update_files(view, line + 1)) {
6049 report("Failed to update files");
6050 return FALSE;
6053 } else if (!status_update_file(&stage_status, stage_line_type)) {
6054 report("Failed to update file");
6055 return FALSE;
6058 return TRUE;
6061 static bool
6062 stage_revert(struct view *view, struct line *line)
6064 struct line *chunk = NULL;
6066 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6067 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6069 if (chunk) {
6070 if (!prompt_yesno("Are you sure you want to revert changes?"))
6071 return FALSE;
6073 if (!stage_apply_chunk(view, chunk, TRUE)) {
6074 report("Failed to revert chunk");
6075 return FALSE;
6077 return TRUE;
6079 } else {
6080 return status_revert(stage_status.status ? &stage_status : NULL,
6081 stage_line_type, FALSE);
6086 static void
6087 stage_next(struct view *view, struct line *line)
6089 int i;
6091 if (!stage_chunks) {
6092 for (line = view->line; line < view->line + view->lines; line++) {
6093 if (line->type != LINE_DIFF_CHUNK)
6094 continue;
6096 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6097 report("Allocation failure");
6098 return;
6101 stage_chunk[stage_chunks++] = line - view->line;
6105 for (i = 0; i < stage_chunks; i++) {
6106 if (stage_chunk[i] > view->lineno) {
6107 do_scroll_view(view, stage_chunk[i] - view->lineno);
6108 report("Chunk %d of %d", i + 1, stage_chunks);
6109 return;
6113 report("No next chunk found");
6116 static enum request
6117 stage_request(struct view *view, enum request request, struct line *line)
6119 switch (request) {
6120 case REQ_STATUS_UPDATE:
6121 if (!stage_update(view, line))
6122 return REQ_NONE;
6123 break;
6125 case REQ_STATUS_REVERT:
6126 if (!stage_revert(view, line))
6127 return REQ_NONE;
6128 break;
6130 case REQ_STAGE_NEXT:
6131 if (stage_line_type == LINE_STAT_UNTRACKED) {
6132 report("File is untracked; press %s to add",
6133 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6134 return REQ_NONE;
6136 stage_next(view, line);
6137 return REQ_NONE;
6139 case REQ_EDIT:
6140 if (!stage_status.new.name[0])
6141 return request;
6142 if (stage_status.status == 'D') {
6143 report("File has been deleted.");
6144 return REQ_NONE;
6147 open_editor(stage_status.status != '?', stage_status.new.name);
6148 break;
6150 case REQ_REFRESH:
6151 /* Reload everything ... */
6152 break;
6154 case REQ_VIEW_BLAME:
6155 if (stage_status.new.name[0]) {
6156 string_copy(opt_file, stage_status.new.name);
6157 opt_ref[0] = 0;
6159 return request;
6161 case REQ_ENTER:
6162 return pager_request(view, request, line);
6164 default:
6165 return request;
6168 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6169 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6171 /* Check whether the staged entry still exists, and close the
6172 * stage view if it doesn't. */
6173 if (!status_exists(&stage_status, stage_line_type)) {
6174 status_restore(VIEW(REQ_VIEW_STATUS));
6175 return REQ_VIEW_CLOSE;
6178 if (stage_line_type == LINE_STAT_UNTRACKED) {
6179 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6180 report("Cannot display a directory");
6181 return REQ_NONE;
6184 if (!prepare_update_file(view, stage_status.new.name)) {
6185 report("Failed to open file: %s", strerror(errno));
6186 return REQ_NONE;
6189 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6191 return REQ_NONE;
6194 static struct view_ops stage_ops = {
6195 "line",
6196 NULL,
6197 NULL,
6198 pager_read,
6199 pager_draw,
6200 stage_request,
6201 pager_grep,
6202 pager_select,
6207 * Revision graph
6210 struct commit {
6211 char id[SIZEOF_REV]; /* SHA1 ID. */
6212 char title[128]; /* First line of the commit message. */
6213 const char *author; /* Author of the commit. */
6214 time_t time; /* Date from the author ident. */
6215 struct ref_list *refs; /* Repository references. */
6216 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6217 size_t graph_size; /* The width of the graph array. */
6218 bool has_parents; /* Rewritten --parents seen. */
6221 /* Size of rev graph with no "padding" columns */
6222 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6224 struct rev_graph {
6225 struct rev_graph *prev, *next, *parents;
6226 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6227 size_t size;
6228 struct commit *commit;
6229 size_t pos;
6230 unsigned int boundary:1;
6233 /* Parents of the commit being visualized. */
6234 static struct rev_graph graph_parents[4];
6236 /* The current stack of revisions on the graph. */
6237 static struct rev_graph graph_stacks[4] = {
6238 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6239 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6240 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6241 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6244 static inline bool
6245 graph_parent_is_merge(struct rev_graph *graph)
6247 return graph->parents->size > 1;
6250 static inline void
6251 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6253 struct commit *commit = graph->commit;
6255 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6256 commit->graph[commit->graph_size++] = symbol;
6259 static void
6260 clear_rev_graph(struct rev_graph *graph)
6262 graph->boundary = 0;
6263 graph->size = graph->pos = 0;
6264 graph->commit = NULL;
6265 memset(graph->parents, 0, sizeof(*graph->parents));
6268 static void
6269 done_rev_graph(struct rev_graph *graph)
6271 if (graph_parent_is_merge(graph) &&
6272 graph->pos < graph->size - 1 &&
6273 graph->next->size == graph->size + graph->parents->size - 1) {
6274 size_t i = graph->pos + graph->parents->size - 1;
6276 graph->commit->graph_size = i * 2;
6277 while (i < graph->next->size - 1) {
6278 append_to_rev_graph(graph, ' ');
6279 append_to_rev_graph(graph, '\\');
6280 i++;
6284 clear_rev_graph(graph);
6287 static void
6288 push_rev_graph(struct rev_graph *graph, const char *parent)
6290 int i;
6292 /* "Collapse" duplicate parents lines.
6294 * FIXME: This needs to also update update the drawn graph but
6295 * for now it just serves as a method for pruning graph lines. */
6296 for (i = 0; i < graph->size; i++)
6297 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6298 return;
6300 if (graph->size < SIZEOF_REVITEMS) {
6301 string_copy_rev(graph->rev[graph->size++], parent);
6305 static chtype
6306 get_rev_graph_symbol(struct rev_graph *graph)
6308 chtype symbol;
6310 if (graph->boundary)
6311 symbol = REVGRAPH_BOUND;
6312 else if (graph->parents->size == 0)
6313 symbol = REVGRAPH_INIT;
6314 else if (graph_parent_is_merge(graph))
6315 symbol = REVGRAPH_MERGE;
6316 else if (graph->pos >= graph->size)
6317 symbol = REVGRAPH_BRANCH;
6318 else
6319 symbol = REVGRAPH_COMMIT;
6321 return symbol;
6324 static void
6325 draw_rev_graph(struct rev_graph *graph)
6327 struct rev_filler {
6328 chtype separator, line;
6330 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6331 static struct rev_filler fillers[] = {
6332 { ' ', '|' },
6333 { '`', '.' },
6334 { '\'', ' ' },
6335 { '/', ' ' },
6337 chtype symbol = get_rev_graph_symbol(graph);
6338 struct rev_filler *filler;
6339 size_t i;
6341 if (opt_line_graphics)
6342 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6344 filler = &fillers[DEFAULT];
6346 for (i = 0; i < graph->pos; i++) {
6347 append_to_rev_graph(graph, filler->line);
6348 if (graph_parent_is_merge(graph->prev) &&
6349 graph->prev->pos == i)
6350 filler = &fillers[RSHARP];
6352 append_to_rev_graph(graph, filler->separator);
6355 /* Place the symbol for this revision. */
6356 append_to_rev_graph(graph, symbol);
6358 if (graph->prev->size > graph->size)
6359 filler = &fillers[RDIAG];
6360 else
6361 filler = &fillers[DEFAULT];
6363 i++;
6365 for (; i < graph->size; i++) {
6366 append_to_rev_graph(graph, filler->separator);
6367 append_to_rev_graph(graph, filler->line);
6368 if (graph_parent_is_merge(graph->prev) &&
6369 i < graph->prev->pos + graph->parents->size)
6370 filler = &fillers[RSHARP];
6371 if (graph->prev->size > graph->size)
6372 filler = &fillers[LDIAG];
6375 if (graph->prev->size > graph->size) {
6376 append_to_rev_graph(graph, filler->separator);
6377 if (filler->line != ' ')
6378 append_to_rev_graph(graph, filler->line);
6382 /* Prepare the next rev graph */
6383 static void
6384 prepare_rev_graph(struct rev_graph *graph)
6386 size_t i;
6388 /* First, traverse all lines of revisions up to the active one. */
6389 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6390 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6391 break;
6393 push_rev_graph(graph->next, graph->rev[graph->pos]);
6396 /* Interleave the new revision parent(s). */
6397 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6398 push_rev_graph(graph->next, graph->parents->rev[i]);
6400 /* Lastly, put any remaining revisions. */
6401 for (i = graph->pos + 1; i < graph->size; i++)
6402 push_rev_graph(graph->next, graph->rev[i]);
6405 static void
6406 update_rev_graph(struct view *view, struct rev_graph *graph)
6408 /* If this is the finalizing update ... */
6409 if (graph->commit)
6410 prepare_rev_graph(graph);
6412 /* Graph visualization needs a one rev look-ahead,
6413 * so the first update doesn't visualize anything. */
6414 if (!graph->prev->commit)
6415 return;
6417 if (view->lines > 2)
6418 view->line[view->lines - 3].dirty = 1;
6419 if (view->lines > 1)
6420 view->line[view->lines - 2].dirty = 1;
6421 draw_rev_graph(graph->prev);
6422 done_rev_graph(graph->prev->prev);
6427 * Main view backend
6430 static const char *main_argv[SIZEOF_ARG] = {
6431 "git", "log", "--no-color", "--pretty=raw", "--parents",
6432 "--topo-order", "%(head)", NULL
6435 static bool
6436 main_draw(struct view *view, struct line *line, unsigned int lineno)
6438 struct commit *commit = line->data;
6440 if (!commit->author)
6441 return FALSE;
6443 if (opt_date && draw_date(view, &commit->time))
6444 return TRUE;
6446 if (opt_author && draw_author(view, commit->author))
6447 return TRUE;
6449 if (opt_rev_graph && commit->graph_size &&
6450 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6451 return TRUE;
6453 if (opt_show_refs && commit->refs) {
6454 size_t i;
6456 for (i = 0; i < commit->refs->size; i++) {
6457 struct ref *ref = commit->refs->refs[i];
6458 enum line_type type;
6460 if (ref->head)
6461 type = LINE_MAIN_HEAD;
6462 else if (ref->ltag)
6463 type = LINE_MAIN_LOCAL_TAG;
6464 else if (ref->tag)
6465 type = LINE_MAIN_TAG;
6466 else if (ref->tracked)
6467 type = LINE_MAIN_TRACKED;
6468 else if (ref->remote)
6469 type = LINE_MAIN_REMOTE;
6470 else
6471 type = LINE_MAIN_REF;
6473 if (draw_text(view, type, "[", TRUE) ||
6474 draw_text(view, type, ref->name, TRUE) ||
6475 draw_text(view, type, "]", TRUE))
6476 return TRUE;
6478 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6479 return TRUE;
6483 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6484 return TRUE;
6487 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6488 static bool
6489 main_read(struct view *view, char *line)
6491 static struct rev_graph *graph = graph_stacks;
6492 enum line_type type;
6493 struct commit *commit;
6495 if (!line) {
6496 int i;
6498 if (!view->lines && !view->parent)
6499 die("No revisions match the given arguments.");
6500 if (view->lines > 0) {
6501 commit = view->line[view->lines - 1].data;
6502 view->line[view->lines - 1].dirty = 1;
6503 if (!commit->author) {
6504 view->lines--;
6505 free(commit);
6506 graph->commit = NULL;
6509 update_rev_graph(view, graph);
6511 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6512 clear_rev_graph(&graph_stacks[i]);
6513 return TRUE;
6516 type = get_line_type(line);
6517 if (type == LINE_COMMIT) {
6518 commit = calloc(1, sizeof(struct commit));
6519 if (!commit)
6520 return FALSE;
6522 line += STRING_SIZE("commit ");
6523 if (*line == '-') {
6524 graph->boundary = 1;
6525 line++;
6528 string_copy_rev(commit->id, line);
6529 commit->refs = get_ref_list(commit->id);
6530 graph->commit = commit;
6531 add_line_data(view, commit, LINE_MAIN_COMMIT);
6533 while ((line = strchr(line, ' '))) {
6534 line++;
6535 push_rev_graph(graph->parents, line);
6536 commit->has_parents = TRUE;
6538 return TRUE;
6541 if (!view->lines)
6542 return TRUE;
6543 commit = view->line[view->lines - 1].data;
6545 switch (type) {
6546 case LINE_PARENT:
6547 if (commit->has_parents)
6548 break;
6549 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6550 break;
6552 case LINE_AUTHOR:
6553 parse_author_line(line + STRING_SIZE("author "),
6554 &commit->author, &commit->time);
6555 update_rev_graph(view, graph);
6556 graph = graph->next;
6557 break;
6559 default:
6560 /* Fill in the commit title if it has not already been set. */
6561 if (commit->title[0])
6562 break;
6564 /* Require titles to start with a non-space character at the
6565 * offset used by git log. */
6566 if (strncmp(line, " ", 4))
6567 break;
6568 line += 4;
6569 /* Well, if the title starts with a whitespace character,
6570 * try to be forgiving. Otherwise we end up with no title. */
6571 while (isspace(*line))
6572 line++;
6573 if (*line == '\0')
6574 break;
6575 /* FIXME: More graceful handling of titles; append "..." to
6576 * shortened titles, etc. */
6578 string_expand(commit->title, sizeof(commit->title), line, 1);
6579 view->line[view->lines - 1].dirty = 1;
6582 return TRUE;
6585 static enum request
6586 main_request(struct view *view, enum request request, struct line *line)
6588 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6590 switch (request) {
6591 case REQ_ENTER:
6592 open_view(view, REQ_VIEW_DIFF, flags);
6593 break;
6594 case REQ_REFRESH:
6595 load_refs();
6596 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6597 break;
6598 default:
6599 return request;
6602 return REQ_NONE;
6605 static bool
6606 grep_refs(struct ref_list *list, regex_t *regex)
6608 regmatch_t pmatch;
6609 size_t i;
6611 if (!opt_show_refs || !list)
6612 return FALSE;
6614 for (i = 0; i < list->size; i++) {
6615 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6616 return TRUE;
6619 return FALSE;
6622 static bool
6623 main_grep(struct view *view, struct line *line)
6625 struct commit *commit = line->data;
6626 const char *text[] = {
6627 commit->title,
6628 opt_author ? commit->author : "",
6629 opt_date ? mkdate(&commit->time) : "",
6630 NULL
6633 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6636 static void
6637 main_select(struct view *view, struct line *line)
6639 struct commit *commit = line->data;
6641 string_copy_rev(view->ref, commit->id);
6642 string_copy_rev(ref_commit, view->ref);
6645 static struct view_ops main_ops = {
6646 "commit",
6647 main_argv,
6648 NULL,
6649 main_read,
6650 main_draw,
6651 main_request,
6652 main_grep,
6653 main_select,
6658 * Unicode / UTF-8 handling
6660 * NOTE: Much of the following code for dealing with Unicode is derived from
6661 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6662 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6665 static inline int
6666 unicode_width(unsigned long c)
6668 if (c >= 0x1100 &&
6669 (c <= 0x115f /* Hangul Jamo */
6670 || c == 0x2329
6671 || c == 0x232a
6672 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6673 /* CJK ... Yi */
6674 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6675 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6676 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6677 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6678 || (c >= 0xffe0 && c <= 0xffe6)
6679 || (c >= 0x20000 && c <= 0x2fffd)
6680 || (c >= 0x30000 && c <= 0x3fffd)))
6681 return 2;
6683 if (c == '\t')
6684 return opt_tab_size;
6686 return 1;
6689 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6690 * Illegal bytes are set one. */
6691 static const unsigned char utf8_bytes[256] = {
6692 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,
6693 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,
6694 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,
6695 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,
6696 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,
6697 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,
6698 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,
6699 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,
6702 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6703 static inline unsigned long
6704 utf8_to_unicode(const char *string, size_t length)
6706 unsigned long unicode;
6708 switch (length) {
6709 case 1:
6710 unicode = string[0];
6711 break;
6712 case 2:
6713 unicode = (string[0] & 0x1f) << 6;
6714 unicode += (string[1] & 0x3f);
6715 break;
6716 case 3:
6717 unicode = (string[0] & 0x0f) << 12;
6718 unicode += ((string[1] & 0x3f) << 6);
6719 unicode += (string[2] & 0x3f);
6720 break;
6721 case 4:
6722 unicode = (string[0] & 0x0f) << 18;
6723 unicode += ((string[1] & 0x3f) << 12);
6724 unicode += ((string[2] & 0x3f) << 6);
6725 unicode += (string[3] & 0x3f);
6726 break;
6727 case 5:
6728 unicode = (string[0] & 0x0f) << 24;
6729 unicode += ((string[1] & 0x3f) << 18);
6730 unicode += ((string[2] & 0x3f) << 12);
6731 unicode += ((string[3] & 0x3f) << 6);
6732 unicode += (string[4] & 0x3f);
6733 break;
6734 case 6:
6735 unicode = (string[0] & 0x01) << 30;
6736 unicode += ((string[1] & 0x3f) << 24);
6737 unicode += ((string[2] & 0x3f) << 18);
6738 unicode += ((string[3] & 0x3f) << 12);
6739 unicode += ((string[4] & 0x3f) << 6);
6740 unicode += (string[5] & 0x3f);
6741 break;
6742 default:
6743 die("Invalid Unicode length");
6746 /* Invalid characters could return the special 0xfffd value but NUL
6747 * should be just as good. */
6748 return unicode > 0xffff ? 0 : unicode;
6751 /* Calculates how much of string can be shown within the given maximum width
6752 * and sets trimmed parameter to non-zero value if all of string could not be
6753 * shown. If the reserve flag is TRUE, it will reserve at least one
6754 * trailing character, which can be useful when drawing a delimiter.
6756 * Returns the number of bytes to output from string to satisfy max_width. */
6757 static size_t
6758 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6760 const char *string = *start;
6761 const char *end = strchr(string, '\0');
6762 unsigned char last_bytes = 0;
6763 size_t last_ucwidth = 0;
6765 *width = 0;
6766 *trimmed = 0;
6768 while (string < end) {
6769 int c = *(unsigned char *) string;
6770 unsigned char bytes = utf8_bytes[c];
6771 size_t ucwidth;
6772 unsigned long unicode;
6774 if (string + bytes > end)
6775 break;
6777 /* Change representation to figure out whether
6778 * it is a single- or double-width character. */
6780 unicode = utf8_to_unicode(string, bytes);
6781 /* FIXME: Graceful handling of invalid Unicode character. */
6782 if (!unicode)
6783 break;
6785 ucwidth = unicode_width(unicode);
6786 if (skip > 0) {
6787 skip -= ucwidth <= skip ? ucwidth : skip;
6788 *start += bytes;
6790 *width += ucwidth;
6791 if (*width > max_width) {
6792 *trimmed = 1;
6793 *width -= ucwidth;
6794 if (reserve && *width == max_width) {
6795 string -= last_bytes;
6796 *width -= last_ucwidth;
6798 break;
6801 string += bytes;
6802 last_bytes = ucwidth ? bytes : 0;
6803 last_ucwidth = ucwidth;
6806 return string - *start;
6811 * Status management
6814 /* Whether or not the curses interface has been initialized. */
6815 static bool cursed = FALSE;
6817 /* Terminal hacks and workarounds. */
6818 static bool use_scroll_redrawwin;
6819 static bool use_scroll_status_wclear;
6821 /* The status window is used for polling keystrokes. */
6822 static WINDOW *status_win;
6824 /* Reading from the prompt? */
6825 static bool input_mode = FALSE;
6827 static bool status_empty = FALSE;
6829 /* Update status and title window. */
6830 static void
6831 report(const char *msg, ...)
6833 struct view *view = display[current_view];
6835 if (input_mode)
6836 return;
6838 if (!view) {
6839 char buf[SIZEOF_STR];
6840 va_list args;
6842 va_start(args, msg);
6843 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6844 buf[sizeof(buf) - 1] = 0;
6845 buf[sizeof(buf) - 2] = '.';
6846 buf[sizeof(buf) - 3] = '.';
6847 buf[sizeof(buf) - 4] = '.';
6849 va_end(args);
6850 die("%s", buf);
6853 if (!status_empty || *msg) {
6854 va_list args;
6856 va_start(args, msg);
6858 wmove(status_win, 0, 0);
6859 if (view->has_scrolled && use_scroll_status_wclear)
6860 wclear(status_win);
6861 if (*msg) {
6862 vwprintw(status_win, msg, args);
6863 status_empty = FALSE;
6864 } else {
6865 status_empty = TRUE;
6867 wclrtoeol(status_win);
6868 wnoutrefresh(status_win);
6870 va_end(args);
6873 update_view_title(view);
6876 /* Controls when nodelay should be in effect when polling user input. */
6877 static void
6878 set_nonblocking_input(bool loading)
6880 static unsigned int loading_views;
6882 if ((loading == FALSE && loading_views-- == 1) ||
6883 (loading == TRUE && loading_views++ == 0))
6884 nodelay(status_win, loading);
6887 static void
6888 init_display(void)
6890 const char *term;
6891 int x, y;
6893 /* Initialize the curses library */
6894 if (isatty(STDIN_FILENO)) {
6895 cursed = !!initscr();
6896 opt_tty = stdin;
6897 } else {
6898 /* Leave stdin and stdout alone when acting as a pager. */
6899 opt_tty = fopen("/dev/tty", "r+");
6900 if (!opt_tty)
6901 die("Failed to open /dev/tty");
6902 cursed = !!newterm(NULL, opt_tty, opt_tty);
6905 if (!cursed)
6906 die("Failed to initialize curses");
6908 nonl(); /* Disable conversion and detect newlines from input. */
6909 cbreak(); /* Take input chars one at a time, no wait for \n */
6910 noecho(); /* Don't echo input */
6911 leaveok(stdscr, FALSE);
6913 if (has_colors())
6914 init_colors();
6916 getmaxyx(stdscr, y, x);
6917 status_win = newwin(1, 0, y - 1, 0);
6918 if (!status_win)
6919 die("Failed to create status window");
6921 /* Enable keyboard mapping */
6922 keypad(status_win, TRUE);
6923 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6925 TABSIZE = opt_tab_size;
6926 if (opt_line_graphics) {
6927 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6930 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6931 if (term && !strcmp(term, "gnome-terminal")) {
6932 /* In the gnome-terminal-emulator, the message from
6933 * scrolling up one line when impossible followed by
6934 * scrolling down one line causes corruption of the
6935 * status line. This is fixed by calling wclear. */
6936 use_scroll_status_wclear = TRUE;
6937 use_scroll_redrawwin = FALSE;
6939 } else if (term && !strcmp(term, "xrvt-xpm")) {
6940 /* No problems with full optimizations in xrvt-(unicode)
6941 * and aterm. */
6942 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6944 } else {
6945 /* When scrolling in (u)xterm the last line in the
6946 * scrolling direction will update slowly. */
6947 use_scroll_redrawwin = TRUE;
6948 use_scroll_status_wclear = FALSE;
6952 static int
6953 get_input(int prompt_position)
6955 struct view *view;
6956 int i, key, cursor_y, cursor_x;
6958 if (prompt_position)
6959 input_mode = TRUE;
6961 while (TRUE) {
6962 foreach_view (view, i) {
6963 update_view(view);
6964 if (view_is_displayed(view) && view->has_scrolled &&
6965 use_scroll_redrawwin)
6966 redrawwin(view->win);
6967 view->has_scrolled = FALSE;
6970 /* Update the cursor position. */
6971 if (prompt_position) {
6972 getbegyx(status_win, cursor_y, cursor_x);
6973 cursor_x = prompt_position;
6974 } else {
6975 view = display[current_view];
6976 getbegyx(view->win, cursor_y, cursor_x);
6977 cursor_x = view->width - 1;
6978 cursor_y += view->lineno - view->offset;
6980 setsyx(cursor_y, cursor_x);
6982 /* Refresh, accept single keystroke of input */
6983 doupdate();
6984 key = wgetch(status_win);
6986 /* wgetch() with nodelay() enabled returns ERR when
6987 * there's no input. */
6988 if (key == ERR) {
6990 } else if (key == KEY_RESIZE) {
6991 int height, width;
6993 getmaxyx(stdscr, height, width);
6995 wresize(status_win, 1, width);
6996 mvwin(status_win, height - 1, 0);
6997 wnoutrefresh(status_win);
6998 resize_display();
6999 redraw_display(TRUE);
7001 } else {
7002 input_mode = FALSE;
7003 return key;
7008 static char *
7009 prompt_input(const char *prompt, input_handler handler, void *data)
7011 enum input_status status = INPUT_OK;
7012 static char buf[SIZEOF_STR];
7013 size_t pos = 0;
7015 buf[pos] = 0;
7017 while (status == INPUT_OK || status == INPUT_SKIP) {
7018 int key;
7020 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7021 wclrtoeol(status_win);
7023 key = get_input(pos + 1);
7024 switch (key) {
7025 case KEY_RETURN:
7026 case KEY_ENTER:
7027 case '\n':
7028 status = pos ? INPUT_STOP : INPUT_CANCEL;
7029 break;
7031 case KEY_BACKSPACE:
7032 if (pos > 0)
7033 buf[--pos] = 0;
7034 else
7035 status = INPUT_CANCEL;
7036 break;
7038 case KEY_ESC:
7039 status = INPUT_CANCEL;
7040 break;
7042 default:
7043 if (pos >= sizeof(buf)) {
7044 report("Input string too long");
7045 return NULL;
7048 status = handler(data, buf, key);
7049 if (status == INPUT_OK)
7050 buf[pos++] = (char) key;
7054 /* Clear the status window */
7055 status_empty = FALSE;
7056 report("");
7058 if (status == INPUT_CANCEL)
7059 return NULL;
7061 buf[pos++] = 0;
7063 return buf;
7066 static enum input_status
7067 prompt_yesno_handler(void *data, char *buf, int c)
7069 if (c == 'y' || c == 'Y')
7070 return INPUT_STOP;
7071 if (c == 'n' || c == 'N')
7072 return INPUT_CANCEL;
7073 return INPUT_SKIP;
7076 static bool
7077 prompt_yesno(const char *prompt)
7079 char prompt2[SIZEOF_STR];
7081 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7082 return FALSE;
7084 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7087 static enum input_status
7088 read_prompt_handler(void *data, char *buf, int c)
7090 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7093 static char *
7094 read_prompt(const char *prompt)
7096 return prompt_input(prompt, read_prompt_handler, NULL);
7099 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7101 enum input_status status = INPUT_OK;
7102 int size = 0;
7104 while (items[size].text)
7105 size++;
7107 while (status == INPUT_OK) {
7108 const struct menu_item *item = &items[*selected];
7109 int key;
7110 int i;
7112 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7113 prompt, *selected + 1, size);
7114 if (item->hotkey)
7115 wprintw(status_win, "[%c] ", (char) item->hotkey);
7116 wprintw(status_win, "%s", item->text);
7117 wclrtoeol(status_win);
7119 key = get_input(COLS - 1);
7120 switch (key) {
7121 case KEY_RETURN:
7122 case KEY_ENTER:
7123 case '\n':
7124 status = INPUT_STOP;
7125 break;
7127 case KEY_LEFT:
7128 case KEY_UP:
7129 *selected = *selected - 1;
7130 if (*selected < 0)
7131 *selected = size - 1;
7132 break;
7134 case KEY_RIGHT:
7135 case KEY_DOWN:
7136 *selected = (*selected + 1) % size;
7137 break;
7139 case KEY_ESC:
7140 status = INPUT_CANCEL;
7141 break;
7143 default:
7144 for (i = 0; items[i].text; i++)
7145 if (items[i].hotkey == key) {
7146 *selected = i;
7147 status = INPUT_STOP;
7148 break;
7153 /* Clear the status window */
7154 status_empty = FALSE;
7155 report("");
7157 return status != INPUT_CANCEL;
7161 * Repository properties
7164 static struct ref **refs = NULL;
7165 static size_t refs_size = 0;
7167 static struct ref_list **ref_lists = NULL;
7168 static size_t ref_lists_size = 0;
7170 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7171 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7172 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7174 static int
7175 compare_refs(const void *ref1_, const void *ref2_)
7177 const struct ref *ref1 = *(const struct ref **)ref1_;
7178 const struct ref *ref2 = *(const struct ref **)ref2_;
7180 if (ref1->tag != ref2->tag)
7181 return ref2->tag - ref1->tag;
7182 if (ref1->ltag != ref2->ltag)
7183 return ref2->ltag - ref2->ltag;
7184 if (ref1->head != ref2->head)
7185 return ref2->head - ref1->head;
7186 if (ref1->tracked != ref2->tracked)
7187 return ref2->tracked - ref1->tracked;
7188 if (ref1->remote != ref2->remote)
7189 return ref2->remote - ref1->remote;
7190 return strcmp(ref1->name, ref2->name);
7193 static void
7194 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
7196 size_t i;
7198 for (i = 0; i < refs_size; i++)
7199 if (!visitor(data, refs[i]))
7200 break;
7203 static struct ref_list *
7204 get_ref_list(const char *id)
7206 struct ref_list *list;
7207 size_t i;
7209 for (i = 0; i < ref_lists_size; i++)
7210 if (!strcmp(id, ref_lists[i]->id))
7211 return ref_lists[i];
7213 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7214 return NULL;
7215 list = calloc(1, sizeof(*list));
7216 if (!list)
7217 return NULL;
7219 for (i = 0; i < refs_size; i++) {
7220 if (!strcmp(id, refs[i]->id) &&
7221 realloc_refs_list(&list->refs, list->size, 1))
7222 list->refs[list->size++] = refs[i];
7225 if (!list->refs) {
7226 free(list);
7227 return NULL;
7230 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7231 ref_lists[ref_lists_size++] = list;
7232 return list;
7235 static int
7236 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7238 struct ref *ref = NULL;
7239 bool tag = FALSE;
7240 bool ltag = FALSE;
7241 bool remote = FALSE;
7242 bool tracked = FALSE;
7243 bool head = FALSE;
7244 int from = 0, to = refs_size - 1;
7246 if (!prefixcmp(name, "refs/tags/")) {
7247 if (!suffixcmp(name, namelen, "^{}")) {
7248 namelen -= 3;
7249 name[namelen] = 0;
7250 } else {
7251 ltag = TRUE;
7254 tag = TRUE;
7255 namelen -= STRING_SIZE("refs/tags/");
7256 name += STRING_SIZE("refs/tags/");
7258 } else if (!prefixcmp(name, "refs/remotes/")) {
7259 remote = TRUE;
7260 namelen -= STRING_SIZE("refs/remotes/");
7261 name += STRING_SIZE("refs/remotes/");
7262 tracked = !strcmp(opt_remote, name);
7264 } else if (!prefixcmp(name, "refs/heads/")) {
7265 namelen -= STRING_SIZE("refs/heads/");
7266 name += STRING_SIZE("refs/heads/");
7267 head = !strncmp(opt_head, name, namelen);
7269 } else if (!strcmp(name, "HEAD")) {
7270 string_ncopy(opt_head_rev, id, idlen);
7271 return OK;
7274 /* If we are reloading or it's an annotated tag, replace the
7275 * previous SHA1 with the resolved commit id; relies on the fact
7276 * git-ls-remote lists the commit id of an annotated tag right
7277 * before the commit id it points to. */
7278 while (from <= to) {
7279 size_t pos = (to + from) / 2;
7280 int cmp = strcmp(name, refs[pos]->name);
7282 if (!cmp) {
7283 ref = refs[pos];
7284 break;
7287 if (cmp < 0)
7288 to = pos - 1;
7289 else
7290 from = pos + 1;
7293 if (!ref) {
7294 if (!realloc_refs(&refs, refs_size, 1))
7295 return ERR;
7296 ref = calloc(1, sizeof(*ref) + namelen);
7297 if (!ref)
7298 return ERR;
7299 memmove(refs + from + 1, refs + from,
7300 (refs_size - from) * sizeof(*refs));
7301 refs[from] = ref;
7302 strncpy(ref->name, name, namelen);
7303 refs_size++;
7306 ref->head = head;
7307 ref->tag = tag;
7308 ref->ltag = ltag;
7309 ref->remote = remote;
7310 ref->tracked = tracked;
7311 string_copy_rev(ref->id, id);
7313 return OK;
7316 static int
7317 load_refs(void)
7319 const char *head_argv[] = {
7320 "git", "symbolic-ref", "HEAD", NULL
7322 static const char *ls_remote_argv[SIZEOF_ARG] = {
7323 "git", "ls-remote", opt_git_dir, NULL
7325 static bool init = FALSE;
7326 size_t i;
7328 if (!init) {
7329 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7330 init = TRUE;
7333 if (!*opt_git_dir)
7334 return OK;
7336 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7337 !prefixcmp(opt_head, "refs/heads/")) {
7338 char *offset = opt_head + STRING_SIZE("refs/heads/");
7340 memmove(opt_head, offset, strlen(offset) + 1);
7343 for (i = 0; i < refs_size; i++)
7344 refs[i]->id[0] = 0;
7346 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7347 return ERR;
7349 /* Update the ref lists to reflect changes. */
7350 for (i = 0; i < ref_lists_size; i++) {
7351 struct ref_list *list = ref_lists[i];
7352 size_t old, new;
7354 for (old = new = 0; old < list->size; old++)
7355 if (!strcmp(list->id, list->refs[old]->id))
7356 list->refs[new++] = list->refs[old];
7357 list->size = new;
7360 return OK;
7363 static void
7364 set_remote_branch(const char *name, const char *value, size_t valuelen)
7366 if (!strcmp(name, ".remote")) {
7367 string_ncopy(opt_remote, value, valuelen);
7369 } else if (*opt_remote && !strcmp(name, ".merge")) {
7370 size_t from = strlen(opt_remote);
7372 if (!prefixcmp(value, "refs/heads/"))
7373 value += STRING_SIZE("refs/heads/");
7375 if (!string_format_from(opt_remote, &from, "/%s", value))
7376 opt_remote[0] = 0;
7380 static void
7381 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7383 const char *argv[SIZEOF_ARG] = { name, "=" };
7384 int argc = 1 + (cmd == option_set_command);
7385 int error = ERR;
7387 if (!argv_from_string(argv, &argc, value))
7388 config_msg = "Too many option arguments";
7389 else
7390 error = cmd(argc, argv);
7392 if (error == ERR)
7393 warn("Option 'tig.%s': %s", name, config_msg);
7396 static bool
7397 set_environment_variable(const char *name, const char *value)
7399 size_t len = strlen(name) + 1 + strlen(value) + 1;
7400 char *env = malloc(len);
7402 if (env &&
7403 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7404 putenv(env) == 0)
7405 return TRUE;
7406 free(env);
7407 return FALSE;
7410 static void
7411 set_work_tree(const char *value)
7413 char cwd[SIZEOF_STR];
7415 if (!getcwd(cwd, sizeof(cwd)))
7416 die("Failed to get cwd path: %s", strerror(errno));
7417 if (chdir(opt_git_dir) < 0)
7418 die("Failed to chdir(%s): %s", strerror(errno));
7419 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7420 die("Failed to get git path: %s", strerror(errno));
7421 if (chdir(cwd) < 0)
7422 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7423 if (chdir(value) < 0)
7424 die("Failed to chdir(%s): %s", value, strerror(errno));
7425 if (!getcwd(cwd, sizeof(cwd)))
7426 die("Failed to get cwd path: %s", strerror(errno));
7427 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7428 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7429 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7430 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7431 opt_is_inside_work_tree = TRUE;
7434 static int
7435 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7437 if (!strcmp(name, "i18n.commitencoding"))
7438 string_ncopy(opt_encoding, value, valuelen);
7440 else if (!strcmp(name, "core.editor"))
7441 string_ncopy(opt_editor, value, valuelen);
7443 else if (!strcmp(name, "core.worktree"))
7444 set_work_tree(value);
7446 else if (!prefixcmp(name, "tig.color."))
7447 set_repo_config_option(name + 10, value, option_color_command);
7449 else if (!prefixcmp(name, "tig.bind."))
7450 set_repo_config_option(name + 9, value, option_bind_command);
7452 else if (!prefixcmp(name, "tig."))
7453 set_repo_config_option(name + 4, value, option_set_command);
7455 else if (*opt_head && !prefixcmp(name, "branch.") &&
7456 !strncmp(name + 7, opt_head, strlen(opt_head)))
7457 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7459 return OK;
7462 static int
7463 load_git_config(void)
7465 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7467 return run_io_load(config_list_argv, "=", read_repo_config_option);
7470 static int
7471 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7473 if (!opt_git_dir[0]) {
7474 string_ncopy(opt_git_dir, name, namelen);
7476 } else if (opt_is_inside_work_tree == -1) {
7477 /* This can be 3 different values depending on the
7478 * version of git being used. If git-rev-parse does not
7479 * understand --is-inside-work-tree it will simply echo
7480 * the option else either "true" or "false" is printed.
7481 * Default to true for the unknown case. */
7482 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7484 } else if (*name == '.') {
7485 string_ncopy(opt_cdup, name, namelen);
7487 } else {
7488 string_ncopy(opt_prefix, name, namelen);
7491 return OK;
7494 static int
7495 load_repo_info(void)
7497 const char *rev_parse_argv[] = {
7498 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7499 "--show-cdup", "--show-prefix", NULL
7502 return run_io_load(rev_parse_argv, "=", read_repo_info);
7507 * Main
7510 static const char usage[] =
7511 "tig " TIG_VERSION " (" __DATE__ ")\n"
7512 "\n"
7513 "Usage: tig [options] [revs] [--] [paths]\n"
7514 " or: tig show [options] [revs] [--] [paths]\n"
7515 " or: tig blame [rev] path\n"
7516 " or: tig status\n"
7517 " or: tig < [git command output]\n"
7518 "\n"
7519 "Options:\n"
7520 " -v, --version Show version and exit\n"
7521 " -h, --help Show help message and exit";
7523 static void __NORETURN
7524 quit(int sig)
7526 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7527 if (cursed)
7528 endwin();
7529 exit(0);
7532 static void __NORETURN
7533 die(const char *err, ...)
7535 va_list args;
7537 endwin();
7539 va_start(args, err);
7540 fputs("tig: ", stderr);
7541 vfprintf(stderr, err, args);
7542 fputs("\n", stderr);
7543 va_end(args);
7545 exit(1);
7548 static void
7549 warn(const char *msg, ...)
7551 va_list args;
7553 va_start(args, msg);
7554 fputs("tig warning: ", stderr);
7555 vfprintf(stderr, msg, args);
7556 fputs("\n", stderr);
7557 va_end(args);
7560 static enum request
7561 parse_options(int argc, const char *argv[])
7563 enum request request = REQ_VIEW_MAIN;
7564 const char *subcommand;
7565 bool seen_dashdash = FALSE;
7566 /* XXX: This is vulnerable to the user overriding options
7567 * required for the main view parser. */
7568 const char *custom_argv[SIZEOF_ARG] = {
7569 "git", "log", "--no-color", "--pretty=raw", "--parents",
7570 "--topo-order", NULL
7572 int i, j = 6;
7574 if (!isatty(STDIN_FILENO)) {
7575 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7576 return REQ_VIEW_PAGER;
7579 if (argc <= 1)
7580 return REQ_NONE;
7582 subcommand = argv[1];
7583 if (!strcmp(subcommand, "status")) {
7584 if (argc > 2)
7585 warn("ignoring arguments after `%s'", subcommand);
7586 return REQ_VIEW_STATUS;
7588 } else if (!strcmp(subcommand, "blame")) {
7589 if (argc <= 2 || argc > 4)
7590 die("invalid number of options to blame\n\n%s", usage);
7592 i = 2;
7593 if (argc == 4) {
7594 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7595 i++;
7598 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7599 return REQ_VIEW_BLAME;
7601 } else if (!strcmp(subcommand, "show")) {
7602 request = REQ_VIEW_DIFF;
7604 } else {
7605 subcommand = NULL;
7608 if (subcommand) {
7609 custom_argv[1] = subcommand;
7610 j = 2;
7613 for (i = 1 + !!subcommand; i < argc; i++) {
7614 const char *opt = argv[i];
7616 if (seen_dashdash || !strcmp(opt, "--")) {
7617 seen_dashdash = TRUE;
7619 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7620 printf("tig version %s\n", TIG_VERSION);
7621 quit(0);
7623 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7624 printf("%s\n", usage);
7625 quit(0);
7628 custom_argv[j++] = opt;
7629 if (j >= ARRAY_SIZE(custom_argv))
7630 die("command too long");
7633 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7634 die("Failed to format arguments");
7636 return request;
7640 main(int argc, const char *argv[])
7642 enum request request = parse_options(argc, argv);
7643 struct view *view;
7644 size_t i;
7646 signal(SIGINT, quit);
7647 signal(SIGPIPE, SIG_IGN);
7649 if (setlocale(LC_ALL, "")) {
7650 char *codeset = nl_langinfo(CODESET);
7652 string_ncopy(opt_codeset, codeset, strlen(codeset));
7655 if (load_repo_info() == ERR)
7656 die("Failed to load repo info.");
7658 if (load_options() == ERR)
7659 die("Failed to load user config.");
7661 if (load_git_config() == ERR)
7662 die("Failed to load repo config.");
7664 /* Require a git repository unless when running in pager mode. */
7665 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7666 die("Not a git repository");
7668 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7669 opt_utf8 = FALSE;
7671 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7672 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7673 if (opt_iconv == ICONV_NONE)
7674 die("Failed to initialize character set conversion");
7677 if (load_refs() == ERR)
7678 die("Failed to load refs.");
7680 foreach_view (view, i)
7681 argv_from_env(view->ops->argv, view->cmd_env);
7683 init_display();
7685 if (request != REQ_NONE)
7686 open_view(NULL, request, OPEN_PREPARED);
7687 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7689 while (view_driver(display[current_view], request)) {
7690 int key = get_input(0);
7692 view = display[current_view];
7693 request = get_keybinding(view->keymap, key);
7695 /* Some low-level request handling. This keeps access to
7696 * status_win restricted. */
7697 switch (request) {
7698 case REQ_PROMPT:
7700 char *cmd = read_prompt(":");
7702 if (cmd && isdigit(*cmd)) {
7703 int lineno = view->lineno + 1;
7705 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7706 select_view_line(view, lineno - 1);
7707 report("");
7708 } else {
7709 report("Unable to parse '%s' as a line number", cmd);
7712 } else if (cmd) {
7713 struct view *next = VIEW(REQ_VIEW_PAGER);
7714 const char *argv[SIZEOF_ARG] = { "git" };
7715 int argc = 1;
7717 /* When running random commands, initially show the
7718 * command in the title. However, it maybe later be
7719 * overwritten if a commit line is selected. */
7720 string_ncopy(next->ref, cmd, strlen(cmd));
7722 if (!argv_from_string(argv, &argc, cmd)) {
7723 report("Too many arguments");
7724 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7725 report("Failed to format command");
7726 } else {
7727 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7731 request = REQ_NONE;
7732 break;
7734 case REQ_SEARCH:
7735 case REQ_SEARCH_BACK:
7737 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7738 char *search = read_prompt(prompt);
7740 if (search)
7741 string_ncopy(opt_search, search, strlen(search));
7742 else if (*opt_search)
7743 request = request == REQ_SEARCH ?
7744 REQ_FIND_NEXT :
7745 REQ_FIND_PREV;
7746 else
7747 request = REQ_NONE;
7748 break;
7750 default:
7751 break;
7755 quit(0);
7757 return 0;