Status view: limit untracked file by the prefix/subdirectory
[tig.git] / tig.c
blob602c8678614339d8ad6310f6ee158c4063ec25f6
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
110 #define AUTHOR_COLS 19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_TAB '\t'
120 #define KEY_RETURN '\r'
121 #define KEY_ESC 27
124 struct ref {
125 char id[SIZEOF_REV]; /* Commit SHA1 ID */
126 unsigned int head:1; /* Is it the current HEAD? */
127 unsigned int tag:1; /* Is it a tag? */
128 unsigned int ltag:1; /* If so, is the tag local? */
129 unsigned int remote:1; /* Is it a remote ref? */
130 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
131 char name[1]; /* Ref name; tag or head names are shortened. */
134 struct ref_list {
135 char id[SIZEOF_REV]; /* Commit SHA1 ID */
136 size_t size; /* Number of refs. */
137 struct ref **refs; /* References for this ID. */
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum format_flags {
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
152 enum input_status {
153 INPUT_OK,
154 INPUT_SKIP,
155 INPUT_STOP,
156 INPUT_CANCEL
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
164 struct menu_item {
165 int hotkey;
166 const char *text;
167 void *data;
170 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 * Allocation helpers ... Entering macro hell to never be seen again.
176 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
177 static type * \
178 name(type **mem, size_t size, size_t increase) \
180 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
181 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
182 type *tmp = *mem; \
184 if (mem == NULL || num_chunks != num_chunks_new) { \
185 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
186 if (tmp) \
187 *mem = tmp; \
190 return tmp; \
194 * String helpers
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200 if (srclen > dstlen - 1)
201 srclen = dstlen - 1;
203 strncpy(dst, src, srclen);
204 dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213 string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static void
222 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
224 size_t size, pos;
226 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
227 if (src[pos] == '\t') {
228 size_t expanded = tabsize - (size % tabsize);
230 if (expanded + size >= dstlen - 1)
231 expanded = dstlen - size - 1;
232 memcpy(dst + size, " ", expanded);
233 size += expanded;
234 } else {
235 dst[size++] = src[pos];
239 dst[size] = 0;
242 static char *
243 chomp_string(char *name)
245 int namelen;
247 while (isspace(*name))
248 name++;
250 namelen = strlen(name) - 1;
251 while (namelen > 0 && isspace(name[namelen]))
252 name[namelen--] = 0;
254 return name;
257 static bool
258 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
260 va_list args;
261 size_t pos = bufpos ? *bufpos : 0;
263 va_start(args, fmt);
264 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
265 va_end(args);
267 if (bufpos)
268 *bufpos = pos;
270 return pos >= bufsize ? FALSE : TRUE;
273 #define string_format(buf, fmt, args...) \
274 string_nformat(buf, sizeof(buf), NULL, fmt, args)
276 #define string_format_from(buf, from, fmt, args...) \
277 string_nformat(buf, sizeof(buf), from, fmt, args)
279 static int
280 string_enum_compare(const char *str1, const char *str2, int len)
282 size_t i;
284 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
286 /* Diff-Header == DIFF_HEADER */
287 for (i = 0; i < len; i++) {
288 if (toupper(str1[i]) == toupper(str2[i]))
289 continue;
291 if (string_enum_sep(str1[i]) &&
292 string_enum_sep(str2[i]))
293 continue;
295 return str1[i] - str2[i];
298 return 0;
301 #define enum_equals(entry, str, len) \
302 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
304 struct enum_map {
305 const char *name;
306 int namelen;
307 int value;
310 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
312 static char *
313 enum_map_name(const char *name, size_t namelen)
315 static char buf[SIZEOF_STR];
316 int bufpos;
318 for (bufpos = 0; bufpos <= namelen; bufpos++) {
319 buf[bufpos] = tolower(name[bufpos]);
320 if (buf[bufpos] == '_')
321 buf[bufpos] = '-';
324 buf[bufpos] = 0;
325 return buf;
328 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
330 static bool
331 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
333 size_t namelen = strlen(name);
334 int i;
336 for (i = 0; i < map_size; i++)
337 if (enum_equals(map[i], name, namelen)) {
338 *value = map[i].value;
339 return TRUE;
342 return FALSE;
345 #define map_enum(attr, map, name) \
346 map_enum_do(map, ARRAY_SIZE(map), attr, name)
348 #define prefixcmp(str1, str2) \
349 strncmp(str1, str2, STRING_SIZE(str2))
351 static inline int
352 suffixcmp(const char *str, int slen, const char *suffix)
354 size_t len = slen >= 0 ? slen : strlen(str);
355 size_t suffixlen = strlen(suffix);
357 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 * What value of "tz" was in effect back then at "time" in the
363 * local timezone?
365 static int local_tzoffset(time_t time)
367 time_t t, t_local;
368 struct tm tm;
369 int offset, eastwest;
371 t = time;
372 localtime_r(&t, &tm);
373 t_local = mktime(&tm);
375 if (t_local < t) {
376 eastwest = -1;
377 offset = t - t_local;
378 } else {
379 eastwest = 1;
380 offset = t_local - t;
382 offset /= 60; /* in minutes */
383 offset = (offset % 60) + ((offset / 60) * 100);
384 return offset * eastwest;
387 #define DATE_INFO \
388 DATE_(NO), \
389 DATE_(DEFAULT), \
390 DATE_(RELATIVE), \
391 DATE_(SHORT)
393 enum date {
394 #define DATE_(name) DATE_##name
395 DATE_INFO
396 #undef DATE_
399 static const struct enum_map date_map[] = {
400 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
401 DATE_INFO
402 #undef DATE_
405 static const char *
406 string_date(const time_t *time, enum date date)
408 static char buf[DATE_COLS + 1];
409 static const struct enum_map reldate[] = {
410 { "second", 1, 60 * 2 },
411 { "minute", 60, 60 * 60 * 2 },
412 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
413 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
414 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
415 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
417 struct tm tm;
419 if (date == DATE_RELATIVE) {
420 struct timeval now;
421 time_t date = *time + local_tzoffset(*time);
422 time_t seconds;
423 int i;
425 gettimeofday(&now, NULL);
426 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
427 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
428 if (seconds >= reldate[i].value)
429 continue;
431 seconds /= reldate[i].namelen;
432 if (!string_format(buf, "%ld %s%s %s",
433 seconds, reldate[i].name,
434 seconds > 1 ? "s" : "",
435 now.tv_sec >= date ? "ago" : "ahead"))
436 break;
437 return buf;
441 gmtime_r(time, &tm);
442 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
446 #define AUTHOR_VALUES \
447 AUTHOR_(NO), \
448 AUTHOR_(FULL), \
449 AUTHOR_(ABBREVIATED)
451 enum author {
452 #define AUTHOR_(name) AUTHOR_##name
453 AUTHOR_VALUES,
454 #undef AUTHOR_
455 AUTHOR_DEFAULT = AUTHOR_FULL
458 static const struct enum_map author_map[] = {
459 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
460 AUTHOR_VALUES
461 #undef AUTHOR_
464 /* FIXME: Handle multi-byte and multi-column characters. */
465 static const char *
466 get_author_initials(const char *author, size_t max_columns)
468 static char initials[AUTHOR_COLS];
469 size_t pos;
471 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
473 memset(initials, 0, sizeof(initials));
474 for (pos = 0; *author && pos < sizeof(initials) - 1; author++, pos++) {
475 while (is_initial_sep(*author))
476 author++;
477 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
478 while (*author && author[1] && !is_initial_sep(author[1]))
479 author++;
482 return initials;
486 static bool
487 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
489 int valuelen;
491 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
492 bool advance = cmd[valuelen] != 0;
494 cmd[valuelen] = 0;
495 argv[(*argc)++] = chomp_string(cmd);
496 cmd = chomp_string(cmd + valuelen + advance);
499 if (*argc < SIZEOF_ARG)
500 argv[*argc] = NULL;
501 return *argc < SIZEOF_ARG;
504 static void
505 argv_from_env(const char **argv, const char *name)
507 char *env = argv ? getenv(name) : NULL;
508 int argc = 0;
510 if (env && *env)
511 env = strdup(env);
512 if (env && !argv_from_string(argv, &argc, env))
513 die("Too many arguments in the `%s` environment variable", name);
518 * Executing external commands.
521 enum io_type {
522 IO_FD, /* File descriptor based IO. */
523 IO_BG, /* Execute command in the background. */
524 IO_FG, /* Execute command with same std{in,out,err}. */
525 IO_RD, /* Read only fork+exec IO. */
526 IO_WR, /* Write only fork+exec IO. */
527 IO_AP, /* Append fork+exec output to file. */
530 struct io {
531 enum io_type type; /* The requested type of pipe. */
532 const char *dir; /* Directory from which to execute. */
533 pid_t pid; /* Pipe for reading or writing. */
534 int pipe; /* Pipe end for reading or writing. */
535 int error; /* Error status. */
536 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
537 char *buf; /* Read buffer. */
538 size_t bufalloc; /* Allocated buffer size. */
539 size_t bufsize; /* Buffer content size. */
540 char *bufpos; /* Current buffer position. */
541 unsigned int eof:1; /* Has end of file been reached. */
544 static void
545 reset_io(struct io *io)
547 io->pipe = -1;
548 io->pid = 0;
549 io->buf = io->bufpos = NULL;
550 io->bufalloc = io->bufsize = 0;
551 io->error = 0;
552 io->eof = 0;
555 static void
556 init_io(struct io *io, const char *dir, enum io_type type)
558 reset_io(io);
559 io->type = type;
560 io->dir = dir;
563 static bool
564 init_io_rd(struct io *io, const char *argv[], const char *dir,
565 enum format_flags flags)
567 init_io(io, dir, IO_RD);
568 return format_argv(io->argv, argv, flags);
571 static bool
572 io_open(struct io *io, const char *fmt, ...)
574 char name[SIZEOF_STR] = "";
575 bool fits;
576 va_list args;
578 init_io(io, NULL, IO_FD);
580 va_start(args, fmt);
581 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
582 va_end(args);
584 if (!fits) {
585 io->error = ENAMETOOLONG;
586 return FALSE;
588 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
589 if (io->pipe == -1)
590 io->error = errno;
591 return io->pipe != -1;
594 static bool
595 kill_io(struct io *io)
597 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
600 static bool
601 done_io(struct io *io)
603 pid_t pid = io->pid;
605 if (io->pipe != -1)
606 close(io->pipe);
607 free(io->buf);
608 reset_io(io);
610 while (pid > 0) {
611 int status;
612 pid_t waiting = waitpid(pid, &status, 0);
614 if (waiting < 0) {
615 if (errno == EINTR)
616 continue;
617 report("waitpid failed (%s)", strerror(errno));
618 return FALSE;
621 return waiting == pid &&
622 !WIFSIGNALED(status) &&
623 WIFEXITED(status) &&
624 !WEXITSTATUS(status);
627 return TRUE;
630 static bool
631 start_io(struct io *io)
633 int pipefds[2] = { -1, -1 };
635 if (io->type == IO_FD)
636 return TRUE;
638 if ((io->type == IO_RD || io->type == IO_WR) &&
639 pipe(pipefds) < 0)
640 return FALSE;
641 else if (io->type == IO_AP)
642 pipefds[1] = io->pipe;
644 if ((io->pid = fork())) {
645 if (pipefds[!(io->type == IO_WR)] != -1)
646 close(pipefds[!(io->type == IO_WR)]);
647 if (io->pid != -1) {
648 io->pipe = pipefds[!!(io->type == IO_WR)];
649 return TRUE;
652 } else {
653 if (io->type != IO_FG) {
654 int devnull = open("/dev/null", O_RDWR);
655 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
656 int writefd = (io->type == IO_RD || io->type == IO_AP)
657 ? pipefds[1] : devnull;
659 dup2(readfd, STDIN_FILENO);
660 dup2(writefd, STDOUT_FILENO);
661 dup2(devnull, STDERR_FILENO);
663 close(devnull);
664 if (pipefds[0] != -1)
665 close(pipefds[0]);
666 if (pipefds[1] != -1)
667 close(pipefds[1]);
670 if (io->dir && *io->dir && chdir(io->dir) == -1)
671 die("Failed to change directory: %s", strerror(errno));
673 execvp(io->argv[0], (char *const*) io->argv);
674 die("Failed to execute program: %s", strerror(errno));
677 if (pipefds[!!(io->type == IO_WR)] != -1)
678 close(pipefds[!!(io->type == IO_WR)]);
679 return FALSE;
682 static bool
683 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
685 init_io(io, dir, type);
686 if (!format_argv(io->argv, argv, FORMAT_NONE))
687 return FALSE;
688 return start_io(io);
691 static int
692 run_io_do(struct io *io)
694 return start_io(io) && done_io(io);
697 static int
698 run_io_bg(const char **argv)
700 struct io io = {};
702 init_io(&io, NULL, IO_BG);
703 if (!format_argv(io.argv, argv, FORMAT_NONE))
704 return FALSE;
705 return run_io_do(&io);
708 static bool
709 run_io_fg(const char **argv, const char *dir)
711 struct io io = {};
713 init_io(&io, dir, IO_FG);
714 if (!format_argv(io.argv, argv, FORMAT_NONE))
715 return FALSE;
716 return run_io_do(&io);
719 static bool
720 run_io_append(const char **argv, enum format_flags flags, int fd)
722 struct io io = {};
724 init_io(&io, NULL, IO_AP);
725 io.pipe = fd;
726 if (format_argv(io.argv, argv, flags))
727 return run_io_do(&io);
728 close(fd);
729 return FALSE;
732 static bool
733 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
735 return init_io_rd(io, argv, dir, flags) && start_io(io);
738 static bool
739 io_eof(struct io *io)
741 return io->eof;
744 static int
745 io_error(struct io *io)
747 return io->error;
750 static char *
751 io_strerror(struct io *io)
753 return strerror(io->error);
756 static bool
757 io_can_read(struct io *io)
759 struct timeval tv = { 0, 500 };
760 fd_set fds;
762 FD_ZERO(&fds);
763 FD_SET(io->pipe, &fds);
765 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
768 static ssize_t
769 io_read(struct io *io, void *buf, size_t bufsize)
771 do {
772 ssize_t readsize = read(io->pipe, buf, bufsize);
774 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
775 continue;
776 else if (readsize == -1)
777 io->error = errno;
778 else if (readsize == 0)
779 io->eof = 1;
780 return readsize;
781 } while (1);
784 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
786 static char *
787 io_get(struct io *io, int c, bool can_read)
789 char *eol;
790 ssize_t readsize;
792 while (TRUE) {
793 if (io->bufsize > 0) {
794 eol = memchr(io->bufpos, c, io->bufsize);
795 if (eol) {
796 char *line = io->bufpos;
798 *eol = 0;
799 io->bufpos = eol + 1;
800 io->bufsize -= io->bufpos - line;
801 return line;
805 if (io_eof(io)) {
806 if (io->bufsize) {
807 io->bufpos[io->bufsize] = 0;
808 io->bufsize = 0;
809 return io->bufpos;
811 return NULL;
814 if (!can_read)
815 return NULL;
817 if (io->bufsize > 0 && io->bufpos > io->buf)
818 memmove(io->buf, io->bufpos, io->bufsize);
820 if (io->bufalloc == io->bufsize) {
821 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
822 return NULL;
823 io->bufalloc += BUFSIZ;
826 io->bufpos = io->buf;
827 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
828 if (io_error(io))
829 return NULL;
830 io->bufsize += readsize;
834 static bool
835 io_write(struct io *io, const void *buf, size_t bufsize)
837 size_t written = 0;
839 while (!io_error(io) && written < bufsize) {
840 ssize_t size;
842 size = write(io->pipe, buf + written, bufsize - written);
843 if (size < 0 && (errno == EAGAIN || errno == EINTR))
844 continue;
845 else if (size == -1)
846 io->error = errno;
847 else
848 written += size;
851 return written == bufsize;
854 static bool
855 io_read_buf(struct io *io, char buf[], size_t bufsize)
857 char *result = io_get(io, '\n', TRUE);
859 if (result) {
860 result = chomp_string(result);
861 string_ncopy_do(buf, bufsize, result, strlen(result));
864 return done_io(io) && result;
867 static bool
868 run_io_buf(const char **argv, char buf[], size_t bufsize)
870 struct io io = {};
872 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
873 && io_read_buf(&io, buf, bufsize);
876 static int
877 io_load(struct io *io, const char *separators,
878 int (*read_property)(char *, size_t, char *, size_t))
880 char *name;
881 int state = OK;
883 if (!start_io(io))
884 return ERR;
886 while (state == OK && (name = io_get(io, '\n', TRUE))) {
887 char *value;
888 size_t namelen;
889 size_t valuelen;
891 name = chomp_string(name);
892 namelen = strcspn(name, separators);
894 if (name[namelen]) {
895 name[namelen] = 0;
896 value = chomp_string(name + namelen + 1);
897 valuelen = strlen(value);
899 } else {
900 value = "";
901 valuelen = 0;
904 state = read_property(name, namelen, value, valuelen);
907 if (state != ERR && io_error(io))
908 state = ERR;
909 done_io(io);
911 return state;
914 static int
915 run_io_load(const char **argv, const char *separators,
916 int (*read_property)(char *, size_t, char *, size_t))
918 struct io io = {};
920 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
921 ? io_load(&io, separators, read_property) : ERR;
926 * User requests
929 #define REQ_INFO \
930 /* XXX: Keep the view request first and in sync with views[]. */ \
931 REQ_GROUP("View switching") \
932 REQ_(VIEW_MAIN, "Show main view"), \
933 REQ_(VIEW_DIFF, "Show diff view"), \
934 REQ_(VIEW_LOG, "Show log view"), \
935 REQ_(VIEW_TREE, "Show tree view"), \
936 REQ_(VIEW_BLOB, "Show blob view"), \
937 REQ_(VIEW_BLAME, "Show blame view"), \
938 REQ_(VIEW_BRANCH, "Show branch view"), \
939 REQ_(VIEW_HELP, "Show help page"), \
940 REQ_(VIEW_PAGER, "Show pager view"), \
941 REQ_(VIEW_STATUS, "Show status view"), \
942 REQ_(VIEW_STAGE, "Show stage view"), \
944 REQ_GROUP("View manipulation") \
945 REQ_(ENTER, "Enter current line and scroll"), \
946 REQ_(NEXT, "Move to next"), \
947 REQ_(PREVIOUS, "Move to previous"), \
948 REQ_(PARENT, "Move to parent"), \
949 REQ_(VIEW_NEXT, "Move focus to next view"), \
950 REQ_(REFRESH, "Reload and refresh"), \
951 REQ_(MAXIMIZE, "Maximize the current view"), \
952 REQ_(VIEW_CLOSE, "Close the current view"), \
953 REQ_(QUIT, "Close all views and quit"), \
955 REQ_GROUP("View specific requests") \
956 REQ_(STATUS_UPDATE, "Update file status"), \
957 REQ_(STATUS_REVERT, "Revert file changes"), \
958 REQ_(STATUS_MERGE, "Merge file using external tool"), \
959 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
961 REQ_GROUP("Cursor navigation") \
962 REQ_(MOVE_UP, "Move cursor one line up"), \
963 REQ_(MOVE_DOWN, "Move cursor one line down"), \
964 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
965 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
966 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
967 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
969 REQ_GROUP("Scrolling") \
970 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
971 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
972 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
973 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
974 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
975 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
977 REQ_GROUP("Searching") \
978 REQ_(SEARCH, "Search the view"), \
979 REQ_(SEARCH_BACK, "Search backwards in the view"), \
980 REQ_(FIND_NEXT, "Find next search match"), \
981 REQ_(FIND_PREV, "Find previous search match"), \
983 REQ_GROUP("Option manipulation") \
984 REQ_(OPTIONS, "Open option menu"), \
985 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
986 REQ_(TOGGLE_DATE, "Toggle date display"), \
987 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
988 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
989 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
990 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
991 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
992 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
994 REQ_GROUP("Misc") \
995 REQ_(PROMPT, "Bring up the prompt"), \
996 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
997 REQ_(SHOW_VERSION, "Show version information"), \
998 REQ_(STOP_LOADING, "Stop all loading views"), \
999 REQ_(EDIT, "Open in editor"), \
1000 REQ_(NONE, "Do nothing")
1003 /* User action requests. */
1004 enum request {
1005 #define REQ_GROUP(help)
1006 #define REQ_(req, help) REQ_##req
1008 /* Offset all requests to avoid conflicts with ncurses getch values. */
1009 REQ_OFFSET = KEY_MAX + 1,
1010 REQ_INFO
1012 #undef REQ_GROUP
1013 #undef REQ_
1016 struct request_info {
1017 enum request request;
1018 const char *name;
1019 int namelen;
1020 const char *help;
1023 static const struct request_info req_info[] = {
1024 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1025 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1026 REQ_INFO
1027 #undef REQ_GROUP
1028 #undef REQ_
1031 static enum request
1032 get_request(const char *name)
1034 int namelen = strlen(name);
1035 int i;
1037 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1038 if (enum_equals(req_info[i], name, namelen))
1039 return req_info[i].request;
1041 return REQ_NONE;
1046 * Options
1049 /* Option and state variables. */
1050 static enum date opt_date = DATE_DEFAULT;
1051 static enum author opt_author = AUTHOR_DEFAULT;
1052 static bool opt_line_number = FALSE;
1053 static bool opt_line_graphics = TRUE;
1054 static bool opt_rev_graph = FALSE;
1055 static bool opt_show_refs = TRUE;
1056 static int opt_num_interval = 5;
1057 static double opt_hscroll = 0.50;
1058 static double opt_scale_split_view = 2.0 / 3.0;
1059 static int opt_tab_size = 8;
1060 static int opt_author_cols = AUTHOR_COLS;
1061 static char opt_path[SIZEOF_STR] = "";
1062 static char opt_file[SIZEOF_STR] = "";
1063 static char opt_ref[SIZEOF_REF] = "";
1064 static char opt_head[SIZEOF_REF] = "";
1065 static char opt_head_rev[SIZEOF_REV] = "";
1066 static char opt_remote[SIZEOF_REF] = "";
1067 static char opt_encoding[20] = "UTF-8";
1068 static char opt_codeset[20] = "UTF-8";
1069 static iconv_t opt_iconv_in = ICONV_NONE;
1070 static iconv_t opt_iconv_out = ICONV_NONE;
1071 static char opt_search[SIZEOF_STR] = "";
1072 static char opt_cdup[SIZEOF_STR] = "";
1073 static char opt_prefix[SIZEOF_STR] = "";
1074 static char opt_git_dir[SIZEOF_STR] = "";
1075 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1076 static char opt_editor[SIZEOF_STR] = "";
1077 static FILE *opt_tty = NULL;
1079 #define is_initial_commit() (!*opt_head_rev)
1080 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1081 #define mkdate(time) string_date(time, opt_date)
1085 * Line-oriented content detection.
1088 #define LINE_INFO \
1089 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1102 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1103 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1104 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1105 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1106 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1109 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1110 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1111 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1113 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1114 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1115 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1117 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1118 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1119 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1120 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1121 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1122 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1124 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1125 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1126 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1127 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1128 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1130 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1131 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1132 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1133 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1134 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1135 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1137 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1138 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1139 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1140 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1143 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1144 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1145 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1147 enum line_type {
1148 #define LINE(type, line, fg, bg, attr) \
1149 LINE_##type
1150 LINE_INFO,
1151 LINE_NONE
1152 #undef LINE
1155 struct line_info {
1156 const char *name; /* Option name. */
1157 int namelen; /* Size of option name. */
1158 const char *line; /* The start of line to match. */
1159 int linelen; /* Size of string to match. */
1160 int fg, bg, attr; /* Color and text attributes for the lines. */
1163 static struct line_info line_info[] = {
1164 #define LINE(type, line, fg, bg, attr) \
1165 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1166 LINE_INFO
1167 #undef LINE
1170 static enum line_type
1171 get_line_type(const char *line)
1173 int linelen = strlen(line);
1174 enum line_type type;
1176 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1177 /* Case insensitive search matches Signed-off-by lines better. */
1178 if (linelen >= line_info[type].linelen &&
1179 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1180 return type;
1182 return LINE_DEFAULT;
1185 static inline int
1186 get_line_attr(enum line_type type)
1188 assert(type < ARRAY_SIZE(line_info));
1189 return COLOR_PAIR(type) | line_info[type].attr;
1192 static struct line_info *
1193 get_line_info(const char *name)
1195 size_t namelen = strlen(name);
1196 enum line_type type;
1198 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1199 if (enum_equals(line_info[type], name, namelen))
1200 return &line_info[type];
1202 return NULL;
1205 static void
1206 init_colors(void)
1208 int default_bg = line_info[LINE_DEFAULT].bg;
1209 int default_fg = line_info[LINE_DEFAULT].fg;
1210 enum line_type type;
1212 start_color();
1214 if (assume_default_colors(default_fg, default_bg) == ERR) {
1215 default_bg = COLOR_BLACK;
1216 default_fg = COLOR_WHITE;
1219 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1220 struct line_info *info = &line_info[type];
1221 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1222 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1224 init_pair(type, fg, bg);
1228 struct line {
1229 enum line_type type;
1231 /* State flags */
1232 unsigned int selected:1;
1233 unsigned int dirty:1;
1234 unsigned int cleareol:1;
1235 unsigned int other:16;
1237 void *data; /* User data */
1242 * Keys
1245 struct keybinding {
1246 int alias;
1247 enum request request;
1250 static const struct keybinding default_keybindings[] = {
1251 /* View switching */
1252 { 'm', REQ_VIEW_MAIN },
1253 { 'd', REQ_VIEW_DIFF },
1254 { 'l', REQ_VIEW_LOG },
1255 { 't', REQ_VIEW_TREE },
1256 { 'f', REQ_VIEW_BLOB },
1257 { 'B', REQ_VIEW_BLAME },
1258 { 'H', REQ_VIEW_BRANCH },
1259 { 'p', REQ_VIEW_PAGER },
1260 { 'h', REQ_VIEW_HELP },
1261 { 'S', REQ_VIEW_STATUS },
1262 { 'c', REQ_VIEW_STAGE },
1264 /* View manipulation */
1265 { 'q', REQ_VIEW_CLOSE },
1266 { KEY_TAB, REQ_VIEW_NEXT },
1267 { KEY_RETURN, REQ_ENTER },
1268 { KEY_UP, REQ_PREVIOUS },
1269 { KEY_DOWN, REQ_NEXT },
1270 { 'R', REQ_REFRESH },
1271 { KEY_F(5), REQ_REFRESH },
1272 { 'O', REQ_MAXIMIZE },
1274 /* Cursor navigation */
1275 { 'k', REQ_MOVE_UP },
1276 { 'j', REQ_MOVE_DOWN },
1277 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1278 { KEY_END, REQ_MOVE_LAST_LINE },
1279 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1280 { ' ', REQ_MOVE_PAGE_DOWN },
1281 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1282 { 'b', REQ_MOVE_PAGE_UP },
1283 { '-', REQ_MOVE_PAGE_UP },
1285 /* Scrolling */
1286 { KEY_LEFT, REQ_SCROLL_LEFT },
1287 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1288 { KEY_IC, REQ_SCROLL_LINE_UP },
1289 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1290 { 'w', REQ_SCROLL_PAGE_UP },
1291 { 's', REQ_SCROLL_PAGE_DOWN },
1293 /* Searching */
1294 { '/', REQ_SEARCH },
1295 { '?', REQ_SEARCH_BACK },
1296 { 'n', REQ_FIND_NEXT },
1297 { 'N', REQ_FIND_PREV },
1299 /* Misc */
1300 { 'Q', REQ_QUIT },
1301 { 'z', REQ_STOP_LOADING },
1302 { 'v', REQ_SHOW_VERSION },
1303 { 'r', REQ_SCREEN_REDRAW },
1304 { 'o', REQ_OPTIONS },
1305 { '.', REQ_TOGGLE_LINENO },
1306 { 'D', REQ_TOGGLE_DATE },
1307 { 'A', REQ_TOGGLE_AUTHOR },
1308 { 'g', REQ_TOGGLE_REV_GRAPH },
1309 { 'F', REQ_TOGGLE_REFS },
1310 { 'I', REQ_TOGGLE_SORT_ORDER },
1311 { 'i', REQ_TOGGLE_SORT_FIELD },
1312 { ':', REQ_PROMPT },
1313 { 'u', REQ_STATUS_UPDATE },
1314 { '!', REQ_STATUS_REVERT },
1315 { 'M', REQ_STATUS_MERGE },
1316 { '@', REQ_STAGE_NEXT },
1317 { ',', REQ_PARENT },
1318 { 'e', REQ_EDIT },
1321 #define KEYMAP_INFO \
1322 KEYMAP_(GENERIC), \
1323 KEYMAP_(MAIN), \
1324 KEYMAP_(DIFF), \
1325 KEYMAP_(LOG), \
1326 KEYMAP_(TREE), \
1327 KEYMAP_(BLOB), \
1328 KEYMAP_(BLAME), \
1329 KEYMAP_(BRANCH), \
1330 KEYMAP_(PAGER), \
1331 KEYMAP_(HELP), \
1332 KEYMAP_(STATUS), \
1333 KEYMAP_(STAGE)
1335 enum keymap {
1336 #define KEYMAP_(name) KEYMAP_##name
1337 KEYMAP_INFO
1338 #undef KEYMAP_
1341 static const struct enum_map keymap_table[] = {
1342 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1343 KEYMAP_INFO
1344 #undef KEYMAP_
1347 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1349 struct keybinding_table {
1350 struct keybinding *data;
1351 size_t size;
1354 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1356 static void
1357 add_keybinding(enum keymap keymap, enum request request, int key)
1359 struct keybinding_table *table = &keybindings[keymap];
1361 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1362 if (!table->data)
1363 die("Failed to allocate keybinding");
1364 table->data[table->size].alias = key;
1365 table->data[table->size++].request = request;
1368 /* Looks for a key binding first in the given map, then in the generic map, and
1369 * lastly in the default keybindings. */
1370 static enum request
1371 get_keybinding(enum keymap keymap, int key)
1373 size_t i;
1375 for (i = 0; i < keybindings[keymap].size; i++)
1376 if (keybindings[keymap].data[i].alias == key)
1377 return keybindings[keymap].data[i].request;
1379 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1380 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1381 return keybindings[KEYMAP_GENERIC].data[i].request;
1383 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1384 if (default_keybindings[i].alias == key)
1385 return default_keybindings[i].request;
1387 return (enum request) key;
1391 struct key {
1392 const char *name;
1393 int value;
1396 static const struct key key_table[] = {
1397 { "Enter", KEY_RETURN },
1398 { "Space", ' ' },
1399 { "Backspace", KEY_BACKSPACE },
1400 { "Tab", KEY_TAB },
1401 { "Escape", KEY_ESC },
1402 { "Left", KEY_LEFT },
1403 { "Right", KEY_RIGHT },
1404 { "Up", KEY_UP },
1405 { "Down", KEY_DOWN },
1406 { "Insert", KEY_IC },
1407 { "Delete", KEY_DC },
1408 { "Hash", '#' },
1409 { "Home", KEY_HOME },
1410 { "End", KEY_END },
1411 { "PageUp", KEY_PPAGE },
1412 { "PageDown", KEY_NPAGE },
1413 { "F1", KEY_F(1) },
1414 { "F2", KEY_F(2) },
1415 { "F3", KEY_F(3) },
1416 { "F4", KEY_F(4) },
1417 { "F5", KEY_F(5) },
1418 { "F6", KEY_F(6) },
1419 { "F7", KEY_F(7) },
1420 { "F8", KEY_F(8) },
1421 { "F9", KEY_F(9) },
1422 { "F10", KEY_F(10) },
1423 { "F11", KEY_F(11) },
1424 { "F12", KEY_F(12) },
1427 static int
1428 get_key_value(const char *name)
1430 int i;
1432 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1433 if (!strcasecmp(key_table[i].name, name))
1434 return key_table[i].value;
1436 if (strlen(name) == 1 && isprint(*name))
1437 return (int) *name;
1439 return ERR;
1442 static const char *
1443 get_key_name(int key_value)
1445 static char key_char[] = "'X'";
1446 const char *seq = NULL;
1447 int key;
1449 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1450 if (key_table[key].value == key_value)
1451 seq = key_table[key].name;
1453 if (seq == NULL &&
1454 key_value < 127 &&
1455 isprint(key_value)) {
1456 key_char[1] = (char) key_value;
1457 seq = key_char;
1460 return seq ? seq : "(no key)";
1463 static bool
1464 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1466 const char *sep = *pos > 0 ? ", " : "";
1467 const char *keyname = get_key_name(keybinding->alias);
1469 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1472 static bool
1473 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1474 enum keymap keymap, bool all)
1476 int i;
1478 for (i = 0; i < keybindings[keymap].size; i++) {
1479 if (keybindings[keymap].data[i].request == request) {
1480 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1481 return FALSE;
1482 if (!all)
1483 break;
1487 return TRUE;
1490 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1492 static const char *
1493 get_keys(enum keymap keymap, enum request request, bool all)
1495 static char buf[BUFSIZ];
1496 size_t pos = 0;
1497 int i;
1499 buf[pos] = 0;
1501 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1502 return "Too many keybindings!";
1503 if (pos > 0 && !all)
1504 return buf;
1506 if (keymap != KEYMAP_GENERIC) {
1507 /* Only the generic keymap includes the default keybindings when
1508 * listing all keys. */
1509 if (all)
1510 return buf;
1512 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1513 return "Too many keybindings!";
1514 if (pos)
1515 return buf;
1518 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1519 if (default_keybindings[i].request == request) {
1520 if (!append_key(buf, &pos, &default_keybindings[i]))
1521 return "Too many keybindings!";
1522 if (!all)
1523 return buf;
1527 return buf;
1530 struct run_request {
1531 enum keymap keymap;
1532 int key;
1533 const char *argv[SIZEOF_ARG];
1536 static struct run_request *run_request;
1537 static size_t run_requests;
1539 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1541 static enum request
1542 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1544 struct run_request *req;
1546 if (argc >= ARRAY_SIZE(req->argv) - 1)
1547 return REQ_NONE;
1549 if (!realloc_run_requests(&run_request, run_requests, 1))
1550 return REQ_NONE;
1552 req = &run_request[run_requests];
1553 req->keymap = keymap;
1554 req->key = key;
1555 req->argv[0] = NULL;
1557 if (!format_argv(req->argv, argv, FORMAT_NONE))
1558 return REQ_NONE;
1560 return REQ_NONE + ++run_requests;
1563 static struct run_request *
1564 get_run_request(enum request request)
1566 if (request <= REQ_NONE)
1567 return NULL;
1568 return &run_request[request - REQ_NONE - 1];
1571 static void
1572 add_builtin_run_requests(void)
1574 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1575 const char *commit[] = { "git", "commit", NULL };
1576 const char *gc[] = { "git", "gc", NULL };
1577 struct {
1578 enum keymap keymap;
1579 int key;
1580 int argc;
1581 const char **argv;
1582 } reqs[] = {
1583 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1584 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1585 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1587 int i;
1589 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1590 enum request req;
1592 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1593 if (req != REQ_NONE)
1594 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1599 * User config file handling.
1602 static int config_lineno;
1603 static bool config_errors;
1604 static const char *config_msg;
1606 static const struct enum_map color_map[] = {
1607 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1608 COLOR_MAP(DEFAULT),
1609 COLOR_MAP(BLACK),
1610 COLOR_MAP(BLUE),
1611 COLOR_MAP(CYAN),
1612 COLOR_MAP(GREEN),
1613 COLOR_MAP(MAGENTA),
1614 COLOR_MAP(RED),
1615 COLOR_MAP(WHITE),
1616 COLOR_MAP(YELLOW),
1619 static const struct enum_map attr_map[] = {
1620 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1621 ATTR_MAP(NORMAL),
1622 ATTR_MAP(BLINK),
1623 ATTR_MAP(BOLD),
1624 ATTR_MAP(DIM),
1625 ATTR_MAP(REVERSE),
1626 ATTR_MAP(STANDOUT),
1627 ATTR_MAP(UNDERLINE),
1630 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1632 static int parse_step(double *opt, const char *arg)
1634 *opt = atoi(arg);
1635 if (!strchr(arg, '%'))
1636 return OK;
1638 /* "Shift down" so 100% and 1 does not conflict. */
1639 *opt = (*opt - 1) / 100;
1640 if (*opt >= 1.0) {
1641 *opt = 0.99;
1642 config_msg = "Step value larger than 100%";
1643 return ERR;
1645 if (*opt < 0.0) {
1646 *opt = 1;
1647 config_msg = "Invalid step value";
1648 return ERR;
1650 return OK;
1653 static int
1654 parse_int(int *opt, const char *arg, int min, int max)
1656 int value = atoi(arg);
1658 if (min <= value && value <= max) {
1659 *opt = value;
1660 return OK;
1663 config_msg = "Integer value out of bound";
1664 return ERR;
1667 static bool
1668 set_color(int *color, const char *name)
1670 if (map_enum(color, color_map, name))
1671 return TRUE;
1672 if (!prefixcmp(name, "color"))
1673 return parse_int(color, name + 5, 0, 255) == OK;
1674 return FALSE;
1677 /* Wants: object fgcolor bgcolor [attribute] */
1678 static int
1679 option_color_command(int argc, const char *argv[])
1681 struct line_info *info;
1683 if (argc < 3) {
1684 config_msg = "Wrong number of arguments given to color command";
1685 return ERR;
1688 info = get_line_info(argv[0]);
1689 if (!info) {
1690 static const struct enum_map obsolete[] = {
1691 ENUM_MAP("main-delim", LINE_DELIMITER),
1692 ENUM_MAP("main-date", LINE_DATE),
1693 ENUM_MAP("main-author", LINE_AUTHOR),
1695 int index;
1697 if (!map_enum(&index, obsolete, argv[0])) {
1698 config_msg = "Unknown color name";
1699 return ERR;
1701 info = &line_info[index];
1704 if (!set_color(&info->fg, argv[1]) ||
1705 !set_color(&info->bg, argv[2])) {
1706 config_msg = "Unknown color";
1707 return ERR;
1710 info->attr = 0;
1711 while (argc-- > 3) {
1712 int attr;
1714 if (!set_attribute(&attr, argv[argc])) {
1715 config_msg = "Unknown attribute";
1716 return ERR;
1718 info->attr |= attr;
1721 return OK;
1724 static int parse_bool(bool *opt, const char *arg)
1726 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1727 ? TRUE : FALSE;
1728 return OK;
1731 static int parse_enum_do(unsigned int *opt, const char *arg,
1732 const struct enum_map *map, size_t map_size)
1734 bool is_true;
1736 assert(map_size > 1);
1738 if (map_enum_do(map, map_size, (int *) opt, arg))
1739 return OK;
1741 if (parse_bool(&is_true, arg) != OK)
1742 return ERR;
1744 *opt = is_true ? map[1].value : map[0].value;
1745 return OK;
1748 #define parse_enum(opt, arg, map) \
1749 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1751 static int
1752 parse_string(char *opt, const char *arg, size_t optsize)
1754 int arglen = strlen(arg);
1756 switch (arg[0]) {
1757 case '\"':
1758 case '\'':
1759 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1760 config_msg = "Unmatched quotation";
1761 return ERR;
1763 arg += 1; arglen -= 2;
1764 default:
1765 string_ncopy_do(opt, optsize, arg, arglen);
1766 return OK;
1770 /* Wants: name = value */
1771 static int
1772 option_set_command(int argc, const char *argv[])
1774 if (argc != 3) {
1775 config_msg = "Wrong number of arguments given to set command";
1776 return ERR;
1779 if (strcmp(argv[1], "=")) {
1780 config_msg = "No value assigned";
1781 return ERR;
1784 if (!strcmp(argv[0], "show-author"))
1785 return parse_enum(&opt_author, argv[2], author_map);
1787 if (!strcmp(argv[0], "show-date"))
1788 return parse_enum(&opt_date, argv[2], date_map);
1790 if (!strcmp(argv[0], "show-rev-graph"))
1791 return parse_bool(&opt_rev_graph, argv[2]);
1793 if (!strcmp(argv[0], "show-refs"))
1794 return parse_bool(&opt_show_refs, argv[2]);
1796 if (!strcmp(argv[0], "show-line-numbers"))
1797 return parse_bool(&opt_line_number, argv[2]);
1799 if (!strcmp(argv[0], "line-graphics"))
1800 return parse_bool(&opt_line_graphics, argv[2]);
1802 if (!strcmp(argv[0], "line-number-interval"))
1803 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1805 if (!strcmp(argv[0], "author-width"))
1806 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1808 if (!strcmp(argv[0], "horizontal-scroll"))
1809 return parse_step(&opt_hscroll, argv[2]);
1811 if (!strcmp(argv[0], "split-view-height"))
1812 return parse_step(&opt_scale_split_view, argv[2]);
1814 if (!strcmp(argv[0], "tab-size"))
1815 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1817 if (!strcmp(argv[0], "commit-encoding"))
1818 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1820 config_msg = "Unknown variable name";
1821 return ERR;
1824 /* Wants: mode request key */
1825 static int
1826 option_bind_command(int argc, const char *argv[])
1828 enum request request;
1829 int keymap = -1;
1830 int key;
1832 if (argc < 3) {
1833 config_msg = "Wrong number of arguments given to bind command";
1834 return ERR;
1837 if (set_keymap(&keymap, argv[0]) == ERR) {
1838 config_msg = "Unknown key map";
1839 return ERR;
1842 key = get_key_value(argv[1]);
1843 if (key == ERR) {
1844 config_msg = "Unknown key";
1845 return ERR;
1848 request = get_request(argv[2]);
1849 if (request == REQ_NONE) {
1850 static const struct enum_map obsolete[] = {
1851 ENUM_MAP("cherry-pick", REQ_NONE),
1852 ENUM_MAP("screen-resize", REQ_NONE),
1853 ENUM_MAP("tree-parent", REQ_PARENT),
1855 int alias;
1857 if (map_enum(&alias, obsolete, argv[2])) {
1858 if (alias != REQ_NONE)
1859 add_keybinding(keymap, alias, key);
1860 config_msg = "Obsolete request name";
1861 return ERR;
1864 if (request == REQ_NONE && *argv[2]++ == '!')
1865 request = add_run_request(keymap, key, argc - 2, argv + 2);
1866 if (request == REQ_NONE) {
1867 config_msg = "Unknown request name";
1868 return ERR;
1871 add_keybinding(keymap, request, key);
1873 return OK;
1876 static int
1877 set_option(const char *opt, char *value)
1879 const char *argv[SIZEOF_ARG];
1880 int argc = 0;
1882 if (!argv_from_string(argv, &argc, value)) {
1883 config_msg = "Too many option arguments";
1884 return ERR;
1887 if (!strcmp(opt, "color"))
1888 return option_color_command(argc, argv);
1890 if (!strcmp(opt, "set"))
1891 return option_set_command(argc, argv);
1893 if (!strcmp(opt, "bind"))
1894 return option_bind_command(argc, argv);
1896 config_msg = "Unknown option command";
1897 return ERR;
1900 static int
1901 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1903 int status = OK;
1905 config_lineno++;
1906 config_msg = "Internal error";
1908 /* Check for comment markers, since read_properties() will
1909 * only ensure opt and value are split at first " \t". */
1910 optlen = strcspn(opt, "#");
1911 if (optlen == 0)
1912 return OK;
1914 if (opt[optlen] != 0) {
1915 config_msg = "No option value";
1916 status = ERR;
1918 } else {
1919 /* Look for comment endings in the value. */
1920 size_t len = strcspn(value, "#");
1922 if (len < valuelen) {
1923 valuelen = len;
1924 value[valuelen] = 0;
1927 status = set_option(opt, value);
1930 if (status == ERR) {
1931 warn("Error on line %d, near '%.*s': %s",
1932 config_lineno, (int) optlen, opt, config_msg);
1933 config_errors = TRUE;
1936 /* Always keep going if errors are encountered. */
1937 return OK;
1940 static void
1941 load_option_file(const char *path)
1943 struct io io = {};
1945 /* It's OK that the file doesn't exist. */
1946 if (!io_open(&io, "%s", path))
1947 return;
1949 config_lineno = 0;
1950 config_errors = FALSE;
1952 if (io_load(&io, " \t", read_option) == ERR ||
1953 config_errors == TRUE)
1954 warn("Errors while loading %s.", path);
1957 static int
1958 load_options(void)
1960 const char *home = getenv("HOME");
1961 const char *tigrc_user = getenv("TIGRC_USER");
1962 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1963 char buf[SIZEOF_STR];
1965 add_builtin_run_requests();
1967 if (!tigrc_system)
1968 tigrc_system = SYSCONFDIR "/tigrc";
1969 load_option_file(tigrc_system);
1971 if (!tigrc_user) {
1972 if (!home || !string_format(buf, "%s/.tigrc", home))
1973 return ERR;
1974 tigrc_user = buf;
1976 load_option_file(tigrc_user);
1978 return OK;
1983 * The viewer
1986 struct view;
1987 struct view_ops;
1989 /* The display array of active views and the index of the current view. */
1990 static struct view *display[2];
1991 static unsigned int current_view;
1993 #define foreach_displayed_view(view, i) \
1994 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1996 #define displayed_views() (display[1] != NULL ? 2 : 1)
1998 /* Current head and commit ID */
1999 static char ref_blob[SIZEOF_REF] = "";
2000 static char ref_commit[SIZEOF_REF] = "HEAD";
2001 static char ref_head[SIZEOF_REF] = "HEAD";
2003 struct view {
2004 const char *name; /* View name */
2005 const char *cmd_env; /* Command line set via environment */
2006 const char *id; /* Points to either of ref_{head,commit,blob} */
2008 struct view_ops *ops; /* View operations */
2010 enum keymap keymap; /* What keymap does this view have */
2011 bool git_dir; /* Whether the view requires a git directory. */
2013 char ref[SIZEOF_REF]; /* Hovered commit reference */
2014 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2016 int height, width; /* The width and height of the main window */
2017 WINDOW *win; /* The main window */
2018 WINDOW *title; /* The title window living below the main window */
2020 /* Navigation */
2021 unsigned long offset; /* Offset of the window top */
2022 unsigned long yoffset; /* Offset from the window side. */
2023 unsigned long lineno; /* Current line number */
2024 unsigned long p_offset; /* Previous offset of the window top */
2025 unsigned long p_yoffset;/* Previous offset from the window side */
2026 unsigned long p_lineno; /* Previous current line number */
2027 bool p_restore; /* Should the previous position be restored. */
2029 /* Searching */
2030 char grep[SIZEOF_STR]; /* Search string */
2031 regex_t *regex; /* Pre-compiled regexp */
2033 /* If non-NULL, points to the view that opened this view. If this view
2034 * is closed tig will switch back to the parent view. */
2035 struct view *parent;
2037 /* Buffering */
2038 size_t lines; /* Total number of lines */
2039 struct line *line; /* Line index */
2040 unsigned int digits; /* Number of digits in the lines member. */
2042 /* Drawing */
2043 struct line *curline; /* Line currently being drawn. */
2044 enum line_type curtype; /* Attribute currently used for drawing. */
2045 unsigned long col; /* Column when drawing. */
2046 bool has_scrolled; /* View was scrolled. */
2048 /* Loading */
2049 struct io io;
2050 struct io *pipe;
2051 time_t start_time;
2052 time_t update_secs;
2055 struct view_ops {
2056 /* What type of content being displayed. Used in the title bar. */
2057 const char *type;
2058 /* Default command arguments. */
2059 const char **argv;
2060 /* Open and reads in all view content. */
2061 bool (*open)(struct view *view);
2062 /* Read one line; updates view->line. */
2063 bool (*read)(struct view *view, char *data);
2064 /* Draw one line; @lineno must be < view->height. */
2065 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2066 /* Depending on view handle a special requests. */
2067 enum request (*request)(struct view *view, enum request request, struct line *line);
2068 /* Search for regexp in a line. */
2069 bool (*grep)(struct view *view, struct line *line);
2070 /* Select line */
2071 void (*select)(struct view *view, struct line *line);
2072 /* Prepare view for loading */
2073 bool (*prepare)(struct view *view);
2076 static struct view_ops blame_ops;
2077 static struct view_ops blob_ops;
2078 static struct view_ops diff_ops;
2079 static struct view_ops help_ops;
2080 static struct view_ops log_ops;
2081 static struct view_ops main_ops;
2082 static struct view_ops pager_ops;
2083 static struct view_ops stage_ops;
2084 static struct view_ops status_ops;
2085 static struct view_ops tree_ops;
2086 static struct view_ops branch_ops;
2088 #define VIEW_STR(name, env, ref, ops, map, git) \
2089 { name, #env, ref, ops, map, git }
2091 #define VIEW_(id, name, ops, git, ref) \
2092 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2095 static struct view views[] = {
2096 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2097 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2098 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2099 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2100 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2101 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2102 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2103 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2104 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2105 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2106 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2109 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2110 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2112 #define foreach_view(view, i) \
2113 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2115 #define view_is_displayed(view) \
2116 (view == display[0] || view == display[1])
2119 enum line_graphic {
2120 LINE_GRAPHIC_VLINE
2123 static chtype line_graphics[] = {
2124 /* LINE_GRAPHIC_VLINE: */ '|'
2127 static inline void
2128 set_view_attr(struct view *view, enum line_type type)
2130 if (!view->curline->selected && view->curtype != type) {
2131 wattrset(view->win, get_line_attr(type));
2132 wchgat(view->win, -1, 0, type, NULL);
2133 view->curtype = type;
2137 static int
2138 draw_chars(struct view *view, enum line_type type, const char *string,
2139 int max_len, bool use_tilde)
2141 static char out_buffer[BUFSIZ * 2];
2142 int len = 0;
2143 int col = 0;
2144 int trimmed = FALSE;
2145 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2147 if (max_len <= 0)
2148 return 0;
2150 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2152 set_view_attr(view, type);
2153 if (len > 0) {
2154 if (opt_iconv_out != ICONV_NONE) {
2155 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2156 size_t inlen = len + 1;
2158 char *outbuf = out_buffer;
2159 size_t outlen = sizeof(out_buffer);
2161 size_t ret;
2163 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2164 if (ret != (size_t) -1) {
2165 string = out_buffer;
2166 len = sizeof(out_buffer) - outlen;
2170 waddnstr(view->win, string, len);
2172 if (trimmed && use_tilde) {
2173 set_view_attr(view, LINE_DELIMITER);
2174 waddch(view->win, '~');
2175 col++;
2178 return col;
2181 static int
2182 draw_space(struct view *view, enum line_type type, int max, int spaces)
2184 static char space[] = " ";
2185 int col = 0;
2187 spaces = MIN(max, spaces);
2189 while (spaces > 0) {
2190 int len = MIN(spaces, sizeof(space) - 1);
2192 col += draw_chars(view, type, space, len, FALSE);
2193 spaces -= len;
2196 return col;
2199 static bool
2200 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2202 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2203 return view->width + view->yoffset <= view->col;
2206 static bool
2207 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2209 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2210 int max = view->width + view->yoffset - view->col;
2211 int i;
2213 if (max < size)
2214 size = max;
2216 set_view_attr(view, type);
2217 /* Using waddch() instead of waddnstr() ensures that
2218 * they'll be rendered correctly for the cursor line. */
2219 for (i = skip; i < size; i++)
2220 waddch(view->win, graphic[i]);
2222 view->col += size;
2223 if (size < max && skip <= size)
2224 waddch(view->win, ' ');
2225 view->col++;
2227 return view->width + view->yoffset <= view->col;
2230 static bool
2231 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2233 int max = MIN(view->width + view->yoffset - view->col, len);
2234 int col;
2236 if (text)
2237 col = draw_chars(view, type, text, max - 1, trim);
2238 else
2239 col = draw_space(view, type, max - 1, max - 1);
2241 view->col += col;
2242 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2243 return view->width + view->yoffset <= view->col;
2246 static bool
2247 draw_date(struct view *view, time_t *time)
2249 const char *date = time ? mkdate(time) : "";
2250 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2252 return draw_field(view, LINE_DATE, date, cols, FALSE);
2255 static bool
2256 draw_author(struct view *view, const char *author)
2258 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2259 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2261 if (abbreviate && author)
2262 author = get_author_initials(author, opt_author_cols);
2264 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2267 static bool
2268 draw_mode(struct view *view, mode_t mode)
2270 const char *str;
2272 if (S_ISDIR(mode))
2273 str = "drwxr-xr-x";
2274 else if (S_ISLNK(mode))
2275 str = "lrwxrwxrwx";
2276 else if (S_ISGITLINK(mode))
2277 str = "m---------";
2278 else if (S_ISREG(mode) && mode & S_IXUSR)
2279 str = "-rwxr-xr-x";
2280 else if (S_ISREG(mode))
2281 str = "-rw-r--r--";
2282 else
2283 str = "----------";
2285 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2288 static bool
2289 draw_lineno(struct view *view, unsigned int lineno)
2291 char number[10];
2292 int digits3 = view->digits < 3 ? 3 : view->digits;
2293 int max = MIN(view->width + view->yoffset - view->col, digits3);
2294 char *text = NULL;
2296 lineno += view->offset + 1;
2297 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2298 static char fmt[] = "%1ld";
2300 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2301 if (string_format(number, fmt, lineno))
2302 text = number;
2304 if (text)
2305 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2306 else
2307 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2308 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2311 static bool
2312 draw_view_line(struct view *view, unsigned int lineno)
2314 struct line *line;
2315 bool selected = (view->offset + lineno == view->lineno);
2317 assert(view_is_displayed(view));
2319 if (view->offset + lineno >= view->lines)
2320 return FALSE;
2322 line = &view->line[view->offset + lineno];
2324 wmove(view->win, lineno, 0);
2325 if (line->cleareol)
2326 wclrtoeol(view->win);
2327 view->col = 0;
2328 view->curline = line;
2329 view->curtype = LINE_NONE;
2330 line->selected = FALSE;
2331 line->dirty = line->cleareol = 0;
2333 if (selected) {
2334 set_view_attr(view, LINE_CURSOR);
2335 line->selected = TRUE;
2336 view->ops->select(view, line);
2339 return view->ops->draw(view, line, lineno);
2342 static void
2343 redraw_view_dirty(struct view *view)
2345 bool dirty = FALSE;
2346 int lineno;
2348 for (lineno = 0; lineno < view->height; lineno++) {
2349 if (view->offset + lineno >= view->lines)
2350 break;
2351 if (!view->line[view->offset + lineno].dirty)
2352 continue;
2353 dirty = TRUE;
2354 if (!draw_view_line(view, lineno))
2355 break;
2358 if (!dirty)
2359 return;
2360 wnoutrefresh(view->win);
2363 static void
2364 redraw_view_from(struct view *view, int lineno)
2366 assert(0 <= lineno && lineno < view->height);
2368 for (; lineno < view->height; lineno++) {
2369 if (!draw_view_line(view, lineno))
2370 break;
2373 wnoutrefresh(view->win);
2376 static void
2377 redraw_view(struct view *view)
2379 werase(view->win);
2380 redraw_view_from(view, 0);
2384 static void
2385 update_view_title(struct view *view)
2387 char buf[SIZEOF_STR];
2388 char state[SIZEOF_STR];
2389 size_t bufpos = 0, statelen = 0;
2391 assert(view_is_displayed(view));
2393 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2394 unsigned int view_lines = view->offset + view->height;
2395 unsigned int lines = view->lines
2396 ? MIN(view_lines, view->lines) * 100 / view->lines
2397 : 0;
2399 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2400 view->ops->type,
2401 view->lineno + 1,
2402 view->lines,
2403 lines);
2407 if (view->pipe) {
2408 time_t secs = time(NULL) - view->start_time;
2410 /* Three git seconds are a long time ... */
2411 if (secs > 2)
2412 string_format_from(state, &statelen, " loading %lds", secs);
2415 string_format_from(buf, &bufpos, "[%s]", view->name);
2416 if (*view->ref && bufpos < view->width) {
2417 size_t refsize = strlen(view->ref);
2418 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2420 if (minsize < view->width)
2421 refsize = view->width - minsize + 7;
2422 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2425 if (statelen && bufpos < view->width) {
2426 string_format_from(buf, &bufpos, "%s", state);
2429 if (view == display[current_view])
2430 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2431 else
2432 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2434 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2435 wclrtoeol(view->title);
2436 wnoutrefresh(view->title);
2439 static int
2440 apply_step(double step, int value)
2442 if (step >= 1)
2443 return (int) step;
2444 value *= step + 0.01;
2445 return value ? value : 1;
2448 static void
2449 resize_display(void)
2451 int offset, i;
2452 struct view *base = display[0];
2453 struct view *view = display[1] ? display[1] : display[0];
2455 /* Setup window dimensions */
2457 getmaxyx(stdscr, base->height, base->width);
2459 /* Make room for the status window. */
2460 base->height -= 1;
2462 if (view != base) {
2463 /* Horizontal split. */
2464 view->width = base->width;
2465 view->height = apply_step(opt_scale_split_view, base->height);
2466 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2467 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2468 base->height -= view->height;
2470 /* Make room for the title bar. */
2471 view->height -= 1;
2474 /* Make room for the title bar. */
2475 base->height -= 1;
2477 offset = 0;
2479 foreach_displayed_view (view, i) {
2480 if (!view->win) {
2481 view->win = newwin(view->height, 0, offset, 0);
2482 if (!view->win)
2483 die("Failed to create %s view", view->name);
2485 scrollok(view->win, FALSE);
2487 view->title = newwin(1, 0, offset + view->height, 0);
2488 if (!view->title)
2489 die("Failed to create title window");
2491 } else {
2492 wresize(view->win, view->height, view->width);
2493 mvwin(view->win, offset, 0);
2494 mvwin(view->title, offset + view->height, 0);
2497 offset += view->height + 1;
2501 static void
2502 redraw_display(bool clear)
2504 struct view *view;
2505 int i;
2507 foreach_displayed_view (view, i) {
2508 if (clear)
2509 wclear(view->win);
2510 redraw_view(view);
2511 update_view_title(view);
2515 static void
2516 toggle_enum_option_do(unsigned int *opt, const char *help,
2517 const struct enum_map *map, size_t size)
2519 *opt = (*opt + 1) % size;
2520 redraw_display(FALSE);
2521 report("Displaying %s %s", enum_name(map[*opt]), help);
2524 #define toggle_enum_option(opt, help, map) \
2525 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2527 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2528 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2530 static void
2531 toggle_view_option(bool *option, const char *help)
2533 *option = !*option;
2534 redraw_display(FALSE);
2535 report("%sabling %s", *option ? "En" : "Dis", help);
2538 static void
2539 open_option_menu(void)
2541 const struct menu_item menu[] = {
2542 { '.', "line numbers", &opt_line_number },
2543 { 'D', "date display", &opt_date },
2544 { 'A', "author display", &opt_author },
2545 { 'g', "revision graph display", &opt_rev_graph },
2546 { 'F', "reference display", &opt_show_refs },
2547 { 0 }
2549 int selected = 0;
2551 if (prompt_menu("Toggle option", menu, &selected)) {
2552 if (menu[selected].data == &opt_date)
2553 toggle_date();
2554 else if (menu[selected].data == &opt_author)
2555 toggle_author();
2556 else
2557 toggle_view_option(menu[selected].data, menu[selected].text);
2561 static void
2562 maximize_view(struct view *view)
2564 memset(display, 0, sizeof(display));
2565 current_view = 0;
2566 display[current_view] = view;
2567 resize_display();
2568 redraw_display(FALSE);
2569 report("");
2574 * Navigation
2577 static bool
2578 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2580 if (lineno >= view->lines)
2581 lineno = view->lines > 0 ? view->lines - 1 : 0;
2583 if (offset > lineno || offset + view->height <= lineno) {
2584 unsigned long half = view->height / 2;
2586 if (lineno > half)
2587 offset = lineno - half;
2588 else
2589 offset = 0;
2592 if (offset != view->offset || lineno != view->lineno) {
2593 view->offset = offset;
2594 view->lineno = lineno;
2595 return TRUE;
2598 return FALSE;
2601 /* Scrolling backend */
2602 static void
2603 do_scroll_view(struct view *view, int lines)
2605 bool redraw_current_line = FALSE;
2607 /* The rendering expects the new offset. */
2608 view->offset += lines;
2610 assert(0 <= view->offset && view->offset < view->lines);
2611 assert(lines);
2613 /* Move current line into the view. */
2614 if (view->lineno < view->offset) {
2615 view->lineno = view->offset;
2616 redraw_current_line = TRUE;
2617 } else if (view->lineno >= view->offset + view->height) {
2618 view->lineno = view->offset + view->height - 1;
2619 redraw_current_line = TRUE;
2622 assert(view->offset <= view->lineno && view->lineno < view->lines);
2624 /* Redraw the whole screen if scrolling is pointless. */
2625 if (view->height < ABS(lines)) {
2626 redraw_view(view);
2628 } else {
2629 int line = lines > 0 ? view->height - lines : 0;
2630 int end = line + ABS(lines);
2632 scrollok(view->win, TRUE);
2633 wscrl(view->win, lines);
2634 scrollok(view->win, FALSE);
2636 while (line < end && draw_view_line(view, line))
2637 line++;
2639 if (redraw_current_line)
2640 draw_view_line(view, view->lineno - view->offset);
2641 wnoutrefresh(view->win);
2644 view->has_scrolled = TRUE;
2645 report("");
2648 /* Scroll frontend */
2649 static void
2650 scroll_view(struct view *view, enum request request)
2652 int lines = 1;
2654 assert(view_is_displayed(view));
2656 switch (request) {
2657 case REQ_SCROLL_LEFT:
2658 if (view->yoffset == 0) {
2659 report("Cannot scroll beyond the first column");
2660 return;
2662 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2663 view->yoffset = 0;
2664 else
2665 view->yoffset -= apply_step(opt_hscroll, view->width);
2666 redraw_view_from(view, 0);
2667 report("");
2668 return;
2669 case REQ_SCROLL_RIGHT:
2670 view->yoffset += apply_step(opt_hscroll, view->width);
2671 redraw_view(view);
2672 report("");
2673 return;
2674 case REQ_SCROLL_PAGE_DOWN:
2675 lines = view->height;
2676 case REQ_SCROLL_LINE_DOWN:
2677 if (view->offset + lines > view->lines)
2678 lines = view->lines - view->offset;
2680 if (lines == 0 || view->offset + view->height >= view->lines) {
2681 report("Cannot scroll beyond the last line");
2682 return;
2684 break;
2686 case REQ_SCROLL_PAGE_UP:
2687 lines = view->height;
2688 case REQ_SCROLL_LINE_UP:
2689 if (lines > view->offset)
2690 lines = view->offset;
2692 if (lines == 0) {
2693 report("Cannot scroll beyond the first line");
2694 return;
2697 lines = -lines;
2698 break;
2700 default:
2701 die("request %d not handled in switch", request);
2704 do_scroll_view(view, lines);
2707 /* Cursor moving */
2708 static void
2709 move_view(struct view *view, enum request request)
2711 int scroll_steps = 0;
2712 int steps;
2714 switch (request) {
2715 case REQ_MOVE_FIRST_LINE:
2716 steps = -view->lineno;
2717 break;
2719 case REQ_MOVE_LAST_LINE:
2720 steps = view->lines - view->lineno - 1;
2721 break;
2723 case REQ_MOVE_PAGE_UP:
2724 steps = view->height > view->lineno
2725 ? -view->lineno : -view->height;
2726 break;
2728 case REQ_MOVE_PAGE_DOWN:
2729 steps = view->lineno + view->height >= view->lines
2730 ? view->lines - view->lineno - 1 : view->height;
2731 break;
2733 case REQ_MOVE_UP:
2734 steps = -1;
2735 break;
2737 case REQ_MOVE_DOWN:
2738 steps = 1;
2739 break;
2741 default:
2742 die("request %d not handled in switch", request);
2745 if (steps <= 0 && view->lineno == 0) {
2746 report("Cannot move beyond the first line");
2747 return;
2749 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2750 report("Cannot move beyond the last line");
2751 return;
2754 /* Move the current line */
2755 view->lineno += steps;
2756 assert(0 <= view->lineno && view->lineno < view->lines);
2758 /* Check whether the view needs to be scrolled */
2759 if (view->lineno < view->offset ||
2760 view->lineno >= view->offset + view->height) {
2761 scroll_steps = steps;
2762 if (steps < 0 && -steps > view->offset) {
2763 scroll_steps = -view->offset;
2765 } else if (steps > 0) {
2766 if (view->lineno == view->lines - 1 &&
2767 view->lines > view->height) {
2768 scroll_steps = view->lines - view->offset - 1;
2769 if (scroll_steps >= view->height)
2770 scroll_steps -= view->height - 1;
2775 if (!view_is_displayed(view)) {
2776 view->offset += scroll_steps;
2777 assert(0 <= view->offset && view->offset < view->lines);
2778 view->ops->select(view, &view->line[view->lineno]);
2779 return;
2782 /* Repaint the old "current" line if we be scrolling */
2783 if (ABS(steps) < view->height)
2784 draw_view_line(view, view->lineno - steps - view->offset);
2786 if (scroll_steps) {
2787 do_scroll_view(view, scroll_steps);
2788 return;
2791 /* Draw the current line */
2792 draw_view_line(view, view->lineno - view->offset);
2794 wnoutrefresh(view->win);
2795 report("");
2800 * Searching
2803 static void search_view(struct view *view, enum request request);
2805 static bool
2806 grep_text(struct view *view, const char *text[])
2808 regmatch_t pmatch;
2809 size_t i;
2811 for (i = 0; text[i]; i++)
2812 if (*text[i] &&
2813 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2814 return TRUE;
2815 return FALSE;
2818 static void
2819 select_view_line(struct view *view, unsigned long lineno)
2821 unsigned long old_lineno = view->lineno;
2822 unsigned long old_offset = view->offset;
2824 if (goto_view_line(view, view->offset, lineno)) {
2825 if (view_is_displayed(view)) {
2826 if (old_offset != view->offset) {
2827 redraw_view(view);
2828 } else {
2829 draw_view_line(view, old_lineno - view->offset);
2830 draw_view_line(view, view->lineno - view->offset);
2831 wnoutrefresh(view->win);
2833 } else {
2834 view->ops->select(view, &view->line[view->lineno]);
2839 static void
2840 find_next(struct view *view, enum request request)
2842 unsigned long lineno = view->lineno;
2843 int direction;
2845 if (!*view->grep) {
2846 if (!*opt_search)
2847 report("No previous search");
2848 else
2849 search_view(view, request);
2850 return;
2853 switch (request) {
2854 case REQ_SEARCH:
2855 case REQ_FIND_NEXT:
2856 direction = 1;
2857 break;
2859 case REQ_SEARCH_BACK:
2860 case REQ_FIND_PREV:
2861 direction = -1;
2862 break;
2864 default:
2865 return;
2868 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2869 lineno += direction;
2871 /* Note, lineno is unsigned long so will wrap around in which case it
2872 * will become bigger than view->lines. */
2873 for (; lineno < view->lines; lineno += direction) {
2874 if (view->ops->grep(view, &view->line[lineno])) {
2875 select_view_line(view, lineno);
2876 report("Line %ld matches '%s'", lineno + 1, view->grep);
2877 return;
2881 report("No match found for '%s'", view->grep);
2884 static void
2885 search_view(struct view *view, enum request request)
2887 int regex_err;
2889 if (view->regex) {
2890 regfree(view->regex);
2891 *view->grep = 0;
2892 } else {
2893 view->regex = calloc(1, sizeof(*view->regex));
2894 if (!view->regex)
2895 return;
2898 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2899 if (regex_err != 0) {
2900 char buf[SIZEOF_STR] = "unknown error";
2902 regerror(regex_err, view->regex, buf, sizeof(buf));
2903 report("Search failed: %s", buf);
2904 return;
2907 string_copy(view->grep, opt_search);
2909 find_next(view, request);
2913 * Incremental updating
2916 static void
2917 reset_view(struct view *view)
2919 int i;
2921 for (i = 0; i < view->lines; i++)
2922 free(view->line[i].data);
2923 free(view->line);
2925 view->p_offset = view->offset;
2926 view->p_yoffset = view->yoffset;
2927 view->p_lineno = view->lineno;
2929 view->line = NULL;
2930 view->offset = 0;
2931 view->yoffset = 0;
2932 view->lines = 0;
2933 view->lineno = 0;
2934 view->vid[0] = 0;
2935 view->update_secs = 0;
2938 static void
2939 free_argv(const char *argv[])
2941 int argc;
2943 for (argc = 0; argv[argc]; argc++)
2944 free((void *) argv[argc]);
2947 static const char *
2948 format_arg(const char *name)
2950 static struct {
2951 const char *name;
2952 size_t namelen;
2953 const char *value;
2954 const char *value_if_empty;
2955 } vars[] = {
2956 #define FORMAT_VAR(name, value, value_if_empty) \
2957 { name, STRING_SIZE(name), value, value_if_empty }
2958 FORMAT_VAR("%(directory)", opt_path, ""),
2959 FORMAT_VAR("%(file)", opt_file, ""),
2960 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2961 FORMAT_VAR("%(head)", ref_head, ""),
2962 FORMAT_VAR("%(commit)", ref_commit, ""),
2963 FORMAT_VAR("%(blob)", ref_blob, ""),
2965 int i;
2967 for (i = 0; i < ARRAY_SIZE(vars); i++)
2968 if (!strncmp(name, vars[i].name, vars[i].namelen))
2969 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2971 return NULL;
2973 static bool
2974 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2976 char buf[SIZEOF_STR];
2977 int argc;
2978 bool noreplace = flags == FORMAT_NONE;
2980 free_argv(dst_argv);
2982 for (argc = 0; src_argv[argc]; argc++) {
2983 const char *arg = src_argv[argc];
2984 size_t bufpos = 0;
2986 while (arg) {
2987 char *next = strstr(arg, "%(");
2988 int len = next - arg;
2989 const char *value;
2991 if (!next || noreplace) {
2992 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2993 noreplace = TRUE;
2994 len = strlen(arg);
2995 value = "";
2997 } else {
2998 value = format_arg(next);
3000 if (!value) {
3001 report("Unknown replacement: `%s`", next);
3002 return FALSE;
3006 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3007 return FALSE;
3009 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3012 dst_argv[argc] = strdup(buf);
3013 if (!dst_argv[argc])
3014 break;
3017 dst_argv[argc] = NULL;
3019 return src_argv[argc] == NULL;
3022 static bool
3023 restore_view_position(struct view *view)
3025 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3026 return FALSE;
3028 /* Changing the view position cancels the restoring. */
3029 /* FIXME: Changing back to the first line is not detected. */
3030 if (view->offset != 0 || view->lineno != 0) {
3031 view->p_restore = FALSE;
3032 return FALSE;
3035 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3036 view_is_displayed(view))
3037 werase(view->win);
3039 view->yoffset = view->p_yoffset;
3040 view->p_restore = FALSE;
3042 return TRUE;
3045 static void
3046 end_update(struct view *view, bool force)
3048 if (!view->pipe)
3049 return;
3050 while (!view->ops->read(view, NULL))
3051 if (!force)
3052 return;
3053 set_nonblocking_input(FALSE);
3054 if (force)
3055 kill_io(view->pipe);
3056 done_io(view->pipe);
3057 view->pipe = NULL;
3060 static void
3061 setup_update(struct view *view, const char *vid)
3063 set_nonblocking_input(TRUE);
3064 reset_view(view);
3065 string_copy_rev(view->vid, vid);
3066 view->pipe = &view->io;
3067 view->start_time = time(NULL);
3070 static bool
3071 prepare_update(struct view *view, const char *argv[], const char *dir,
3072 enum format_flags flags)
3074 if (view->pipe)
3075 end_update(view, TRUE);
3076 return init_io_rd(&view->io, argv, dir, flags);
3079 static bool
3080 prepare_update_file(struct view *view, const char *name)
3082 if (view->pipe)
3083 end_update(view, TRUE);
3084 return io_open(&view->io, "%s", name);
3087 static bool
3088 begin_update(struct view *view, bool refresh)
3090 if (view->pipe)
3091 end_update(view, TRUE);
3093 if (!refresh) {
3094 if (view->ops->prepare) {
3095 if (!view->ops->prepare(view))
3096 return FALSE;
3097 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3098 return FALSE;
3101 /* Put the current ref_* value to the view title ref
3102 * member. This is needed by the blob view. Most other
3103 * views sets it automatically after loading because the
3104 * first line is a commit line. */
3105 string_copy_rev(view->ref, view->id);
3108 if (!start_io(&view->io))
3109 return FALSE;
3111 setup_update(view, view->id);
3113 return TRUE;
3116 static bool
3117 update_view(struct view *view)
3119 char out_buffer[BUFSIZ * 2];
3120 char *line;
3121 /* Clear the view and redraw everything since the tree sorting
3122 * might have rearranged things. */
3123 bool redraw = view->lines == 0;
3124 bool can_read = TRUE;
3126 if (!view->pipe)
3127 return TRUE;
3129 if (!io_can_read(view->pipe)) {
3130 if (view->lines == 0 && view_is_displayed(view)) {
3131 time_t secs = time(NULL) - view->start_time;
3133 if (secs > 1 && secs > view->update_secs) {
3134 if (view->update_secs == 0)
3135 redraw_view(view);
3136 update_view_title(view);
3137 view->update_secs = secs;
3140 return TRUE;
3143 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3144 if (opt_iconv_in != ICONV_NONE) {
3145 ICONV_CONST char *inbuf = line;
3146 size_t inlen = strlen(line) + 1;
3148 char *outbuf = out_buffer;
3149 size_t outlen = sizeof(out_buffer);
3151 size_t ret;
3153 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3154 if (ret != (size_t) -1)
3155 line = out_buffer;
3158 if (!view->ops->read(view, line)) {
3159 report("Allocation failure");
3160 end_update(view, TRUE);
3161 return FALSE;
3166 unsigned long lines = view->lines;
3167 int digits;
3169 for (digits = 0; lines; digits++)
3170 lines /= 10;
3172 /* Keep the displayed view in sync with line number scaling. */
3173 if (digits != view->digits) {
3174 view->digits = digits;
3175 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3176 redraw = TRUE;
3180 if (io_error(view->pipe)) {
3181 report("Failed to read: %s", io_strerror(view->pipe));
3182 end_update(view, TRUE);
3184 } else if (io_eof(view->pipe)) {
3185 report("");
3186 end_update(view, FALSE);
3189 if (restore_view_position(view))
3190 redraw = TRUE;
3192 if (!view_is_displayed(view))
3193 return TRUE;
3195 if (redraw)
3196 redraw_view_from(view, 0);
3197 else
3198 redraw_view_dirty(view);
3200 /* Update the title _after_ the redraw so that if the redraw picks up a
3201 * commit reference in view->ref it'll be available here. */
3202 update_view_title(view);
3203 return TRUE;
3206 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3208 static struct line *
3209 add_line_data(struct view *view, void *data, enum line_type type)
3211 struct line *line;
3213 if (!realloc_lines(&view->line, view->lines, 1))
3214 return NULL;
3216 line = &view->line[view->lines++];
3217 memset(line, 0, sizeof(*line));
3218 line->type = type;
3219 line->data = data;
3220 line->dirty = 1;
3222 return line;
3225 static struct line *
3226 add_line_text(struct view *view, const char *text, enum line_type type)
3228 char *data = text ? strdup(text) : NULL;
3230 return data ? add_line_data(view, data, type) : NULL;
3233 static struct line *
3234 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3236 char buf[SIZEOF_STR];
3237 va_list args;
3239 va_start(args, fmt);
3240 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3241 buf[0] = 0;
3242 va_end(args);
3244 return buf[0] ? add_line_text(view, buf, type) : NULL;
3248 * View opening
3251 enum open_flags {
3252 OPEN_DEFAULT = 0, /* Use default view switching. */
3253 OPEN_SPLIT = 1, /* Split current view. */
3254 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3255 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3256 OPEN_PREPARED = 32, /* Open already prepared command. */
3259 static void
3260 open_view(struct view *prev, enum request request, enum open_flags flags)
3262 bool split = !!(flags & OPEN_SPLIT);
3263 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3264 bool nomaximize = !!(flags & OPEN_REFRESH);
3265 struct view *view = VIEW(request);
3266 int nviews = displayed_views();
3267 struct view *base_view = display[0];
3269 if (view == prev && nviews == 1 && !reload) {
3270 report("Already in %s view", view->name);
3271 return;
3274 if (view->git_dir && !opt_git_dir[0]) {
3275 report("The %s view is disabled in pager view", view->name);
3276 return;
3279 if (split) {
3280 display[1] = view;
3281 current_view = 1;
3282 } else if (!nomaximize) {
3283 /* Maximize the current view. */
3284 memset(display, 0, sizeof(display));
3285 current_view = 0;
3286 display[current_view] = view;
3289 /* No parent signals that this is the first loaded view. */
3290 if (prev && view != prev) {
3291 view->parent = prev;
3294 /* Resize the view when switching between split- and full-screen,
3295 * or when switching between two different full-screen views. */
3296 if (nviews != displayed_views() ||
3297 (nviews == 1 && base_view != display[0]))
3298 resize_display();
3300 if (view->ops->open) {
3301 if (view->pipe)
3302 end_update(view, TRUE);
3303 if (!view->ops->open(view)) {
3304 report("Failed to load %s view", view->name);
3305 return;
3307 restore_view_position(view);
3309 } else if ((reload || strcmp(view->vid, view->id)) &&
3310 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3311 report("Failed to load %s view", view->name);
3312 return;
3315 if (split && prev->lineno - prev->offset >= prev->height) {
3316 /* Take the title line into account. */
3317 int lines = prev->lineno - prev->offset - prev->height + 1;
3319 /* Scroll the view that was split if the current line is
3320 * outside the new limited view. */
3321 do_scroll_view(prev, lines);
3324 if (prev && view != prev && split && view_is_displayed(prev)) {
3325 /* "Blur" the previous view. */
3326 update_view_title(prev);
3329 if (view->pipe && view->lines == 0) {
3330 /* Clear the old view and let the incremental updating refill
3331 * the screen. */
3332 werase(view->win);
3333 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3334 report("");
3335 } else if (view_is_displayed(view)) {
3336 redraw_view(view);
3337 report("");
3341 static void
3342 open_external_viewer(const char *argv[], const char *dir)
3344 def_prog_mode(); /* save current tty modes */
3345 endwin(); /* restore original tty modes */
3346 run_io_fg(argv, dir);
3347 fprintf(stderr, "Press Enter to continue");
3348 getc(opt_tty);
3349 reset_prog_mode();
3350 redraw_display(TRUE);
3353 static void
3354 open_mergetool(const char *file)
3356 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3358 open_external_viewer(mergetool_argv, opt_cdup);
3361 static void
3362 open_editor(const char *file)
3364 const char *editor_argv[] = { "vi", file, NULL };
3365 const char *editor;
3367 editor = getenv("GIT_EDITOR");
3368 if (!editor && *opt_editor)
3369 editor = opt_editor;
3370 if (!editor)
3371 editor = getenv("VISUAL");
3372 if (!editor)
3373 editor = getenv("EDITOR");
3374 if (!editor)
3375 editor = "vi";
3377 editor_argv[0] = editor;
3378 open_external_viewer(editor_argv, opt_cdup);
3381 static void
3382 open_run_request(enum request request)
3384 struct run_request *req = get_run_request(request);
3385 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3387 if (!req) {
3388 report("Unknown run request");
3389 return;
3392 if (format_argv(argv, req->argv, FORMAT_ALL))
3393 open_external_viewer(argv, NULL);
3394 free_argv(argv);
3398 * User request switch noodle
3401 static int
3402 view_driver(struct view *view, enum request request)
3404 int i;
3406 if (request == REQ_NONE)
3407 return TRUE;
3409 if (request > REQ_NONE) {
3410 open_run_request(request);
3411 /* FIXME: When all views can refresh always do this. */
3412 if (view == VIEW(REQ_VIEW_STATUS) ||
3413 view == VIEW(REQ_VIEW_MAIN) ||
3414 view == VIEW(REQ_VIEW_LOG) ||
3415 view == VIEW(REQ_VIEW_BRANCH) ||
3416 view == VIEW(REQ_VIEW_STAGE))
3417 request = REQ_REFRESH;
3418 else
3419 return TRUE;
3422 if (view && view->lines) {
3423 request = view->ops->request(view, request, &view->line[view->lineno]);
3424 if (request == REQ_NONE)
3425 return TRUE;
3428 switch (request) {
3429 case REQ_MOVE_UP:
3430 case REQ_MOVE_DOWN:
3431 case REQ_MOVE_PAGE_UP:
3432 case REQ_MOVE_PAGE_DOWN:
3433 case REQ_MOVE_FIRST_LINE:
3434 case REQ_MOVE_LAST_LINE:
3435 move_view(view, request);
3436 break;
3438 case REQ_SCROLL_LEFT:
3439 case REQ_SCROLL_RIGHT:
3440 case REQ_SCROLL_LINE_DOWN:
3441 case REQ_SCROLL_LINE_UP:
3442 case REQ_SCROLL_PAGE_DOWN:
3443 case REQ_SCROLL_PAGE_UP:
3444 scroll_view(view, request);
3445 break;
3447 case REQ_VIEW_BLAME:
3448 if (!opt_file[0]) {
3449 report("No file chosen, press %s to open tree view",
3450 get_key(view->keymap, REQ_VIEW_TREE));
3451 break;
3453 open_view(view, request, OPEN_DEFAULT);
3454 break;
3456 case REQ_VIEW_BLOB:
3457 if (!ref_blob[0]) {
3458 report("No file chosen, press %s to open tree view",
3459 get_key(view->keymap, REQ_VIEW_TREE));
3460 break;
3462 open_view(view, request, OPEN_DEFAULT);
3463 break;
3465 case REQ_VIEW_PAGER:
3466 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3467 report("No pager content, press %s to run command from prompt",
3468 get_key(view->keymap, REQ_PROMPT));
3469 break;
3471 open_view(view, request, OPEN_DEFAULT);
3472 break;
3474 case REQ_VIEW_STAGE:
3475 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3476 report("No stage content, press %s to open the status view and choose file",
3477 get_key(view->keymap, REQ_VIEW_STATUS));
3478 break;
3480 open_view(view, request, OPEN_DEFAULT);
3481 break;
3483 case REQ_VIEW_STATUS:
3484 if (opt_is_inside_work_tree == FALSE) {
3485 report("The status view requires a working tree");
3486 break;
3488 open_view(view, request, OPEN_DEFAULT);
3489 break;
3491 case REQ_VIEW_MAIN:
3492 case REQ_VIEW_DIFF:
3493 case REQ_VIEW_LOG:
3494 case REQ_VIEW_TREE:
3495 case REQ_VIEW_HELP:
3496 case REQ_VIEW_BRANCH:
3497 open_view(view, request, OPEN_DEFAULT);
3498 break;
3500 case REQ_NEXT:
3501 case REQ_PREVIOUS:
3502 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3504 if ((view == VIEW(REQ_VIEW_DIFF) &&
3505 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3506 (view == VIEW(REQ_VIEW_DIFF) &&
3507 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3508 (view == VIEW(REQ_VIEW_STAGE) &&
3509 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3510 (view == VIEW(REQ_VIEW_BLOB) &&
3511 view->parent == VIEW(REQ_VIEW_TREE)) ||
3512 (view == VIEW(REQ_VIEW_MAIN) &&
3513 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3514 int line;
3516 view = view->parent;
3517 line = view->lineno;
3518 move_view(view, request);
3519 if (view_is_displayed(view))
3520 update_view_title(view);
3521 if (line != view->lineno)
3522 view->ops->request(view, REQ_ENTER,
3523 &view->line[view->lineno]);
3525 } else {
3526 move_view(view, request);
3528 break;
3530 case REQ_VIEW_NEXT:
3532 int nviews = displayed_views();
3533 int next_view = (current_view + 1) % nviews;
3535 if (next_view == current_view) {
3536 report("Only one view is displayed");
3537 break;
3540 current_view = next_view;
3541 /* Blur out the title of the previous view. */
3542 update_view_title(view);
3543 report("");
3544 break;
3546 case REQ_REFRESH:
3547 report("Refreshing is not yet supported for the %s view", view->name);
3548 break;
3550 case REQ_MAXIMIZE:
3551 if (displayed_views() == 2)
3552 maximize_view(view);
3553 break;
3555 case REQ_OPTIONS:
3556 open_option_menu();
3557 break;
3559 case REQ_TOGGLE_LINENO:
3560 toggle_view_option(&opt_line_number, "line numbers");
3561 break;
3563 case REQ_TOGGLE_DATE:
3564 toggle_date();
3565 break;
3567 case REQ_TOGGLE_AUTHOR:
3568 toggle_author();
3569 break;
3571 case REQ_TOGGLE_REV_GRAPH:
3572 toggle_view_option(&opt_rev_graph, "revision graph display");
3573 break;
3575 case REQ_TOGGLE_REFS:
3576 toggle_view_option(&opt_show_refs, "reference display");
3577 break;
3579 case REQ_TOGGLE_SORT_FIELD:
3580 case REQ_TOGGLE_SORT_ORDER:
3581 report("Sorting is not yet supported for the %s view", view->name);
3582 break;
3584 case REQ_SEARCH:
3585 case REQ_SEARCH_BACK:
3586 search_view(view, request);
3587 break;
3589 case REQ_FIND_NEXT:
3590 case REQ_FIND_PREV:
3591 find_next(view, request);
3592 break;
3594 case REQ_STOP_LOADING:
3595 for (i = 0; i < ARRAY_SIZE(views); i++) {
3596 view = &views[i];
3597 if (view->pipe)
3598 report("Stopped loading the %s view", view->name),
3599 end_update(view, TRUE);
3601 break;
3603 case REQ_SHOW_VERSION:
3604 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3605 return TRUE;
3607 case REQ_SCREEN_REDRAW:
3608 redraw_display(TRUE);
3609 break;
3611 case REQ_EDIT:
3612 report("Nothing to edit");
3613 break;
3615 case REQ_ENTER:
3616 report("Nothing to enter");
3617 break;
3619 case REQ_VIEW_CLOSE:
3620 /* XXX: Mark closed views by letting view->parent point to the
3621 * view itself. Parents to closed view should never be
3622 * followed. */
3623 if (view->parent &&
3624 view->parent->parent != view->parent) {
3625 maximize_view(view->parent);
3626 view->parent = view;
3627 break;
3629 /* Fall-through */
3630 case REQ_QUIT:
3631 return FALSE;
3633 default:
3634 report("Unknown key, press %s for help",
3635 get_key(view->keymap, REQ_VIEW_HELP));
3636 return TRUE;
3639 return TRUE;
3644 * View backend utilities
3647 enum sort_field {
3648 ORDERBY_NAME,
3649 ORDERBY_DATE,
3650 ORDERBY_AUTHOR,
3653 struct sort_state {
3654 const enum sort_field *fields;
3655 size_t size, current;
3656 bool reverse;
3659 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3660 #define get_sort_field(state) ((state).fields[(state).current])
3661 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3663 static void
3664 sort_view(struct view *view, enum request request, struct sort_state *state,
3665 int (*compare)(const void *, const void *))
3667 switch (request) {
3668 case REQ_TOGGLE_SORT_FIELD:
3669 state->current = (state->current + 1) % state->size;
3670 break;
3672 case REQ_TOGGLE_SORT_ORDER:
3673 state->reverse = !state->reverse;
3674 break;
3675 default:
3676 die("Not a sort request");
3679 qsort(view->line, view->lines, sizeof(*view->line), compare);
3680 redraw_view(view);
3683 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3685 /* Small author cache to reduce memory consumption. It uses binary
3686 * search to lookup or find place to position new entries. No entries
3687 * are ever freed. */
3688 static const char *
3689 get_author(const char *name)
3691 static const char **authors;
3692 static size_t authors_size;
3693 int from = 0, to = authors_size - 1;
3695 while (from <= to) {
3696 size_t pos = (to + from) / 2;
3697 int cmp = strcmp(name, authors[pos]);
3699 if (!cmp)
3700 return authors[pos];
3702 if (cmp < 0)
3703 to = pos - 1;
3704 else
3705 from = pos + 1;
3708 if (!realloc_authors(&authors, authors_size, 1))
3709 return NULL;
3710 name = strdup(name);
3711 if (!name)
3712 return NULL;
3714 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3715 authors[from] = name;
3716 authors_size++;
3718 return name;
3721 static void
3722 parse_timezone(time_t *time, const char *zone)
3724 long tz;
3726 tz = ('0' - zone[1]) * 60 * 60 * 10;
3727 tz += ('0' - zone[2]) * 60 * 60;
3728 tz += ('0' - zone[3]) * 60;
3729 tz += ('0' - zone[4]);
3731 if (zone[0] == '-')
3732 tz = -tz;
3734 *time -= tz;
3737 /* Parse author lines where the name may be empty:
3738 * author <email@address.tld> 1138474660 +0100
3740 static void
3741 parse_author_line(char *ident, const char **author, time_t *time)
3743 char *nameend = strchr(ident, '<');
3744 char *emailend = strchr(ident, '>');
3746 if (nameend && emailend)
3747 *nameend = *emailend = 0;
3748 ident = chomp_string(ident);
3749 if (!*ident) {
3750 if (nameend)
3751 ident = chomp_string(nameend + 1);
3752 if (!*ident)
3753 ident = "Unknown";
3756 *author = get_author(ident);
3758 /* Parse epoch and timezone */
3759 if (emailend && emailend[1] == ' ') {
3760 char *secs = emailend + 2;
3761 char *zone = strchr(secs, ' ');
3763 *time = (time_t) atol(secs);
3765 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3766 parse_timezone(time, zone + 1);
3770 static bool
3771 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3773 char rev[SIZEOF_REV];
3774 const char *revlist_argv[] = {
3775 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3777 struct menu_item *items;
3778 char text[SIZEOF_STR];
3779 bool ok = TRUE;
3780 int i;
3782 items = calloc(*parents + 1, sizeof(*items));
3783 if (!items)
3784 return FALSE;
3786 for (i = 0; i < *parents; i++) {
3787 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3788 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3789 !(items[i].text = strdup(text))) {
3790 ok = FALSE;
3791 break;
3795 if (ok) {
3796 *parents = 0;
3797 ok = prompt_menu("Select parent", items, parents);
3799 for (i = 0; items[i].text; i++)
3800 free((char *) items[i].text);
3801 free(items);
3802 return ok;
3805 static bool
3806 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3808 char buf[SIZEOF_STR * 4];
3809 const char *revlist_argv[] = {
3810 "git", "log", "--no-color", "-1",
3811 "--pretty=format:%P", id, "--", path, NULL
3813 int parents;
3815 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3816 (parents = strlen(buf) / 40) < 0) {
3817 report("Failed to get parent information");
3818 return FALSE;
3820 } else if (parents == 0) {
3821 if (path)
3822 report("Path '%s' does not exist in the parent", path);
3823 else
3824 report("The selected commit has no parents");
3825 return FALSE;
3828 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3829 return FALSE;
3831 string_copy_rev(rev, &buf[41 * parents]);
3832 return TRUE;
3836 * Pager backend
3839 static bool
3840 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3842 char text[SIZEOF_STR];
3844 if (opt_line_number && draw_lineno(view, lineno))
3845 return TRUE;
3847 string_expand(text, sizeof(text), line->data, opt_tab_size);
3848 draw_text(view, line->type, text, TRUE);
3849 return TRUE;
3852 static bool
3853 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3855 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3856 char ref[SIZEOF_STR];
3858 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3859 return TRUE;
3861 /* This is the only fatal call, since it can "corrupt" the buffer. */
3862 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3863 return FALSE;
3865 return TRUE;
3868 static void
3869 add_pager_refs(struct view *view, struct line *line)
3871 char buf[SIZEOF_STR];
3872 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3873 struct ref_list *list;
3874 size_t bufpos = 0, i;
3875 const char *sep = "Refs: ";
3876 bool is_tag = FALSE;
3878 assert(line->type == LINE_COMMIT);
3880 list = get_ref_list(commit_id);
3881 if (!list) {
3882 if (view == VIEW(REQ_VIEW_DIFF))
3883 goto try_add_describe_ref;
3884 return;
3887 for (i = 0; i < list->size; i++) {
3888 struct ref *ref = list->refs[i];
3889 const char *fmt = ref->tag ? "%s[%s]" :
3890 ref->remote ? "%s<%s>" : "%s%s";
3892 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3893 return;
3894 sep = ", ";
3895 if (ref->tag)
3896 is_tag = TRUE;
3899 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3900 try_add_describe_ref:
3901 /* Add <tag>-g<commit_id> "fake" reference. */
3902 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3903 return;
3906 if (bufpos == 0)
3907 return;
3909 add_line_text(view, buf, LINE_PP_REFS);
3912 static bool
3913 pager_read(struct view *view, char *data)
3915 struct line *line;
3917 if (!data)
3918 return TRUE;
3920 line = add_line_text(view, data, get_line_type(data));
3921 if (!line)
3922 return FALSE;
3924 if (line->type == LINE_COMMIT &&
3925 (view == VIEW(REQ_VIEW_DIFF) ||
3926 view == VIEW(REQ_VIEW_LOG)))
3927 add_pager_refs(view, line);
3929 return TRUE;
3932 static enum request
3933 pager_request(struct view *view, enum request request, struct line *line)
3935 int split = 0;
3937 if (request != REQ_ENTER)
3938 return request;
3940 if (line->type == LINE_COMMIT &&
3941 (view == VIEW(REQ_VIEW_LOG) ||
3942 view == VIEW(REQ_VIEW_PAGER))) {
3943 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3944 split = 1;
3947 /* Always scroll the view even if it was split. That way
3948 * you can use Enter to scroll through the log view and
3949 * split open each commit diff. */
3950 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3952 /* FIXME: A minor workaround. Scrolling the view will call report("")
3953 * but if we are scrolling a non-current view this won't properly
3954 * update the view title. */
3955 if (split)
3956 update_view_title(view);
3958 return REQ_NONE;
3961 static bool
3962 pager_grep(struct view *view, struct line *line)
3964 const char *text[] = { line->data, NULL };
3966 return grep_text(view, text);
3969 static void
3970 pager_select(struct view *view, struct line *line)
3972 if (line->type == LINE_COMMIT) {
3973 char *text = (char *)line->data + STRING_SIZE("commit ");
3975 if (view != VIEW(REQ_VIEW_PAGER))
3976 string_copy_rev(view->ref, text);
3977 string_copy_rev(ref_commit, text);
3981 static struct view_ops pager_ops = {
3982 "line",
3983 NULL,
3984 NULL,
3985 pager_read,
3986 pager_draw,
3987 pager_request,
3988 pager_grep,
3989 pager_select,
3992 static const char *log_argv[SIZEOF_ARG] = {
3993 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3996 static enum request
3997 log_request(struct view *view, enum request request, struct line *line)
3999 switch (request) {
4000 case REQ_REFRESH:
4001 load_refs();
4002 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4003 return REQ_NONE;
4004 default:
4005 return pager_request(view, request, line);
4009 static struct view_ops log_ops = {
4010 "line",
4011 log_argv,
4012 NULL,
4013 pager_read,
4014 pager_draw,
4015 log_request,
4016 pager_grep,
4017 pager_select,
4020 static const char *diff_argv[SIZEOF_ARG] = {
4021 "git", "show", "--pretty=fuller", "--no-color", "--root",
4022 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4025 static struct view_ops diff_ops = {
4026 "line",
4027 diff_argv,
4028 NULL,
4029 pager_read,
4030 pager_draw,
4031 pager_request,
4032 pager_grep,
4033 pager_select,
4037 * Help backend
4040 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4042 static bool
4043 help_open_keymap_title(struct view *view, enum keymap keymap)
4045 struct line *line;
4047 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4048 help_keymap_hidden[keymap] ? '+' : '-',
4049 enum_name(keymap_table[keymap]));
4050 if (line)
4051 line->other = keymap;
4053 return help_keymap_hidden[keymap];
4056 static void
4057 help_open_keymap(struct view *view, enum keymap keymap)
4059 const char *group = NULL;
4060 char buf[SIZEOF_STR];
4061 size_t bufpos;
4062 bool add_title = TRUE;
4063 int i;
4065 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4066 const char *key = NULL;
4068 if (req_info[i].request == REQ_NONE)
4069 continue;
4071 if (!req_info[i].request) {
4072 group = req_info[i].help;
4073 continue;
4076 key = get_keys(keymap, req_info[i].request, TRUE);
4077 if (!key || !*key)
4078 continue;
4080 if (add_title && help_open_keymap_title(view, keymap))
4081 return;
4082 add_title = false;
4084 if (group) {
4085 add_line_text(view, group, LINE_HELP_GROUP);
4086 group = NULL;
4089 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4090 enum_name(req_info[i]), req_info[i].help);
4093 group = "External commands:";
4095 for (i = 0; i < run_requests; i++) {
4096 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4097 const char *key;
4098 int argc;
4100 if (!req || req->keymap != keymap)
4101 continue;
4103 key = get_key_name(req->key);
4104 if (!*key)
4105 key = "(no key defined)";
4107 if (add_title && help_open_keymap_title(view, keymap))
4108 return;
4109 if (group) {
4110 add_line_text(view, group, LINE_HELP_GROUP);
4111 group = NULL;
4114 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4115 if (!string_format_from(buf, &bufpos, "%s%s",
4116 argc ? " " : "", req->argv[argc]))
4117 return;
4119 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4123 static bool
4124 help_open(struct view *view)
4126 enum keymap keymap;
4128 reset_view(view);
4129 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4130 add_line_text(view, "", LINE_DEFAULT);
4132 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4133 help_open_keymap(view, keymap);
4135 return TRUE;
4138 static enum request
4139 help_request(struct view *view, enum request request, struct line *line)
4141 switch (request) {
4142 case REQ_ENTER:
4143 if (line->type == LINE_HELP_KEYMAP) {
4144 help_keymap_hidden[line->other] =
4145 !help_keymap_hidden[line->other];
4146 view->p_restore = TRUE;
4147 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4150 return REQ_NONE;
4151 default:
4152 return pager_request(view, request, line);
4156 static struct view_ops help_ops = {
4157 "line",
4158 NULL,
4159 help_open,
4160 NULL,
4161 pager_draw,
4162 help_request,
4163 pager_grep,
4164 pager_select,
4169 * Tree backend
4172 struct tree_stack_entry {
4173 struct tree_stack_entry *prev; /* Entry below this in the stack */
4174 unsigned long lineno; /* Line number to restore */
4175 char *name; /* Position of name in opt_path */
4178 /* The top of the path stack. */
4179 static struct tree_stack_entry *tree_stack = NULL;
4180 unsigned long tree_lineno = 0;
4182 static void
4183 pop_tree_stack_entry(void)
4185 struct tree_stack_entry *entry = tree_stack;
4187 tree_lineno = entry->lineno;
4188 entry->name[0] = 0;
4189 tree_stack = entry->prev;
4190 free(entry);
4193 static void
4194 push_tree_stack_entry(const char *name, unsigned long lineno)
4196 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4197 size_t pathlen = strlen(opt_path);
4199 if (!entry)
4200 return;
4202 entry->prev = tree_stack;
4203 entry->name = opt_path + pathlen;
4204 tree_stack = entry;
4206 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4207 pop_tree_stack_entry();
4208 return;
4211 /* Move the current line to the first tree entry. */
4212 tree_lineno = 1;
4213 entry->lineno = lineno;
4216 /* Parse output from git-ls-tree(1):
4218 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4221 #define SIZEOF_TREE_ATTR \
4222 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4224 #define SIZEOF_TREE_MODE \
4225 STRING_SIZE("100644 ")
4227 #define TREE_ID_OFFSET \
4228 STRING_SIZE("100644 blob ")
4230 struct tree_entry {
4231 char id[SIZEOF_REV];
4232 mode_t mode;
4233 time_t time; /* Date from the author ident. */
4234 const char *author; /* Author of the commit. */
4235 char name[1];
4238 static const char *
4239 tree_path(const struct line *line)
4241 return ((struct tree_entry *) line->data)->name;
4244 static int
4245 tree_compare_entry(const struct line *line1, const struct line *line2)
4247 if (line1->type != line2->type)
4248 return line1->type == LINE_TREE_DIR ? -1 : 1;
4249 return strcmp(tree_path(line1), tree_path(line2));
4252 static const enum sort_field tree_sort_fields[] = {
4253 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4255 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4257 static int
4258 tree_compare(const void *l1, const void *l2)
4260 const struct line *line1 = (const struct line *) l1;
4261 const struct line *line2 = (const struct line *) l2;
4262 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4263 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4265 if (line1->type == LINE_TREE_HEAD)
4266 return -1;
4267 if (line2->type == LINE_TREE_HEAD)
4268 return 1;
4270 switch (get_sort_field(tree_sort_state)) {
4271 case ORDERBY_DATE:
4272 return sort_order(tree_sort_state, entry1->time - entry2->time);
4274 case ORDERBY_AUTHOR:
4275 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4277 case ORDERBY_NAME:
4278 default:
4279 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4284 static struct line *
4285 tree_entry(struct view *view, enum line_type type, const char *path,
4286 const char *mode, const char *id)
4288 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4289 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4291 if (!entry || !line) {
4292 free(entry);
4293 return NULL;
4296 strncpy(entry->name, path, strlen(path));
4297 if (mode)
4298 entry->mode = strtoul(mode, NULL, 8);
4299 if (id)
4300 string_copy_rev(entry->id, id);
4302 return line;
4305 static bool
4306 tree_read_date(struct view *view, char *text, bool *read_date)
4308 static const char *author_name;
4309 static time_t author_time;
4311 if (!text && *read_date) {
4312 *read_date = FALSE;
4313 return TRUE;
4315 } else if (!text) {
4316 char *path = *opt_path ? opt_path : ".";
4317 /* Find next entry to process */
4318 const char *log_file[] = {
4319 "git", "log", "--no-color", "--pretty=raw",
4320 "--cc", "--raw", view->id, "--", path, NULL
4322 struct io io = {};
4324 if (!view->lines) {
4325 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4326 report("Tree is empty");
4327 return TRUE;
4330 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4331 report("Failed to load tree data");
4332 return TRUE;
4335 done_io(view->pipe);
4336 view->io = io;
4337 *read_date = TRUE;
4338 return FALSE;
4340 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4341 parse_author_line(text + STRING_SIZE("author "),
4342 &author_name, &author_time);
4344 } else if (*text == ':') {
4345 char *pos;
4346 size_t annotated = 1;
4347 size_t i;
4349 pos = strchr(text, '\t');
4350 if (!pos)
4351 return TRUE;
4352 text = pos + 1;
4353 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4354 text += strlen(opt_path);
4355 pos = strchr(text, '/');
4356 if (pos)
4357 *pos = 0;
4359 for (i = 1; i < view->lines; i++) {
4360 struct line *line = &view->line[i];
4361 struct tree_entry *entry = line->data;
4363 annotated += !!entry->author;
4364 if (entry->author || strcmp(entry->name, text))
4365 continue;
4367 entry->author = author_name;
4368 entry->time = author_time;
4369 line->dirty = 1;
4370 break;
4373 if (annotated == view->lines)
4374 kill_io(view->pipe);
4376 return TRUE;
4379 static bool
4380 tree_read(struct view *view, char *text)
4382 static bool read_date = FALSE;
4383 struct tree_entry *data;
4384 struct line *entry, *line;
4385 enum line_type type;
4386 size_t textlen = text ? strlen(text) : 0;
4387 char *path = text + SIZEOF_TREE_ATTR;
4389 if (read_date || !text)
4390 return tree_read_date(view, text, &read_date);
4392 if (textlen <= SIZEOF_TREE_ATTR)
4393 return FALSE;
4394 if (view->lines == 0 &&
4395 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4396 return FALSE;
4398 /* Strip the path part ... */
4399 if (*opt_path) {
4400 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4401 size_t striplen = strlen(opt_path);
4403 if (pathlen > striplen)
4404 memmove(path, path + striplen,
4405 pathlen - striplen + 1);
4407 /* Insert "link" to parent directory. */
4408 if (view->lines == 1 &&
4409 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4410 return FALSE;
4413 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4414 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4415 if (!entry)
4416 return FALSE;
4417 data = entry->data;
4419 /* Skip "Directory ..." and ".." line. */
4420 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4421 if (tree_compare_entry(line, entry) <= 0)
4422 continue;
4424 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4426 line->data = data;
4427 line->type = type;
4428 for (; line <= entry; line++)
4429 line->dirty = line->cleareol = 1;
4430 return TRUE;
4433 if (tree_lineno > view->lineno) {
4434 view->lineno = tree_lineno;
4435 tree_lineno = 0;
4438 return TRUE;
4441 static bool
4442 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4444 struct tree_entry *entry = line->data;
4446 if (line->type == LINE_TREE_HEAD) {
4447 if (draw_text(view, line->type, "Directory path /", TRUE))
4448 return TRUE;
4449 } else {
4450 if (draw_mode(view, entry->mode))
4451 return TRUE;
4453 if (opt_author && draw_author(view, entry->author))
4454 return TRUE;
4456 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4457 return TRUE;
4459 if (draw_text(view, line->type, entry->name, TRUE))
4460 return TRUE;
4461 return TRUE;
4464 static void
4465 open_blob_editor()
4467 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4468 int fd = mkstemp(file);
4470 if (fd == -1)
4471 report("Failed to create temporary file");
4472 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4473 report("Failed to save blob data to file");
4474 else
4475 open_editor(file);
4476 if (fd != -1)
4477 unlink(file);
4480 static enum request
4481 tree_request(struct view *view, enum request request, struct line *line)
4483 enum open_flags flags;
4485 switch (request) {
4486 case REQ_VIEW_BLAME:
4487 if (line->type != LINE_TREE_FILE) {
4488 report("Blame only supported for files");
4489 return REQ_NONE;
4492 string_copy(opt_ref, view->vid);
4493 return request;
4495 case REQ_EDIT:
4496 if (line->type != LINE_TREE_FILE) {
4497 report("Edit only supported for files");
4498 } else if (!is_head_commit(view->vid)) {
4499 open_blob_editor();
4500 } else {
4501 open_editor(opt_file);
4503 return REQ_NONE;
4505 case REQ_TOGGLE_SORT_FIELD:
4506 case REQ_TOGGLE_SORT_ORDER:
4507 sort_view(view, request, &tree_sort_state, tree_compare);
4508 return REQ_NONE;
4510 case REQ_PARENT:
4511 if (!*opt_path) {
4512 /* quit view if at top of tree */
4513 return REQ_VIEW_CLOSE;
4515 /* fake 'cd ..' */
4516 line = &view->line[1];
4517 break;
4519 case REQ_ENTER:
4520 break;
4522 default:
4523 return request;
4526 /* Cleanup the stack if the tree view is at a different tree. */
4527 while (!*opt_path && tree_stack)
4528 pop_tree_stack_entry();
4530 switch (line->type) {
4531 case LINE_TREE_DIR:
4532 /* Depending on whether it is a subdirectory or parent link
4533 * mangle the path buffer. */
4534 if (line == &view->line[1] && *opt_path) {
4535 pop_tree_stack_entry();
4537 } else {
4538 const char *basename = tree_path(line);
4540 push_tree_stack_entry(basename, view->lineno);
4543 /* Trees and subtrees share the same ID, so they are not not
4544 * unique like blobs. */
4545 flags = OPEN_RELOAD;
4546 request = REQ_VIEW_TREE;
4547 break;
4549 case LINE_TREE_FILE:
4550 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4551 request = REQ_VIEW_BLOB;
4552 break;
4554 default:
4555 return REQ_NONE;
4558 open_view(view, request, flags);
4559 if (request == REQ_VIEW_TREE)
4560 view->lineno = tree_lineno;
4562 return REQ_NONE;
4565 static bool
4566 tree_grep(struct view *view, struct line *line)
4568 struct tree_entry *entry = line->data;
4569 const char *text[] = {
4570 entry->name,
4571 opt_author ? entry->author : "",
4572 opt_date ? mkdate(&entry->time) : "",
4573 NULL
4576 return grep_text(view, text);
4579 static void
4580 tree_select(struct view *view, struct line *line)
4582 struct tree_entry *entry = line->data;
4584 if (line->type == LINE_TREE_FILE) {
4585 string_copy_rev(ref_blob, entry->id);
4586 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4588 } else if (line->type != LINE_TREE_DIR) {
4589 return;
4592 string_copy_rev(view->ref, entry->id);
4595 static bool
4596 tree_prepare(struct view *view)
4598 if (view->lines == 0 && opt_prefix[0]) {
4599 char *pos = opt_prefix;
4601 while (pos && *pos) {
4602 char *end = strchr(pos, '/');
4604 if (end)
4605 *end = 0;
4606 push_tree_stack_entry(pos, 0);
4607 pos = end;
4608 if (end) {
4609 *end = '/';
4610 pos++;
4614 } else if (strcmp(view->vid, view->id)) {
4615 opt_path[0] = 0;
4618 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4621 static const char *tree_argv[SIZEOF_ARG] = {
4622 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4625 static struct view_ops tree_ops = {
4626 "file",
4627 tree_argv,
4628 NULL,
4629 tree_read,
4630 tree_draw,
4631 tree_request,
4632 tree_grep,
4633 tree_select,
4634 tree_prepare,
4637 static bool
4638 blob_read(struct view *view, char *line)
4640 if (!line)
4641 return TRUE;
4642 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4645 static enum request
4646 blob_request(struct view *view, enum request request, struct line *line)
4648 switch (request) {
4649 case REQ_EDIT:
4650 open_blob_editor();
4651 return REQ_NONE;
4652 default:
4653 return pager_request(view, request, line);
4657 static const char *blob_argv[SIZEOF_ARG] = {
4658 "git", "cat-file", "blob", "%(blob)", NULL
4661 static struct view_ops blob_ops = {
4662 "line",
4663 blob_argv,
4664 NULL,
4665 blob_read,
4666 pager_draw,
4667 blob_request,
4668 pager_grep,
4669 pager_select,
4673 * Blame backend
4675 * Loading the blame view is a two phase job:
4677 * 1. File content is read either using opt_file from the
4678 * filesystem or using git-cat-file.
4679 * 2. Then blame information is incrementally added by
4680 * reading output from git-blame.
4683 static const char *blame_head_argv[] = {
4684 "git", "blame", "--incremental", "--", "%(file)", NULL
4687 static const char *blame_ref_argv[] = {
4688 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4691 static const char *blame_cat_file_argv[] = {
4692 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4695 struct blame_commit {
4696 char id[SIZEOF_REV]; /* SHA1 ID. */
4697 char title[128]; /* First line of the commit message. */
4698 const char *author; /* Author of the commit. */
4699 time_t time; /* Date from the author ident. */
4700 char filename[128]; /* Name of file. */
4701 bool has_previous; /* Was a "previous" line detected. */
4704 struct blame {
4705 struct blame_commit *commit;
4706 unsigned long lineno;
4707 char text[1];
4710 static bool
4711 blame_open(struct view *view)
4713 char path[SIZEOF_STR];
4715 if (!view->parent && *opt_prefix) {
4716 string_copy(path, opt_file);
4717 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4718 return FALSE;
4721 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4722 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4723 return FALSE;
4726 setup_update(view, opt_file);
4727 string_format(view->ref, "%s ...", opt_file);
4729 return TRUE;
4732 static struct blame_commit *
4733 get_blame_commit(struct view *view, const char *id)
4735 size_t i;
4737 for (i = 0; i < view->lines; i++) {
4738 struct blame *blame = view->line[i].data;
4740 if (!blame->commit)
4741 continue;
4743 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4744 return blame->commit;
4748 struct blame_commit *commit = calloc(1, sizeof(*commit));
4750 if (commit)
4751 string_ncopy(commit->id, id, SIZEOF_REV);
4752 return commit;
4756 static bool
4757 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4759 const char *pos = *posref;
4761 *posref = NULL;
4762 pos = strchr(pos + 1, ' ');
4763 if (!pos || !isdigit(pos[1]))
4764 return FALSE;
4765 *number = atoi(pos + 1);
4766 if (*number < min || *number > max)
4767 return FALSE;
4769 *posref = pos;
4770 return TRUE;
4773 static struct blame_commit *
4774 parse_blame_commit(struct view *view, const char *text, int *blamed)
4776 struct blame_commit *commit;
4777 struct blame *blame;
4778 const char *pos = text + SIZEOF_REV - 2;
4779 size_t orig_lineno = 0;
4780 size_t lineno;
4781 size_t group;
4783 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4784 return NULL;
4786 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4787 !parse_number(&pos, &lineno, 1, view->lines) ||
4788 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4789 return NULL;
4791 commit = get_blame_commit(view, text);
4792 if (!commit)
4793 return NULL;
4795 *blamed += group;
4796 while (group--) {
4797 struct line *line = &view->line[lineno + group - 1];
4799 blame = line->data;
4800 blame->commit = commit;
4801 blame->lineno = orig_lineno + group - 1;
4802 line->dirty = 1;
4805 return commit;
4808 static bool
4809 blame_read_file(struct view *view, const char *line, bool *read_file)
4811 if (!line) {
4812 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4813 struct io io = {};
4815 if (view->lines == 0 && !view->parent)
4816 die("No blame exist for %s", view->vid);
4818 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4819 report("Failed to load blame data");
4820 return TRUE;
4823 done_io(view->pipe);
4824 view->io = io;
4825 *read_file = FALSE;
4826 return FALSE;
4828 } else {
4829 size_t linelen = strlen(line);
4830 struct blame *blame = malloc(sizeof(*blame) + linelen);
4832 if (!blame)
4833 return FALSE;
4835 blame->commit = NULL;
4836 strncpy(blame->text, line, linelen);
4837 blame->text[linelen] = 0;
4838 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4842 static bool
4843 match_blame_header(const char *name, char **line)
4845 size_t namelen = strlen(name);
4846 bool matched = !strncmp(name, *line, namelen);
4848 if (matched)
4849 *line += namelen;
4851 return matched;
4854 static bool
4855 blame_read(struct view *view, char *line)
4857 static struct blame_commit *commit = NULL;
4858 static int blamed = 0;
4859 static bool read_file = TRUE;
4861 if (read_file)
4862 return blame_read_file(view, line, &read_file);
4864 if (!line) {
4865 /* Reset all! */
4866 commit = NULL;
4867 blamed = 0;
4868 read_file = TRUE;
4869 string_format(view->ref, "%s", view->vid);
4870 if (view_is_displayed(view)) {
4871 update_view_title(view);
4872 redraw_view_from(view, 0);
4874 return TRUE;
4877 if (!commit) {
4878 commit = parse_blame_commit(view, line, &blamed);
4879 string_format(view->ref, "%s %2d%%", view->vid,
4880 view->lines ? blamed * 100 / view->lines : 0);
4882 } else if (match_blame_header("author ", &line)) {
4883 commit->author = get_author(line);
4885 } else if (match_blame_header("author-time ", &line)) {
4886 commit->time = (time_t) atol(line);
4888 } else if (match_blame_header("author-tz ", &line)) {
4889 parse_timezone(&commit->time, line);
4891 } else if (match_blame_header("summary ", &line)) {
4892 string_ncopy(commit->title, line, strlen(line));
4894 } else if (match_blame_header("previous ", &line)) {
4895 commit->has_previous = TRUE;
4897 } else if (match_blame_header("filename ", &line)) {
4898 string_ncopy(commit->filename, line, strlen(line));
4899 commit = NULL;
4902 return TRUE;
4905 static bool
4906 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4908 struct blame *blame = line->data;
4909 time_t *time = NULL;
4910 const char *id = NULL, *author = NULL;
4911 char text[SIZEOF_STR];
4913 if (blame->commit && *blame->commit->filename) {
4914 id = blame->commit->id;
4915 author = blame->commit->author;
4916 time = &blame->commit->time;
4919 if (opt_date && draw_date(view, time))
4920 return TRUE;
4922 if (opt_author && draw_author(view, author))
4923 return TRUE;
4925 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4926 return TRUE;
4928 if (draw_lineno(view, lineno))
4929 return TRUE;
4931 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4932 draw_text(view, LINE_DEFAULT, text, TRUE);
4933 return TRUE;
4936 static bool
4937 check_blame_commit(struct blame *blame, bool check_null_id)
4939 if (!blame->commit)
4940 report("Commit data not loaded yet");
4941 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4942 report("No commit exist for the selected line");
4943 else
4944 return TRUE;
4945 return FALSE;
4948 static void
4949 setup_blame_parent_line(struct view *view, struct blame *blame)
4951 const char *diff_tree_argv[] = {
4952 "git", "diff-tree", "-U0", blame->commit->id,
4953 "--", blame->commit->filename, NULL
4955 struct io io = {};
4956 int parent_lineno = -1;
4957 int blamed_lineno = -1;
4958 char *line;
4960 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4961 return;
4963 while ((line = io_get(&io, '\n', TRUE))) {
4964 if (*line == '@') {
4965 char *pos = strchr(line, '+');
4967 parent_lineno = atoi(line + 4);
4968 if (pos)
4969 blamed_lineno = atoi(pos + 1);
4971 } else if (*line == '+' && parent_lineno != -1) {
4972 if (blame->lineno == blamed_lineno - 1 &&
4973 !strcmp(blame->text, line + 1)) {
4974 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4975 break;
4977 blamed_lineno++;
4981 done_io(&io);
4984 static enum request
4985 blame_request(struct view *view, enum request request, struct line *line)
4987 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4988 struct blame *blame = line->data;
4990 switch (request) {
4991 case REQ_VIEW_BLAME:
4992 if (check_blame_commit(blame, TRUE)) {
4993 string_copy(opt_ref, blame->commit->id);
4994 string_copy(opt_file, blame->commit->filename);
4995 if (blame->lineno)
4996 view->lineno = blame->lineno;
4997 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4999 break;
5001 case REQ_PARENT:
5002 if (check_blame_commit(blame, TRUE) &&
5003 select_commit_parent(blame->commit->id, opt_ref,
5004 blame->commit->filename)) {
5005 string_copy(opt_file, blame->commit->filename);
5006 setup_blame_parent_line(view, blame);
5007 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5009 break;
5011 case REQ_ENTER:
5012 if (!check_blame_commit(blame, FALSE))
5013 break;
5015 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5016 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5017 break;
5019 if (!strcmp(blame->commit->id, NULL_ID)) {
5020 struct view *diff = VIEW(REQ_VIEW_DIFF);
5021 const char *diff_index_argv[] = {
5022 "git", "diff-index", "--root", "--patch-with-stat",
5023 "-C", "-M", "HEAD", "--", view->vid, NULL
5026 if (!blame->commit->has_previous) {
5027 diff_index_argv[1] = "diff";
5028 diff_index_argv[2] = "--no-color";
5029 diff_index_argv[6] = "--";
5030 diff_index_argv[7] = "/dev/null";
5033 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5034 report("Failed to allocate diff command");
5035 break;
5037 flags |= OPEN_PREPARED;
5040 open_view(view, REQ_VIEW_DIFF, flags);
5041 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5042 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5043 break;
5045 default:
5046 return request;
5049 return REQ_NONE;
5052 static bool
5053 blame_grep(struct view *view, struct line *line)
5055 struct blame *blame = line->data;
5056 struct blame_commit *commit = blame->commit;
5057 const char *text[] = {
5058 blame->text,
5059 commit ? commit->title : "",
5060 commit ? commit->id : "",
5061 commit && opt_author ? commit->author : "",
5062 commit && opt_date ? mkdate(&commit->time) : "",
5063 NULL
5066 return grep_text(view, text);
5069 static void
5070 blame_select(struct view *view, struct line *line)
5072 struct blame *blame = line->data;
5073 struct blame_commit *commit = blame->commit;
5075 if (!commit)
5076 return;
5078 if (!strcmp(commit->id, NULL_ID))
5079 string_ncopy(ref_commit, "HEAD", 4);
5080 else
5081 string_copy_rev(ref_commit, commit->id);
5084 static struct view_ops blame_ops = {
5085 "line",
5086 NULL,
5087 blame_open,
5088 blame_read,
5089 blame_draw,
5090 blame_request,
5091 blame_grep,
5092 blame_select,
5096 * Branch backend
5099 struct branch {
5100 const char *author; /* Author of the last commit. */
5101 time_t time; /* Date of the last activity. */
5102 const struct ref *ref; /* Name and commit ID information. */
5105 static const struct ref branch_all;
5107 static const enum sort_field branch_sort_fields[] = {
5108 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5110 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5112 static int
5113 branch_compare(const void *l1, const void *l2)
5115 const struct branch *branch1 = ((const struct line *) l1)->data;
5116 const struct branch *branch2 = ((const struct line *) l2)->data;
5118 switch (get_sort_field(branch_sort_state)) {
5119 case ORDERBY_DATE:
5120 return sort_order(branch_sort_state, branch1->time - branch2->time);
5122 case ORDERBY_AUTHOR:
5123 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5125 case ORDERBY_NAME:
5126 default:
5127 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5131 static bool
5132 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5134 struct branch *branch = line->data;
5135 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5137 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5138 return TRUE;
5140 if (opt_author && draw_author(view, branch->author))
5141 return TRUE;
5143 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5144 return TRUE;
5147 static enum request
5148 branch_request(struct view *view, enum request request, struct line *line)
5150 struct branch *branch = line->data;
5152 switch (request) {
5153 case REQ_REFRESH:
5154 load_refs();
5155 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5156 return REQ_NONE;
5158 case REQ_TOGGLE_SORT_FIELD:
5159 case REQ_TOGGLE_SORT_ORDER:
5160 sort_view(view, request, &branch_sort_state, branch_compare);
5161 return REQ_NONE;
5163 case REQ_ENTER:
5164 if (branch->ref == &branch_all) {
5165 const char *all_branches_argv[] = {
5166 "git", "log", "--no-color", "--pretty=raw", "--parents",
5167 "--topo-order", "--all", NULL
5169 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5171 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5172 report("Failed to load view of all branches");
5173 return REQ_NONE;
5175 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5176 } else {
5177 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5179 return REQ_NONE;
5181 default:
5182 return request;
5186 static bool
5187 branch_read(struct view *view, char *line)
5189 static char id[SIZEOF_REV];
5190 struct branch *reference;
5191 size_t i;
5193 if (!line)
5194 return TRUE;
5196 switch (get_line_type(line)) {
5197 case LINE_COMMIT:
5198 string_copy_rev(id, line + STRING_SIZE("commit "));
5199 return TRUE;
5201 case LINE_AUTHOR:
5202 for (i = 0, reference = NULL; i < view->lines; i++) {
5203 struct branch *branch = view->line[i].data;
5205 if (strcmp(branch->ref->id, id))
5206 continue;
5208 view->line[i].dirty = TRUE;
5209 if (reference) {
5210 branch->author = reference->author;
5211 branch->time = reference->time;
5212 continue;
5215 parse_author_line(line + STRING_SIZE("author "),
5216 &branch->author, &branch->time);
5217 reference = branch;
5219 return TRUE;
5221 default:
5222 return TRUE;
5227 static bool
5228 branch_open_visitor(void *data, const struct ref *ref)
5230 struct view *view = data;
5231 struct branch *branch;
5233 if (ref->tag || ref->ltag || ref->remote)
5234 return TRUE;
5236 branch = calloc(1, sizeof(*branch));
5237 if (!branch)
5238 return FALSE;
5240 branch->ref = ref;
5241 return !!add_line_data(view, branch, LINE_DEFAULT);
5244 static bool
5245 branch_open(struct view *view)
5247 const char *branch_log[] = {
5248 "git", "log", "--no-color", "--pretty=raw",
5249 "--simplify-by-decoration", "--all", NULL
5252 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5253 report("Failed to load branch data");
5254 return TRUE;
5257 setup_update(view, view->id);
5258 branch_open_visitor(view, &branch_all);
5259 foreach_ref(branch_open_visitor, view);
5260 view->p_restore = TRUE;
5262 return TRUE;
5265 static bool
5266 branch_grep(struct view *view, struct line *line)
5268 struct branch *branch = line->data;
5269 const char *text[] = {
5270 branch->ref->name,
5271 branch->author,
5272 NULL
5275 return grep_text(view, text);
5278 static void
5279 branch_select(struct view *view, struct line *line)
5281 struct branch *branch = line->data;
5283 string_copy_rev(view->ref, branch->ref->id);
5284 string_copy_rev(ref_commit, branch->ref->id);
5285 string_copy_rev(ref_head, branch->ref->id);
5288 static struct view_ops branch_ops = {
5289 "branch",
5290 NULL,
5291 branch_open,
5292 branch_read,
5293 branch_draw,
5294 branch_request,
5295 branch_grep,
5296 branch_select,
5300 * Status backend
5303 struct status {
5304 char status;
5305 struct {
5306 mode_t mode;
5307 char rev[SIZEOF_REV];
5308 char name[SIZEOF_STR];
5309 } old;
5310 struct {
5311 mode_t mode;
5312 char rev[SIZEOF_REV];
5313 char name[SIZEOF_STR];
5314 } new;
5317 static char status_onbranch[SIZEOF_STR];
5318 static struct status stage_status;
5319 static enum line_type stage_line_type;
5320 static size_t stage_chunks;
5321 static int *stage_chunk;
5323 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5325 /* This should work even for the "On branch" line. */
5326 static inline bool
5327 status_has_none(struct view *view, struct line *line)
5329 return line < view->line + view->lines && !line[1].data;
5332 /* Get fields from the diff line:
5333 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5335 static inline bool
5336 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5338 const char *old_mode = buf + 1;
5339 const char *new_mode = buf + 8;
5340 const char *old_rev = buf + 15;
5341 const char *new_rev = buf + 56;
5342 const char *status = buf + 97;
5344 if (bufsize < 98 ||
5345 old_mode[-1] != ':' ||
5346 new_mode[-1] != ' ' ||
5347 old_rev[-1] != ' ' ||
5348 new_rev[-1] != ' ' ||
5349 status[-1] != ' ')
5350 return FALSE;
5352 file->status = *status;
5354 string_copy_rev(file->old.rev, old_rev);
5355 string_copy_rev(file->new.rev, new_rev);
5357 file->old.mode = strtoul(old_mode, NULL, 8);
5358 file->new.mode = strtoul(new_mode, NULL, 8);
5360 file->old.name[0] = file->new.name[0] = 0;
5362 return TRUE;
5365 static bool
5366 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5368 struct status *unmerged = NULL;
5369 char *buf;
5370 struct io io = {};
5372 if (!run_io(&io, argv, opt_cdup, IO_RD))
5373 return FALSE;
5375 add_line_data(view, NULL, type);
5377 while ((buf = io_get(&io, 0, TRUE))) {
5378 struct status *file = unmerged;
5380 if (!file) {
5381 file = calloc(1, sizeof(*file));
5382 if (!file || !add_line_data(view, file, type))
5383 goto error_out;
5386 /* Parse diff info part. */
5387 if (status) {
5388 file->status = status;
5389 if (status == 'A')
5390 string_copy(file->old.rev, NULL_ID);
5392 } else if (!file->status || file == unmerged) {
5393 if (!status_get_diff(file, buf, strlen(buf)))
5394 goto error_out;
5396 buf = io_get(&io, 0, TRUE);
5397 if (!buf)
5398 break;
5400 /* Collapse all modified entries that follow an
5401 * associated unmerged entry. */
5402 if (unmerged == file) {
5403 unmerged->status = 'U';
5404 unmerged = NULL;
5405 } else if (file->status == 'U') {
5406 unmerged = file;
5410 /* Grab the old name for rename/copy. */
5411 if (!*file->old.name &&
5412 (file->status == 'R' || file->status == 'C')) {
5413 string_ncopy(file->old.name, buf, strlen(buf));
5415 buf = io_get(&io, 0, TRUE);
5416 if (!buf)
5417 break;
5420 /* git-ls-files just delivers a NUL separated list of
5421 * file names similar to the second half of the
5422 * git-diff-* output. */
5423 string_ncopy(file->new.name, buf, strlen(buf));
5424 if (!*file->old.name)
5425 string_copy(file->old.name, file->new.name);
5426 file = NULL;
5429 if (io_error(&io)) {
5430 error_out:
5431 done_io(&io);
5432 return FALSE;
5435 if (!view->line[view->lines - 1].data)
5436 add_line_data(view, NULL, LINE_STAT_NONE);
5438 done_io(&io);
5439 return TRUE;
5442 /* Don't show unmerged entries in the staged section. */
5443 static const char *status_diff_index_argv[] = {
5444 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5445 "--cached", "-M", "HEAD", NULL
5448 static const char *status_diff_files_argv[] = {
5449 "git", "diff-files", "-z", NULL
5452 static const char *status_list_other_argv[] = {
5453 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5456 static const char *status_list_no_head_argv[] = {
5457 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5460 static const char *update_index_argv[] = {
5461 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5464 /* Restore the previous line number to stay in the context or select a
5465 * line with something that can be updated. */
5466 static void
5467 status_restore(struct view *view)
5469 if (view->p_lineno >= view->lines)
5470 view->p_lineno = view->lines - 1;
5471 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5472 view->p_lineno++;
5473 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5474 view->p_lineno--;
5476 /* If the above fails, always skip the "On branch" line. */
5477 if (view->p_lineno < view->lines)
5478 view->lineno = view->p_lineno;
5479 else
5480 view->lineno = 1;
5482 if (view->lineno < view->offset)
5483 view->offset = view->lineno;
5484 else if (view->offset + view->height <= view->lineno)
5485 view->offset = view->lineno - view->height + 1;
5487 view->p_restore = FALSE;
5490 static void
5491 status_update_onbranch(void)
5493 static const char *paths[][2] = {
5494 { "rebase-apply/rebasing", "Rebasing" },
5495 { "rebase-apply/applying", "Applying mailbox" },
5496 { "rebase-apply/", "Rebasing mailbox" },
5497 { "rebase-merge/interactive", "Interactive rebase" },
5498 { "rebase-merge/", "Rebase merge" },
5499 { "MERGE_HEAD", "Merging" },
5500 { "BISECT_LOG", "Bisecting" },
5501 { "HEAD", "On branch" },
5503 char buf[SIZEOF_STR];
5504 struct stat stat;
5505 int i;
5507 if (is_initial_commit()) {
5508 string_copy(status_onbranch, "Initial commit");
5509 return;
5512 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5513 char *head = opt_head;
5515 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5516 lstat(buf, &stat) < 0)
5517 continue;
5519 if (!*opt_head) {
5520 struct io io = {};
5522 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5523 io_read_buf(&io, buf, sizeof(buf))) {
5524 head = buf;
5525 if (!prefixcmp(head, "refs/heads/"))
5526 head += STRING_SIZE("refs/heads/");
5530 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5531 string_copy(status_onbranch, opt_head);
5532 return;
5535 string_copy(status_onbranch, "Not currently on any branch");
5538 /* First parse staged info using git-diff-index(1), then parse unstaged
5539 * info using git-diff-files(1), and finally untracked files using
5540 * git-ls-files(1). */
5541 static bool
5542 status_open(struct view *view)
5544 reset_view(view);
5546 add_line_data(view, NULL, LINE_STAT_HEAD);
5547 status_update_onbranch();
5549 run_io_bg(update_index_argv);
5551 if (is_initial_commit()) {
5552 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5553 return FALSE;
5554 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5555 return FALSE;
5558 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5559 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5560 return FALSE;
5562 /* Restore the exact position or use the specialized restore
5563 * mode? */
5564 if (!view->p_restore)
5565 status_restore(view);
5566 return TRUE;
5569 static bool
5570 status_draw(struct view *view, struct line *line, unsigned int lineno)
5572 struct status *status = line->data;
5573 enum line_type type;
5574 const char *text;
5576 if (!status) {
5577 switch (line->type) {
5578 case LINE_STAT_STAGED:
5579 type = LINE_STAT_SECTION;
5580 text = "Changes to be committed:";
5581 break;
5583 case LINE_STAT_UNSTAGED:
5584 type = LINE_STAT_SECTION;
5585 text = "Changed but not updated:";
5586 break;
5588 case LINE_STAT_UNTRACKED:
5589 type = LINE_STAT_SECTION;
5590 text = "Untracked files:";
5591 break;
5593 case LINE_STAT_NONE:
5594 type = LINE_DEFAULT;
5595 text = " (no files)";
5596 break;
5598 case LINE_STAT_HEAD:
5599 type = LINE_STAT_HEAD;
5600 text = status_onbranch;
5601 break;
5603 default:
5604 return FALSE;
5606 } else {
5607 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5609 buf[0] = status->status;
5610 if (draw_text(view, line->type, buf, TRUE))
5611 return TRUE;
5612 type = LINE_DEFAULT;
5613 text = status->new.name;
5616 draw_text(view, type, text, TRUE);
5617 return TRUE;
5620 static enum request
5621 status_load_error(struct view *view, struct view *stage, const char *path)
5623 if (displayed_views() == 2 || display[current_view] != view)
5624 maximize_view(view);
5625 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5626 return REQ_NONE;
5629 static enum request
5630 status_enter(struct view *view, struct line *line)
5632 struct status *status = line->data;
5633 const char *oldpath = status ? status->old.name : NULL;
5634 /* Diffs for unmerged entries are empty when passing the new
5635 * path, so leave it empty. */
5636 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5637 const char *info;
5638 enum open_flags split;
5639 struct view *stage = VIEW(REQ_VIEW_STAGE);
5641 if (line->type == LINE_STAT_NONE ||
5642 (!status && line[1].type == LINE_STAT_NONE)) {
5643 report("No file to diff");
5644 return REQ_NONE;
5647 switch (line->type) {
5648 case LINE_STAT_STAGED:
5649 if (is_initial_commit()) {
5650 const char *no_head_diff_argv[] = {
5651 "git", "diff", "--no-color", "--patch-with-stat",
5652 "--", "/dev/null", newpath, NULL
5655 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5656 return status_load_error(view, stage, newpath);
5657 } else {
5658 const char *index_show_argv[] = {
5659 "git", "diff-index", "--root", "--patch-with-stat",
5660 "-C", "-M", "--cached", "HEAD", "--",
5661 oldpath, newpath, NULL
5664 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5665 return status_load_error(view, stage, newpath);
5668 if (status)
5669 info = "Staged changes to %s";
5670 else
5671 info = "Staged changes";
5672 break;
5674 case LINE_STAT_UNSTAGED:
5676 const char *files_show_argv[] = {
5677 "git", "diff-files", "--root", "--patch-with-stat",
5678 "-C", "-M", "--", oldpath, newpath, NULL
5681 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5682 return status_load_error(view, stage, newpath);
5683 if (status)
5684 info = "Unstaged changes to %s";
5685 else
5686 info = "Unstaged changes";
5687 break;
5689 case LINE_STAT_UNTRACKED:
5690 if (!newpath) {
5691 report("No file to show");
5692 return REQ_NONE;
5695 if (!suffixcmp(status->new.name, -1, "/")) {
5696 report("Cannot display a directory");
5697 return REQ_NONE;
5700 if (!prepare_update_file(stage, newpath))
5701 return status_load_error(view, stage, newpath);
5702 info = "Untracked file %s";
5703 break;
5705 case LINE_STAT_HEAD:
5706 return REQ_NONE;
5708 default:
5709 die("line type %d not handled in switch", line->type);
5712 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5713 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5714 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5715 if (status) {
5716 stage_status = *status;
5717 } else {
5718 memset(&stage_status, 0, sizeof(stage_status));
5721 stage_line_type = line->type;
5722 stage_chunks = 0;
5723 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5726 return REQ_NONE;
5729 static bool
5730 status_exists(struct status *status, enum line_type type)
5732 struct view *view = VIEW(REQ_VIEW_STATUS);
5733 unsigned long lineno;
5735 for (lineno = 0; lineno < view->lines; lineno++) {
5736 struct line *line = &view->line[lineno];
5737 struct status *pos = line->data;
5739 if (line->type != type)
5740 continue;
5741 if (!pos && (!status || !status->status) && line[1].data) {
5742 select_view_line(view, lineno);
5743 return TRUE;
5745 if (pos && !strcmp(status->new.name, pos->new.name)) {
5746 select_view_line(view, lineno);
5747 return TRUE;
5751 return FALSE;
5755 static bool
5756 status_update_prepare(struct io *io, enum line_type type)
5758 const char *staged_argv[] = {
5759 "git", "update-index", "-z", "--index-info", NULL
5761 const char *others_argv[] = {
5762 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5765 switch (type) {
5766 case LINE_STAT_STAGED:
5767 return run_io(io, staged_argv, opt_cdup, IO_WR);
5769 case LINE_STAT_UNSTAGED:
5770 case LINE_STAT_UNTRACKED:
5771 return run_io(io, others_argv, opt_cdup, IO_WR);
5773 default:
5774 die("line type %d not handled in switch", type);
5775 return FALSE;
5779 static bool
5780 status_update_write(struct io *io, struct status *status, enum line_type type)
5782 char buf[SIZEOF_STR];
5783 size_t bufsize = 0;
5785 switch (type) {
5786 case LINE_STAT_STAGED:
5787 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5788 status->old.mode,
5789 status->old.rev,
5790 status->old.name, 0))
5791 return FALSE;
5792 break;
5794 case LINE_STAT_UNSTAGED:
5795 case LINE_STAT_UNTRACKED:
5796 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5797 return FALSE;
5798 break;
5800 default:
5801 die("line type %d not handled in switch", type);
5804 return io_write(io, buf, bufsize);
5807 static bool
5808 status_update_file(struct status *status, enum line_type type)
5810 struct io io = {};
5811 bool result;
5813 if (!status_update_prepare(&io, type))
5814 return FALSE;
5816 result = status_update_write(&io, status, type);
5817 return done_io(&io) && result;
5820 static bool
5821 status_update_files(struct view *view, struct line *line)
5823 char buf[sizeof(view->ref)];
5824 struct io io = {};
5825 bool result = TRUE;
5826 struct line *pos = view->line + view->lines;
5827 int files = 0;
5828 int file, done;
5829 int cursor_y = -1, cursor_x = -1;
5831 if (!status_update_prepare(&io, line->type))
5832 return FALSE;
5834 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5835 files++;
5837 string_copy(buf, view->ref);
5838 getsyx(cursor_y, cursor_x);
5839 for (file = 0, done = 5; result && file < files; line++, file++) {
5840 int almost_done = file * 100 / files;
5842 if (almost_done > done) {
5843 done = almost_done;
5844 string_format(view->ref, "updating file %u of %u (%d%% done)",
5845 file, files, done);
5846 update_view_title(view);
5847 setsyx(cursor_y, cursor_x);
5848 doupdate();
5850 result = status_update_write(&io, line->data, line->type);
5852 string_copy(view->ref, buf);
5854 return done_io(&io) && result;
5857 static bool
5858 status_update(struct view *view)
5860 struct line *line = &view->line[view->lineno];
5862 assert(view->lines);
5864 if (!line->data) {
5865 /* This should work even for the "On branch" line. */
5866 if (line < view->line + view->lines && !line[1].data) {
5867 report("Nothing to update");
5868 return FALSE;
5871 if (!status_update_files(view, line + 1)) {
5872 report("Failed to update file status");
5873 return FALSE;
5876 } else if (!status_update_file(line->data, line->type)) {
5877 report("Failed to update file status");
5878 return FALSE;
5881 return TRUE;
5884 static bool
5885 status_revert(struct status *status, enum line_type type, bool has_none)
5887 if (!status || type != LINE_STAT_UNSTAGED) {
5888 if (type == LINE_STAT_STAGED) {
5889 report("Cannot revert changes to staged files");
5890 } else if (type == LINE_STAT_UNTRACKED) {
5891 report("Cannot revert changes to untracked files");
5892 } else if (has_none) {
5893 report("Nothing to revert");
5894 } else {
5895 report("Cannot revert changes to multiple files");
5898 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5899 char mode[10] = "100644";
5900 const char *reset_argv[] = {
5901 "git", "update-index", "--cacheinfo", mode,
5902 status->old.rev, status->old.name, NULL
5904 const char *checkout_argv[] = {
5905 "git", "checkout", "--", status->old.name, NULL
5908 if (status->status == 'U') {
5909 string_format(mode, "%5o", status->old.mode);
5911 if (status->old.mode == 0 && status->new.mode == 0) {
5912 reset_argv[2] = "--force-remove";
5913 reset_argv[3] = status->old.name;
5914 reset_argv[4] = NULL;
5917 if (!run_io_fg(reset_argv, opt_cdup))
5918 return FALSE;
5919 if (status->old.mode == 0 && status->new.mode == 0)
5920 return TRUE;
5923 return run_io_fg(checkout_argv, opt_cdup);
5926 return FALSE;
5929 static enum request
5930 status_request(struct view *view, enum request request, struct line *line)
5932 struct status *status = line->data;
5934 switch (request) {
5935 case REQ_STATUS_UPDATE:
5936 if (!status_update(view))
5937 return REQ_NONE;
5938 break;
5940 case REQ_STATUS_REVERT:
5941 if (!status_revert(status, line->type, status_has_none(view, line)))
5942 return REQ_NONE;
5943 break;
5945 case REQ_STATUS_MERGE:
5946 if (!status || status->status != 'U') {
5947 report("Merging only possible for files with unmerged status ('U').");
5948 return REQ_NONE;
5950 open_mergetool(status->new.name);
5951 break;
5953 case REQ_EDIT:
5954 if (!status)
5955 return request;
5956 if (status->status == 'D') {
5957 report("File has been deleted.");
5958 return REQ_NONE;
5961 open_editor(status->new.name);
5962 break;
5964 case REQ_VIEW_BLAME:
5965 if (status)
5966 opt_ref[0] = 0;
5967 return request;
5969 case REQ_ENTER:
5970 /* After returning the status view has been split to
5971 * show the stage view. No further reloading is
5972 * necessary. */
5973 return status_enter(view, line);
5975 case REQ_REFRESH:
5976 /* Simply reload the view. */
5977 break;
5979 default:
5980 return request;
5983 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5985 return REQ_NONE;
5988 static void
5989 status_select(struct view *view, struct line *line)
5991 struct status *status = line->data;
5992 char file[SIZEOF_STR] = "all files";
5993 const char *text;
5994 const char *key;
5996 if (status && !string_format(file, "'%s'", status->new.name))
5997 return;
5999 if (!status && line[1].type == LINE_STAT_NONE)
6000 line++;
6002 switch (line->type) {
6003 case LINE_STAT_STAGED:
6004 text = "Press %s to unstage %s for commit";
6005 break;
6007 case LINE_STAT_UNSTAGED:
6008 text = "Press %s to stage %s for commit";
6009 break;
6011 case LINE_STAT_UNTRACKED:
6012 text = "Press %s to stage %s for addition";
6013 break;
6015 case LINE_STAT_HEAD:
6016 case LINE_STAT_NONE:
6017 text = "Nothing to update";
6018 break;
6020 default:
6021 die("line type %d not handled in switch", line->type);
6024 if (status && status->status == 'U') {
6025 text = "Press %s to resolve conflict in %s";
6026 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6028 } else {
6029 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6032 string_format(view->ref, text, key, file);
6033 if (status)
6034 string_copy(opt_file, status->new.name);
6037 static bool
6038 status_grep(struct view *view, struct line *line)
6040 struct status *status = line->data;
6042 if (status) {
6043 const char buf[2] = { status->status, 0 };
6044 const char *text[] = { status->new.name, buf, NULL };
6046 return grep_text(view, text);
6049 return FALSE;
6052 static struct view_ops status_ops = {
6053 "file",
6054 NULL,
6055 status_open,
6056 NULL,
6057 status_draw,
6058 status_request,
6059 status_grep,
6060 status_select,
6064 static bool
6065 stage_diff_write(struct io *io, struct line *line, struct line *end)
6067 while (line < end) {
6068 if (!io_write(io, line->data, strlen(line->data)) ||
6069 !io_write(io, "\n", 1))
6070 return FALSE;
6071 line++;
6072 if (line->type == LINE_DIFF_CHUNK ||
6073 line->type == LINE_DIFF_HEADER)
6074 break;
6077 return TRUE;
6080 static struct line *
6081 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6083 for (; view->line < line; line--)
6084 if (line->type == type)
6085 return line;
6087 return NULL;
6090 static bool
6091 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6093 const char *apply_argv[SIZEOF_ARG] = {
6094 "git", "apply", "--whitespace=nowarn", NULL
6096 struct line *diff_hdr;
6097 struct io io = {};
6098 int argc = 3;
6100 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6101 if (!diff_hdr)
6102 return FALSE;
6104 if (!revert)
6105 apply_argv[argc++] = "--cached";
6106 if (revert || stage_line_type == LINE_STAT_STAGED)
6107 apply_argv[argc++] = "-R";
6108 apply_argv[argc++] = "-";
6109 apply_argv[argc++] = NULL;
6110 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6111 return FALSE;
6113 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6114 !stage_diff_write(&io, chunk, view->line + view->lines))
6115 chunk = NULL;
6117 done_io(&io);
6118 run_io_bg(update_index_argv);
6120 return chunk ? TRUE : FALSE;
6123 static bool
6124 stage_update(struct view *view, struct line *line)
6126 struct line *chunk = NULL;
6128 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6129 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6131 if (chunk) {
6132 if (!stage_apply_chunk(view, chunk, FALSE)) {
6133 report("Failed to apply chunk");
6134 return FALSE;
6137 } else if (!stage_status.status) {
6138 view = VIEW(REQ_VIEW_STATUS);
6140 for (line = view->line; line < view->line + view->lines; line++)
6141 if (line->type == stage_line_type)
6142 break;
6144 if (!status_update_files(view, line + 1)) {
6145 report("Failed to update files");
6146 return FALSE;
6149 } else if (!status_update_file(&stage_status, stage_line_type)) {
6150 report("Failed to update file");
6151 return FALSE;
6154 return TRUE;
6157 static bool
6158 stage_revert(struct view *view, struct line *line)
6160 struct line *chunk = NULL;
6162 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6163 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6165 if (chunk) {
6166 if (!prompt_yesno("Are you sure you want to revert changes?"))
6167 return FALSE;
6169 if (!stage_apply_chunk(view, chunk, TRUE)) {
6170 report("Failed to revert chunk");
6171 return FALSE;
6173 return TRUE;
6175 } else {
6176 return status_revert(stage_status.status ? &stage_status : NULL,
6177 stage_line_type, FALSE);
6182 static void
6183 stage_next(struct view *view, struct line *line)
6185 int i;
6187 if (!stage_chunks) {
6188 for (line = view->line; line < view->line + view->lines; line++) {
6189 if (line->type != LINE_DIFF_CHUNK)
6190 continue;
6192 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6193 report("Allocation failure");
6194 return;
6197 stage_chunk[stage_chunks++] = line - view->line;
6201 for (i = 0; i < stage_chunks; i++) {
6202 if (stage_chunk[i] > view->lineno) {
6203 do_scroll_view(view, stage_chunk[i] - view->lineno);
6204 report("Chunk %d of %d", i + 1, stage_chunks);
6205 return;
6209 report("No next chunk found");
6212 static enum request
6213 stage_request(struct view *view, enum request request, struct line *line)
6215 switch (request) {
6216 case REQ_STATUS_UPDATE:
6217 if (!stage_update(view, line))
6218 return REQ_NONE;
6219 break;
6221 case REQ_STATUS_REVERT:
6222 if (!stage_revert(view, line))
6223 return REQ_NONE;
6224 break;
6226 case REQ_STAGE_NEXT:
6227 if (stage_line_type == LINE_STAT_UNTRACKED) {
6228 report("File is untracked; press %s to add",
6229 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6230 return REQ_NONE;
6232 stage_next(view, line);
6233 return REQ_NONE;
6235 case REQ_EDIT:
6236 if (!stage_status.new.name[0])
6237 return request;
6238 if (stage_status.status == 'D') {
6239 report("File has been deleted.");
6240 return REQ_NONE;
6243 open_editor(stage_status.new.name);
6244 break;
6246 case REQ_REFRESH:
6247 /* Reload everything ... */
6248 break;
6250 case REQ_VIEW_BLAME:
6251 if (stage_status.new.name[0]) {
6252 string_copy(opt_file, stage_status.new.name);
6253 opt_ref[0] = 0;
6255 return request;
6257 case REQ_ENTER:
6258 return pager_request(view, request, line);
6260 default:
6261 return request;
6264 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6265 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6267 /* Check whether the staged entry still exists, and close the
6268 * stage view if it doesn't. */
6269 if (!status_exists(&stage_status, stage_line_type)) {
6270 status_restore(VIEW(REQ_VIEW_STATUS));
6271 return REQ_VIEW_CLOSE;
6274 if (stage_line_type == LINE_STAT_UNTRACKED) {
6275 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6276 report("Cannot display a directory");
6277 return REQ_NONE;
6280 if (!prepare_update_file(view, stage_status.new.name)) {
6281 report("Failed to open file: %s", strerror(errno));
6282 return REQ_NONE;
6285 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6287 return REQ_NONE;
6290 static struct view_ops stage_ops = {
6291 "line",
6292 NULL,
6293 NULL,
6294 pager_read,
6295 pager_draw,
6296 stage_request,
6297 pager_grep,
6298 pager_select,
6303 * Revision graph
6306 struct commit {
6307 char id[SIZEOF_REV]; /* SHA1 ID. */
6308 char title[128]; /* First line of the commit message. */
6309 const char *author; /* Author of the commit. */
6310 time_t time; /* Date from the author ident. */
6311 struct ref_list *refs; /* Repository references. */
6312 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6313 size_t graph_size; /* The width of the graph array. */
6314 bool has_parents; /* Rewritten --parents seen. */
6317 /* Size of rev graph with no "padding" columns */
6318 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6320 struct rev_graph {
6321 struct rev_graph *prev, *next, *parents;
6322 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6323 size_t size;
6324 struct commit *commit;
6325 size_t pos;
6326 unsigned int boundary:1;
6329 /* Parents of the commit being visualized. */
6330 static struct rev_graph graph_parents[4];
6332 /* The current stack of revisions on the graph. */
6333 static struct rev_graph graph_stacks[4] = {
6334 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6335 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6336 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6337 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6340 static inline bool
6341 graph_parent_is_merge(struct rev_graph *graph)
6343 return graph->parents->size > 1;
6346 static inline void
6347 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6349 struct commit *commit = graph->commit;
6351 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6352 commit->graph[commit->graph_size++] = symbol;
6355 static void
6356 clear_rev_graph(struct rev_graph *graph)
6358 graph->boundary = 0;
6359 graph->size = graph->pos = 0;
6360 graph->commit = NULL;
6361 memset(graph->parents, 0, sizeof(*graph->parents));
6364 static void
6365 done_rev_graph(struct rev_graph *graph)
6367 if (graph_parent_is_merge(graph) &&
6368 graph->pos < graph->size - 1 &&
6369 graph->next->size == graph->size + graph->parents->size - 1) {
6370 size_t i = graph->pos + graph->parents->size - 1;
6372 graph->commit->graph_size = i * 2;
6373 while (i < graph->next->size - 1) {
6374 append_to_rev_graph(graph, ' ');
6375 append_to_rev_graph(graph, '\\');
6376 i++;
6380 clear_rev_graph(graph);
6383 static void
6384 push_rev_graph(struct rev_graph *graph, const char *parent)
6386 int i;
6388 /* "Collapse" duplicate parents lines.
6390 * FIXME: This needs to also update update the drawn graph but
6391 * for now it just serves as a method for pruning graph lines. */
6392 for (i = 0; i < graph->size; i++)
6393 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6394 return;
6396 if (graph->size < SIZEOF_REVITEMS) {
6397 string_copy_rev(graph->rev[graph->size++], parent);
6401 static chtype
6402 get_rev_graph_symbol(struct rev_graph *graph)
6404 chtype symbol;
6406 if (graph->boundary)
6407 symbol = REVGRAPH_BOUND;
6408 else if (graph->parents->size == 0)
6409 symbol = REVGRAPH_INIT;
6410 else if (graph_parent_is_merge(graph))
6411 symbol = REVGRAPH_MERGE;
6412 else if (graph->pos >= graph->size)
6413 symbol = REVGRAPH_BRANCH;
6414 else
6415 symbol = REVGRAPH_COMMIT;
6417 return symbol;
6420 static void
6421 draw_rev_graph(struct rev_graph *graph)
6423 struct rev_filler {
6424 chtype separator, line;
6426 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6427 static struct rev_filler fillers[] = {
6428 { ' ', '|' },
6429 { '`', '.' },
6430 { '\'', ' ' },
6431 { '/', ' ' },
6433 chtype symbol = get_rev_graph_symbol(graph);
6434 struct rev_filler *filler;
6435 size_t i;
6437 if (opt_line_graphics)
6438 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6440 filler = &fillers[DEFAULT];
6442 for (i = 0; i < graph->pos; i++) {
6443 append_to_rev_graph(graph, filler->line);
6444 if (graph_parent_is_merge(graph->prev) &&
6445 graph->prev->pos == i)
6446 filler = &fillers[RSHARP];
6448 append_to_rev_graph(graph, filler->separator);
6451 /* Place the symbol for this revision. */
6452 append_to_rev_graph(graph, symbol);
6454 if (graph->prev->size > graph->size)
6455 filler = &fillers[RDIAG];
6456 else
6457 filler = &fillers[DEFAULT];
6459 i++;
6461 for (; i < graph->size; i++) {
6462 append_to_rev_graph(graph, filler->separator);
6463 append_to_rev_graph(graph, filler->line);
6464 if (graph_parent_is_merge(graph->prev) &&
6465 i < graph->prev->pos + graph->parents->size)
6466 filler = &fillers[RSHARP];
6467 if (graph->prev->size > graph->size)
6468 filler = &fillers[LDIAG];
6471 if (graph->prev->size > graph->size) {
6472 append_to_rev_graph(graph, filler->separator);
6473 if (filler->line != ' ')
6474 append_to_rev_graph(graph, filler->line);
6478 /* Prepare the next rev graph */
6479 static void
6480 prepare_rev_graph(struct rev_graph *graph)
6482 size_t i;
6484 /* First, traverse all lines of revisions up to the active one. */
6485 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6486 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6487 break;
6489 push_rev_graph(graph->next, graph->rev[graph->pos]);
6492 /* Interleave the new revision parent(s). */
6493 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6494 push_rev_graph(graph->next, graph->parents->rev[i]);
6496 /* Lastly, put any remaining revisions. */
6497 for (i = graph->pos + 1; i < graph->size; i++)
6498 push_rev_graph(graph->next, graph->rev[i]);
6501 static void
6502 update_rev_graph(struct view *view, struct rev_graph *graph)
6504 /* If this is the finalizing update ... */
6505 if (graph->commit)
6506 prepare_rev_graph(graph);
6508 /* Graph visualization needs a one rev look-ahead,
6509 * so the first update doesn't visualize anything. */
6510 if (!graph->prev->commit)
6511 return;
6513 if (view->lines > 2)
6514 view->line[view->lines - 3].dirty = 1;
6515 if (view->lines > 1)
6516 view->line[view->lines - 2].dirty = 1;
6517 draw_rev_graph(graph->prev);
6518 done_rev_graph(graph->prev->prev);
6523 * Main view backend
6526 static const char *main_argv[SIZEOF_ARG] = {
6527 "git", "log", "--no-color", "--pretty=raw", "--parents",
6528 "--topo-order", "%(head)", NULL
6531 static bool
6532 main_draw(struct view *view, struct line *line, unsigned int lineno)
6534 struct commit *commit = line->data;
6536 if (!commit->author)
6537 return FALSE;
6539 if (opt_date && draw_date(view, &commit->time))
6540 return TRUE;
6542 if (opt_author && draw_author(view, commit->author))
6543 return TRUE;
6545 if (opt_rev_graph && commit->graph_size &&
6546 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6547 return TRUE;
6549 if (opt_show_refs && commit->refs) {
6550 size_t i;
6552 for (i = 0; i < commit->refs->size; i++) {
6553 struct ref *ref = commit->refs->refs[i];
6554 enum line_type type;
6556 if (ref->head)
6557 type = LINE_MAIN_HEAD;
6558 else if (ref->ltag)
6559 type = LINE_MAIN_LOCAL_TAG;
6560 else if (ref->tag)
6561 type = LINE_MAIN_TAG;
6562 else if (ref->tracked)
6563 type = LINE_MAIN_TRACKED;
6564 else if (ref->remote)
6565 type = LINE_MAIN_REMOTE;
6566 else
6567 type = LINE_MAIN_REF;
6569 if (draw_text(view, type, "[", TRUE) ||
6570 draw_text(view, type, ref->name, TRUE) ||
6571 draw_text(view, type, "]", TRUE))
6572 return TRUE;
6574 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6575 return TRUE;
6579 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6580 return TRUE;
6583 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6584 static bool
6585 main_read(struct view *view, char *line)
6587 static struct rev_graph *graph = graph_stacks;
6588 enum line_type type;
6589 struct commit *commit;
6591 if (!line) {
6592 int i;
6594 if (!view->lines && !view->parent)
6595 die("No revisions match the given arguments.");
6596 if (view->lines > 0) {
6597 commit = view->line[view->lines - 1].data;
6598 view->line[view->lines - 1].dirty = 1;
6599 if (!commit->author) {
6600 view->lines--;
6601 free(commit);
6602 graph->commit = NULL;
6605 update_rev_graph(view, graph);
6607 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6608 clear_rev_graph(&graph_stacks[i]);
6609 return TRUE;
6612 type = get_line_type(line);
6613 if (type == LINE_COMMIT) {
6614 commit = calloc(1, sizeof(struct commit));
6615 if (!commit)
6616 return FALSE;
6618 line += STRING_SIZE("commit ");
6619 if (*line == '-') {
6620 graph->boundary = 1;
6621 line++;
6624 string_copy_rev(commit->id, line);
6625 commit->refs = get_ref_list(commit->id);
6626 graph->commit = commit;
6627 add_line_data(view, commit, LINE_MAIN_COMMIT);
6629 while ((line = strchr(line, ' '))) {
6630 line++;
6631 push_rev_graph(graph->parents, line);
6632 commit->has_parents = TRUE;
6634 return TRUE;
6637 if (!view->lines)
6638 return TRUE;
6639 commit = view->line[view->lines - 1].data;
6641 switch (type) {
6642 case LINE_PARENT:
6643 if (commit->has_parents)
6644 break;
6645 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6646 break;
6648 case LINE_AUTHOR:
6649 parse_author_line(line + STRING_SIZE("author "),
6650 &commit->author, &commit->time);
6651 update_rev_graph(view, graph);
6652 graph = graph->next;
6653 break;
6655 default:
6656 /* Fill in the commit title if it has not already been set. */
6657 if (commit->title[0])
6658 break;
6660 /* Require titles to start with a non-space character at the
6661 * offset used by git log. */
6662 if (strncmp(line, " ", 4))
6663 break;
6664 line += 4;
6665 /* Well, if the title starts with a whitespace character,
6666 * try to be forgiving. Otherwise we end up with no title. */
6667 while (isspace(*line))
6668 line++;
6669 if (*line == '\0')
6670 break;
6671 /* FIXME: More graceful handling of titles; append "..." to
6672 * shortened titles, etc. */
6674 string_expand(commit->title, sizeof(commit->title), line, 1);
6675 view->line[view->lines - 1].dirty = 1;
6678 return TRUE;
6681 static enum request
6682 main_request(struct view *view, enum request request, struct line *line)
6684 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6686 switch (request) {
6687 case REQ_ENTER:
6688 open_view(view, REQ_VIEW_DIFF, flags);
6689 break;
6690 case REQ_REFRESH:
6691 load_refs();
6692 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6693 break;
6694 default:
6695 return request;
6698 return REQ_NONE;
6701 static bool
6702 grep_refs(struct ref_list *list, regex_t *regex)
6704 regmatch_t pmatch;
6705 size_t i;
6707 if (!opt_show_refs || !list)
6708 return FALSE;
6710 for (i = 0; i < list->size; i++) {
6711 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6712 return TRUE;
6715 return FALSE;
6718 static bool
6719 main_grep(struct view *view, struct line *line)
6721 struct commit *commit = line->data;
6722 const char *text[] = {
6723 commit->title,
6724 opt_author ? commit->author : "",
6725 opt_date ? mkdate(&commit->time) : "",
6726 NULL
6729 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6732 static void
6733 main_select(struct view *view, struct line *line)
6735 struct commit *commit = line->data;
6737 string_copy_rev(view->ref, commit->id);
6738 string_copy_rev(ref_commit, view->ref);
6741 static struct view_ops main_ops = {
6742 "commit",
6743 main_argv,
6744 NULL,
6745 main_read,
6746 main_draw,
6747 main_request,
6748 main_grep,
6749 main_select,
6754 * Unicode / UTF-8 handling
6756 * NOTE: Much of the following code for dealing with Unicode is derived from
6757 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6758 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6761 static inline int
6762 unicode_width(unsigned long c)
6764 if (c >= 0x1100 &&
6765 (c <= 0x115f /* Hangul Jamo */
6766 || c == 0x2329
6767 || c == 0x232a
6768 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6769 /* CJK ... Yi */
6770 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6771 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6772 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6773 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6774 || (c >= 0xffe0 && c <= 0xffe6)
6775 || (c >= 0x20000 && c <= 0x2fffd)
6776 || (c >= 0x30000 && c <= 0x3fffd)))
6777 return 2;
6779 if (c == '\t')
6780 return opt_tab_size;
6782 return 1;
6785 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6786 * Illegal bytes are set one. */
6787 static const unsigned char utf8_bytes[256] = {
6788 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,
6789 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,
6790 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,
6791 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,
6792 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,
6793 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,
6794 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,
6795 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,
6798 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6799 static inline unsigned long
6800 utf8_to_unicode(const char *string, size_t length)
6802 unsigned long unicode;
6804 switch (length) {
6805 case 1:
6806 unicode = string[0];
6807 break;
6808 case 2:
6809 unicode = (string[0] & 0x1f) << 6;
6810 unicode += (string[1] & 0x3f);
6811 break;
6812 case 3:
6813 unicode = (string[0] & 0x0f) << 12;
6814 unicode += ((string[1] & 0x3f) << 6);
6815 unicode += (string[2] & 0x3f);
6816 break;
6817 case 4:
6818 unicode = (string[0] & 0x0f) << 18;
6819 unicode += ((string[1] & 0x3f) << 12);
6820 unicode += ((string[2] & 0x3f) << 6);
6821 unicode += (string[3] & 0x3f);
6822 break;
6823 case 5:
6824 unicode = (string[0] & 0x0f) << 24;
6825 unicode += ((string[1] & 0x3f) << 18);
6826 unicode += ((string[2] & 0x3f) << 12);
6827 unicode += ((string[3] & 0x3f) << 6);
6828 unicode += (string[4] & 0x3f);
6829 break;
6830 case 6:
6831 unicode = (string[0] & 0x01) << 30;
6832 unicode += ((string[1] & 0x3f) << 24);
6833 unicode += ((string[2] & 0x3f) << 18);
6834 unicode += ((string[3] & 0x3f) << 12);
6835 unicode += ((string[4] & 0x3f) << 6);
6836 unicode += (string[5] & 0x3f);
6837 break;
6838 default:
6839 die("Invalid Unicode length");
6842 /* Invalid characters could return the special 0xfffd value but NUL
6843 * should be just as good. */
6844 return unicode > 0xffff ? 0 : unicode;
6847 /* Calculates how much of string can be shown within the given maximum width
6848 * and sets trimmed parameter to non-zero value if all of string could not be
6849 * shown. If the reserve flag is TRUE, it will reserve at least one
6850 * trailing character, which can be useful when drawing a delimiter.
6852 * Returns the number of bytes to output from string to satisfy max_width. */
6853 static size_t
6854 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6856 const char *string = *start;
6857 const char *end = strchr(string, '\0');
6858 unsigned char last_bytes = 0;
6859 size_t last_ucwidth = 0;
6861 *width = 0;
6862 *trimmed = 0;
6864 while (string < end) {
6865 int c = *(unsigned char *) string;
6866 unsigned char bytes = utf8_bytes[c];
6867 size_t ucwidth;
6868 unsigned long unicode;
6870 if (string + bytes > end)
6871 break;
6873 /* Change representation to figure out whether
6874 * it is a single- or double-width character. */
6876 unicode = utf8_to_unicode(string, bytes);
6877 /* FIXME: Graceful handling of invalid Unicode character. */
6878 if (!unicode)
6879 break;
6881 ucwidth = unicode_width(unicode);
6882 if (skip > 0) {
6883 skip -= ucwidth <= skip ? ucwidth : skip;
6884 *start += bytes;
6886 *width += ucwidth;
6887 if (*width > max_width) {
6888 *trimmed = 1;
6889 *width -= ucwidth;
6890 if (reserve && *width == max_width) {
6891 string -= last_bytes;
6892 *width -= last_ucwidth;
6894 break;
6897 string += bytes;
6898 last_bytes = ucwidth ? bytes : 0;
6899 last_ucwidth = ucwidth;
6902 return string - *start;
6907 * Status management
6910 /* Whether or not the curses interface has been initialized. */
6911 static bool cursed = FALSE;
6913 /* Terminal hacks and workarounds. */
6914 static bool use_scroll_redrawwin;
6915 static bool use_scroll_status_wclear;
6917 /* The status window is used for polling keystrokes. */
6918 static WINDOW *status_win;
6920 /* Reading from the prompt? */
6921 static bool input_mode = FALSE;
6923 static bool status_empty = FALSE;
6925 /* Update status and title window. */
6926 static void
6927 report(const char *msg, ...)
6929 struct view *view = display[current_view];
6931 if (input_mode)
6932 return;
6934 if (!view) {
6935 char buf[SIZEOF_STR];
6936 va_list args;
6938 va_start(args, msg);
6939 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6940 buf[sizeof(buf) - 1] = 0;
6941 buf[sizeof(buf) - 2] = '.';
6942 buf[sizeof(buf) - 3] = '.';
6943 buf[sizeof(buf) - 4] = '.';
6945 va_end(args);
6946 die("%s", buf);
6949 if (!status_empty || *msg) {
6950 va_list args;
6952 va_start(args, msg);
6954 wmove(status_win, 0, 0);
6955 if (view->has_scrolled && use_scroll_status_wclear)
6956 wclear(status_win);
6957 if (*msg) {
6958 vwprintw(status_win, msg, args);
6959 status_empty = FALSE;
6960 } else {
6961 status_empty = TRUE;
6963 wclrtoeol(status_win);
6964 wnoutrefresh(status_win);
6966 va_end(args);
6969 update_view_title(view);
6972 /* Controls when nodelay should be in effect when polling user input. */
6973 static void
6974 set_nonblocking_input(bool loading)
6976 static unsigned int loading_views;
6978 if ((loading == FALSE && loading_views-- == 1) ||
6979 (loading == TRUE && loading_views++ == 0))
6980 nodelay(status_win, loading);
6983 static void
6984 init_display(void)
6986 const char *term;
6987 int x, y;
6989 /* Initialize the curses library */
6990 if (isatty(STDIN_FILENO)) {
6991 cursed = !!initscr();
6992 opt_tty = stdin;
6993 } else {
6994 /* Leave stdin and stdout alone when acting as a pager. */
6995 opt_tty = fopen("/dev/tty", "r+");
6996 if (!opt_tty)
6997 die("Failed to open /dev/tty");
6998 cursed = !!newterm(NULL, opt_tty, opt_tty);
7001 if (!cursed)
7002 die("Failed to initialize curses");
7004 nonl(); /* Disable conversion and detect newlines from input. */
7005 cbreak(); /* Take input chars one at a time, no wait for \n */
7006 noecho(); /* Don't echo input */
7007 leaveok(stdscr, FALSE);
7009 if (has_colors())
7010 init_colors();
7012 getmaxyx(stdscr, y, x);
7013 status_win = newwin(1, 0, y - 1, 0);
7014 if (!status_win)
7015 die("Failed to create status window");
7017 /* Enable keyboard mapping */
7018 keypad(status_win, TRUE);
7019 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7021 TABSIZE = opt_tab_size;
7022 if (opt_line_graphics) {
7023 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7026 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7027 if (term && !strcmp(term, "gnome-terminal")) {
7028 /* In the gnome-terminal-emulator, the message from
7029 * scrolling up one line when impossible followed by
7030 * scrolling down one line causes corruption of the
7031 * status line. This is fixed by calling wclear. */
7032 use_scroll_status_wclear = TRUE;
7033 use_scroll_redrawwin = FALSE;
7035 } else if (term && !strcmp(term, "xrvt-xpm")) {
7036 /* No problems with full optimizations in xrvt-(unicode)
7037 * and aterm. */
7038 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7040 } else {
7041 /* When scrolling in (u)xterm the last line in the
7042 * scrolling direction will update slowly. */
7043 use_scroll_redrawwin = TRUE;
7044 use_scroll_status_wclear = FALSE;
7048 static int
7049 get_input(int prompt_position)
7051 struct view *view;
7052 int i, key, cursor_y, cursor_x;
7054 if (prompt_position)
7055 input_mode = TRUE;
7057 while (TRUE) {
7058 foreach_view (view, i) {
7059 update_view(view);
7060 if (view_is_displayed(view) && view->has_scrolled &&
7061 use_scroll_redrawwin)
7062 redrawwin(view->win);
7063 view->has_scrolled = FALSE;
7066 /* Update the cursor position. */
7067 if (prompt_position) {
7068 getbegyx(status_win, cursor_y, cursor_x);
7069 cursor_x = prompt_position;
7070 } else {
7071 view = display[current_view];
7072 getbegyx(view->win, cursor_y, cursor_x);
7073 cursor_x = view->width - 1;
7074 cursor_y += view->lineno - view->offset;
7076 setsyx(cursor_y, cursor_x);
7078 /* Refresh, accept single keystroke of input */
7079 doupdate();
7080 key = wgetch(status_win);
7082 /* wgetch() with nodelay() enabled returns ERR when
7083 * there's no input. */
7084 if (key == ERR) {
7086 } else if (key == KEY_RESIZE) {
7087 int height, width;
7089 getmaxyx(stdscr, height, width);
7091 wresize(status_win, 1, width);
7092 mvwin(status_win, height - 1, 0);
7093 wnoutrefresh(status_win);
7094 resize_display();
7095 redraw_display(TRUE);
7097 } else {
7098 input_mode = FALSE;
7099 return key;
7104 static char *
7105 prompt_input(const char *prompt, input_handler handler, void *data)
7107 enum input_status status = INPUT_OK;
7108 static char buf[SIZEOF_STR];
7109 size_t pos = 0;
7111 buf[pos] = 0;
7113 while (status == INPUT_OK || status == INPUT_SKIP) {
7114 int key;
7116 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7117 wclrtoeol(status_win);
7119 key = get_input(pos + 1);
7120 switch (key) {
7121 case KEY_RETURN:
7122 case KEY_ENTER:
7123 case '\n':
7124 status = pos ? INPUT_STOP : INPUT_CANCEL;
7125 break;
7127 case KEY_BACKSPACE:
7128 if (pos > 0)
7129 buf[--pos] = 0;
7130 else
7131 status = INPUT_CANCEL;
7132 break;
7134 case KEY_ESC:
7135 status = INPUT_CANCEL;
7136 break;
7138 default:
7139 if (pos >= sizeof(buf)) {
7140 report("Input string too long");
7141 return NULL;
7144 status = handler(data, buf, key);
7145 if (status == INPUT_OK)
7146 buf[pos++] = (char) key;
7150 /* Clear the status window */
7151 status_empty = FALSE;
7152 report("");
7154 if (status == INPUT_CANCEL)
7155 return NULL;
7157 buf[pos++] = 0;
7159 return buf;
7162 static enum input_status
7163 prompt_yesno_handler(void *data, char *buf, int c)
7165 if (c == 'y' || c == 'Y')
7166 return INPUT_STOP;
7167 if (c == 'n' || c == 'N')
7168 return INPUT_CANCEL;
7169 return INPUT_SKIP;
7172 static bool
7173 prompt_yesno(const char *prompt)
7175 char prompt2[SIZEOF_STR];
7177 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7178 return FALSE;
7180 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7183 static enum input_status
7184 read_prompt_handler(void *data, char *buf, int c)
7186 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7189 static char *
7190 read_prompt(const char *prompt)
7192 return prompt_input(prompt, read_prompt_handler, NULL);
7195 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7197 enum input_status status = INPUT_OK;
7198 int size = 0;
7200 while (items[size].text)
7201 size++;
7203 while (status == INPUT_OK) {
7204 const struct menu_item *item = &items[*selected];
7205 int key;
7206 int i;
7208 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7209 prompt, *selected + 1, size);
7210 if (item->hotkey)
7211 wprintw(status_win, "[%c] ", (char) item->hotkey);
7212 wprintw(status_win, "%s", item->text);
7213 wclrtoeol(status_win);
7215 key = get_input(COLS - 1);
7216 switch (key) {
7217 case KEY_RETURN:
7218 case KEY_ENTER:
7219 case '\n':
7220 status = INPUT_STOP;
7221 break;
7223 case KEY_LEFT:
7224 case KEY_UP:
7225 *selected = *selected - 1;
7226 if (*selected < 0)
7227 *selected = size - 1;
7228 break;
7230 case KEY_RIGHT:
7231 case KEY_DOWN:
7232 *selected = (*selected + 1) % size;
7233 break;
7235 case KEY_ESC:
7236 status = INPUT_CANCEL;
7237 break;
7239 default:
7240 for (i = 0; items[i].text; i++)
7241 if (items[i].hotkey == key) {
7242 *selected = i;
7243 status = INPUT_STOP;
7244 break;
7249 /* Clear the status window */
7250 status_empty = FALSE;
7251 report("");
7253 return status != INPUT_CANCEL;
7257 * Repository properties
7260 static struct ref **refs = NULL;
7261 static size_t refs_size = 0;
7263 static struct ref_list **ref_lists = NULL;
7264 static size_t ref_lists_size = 0;
7266 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7267 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7268 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7270 static int
7271 compare_refs(const void *ref1_, const void *ref2_)
7273 const struct ref *ref1 = *(const struct ref **)ref1_;
7274 const struct ref *ref2 = *(const struct ref **)ref2_;
7276 if (ref1->tag != ref2->tag)
7277 return ref2->tag - ref1->tag;
7278 if (ref1->ltag != ref2->ltag)
7279 return ref2->ltag - ref2->ltag;
7280 if (ref1->head != ref2->head)
7281 return ref2->head - ref1->head;
7282 if (ref1->tracked != ref2->tracked)
7283 return ref2->tracked - ref1->tracked;
7284 if (ref1->remote != ref2->remote)
7285 return ref2->remote - ref1->remote;
7286 return strcmp(ref1->name, ref2->name);
7289 static void
7290 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7292 size_t i;
7294 for (i = 0; i < refs_size; i++)
7295 if (!visitor(data, refs[i]))
7296 break;
7299 static struct ref_list *
7300 get_ref_list(const char *id)
7302 struct ref_list *list;
7303 size_t i;
7305 for (i = 0; i < ref_lists_size; i++)
7306 if (!strcmp(id, ref_lists[i]->id))
7307 return ref_lists[i];
7309 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7310 return NULL;
7311 list = calloc(1, sizeof(*list));
7312 if (!list)
7313 return NULL;
7315 for (i = 0; i < refs_size; i++) {
7316 if (!strcmp(id, refs[i]->id) &&
7317 realloc_refs_list(&list->refs, list->size, 1))
7318 list->refs[list->size++] = refs[i];
7321 if (!list->refs) {
7322 free(list);
7323 return NULL;
7326 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7327 ref_lists[ref_lists_size++] = list;
7328 return list;
7331 static int
7332 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7334 struct ref *ref = NULL;
7335 bool tag = FALSE;
7336 bool ltag = FALSE;
7337 bool remote = FALSE;
7338 bool tracked = FALSE;
7339 bool head = FALSE;
7340 int from = 0, to = refs_size - 1;
7342 if (!prefixcmp(name, "refs/tags/")) {
7343 if (!suffixcmp(name, namelen, "^{}")) {
7344 namelen -= 3;
7345 name[namelen] = 0;
7346 } else {
7347 ltag = TRUE;
7350 tag = TRUE;
7351 namelen -= STRING_SIZE("refs/tags/");
7352 name += STRING_SIZE("refs/tags/");
7354 } else if (!prefixcmp(name, "refs/remotes/")) {
7355 remote = TRUE;
7356 namelen -= STRING_SIZE("refs/remotes/");
7357 name += STRING_SIZE("refs/remotes/");
7358 tracked = !strcmp(opt_remote, name);
7360 } else if (!prefixcmp(name, "refs/heads/")) {
7361 namelen -= STRING_SIZE("refs/heads/");
7362 name += STRING_SIZE("refs/heads/");
7363 head = !strncmp(opt_head, name, namelen);
7365 } else if (!strcmp(name, "HEAD")) {
7366 string_ncopy(opt_head_rev, id, idlen);
7367 return OK;
7370 /* If we are reloading or it's an annotated tag, replace the
7371 * previous SHA1 with the resolved commit id; relies on the fact
7372 * git-ls-remote lists the commit id of an annotated tag right
7373 * before the commit id it points to. */
7374 while (from <= to) {
7375 size_t pos = (to + from) / 2;
7376 int cmp = strcmp(name, refs[pos]->name);
7378 if (!cmp) {
7379 ref = refs[pos];
7380 break;
7383 if (cmp < 0)
7384 to = pos - 1;
7385 else
7386 from = pos + 1;
7389 if (!ref) {
7390 if (!realloc_refs(&refs, refs_size, 1))
7391 return ERR;
7392 ref = calloc(1, sizeof(*ref) + namelen);
7393 if (!ref)
7394 return ERR;
7395 memmove(refs + from + 1, refs + from,
7396 (refs_size - from) * sizeof(*refs));
7397 refs[from] = ref;
7398 strncpy(ref->name, name, namelen);
7399 refs_size++;
7402 ref->head = head;
7403 ref->tag = tag;
7404 ref->ltag = ltag;
7405 ref->remote = remote;
7406 ref->tracked = tracked;
7407 string_copy_rev(ref->id, id);
7409 return OK;
7412 static int
7413 load_refs(void)
7415 const char *head_argv[] = {
7416 "git", "symbolic-ref", "HEAD", NULL
7418 static const char *ls_remote_argv[SIZEOF_ARG] = {
7419 "git", "ls-remote", opt_git_dir, NULL
7421 static bool init = FALSE;
7422 size_t i;
7424 if (!init) {
7425 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7426 init = TRUE;
7429 if (!*opt_git_dir)
7430 return OK;
7432 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7433 !prefixcmp(opt_head, "refs/heads/")) {
7434 char *offset = opt_head + STRING_SIZE("refs/heads/");
7436 memmove(opt_head, offset, strlen(offset) + 1);
7439 for (i = 0; i < refs_size; i++)
7440 refs[i]->id[0] = 0;
7442 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7443 return ERR;
7445 /* Update the ref lists to reflect changes. */
7446 for (i = 0; i < ref_lists_size; i++) {
7447 struct ref_list *list = ref_lists[i];
7448 size_t old, new;
7450 for (old = new = 0; old < list->size; old++)
7451 if (!strcmp(list->id, list->refs[old]->id))
7452 list->refs[new++] = list->refs[old];
7453 list->size = new;
7456 return OK;
7459 static void
7460 set_remote_branch(const char *name, const char *value, size_t valuelen)
7462 if (!strcmp(name, ".remote")) {
7463 string_ncopy(opt_remote, value, valuelen);
7465 } else if (*opt_remote && !strcmp(name, ".merge")) {
7466 size_t from = strlen(opt_remote);
7468 if (!prefixcmp(value, "refs/heads/"))
7469 value += STRING_SIZE("refs/heads/");
7471 if (!string_format_from(opt_remote, &from, "/%s", value))
7472 opt_remote[0] = 0;
7476 static void
7477 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7479 const char *argv[SIZEOF_ARG] = { name, "=" };
7480 int argc = 1 + (cmd == option_set_command);
7481 int error = ERR;
7483 if (!argv_from_string(argv, &argc, value))
7484 config_msg = "Too many option arguments";
7485 else
7486 error = cmd(argc, argv);
7488 if (error == ERR)
7489 warn("Option 'tig.%s': %s", name, config_msg);
7492 static bool
7493 set_environment_variable(const char *name, const char *value)
7495 size_t len = strlen(name) + 1 + strlen(value) + 1;
7496 char *env = malloc(len);
7498 if (env &&
7499 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7500 putenv(env) == 0)
7501 return TRUE;
7502 free(env);
7503 return FALSE;
7506 static void
7507 set_work_tree(const char *value)
7509 char cwd[SIZEOF_STR];
7511 if (!getcwd(cwd, sizeof(cwd)))
7512 die("Failed to get cwd path: %s", strerror(errno));
7513 if (chdir(opt_git_dir) < 0)
7514 die("Failed to chdir(%s): %s", strerror(errno));
7515 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7516 die("Failed to get git path: %s", strerror(errno));
7517 if (chdir(cwd) < 0)
7518 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7519 if (chdir(value) < 0)
7520 die("Failed to chdir(%s): %s", value, strerror(errno));
7521 if (!getcwd(cwd, sizeof(cwd)))
7522 die("Failed to get cwd path: %s", strerror(errno));
7523 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7524 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7525 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7526 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7527 opt_is_inside_work_tree = TRUE;
7530 static int
7531 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7533 if (!strcmp(name, "i18n.commitencoding"))
7534 string_ncopy(opt_encoding, value, valuelen);
7536 else if (!strcmp(name, "core.editor"))
7537 string_ncopy(opt_editor, value, valuelen);
7539 else if (!strcmp(name, "core.worktree"))
7540 set_work_tree(value);
7542 else if (!prefixcmp(name, "tig.color."))
7543 set_repo_config_option(name + 10, value, option_color_command);
7545 else if (!prefixcmp(name, "tig.bind."))
7546 set_repo_config_option(name + 9, value, option_bind_command);
7548 else if (!prefixcmp(name, "tig."))
7549 set_repo_config_option(name + 4, value, option_set_command);
7551 else if (*opt_head && !prefixcmp(name, "branch.") &&
7552 !strncmp(name + 7, opt_head, strlen(opt_head)))
7553 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7555 return OK;
7558 static int
7559 load_git_config(void)
7561 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7563 return run_io_load(config_list_argv, "=", read_repo_config_option);
7566 static int
7567 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7569 if (!opt_git_dir[0]) {
7570 string_ncopy(opt_git_dir, name, namelen);
7572 } else if (opt_is_inside_work_tree == -1) {
7573 /* This can be 3 different values depending on the
7574 * version of git being used. If git-rev-parse does not
7575 * understand --is-inside-work-tree it will simply echo
7576 * the option else either "true" or "false" is printed.
7577 * Default to true for the unknown case. */
7578 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7580 } else if (*name == '.') {
7581 string_ncopy(opt_cdup, name, namelen);
7583 } else {
7584 string_ncopy(opt_prefix, name, namelen);
7587 return OK;
7590 static int
7591 load_repo_info(void)
7593 const char *rev_parse_argv[] = {
7594 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7595 "--show-cdup", "--show-prefix", NULL
7598 return run_io_load(rev_parse_argv, "=", read_repo_info);
7603 * Main
7606 static const char usage[] =
7607 "tig " TIG_VERSION " (" __DATE__ ")\n"
7608 "\n"
7609 "Usage: tig [options] [revs] [--] [paths]\n"
7610 " or: tig show [options] [revs] [--] [paths]\n"
7611 " or: tig blame [rev] path\n"
7612 " or: tig status\n"
7613 " or: tig < [git command output]\n"
7614 "\n"
7615 "Options:\n"
7616 " -v, --version Show version and exit\n"
7617 " -h, --help Show help message and exit";
7619 static void __NORETURN
7620 quit(int sig)
7622 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7623 if (cursed)
7624 endwin();
7625 exit(0);
7628 static void __NORETURN
7629 die(const char *err, ...)
7631 va_list args;
7633 endwin();
7635 va_start(args, err);
7636 fputs("tig: ", stderr);
7637 vfprintf(stderr, err, args);
7638 fputs("\n", stderr);
7639 va_end(args);
7641 exit(1);
7644 static void
7645 warn(const char *msg, ...)
7647 va_list args;
7649 va_start(args, msg);
7650 fputs("tig warning: ", stderr);
7651 vfprintf(stderr, msg, args);
7652 fputs("\n", stderr);
7653 va_end(args);
7656 static enum request
7657 parse_options(int argc, const char *argv[])
7659 enum request request = REQ_VIEW_MAIN;
7660 const char *subcommand;
7661 bool seen_dashdash = FALSE;
7662 /* XXX: This is vulnerable to the user overriding options
7663 * required for the main view parser. */
7664 const char *custom_argv[SIZEOF_ARG] = {
7665 "git", "log", "--no-color", "--pretty=raw", "--parents",
7666 "--topo-order", NULL
7668 int i, j = 6;
7670 if (!isatty(STDIN_FILENO)) {
7671 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7672 return REQ_VIEW_PAGER;
7675 if (argc <= 1)
7676 return REQ_NONE;
7678 subcommand = argv[1];
7679 if (!strcmp(subcommand, "status")) {
7680 if (argc > 2)
7681 warn("ignoring arguments after `%s'", subcommand);
7682 return REQ_VIEW_STATUS;
7684 } else if (!strcmp(subcommand, "blame")) {
7685 if (argc <= 2 || argc > 4)
7686 die("invalid number of options to blame\n\n%s", usage);
7688 i = 2;
7689 if (argc == 4) {
7690 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7691 i++;
7694 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7695 return REQ_VIEW_BLAME;
7697 } else if (!strcmp(subcommand, "show")) {
7698 request = REQ_VIEW_DIFF;
7700 } else {
7701 subcommand = NULL;
7704 if (subcommand) {
7705 custom_argv[1] = subcommand;
7706 j = 2;
7709 for (i = 1 + !!subcommand; i < argc; i++) {
7710 const char *opt = argv[i];
7712 if (seen_dashdash || !strcmp(opt, "--")) {
7713 seen_dashdash = TRUE;
7715 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7716 printf("tig version %s\n", TIG_VERSION);
7717 quit(0);
7719 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7720 printf("%s\n", usage);
7721 quit(0);
7724 custom_argv[j++] = opt;
7725 if (j >= ARRAY_SIZE(custom_argv))
7726 die("command too long");
7729 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7730 die("Failed to format arguments");
7732 return request;
7736 main(int argc, const char *argv[])
7738 enum request request = parse_options(argc, argv);
7739 struct view *view;
7740 size_t i;
7742 signal(SIGINT, quit);
7743 signal(SIGPIPE, SIG_IGN);
7745 if (setlocale(LC_ALL, "")) {
7746 char *codeset = nl_langinfo(CODESET);
7748 string_ncopy(opt_codeset, codeset, strlen(codeset));
7751 if (load_repo_info() == ERR)
7752 die("Failed to load repo info.");
7754 if (load_options() == ERR)
7755 die("Failed to load user config.");
7757 if (load_git_config() == ERR)
7758 die("Failed to load repo config.");
7760 /* Require a git repository unless when running in pager mode. */
7761 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7762 die("Not a git repository");
7764 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7765 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7766 if (opt_iconv_in == ICONV_NONE)
7767 die("Failed to initialize character set conversion");
7770 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7771 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7772 if (opt_iconv_out == ICONV_NONE)
7773 die("Failed to initialize character set conversion");
7776 if (load_refs() == ERR)
7777 die("Failed to load refs.");
7779 foreach_view (view, i)
7780 argv_from_env(view->ops->argv, view->cmd_env);
7782 init_display();
7784 if (request != REQ_NONE)
7785 open_view(NULL, request, OPEN_PREPARED);
7786 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7788 while (view_driver(display[current_view], request)) {
7789 int key = get_input(0);
7791 view = display[current_view];
7792 request = get_keybinding(view->keymap, key);
7794 /* Some low-level request handling. This keeps access to
7795 * status_win restricted. */
7796 switch (request) {
7797 case REQ_PROMPT:
7799 char *cmd = read_prompt(":");
7801 if (cmd && isdigit(*cmd)) {
7802 int lineno = view->lineno + 1;
7804 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7805 select_view_line(view, lineno - 1);
7806 report("");
7807 } else {
7808 report("Unable to parse '%s' as a line number", cmd);
7811 } else if (cmd) {
7812 struct view *next = VIEW(REQ_VIEW_PAGER);
7813 const char *argv[SIZEOF_ARG] = { "git" };
7814 int argc = 1;
7816 /* When running random commands, initially show the
7817 * command in the title. However, it maybe later be
7818 * overwritten if a commit line is selected. */
7819 string_ncopy(next->ref, cmd, strlen(cmd));
7821 if (!argv_from_string(argv, &argc, cmd)) {
7822 report("Too many arguments");
7823 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7824 report("Failed to format command");
7825 } else {
7826 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7830 request = REQ_NONE;
7831 break;
7833 case REQ_SEARCH:
7834 case REQ_SEARCH_BACK:
7836 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7837 char *search = read_prompt(prompt);
7839 if (search)
7840 string_ncopy(opt_search, search, strlen(search));
7841 else if (*opt_search)
7842 request = request == REQ_SEARCH ?
7843 REQ_FIND_NEXT :
7844 REQ_FIND_PREV;
7845 else
7846 request = REQ_NONE;
7847 break;
7849 default:
7850 break;
7854 quit(0);
7856 return 0;