Update copyrights
[tig.git] / tig.c
blobe3de8052417d058f13cab2983b938088f8cd8193
1 /* Copyright (c) 2006-2010 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);
73 static inline unsigned char utf8_char_length(const char *string, const char *end);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define MAX(x, y) ((x) > (y) ? (x) : (y))
79 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
80 #define STRING_SIZE(x) (sizeof(x) - 1)
82 #define SIZEOF_STR 1024 /* Default string size. */
83 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
84 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
85 #define SIZEOF_ARG 32 /* Default argument array size. */
87 /* Revision graph */
89 #define REVGRAPH_INIT 'I'
90 #define REVGRAPH_MERGE 'M'
91 #define REVGRAPH_BRANCH '+'
92 #define REVGRAPH_COMMIT '*'
93 #define REVGRAPH_BOUND '^'
95 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
97 /* This color name can be used to refer to the default term colors. */
98 #define COLOR_DEFAULT (-1)
100 #define ICONV_NONE ((iconv_t) -1)
101 #ifndef ICONV_CONST
102 #define ICONV_CONST /* nothing */
103 #endif
105 /* The format and size of the date column in the main view. */
106 #define DATE_FORMAT "%Y-%m-%d %H:%M"
107 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
110 #define ID_COLS 8
111 #define AUTHOR_COLS 19
113 #define MIN_VIEW_HEIGHT 4
115 #define NULL_ID "0000000000000000000000000000000000000000"
117 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
119 /* Some ASCII-shorthands fitted into the ncurses namespace. */
120 #define KEY_TAB '\t'
121 #define KEY_RETURN '\r'
122 #define KEY_ESC 27
125 struct ref {
126 char id[SIZEOF_REV]; /* Commit SHA1 ID */
127 unsigned int head:1; /* Is it the current HEAD? */
128 unsigned int tag:1; /* Is it a tag? */
129 unsigned int ltag:1; /* If so, is the tag local? */
130 unsigned int remote:1; /* Is it a remote ref? */
131 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
132 char name[1]; /* Ref name; tag or head names are shortened. */
135 struct ref_list {
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 size_t size; /* Number of refs. */
138 struct ref **refs; /* References for this ID. */
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166 int hotkey;
167 const char *text;
168 void *data;
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 static type * \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 type *tmp = *mem; \
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187 if (tmp) \
188 *mem = tmp; \
191 return tmp; \
195 * String helpers
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201 if (srclen > dstlen - 1)
202 srclen = dstlen - 1;
204 strncpy(dst, src, srclen);
205 dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225 size_t size, pos;
227 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228 if (src[pos] == '\t') {
229 size_t expanded = tabsize - (size % tabsize);
231 if (expanded + size >= dstlen - 1)
232 expanded = dstlen - size - 1;
233 memcpy(dst + size, " ", expanded);
234 size += expanded;
235 } else {
236 dst[size++] = src[pos];
240 dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246 int namelen;
248 while (isspace(*name))
249 name++;
251 namelen = strlen(name) - 1;
252 while (namelen > 0 && isspace(name[namelen]))
253 name[namelen--] = 0;
255 return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261 va_list args;
262 size_t pos = bufpos ? *bufpos : 0;
264 va_start(args, fmt);
265 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266 va_end(args);
268 if (bufpos)
269 *bufpos = pos;
271 return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283 size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i = 0; i < len; i++) {
289 if (toupper(str1[i]) == toupper(str2[i]))
290 continue;
292 if (string_enum_sep(str1[i]) &&
293 string_enum_sep(str2[i]))
294 continue;
296 return str1[i] - str2[i];
299 return 0;
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306 const char *name;
307 int namelen;
308 int value;
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316 static char buf[SIZEOF_STR];
317 int bufpos;
319 for (bufpos = 0; bufpos <= namelen; bufpos++) {
320 buf[bufpos] = tolower(name[bufpos]);
321 if (buf[bufpos] == '_')
322 buf[bufpos] = '-';
325 buf[bufpos] = 0;
326 return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334 size_t namelen = strlen(name);
335 int i;
337 for (i = 0; i < map_size; i++)
338 if (enum_equals(map[i], name, namelen)) {
339 *value = map[i].value;
340 return TRUE;
343 return FALSE;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355 size_t len = slen >= 0 ? slen : strlen(str);
356 size_t suffixlen = strlen(suffix);
358 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363 DATE_(NO), \
364 DATE_(DEFAULT), \
365 DATE_(RELATIVE), \
366 DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370 DATE_INFO
371 #undef DATE_
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376 DATE_INFO
377 #undef DATE_
380 struct time {
381 time_t sec;
382 int tz;
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387 return t1->sec - t2->sec;
390 static const char *
391 string_date(const struct time *time, enum date date)
393 static char buf[DATE_COLS + 1];
394 static const struct enum_map reldate[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
402 struct tm tm;
404 if (date == DATE_RELATIVE) {
405 struct timeval now;
406 time_t date = time->sec + time->tz;
407 time_t seconds;
408 int i;
410 gettimeofday(&now, NULL);
411 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
412 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
413 if (seconds >= reldate[i].value)
414 continue;
416 seconds /= reldate[i].namelen;
417 if (!string_format(buf, "%ld %s%s %s",
418 seconds, reldate[i].name,
419 seconds > 1 ? "s" : "",
420 now.tv_sec >= date ? "ago" : "ahead"))
421 break;
422 return buf;
426 gmtime_r(&time->sec, &tm);
427 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
431 #define AUTHOR_VALUES \
432 AUTHOR_(NO), \
433 AUTHOR_(FULL), \
434 AUTHOR_(ABBREVIATED)
436 enum author {
437 #define AUTHOR_(name) AUTHOR_##name
438 AUTHOR_VALUES,
439 #undef AUTHOR_
440 AUTHOR_DEFAULT = AUTHOR_FULL
443 static const struct enum_map author_map[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
445 AUTHOR_VALUES
446 #undef AUTHOR_
449 static const char *
450 get_author_initials(const char *author)
452 static char initials[AUTHOR_COLS * 6 + 1];
453 size_t pos = 0;
454 const char *end = strchr(author, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458 memset(initials, 0, sizeof(initials));
459 while (author < end) {
460 unsigned char bytes;
461 size_t i;
463 while (is_initial_sep(*author))
464 author++;
466 bytes = utf8_char_length(author, end);
467 if (bytes < sizeof(initials) - 1 - pos) {
468 while (bytes--) {
469 initials[pos++] = *author++;
473 for (i = pos; author < end && !is_initial_sep(*author); author++) {
474 if (i < sizeof(initials) - 1)
475 initials[i++] = *author;
478 initials[i++] = 0;
481 return initials;
485 static bool
486 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
488 int valuelen;
490 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
491 bool advance = cmd[valuelen] != 0;
493 cmd[valuelen] = 0;
494 argv[(*argc)++] = chomp_string(cmd);
495 cmd = chomp_string(cmd + valuelen + advance);
498 if (*argc < SIZEOF_ARG)
499 argv[*argc] = NULL;
500 return *argc < SIZEOF_ARG;
503 static void
504 argv_from_env(const char **argv, const char *name)
506 char *env = argv ? getenv(name) : NULL;
507 int argc = 0;
509 if (env && *env)
510 env = strdup(env);
511 if (env && !argv_from_string(argv, &argc, env))
512 die("Too many arguments in the `%s` environment variable", name);
517 * Executing external commands.
520 enum io_type {
521 IO_FD, /* File descriptor based IO. */
522 IO_BG, /* Execute command in the background. */
523 IO_FG, /* Execute command with same std{in,out,err}. */
524 IO_RD, /* Read only fork+exec IO. */
525 IO_WR, /* Write only fork+exec IO. */
526 IO_AP, /* Append fork+exec output to file. */
529 struct io {
530 enum io_type type; /* The requested type of pipe. */
531 const char *dir; /* Directory from which to execute. */
532 pid_t pid; /* Pipe for reading or writing. */
533 int pipe; /* Pipe end for reading or writing. */
534 int error; /* Error status. */
535 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
536 char *buf; /* Read buffer. */
537 size_t bufalloc; /* Allocated buffer size. */
538 size_t bufsize; /* Buffer content size. */
539 char *bufpos; /* Current buffer position. */
540 unsigned int eof:1; /* Has end of file been reached. */
543 static void
544 reset_io(struct io *io)
546 io->pipe = -1;
547 io->pid = 0;
548 io->buf = io->bufpos = NULL;
549 io->bufalloc = io->bufsize = 0;
550 io->error = 0;
551 io->eof = 0;
554 static void
555 init_io(struct io *io, const char *dir, enum io_type type)
557 reset_io(io);
558 io->type = type;
559 io->dir = dir;
562 static bool
563 init_io_rd(struct io *io, const char *argv[], const char *dir,
564 enum format_flags flags)
566 init_io(io, dir, IO_RD);
567 return format_argv(io->argv, argv, flags);
570 static bool
571 io_open(struct io *io, const char *fmt, ...)
573 char name[SIZEOF_STR] = "";
574 bool fits;
575 va_list args;
577 init_io(io, NULL, IO_FD);
579 va_start(args, fmt);
580 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
581 va_end(args);
583 if (!fits) {
584 io->error = ENAMETOOLONG;
585 return FALSE;
587 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
588 if (io->pipe == -1)
589 io->error = errno;
590 return io->pipe != -1;
593 static bool
594 kill_io(struct io *io)
596 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
599 static bool
600 done_io(struct io *io)
602 pid_t pid = io->pid;
604 if (io->pipe != -1)
605 close(io->pipe);
606 free(io->buf);
607 reset_io(io);
609 while (pid > 0) {
610 int status;
611 pid_t waiting = waitpid(pid, &status, 0);
613 if (waiting < 0) {
614 if (errno == EINTR)
615 continue;
616 report("waitpid failed (%s)", strerror(errno));
617 return FALSE;
620 return waiting == pid &&
621 !WIFSIGNALED(status) &&
622 WIFEXITED(status) &&
623 !WEXITSTATUS(status);
626 return TRUE;
629 static bool
630 start_io(struct io *io)
632 int pipefds[2] = { -1, -1 };
634 if (io->type == IO_FD)
635 return TRUE;
637 if ((io->type == IO_RD || io->type == IO_WR) &&
638 pipe(pipefds) < 0)
639 return FALSE;
640 else if (io->type == IO_AP)
641 pipefds[1] = io->pipe;
643 if ((io->pid = fork())) {
644 if (pipefds[!(io->type == IO_WR)] != -1)
645 close(pipefds[!(io->type == IO_WR)]);
646 if (io->pid != -1) {
647 io->pipe = pipefds[!!(io->type == IO_WR)];
648 return TRUE;
651 } else {
652 if (io->type != IO_FG) {
653 int devnull = open("/dev/null", O_RDWR);
654 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
655 int writefd = (io->type == IO_RD || io->type == IO_AP)
656 ? pipefds[1] : devnull;
658 dup2(readfd, STDIN_FILENO);
659 dup2(writefd, STDOUT_FILENO);
660 dup2(devnull, STDERR_FILENO);
662 close(devnull);
663 if (pipefds[0] != -1)
664 close(pipefds[0]);
665 if (pipefds[1] != -1)
666 close(pipefds[1]);
669 if (io->dir && *io->dir && chdir(io->dir) == -1)
670 die("Failed to change directory: %s", strerror(errno));
672 execvp(io->argv[0], (char *const*) io->argv);
673 die("Failed to execute program: %s", strerror(errno));
676 if (pipefds[!!(io->type == IO_WR)] != -1)
677 close(pipefds[!!(io->type == IO_WR)]);
678 return FALSE;
681 static bool
682 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
684 init_io(io, dir, type);
685 if (!format_argv(io->argv, argv, FORMAT_NONE))
686 return FALSE;
687 return start_io(io);
690 static int
691 run_io_do(struct io *io)
693 return start_io(io) && done_io(io);
696 static int
697 run_io_bg(const char **argv)
699 struct io io = {};
701 init_io(&io, NULL, IO_BG);
702 if (!format_argv(io.argv, argv, FORMAT_NONE))
703 return FALSE;
704 return run_io_do(&io);
707 static bool
708 run_io_fg(const char **argv, const char *dir)
710 struct io io = {};
712 init_io(&io, dir, IO_FG);
713 if (!format_argv(io.argv, argv, FORMAT_NONE))
714 return FALSE;
715 return run_io_do(&io);
718 static bool
719 run_io_append(const char **argv, enum format_flags flags, int fd)
721 struct io io = {};
723 init_io(&io, NULL, IO_AP);
724 io.pipe = fd;
725 if (format_argv(io.argv, argv, flags))
726 return run_io_do(&io);
727 close(fd);
728 return FALSE;
731 static bool
732 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
734 return init_io_rd(io, argv, dir, flags) && start_io(io);
737 static bool
738 io_eof(struct io *io)
740 return io->eof;
743 static int
744 io_error(struct io *io)
746 return io->error;
749 static char *
750 io_strerror(struct io *io)
752 return strerror(io->error);
755 static bool
756 io_can_read(struct io *io)
758 struct timeval tv = { 0, 500 };
759 fd_set fds;
761 FD_ZERO(&fds);
762 FD_SET(io->pipe, &fds);
764 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
767 static ssize_t
768 io_read(struct io *io, void *buf, size_t bufsize)
770 do {
771 ssize_t readsize = read(io->pipe, buf, bufsize);
773 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
774 continue;
775 else if (readsize == -1)
776 io->error = errno;
777 else if (readsize == 0)
778 io->eof = 1;
779 return readsize;
780 } while (1);
783 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
785 static char *
786 io_get(struct io *io, int c, bool can_read)
788 char *eol;
789 ssize_t readsize;
791 while (TRUE) {
792 if (io->bufsize > 0) {
793 eol = memchr(io->bufpos, c, io->bufsize);
794 if (eol) {
795 char *line = io->bufpos;
797 *eol = 0;
798 io->bufpos = eol + 1;
799 io->bufsize -= io->bufpos - line;
800 return line;
804 if (io_eof(io)) {
805 if (io->bufsize) {
806 io->bufpos[io->bufsize] = 0;
807 io->bufsize = 0;
808 return io->bufpos;
810 return NULL;
813 if (!can_read)
814 return NULL;
816 if (io->bufsize > 0 && io->bufpos > io->buf)
817 memmove(io->buf, io->bufpos, io->bufsize);
819 if (io->bufalloc == io->bufsize) {
820 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
821 return NULL;
822 io->bufalloc += BUFSIZ;
825 io->bufpos = io->buf;
826 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
827 if (io_error(io))
828 return NULL;
829 io->bufsize += readsize;
833 static bool
834 io_write(struct io *io, const void *buf, size_t bufsize)
836 size_t written = 0;
838 while (!io_error(io) && written < bufsize) {
839 ssize_t size;
841 size = write(io->pipe, buf + written, bufsize - written);
842 if (size < 0 && (errno == EAGAIN || errno == EINTR))
843 continue;
844 else if (size == -1)
845 io->error = errno;
846 else
847 written += size;
850 return written == bufsize;
853 static bool
854 io_read_buf(struct io *io, char buf[], size_t bufsize)
856 char *result = io_get(io, '\n', TRUE);
858 if (result) {
859 result = chomp_string(result);
860 string_ncopy_do(buf, bufsize, result, strlen(result));
863 return done_io(io) && result;
866 static bool
867 run_io_buf(const char **argv, char buf[], size_t bufsize)
869 struct io io = {};
871 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
872 && io_read_buf(&io, buf, bufsize);
875 static int
876 io_load(struct io *io, const char *separators,
877 int (*read_property)(char *, size_t, char *, size_t))
879 char *name;
880 int state = OK;
882 if (!start_io(io))
883 return ERR;
885 while (state == OK && (name = io_get(io, '\n', TRUE))) {
886 char *value;
887 size_t namelen;
888 size_t valuelen;
890 name = chomp_string(name);
891 namelen = strcspn(name, separators);
893 if (name[namelen]) {
894 name[namelen] = 0;
895 value = chomp_string(name + namelen + 1);
896 valuelen = strlen(value);
898 } else {
899 value = "";
900 valuelen = 0;
903 state = read_property(name, namelen, value, valuelen);
906 if (state != ERR && io_error(io))
907 state = ERR;
908 done_io(io);
910 return state;
913 static int
914 run_io_load(const char **argv, const char *separators,
915 int (*read_property)(char *, size_t, char *, size_t))
917 struct io io = {};
919 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
920 ? io_load(&io, separators, read_property) : ERR;
925 * User requests
928 #define REQ_INFO \
929 /* XXX: Keep the view request first and in sync with views[]. */ \
930 REQ_GROUP("View switching") \
931 REQ_(VIEW_MAIN, "Show main view"), \
932 REQ_(VIEW_DIFF, "Show diff view"), \
933 REQ_(VIEW_LOG, "Show log view"), \
934 REQ_(VIEW_TREE, "Show tree view"), \
935 REQ_(VIEW_BLOB, "Show blob view"), \
936 REQ_(VIEW_BLAME, "Show blame view"), \
937 REQ_(VIEW_BRANCH, "Show branch view"), \
938 REQ_(VIEW_HELP, "Show help page"), \
939 REQ_(VIEW_PAGER, "Show pager view"), \
940 REQ_(VIEW_STATUS, "Show status view"), \
941 REQ_(VIEW_STAGE, "Show stage view"), \
943 REQ_GROUP("View manipulation") \
944 REQ_(ENTER, "Enter current line and scroll"), \
945 REQ_(NEXT, "Move to next"), \
946 REQ_(PREVIOUS, "Move to previous"), \
947 REQ_(PARENT, "Move to parent"), \
948 REQ_(VIEW_NEXT, "Move focus to next view"), \
949 REQ_(REFRESH, "Reload and refresh"), \
950 REQ_(MAXIMIZE, "Maximize the current view"), \
951 REQ_(VIEW_CLOSE, "Close the current view"), \
952 REQ_(QUIT, "Close all views and quit"), \
954 REQ_GROUP("View specific requests") \
955 REQ_(STATUS_UPDATE, "Update file status"), \
956 REQ_(STATUS_REVERT, "Revert file changes"), \
957 REQ_(STATUS_MERGE, "Merge file using external tool"), \
958 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
960 REQ_GROUP("Cursor navigation") \
961 REQ_(MOVE_UP, "Move cursor one line up"), \
962 REQ_(MOVE_DOWN, "Move cursor one line down"), \
963 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
964 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
965 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
966 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
968 REQ_GROUP("Scrolling") \
969 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
970 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
971 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
972 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
973 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
974 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
976 REQ_GROUP("Searching") \
977 REQ_(SEARCH, "Search the view"), \
978 REQ_(SEARCH_BACK, "Search backwards in the view"), \
979 REQ_(FIND_NEXT, "Find next search match"), \
980 REQ_(FIND_PREV, "Find previous search match"), \
982 REQ_GROUP("Option manipulation") \
983 REQ_(OPTIONS, "Open option menu"), \
984 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
985 REQ_(TOGGLE_DATE, "Toggle date display"), \
986 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
988 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
989 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
990 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
993 REQ_GROUP("Misc") \
994 REQ_(PROMPT, "Bring up the prompt"), \
995 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
996 REQ_(SHOW_VERSION, "Show version information"), \
997 REQ_(STOP_LOADING, "Stop all loading views"), \
998 REQ_(EDIT, "Open in editor"), \
999 REQ_(NONE, "Do nothing")
1002 /* User action requests. */
1003 enum request {
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007 /* Offset all requests to avoid conflicts with ncurses getch values. */
1008 REQ_OFFSET = KEY_MAX + 1,
1009 REQ_INFO
1011 #undef REQ_GROUP
1012 #undef REQ_
1015 struct request_info {
1016 enum request request;
1017 const char *name;
1018 int namelen;
1019 const char *help;
1022 static const struct request_info req_info[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1025 REQ_INFO
1026 #undef REQ_GROUP
1027 #undef REQ_
1030 static enum request
1031 get_request(const char *name)
1033 int namelen = strlen(name);
1034 int i;
1036 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1037 if (enum_equals(req_info[i], name, namelen))
1038 return req_info[i].request;
1040 return REQ_NONE;
1045 * Options
1048 /* Option and state variables. */
1049 static enum date opt_date = DATE_DEFAULT;
1050 static enum author opt_author = AUTHOR_DEFAULT;
1051 static bool opt_line_number = FALSE;
1052 static bool opt_line_graphics = TRUE;
1053 static bool opt_rev_graph = FALSE;
1054 static bool opt_show_refs = TRUE;
1055 static int opt_num_interval = 5;
1056 static double opt_hscroll = 0.50;
1057 static double opt_scale_split_view = 2.0 / 3.0;
1058 static int opt_tab_size = 8;
1059 static int opt_author_cols = AUTHOR_COLS;
1060 static char opt_path[SIZEOF_STR] = "";
1061 static char opt_file[SIZEOF_STR] = "";
1062 static char opt_ref[SIZEOF_REF] = "";
1063 static char opt_head[SIZEOF_REF] = "";
1064 static char opt_head_rev[SIZEOF_REV] = "";
1065 static char opt_remote[SIZEOF_REF] = "";
1066 static char opt_encoding[20] = "UTF-8";
1067 static char opt_codeset[20] = "UTF-8";
1068 static iconv_t opt_iconv_in = ICONV_NONE;
1069 static iconv_t opt_iconv_out = ICONV_NONE;
1070 static char opt_search[SIZEOF_STR] = "";
1071 static char opt_cdup[SIZEOF_STR] = "";
1072 static char opt_prefix[SIZEOF_STR] = "";
1073 static char opt_git_dir[SIZEOF_STR] = "";
1074 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1075 static char opt_editor[SIZEOF_STR] = "";
1076 static FILE *opt_tty = NULL;
1078 #define is_initial_commit() (!*opt_head_rev)
1079 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1080 #define mkdate(time) string_date(time, opt_date)
1084 * Line-oriented content detection.
1087 #define LINE_INFO \
1088 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1102 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1104 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1109 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1110 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1111 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1114 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1117 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1118 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1119 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1122 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1124 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1125 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1133 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1134 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1135 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1137 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1139 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1143 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1144 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1146 enum line_type {
1147 #define LINE(type, line, fg, bg, attr) \
1148 LINE_##type
1149 LINE_INFO,
1150 LINE_NONE
1151 #undef LINE
1154 struct line_info {
1155 const char *name; /* Option name. */
1156 int namelen; /* Size of option name. */
1157 const char *line; /* The start of line to match. */
1158 int linelen; /* Size of string to match. */
1159 int fg, bg, attr; /* Color and text attributes for the lines. */
1162 static struct line_info line_info[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1165 LINE_INFO
1166 #undef LINE
1169 static enum line_type
1170 get_line_type(const char *line)
1172 int linelen = strlen(line);
1173 enum line_type type;
1175 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1176 /* Case insensitive search matches Signed-off-by lines better. */
1177 if (linelen >= line_info[type].linelen &&
1178 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1179 return type;
1181 return LINE_DEFAULT;
1184 static inline int
1185 get_line_attr(enum line_type type)
1187 assert(type < ARRAY_SIZE(line_info));
1188 return COLOR_PAIR(type) | line_info[type].attr;
1191 static struct line_info *
1192 get_line_info(const char *name)
1194 size_t namelen = strlen(name);
1195 enum line_type type;
1197 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1198 if (enum_equals(line_info[type], name, namelen))
1199 return &line_info[type];
1201 return NULL;
1204 static void
1205 init_colors(void)
1207 int default_bg = line_info[LINE_DEFAULT].bg;
1208 int default_fg = line_info[LINE_DEFAULT].fg;
1209 enum line_type type;
1211 start_color();
1213 if (assume_default_colors(default_fg, default_bg) == ERR) {
1214 default_bg = COLOR_BLACK;
1215 default_fg = COLOR_WHITE;
1218 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1219 struct line_info *info = &line_info[type];
1220 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1221 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1223 init_pair(type, fg, bg);
1227 struct line {
1228 enum line_type type;
1230 /* State flags */
1231 unsigned int selected:1;
1232 unsigned int dirty:1;
1233 unsigned int cleareol:1;
1234 unsigned int other:16;
1236 void *data; /* User data */
1241 * Keys
1244 struct keybinding {
1245 int alias;
1246 enum request request;
1249 static const struct keybinding default_keybindings[] = {
1250 /* View switching */
1251 { 'm', REQ_VIEW_MAIN },
1252 { 'd', REQ_VIEW_DIFF },
1253 { 'l', REQ_VIEW_LOG },
1254 { 't', REQ_VIEW_TREE },
1255 { 'f', REQ_VIEW_BLOB },
1256 { 'B', REQ_VIEW_BLAME },
1257 { 'H', REQ_VIEW_BRANCH },
1258 { 'p', REQ_VIEW_PAGER },
1259 { 'h', REQ_VIEW_HELP },
1260 { 'S', REQ_VIEW_STATUS },
1261 { 'c', REQ_VIEW_STAGE },
1263 /* View manipulation */
1264 { 'q', REQ_VIEW_CLOSE },
1265 { KEY_TAB, REQ_VIEW_NEXT },
1266 { KEY_RETURN, REQ_ENTER },
1267 { KEY_UP, REQ_PREVIOUS },
1268 { KEY_DOWN, REQ_NEXT },
1269 { 'R', REQ_REFRESH },
1270 { KEY_F(5), REQ_REFRESH },
1271 { 'O', REQ_MAXIMIZE },
1273 /* Cursor navigation */
1274 { 'k', REQ_MOVE_UP },
1275 { 'j', REQ_MOVE_DOWN },
1276 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1277 { KEY_END, REQ_MOVE_LAST_LINE },
1278 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1279 { ' ', REQ_MOVE_PAGE_DOWN },
1280 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1281 { 'b', REQ_MOVE_PAGE_UP },
1282 { '-', REQ_MOVE_PAGE_UP },
1284 /* Scrolling */
1285 { KEY_LEFT, REQ_SCROLL_LEFT },
1286 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1287 { KEY_IC, REQ_SCROLL_LINE_UP },
1288 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1289 { 'w', REQ_SCROLL_PAGE_UP },
1290 { 's', REQ_SCROLL_PAGE_DOWN },
1292 /* Searching */
1293 { '/', REQ_SEARCH },
1294 { '?', REQ_SEARCH_BACK },
1295 { 'n', REQ_FIND_NEXT },
1296 { 'N', REQ_FIND_PREV },
1298 /* Misc */
1299 { 'Q', REQ_QUIT },
1300 { 'z', REQ_STOP_LOADING },
1301 { 'v', REQ_SHOW_VERSION },
1302 { 'r', REQ_SCREEN_REDRAW },
1303 { 'o', REQ_OPTIONS },
1304 { '.', REQ_TOGGLE_LINENO },
1305 { 'D', REQ_TOGGLE_DATE },
1306 { 'A', REQ_TOGGLE_AUTHOR },
1307 { 'g', REQ_TOGGLE_REV_GRAPH },
1308 { 'F', REQ_TOGGLE_REFS },
1309 { 'I', REQ_TOGGLE_SORT_ORDER },
1310 { 'i', REQ_TOGGLE_SORT_FIELD },
1311 { ':', REQ_PROMPT },
1312 { 'u', REQ_STATUS_UPDATE },
1313 { '!', REQ_STATUS_REVERT },
1314 { 'M', REQ_STATUS_MERGE },
1315 { '@', REQ_STAGE_NEXT },
1316 { ',', REQ_PARENT },
1317 { 'e', REQ_EDIT },
1320 #define KEYMAP_INFO \
1321 KEYMAP_(GENERIC), \
1322 KEYMAP_(MAIN), \
1323 KEYMAP_(DIFF), \
1324 KEYMAP_(LOG), \
1325 KEYMAP_(TREE), \
1326 KEYMAP_(BLOB), \
1327 KEYMAP_(BLAME), \
1328 KEYMAP_(BRANCH), \
1329 KEYMAP_(PAGER), \
1330 KEYMAP_(HELP), \
1331 KEYMAP_(STATUS), \
1332 KEYMAP_(STAGE)
1334 enum keymap {
1335 #define KEYMAP_(name) KEYMAP_##name
1336 KEYMAP_INFO
1337 #undef KEYMAP_
1340 static const struct enum_map keymap_table[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1342 KEYMAP_INFO
1343 #undef KEYMAP_
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table {
1349 struct keybinding *data;
1350 size_t size;
1353 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1355 static void
1356 add_keybinding(enum keymap keymap, enum request request, int key)
1358 struct keybinding_table *table = &keybindings[keymap];
1360 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1361 if (!table->data)
1362 die("Failed to allocate keybinding");
1363 table->data[table->size].alias = key;
1364 table->data[table->size++].request = request;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368 * lastly in the default keybindings. */
1369 static enum request
1370 get_keybinding(enum keymap keymap, int key)
1372 size_t i;
1374 for (i = 0; i < keybindings[keymap].size; i++)
1375 if (keybindings[keymap].data[i].alias == key)
1376 return keybindings[keymap].data[i].request;
1378 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1379 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1380 return keybindings[KEYMAP_GENERIC].data[i].request;
1382 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1383 if (default_keybindings[i].alias == key)
1384 return default_keybindings[i].request;
1386 return (enum request) key;
1390 struct key {
1391 const char *name;
1392 int value;
1395 static const struct key key_table[] = {
1396 { "Enter", KEY_RETURN },
1397 { "Space", ' ' },
1398 { "Backspace", KEY_BACKSPACE },
1399 { "Tab", KEY_TAB },
1400 { "Escape", KEY_ESC },
1401 { "Left", KEY_LEFT },
1402 { "Right", KEY_RIGHT },
1403 { "Up", KEY_UP },
1404 { "Down", KEY_DOWN },
1405 { "Insert", KEY_IC },
1406 { "Delete", KEY_DC },
1407 { "Hash", '#' },
1408 { "Home", KEY_HOME },
1409 { "End", KEY_END },
1410 { "PageUp", KEY_PPAGE },
1411 { "PageDown", KEY_NPAGE },
1412 { "F1", KEY_F(1) },
1413 { "F2", KEY_F(2) },
1414 { "F3", KEY_F(3) },
1415 { "F4", KEY_F(4) },
1416 { "F5", KEY_F(5) },
1417 { "F6", KEY_F(6) },
1418 { "F7", KEY_F(7) },
1419 { "F8", KEY_F(8) },
1420 { "F9", KEY_F(9) },
1421 { "F10", KEY_F(10) },
1422 { "F11", KEY_F(11) },
1423 { "F12", KEY_F(12) },
1426 static int
1427 get_key_value(const char *name)
1429 int i;
1431 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1432 if (!strcasecmp(key_table[i].name, name))
1433 return key_table[i].value;
1435 if (strlen(name) == 1 && isprint(*name))
1436 return (int) *name;
1438 return ERR;
1441 static const char *
1442 get_key_name(int key_value)
1444 static char key_char[] = "'X'";
1445 const char *seq = NULL;
1446 int key;
1448 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1449 if (key_table[key].value == key_value)
1450 seq = key_table[key].name;
1452 if (seq == NULL &&
1453 key_value < 127 &&
1454 isprint(key_value)) {
1455 key_char[1] = (char) key_value;
1456 seq = key_char;
1459 return seq ? seq : "(no key)";
1462 static bool
1463 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1465 const char *sep = *pos > 0 ? ", " : "";
1466 const char *keyname = get_key_name(keybinding->alias);
1468 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1471 static bool
1472 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1473 enum keymap keymap, bool all)
1475 int i;
1477 for (i = 0; i < keybindings[keymap].size; i++) {
1478 if (keybindings[keymap].data[i].request == request) {
1479 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1480 return FALSE;
1481 if (!all)
1482 break;
1486 return TRUE;
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1491 static const char *
1492 get_keys(enum keymap keymap, enum request request, bool all)
1494 static char buf[BUFSIZ];
1495 size_t pos = 0;
1496 int i;
1498 buf[pos] = 0;
1500 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1501 return "Too many keybindings!";
1502 if (pos > 0 && !all)
1503 return buf;
1505 if (keymap != KEYMAP_GENERIC) {
1506 /* Only the generic keymap includes the default keybindings when
1507 * listing all keys. */
1508 if (all)
1509 return buf;
1511 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1512 return "Too many keybindings!";
1513 if (pos)
1514 return buf;
1517 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1518 if (default_keybindings[i].request == request) {
1519 if (!append_key(buf, &pos, &default_keybindings[i]))
1520 return "Too many keybindings!";
1521 if (!all)
1522 return buf;
1526 return buf;
1529 struct run_request {
1530 enum keymap keymap;
1531 int key;
1532 const char *argv[SIZEOF_ARG];
1535 static struct run_request *run_request;
1536 static size_t run_requests;
1538 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1540 static enum request
1541 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1543 struct run_request *req;
1545 if (argc >= ARRAY_SIZE(req->argv) - 1)
1546 return REQ_NONE;
1548 if (!realloc_run_requests(&run_request, run_requests, 1))
1549 return REQ_NONE;
1551 req = &run_request[run_requests];
1552 req->keymap = keymap;
1553 req->key = key;
1554 req->argv[0] = NULL;
1556 if (!format_argv(req->argv, argv, FORMAT_NONE))
1557 return REQ_NONE;
1559 return REQ_NONE + ++run_requests;
1562 static struct run_request *
1563 get_run_request(enum request request)
1565 if (request <= REQ_NONE)
1566 return NULL;
1567 return &run_request[request - REQ_NONE - 1];
1570 static void
1571 add_builtin_run_requests(void)
1573 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1574 const char *commit[] = { "git", "commit", NULL };
1575 const char *gc[] = { "git", "gc", NULL };
1576 struct {
1577 enum keymap keymap;
1578 int key;
1579 int argc;
1580 const char **argv;
1581 } reqs[] = {
1582 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1583 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1584 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1586 int i;
1588 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1589 enum request req;
1591 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1592 if (req != REQ_NONE)
1593 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1598 * User config file handling.
1601 static int config_lineno;
1602 static bool config_errors;
1603 static const char *config_msg;
1605 static const struct enum_map color_map[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1607 COLOR_MAP(DEFAULT),
1608 COLOR_MAP(BLACK),
1609 COLOR_MAP(BLUE),
1610 COLOR_MAP(CYAN),
1611 COLOR_MAP(GREEN),
1612 COLOR_MAP(MAGENTA),
1613 COLOR_MAP(RED),
1614 COLOR_MAP(WHITE),
1615 COLOR_MAP(YELLOW),
1618 static const struct enum_map attr_map[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1620 ATTR_MAP(NORMAL),
1621 ATTR_MAP(BLINK),
1622 ATTR_MAP(BOLD),
1623 ATTR_MAP(DIM),
1624 ATTR_MAP(REVERSE),
1625 ATTR_MAP(STANDOUT),
1626 ATTR_MAP(UNDERLINE),
1629 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt, const char *arg)
1633 *opt = atoi(arg);
1634 if (!strchr(arg, '%'))
1635 return OK;
1637 /* "Shift down" so 100% and 1 does not conflict. */
1638 *opt = (*opt - 1) / 100;
1639 if (*opt >= 1.0) {
1640 *opt = 0.99;
1641 config_msg = "Step value larger than 100%";
1642 return ERR;
1644 if (*opt < 0.0) {
1645 *opt = 1;
1646 config_msg = "Invalid step value";
1647 return ERR;
1649 return OK;
1652 static int
1653 parse_int(int *opt, const char *arg, int min, int max)
1655 int value = atoi(arg);
1657 if (min <= value && value <= max) {
1658 *opt = value;
1659 return OK;
1662 config_msg = "Integer value out of bound";
1663 return ERR;
1666 static bool
1667 set_color(int *color, const char *name)
1669 if (map_enum(color, color_map, name))
1670 return TRUE;
1671 if (!prefixcmp(name, "color"))
1672 return parse_int(color, name + 5, 0, 255) == OK;
1673 return FALSE;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1677 static int
1678 option_color_command(int argc, const char *argv[])
1680 struct line_info *info;
1682 if (argc < 3) {
1683 config_msg = "Wrong number of arguments given to color command";
1684 return ERR;
1687 info = get_line_info(argv[0]);
1688 if (!info) {
1689 static const struct enum_map obsolete[] = {
1690 ENUM_MAP("main-delim", LINE_DELIMITER),
1691 ENUM_MAP("main-date", LINE_DATE),
1692 ENUM_MAP("main-author", LINE_AUTHOR),
1694 int index;
1696 if (!map_enum(&index, obsolete, argv[0])) {
1697 config_msg = "Unknown color name";
1698 return ERR;
1700 info = &line_info[index];
1703 if (!set_color(&info->fg, argv[1]) ||
1704 !set_color(&info->bg, argv[2])) {
1705 config_msg = "Unknown color";
1706 return ERR;
1709 info->attr = 0;
1710 while (argc-- > 3) {
1711 int attr;
1713 if (!set_attribute(&attr, argv[argc])) {
1714 config_msg = "Unknown attribute";
1715 return ERR;
1717 info->attr |= attr;
1720 return OK;
1723 static int parse_bool(bool *opt, const char *arg)
1725 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1726 ? TRUE : FALSE;
1727 return OK;
1730 static int parse_enum_do(unsigned int *opt, const char *arg,
1731 const struct enum_map *map, size_t map_size)
1733 bool is_true;
1735 assert(map_size > 1);
1737 if (map_enum_do(map, map_size, (int *) opt, arg))
1738 return OK;
1740 if (parse_bool(&is_true, arg) != OK)
1741 return ERR;
1743 *opt = is_true ? map[1].value : map[0].value;
1744 return OK;
1747 #define parse_enum(opt, arg, map) \
1748 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1750 static int
1751 parse_string(char *opt, const char *arg, size_t optsize)
1753 int arglen = strlen(arg);
1755 switch (arg[0]) {
1756 case '\"':
1757 case '\'':
1758 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1759 config_msg = "Unmatched quotation";
1760 return ERR;
1762 arg += 1; arglen -= 2;
1763 default:
1764 string_ncopy_do(opt, optsize, arg, arglen);
1765 return OK;
1769 /* Wants: name = value */
1770 static int
1771 option_set_command(int argc, const char *argv[])
1773 if (argc != 3) {
1774 config_msg = "Wrong number of arguments given to set command";
1775 return ERR;
1778 if (strcmp(argv[1], "=")) {
1779 config_msg = "No value assigned";
1780 return ERR;
1783 if (!strcmp(argv[0], "show-author"))
1784 return parse_enum(&opt_author, argv[2], author_map);
1786 if (!strcmp(argv[0], "show-date"))
1787 return parse_enum(&opt_date, argv[2], date_map);
1789 if (!strcmp(argv[0], "show-rev-graph"))
1790 return parse_bool(&opt_rev_graph, argv[2]);
1792 if (!strcmp(argv[0], "show-refs"))
1793 return parse_bool(&opt_show_refs, argv[2]);
1795 if (!strcmp(argv[0], "show-line-numbers"))
1796 return parse_bool(&opt_line_number, argv[2]);
1798 if (!strcmp(argv[0], "line-graphics"))
1799 return parse_bool(&opt_line_graphics, argv[2]);
1801 if (!strcmp(argv[0], "line-number-interval"))
1802 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1804 if (!strcmp(argv[0], "author-width"))
1805 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1807 if (!strcmp(argv[0], "horizontal-scroll"))
1808 return parse_step(&opt_hscroll, argv[2]);
1810 if (!strcmp(argv[0], "split-view-height"))
1811 return parse_step(&opt_scale_split_view, argv[2]);
1813 if (!strcmp(argv[0], "tab-size"))
1814 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1816 if (!strcmp(argv[0], "commit-encoding"))
1817 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1819 config_msg = "Unknown variable name";
1820 return ERR;
1823 /* Wants: mode request key */
1824 static int
1825 option_bind_command(int argc, const char *argv[])
1827 enum request request;
1828 int keymap = -1;
1829 int key;
1831 if (argc < 3) {
1832 config_msg = "Wrong number of arguments given to bind command";
1833 return ERR;
1836 if (set_keymap(&keymap, argv[0]) == ERR) {
1837 config_msg = "Unknown key map";
1838 return ERR;
1841 key = get_key_value(argv[1]);
1842 if (key == ERR) {
1843 config_msg = "Unknown key";
1844 return ERR;
1847 request = get_request(argv[2]);
1848 if (request == REQ_NONE) {
1849 static const struct enum_map obsolete[] = {
1850 ENUM_MAP("cherry-pick", REQ_NONE),
1851 ENUM_MAP("screen-resize", REQ_NONE),
1852 ENUM_MAP("tree-parent", REQ_PARENT),
1854 int alias;
1856 if (map_enum(&alias, obsolete, argv[2])) {
1857 if (alias != REQ_NONE)
1858 add_keybinding(keymap, alias, key);
1859 config_msg = "Obsolete request name";
1860 return ERR;
1863 if (request == REQ_NONE && *argv[2]++ == '!')
1864 request = add_run_request(keymap, key, argc - 2, argv + 2);
1865 if (request == REQ_NONE) {
1866 config_msg = "Unknown request name";
1867 return ERR;
1870 add_keybinding(keymap, request, key);
1872 return OK;
1875 static int
1876 set_option(const char *opt, char *value)
1878 const char *argv[SIZEOF_ARG];
1879 int argc = 0;
1881 if (!argv_from_string(argv, &argc, value)) {
1882 config_msg = "Too many option arguments";
1883 return ERR;
1886 if (!strcmp(opt, "color"))
1887 return option_color_command(argc, argv);
1889 if (!strcmp(opt, "set"))
1890 return option_set_command(argc, argv);
1892 if (!strcmp(opt, "bind"))
1893 return option_bind_command(argc, argv);
1895 config_msg = "Unknown option command";
1896 return ERR;
1899 static int
1900 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1902 int status = OK;
1904 config_lineno++;
1905 config_msg = "Internal error";
1907 /* Check for comment markers, since read_properties() will
1908 * only ensure opt and value are split at first " \t". */
1909 optlen = strcspn(opt, "#");
1910 if (optlen == 0)
1911 return OK;
1913 if (opt[optlen] != 0) {
1914 config_msg = "No option value";
1915 status = ERR;
1917 } else {
1918 /* Look for comment endings in the value. */
1919 size_t len = strcspn(value, "#");
1921 if (len < valuelen) {
1922 valuelen = len;
1923 value[valuelen] = 0;
1926 status = set_option(opt, value);
1929 if (status == ERR) {
1930 warn("Error on line %d, near '%.*s': %s",
1931 config_lineno, (int) optlen, opt, config_msg);
1932 config_errors = TRUE;
1935 /* Always keep going if errors are encountered. */
1936 return OK;
1939 static void
1940 load_option_file(const char *path)
1942 struct io io = {};
1944 /* It's OK that the file doesn't exist. */
1945 if (!io_open(&io, "%s", path))
1946 return;
1948 config_lineno = 0;
1949 config_errors = FALSE;
1951 if (io_load(&io, " \t", read_option) == ERR ||
1952 config_errors == TRUE)
1953 warn("Errors while loading %s.", path);
1956 static int
1957 load_options(void)
1959 const char *home = getenv("HOME");
1960 const char *tigrc_user = getenv("TIGRC_USER");
1961 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1962 char buf[SIZEOF_STR];
1964 add_builtin_run_requests();
1966 if (!tigrc_system)
1967 tigrc_system = SYSCONFDIR "/tigrc";
1968 load_option_file(tigrc_system);
1970 if (!tigrc_user) {
1971 if (!home || !string_format(buf, "%s/.tigrc", home))
1972 return ERR;
1973 tigrc_user = buf;
1975 load_option_file(tigrc_user);
1977 return OK;
1982 * The viewer
1985 struct view;
1986 struct view_ops;
1988 /* The display array of active views and the index of the current view. */
1989 static struct view *display[2];
1990 static unsigned int current_view;
1992 #define foreach_displayed_view(view, i) \
1993 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views() (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob[SIZEOF_REF] = "";
1999 static char ref_commit[SIZEOF_REF] = "HEAD";
2000 static char ref_head[SIZEOF_REF] = "HEAD";
2002 struct view {
2003 const char *name; /* View name */
2004 const char *cmd_env; /* Command line set via environment */
2005 const char *id; /* Points to either of ref_{head,commit,blob} */
2007 struct view_ops *ops; /* View operations */
2009 enum keymap keymap; /* What keymap does this view have */
2010 bool git_dir; /* Whether the view requires a git directory. */
2012 char ref[SIZEOF_REF]; /* Hovered commit reference */
2013 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2015 int height, width; /* The width and height of the main window */
2016 WINDOW *win; /* The main window */
2017 WINDOW *title; /* The title window living below the main window */
2019 /* Navigation */
2020 unsigned long offset; /* Offset of the window top */
2021 unsigned long yoffset; /* Offset from the window side. */
2022 unsigned long lineno; /* Current line number */
2023 unsigned long p_offset; /* Previous offset of the window top */
2024 unsigned long p_yoffset;/* Previous offset from the window side */
2025 unsigned long p_lineno; /* Previous current line number */
2026 bool p_restore; /* Should the previous position be restored. */
2028 /* Searching */
2029 char grep[SIZEOF_STR]; /* Search string */
2030 regex_t *regex; /* Pre-compiled regexp */
2032 /* If non-NULL, points to the view that opened this view. If this view
2033 * is closed tig will switch back to the parent view. */
2034 struct view *parent;
2036 /* Buffering */
2037 size_t lines; /* Total number of lines */
2038 struct line *line; /* Line index */
2039 unsigned int digits; /* Number of digits in the lines member. */
2041 /* Drawing */
2042 struct line *curline; /* Line currently being drawn. */
2043 enum line_type curtype; /* Attribute currently used for drawing. */
2044 unsigned long col; /* Column when drawing. */
2045 bool has_scrolled; /* View was scrolled. */
2047 /* Loading */
2048 struct io io;
2049 struct io *pipe;
2050 time_t start_time;
2051 time_t update_secs;
2054 struct view_ops {
2055 /* What type of content being displayed. Used in the title bar. */
2056 const char *type;
2057 /* Default command arguments. */
2058 const char **argv;
2059 /* Open and reads in all view content. */
2060 bool (*open)(struct view *view);
2061 /* Read one line; updates view->line. */
2062 bool (*read)(struct view *view, char *data);
2063 /* Draw one line; @lineno must be < view->height. */
2064 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2065 /* Depending on view handle a special requests. */
2066 enum request (*request)(struct view *view, enum request request, struct line *line);
2067 /* Search for regexp in a line. */
2068 bool (*grep)(struct view *view, struct line *line);
2069 /* Select line */
2070 void (*select)(struct view *view, struct line *line);
2071 /* Prepare view for loading */
2072 bool (*prepare)(struct view *view);
2075 static struct view_ops blame_ops;
2076 static struct view_ops blob_ops;
2077 static struct view_ops diff_ops;
2078 static struct view_ops help_ops;
2079 static struct view_ops log_ops;
2080 static struct view_ops main_ops;
2081 static struct view_ops pager_ops;
2082 static struct view_ops stage_ops;
2083 static struct view_ops status_ops;
2084 static struct view_ops tree_ops;
2085 static struct view_ops branch_ops;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088 { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views[] = {
2095 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2096 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2097 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2098 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2099 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2100 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2101 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2102 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2103 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2104 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2105 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2108 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115 (view == display[0] || view == display[1])
2118 enum line_graphic {
2119 LINE_GRAPHIC_VLINE
2122 static chtype line_graphics[] = {
2123 /* LINE_GRAPHIC_VLINE: */ '|'
2126 static inline void
2127 set_view_attr(struct view *view, enum line_type type)
2129 if (!view->curline->selected && view->curtype != type) {
2130 wattrset(view->win, get_line_attr(type));
2131 wchgat(view->win, -1, 0, type, NULL);
2132 view->curtype = type;
2136 static int
2137 draw_chars(struct view *view, enum line_type type, const char *string,
2138 int max_len, bool use_tilde)
2140 static char out_buffer[BUFSIZ * 2];
2141 int len = 0;
2142 int col = 0;
2143 int trimmed = FALSE;
2144 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2146 if (max_len <= 0)
2147 return 0;
2149 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2151 set_view_attr(view, type);
2152 if (len > 0) {
2153 if (opt_iconv_out != ICONV_NONE) {
2154 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2155 size_t inlen = len + 1;
2157 char *outbuf = out_buffer;
2158 size_t outlen = sizeof(out_buffer);
2160 size_t ret;
2162 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2163 if (ret != (size_t) -1) {
2164 string = out_buffer;
2165 len = sizeof(out_buffer) - outlen;
2169 waddnstr(view->win, string, len);
2171 if (trimmed && use_tilde) {
2172 set_view_attr(view, LINE_DELIMITER);
2173 waddch(view->win, '~');
2174 col++;
2177 return col;
2180 static int
2181 draw_space(struct view *view, enum line_type type, int max, int spaces)
2183 static char space[] = " ";
2184 int col = 0;
2186 spaces = MIN(max, spaces);
2188 while (spaces > 0) {
2189 int len = MIN(spaces, sizeof(space) - 1);
2191 col += draw_chars(view, type, space, len, FALSE);
2192 spaces -= len;
2195 return col;
2198 static bool
2199 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2201 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2202 return view->width + view->yoffset <= view->col;
2205 static bool
2206 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2208 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2209 int max = view->width + view->yoffset - view->col;
2210 int i;
2212 if (max < size)
2213 size = max;
2215 set_view_attr(view, type);
2216 /* Using waddch() instead of waddnstr() ensures that
2217 * they'll be rendered correctly for the cursor line. */
2218 for (i = skip; i < size; i++)
2219 waddch(view->win, graphic[i]);
2221 view->col += size;
2222 if (size < max && skip <= size)
2223 waddch(view->win, ' ');
2224 view->col++;
2226 return view->width + view->yoffset <= view->col;
2229 static bool
2230 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2232 int max = MIN(view->width + view->yoffset - view->col, len);
2233 int col;
2235 if (text)
2236 col = draw_chars(view, type, text, max - 1, trim);
2237 else
2238 col = draw_space(view, type, max - 1, max - 1);
2240 view->col += col;
2241 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2242 return view->width + view->yoffset <= view->col;
2245 static bool
2246 draw_date(struct view *view, struct time *time)
2248 const char *date = time && time->sec ? mkdate(time) : "";
2249 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2251 return draw_field(view, LINE_DATE, date, cols, FALSE);
2254 static bool
2255 draw_author(struct view *view, const char *author)
2257 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2258 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2260 if (abbreviate && author)
2261 author = get_author_initials(author);
2263 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2266 static bool
2267 draw_mode(struct view *view, mode_t mode)
2269 const char *str;
2271 if (S_ISDIR(mode))
2272 str = "drwxr-xr-x";
2273 else if (S_ISLNK(mode))
2274 str = "lrwxrwxrwx";
2275 else if (S_ISGITLINK(mode))
2276 str = "m---------";
2277 else if (S_ISREG(mode) && mode & S_IXUSR)
2278 str = "-rwxr-xr-x";
2279 else if (S_ISREG(mode))
2280 str = "-rw-r--r--";
2281 else
2282 str = "----------";
2284 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2287 static bool
2288 draw_lineno(struct view *view, unsigned int lineno)
2290 char number[10];
2291 int digits3 = view->digits < 3 ? 3 : view->digits;
2292 int max = MIN(view->width + view->yoffset - view->col, digits3);
2293 char *text = NULL;
2295 lineno += view->offset + 1;
2296 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2297 static char fmt[] = "%1ld";
2299 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2300 if (string_format(number, fmt, lineno))
2301 text = number;
2303 if (text)
2304 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2305 else
2306 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2307 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2310 static bool
2311 draw_view_line(struct view *view, unsigned int lineno)
2313 struct line *line;
2314 bool selected = (view->offset + lineno == view->lineno);
2316 assert(view_is_displayed(view));
2318 if (view->offset + lineno >= view->lines)
2319 return FALSE;
2321 line = &view->line[view->offset + lineno];
2323 wmove(view->win, lineno, 0);
2324 if (line->cleareol)
2325 wclrtoeol(view->win);
2326 view->col = 0;
2327 view->curline = line;
2328 view->curtype = LINE_NONE;
2329 line->selected = FALSE;
2330 line->dirty = line->cleareol = 0;
2332 if (selected) {
2333 set_view_attr(view, LINE_CURSOR);
2334 line->selected = TRUE;
2335 view->ops->select(view, line);
2338 return view->ops->draw(view, line, lineno);
2341 static void
2342 redraw_view_dirty(struct view *view)
2344 bool dirty = FALSE;
2345 int lineno;
2347 for (lineno = 0; lineno < view->height; lineno++) {
2348 if (view->offset + lineno >= view->lines)
2349 break;
2350 if (!view->line[view->offset + lineno].dirty)
2351 continue;
2352 dirty = TRUE;
2353 if (!draw_view_line(view, lineno))
2354 break;
2357 if (!dirty)
2358 return;
2359 wnoutrefresh(view->win);
2362 static void
2363 redraw_view_from(struct view *view, int lineno)
2365 assert(0 <= lineno && lineno < view->height);
2367 for (; lineno < view->height; lineno++) {
2368 if (!draw_view_line(view, lineno))
2369 break;
2372 wnoutrefresh(view->win);
2375 static void
2376 redraw_view(struct view *view)
2378 werase(view->win);
2379 redraw_view_from(view, 0);
2383 static void
2384 update_view_title(struct view *view)
2386 char buf[SIZEOF_STR];
2387 char state[SIZEOF_STR];
2388 size_t bufpos = 0, statelen = 0;
2390 assert(view_is_displayed(view));
2392 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2393 unsigned int view_lines = view->offset + view->height;
2394 unsigned int lines = view->lines
2395 ? MIN(view_lines, view->lines) * 100 / view->lines
2396 : 0;
2398 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2399 view->ops->type,
2400 view->lineno + 1,
2401 view->lines,
2402 lines);
2406 if (view->pipe) {
2407 time_t secs = time(NULL) - view->start_time;
2409 /* Three git seconds are a long time ... */
2410 if (secs > 2)
2411 string_format_from(state, &statelen, " loading %lds", secs);
2414 string_format_from(buf, &bufpos, "[%s]", view->name);
2415 if (*view->ref && bufpos < view->width) {
2416 size_t refsize = strlen(view->ref);
2417 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2419 if (minsize < view->width)
2420 refsize = view->width - minsize + 7;
2421 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2424 if (statelen && bufpos < view->width) {
2425 string_format_from(buf, &bufpos, "%s", state);
2428 if (view == display[current_view])
2429 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2430 else
2431 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2433 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2434 wclrtoeol(view->title);
2435 wnoutrefresh(view->title);
2438 static int
2439 apply_step(double step, int value)
2441 if (step >= 1)
2442 return (int) step;
2443 value *= step + 0.01;
2444 return value ? value : 1;
2447 static void
2448 resize_display(void)
2450 int offset, i;
2451 struct view *base = display[0];
2452 struct view *view = display[1] ? display[1] : display[0];
2454 /* Setup window dimensions */
2456 getmaxyx(stdscr, base->height, base->width);
2458 /* Make room for the status window. */
2459 base->height -= 1;
2461 if (view != base) {
2462 /* Horizontal split. */
2463 view->width = base->width;
2464 view->height = apply_step(opt_scale_split_view, base->height);
2465 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2466 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2467 base->height -= view->height;
2469 /* Make room for the title bar. */
2470 view->height -= 1;
2473 /* Make room for the title bar. */
2474 base->height -= 1;
2476 offset = 0;
2478 foreach_displayed_view (view, i) {
2479 if (!view->win) {
2480 view->win = newwin(view->height, 0, offset, 0);
2481 if (!view->win)
2482 die("Failed to create %s view", view->name);
2484 scrollok(view->win, FALSE);
2486 view->title = newwin(1, 0, offset + view->height, 0);
2487 if (!view->title)
2488 die("Failed to create title window");
2490 } else {
2491 wresize(view->win, view->height, view->width);
2492 mvwin(view->win, offset, 0);
2493 mvwin(view->title, offset + view->height, 0);
2496 offset += view->height + 1;
2500 static void
2501 redraw_display(bool clear)
2503 struct view *view;
2504 int i;
2506 foreach_displayed_view (view, i) {
2507 if (clear)
2508 wclear(view->win);
2509 redraw_view(view);
2510 update_view_title(view);
2514 static void
2515 toggle_enum_option_do(unsigned int *opt, const char *help,
2516 const struct enum_map *map, size_t size)
2518 *opt = (*opt + 1) % size;
2519 redraw_display(FALSE);
2520 report("Displaying %s %s", enum_name(map[*opt]), help);
2523 #define toggle_enum_option(opt, help, map) \
2524 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2526 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2527 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2529 static void
2530 toggle_view_option(bool *option, const char *help)
2532 *option = !*option;
2533 redraw_display(FALSE);
2534 report("%sabling %s", *option ? "En" : "Dis", help);
2537 static void
2538 open_option_menu(void)
2540 const struct menu_item menu[] = {
2541 { '.', "line numbers", &opt_line_number },
2542 { 'D', "date display", &opt_date },
2543 { 'A', "author display", &opt_author },
2544 { 'g', "revision graph display", &opt_rev_graph },
2545 { 'F', "reference display", &opt_show_refs },
2546 { 0 }
2548 int selected = 0;
2550 if (prompt_menu("Toggle option", menu, &selected)) {
2551 if (menu[selected].data == &opt_date)
2552 toggle_date();
2553 else if (menu[selected].data == &opt_author)
2554 toggle_author();
2555 else
2556 toggle_view_option(menu[selected].data, menu[selected].text);
2560 static void
2561 maximize_view(struct view *view)
2563 memset(display, 0, sizeof(display));
2564 current_view = 0;
2565 display[current_view] = view;
2566 resize_display();
2567 redraw_display(FALSE);
2568 report("");
2573 * Navigation
2576 static bool
2577 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2579 if (lineno >= view->lines)
2580 lineno = view->lines > 0 ? view->lines - 1 : 0;
2582 if (offset > lineno || offset + view->height <= lineno) {
2583 unsigned long half = view->height / 2;
2585 if (lineno > half)
2586 offset = lineno - half;
2587 else
2588 offset = 0;
2591 if (offset != view->offset || lineno != view->lineno) {
2592 view->offset = offset;
2593 view->lineno = lineno;
2594 return TRUE;
2597 return FALSE;
2600 /* Scrolling backend */
2601 static void
2602 do_scroll_view(struct view *view, int lines)
2604 bool redraw_current_line = FALSE;
2606 /* The rendering expects the new offset. */
2607 view->offset += lines;
2609 assert(0 <= view->offset && view->offset < view->lines);
2610 assert(lines);
2612 /* Move current line into the view. */
2613 if (view->lineno < view->offset) {
2614 view->lineno = view->offset;
2615 redraw_current_line = TRUE;
2616 } else if (view->lineno >= view->offset + view->height) {
2617 view->lineno = view->offset + view->height - 1;
2618 redraw_current_line = TRUE;
2621 assert(view->offset <= view->lineno && view->lineno < view->lines);
2623 /* Redraw the whole screen if scrolling is pointless. */
2624 if (view->height < ABS(lines)) {
2625 redraw_view(view);
2627 } else {
2628 int line = lines > 0 ? view->height - lines : 0;
2629 int end = line + ABS(lines);
2631 scrollok(view->win, TRUE);
2632 wscrl(view->win, lines);
2633 scrollok(view->win, FALSE);
2635 while (line < end && draw_view_line(view, line))
2636 line++;
2638 if (redraw_current_line)
2639 draw_view_line(view, view->lineno - view->offset);
2640 wnoutrefresh(view->win);
2643 view->has_scrolled = TRUE;
2644 report("");
2647 /* Scroll frontend */
2648 static void
2649 scroll_view(struct view *view, enum request request)
2651 int lines = 1;
2653 assert(view_is_displayed(view));
2655 switch (request) {
2656 case REQ_SCROLL_LEFT:
2657 if (view->yoffset == 0) {
2658 report("Cannot scroll beyond the first column");
2659 return;
2661 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2662 view->yoffset = 0;
2663 else
2664 view->yoffset -= apply_step(opt_hscroll, view->width);
2665 redraw_view_from(view, 0);
2666 report("");
2667 return;
2668 case REQ_SCROLL_RIGHT:
2669 view->yoffset += apply_step(opt_hscroll, view->width);
2670 redraw_view(view);
2671 report("");
2672 return;
2673 case REQ_SCROLL_PAGE_DOWN:
2674 lines = view->height;
2675 case REQ_SCROLL_LINE_DOWN:
2676 if (view->offset + lines > view->lines)
2677 lines = view->lines - view->offset;
2679 if (lines == 0 || view->offset + view->height >= view->lines) {
2680 report("Cannot scroll beyond the last line");
2681 return;
2683 break;
2685 case REQ_SCROLL_PAGE_UP:
2686 lines = view->height;
2687 case REQ_SCROLL_LINE_UP:
2688 if (lines > view->offset)
2689 lines = view->offset;
2691 if (lines == 0) {
2692 report("Cannot scroll beyond the first line");
2693 return;
2696 lines = -lines;
2697 break;
2699 default:
2700 die("request %d not handled in switch", request);
2703 do_scroll_view(view, lines);
2706 /* Cursor moving */
2707 static void
2708 move_view(struct view *view, enum request request)
2710 int scroll_steps = 0;
2711 int steps;
2713 switch (request) {
2714 case REQ_MOVE_FIRST_LINE:
2715 steps = -view->lineno;
2716 break;
2718 case REQ_MOVE_LAST_LINE:
2719 steps = view->lines - view->lineno - 1;
2720 break;
2722 case REQ_MOVE_PAGE_UP:
2723 steps = view->height > view->lineno
2724 ? -view->lineno : -view->height;
2725 break;
2727 case REQ_MOVE_PAGE_DOWN:
2728 steps = view->lineno + view->height >= view->lines
2729 ? view->lines - view->lineno - 1 : view->height;
2730 break;
2732 case REQ_MOVE_UP:
2733 steps = -1;
2734 break;
2736 case REQ_MOVE_DOWN:
2737 steps = 1;
2738 break;
2740 default:
2741 die("request %d not handled in switch", request);
2744 if (steps <= 0 && view->lineno == 0) {
2745 report("Cannot move beyond the first line");
2746 return;
2748 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2749 report("Cannot move beyond the last line");
2750 return;
2753 /* Move the current line */
2754 view->lineno += steps;
2755 assert(0 <= view->lineno && view->lineno < view->lines);
2757 /* Check whether the view needs to be scrolled */
2758 if (view->lineno < view->offset ||
2759 view->lineno >= view->offset + view->height) {
2760 scroll_steps = steps;
2761 if (steps < 0 && -steps > view->offset) {
2762 scroll_steps = -view->offset;
2764 } else if (steps > 0) {
2765 if (view->lineno == view->lines - 1 &&
2766 view->lines > view->height) {
2767 scroll_steps = view->lines - view->offset - 1;
2768 if (scroll_steps >= view->height)
2769 scroll_steps -= view->height - 1;
2774 if (!view_is_displayed(view)) {
2775 view->offset += scroll_steps;
2776 assert(0 <= view->offset && view->offset < view->lines);
2777 view->ops->select(view, &view->line[view->lineno]);
2778 return;
2781 /* Repaint the old "current" line if we be scrolling */
2782 if (ABS(steps) < view->height)
2783 draw_view_line(view, view->lineno - steps - view->offset);
2785 if (scroll_steps) {
2786 do_scroll_view(view, scroll_steps);
2787 return;
2790 /* Draw the current line */
2791 draw_view_line(view, view->lineno - view->offset);
2793 wnoutrefresh(view->win);
2794 report("");
2799 * Searching
2802 static void search_view(struct view *view, enum request request);
2804 static bool
2805 grep_text(struct view *view, const char *text[])
2807 regmatch_t pmatch;
2808 size_t i;
2810 for (i = 0; text[i]; i++)
2811 if (*text[i] &&
2812 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2813 return TRUE;
2814 return FALSE;
2817 static void
2818 select_view_line(struct view *view, unsigned long lineno)
2820 unsigned long old_lineno = view->lineno;
2821 unsigned long old_offset = view->offset;
2823 if (goto_view_line(view, view->offset, lineno)) {
2824 if (view_is_displayed(view)) {
2825 if (old_offset != view->offset) {
2826 redraw_view(view);
2827 } else {
2828 draw_view_line(view, old_lineno - view->offset);
2829 draw_view_line(view, view->lineno - view->offset);
2830 wnoutrefresh(view->win);
2832 } else {
2833 view->ops->select(view, &view->line[view->lineno]);
2838 static void
2839 find_next(struct view *view, enum request request)
2841 unsigned long lineno = view->lineno;
2842 int direction;
2844 if (!*view->grep) {
2845 if (!*opt_search)
2846 report("No previous search");
2847 else
2848 search_view(view, request);
2849 return;
2852 switch (request) {
2853 case REQ_SEARCH:
2854 case REQ_FIND_NEXT:
2855 direction = 1;
2856 break;
2858 case REQ_SEARCH_BACK:
2859 case REQ_FIND_PREV:
2860 direction = -1;
2861 break;
2863 default:
2864 return;
2867 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2868 lineno += direction;
2870 /* Note, lineno is unsigned long so will wrap around in which case it
2871 * will become bigger than view->lines. */
2872 for (; lineno < view->lines; lineno += direction) {
2873 if (view->ops->grep(view, &view->line[lineno])) {
2874 select_view_line(view, lineno);
2875 report("Line %ld matches '%s'", lineno + 1, view->grep);
2876 return;
2880 report("No match found for '%s'", view->grep);
2883 static void
2884 search_view(struct view *view, enum request request)
2886 int regex_err;
2888 if (view->regex) {
2889 regfree(view->regex);
2890 *view->grep = 0;
2891 } else {
2892 view->regex = calloc(1, sizeof(*view->regex));
2893 if (!view->regex)
2894 return;
2897 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2898 if (regex_err != 0) {
2899 char buf[SIZEOF_STR] = "unknown error";
2901 regerror(regex_err, view->regex, buf, sizeof(buf));
2902 report("Search failed: %s", buf);
2903 return;
2906 string_copy(view->grep, opt_search);
2908 find_next(view, request);
2912 * Incremental updating
2915 static void
2916 reset_view(struct view *view)
2918 int i;
2920 for (i = 0; i < view->lines; i++)
2921 free(view->line[i].data);
2922 free(view->line);
2924 view->p_offset = view->offset;
2925 view->p_yoffset = view->yoffset;
2926 view->p_lineno = view->lineno;
2928 view->line = NULL;
2929 view->offset = 0;
2930 view->yoffset = 0;
2931 view->lines = 0;
2932 view->lineno = 0;
2933 view->vid[0] = 0;
2934 view->update_secs = 0;
2937 static void
2938 free_argv(const char *argv[])
2940 int argc;
2942 for (argc = 0; argv[argc]; argc++)
2943 free((void *) argv[argc]);
2946 static const char *
2947 format_arg(const char *name)
2949 static struct {
2950 const char *name;
2951 size_t namelen;
2952 const char *value;
2953 const char *value_if_empty;
2954 } vars[] = {
2955 #define FORMAT_VAR(name, value, value_if_empty) \
2956 { name, STRING_SIZE(name), value, value_if_empty }
2957 FORMAT_VAR("%(directory)", opt_path, ""),
2958 FORMAT_VAR("%(file)", opt_file, ""),
2959 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2960 FORMAT_VAR("%(head)", ref_head, ""),
2961 FORMAT_VAR("%(commit)", ref_commit, ""),
2962 FORMAT_VAR("%(blob)", ref_blob, ""),
2964 int i;
2966 for (i = 0; i < ARRAY_SIZE(vars); i++)
2967 if (!strncmp(name, vars[i].name, vars[i].namelen))
2968 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2970 return NULL;
2972 static bool
2973 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2975 char buf[SIZEOF_STR];
2976 int argc;
2977 bool noreplace = flags == FORMAT_NONE;
2979 free_argv(dst_argv);
2981 for (argc = 0; src_argv[argc]; argc++) {
2982 const char *arg = src_argv[argc];
2983 size_t bufpos = 0;
2985 while (arg) {
2986 char *next = strstr(arg, "%(");
2987 int len = next - arg;
2988 const char *value;
2990 if (!next || noreplace) {
2991 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2992 noreplace = TRUE;
2993 len = strlen(arg);
2994 value = "";
2996 } else {
2997 value = format_arg(next);
2999 if (!value) {
3000 report("Unknown replacement: `%s`", next);
3001 return FALSE;
3005 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3006 return FALSE;
3008 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3011 dst_argv[argc] = strdup(buf);
3012 if (!dst_argv[argc])
3013 break;
3016 dst_argv[argc] = NULL;
3018 return src_argv[argc] == NULL;
3021 static bool
3022 restore_view_position(struct view *view)
3024 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3025 return FALSE;
3027 /* Changing the view position cancels the restoring. */
3028 /* FIXME: Changing back to the first line is not detected. */
3029 if (view->offset != 0 || view->lineno != 0) {
3030 view->p_restore = FALSE;
3031 return FALSE;
3034 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3035 view_is_displayed(view))
3036 werase(view->win);
3038 view->yoffset = view->p_yoffset;
3039 view->p_restore = FALSE;
3041 return TRUE;
3044 static void
3045 end_update(struct view *view, bool force)
3047 if (!view->pipe)
3048 return;
3049 while (!view->ops->read(view, NULL))
3050 if (!force)
3051 return;
3052 set_nonblocking_input(FALSE);
3053 if (force)
3054 kill_io(view->pipe);
3055 done_io(view->pipe);
3056 view->pipe = NULL;
3059 static void
3060 setup_update(struct view *view, const char *vid)
3062 set_nonblocking_input(TRUE);
3063 reset_view(view);
3064 string_copy_rev(view->vid, vid);
3065 view->pipe = &view->io;
3066 view->start_time = time(NULL);
3069 static bool
3070 prepare_update(struct view *view, const char *argv[], const char *dir,
3071 enum format_flags flags)
3073 if (view->pipe)
3074 end_update(view, TRUE);
3075 return init_io_rd(&view->io, argv, dir, flags);
3078 static bool
3079 prepare_update_file(struct view *view, const char *name)
3081 if (view->pipe)
3082 end_update(view, TRUE);
3083 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3086 static bool
3087 begin_update(struct view *view, bool refresh)
3089 if (view->pipe)
3090 end_update(view, TRUE);
3092 if (!refresh) {
3093 if (view->ops->prepare) {
3094 if (!view->ops->prepare(view))
3095 return FALSE;
3096 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3097 return FALSE;
3100 /* Put the current ref_* value to the view title ref
3101 * member. This is needed by the blob view. Most other
3102 * views sets it automatically after loading because the
3103 * first line is a commit line. */
3104 string_copy_rev(view->ref, view->id);
3107 if (!start_io(&view->io))
3108 return FALSE;
3110 setup_update(view, view->id);
3112 return TRUE;
3115 static bool
3116 update_view(struct view *view)
3118 char out_buffer[BUFSIZ * 2];
3119 char *line;
3120 /* Clear the view and redraw everything since the tree sorting
3121 * might have rearranged things. */
3122 bool redraw = view->lines == 0;
3123 bool can_read = TRUE;
3125 if (!view->pipe)
3126 return TRUE;
3128 if (!io_can_read(view->pipe)) {
3129 if (view->lines == 0 && view_is_displayed(view)) {
3130 time_t secs = time(NULL) - view->start_time;
3132 if (secs > 1 && secs > view->update_secs) {
3133 if (view->update_secs == 0)
3134 redraw_view(view);
3135 update_view_title(view);
3136 view->update_secs = secs;
3139 return TRUE;
3142 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3143 if (opt_iconv_in != ICONV_NONE) {
3144 ICONV_CONST char *inbuf = line;
3145 size_t inlen = strlen(line) + 1;
3147 char *outbuf = out_buffer;
3148 size_t outlen = sizeof(out_buffer);
3150 size_t ret;
3152 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3153 if (ret != (size_t) -1)
3154 line = out_buffer;
3157 if (!view->ops->read(view, line)) {
3158 report("Allocation failure");
3159 end_update(view, TRUE);
3160 return FALSE;
3165 unsigned long lines = view->lines;
3166 int digits;
3168 for (digits = 0; lines; digits++)
3169 lines /= 10;
3171 /* Keep the displayed view in sync with line number scaling. */
3172 if (digits != view->digits) {
3173 view->digits = digits;
3174 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3175 redraw = TRUE;
3179 if (io_error(view->pipe)) {
3180 report("Failed to read: %s", io_strerror(view->pipe));
3181 end_update(view, TRUE);
3183 } else if (io_eof(view->pipe)) {
3184 report("");
3185 end_update(view, FALSE);
3188 if (restore_view_position(view))
3189 redraw = TRUE;
3191 if (!view_is_displayed(view))
3192 return TRUE;
3194 if (redraw)
3195 redraw_view_from(view, 0);
3196 else
3197 redraw_view_dirty(view);
3199 /* Update the title _after_ the redraw so that if the redraw picks up a
3200 * commit reference in view->ref it'll be available here. */
3201 update_view_title(view);
3202 return TRUE;
3205 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3207 static struct line *
3208 add_line_data(struct view *view, void *data, enum line_type type)
3210 struct line *line;
3212 if (!realloc_lines(&view->line, view->lines, 1))
3213 return NULL;
3215 line = &view->line[view->lines++];
3216 memset(line, 0, sizeof(*line));
3217 line->type = type;
3218 line->data = data;
3219 line->dirty = 1;
3221 return line;
3224 static struct line *
3225 add_line_text(struct view *view, const char *text, enum line_type type)
3227 char *data = text ? strdup(text) : NULL;
3229 return data ? add_line_data(view, data, type) : NULL;
3232 static struct line *
3233 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3235 char buf[SIZEOF_STR];
3236 va_list args;
3238 va_start(args, fmt);
3239 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3240 buf[0] = 0;
3241 va_end(args);
3243 return buf[0] ? add_line_text(view, buf, type) : NULL;
3247 * View opening
3250 enum open_flags {
3251 OPEN_DEFAULT = 0, /* Use default view switching. */
3252 OPEN_SPLIT = 1, /* Split current view. */
3253 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3254 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3255 OPEN_PREPARED = 32, /* Open already prepared command. */
3258 static void
3259 open_view(struct view *prev, enum request request, enum open_flags flags)
3261 bool split = !!(flags & OPEN_SPLIT);
3262 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3263 bool nomaximize = !!(flags & OPEN_REFRESH);
3264 struct view *view = VIEW(request);
3265 int nviews = displayed_views();
3266 struct view *base_view = display[0];
3268 if (view == prev && nviews == 1 && !reload) {
3269 report("Already in %s view", view->name);
3270 return;
3273 if (view->git_dir && !opt_git_dir[0]) {
3274 report("The %s view is disabled in pager view", view->name);
3275 return;
3278 if (split) {
3279 display[1] = view;
3280 current_view = 1;
3281 } else if (!nomaximize) {
3282 /* Maximize the current view. */
3283 memset(display, 0, sizeof(display));
3284 current_view = 0;
3285 display[current_view] = view;
3288 /* No parent signals that this is the first loaded view. */
3289 if (prev && view != prev) {
3290 view->parent = prev;
3293 /* Resize the view when switching between split- and full-screen,
3294 * or when switching between two different full-screen views. */
3295 if (nviews != displayed_views() ||
3296 (nviews == 1 && base_view != display[0]))
3297 resize_display();
3299 if (view->ops->open) {
3300 if (view->pipe)
3301 end_update(view, TRUE);
3302 if (!view->ops->open(view)) {
3303 report("Failed to load %s view", view->name);
3304 return;
3306 restore_view_position(view);
3308 } else if ((reload || strcmp(view->vid, view->id)) &&
3309 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3310 report("Failed to load %s view", view->name);
3311 return;
3314 if (split && prev->lineno - prev->offset >= prev->height) {
3315 /* Take the title line into account. */
3316 int lines = prev->lineno - prev->offset - prev->height + 1;
3318 /* Scroll the view that was split if the current line is
3319 * outside the new limited view. */
3320 do_scroll_view(prev, lines);
3323 if (prev && view != prev && split && view_is_displayed(prev)) {
3324 /* "Blur" the previous view. */
3325 update_view_title(prev);
3328 if (view->pipe && view->lines == 0) {
3329 /* Clear the old view and let the incremental updating refill
3330 * the screen. */
3331 werase(view->win);
3332 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3333 report("");
3334 } else if (view_is_displayed(view)) {
3335 redraw_view(view);
3336 report("");
3340 static void
3341 open_external_viewer(const char *argv[], const char *dir)
3343 def_prog_mode(); /* save current tty modes */
3344 endwin(); /* restore original tty modes */
3345 run_io_fg(argv, dir);
3346 fprintf(stderr, "Press Enter to continue");
3347 getc(opt_tty);
3348 reset_prog_mode();
3349 redraw_display(TRUE);
3352 static void
3353 open_mergetool(const char *file)
3355 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3357 open_external_viewer(mergetool_argv, opt_cdup);
3360 static void
3361 open_editor(const char *file)
3363 const char *editor_argv[] = { "vi", file, NULL };
3364 const char *editor;
3366 editor = getenv("GIT_EDITOR");
3367 if (!editor && *opt_editor)
3368 editor = opt_editor;
3369 if (!editor)
3370 editor = getenv("VISUAL");
3371 if (!editor)
3372 editor = getenv("EDITOR");
3373 if (!editor)
3374 editor = "vi";
3376 editor_argv[0] = editor;
3377 open_external_viewer(editor_argv, opt_cdup);
3380 static void
3381 open_run_request(enum request request)
3383 struct run_request *req = get_run_request(request);
3384 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3386 if (!req) {
3387 report("Unknown run request");
3388 return;
3391 if (format_argv(argv, req->argv, FORMAT_ALL))
3392 open_external_viewer(argv, NULL);
3393 free_argv(argv);
3397 * User request switch noodle
3400 static int
3401 view_driver(struct view *view, enum request request)
3403 int i;
3405 if (request == REQ_NONE)
3406 return TRUE;
3408 if (request > REQ_NONE) {
3409 open_run_request(request);
3410 /* FIXME: When all views can refresh always do this. */
3411 if (view == VIEW(REQ_VIEW_STATUS) ||
3412 view == VIEW(REQ_VIEW_MAIN) ||
3413 view == VIEW(REQ_VIEW_LOG) ||
3414 view == VIEW(REQ_VIEW_BRANCH) ||
3415 view == VIEW(REQ_VIEW_STAGE))
3416 request = REQ_REFRESH;
3417 else
3418 return TRUE;
3421 if (view && view->lines) {
3422 request = view->ops->request(view, request, &view->line[view->lineno]);
3423 if (request == REQ_NONE)
3424 return TRUE;
3427 switch (request) {
3428 case REQ_MOVE_UP:
3429 case REQ_MOVE_DOWN:
3430 case REQ_MOVE_PAGE_UP:
3431 case REQ_MOVE_PAGE_DOWN:
3432 case REQ_MOVE_FIRST_LINE:
3433 case REQ_MOVE_LAST_LINE:
3434 move_view(view, request);
3435 break;
3437 case REQ_SCROLL_LEFT:
3438 case REQ_SCROLL_RIGHT:
3439 case REQ_SCROLL_LINE_DOWN:
3440 case REQ_SCROLL_LINE_UP:
3441 case REQ_SCROLL_PAGE_DOWN:
3442 case REQ_SCROLL_PAGE_UP:
3443 scroll_view(view, request);
3444 break;
3446 case REQ_VIEW_BLAME:
3447 if (!opt_file[0]) {
3448 report("No file chosen, press %s to open tree view",
3449 get_key(view->keymap, REQ_VIEW_TREE));
3450 break;
3452 open_view(view, request, OPEN_DEFAULT);
3453 break;
3455 case REQ_VIEW_BLOB:
3456 if (!ref_blob[0]) {
3457 report("No file chosen, press %s to open tree view",
3458 get_key(view->keymap, REQ_VIEW_TREE));
3459 break;
3461 open_view(view, request, OPEN_DEFAULT);
3462 break;
3464 case REQ_VIEW_PAGER:
3465 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3466 report("No pager content, press %s to run command from prompt",
3467 get_key(view->keymap, REQ_PROMPT));
3468 break;
3470 open_view(view, request, OPEN_DEFAULT);
3471 break;
3473 case REQ_VIEW_STAGE:
3474 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3475 report("No stage content, press %s to open the status view and choose file",
3476 get_key(view->keymap, REQ_VIEW_STATUS));
3477 break;
3479 open_view(view, request, OPEN_DEFAULT);
3480 break;
3482 case REQ_VIEW_STATUS:
3483 if (opt_is_inside_work_tree == FALSE) {
3484 report("The status view requires a working tree");
3485 break;
3487 open_view(view, request, OPEN_DEFAULT);
3488 break;
3490 case REQ_VIEW_MAIN:
3491 case REQ_VIEW_DIFF:
3492 case REQ_VIEW_LOG:
3493 case REQ_VIEW_TREE:
3494 case REQ_VIEW_HELP:
3495 case REQ_VIEW_BRANCH:
3496 open_view(view, request, OPEN_DEFAULT);
3497 break;
3499 case REQ_NEXT:
3500 case REQ_PREVIOUS:
3501 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3503 if ((view == VIEW(REQ_VIEW_DIFF) &&
3504 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3505 (view == VIEW(REQ_VIEW_DIFF) &&
3506 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3507 (view == VIEW(REQ_VIEW_STAGE) &&
3508 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3509 (view == VIEW(REQ_VIEW_BLOB) &&
3510 view->parent == VIEW(REQ_VIEW_TREE)) ||
3511 (view == VIEW(REQ_VIEW_MAIN) &&
3512 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3513 int line;
3515 view = view->parent;
3516 line = view->lineno;
3517 move_view(view, request);
3518 if (view_is_displayed(view))
3519 update_view_title(view);
3520 if (line != view->lineno)
3521 view->ops->request(view, REQ_ENTER,
3522 &view->line[view->lineno]);
3524 } else {
3525 move_view(view, request);
3527 break;
3529 case REQ_VIEW_NEXT:
3531 int nviews = displayed_views();
3532 int next_view = (current_view + 1) % nviews;
3534 if (next_view == current_view) {
3535 report("Only one view is displayed");
3536 break;
3539 current_view = next_view;
3540 /* Blur out the title of the previous view. */
3541 update_view_title(view);
3542 report("");
3543 break;
3545 case REQ_REFRESH:
3546 report("Refreshing is not yet supported for the %s view", view->name);
3547 break;
3549 case REQ_MAXIMIZE:
3550 if (displayed_views() == 2)
3551 maximize_view(view);
3552 break;
3554 case REQ_OPTIONS:
3555 open_option_menu();
3556 break;
3558 case REQ_TOGGLE_LINENO:
3559 toggle_view_option(&opt_line_number, "line numbers");
3560 break;
3562 case REQ_TOGGLE_DATE:
3563 toggle_date();
3564 break;
3566 case REQ_TOGGLE_AUTHOR:
3567 toggle_author();
3568 break;
3570 case REQ_TOGGLE_REV_GRAPH:
3571 toggle_view_option(&opt_rev_graph, "revision graph display");
3572 break;
3574 case REQ_TOGGLE_REFS:
3575 toggle_view_option(&opt_show_refs, "reference display");
3576 break;
3578 case REQ_TOGGLE_SORT_FIELD:
3579 case REQ_TOGGLE_SORT_ORDER:
3580 report("Sorting is not yet supported for the %s view", view->name);
3581 break;
3583 case REQ_SEARCH:
3584 case REQ_SEARCH_BACK:
3585 search_view(view, request);
3586 break;
3588 case REQ_FIND_NEXT:
3589 case REQ_FIND_PREV:
3590 find_next(view, request);
3591 break;
3593 case REQ_STOP_LOADING:
3594 for (i = 0; i < ARRAY_SIZE(views); i++) {
3595 view = &views[i];
3596 if (view->pipe)
3597 report("Stopped loading the %s view", view->name),
3598 end_update(view, TRUE);
3600 break;
3602 case REQ_SHOW_VERSION:
3603 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3604 return TRUE;
3606 case REQ_SCREEN_REDRAW:
3607 redraw_display(TRUE);
3608 break;
3610 case REQ_EDIT:
3611 report("Nothing to edit");
3612 break;
3614 case REQ_ENTER:
3615 report("Nothing to enter");
3616 break;
3618 case REQ_VIEW_CLOSE:
3619 /* XXX: Mark closed views by letting view->parent point to the
3620 * view itself. Parents to closed view should never be
3621 * followed. */
3622 if (view->parent &&
3623 view->parent->parent != view->parent) {
3624 maximize_view(view->parent);
3625 view->parent = view;
3626 break;
3628 /* Fall-through */
3629 case REQ_QUIT:
3630 return FALSE;
3632 default:
3633 report("Unknown key, press %s for help",
3634 get_key(view->keymap, REQ_VIEW_HELP));
3635 return TRUE;
3638 return TRUE;
3643 * View backend utilities
3646 enum sort_field {
3647 ORDERBY_NAME,
3648 ORDERBY_DATE,
3649 ORDERBY_AUTHOR,
3652 struct sort_state {
3653 const enum sort_field *fields;
3654 size_t size, current;
3655 bool reverse;
3658 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3659 #define get_sort_field(state) ((state).fields[(state).current])
3660 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3662 static void
3663 sort_view(struct view *view, enum request request, struct sort_state *state,
3664 int (*compare)(const void *, const void *))
3666 switch (request) {
3667 case REQ_TOGGLE_SORT_FIELD:
3668 state->current = (state->current + 1) % state->size;
3669 break;
3671 case REQ_TOGGLE_SORT_ORDER:
3672 state->reverse = !state->reverse;
3673 break;
3674 default:
3675 die("Not a sort request");
3678 qsort(view->line, view->lines, sizeof(*view->line), compare);
3679 redraw_view(view);
3682 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3684 /* Small author cache to reduce memory consumption. It uses binary
3685 * search to lookup or find place to position new entries. No entries
3686 * are ever freed. */
3687 static const char *
3688 get_author(const char *name)
3690 static const char **authors;
3691 static size_t authors_size;
3692 int from = 0, to = authors_size - 1;
3694 while (from <= to) {
3695 size_t pos = (to + from) / 2;
3696 int cmp = strcmp(name, authors[pos]);
3698 if (!cmp)
3699 return authors[pos];
3701 if (cmp < 0)
3702 to = pos - 1;
3703 else
3704 from = pos + 1;
3707 if (!realloc_authors(&authors, authors_size, 1))
3708 return NULL;
3709 name = strdup(name);
3710 if (!name)
3711 return NULL;
3713 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3714 authors[from] = name;
3715 authors_size++;
3717 return name;
3720 static void
3721 parse_timesec(struct time *time, const char *sec)
3723 time->sec = (time_t) atol(sec);
3726 static void
3727 parse_timezone(struct time *time, const char *zone)
3729 long tz;
3731 tz = ('0' - zone[1]) * 60 * 60 * 10;
3732 tz += ('0' - zone[2]) * 60 * 60;
3733 tz += ('0' - zone[3]) * 60;
3734 tz += ('0' - zone[4]);
3736 if (zone[0] == '-')
3737 tz = -tz;
3739 time->tz = tz;
3740 time->sec -= tz;
3743 /* Parse author lines where the name may be empty:
3744 * author <email@address.tld> 1138474660 +0100
3746 static void
3747 parse_author_line(char *ident, const char **author, struct time *time)
3749 char *nameend = strchr(ident, '<');
3750 char *emailend = strchr(ident, '>');
3752 if (nameend && emailend)
3753 *nameend = *emailend = 0;
3754 ident = chomp_string(ident);
3755 if (!*ident) {
3756 if (nameend)
3757 ident = chomp_string(nameend + 1);
3758 if (!*ident)
3759 ident = "Unknown";
3762 *author = get_author(ident);
3764 /* Parse epoch and timezone */
3765 if (emailend && emailend[1] == ' ') {
3766 char *secs = emailend + 2;
3767 char *zone = strchr(secs, ' ');
3769 parse_timesec(time, secs);
3771 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3772 parse_timezone(time, zone + 1);
3776 static bool
3777 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3779 char rev[SIZEOF_REV];
3780 const char *revlist_argv[] = {
3781 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3783 struct menu_item *items;
3784 char text[SIZEOF_STR];
3785 bool ok = TRUE;
3786 int i;
3788 items = calloc(*parents + 1, sizeof(*items));
3789 if (!items)
3790 return FALSE;
3792 for (i = 0; i < *parents; i++) {
3793 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3794 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3795 !(items[i].text = strdup(text))) {
3796 ok = FALSE;
3797 break;
3801 if (ok) {
3802 *parents = 0;
3803 ok = prompt_menu("Select parent", items, parents);
3805 for (i = 0; items[i].text; i++)
3806 free((char *) items[i].text);
3807 free(items);
3808 return ok;
3811 static bool
3812 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3814 char buf[SIZEOF_STR * 4];
3815 const char *revlist_argv[] = {
3816 "git", "log", "--no-color", "-1",
3817 "--pretty=format:%P", id, "--", path, NULL
3819 int parents;
3821 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3822 (parents = strlen(buf) / 40) < 0) {
3823 report("Failed to get parent information");
3824 return FALSE;
3826 } else if (parents == 0) {
3827 if (path)
3828 report("Path '%s' does not exist in the parent", path);
3829 else
3830 report("The selected commit has no parents");
3831 return FALSE;
3834 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3835 return FALSE;
3837 string_copy_rev(rev, &buf[41 * parents]);
3838 return TRUE;
3842 * Pager backend
3845 static bool
3846 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3848 char text[SIZEOF_STR];
3850 if (opt_line_number && draw_lineno(view, lineno))
3851 return TRUE;
3853 string_expand(text, sizeof(text), line->data, opt_tab_size);
3854 draw_text(view, line->type, text, TRUE);
3855 return TRUE;
3858 static bool
3859 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3861 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3862 char ref[SIZEOF_STR];
3864 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3865 return TRUE;
3867 /* This is the only fatal call, since it can "corrupt" the buffer. */
3868 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3869 return FALSE;
3871 return TRUE;
3874 static void
3875 add_pager_refs(struct view *view, struct line *line)
3877 char buf[SIZEOF_STR];
3878 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3879 struct ref_list *list;
3880 size_t bufpos = 0, i;
3881 const char *sep = "Refs: ";
3882 bool is_tag = FALSE;
3884 assert(line->type == LINE_COMMIT);
3886 list = get_ref_list(commit_id);
3887 if (!list) {
3888 if (view == VIEW(REQ_VIEW_DIFF))
3889 goto try_add_describe_ref;
3890 return;
3893 for (i = 0; i < list->size; i++) {
3894 struct ref *ref = list->refs[i];
3895 const char *fmt = ref->tag ? "%s[%s]" :
3896 ref->remote ? "%s<%s>" : "%s%s";
3898 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3899 return;
3900 sep = ", ";
3901 if (ref->tag)
3902 is_tag = TRUE;
3905 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3906 try_add_describe_ref:
3907 /* Add <tag>-g<commit_id> "fake" reference. */
3908 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3909 return;
3912 if (bufpos == 0)
3913 return;
3915 add_line_text(view, buf, LINE_PP_REFS);
3918 static bool
3919 pager_read(struct view *view, char *data)
3921 struct line *line;
3923 if (!data)
3924 return TRUE;
3926 line = add_line_text(view, data, get_line_type(data));
3927 if (!line)
3928 return FALSE;
3930 if (line->type == LINE_COMMIT &&
3931 (view == VIEW(REQ_VIEW_DIFF) ||
3932 view == VIEW(REQ_VIEW_LOG)))
3933 add_pager_refs(view, line);
3935 return TRUE;
3938 static enum request
3939 pager_request(struct view *view, enum request request, struct line *line)
3941 int split = 0;
3943 if (request != REQ_ENTER)
3944 return request;
3946 if (line->type == LINE_COMMIT &&
3947 (view == VIEW(REQ_VIEW_LOG) ||
3948 view == VIEW(REQ_VIEW_PAGER))) {
3949 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3950 split = 1;
3953 /* Always scroll the view even if it was split. That way
3954 * you can use Enter to scroll through the log view and
3955 * split open each commit diff. */
3956 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3958 /* FIXME: A minor workaround. Scrolling the view will call report("")
3959 * but if we are scrolling a non-current view this won't properly
3960 * update the view title. */
3961 if (split)
3962 update_view_title(view);
3964 return REQ_NONE;
3967 static bool
3968 pager_grep(struct view *view, struct line *line)
3970 const char *text[] = { line->data, NULL };
3972 return grep_text(view, text);
3975 static void
3976 pager_select(struct view *view, struct line *line)
3978 if (line->type == LINE_COMMIT) {
3979 char *text = (char *)line->data + STRING_SIZE("commit ");
3981 if (view != VIEW(REQ_VIEW_PAGER))
3982 string_copy_rev(view->ref, text);
3983 string_copy_rev(ref_commit, text);
3987 static struct view_ops pager_ops = {
3988 "line",
3989 NULL,
3990 NULL,
3991 pager_read,
3992 pager_draw,
3993 pager_request,
3994 pager_grep,
3995 pager_select,
3998 static const char *log_argv[SIZEOF_ARG] = {
3999 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4002 static enum request
4003 log_request(struct view *view, enum request request, struct line *line)
4005 switch (request) {
4006 case REQ_REFRESH:
4007 load_refs();
4008 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4009 return REQ_NONE;
4010 default:
4011 return pager_request(view, request, line);
4015 static struct view_ops log_ops = {
4016 "line",
4017 log_argv,
4018 NULL,
4019 pager_read,
4020 pager_draw,
4021 log_request,
4022 pager_grep,
4023 pager_select,
4026 static const char *diff_argv[SIZEOF_ARG] = {
4027 "git", "show", "--pretty=fuller", "--no-color", "--root",
4028 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4031 static struct view_ops diff_ops = {
4032 "line",
4033 diff_argv,
4034 NULL,
4035 pager_read,
4036 pager_draw,
4037 pager_request,
4038 pager_grep,
4039 pager_select,
4043 * Help backend
4046 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4048 static bool
4049 help_open_keymap_title(struct view *view, enum keymap keymap)
4051 struct line *line;
4053 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4054 help_keymap_hidden[keymap] ? '+' : '-',
4055 enum_name(keymap_table[keymap]));
4056 if (line)
4057 line->other = keymap;
4059 return help_keymap_hidden[keymap];
4062 static void
4063 help_open_keymap(struct view *view, enum keymap keymap)
4065 const char *group = NULL;
4066 char buf[SIZEOF_STR];
4067 size_t bufpos;
4068 bool add_title = TRUE;
4069 int i;
4071 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4072 const char *key = NULL;
4074 if (req_info[i].request == REQ_NONE)
4075 continue;
4077 if (!req_info[i].request) {
4078 group = req_info[i].help;
4079 continue;
4082 key = get_keys(keymap, req_info[i].request, TRUE);
4083 if (!key || !*key)
4084 continue;
4086 if (add_title && help_open_keymap_title(view, keymap))
4087 return;
4088 add_title = FALSE;
4090 if (group) {
4091 add_line_text(view, group, LINE_HELP_GROUP);
4092 group = NULL;
4095 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4096 enum_name(req_info[i]), req_info[i].help);
4099 group = "External commands:";
4101 for (i = 0; i < run_requests; i++) {
4102 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4103 const char *key;
4104 int argc;
4106 if (!req || req->keymap != keymap)
4107 continue;
4109 key = get_key_name(req->key);
4110 if (!*key)
4111 key = "(no key defined)";
4113 if (add_title && help_open_keymap_title(view, keymap))
4114 return;
4115 if (group) {
4116 add_line_text(view, group, LINE_HELP_GROUP);
4117 group = NULL;
4120 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4121 if (!string_format_from(buf, &bufpos, "%s%s",
4122 argc ? " " : "", req->argv[argc]))
4123 return;
4125 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4129 static bool
4130 help_open(struct view *view)
4132 enum keymap keymap;
4134 reset_view(view);
4135 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4136 add_line_text(view, "", LINE_DEFAULT);
4138 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4139 help_open_keymap(view, keymap);
4141 return TRUE;
4144 static enum request
4145 help_request(struct view *view, enum request request, struct line *line)
4147 switch (request) {
4148 case REQ_ENTER:
4149 if (line->type == LINE_HELP_KEYMAP) {
4150 help_keymap_hidden[line->other] =
4151 !help_keymap_hidden[line->other];
4152 view->p_restore = TRUE;
4153 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4156 return REQ_NONE;
4157 default:
4158 return pager_request(view, request, line);
4162 static struct view_ops help_ops = {
4163 "line",
4164 NULL,
4165 help_open,
4166 NULL,
4167 pager_draw,
4168 help_request,
4169 pager_grep,
4170 pager_select,
4175 * Tree backend
4178 struct tree_stack_entry {
4179 struct tree_stack_entry *prev; /* Entry below this in the stack */
4180 unsigned long lineno; /* Line number to restore */
4181 char *name; /* Position of name in opt_path */
4184 /* The top of the path stack. */
4185 static struct tree_stack_entry *tree_stack = NULL;
4186 unsigned long tree_lineno = 0;
4188 static void
4189 pop_tree_stack_entry(void)
4191 struct tree_stack_entry *entry = tree_stack;
4193 tree_lineno = entry->lineno;
4194 entry->name[0] = 0;
4195 tree_stack = entry->prev;
4196 free(entry);
4199 static void
4200 push_tree_stack_entry(const char *name, unsigned long lineno)
4202 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4203 size_t pathlen = strlen(opt_path);
4205 if (!entry)
4206 return;
4208 entry->prev = tree_stack;
4209 entry->name = opt_path + pathlen;
4210 tree_stack = entry;
4212 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4213 pop_tree_stack_entry();
4214 return;
4217 /* Move the current line to the first tree entry. */
4218 tree_lineno = 1;
4219 entry->lineno = lineno;
4222 /* Parse output from git-ls-tree(1):
4224 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4227 #define SIZEOF_TREE_ATTR \
4228 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4230 #define SIZEOF_TREE_MODE \
4231 STRING_SIZE("100644 ")
4233 #define TREE_ID_OFFSET \
4234 STRING_SIZE("100644 blob ")
4236 struct tree_entry {
4237 char id[SIZEOF_REV];
4238 mode_t mode;
4239 struct time time; /* Date from the author ident. */
4240 const char *author; /* Author of the commit. */
4241 char name[1];
4244 static const char *
4245 tree_path(const struct line *line)
4247 return ((struct tree_entry *) line->data)->name;
4250 static int
4251 tree_compare_entry(const struct line *line1, const struct line *line2)
4253 if (line1->type != line2->type)
4254 return line1->type == LINE_TREE_DIR ? -1 : 1;
4255 return strcmp(tree_path(line1), tree_path(line2));
4258 static const enum sort_field tree_sort_fields[] = {
4259 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4261 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4263 static int
4264 tree_compare(const void *l1, const void *l2)
4266 const struct line *line1 = (const struct line *) l1;
4267 const struct line *line2 = (const struct line *) l2;
4268 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4269 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4271 if (line1->type == LINE_TREE_HEAD)
4272 return -1;
4273 if (line2->type == LINE_TREE_HEAD)
4274 return 1;
4276 switch (get_sort_field(tree_sort_state)) {
4277 case ORDERBY_DATE:
4278 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4280 case ORDERBY_AUTHOR:
4281 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4283 case ORDERBY_NAME:
4284 default:
4285 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4290 static struct line *
4291 tree_entry(struct view *view, enum line_type type, const char *path,
4292 const char *mode, const char *id)
4294 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4295 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4297 if (!entry || !line) {
4298 free(entry);
4299 return NULL;
4302 strncpy(entry->name, path, strlen(path));
4303 if (mode)
4304 entry->mode = strtoul(mode, NULL, 8);
4305 if (id)
4306 string_copy_rev(entry->id, id);
4308 return line;
4311 static bool
4312 tree_read_date(struct view *view, char *text, bool *read_date)
4314 static const char *author_name;
4315 static struct time author_time;
4317 if (!text && *read_date) {
4318 *read_date = FALSE;
4319 return TRUE;
4321 } else if (!text) {
4322 char *path = *opt_path ? opt_path : ".";
4323 /* Find next entry to process */
4324 const char *log_file[] = {
4325 "git", "log", "--no-color", "--pretty=raw",
4326 "--cc", "--raw", view->id, "--", path, NULL
4328 struct io io = {};
4330 if (!view->lines) {
4331 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4332 report("Tree is empty");
4333 return TRUE;
4336 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4337 report("Failed to load tree data");
4338 return TRUE;
4341 done_io(view->pipe);
4342 view->io = io;
4343 *read_date = TRUE;
4344 return FALSE;
4346 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4347 parse_author_line(text + STRING_SIZE("author "),
4348 &author_name, &author_time);
4350 } else if (*text == ':') {
4351 char *pos;
4352 size_t annotated = 1;
4353 size_t i;
4355 pos = strchr(text, '\t');
4356 if (!pos)
4357 return TRUE;
4358 text = pos + 1;
4359 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4360 text += strlen(opt_path);
4361 pos = strchr(text, '/');
4362 if (pos)
4363 *pos = 0;
4365 for (i = 1; i < view->lines; i++) {
4366 struct line *line = &view->line[i];
4367 struct tree_entry *entry = line->data;
4369 annotated += !!entry->author;
4370 if (entry->author || strcmp(entry->name, text))
4371 continue;
4373 entry->author = author_name;
4374 entry->time = author_time;
4375 line->dirty = 1;
4376 break;
4379 if (annotated == view->lines)
4380 kill_io(view->pipe);
4382 return TRUE;
4385 static bool
4386 tree_read(struct view *view, char *text)
4388 static bool read_date = FALSE;
4389 struct tree_entry *data;
4390 struct line *entry, *line;
4391 enum line_type type;
4392 size_t textlen = text ? strlen(text) : 0;
4393 char *path = text + SIZEOF_TREE_ATTR;
4395 if (read_date || !text)
4396 return tree_read_date(view, text, &read_date);
4398 if (textlen <= SIZEOF_TREE_ATTR)
4399 return FALSE;
4400 if (view->lines == 0 &&
4401 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4402 return FALSE;
4404 /* Strip the path part ... */
4405 if (*opt_path) {
4406 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4407 size_t striplen = strlen(opt_path);
4409 if (pathlen > striplen)
4410 memmove(path, path + striplen,
4411 pathlen - striplen + 1);
4413 /* Insert "link" to parent directory. */
4414 if (view->lines == 1 &&
4415 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4416 return FALSE;
4419 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4420 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4421 if (!entry)
4422 return FALSE;
4423 data = entry->data;
4425 /* Skip "Directory ..." and ".." line. */
4426 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4427 if (tree_compare_entry(line, entry) <= 0)
4428 continue;
4430 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4432 line->data = data;
4433 line->type = type;
4434 for (; line <= entry; line++)
4435 line->dirty = line->cleareol = 1;
4436 return TRUE;
4439 if (tree_lineno > view->lineno) {
4440 view->lineno = tree_lineno;
4441 tree_lineno = 0;
4444 return TRUE;
4447 static bool
4448 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4450 struct tree_entry *entry = line->data;
4452 if (line->type == LINE_TREE_HEAD) {
4453 if (draw_text(view, line->type, "Directory path /", TRUE))
4454 return TRUE;
4455 } else {
4456 if (draw_mode(view, entry->mode))
4457 return TRUE;
4459 if (opt_author && draw_author(view, entry->author))
4460 return TRUE;
4462 if (opt_date && draw_date(view, &entry->time))
4463 return TRUE;
4465 if (draw_text(view, line->type, entry->name, TRUE))
4466 return TRUE;
4467 return TRUE;
4470 static void
4471 open_blob_editor()
4473 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4474 int fd = mkstemp(file);
4476 if (fd == -1)
4477 report("Failed to create temporary file");
4478 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4479 report("Failed to save blob data to file");
4480 else
4481 open_editor(file);
4482 if (fd != -1)
4483 unlink(file);
4486 static enum request
4487 tree_request(struct view *view, enum request request, struct line *line)
4489 enum open_flags flags;
4491 switch (request) {
4492 case REQ_VIEW_BLAME:
4493 if (line->type != LINE_TREE_FILE) {
4494 report("Blame only supported for files");
4495 return REQ_NONE;
4498 string_copy(opt_ref, view->vid);
4499 return request;
4501 case REQ_EDIT:
4502 if (line->type != LINE_TREE_FILE) {
4503 report("Edit only supported for files");
4504 } else if (!is_head_commit(view->vid)) {
4505 open_blob_editor();
4506 } else {
4507 open_editor(opt_file);
4509 return REQ_NONE;
4511 case REQ_TOGGLE_SORT_FIELD:
4512 case REQ_TOGGLE_SORT_ORDER:
4513 sort_view(view, request, &tree_sort_state, tree_compare);
4514 return REQ_NONE;
4516 case REQ_PARENT:
4517 if (!*opt_path) {
4518 /* quit view if at top of tree */
4519 return REQ_VIEW_CLOSE;
4521 /* fake 'cd ..' */
4522 line = &view->line[1];
4523 break;
4525 case REQ_ENTER:
4526 break;
4528 default:
4529 return request;
4532 /* Cleanup the stack if the tree view is at a different tree. */
4533 while (!*opt_path && tree_stack)
4534 pop_tree_stack_entry();
4536 switch (line->type) {
4537 case LINE_TREE_DIR:
4538 /* Depending on whether it is a subdirectory or parent link
4539 * mangle the path buffer. */
4540 if (line == &view->line[1] && *opt_path) {
4541 pop_tree_stack_entry();
4543 } else {
4544 const char *basename = tree_path(line);
4546 push_tree_stack_entry(basename, view->lineno);
4549 /* Trees and subtrees share the same ID, so they are not not
4550 * unique like blobs. */
4551 flags = OPEN_RELOAD;
4552 request = REQ_VIEW_TREE;
4553 break;
4555 case LINE_TREE_FILE:
4556 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4557 request = REQ_VIEW_BLOB;
4558 break;
4560 default:
4561 return REQ_NONE;
4564 open_view(view, request, flags);
4565 if (request == REQ_VIEW_TREE)
4566 view->lineno = tree_lineno;
4568 return REQ_NONE;
4571 static bool
4572 tree_grep(struct view *view, struct line *line)
4574 struct tree_entry *entry = line->data;
4575 const char *text[] = {
4576 entry->name,
4577 opt_author ? entry->author : "",
4578 opt_date ? mkdate(&entry->time) : "",
4579 NULL
4582 return grep_text(view, text);
4585 static void
4586 tree_select(struct view *view, struct line *line)
4588 struct tree_entry *entry = line->data;
4590 if (line->type == LINE_TREE_FILE) {
4591 string_copy_rev(ref_blob, entry->id);
4592 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4594 } else if (line->type != LINE_TREE_DIR) {
4595 return;
4598 string_copy_rev(view->ref, entry->id);
4601 static bool
4602 tree_prepare(struct view *view)
4604 if (view->lines == 0 && opt_prefix[0]) {
4605 char *pos = opt_prefix;
4607 while (pos && *pos) {
4608 char *end = strchr(pos, '/');
4610 if (end)
4611 *end = 0;
4612 push_tree_stack_entry(pos, 0);
4613 pos = end;
4614 if (end) {
4615 *end = '/';
4616 pos++;
4620 } else if (strcmp(view->vid, view->id)) {
4621 opt_path[0] = 0;
4624 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4627 static const char *tree_argv[SIZEOF_ARG] = {
4628 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4631 static struct view_ops tree_ops = {
4632 "file",
4633 tree_argv,
4634 NULL,
4635 tree_read,
4636 tree_draw,
4637 tree_request,
4638 tree_grep,
4639 tree_select,
4640 tree_prepare,
4643 static bool
4644 blob_read(struct view *view, char *line)
4646 if (!line)
4647 return TRUE;
4648 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4651 static enum request
4652 blob_request(struct view *view, enum request request, struct line *line)
4654 switch (request) {
4655 case REQ_EDIT:
4656 open_blob_editor();
4657 return REQ_NONE;
4658 default:
4659 return pager_request(view, request, line);
4663 static const char *blob_argv[SIZEOF_ARG] = {
4664 "git", "cat-file", "blob", "%(blob)", NULL
4667 static struct view_ops blob_ops = {
4668 "line",
4669 blob_argv,
4670 NULL,
4671 blob_read,
4672 pager_draw,
4673 blob_request,
4674 pager_grep,
4675 pager_select,
4679 * Blame backend
4681 * Loading the blame view is a two phase job:
4683 * 1. File content is read either using opt_file from the
4684 * filesystem or using git-cat-file.
4685 * 2. Then blame information is incrementally added by
4686 * reading output from git-blame.
4689 static const char *blame_head_argv[] = {
4690 "git", "blame", "--incremental", "--", "%(file)", NULL
4693 static const char *blame_ref_argv[] = {
4694 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4697 static const char *blame_cat_file_argv[] = {
4698 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4701 struct blame_commit {
4702 char id[SIZEOF_REV]; /* SHA1 ID. */
4703 char title[128]; /* First line of the commit message. */
4704 const char *author; /* Author of the commit. */
4705 struct time time; /* Date from the author ident. */
4706 char filename[128]; /* Name of file. */
4707 bool has_previous; /* Was a "previous" line detected. */
4710 struct blame {
4711 struct blame_commit *commit;
4712 unsigned long lineno;
4713 char text[1];
4716 static bool
4717 blame_open(struct view *view)
4719 char path[SIZEOF_STR];
4721 if (!view->parent && *opt_prefix) {
4722 string_copy(path, opt_file);
4723 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4724 return FALSE;
4727 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4728 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4729 return FALSE;
4732 setup_update(view, opt_file);
4733 string_format(view->ref, "%s ...", opt_file);
4735 return TRUE;
4738 static struct blame_commit *
4739 get_blame_commit(struct view *view, const char *id)
4741 size_t i;
4743 for (i = 0; i < view->lines; i++) {
4744 struct blame *blame = view->line[i].data;
4746 if (!blame->commit)
4747 continue;
4749 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4750 return blame->commit;
4754 struct blame_commit *commit = calloc(1, sizeof(*commit));
4756 if (commit)
4757 string_ncopy(commit->id, id, SIZEOF_REV);
4758 return commit;
4762 static bool
4763 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4765 const char *pos = *posref;
4767 *posref = NULL;
4768 pos = strchr(pos + 1, ' ');
4769 if (!pos || !isdigit(pos[1]))
4770 return FALSE;
4771 *number = atoi(pos + 1);
4772 if (*number < min || *number > max)
4773 return FALSE;
4775 *posref = pos;
4776 return TRUE;
4779 static struct blame_commit *
4780 parse_blame_commit(struct view *view, const char *text, int *blamed)
4782 struct blame_commit *commit;
4783 struct blame *blame;
4784 const char *pos = text + SIZEOF_REV - 2;
4785 size_t orig_lineno = 0;
4786 size_t lineno;
4787 size_t group;
4789 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4790 return NULL;
4792 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4793 !parse_number(&pos, &lineno, 1, view->lines) ||
4794 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4795 return NULL;
4797 commit = get_blame_commit(view, text);
4798 if (!commit)
4799 return NULL;
4801 *blamed += group;
4802 while (group--) {
4803 struct line *line = &view->line[lineno + group - 1];
4805 blame = line->data;
4806 blame->commit = commit;
4807 blame->lineno = orig_lineno + group - 1;
4808 line->dirty = 1;
4811 return commit;
4814 static bool
4815 blame_read_file(struct view *view, const char *line, bool *read_file)
4817 if (!line) {
4818 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4819 struct io io = {};
4821 if (view->lines == 0 && !view->parent)
4822 die("No blame exist for %s", view->vid);
4824 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4825 report("Failed to load blame data");
4826 return TRUE;
4829 done_io(view->pipe);
4830 view->io = io;
4831 *read_file = FALSE;
4832 return FALSE;
4834 } else {
4835 size_t linelen = strlen(line);
4836 struct blame *blame = malloc(sizeof(*blame) + linelen);
4838 if (!blame)
4839 return FALSE;
4841 blame->commit = NULL;
4842 strncpy(blame->text, line, linelen);
4843 blame->text[linelen] = 0;
4844 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4848 static bool
4849 match_blame_header(const char *name, char **line)
4851 size_t namelen = strlen(name);
4852 bool matched = !strncmp(name, *line, namelen);
4854 if (matched)
4855 *line += namelen;
4857 return matched;
4860 static bool
4861 blame_read(struct view *view, char *line)
4863 static struct blame_commit *commit = NULL;
4864 static int blamed = 0;
4865 static bool read_file = TRUE;
4867 if (read_file)
4868 return blame_read_file(view, line, &read_file);
4870 if (!line) {
4871 /* Reset all! */
4872 commit = NULL;
4873 blamed = 0;
4874 read_file = TRUE;
4875 string_format(view->ref, "%s", view->vid);
4876 if (view_is_displayed(view)) {
4877 update_view_title(view);
4878 redraw_view_from(view, 0);
4880 return TRUE;
4883 if (!commit) {
4884 commit = parse_blame_commit(view, line, &blamed);
4885 string_format(view->ref, "%s %2d%%", view->vid,
4886 view->lines ? blamed * 100 / view->lines : 0);
4888 } else if (match_blame_header("author ", &line)) {
4889 commit->author = get_author(line);
4891 } else if (match_blame_header("author-time ", &line)) {
4892 parse_timesec(&commit->time, line);
4894 } else if (match_blame_header("author-tz ", &line)) {
4895 parse_timezone(&commit->time, line);
4897 } else if (match_blame_header("summary ", &line)) {
4898 string_ncopy(commit->title, line, strlen(line));
4900 } else if (match_blame_header("previous ", &line)) {
4901 commit->has_previous = TRUE;
4903 } else if (match_blame_header("filename ", &line)) {
4904 string_ncopy(commit->filename, line, strlen(line));
4905 commit = NULL;
4908 return TRUE;
4911 static bool
4912 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4914 struct blame *blame = line->data;
4915 struct time *time = NULL;
4916 const char *id = NULL, *author = NULL;
4917 char text[SIZEOF_STR];
4919 if (blame->commit && *blame->commit->filename) {
4920 id = blame->commit->id;
4921 author = blame->commit->author;
4922 time = &blame->commit->time;
4925 if (opt_date && draw_date(view, time))
4926 return TRUE;
4928 if (opt_author && draw_author(view, author))
4929 return TRUE;
4931 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4932 return TRUE;
4934 if (draw_lineno(view, lineno))
4935 return TRUE;
4937 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4938 draw_text(view, LINE_DEFAULT, text, TRUE);
4939 return TRUE;
4942 static bool
4943 check_blame_commit(struct blame *blame, bool check_null_id)
4945 if (!blame->commit)
4946 report("Commit data not loaded yet");
4947 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4948 report("No commit exist for the selected line");
4949 else
4950 return TRUE;
4951 return FALSE;
4954 static void
4955 setup_blame_parent_line(struct view *view, struct blame *blame)
4957 const char *diff_tree_argv[] = {
4958 "git", "diff-tree", "-U0", blame->commit->id,
4959 "--", blame->commit->filename, NULL
4961 struct io io = {};
4962 int parent_lineno = -1;
4963 int blamed_lineno = -1;
4964 char *line;
4966 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4967 return;
4969 while ((line = io_get(&io, '\n', TRUE))) {
4970 if (*line == '@') {
4971 char *pos = strchr(line, '+');
4973 parent_lineno = atoi(line + 4);
4974 if (pos)
4975 blamed_lineno = atoi(pos + 1);
4977 } else if (*line == '+' && parent_lineno != -1) {
4978 if (blame->lineno == blamed_lineno - 1 &&
4979 !strcmp(blame->text, line + 1)) {
4980 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4981 break;
4983 blamed_lineno++;
4987 done_io(&io);
4990 static enum request
4991 blame_request(struct view *view, enum request request, struct line *line)
4993 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4994 struct blame *blame = line->data;
4996 switch (request) {
4997 case REQ_VIEW_BLAME:
4998 if (check_blame_commit(blame, TRUE)) {
4999 string_copy(opt_ref, blame->commit->id);
5000 string_copy(opt_file, blame->commit->filename);
5001 if (blame->lineno)
5002 view->lineno = blame->lineno;
5003 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5005 break;
5007 case REQ_PARENT:
5008 if (check_blame_commit(blame, TRUE) &&
5009 select_commit_parent(blame->commit->id, opt_ref,
5010 blame->commit->filename)) {
5011 string_copy(opt_file, blame->commit->filename);
5012 setup_blame_parent_line(view, blame);
5013 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5015 break;
5017 case REQ_ENTER:
5018 if (!check_blame_commit(blame, FALSE))
5019 break;
5021 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5022 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5023 break;
5025 if (!strcmp(blame->commit->id, NULL_ID)) {
5026 struct view *diff = VIEW(REQ_VIEW_DIFF);
5027 const char *diff_index_argv[] = {
5028 "git", "diff-index", "--root", "--patch-with-stat",
5029 "-C", "-M", "HEAD", "--", view->vid, NULL
5032 if (!blame->commit->has_previous) {
5033 diff_index_argv[1] = "diff";
5034 diff_index_argv[2] = "--no-color";
5035 diff_index_argv[6] = "--";
5036 diff_index_argv[7] = "/dev/null";
5039 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5040 report("Failed to allocate diff command");
5041 break;
5043 flags |= OPEN_PREPARED;
5046 open_view(view, REQ_VIEW_DIFF, flags);
5047 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5048 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5049 break;
5051 default:
5052 return request;
5055 return REQ_NONE;
5058 static bool
5059 blame_grep(struct view *view, struct line *line)
5061 struct blame *blame = line->data;
5062 struct blame_commit *commit = blame->commit;
5063 const char *text[] = {
5064 blame->text,
5065 commit ? commit->title : "",
5066 commit ? commit->id : "",
5067 commit && opt_author ? commit->author : "",
5068 commit && opt_date ? mkdate(&commit->time) : "",
5069 NULL
5072 return grep_text(view, text);
5075 static void
5076 blame_select(struct view *view, struct line *line)
5078 struct blame *blame = line->data;
5079 struct blame_commit *commit = blame->commit;
5081 if (!commit)
5082 return;
5084 if (!strcmp(commit->id, NULL_ID))
5085 string_ncopy(ref_commit, "HEAD", 4);
5086 else
5087 string_copy_rev(ref_commit, commit->id);
5090 static struct view_ops blame_ops = {
5091 "line",
5092 NULL,
5093 blame_open,
5094 blame_read,
5095 blame_draw,
5096 blame_request,
5097 blame_grep,
5098 blame_select,
5102 * Branch backend
5105 struct branch {
5106 const char *author; /* Author of the last commit. */
5107 struct time time; /* Date of the last activity. */
5108 const struct ref *ref; /* Name and commit ID information. */
5111 static const struct ref branch_all;
5113 static const enum sort_field branch_sort_fields[] = {
5114 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5116 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5118 static int
5119 branch_compare(const void *l1, const void *l2)
5121 const struct branch *branch1 = ((const struct line *) l1)->data;
5122 const struct branch *branch2 = ((const struct line *) l2)->data;
5124 switch (get_sort_field(branch_sort_state)) {
5125 case ORDERBY_DATE:
5126 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5128 case ORDERBY_AUTHOR:
5129 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5131 case ORDERBY_NAME:
5132 default:
5133 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5137 static bool
5138 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5140 struct branch *branch = line->data;
5141 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5143 if (opt_date && draw_date(view, &branch->time))
5144 return TRUE;
5146 if (opt_author && draw_author(view, branch->author))
5147 return TRUE;
5149 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5150 return TRUE;
5153 static enum request
5154 branch_request(struct view *view, enum request request, struct line *line)
5156 struct branch *branch = line->data;
5158 switch (request) {
5159 case REQ_REFRESH:
5160 load_refs();
5161 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5162 return REQ_NONE;
5164 case REQ_TOGGLE_SORT_FIELD:
5165 case REQ_TOGGLE_SORT_ORDER:
5166 sort_view(view, request, &branch_sort_state, branch_compare);
5167 return REQ_NONE;
5169 case REQ_ENTER:
5170 if (branch->ref == &branch_all) {
5171 const char *all_branches_argv[] = {
5172 "git", "log", "--no-color", "--pretty=raw", "--parents",
5173 "--topo-order", "--all", NULL
5175 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5177 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5178 report("Failed to load view of all branches");
5179 return REQ_NONE;
5181 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5182 } else {
5183 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5185 return REQ_NONE;
5187 default:
5188 return request;
5192 static bool
5193 branch_read(struct view *view, char *line)
5195 static char id[SIZEOF_REV];
5196 struct branch *reference;
5197 size_t i;
5199 if (!line)
5200 return TRUE;
5202 switch (get_line_type(line)) {
5203 case LINE_COMMIT:
5204 string_copy_rev(id, line + STRING_SIZE("commit "));
5205 return TRUE;
5207 case LINE_AUTHOR:
5208 for (i = 0, reference = NULL; i < view->lines; i++) {
5209 struct branch *branch = view->line[i].data;
5211 if (strcmp(branch->ref->id, id))
5212 continue;
5214 view->line[i].dirty = TRUE;
5215 if (reference) {
5216 branch->author = reference->author;
5217 branch->time = reference->time;
5218 continue;
5221 parse_author_line(line + STRING_SIZE("author "),
5222 &branch->author, &branch->time);
5223 reference = branch;
5225 return TRUE;
5227 default:
5228 return TRUE;
5233 static bool
5234 branch_open_visitor(void *data, const struct ref *ref)
5236 struct view *view = data;
5237 struct branch *branch;
5239 if (ref->tag || ref->ltag || ref->remote)
5240 return TRUE;
5242 branch = calloc(1, sizeof(*branch));
5243 if (!branch)
5244 return FALSE;
5246 branch->ref = ref;
5247 return !!add_line_data(view, branch, LINE_DEFAULT);
5250 static bool
5251 branch_open(struct view *view)
5253 const char *branch_log[] = {
5254 "git", "log", "--no-color", "--pretty=raw",
5255 "--simplify-by-decoration", "--all", NULL
5258 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5259 report("Failed to load branch data");
5260 return TRUE;
5263 setup_update(view, view->id);
5264 branch_open_visitor(view, &branch_all);
5265 foreach_ref(branch_open_visitor, view);
5266 view->p_restore = TRUE;
5268 return TRUE;
5271 static bool
5272 branch_grep(struct view *view, struct line *line)
5274 struct branch *branch = line->data;
5275 const char *text[] = {
5276 branch->ref->name,
5277 branch->author,
5278 NULL
5281 return grep_text(view, text);
5284 static void
5285 branch_select(struct view *view, struct line *line)
5287 struct branch *branch = line->data;
5289 string_copy_rev(view->ref, branch->ref->id);
5290 string_copy_rev(ref_commit, branch->ref->id);
5291 string_copy_rev(ref_head, branch->ref->id);
5294 static struct view_ops branch_ops = {
5295 "branch",
5296 NULL,
5297 branch_open,
5298 branch_read,
5299 branch_draw,
5300 branch_request,
5301 branch_grep,
5302 branch_select,
5306 * Status backend
5309 struct status {
5310 char status;
5311 struct {
5312 mode_t mode;
5313 char rev[SIZEOF_REV];
5314 char name[SIZEOF_STR];
5315 } old;
5316 struct {
5317 mode_t mode;
5318 char rev[SIZEOF_REV];
5319 char name[SIZEOF_STR];
5320 } new;
5323 static char status_onbranch[SIZEOF_STR];
5324 static struct status stage_status;
5325 static enum line_type stage_line_type;
5326 static size_t stage_chunks;
5327 static int *stage_chunk;
5329 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5331 /* This should work even for the "On branch" line. */
5332 static inline bool
5333 status_has_none(struct view *view, struct line *line)
5335 return line < view->line + view->lines && !line[1].data;
5338 /* Get fields from the diff line:
5339 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5341 static inline bool
5342 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5344 const char *old_mode = buf + 1;
5345 const char *new_mode = buf + 8;
5346 const char *old_rev = buf + 15;
5347 const char *new_rev = buf + 56;
5348 const char *status = buf + 97;
5350 if (bufsize < 98 ||
5351 old_mode[-1] != ':' ||
5352 new_mode[-1] != ' ' ||
5353 old_rev[-1] != ' ' ||
5354 new_rev[-1] != ' ' ||
5355 status[-1] != ' ')
5356 return FALSE;
5358 file->status = *status;
5360 string_copy_rev(file->old.rev, old_rev);
5361 string_copy_rev(file->new.rev, new_rev);
5363 file->old.mode = strtoul(old_mode, NULL, 8);
5364 file->new.mode = strtoul(new_mode, NULL, 8);
5366 file->old.name[0] = file->new.name[0] = 0;
5368 return TRUE;
5371 static bool
5372 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5374 struct status *unmerged = NULL;
5375 char *buf;
5376 struct io io = {};
5378 if (!run_io(&io, argv, opt_cdup, IO_RD))
5379 return FALSE;
5381 add_line_data(view, NULL, type);
5383 while ((buf = io_get(&io, 0, TRUE))) {
5384 struct status *file = unmerged;
5386 if (!file) {
5387 file = calloc(1, sizeof(*file));
5388 if (!file || !add_line_data(view, file, type))
5389 goto error_out;
5392 /* Parse diff info part. */
5393 if (status) {
5394 file->status = status;
5395 if (status == 'A')
5396 string_copy(file->old.rev, NULL_ID);
5398 } else if (!file->status || file == unmerged) {
5399 if (!status_get_diff(file, buf, strlen(buf)))
5400 goto error_out;
5402 buf = io_get(&io, 0, TRUE);
5403 if (!buf)
5404 break;
5406 /* Collapse all modified entries that follow an
5407 * associated unmerged entry. */
5408 if (unmerged == file) {
5409 unmerged->status = 'U';
5410 unmerged = NULL;
5411 } else if (file->status == 'U') {
5412 unmerged = file;
5416 /* Grab the old name for rename/copy. */
5417 if (!*file->old.name &&
5418 (file->status == 'R' || file->status == 'C')) {
5419 string_ncopy(file->old.name, buf, strlen(buf));
5421 buf = io_get(&io, 0, TRUE);
5422 if (!buf)
5423 break;
5426 /* git-ls-files just delivers a NUL separated list of
5427 * file names similar to the second half of the
5428 * git-diff-* output. */
5429 string_ncopy(file->new.name, buf, strlen(buf));
5430 if (!*file->old.name)
5431 string_copy(file->old.name, file->new.name);
5432 file = NULL;
5435 if (io_error(&io)) {
5436 error_out:
5437 done_io(&io);
5438 return FALSE;
5441 if (!view->line[view->lines - 1].data)
5442 add_line_data(view, NULL, LINE_STAT_NONE);
5444 done_io(&io);
5445 return TRUE;
5448 /* Don't show unmerged entries in the staged section. */
5449 static const char *status_diff_index_argv[] = {
5450 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5451 "--cached", "-M", "HEAD", NULL
5454 static const char *status_diff_files_argv[] = {
5455 "git", "diff-files", "-z", NULL
5458 static const char *status_list_other_argv[] = {
5459 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5462 static const char *status_list_no_head_argv[] = {
5463 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5466 static const char *update_index_argv[] = {
5467 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5470 /* Restore the previous line number to stay in the context or select a
5471 * line with something that can be updated. */
5472 static void
5473 status_restore(struct view *view)
5475 if (view->p_lineno >= view->lines)
5476 view->p_lineno = view->lines - 1;
5477 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5478 view->p_lineno++;
5479 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5480 view->p_lineno--;
5482 /* If the above fails, always skip the "On branch" line. */
5483 if (view->p_lineno < view->lines)
5484 view->lineno = view->p_lineno;
5485 else
5486 view->lineno = 1;
5488 if (view->lineno < view->offset)
5489 view->offset = view->lineno;
5490 else if (view->offset + view->height <= view->lineno)
5491 view->offset = view->lineno - view->height + 1;
5493 view->p_restore = FALSE;
5496 static void
5497 status_update_onbranch(void)
5499 static const char *paths[][2] = {
5500 { "rebase-apply/rebasing", "Rebasing" },
5501 { "rebase-apply/applying", "Applying mailbox" },
5502 { "rebase-apply/", "Rebasing mailbox" },
5503 { "rebase-merge/interactive", "Interactive rebase" },
5504 { "rebase-merge/", "Rebase merge" },
5505 { "MERGE_HEAD", "Merging" },
5506 { "BISECT_LOG", "Bisecting" },
5507 { "HEAD", "On branch" },
5509 char buf[SIZEOF_STR];
5510 struct stat stat;
5511 int i;
5513 if (is_initial_commit()) {
5514 string_copy(status_onbranch, "Initial commit");
5515 return;
5518 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5519 char *head = opt_head;
5521 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5522 lstat(buf, &stat) < 0)
5523 continue;
5525 if (!*opt_head) {
5526 struct io io = {};
5528 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5529 io_read_buf(&io, buf, sizeof(buf))) {
5530 head = buf;
5531 if (!prefixcmp(head, "refs/heads/"))
5532 head += STRING_SIZE("refs/heads/");
5536 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5537 string_copy(status_onbranch, opt_head);
5538 return;
5541 string_copy(status_onbranch, "Not currently on any branch");
5544 /* First parse staged info using git-diff-index(1), then parse unstaged
5545 * info using git-diff-files(1), and finally untracked files using
5546 * git-ls-files(1). */
5547 static bool
5548 status_open(struct view *view)
5550 reset_view(view);
5552 add_line_data(view, NULL, LINE_STAT_HEAD);
5553 status_update_onbranch();
5555 run_io_bg(update_index_argv);
5557 if (is_initial_commit()) {
5558 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5559 return FALSE;
5560 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5561 return FALSE;
5564 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5565 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5566 return FALSE;
5568 /* Restore the exact position or use the specialized restore
5569 * mode? */
5570 if (!view->p_restore)
5571 status_restore(view);
5572 return TRUE;
5575 static bool
5576 status_draw(struct view *view, struct line *line, unsigned int lineno)
5578 struct status *status = line->data;
5579 enum line_type type;
5580 const char *text;
5582 if (!status) {
5583 switch (line->type) {
5584 case LINE_STAT_STAGED:
5585 type = LINE_STAT_SECTION;
5586 text = "Changes to be committed:";
5587 break;
5589 case LINE_STAT_UNSTAGED:
5590 type = LINE_STAT_SECTION;
5591 text = "Changed but not updated:";
5592 break;
5594 case LINE_STAT_UNTRACKED:
5595 type = LINE_STAT_SECTION;
5596 text = "Untracked files:";
5597 break;
5599 case LINE_STAT_NONE:
5600 type = LINE_DEFAULT;
5601 text = " (no files)";
5602 break;
5604 case LINE_STAT_HEAD:
5605 type = LINE_STAT_HEAD;
5606 text = status_onbranch;
5607 break;
5609 default:
5610 return FALSE;
5612 } else {
5613 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5615 buf[0] = status->status;
5616 if (draw_text(view, line->type, buf, TRUE))
5617 return TRUE;
5618 type = LINE_DEFAULT;
5619 text = status->new.name;
5622 draw_text(view, type, text, TRUE);
5623 return TRUE;
5626 static enum request
5627 status_load_error(struct view *view, struct view *stage, const char *path)
5629 if (displayed_views() == 2 || display[current_view] != view)
5630 maximize_view(view);
5631 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5632 return REQ_NONE;
5635 static enum request
5636 status_enter(struct view *view, struct line *line)
5638 struct status *status = line->data;
5639 const char *oldpath = status ? status->old.name : NULL;
5640 /* Diffs for unmerged entries are empty when passing the new
5641 * path, so leave it empty. */
5642 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5643 const char *info;
5644 enum open_flags split;
5645 struct view *stage = VIEW(REQ_VIEW_STAGE);
5647 if (line->type == LINE_STAT_NONE ||
5648 (!status && line[1].type == LINE_STAT_NONE)) {
5649 report("No file to diff");
5650 return REQ_NONE;
5653 switch (line->type) {
5654 case LINE_STAT_STAGED:
5655 if (is_initial_commit()) {
5656 const char *no_head_diff_argv[] = {
5657 "git", "diff", "--no-color", "--patch-with-stat",
5658 "--", "/dev/null", newpath, NULL
5661 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5662 return status_load_error(view, stage, newpath);
5663 } else {
5664 const char *index_show_argv[] = {
5665 "git", "diff-index", "--root", "--patch-with-stat",
5666 "-C", "-M", "--cached", "HEAD", "--",
5667 oldpath, newpath, NULL
5670 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5671 return status_load_error(view, stage, newpath);
5674 if (status)
5675 info = "Staged changes to %s";
5676 else
5677 info = "Staged changes";
5678 break;
5680 case LINE_STAT_UNSTAGED:
5682 const char *files_show_argv[] = {
5683 "git", "diff-files", "--root", "--patch-with-stat",
5684 "-C", "-M", "--", oldpath, newpath, NULL
5687 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5688 return status_load_error(view, stage, newpath);
5689 if (status)
5690 info = "Unstaged changes to %s";
5691 else
5692 info = "Unstaged changes";
5693 break;
5695 case LINE_STAT_UNTRACKED:
5696 if (!newpath) {
5697 report("No file to show");
5698 return REQ_NONE;
5701 if (!suffixcmp(status->new.name, -1, "/")) {
5702 report("Cannot display a directory");
5703 return REQ_NONE;
5706 if (!prepare_update_file(stage, newpath))
5707 return status_load_error(view, stage, newpath);
5708 info = "Untracked file %s";
5709 break;
5711 case LINE_STAT_HEAD:
5712 return REQ_NONE;
5714 default:
5715 die("line type %d not handled in switch", line->type);
5718 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5719 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5720 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5721 if (status) {
5722 stage_status = *status;
5723 } else {
5724 memset(&stage_status, 0, sizeof(stage_status));
5727 stage_line_type = line->type;
5728 stage_chunks = 0;
5729 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5732 return REQ_NONE;
5735 static bool
5736 status_exists(struct status *status, enum line_type type)
5738 struct view *view = VIEW(REQ_VIEW_STATUS);
5739 unsigned long lineno;
5741 for (lineno = 0; lineno < view->lines; lineno++) {
5742 struct line *line = &view->line[lineno];
5743 struct status *pos = line->data;
5745 if (line->type != type)
5746 continue;
5747 if (!pos && (!status || !status->status) && line[1].data) {
5748 select_view_line(view, lineno);
5749 return TRUE;
5751 if (pos && !strcmp(status->new.name, pos->new.name)) {
5752 select_view_line(view, lineno);
5753 return TRUE;
5757 return FALSE;
5761 static bool
5762 status_update_prepare(struct io *io, enum line_type type)
5764 const char *staged_argv[] = {
5765 "git", "update-index", "-z", "--index-info", NULL
5767 const char *others_argv[] = {
5768 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5771 switch (type) {
5772 case LINE_STAT_STAGED:
5773 return run_io(io, staged_argv, opt_cdup, IO_WR);
5775 case LINE_STAT_UNSTAGED:
5776 case LINE_STAT_UNTRACKED:
5777 return run_io(io, others_argv, opt_cdup, IO_WR);
5779 default:
5780 die("line type %d not handled in switch", type);
5781 return FALSE;
5785 static bool
5786 status_update_write(struct io *io, struct status *status, enum line_type type)
5788 char buf[SIZEOF_STR];
5789 size_t bufsize = 0;
5791 switch (type) {
5792 case LINE_STAT_STAGED:
5793 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5794 status->old.mode,
5795 status->old.rev,
5796 status->old.name, 0))
5797 return FALSE;
5798 break;
5800 case LINE_STAT_UNSTAGED:
5801 case LINE_STAT_UNTRACKED:
5802 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5803 return FALSE;
5804 break;
5806 default:
5807 die("line type %d not handled in switch", type);
5810 return io_write(io, buf, bufsize);
5813 static bool
5814 status_update_file(struct status *status, enum line_type type)
5816 struct io io = {};
5817 bool result;
5819 if (!status_update_prepare(&io, type))
5820 return FALSE;
5822 result = status_update_write(&io, status, type);
5823 return done_io(&io) && result;
5826 static bool
5827 status_update_files(struct view *view, struct line *line)
5829 char buf[sizeof(view->ref)];
5830 struct io io = {};
5831 bool result = TRUE;
5832 struct line *pos = view->line + view->lines;
5833 int files = 0;
5834 int file, done;
5835 int cursor_y = -1, cursor_x = -1;
5837 if (!status_update_prepare(&io, line->type))
5838 return FALSE;
5840 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5841 files++;
5843 string_copy(buf, view->ref);
5844 getsyx(cursor_y, cursor_x);
5845 for (file = 0, done = 5; result && file < files; line++, file++) {
5846 int almost_done = file * 100 / files;
5848 if (almost_done > done) {
5849 done = almost_done;
5850 string_format(view->ref, "updating file %u of %u (%d%% done)",
5851 file, files, done);
5852 update_view_title(view);
5853 setsyx(cursor_y, cursor_x);
5854 doupdate();
5856 result = status_update_write(&io, line->data, line->type);
5858 string_copy(view->ref, buf);
5860 return done_io(&io) && result;
5863 static bool
5864 status_update(struct view *view)
5866 struct line *line = &view->line[view->lineno];
5868 assert(view->lines);
5870 if (!line->data) {
5871 /* This should work even for the "On branch" line. */
5872 if (line < view->line + view->lines && !line[1].data) {
5873 report("Nothing to update");
5874 return FALSE;
5877 if (!status_update_files(view, line + 1)) {
5878 report("Failed to update file status");
5879 return FALSE;
5882 } else if (!status_update_file(line->data, line->type)) {
5883 report("Failed to update file status");
5884 return FALSE;
5887 return TRUE;
5890 static bool
5891 status_revert(struct status *status, enum line_type type, bool has_none)
5893 if (!status || type != LINE_STAT_UNSTAGED) {
5894 if (type == LINE_STAT_STAGED) {
5895 report("Cannot revert changes to staged files");
5896 } else if (type == LINE_STAT_UNTRACKED) {
5897 report("Cannot revert changes to untracked files");
5898 } else if (has_none) {
5899 report("Nothing to revert");
5900 } else {
5901 report("Cannot revert changes to multiple files");
5904 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5905 char mode[10] = "100644";
5906 const char *reset_argv[] = {
5907 "git", "update-index", "--cacheinfo", mode,
5908 status->old.rev, status->old.name, NULL
5910 const char *checkout_argv[] = {
5911 "git", "checkout", "--", status->old.name, NULL
5914 if (status->status == 'U') {
5915 string_format(mode, "%5o", status->old.mode);
5917 if (status->old.mode == 0 && status->new.mode == 0) {
5918 reset_argv[2] = "--force-remove";
5919 reset_argv[3] = status->old.name;
5920 reset_argv[4] = NULL;
5923 if (!run_io_fg(reset_argv, opt_cdup))
5924 return FALSE;
5925 if (status->old.mode == 0 && status->new.mode == 0)
5926 return TRUE;
5929 return run_io_fg(checkout_argv, opt_cdup);
5932 return FALSE;
5935 static enum request
5936 status_request(struct view *view, enum request request, struct line *line)
5938 struct status *status = line->data;
5940 switch (request) {
5941 case REQ_STATUS_UPDATE:
5942 if (!status_update(view))
5943 return REQ_NONE;
5944 break;
5946 case REQ_STATUS_REVERT:
5947 if (!status_revert(status, line->type, status_has_none(view, line)))
5948 return REQ_NONE;
5949 break;
5951 case REQ_STATUS_MERGE:
5952 if (!status || status->status != 'U') {
5953 report("Merging only possible for files with unmerged status ('U').");
5954 return REQ_NONE;
5956 open_mergetool(status->new.name);
5957 break;
5959 case REQ_EDIT:
5960 if (!status)
5961 return request;
5962 if (status->status == 'D') {
5963 report("File has been deleted.");
5964 return REQ_NONE;
5967 open_editor(status->new.name);
5968 break;
5970 case REQ_VIEW_BLAME:
5971 if (status)
5972 opt_ref[0] = 0;
5973 return request;
5975 case REQ_ENTER:
5976 /* After returning the status view has been split to
5977 * show the stage view. No further reloading is
5978 * necessary. */
5979 return status_enter(view, line);
5981 case REQ_REFRESH:
5982 /* Simply reload the view. */
5983 break;
5985 default:
5986 return request;
5989 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5991 return REQ_NONE;
5994 static void
5995 status_select(struct view *view, struct line *line)
5997 struct status *status = line->data;
5998 char file[SIZEOF_STR] = "all files";
5999 const char *text;
6000 const char *key;
6002 if (status && !string_format(file, "'%s'", status->new.name))
6003 return;
6005 if (!status && line[1].type == LINE_STAT_NONE)
6006 line++;
6008 switch (line->type) {
6009 case LINE_STAT_STAGED:
6010 text = "Press %s to unstage %s for commit";
6011 break;
6013 case LINE_STAT_UNSTAGED:
6014 text = "Press %s to stage %s for commit";
6015 break;
6017 case LINE_STAT_UNTRACKED:
6018 text = "Press %s to stage %s for addition";
6019 break;
6021 case LINE_STAT_HEAD:
6022 case LINE_STAT_NONE:
6023 text = "Nothing to update";
6024 break;
6026 default:
6027 die("line type %d not handled in switch", line->type);
6030 if (status && status->status == 'U') {
6031 text = "Press %s to resolve conflict in %s";
6032 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6034 } else {
6035 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6038 string_format(view->ref, text, key, file);
6039 if (status)
6040 string_copy(opt_file, status->new.name);
6043 static bool
6044 status_grep(struct view *view, struct line *line)
6046 struct status *status = line->data;
6048 if (status) {
6049 const char buf[2] = { status->status, 0 };
6050 const char *text[] = { status->new.name, buf, NULL };
6052 return grep_text(view, text);
6055 return FALSE;
6058 static struct view_ops status_ops = {
6059 "file",
6060 NULL,
6061 status_open,
6062 NULL,
6063 status_draw,
6064 status_request,
6065 status_grep,
6066 status_select,
6070 static bool
6071 stage_diff_write(struct io *io, struct line *line, struct line *end)
6073 while (line < end) {
6074 if (!io_write(io, line->data, strlen(line->data)) ||
6075 !io_write(io, "\n", 1))
6076 return FALSE;
6077 line++;
6078 if (line->type == LINE_DIFF_CHUNK ||
6079 line->type == LINE_DIFF_HEADER)
6080 break;
6083 return TRUE;
6086 static struct line *
6087 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6089 for (; view->line < line; line--)
6090 if (line->type == type)
6091 return line;
6093 return NULL;
6096 static bool
6097 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6099 const char *apply_argv[SIZEOF_ARG] = {
6100 "git", "apply", "--whitespace=nowarn", NULL
6102 struct line *diff_hdr;
6103 struct io io = {};
6104 int argc = 3;
6106 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6107 if (!diff_hdr)
6108 return FALSE;
6110 if (!revert)
6111 apply_argv[argc++] = "--cached";
6112 if (revert || stage_line_type == LINE_STAT_STAGED)
6113 apply_argv[argc++] = "-R";
6114 apply_argv[argc++] = "-";
6115 apply_argv[argc++] = NULL;
6116 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6117 return FALSE;
6119 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6120 !stage_diff_write(&io, chunk, view->line + view->lines))
6121 chunk = NULL;
6123 done_io(&io);
6124 run_io_bg(update_index_argv);
6126 return chunk ? TRUE : FALSE;
6129 static bool
6130 stage_update(struct view *view, struct line *line)
6132 struct line *chunk = NULL;
6134 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6135 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6137 if (chunk) {
6138 if (!stage_apply_chunk(view, chunk, FALSE)) {
6139 report("Failed to apply chunk");
6140 return FALSE;
6143 } else if (!stage_status.status) {
6144 view = VIEW(REQ_VIEW_STATUS);
6146 for (line = view->line; line < view->line + view->lines; line++)
6147 if (line->type == stage_line_type)
6148 break;
6150 if (!status_update_files(view, line + 1)) {
6151 report("Failed to update files");
6152 return FALSE;
6155 } else if (!status_update_file(&stage_status, stage_line_type)) {
6156 report("Failed to update file");
6157 return FALSE;
6160 return TRUE;
6163 static bool
6164 stage_revert(struct view *view, struct line *line)
6166 struct line *chunk = NULL;
6168 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6169 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6171 if (chunk) {
6172 if (!prompt_yesno("Are you sure you want to revert changes?"))
6173 return FALSE;
6175 if (!stage_apply_chunk(view, chunk, TRUE)) {
6176 report("Failed to revert chunk");
6177 return FALSE;
6179 return TRUE;
6181 } else {
6182 return status_revert(stage_status.status ? &stage_status : NULL,
6183 stage_line_type, FALSE);
6188 static void
6189 stage_next(struct view *view, struct line *line)
6191 int i;
6193 if (!stage_chunks) {
6194 for (line = view->line; line < view->line + view->lines; line++) {
6195 if (line->type != LINE_DIFF_CHUNK)
6196 continue;
6198 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6199 report("Allocation failure");
6200 return;
6203 stage_chunk[stage_chunks++] = line - view->line;
6207 for (i = 0; i < stage_chunks; i++) {
6208 if (stage_chunk[i] > view->lineno) {
6209 do_scroll_view(view, stage_chunk[i] - view->lineno);
6210 report("Chunk %d of %d", i + 1, stage_chunks);
6211 return;
6215 report("No next chunk found");
6218 static enum request
6219 stage_request(struct view *view, enum request request, struct line *line)
6221 switch (request) {
6222 case REQ_STATUS_UPDATE:
6223 if (!stage_update(view, line))
6224 return REQ_NONE;
6225 break;
6227 case REQ_STATUS_REVERT:
6228 if (!stage_revert(view, line))
6229 return REQ_NONE;
6230 break;
6232 case REQ_STAGE_NEXT:
6233 if (stage_line_type == LINE_STAT_UNTRACKED) {
6234 report("File is untracked; press %s to add",
6235 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6236 return REQ_NONE;
6238 stage_next(view, line);
6239 return REQ_NONE;
6241 case REQ_EDIT:
6242 if (!stage_status.new.name[0])
6243 return request;
6244 if (stage_status.status == 'D') {
6245 report("File has been deleted.");
6246 return REQ_NONE;
6249 open_editor(stage_status.new.name);
6250 break;
6252 case REQ_REFRESH:
6253 /* Reload everything ... */
6254 break;
6256 case REQ_VIEW_BLAME:
6257 if (stage_status.new.name[0]) {
6258 string_copy(opt_file, stage_status.new.name);
6259 opt_ref[0] = 0;
6261 return request;
6263 case REQ_ENTER:
6264 return pager_request(view, request, line);
6266 default:
6267 return request;
6270 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6271 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6273 /* Check whether the staged entry still exists, and close the
6274 * stage view if it doesn't. */
6275 if (!status_exists(&stage_status, stage_line_type)) {
6276 status_restore(VIEW(REQ_VIEW_STATUS));
6277 return REQ_VIEW_CLOSE;
6280 if (stage_line_type == LINE_STAT_UNTRACKED) {
6281 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6282 report("Cannot display a directory");
6283 return REQ_NONE;
6286 if (!prepare_update_file(view, stage_status.new.name)) {
6287 report("Failed to open file: %s", strerror(errno));
6288 return REQ_NONE;
6291 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6293 return REQ_NONE;
6296 static struct view_ops stage_ops = {
6297 "line",
6298 NULL,
6299 NULL,
6300 pager_read,
6301 pager_draw,
6302 stage_request,
6303 pager_grep,
6304 pager_select,
6309 * Revision graph
6312 struct commit {
6313 char id[SIZEOF_REV]; /* SHA1 ID. */
6314 char title[128]; /* First line of the commit message. */
6315 const char *author; /* Author of the commit. */
6316 struct time time; /* Date from the author ident. */
6317 struct ref_list *refs; /* Repository references. */
6318 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6319 size_t graph_size; /* The width of the graph array. */
6320 bool has_parents; /* Rewritten --parents seen. */
6323 /* Size of rev graph with no "padding" columns */
6324 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6326 struct rev_graph {
6327 struct rev_graph *prev, *next, *parents;
6328 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6329 size_t size;
6330 struct commit *commit;
6331 size_t pos;
6332 unsigned int boundary:1;
6335 /* Parents of the commit being visualized. */
6336 static struct rev_graph graph_parents[4];
6338 /* The current stack of revisions on the graph. */
6339 static struct rev_graph graph_stacks[4] = {
6340 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6341 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6342 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6343 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6346 static inline bool
6347 graph_parent_is_merge(struct rev_graph *graph)
6349 return graph->parents->size > 1;
6352 static inline void
6353 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6355 struct commit *commit = graph->commit;
6357 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6358 commit->graph[commit->graph_size++] = symbol;
6361 static void
6362 clear_rev_graph(struct rev_graph *graph)
6364 graph->boundary = 0;
6365 graph->size = graph->pos = 0;
6366 graph->commit = NULL;
6367 memset(graph->parents, 0, sizeof(*graph->parents));
6370 static void
6371 done_rev_graph(struct rev_graph *graph)
6373 if (graph_parent_is_merge(graph) &&
6374 graph->pos < graph->size - 1 &&
6375 graph->next->size == graph->size + graph->parents->size - 1) {
6376 size_t i = graph->pos + graph->parents->size - 1;
6378 graph->commit->graph_size = i * 2;
6379 while (i < graph->next->size - 1) {
6380 append_to_rev_graph(graph, ' ');
6381 append_to_rev_graph(graph, '\\');
6382 i++;
6386 clear_rev_graph(graph);
6389 static void
6390 push_rev_graph(struct rev_graph *graph, const char *parent)
6392 int i;
6394 /* "Collapse" duplicate parents lines.
6396 * FIXME: This needs to also update update the drawn graph but
6397 * for now it just serves as a method for pruning graph lines. */
6398 for (i = 0; i < graph->size; i++)
6399 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6400 return;
6402 if (graph->size < SIZEOF_REVITEMS) {
6403 string_copy_rev(graph->rev[graph->size++], parent);
6407 static chtype
6408 get_rev_graph_symbol(struct rev_graph *graph)
6410 chtype symbol;
6412 if (graph->boundary)
6413 symbol = REVGRAPH_BOUND;
6414 else if (graph->parents->size == 0)
6415 symbol = REVGRAPH_INIT;
6416 else if (graph_parent_is_merge(graph))
6417 symbol = REVGRAPH_MERGE;
6418 else if (graph->pos >= graph->size)
6419 symbol = REVGRAPH_BRANCH;
6420 else
6421 symbol = REVGRAPH_COMMIT;
6423 return symbol;
6426 static void
6427 draw_rev_graph(struct rev_graph *graph)
6429 struct rev_filler {
6430 chtype separator, line;
6432 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6433 static struct rev_filler fillers[] = {
6434 { ' ', '|' },
6435 { '`', '.' },
6436 { '\'', ' ' },
6437 { '/', ' ' },
6439 chtype symbol = get_rev_graph_symbol(graph);
6440 struct rev_filler *filler;
6441 size_t i;
6443 if (opt_line_graphics)
6444 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6446 filler = &fillers[DEFAULT];
6448 for (i = 0; i < graph->pos; i++) {
6449 append_to_rev_graph(graph, filler->line);
6450 if (graph_parent_is_merge(graph->prev) &&
6451 graph->prev->pos == i)
6452 filler = &fillers[RSHARP];
6454 append_to_rev_graph(graph, filler->separator);
6457 /* Place the symbol for this revision. */
6458 append_to_rev_graph(graph, symbol);
6460 if (graph->prev->size > graph->size)
6461 filler = &fillers[RDIAG];
6462 else
6463 filler = &fillers[DEFAULT];
6465 i++;
6467 for (; i < graph->size; i++) {
6468 append_to_rev_graph(graph, filler->separator);
6469 append_to_rev_graph(graph, filler->line);
6470 if (graph_parent_is_merge(graph->prev) &&
6471 i < graph->prev->pos + graph->parents->size)
6472 filler = &fillers[RSHARP];
6473 if (graph->prev->size > graph->size)
6474 filler = &fillers[LDIAG];
6477 if (graph->prev->size > graph->size) {
6478 append_to_rev_graph(graph, filler->separator);
6479 if (filler->line != ' ')
6480 append_to_rev_graph(graph, filler->line);
6484 /* Prepare the next rev graph */
6485 static void
6486 prepare_rev_graph(struct rev_graph *graph)
6488 size_t i;
6490 /* First, traverse all lines of revisions up to the active one. */
6491 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6492 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6493 break;
6495 push_rev_graph(graph->next, graph->rev[graph->pos]);
6498 /* Interleave the new revision parent(s). */
6499 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6500 push_rev_graph(graph->next, graph->parents->rev[i]);
6502 /* Lastly, put any remaining revisions. */
6503 for (i = graph->pos + 1; i < graph->size; i++)
6504 push_rev_graph(graph->next, graph->rev[i]);
6507 static void
6508 update_rev_graph(struct view *view, struct rev_graph *graph)
6510 /* If this is the finalizing update ... */
6511 if (graph->commit)
6512 prepare_rev_graph(graph);
6514 /* Graph visualization needs a one rev look-ahead,
6515 * so the first update doesn't visualize anything. */
6516 if (!graph->prev->commit)
6517 return;
6519 if (view->lines > 2)
6520 view->line[view->lines - 3].dirty = 1;
6521 if (view->lines > 1)
6522 view->line[view->lines - 2].dirty = 1;
6523 draw_rev_graph(graph->prev);
6524 done_rev_graph(graph->prev->prev);
6529 * Main view backend
6532 static const char *main_argv[SIZEOF_ARG] = {
6533 "git", "log", "--no-color", "--pretty=raw", "--parents",
6534 "--topo-order", "%(head)", NULL
6537 static bool
6538 main_draw(struct view *view, struct line *line, unsigned int lineno)
6540 struct commit *commit = line->data;
6542 if (!commit->author)
6543 return FALSE;
6545 if (opt_date && draw_date(view, &commit->time))
6546 return TRUE;
6548 if (opt_author && draw_author(view, commit->author))
6549 return TRUE;
6551 if (opt_rev_graph && commit->graph_size &&
6552 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6553 return TRUE;
6555 if (opt_show_refs && commit->refs) {
6556 size_t i;
6558 for (i = 0; i < commit->refs->size; i++) {
6559 struct ref *ref = commit->refs->refs[i];
6560 enum line_type type;
6562 if (ref->head)
6563 type = LINE_MAIN_HEAD;
6564 else if (ref->ltag)
6565 type = LINE_MAIN_LOCAL_TAG;
6566 else if (ref->tag)
6567 type = LINE_MAIN_TAG;
6568 else if (ref->tracked)
6569 type = LINE_MAIN_TRACKED;
6570 else if (ref->remote)
6571 type = LINE_MAIN_REMOTE;
6572 else
6573 type = LINE_MAIN_REF;
6575 if (draw_text(view, type, "[", TRUE) ||
6576 draw_text(view, type, ref->name, TRUE) ||
6577 draw_text(view, type, "]", TRUE))
6578 return TRUE;
6580 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6581 return TRUE;
6585 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6586 return TRUE;
6589 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6590 static bool
6591 main_read(struct view *view, char *line)
6593 static struct rev_graph *graph = graph_stacks;
6594 enum line_type type;
6595 struct commit *commit;
6597 if (!line) {
6598 int i;
6600 if (!view->lines && !view->parent)
6601 die("No revisions match the given arguments.");
6602 if (view->lines > 0) {
6603 commit = view->line[view->lines - 1].data;
6604 view->line[view->lines - 1].dirty = 1;
6605 if (!commit->author) {
6606 view->lines--;
6607 free(commit);
6608 graph->commit = NULL;
6611 update_rev_graph(view, graph);
6613 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6614 clear_rev_graph(&graph_stacks[i]);
6615 return TRUE;
6618 type = get_line_type(line);
6619 if (type == LINE_COMMIT) {
6620 commit = calloc(1, sizeof(struct commit));
6621 if (!commit)
6622 return FALSE;
6624 line += STRING_SIZE("commit ");
6625 if (*line == '-') {
6626 graph->boundary = 1;
6627 line++;
6630 string_copy_rev(commit->id, line);
6631 commit->refs = get_ref_list(commit->id);
6632 graph->commit = commit;
6633 add_line_data(view, commit, LINE_MAIN_COMMIT);
6635 while ((line = strchr(line, ' '))) {
6636 line++;
6637 push_rev_graph(graph->parents, line);
6638 commit->has_parents = TRUE;
6640 return TRUE;
6643 if (!view->lines)
6644 return TRUE;
6645 commit = view->line[view->lines - 1].data;
6647 switch (type) {
6648 case LINE_PARENT:
6649 if (commit->has_parents)
6650 break;
6651 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6652 break;
6654 case LINE_AUTHOR:
6655 parse_author_line(line + STRING_SIZE("author "),
6656 &commit->author, &commit->time);
6657 update_rev_graph(view, graph);
6658 graph = graph->next;
6659 break;
6661 default:
6662 /* Fill in the commit title if it has not already been set. */
6663 if (commit->title[0])
6664 break;
6666 /* Require titles to start with a non-space character at the
6667 * offset used by git log. */
6668 if (strncmp(line, " ", 4))
6669 break;
6670 line += 4;
6671 /* Well, if the title starts with a whitespace character,
6672 * try to be forgiving. Otherwise we end up with no title. */
6673 while (isspace(*line))
6674 line++;
6675 if (*line == '\0')
6676 break;
6677 /* FIXME: More graceful handling of titles; append "..." to
6678 * shortened titles, etc. */
6680 string_expand(commit->title, sizeof(commit->title), line, 1);
6681 view->line[view->lines - 1].dirty = 1;
6684 return TRUE;
6687 static enum request
6688 main_request(struct view *view, enum request request, struct line *line)
6690 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6692 switch (request) {
6693 case REQ_ENTER:
6694 open_view(view, REQ_VIEW_DIFF, flags);
6695 break;
6696 case REQ_REFRESH:
6697 load_refs();
6698 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6699 break;
6700 default:
6701 return request;
6704 return REQ_NONE;
6707 static bool
6708 grep_refs(struct ref_list *list, regex_t *regex)
6710 regmatch_t pmatch;
6711 size_t i;
6713 if (!opt_show_refs || !list)
6714 return FALSE;
6716 for (i = 0; i < list->size; i++) {
6717 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6718 return TRUE;
6721 return FALSE;
6724 static bool
6725 main_grep(struct view *view, struct line *line)
6727 struct commit *commit = line->data;
6728 const char *text[] = {
6729 commit->title,
6730 opt_author ? commit->author : "",
6731 opt_date ? mkdate(&commit->time) : "",
6732 NULL
6735 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6738 static void
6739 main_select(struct view *view, struct line *line)
6741 struct commit *commit = line->data;
6743 string_copy_rev(view->ref, commit->id);
6744 string_copy_rev(ref_commit, view->ref);
6747 static struct view_ops main_ops = {
6748 "commit",
6749 main_argv,
6750 NULL,
6751 main_read,
6752 main_draw,
6753 main_request,
6754 main_grep,
6755 main_select,
6760 * Unicode / UTF-8 handling
6762 * NOTE: Much of the following code for dealing with Unicode is derived from
6763 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6764 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6767 static inline int
6768 unicode_width(unsigned long c)
6770 if (c >= 0x1100 &&
6771 (c <= 0x115f /* Hangul Jamo */
6772 || c == 0x2329
6773 || c == 0x232a
6774 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6775 /* CJK ... Yi */
6776 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6777 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6778 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6779 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6780 || (c >= 0xffe0 && c <= 0xffe6)
6781 || (c >= 0x20000 && c <= 0x2fffd)
6782 || (c >= 0x30000 && c <= 0x3fffd)))
6783 return 2;
6785 if (c == '\t')
6786 return opt_tab_size;
6788 return 1;
6791 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6792 * Illegal bytes are set one. */
6793 static const unsigned char utf8_bytes[256] = {
6794 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,
6795 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,
6796 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,
6797 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,
6798 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,
6799 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,
6800 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,
6801 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,
6804 static inline unsigned char
6805 utf8_char_length(const char *string, const char *end)
6807 int c = *(unsigned char *) string;
6809 return utf8_bytes[c];
6812 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6813 static inline unsigned long
6814 utf8_to_unicode(const char *string, size_t length)
6816 unsigned long unicode;
6818 switch (length) {
6819 case 1:
6820 unicode = string[0];
6821 break;
6822 case 2:
6823 unicode = (string[0] & 0x1f) << 6;
6824 unicode += (string[1] & 0x3f);
6825 break;
6826 case 3:
6827 unicode = (string[0] & 0x0f) << 12;
6828 unicode += ((string[1] & 0x3f) << 6);
6829 unicode += (string[2] & 0x3f);
6830 break;
6831 case 4:
6832 unicode = (string[0] & 0x0f) << 18;
6833 unicode += ((string[1] & 0x3f) << 12);
6834 unicode += ((string[2] & 0x3f) << 6);
6835 unicode += (string[3] & 0x3f);
6836 break;
6837 case 5:
6838 unicode = (string[0] & 0x0f) << 24;
6839 unicode += ((string[1] & 0x3f) << 18);
6840 unicode += ((string[2] & 0x3f) << 12);
6841 unicode += ((string[3] & 0x3f) << 6);
6842 unicode += (string[4] & 0x3f);
6843 break;
6844 case 6:
6845 unicode = (string[0] & 0x01) << 30;
6846 unicode += ((string[1] & 0x3f) << 24);
6847 unicode += ((string[2] & 0x3f) << 18);
6848 unicode += ((string[3] & 0x3f) << 12);
6849 unicode += ((string[4] & 0x3f) << 6);
6850 unicode += (string[5] & 0x3f);
6851 break;
6852 default:
6853 die("Invalid Unicode length");
6856 /* Invalid characters could return the special 0xfffd value but NUL
6857 * should be just as good. */
6858 return unicode > 0xffff ? 0 : unicode;
6861 /* Calculates how much of string can be shown within the given maximum width
6862 * and sets trimmed parameter to non-zero value if all of string could not be
6863 * shown. If the reserve flag is TRUE, it will reserve at least one
6864 * trailing character, which can be useful when drawing a delimiter.
6866 * Returns the number of bytes to output from string to satisfy max_width. */
6867 static size_t
6868 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6870 const char *string = *start;
6871 const char *end = strchr(string, '\0');
6872 unsigned char last_bytes = 0;
6873 size_t last_ucwidth = 0;
6875 *width = 0;
6876 *trimmed = 0;
6878 while (string < end) {
6879 unsigned char bytes = utf8_char_length(string, end);
6880 size_t ucwidth;
6881 unsigned long unicode;
6883 if (string + bytes > end)
6884 break;
6886 /* Change representation to figure out whether
6887 * it is a single- or double-width character. */
6889 unicode = utf8_to_unicode(string, bytes);
6890 /* FIXME: Graceful handling of invalid Unicode character. */
6891 if (!unicode)
6892 break;
6894 ucwidth = unicode_width(unicode);
6895 if (skip > 0) {
6896 skip -= ucwidth <= skip ? ucwidth : skip;
6897 *start += bytes;
6899 *width += ucwidth;
6900 if (*width > max_width) {
6901 *trimmed = 1;
6902 *width -= ucwidth;
6903 if (reserve && *width == max_width) {
6904 string -= last_bytes;
6905 *width -= last_ucwidth;
6907 break;
6910 string += bytes;
6911 last_bytes = ucwidth ? bytes : 0;
6912 last_ucwidth = ucwidth;
6915 return string - *start;
6920 * Status management
6923 /* Whether or not the curses interface has been initialized. */
6924 static bool cursed = FALSE;
6926 /* Terminal hacks and workarounds. */
6927 static bool use_scroll_redrawwin;
6928 static bool use_scroll_status_wclear;
6930 /* The status window is used for polling keystrokes. */
6931 static WINDOW *status_win;
6933 /* Reading from the prompt? */
6934 static bool input_mode = FALSE;
6936 static bool status_empty = FALSE;
6938 /* Update status and title window. */
6939 static void
6940 report(const char *msg, ...)
6942 struct view *view = display[current_view];
6944 if (input_mode)
6945 return;
6947 if (!view) {
6948 char buf[SIZEOF_STR];
6949 va_list args;
6951 va_start(args, msg);
6952 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6953 buf[sizeof(buf) - 1] = 0;
6954 buf[sizeof(buf) - 2] = '.';
6955 buf[sizeof(buf) - 3] = '.';
6956 buf[sizeof(buf) - 4] = '.';
6958 va_end(args);
6959 die("%s", buf);
6962 if (!status_empty || *msg) {
6963 va_list args;
6965 va_start(args, msg);
6967 wmove(status_win, 0, 0);
6968 if (view->has_scrolled && use_scroll_status_wclear)
6969 wclear(status_win);
6970 if (*msg) {
6971 vwprintw(status_win, msg, args);
6972 status_empty = FALSE;
6973 } else {
6974 status_empty = TRUE;
6976 wclrtoeol(status_win);
6977 wnoutrefresh(status_win);
6979 va_end(args);
6982 update_view_title(view);
6985 /* Controls when nodelay should be in effect when polling user input. */
6986 static void
6987 set_nonblocking_input(bool loading)
6989 static unsigned int loading_views;
6991 if ((loading == FALSE && loading_views-- == 1) ||
6992 (loading == TRUE && loading_views++ == 0))
6993 nodelay(status_win, loading);
6996 static void
6997 init_display(void)
6999 const char *term;
7000 int x, y;
7002 /* Initialize the curses library */
7003 if (isatty(STDIN_FILENO)) {
7004 cursed = !!initscr();
7005 opt_tty = stdin;
7006 } else {
7007 /* Leave stdin and stdout alone when acting as a pager. */
7008 opt_tty = fopen("/dev/tty", "r+");
7009 if (!opt_tty)
7010 die("Failed to open /dev/tty");
7011 cursed = !!newterm(NULL, opt_tty, opt_tty);
7014 if (!cursed)
7015 die("Failed to initialize curses");
7017 nonl(); /* Disable conversion and detect newlines from input. */
7018 cbreak(); /* Take input chars one at a time, no wait for \n */
7019 noecho(); /* Don't echo input */
7020 leaveok(stdscr, FALSE);
7022 if (has_colors())
7023 init_colors();
7025 getmaxyx(stdscr, y, x);
7026 status_win = newwin(1, 0, y - 1, 0);
7027 if (!status_win)
7028 die("Failed to create status window");
7030 /* Enable keyboard mapping */
7031 keypad(status_win, TRUE);
7032 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7034 TABSIZE = opt_tab_size;
7035 if (opt_line_graphics) {
7036 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
7039 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7040 if (term && !strcmp(term, "gnome-terminal")) {
7041 /* In the gnome-terminal-emulator, the message from
7042 * scrolling up one line when impossible followed by
7043 * scrolling down one line causes corruption of the
7044 * status line. This is fixed by calling wclear. */
7045 use_scroll_status_wclear = TRUE;
7046 use_scroll_redrawwin = FALSE;
7048 } else if (term && !strcmp(term, "xrvt-xpm")) {
7049 /* No problems with full optimizations in xrvt-(unicode)
7050 * and aterm. */
7051 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7053 } else {
7054 /* When scrolling in (u)xterm the last line in the
7055 * scrolling direction will update slowly. */
7056 use_scroll_redrawwin = TRUE;
7057 use_scroll_status_wclear = FALSE;
7061 static int
7062 get_input(int prompt_position)
7064 struct view *view;
7065 int i, key, cursor_y, cursor_x;
7067 if (prompt_position)
7068 input_mode = TRUE;
7070 while (TRUE) {
7071 foreach_view (view, i) {
7072 update_view(view);
7073 if (view_is_displayed(view) && view->has_scrolled &&
7074 use_scroll_redrawwin)
7075 redrawwin(view->win);
7076 view->has_scrolled = FALSE;
7079 /* Update the cursor position. */
7080 if (prompt_position) {
7081 getbegyx(status_win, cursor_y, cursor_x);
7082 cursor_x = prompt_position;
7083 } else {
7084 view = display[current_view];
7085 getbegyx(view->win, cursor_y, cursor_x);
7086 cursor_x = view->width - 1;
7087 cursor_y += view->lineno - view->offset;
7089 setsyx(cursor_y, cursor_x);
7091 /* Refresh, accept single keystroke of input */
7092 doupdate();
7093 key = wgetch(status_win);
7095 /* wgetch() with nodelay() enabled returns ERR when
7096 * there's no input. */
7097 if (key == ERR) {
7099 } else if (key == KEY_RESIZE) {
7100 int height, width;
7102 getmaxyx(stdscr, height, width);
7104 wresize(status_win, 1, width);
7105 mvwin(status_win, height - 1, 0);
7106 wnoutrefresh(status_win);
7107 resize_display();
7108 redraw_display(TRUE);
7110 } else {
7111 input_mode = FALSE;
7112 return key;
7117 static char *
7118 prompt_input(const char *prompt, input_handler handler, void *data)
7120 enum input_status status = INPUT_OK;
7121 static char buf[SIZEOF_STR];
7122 size_t pos = 0;
7124 buf[pos] = 0;
7126 while (status == INPUT_OK || status == INPUT_SKIP) {
7127 int key;
7129 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7130 wclrtoeol(status_win);
7132 key = get_input(pos + 1);
7133 switch (key) {
7134 case KEY_RETURN:
7135 case KEY_ENTER:
7136 case '\n':
7137 status = pos ? INPUT_STOP : INPUT_CANCEL;
7138 break;
7140 case KEY_BACKSPACE:
7141 if (pos > 0)
7142 buf[--pos] = 0;
7143 else
7144 status = INPUT_CANCEL;
7145 break;
7147 case KEY_ESC:
7148 status = INPUT_CANCEL;
7149 break;
7151 default:
7152 if (pos >= sizeof(buf)) {
7153 report("Input string too long");
7154 return NULL;
7157 status = handler(data, buf, key);
7158 if (status == INPUT_OK)
7159 buf[pos++] = (char) key;
7163 /* Clear the status window */
7164 status_empty = FALSE;
7165 report("");
7167 if (status == INPUT_CANCEL)
7168 return NULL;
7170 buf[pos++] = 0;
7172 return buf;
7175 static enum input_status
7176 prompt_yesno_handler(void *data, char *buf, int c)
7178 if (c == 'y' || c == 'Y')
7179 return INPUT_STOP;
7180 if (c == 'n' || c == 'N')
7181 return INPUT_CANCEL;
7182 return INPUT_SKIP;
7185 static bool
7186 prompt_yesno(const char *prompt)
7188 char prompt2[SIZEOF_STR];
7190 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7191 return FALSE;
7193 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7196 static enum input_status
7197 read_prompt_handler(void *data, char *buf, int c)
7199 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7202 static char *
7203 read_prompt(const char *prompt)
7205 return prompt_input(prompt, read_prompt_handler, NULL);
7208 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7210 enum input_status status = INPUT_OK;
7211 int size = 0;
7213 while (items[size].text)
7214 size++;
7216 while (status == INPUT_OK) {
7217 const struct menu_item *item = &items[*selected];
7218 int key;
7219 int i;
7221 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7222 prompt, *selected + 1, size);
7223 if (item->hotkey)
7224 wprintw(status_win, "[%c] ", (char) item->hotkey);
7225 wprintw(status_win, "%s", item->text);
7226 wclrtoeol(status_win);
7228 key = get_input(COLS - 1);
7229 switch (key) {
7230 case KEY_RETURN:
7231 case KEY_ENTER:
7232 case '\n':
7233 status = INPUT_STOP;
7234 break;
7236 case KEY_LEFT:
7237 case KEY_UP:
7238 *selected = *selected - 1;
7239 if (*selected < 0)
7240 *selected = size - 1;
7241 break;
7243 case KEY_RIGHT:
7244 case KEY_DOWN:
7245 *selected = (*selected + 1) % size;
7246 break;
7248 case KEY_ESC:
7249 status = INPUT_CANCEL;
7250 break;
7252 default:
7253 for (i = 0; items[i].text; i++)
7254 if (items[i].hotkey == key) {
7255 *selected = i;
7256 status = INPUT_STOP;
7257 break;
7262 /* Clear the status window */
7263 status_empty = FALSE;
7264 report("");
7266 return status != INPUT_CANCEL;
7270 * Repository properties
7273 static struct ref **refs = NULL;
7274 static size_t refs_size = 0;
7276 static struct ref_list **ref_lists = NULL;
7277 static size_t ref_lists_size = 0;
7279 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7280 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7281 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7283 static int
7284 compare_refs(const void *ref1_, const void *ref2_)
7286 const struct ref *ref1 = *(const struct ref **)ref1_;
7287 const struct ref *ref2 = *(const struct ref **)ref2_;
7289 if (ref1->tag != ref2->tag)
7290 return ref2->tag - ref1->tag;
7291 if (ref1->ltag != ref2->ltag)
7292 return ref2->ltag - ref2->ltag;
7293 if (ref1->head != ref2->head)
7294 return ref2->head - ref1->head;
7295 if (ref1->tracked != ref2->tracked)
7296 return ref2->tracked - ref1->tracked;
7297 if (ref1->remote != ref2->remote)
7298 return ref2->remote - ref1->remote;
7299 return strcmp(ref1->name, ref2->name);
7302 static void
7303 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7305 size_t i;
7307 for (i = 0; i < refs_size; i++)
7308 if (!visitor(data, refs[i]))
7309 break;
7312 static struct ref_list *
7313 get_ref_list(const char *id)
7315 struct ref_list *list;
7316 size_t i;
7318 for (i = 0; i < ref_lists_size; i++)
7319 if (!strcmp(id, ref_lists[i]->id))
7320 return ref_lists[i];
7322 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7323 return NULL;
7324 list = calloc(1, sizeof(*list));
7325 if (!list)
7326 return NULL;
7328 for (i = 0; i < refs_size; i++) {
7329 if (!strcmp(id, refs[i]->id) &&
7330 realloc_refs_list(&list->refs, list->size, 1))
7331 list->refs[list->size++] = refs[i];
7334 if (!list->refs) {
7335 free(list);
7336 return NULL;
7339 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7340 ref_lists[ref_lists_size++] = list;
7341 return list;
7344 static int
7345 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7347 struct ref *ref = NULL;
7348 bool tag = FALSE;
7349 bool ltag = FALSE;
7350 bool remote = FALSE;
7351 bool tracked = FALSE;
7352 bool head = FALSE;
7353 int from = 0, to = refs_size - 1;
7355 if (!prefixcmp(name, "refs/tags/")) {
7356 if (!suffixcmp(name, namelen, "^{}")) {
7357 namelen -= 3;
7358 name[namelen] = 0;
7359 } else {
7360 ltag = TRUE;
7363 tag = TRUE;
7364 namelen -= STRING_SIZE("refs/tags/");
7365 name += STRING_SIZE("refs/tags/");
7367 } else if (!prefixcmp(name, "refs/remotes/")) {
7368 remote = TRUE;
7369 namelen -= STRING_SIZE("refs/remotes/");
7370 name += STRING_SIZE("refs/remotes/");
7371 tracked = !strcmp(opt_remote, name);
7373 } else if (!prefixcmp(name, "refs/heads/")) {
7374 namelen -= STRING_SIZE("refs/heads/");
7375 name += STRING_SIZE("refs/heads/");
7376 head = !strncmp(opt_head, name, namelen);
7378 } else if (!strcmp(name, "HEAD")) {
7379 string_ncopy(opt_head_rev, id, idlen);
7380 return OK;
7383 /* If we are reloading or it's an annotated tag, replace the
7384 * previous SHA1 with the resolved commit id; relies on the fact
7385 * git-ls-remote lists the commit id of an annotated tag right
7386 * before the commit id it points to. */
7387 while (from <= to) {
7388 size_t pos = (to + from) / 2;
7389 int cmp = strcmp(name, refs[pos]->name);
7391 if (!cmp) {
7392 ref = refs[pos];
7393 break;
7396 if (cmp < 0)
7397 to = pos - 1;
7398 else
7399 from = pos + 1;
7402 if (!ref) {
7403 if (!realloc_refs(&refs, refs_size, 1))
7404 return ERR;
7405 ref = calloc(1, sizeof(*ref) + namelen);
7406 if (!ref)
7407 return ERR;
7408 memmove(refs + from + 1, refs + from,
7409 (refs_size - from) * sizeof(*refs));
7410 refs[from] = ref;
7411 strncpy(ref->name, name, namelen);
7412 refs_size++;
7415 ref->head = head;
7416 ref->tag = tag;
7417 ref->ltag = ltag;
7418 ref->remote = remote;
7419 ref->tracked = tracked;
7420 string_copy_rev(ref->id, id);
7422 return OK;
7425 static int
7426 load_refs(void)
7428 const char *head_argv[] = {
7429 "git", "symbolic-ref", "HEAD", NULL
7431 static const char *ls_remote_argv[SIZEOF_ARG] = {
7432 "git", "ls-remote", opt_git_dir, NULL
7434 static bool init = FALSE;
7435 size_t i;
7437 if (!init) {
7438 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7439 init = TRUE;
7442 if (!*opt_git_dir)
7443 return OK;
7445 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7446 !prefixcmp(opt_head, "refs/heads/")) {
7447 char *offset = opt_head + STRING_SIZE("refs/heads/");
7449 memmove(opt_head, offset, strlen(offset) + 1);
7452 for (i = 0; i < refs_size; i++)
7453 refs[i]->id[0] = 0;
7455 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7456 return ERR;
7458 /* Update the ref lists to reflect changes. */
7459 for (i = 0; i < ref_lists_size; i++) {
7460 struct ref_list *list = ref_lists[i];
7461 size_t old, new;
7463 for (old = new = 0; old < list->size; old++)
7464 if (!strcmp(list->id, list->refs[old]->id))
7465 list->refs[new++] = list->refs[old];
7466 list->size = new;
7469 return OK;
7472 static void
7473 set_remote_branch(const char *name, const char *value, size_t valuelen)
7475 if (!strcmp(name, ".remote")) {
7476 string_ncopy(opt_remote, value, valuelen);
7478 } else if (*opt_remote && !strcmp(name, ".merge")) {
7479 size_t from = strlen(opt_remote);
7481 if (!prefixcmp(value, "refs/heads/"))
7482 value += STRING_SIZE("refs/heads/");
7484 if (!string_format_from(opt_remote, &from, "/%s", value))
7485 opt_remote[0] = 0;
7489 static void
7490 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7492 const char *argv[SIZEOF_ARG] = { name, "=" };
7493 int argc = 1 + (cmd == option_set_command);
7494 int error = ERR;
7496 if (!argv_from_string(argv, &argc, value))
7497 config_msg = "Too many option arguments";
7498 else
7499 error = cmd(argc, argv);
7501 if (error == ERR)
7502 warn("Option 'tig.%s': %s", name, config_msg);
7505 static bool
7506 set_environment_variable(const char *name, const char *value)
7508 size_t len = strlen(name) + 1 + strlen(value) + 1;
7509 char *env = malloc(len);
7511 if (env &&
7512 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7513 putenv(env) == 0)
7514 return TRUE;
7515 free(env);
7516 return FALSE;
7519 static void
7520 set_work_tree(const char *value)
7522 char cwd[SIZEOF_STR];
7524 if (!getcwd(cwd, sizeof(cwd)))
7525 die("Failed to get cwd path: %s", strerror(errno));
7526 if (chdir(opt_git_dir) < 0)
7527 die("Failed to chdir(%s): %s", strerror(errno));
7528 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7529 die("Failed to get git path: %s", strerror(errno));
7530 if (chdir(cwd) < 0)
7531 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7532 if (chdir(value) < 0)
7533 die("Failed to chdir(%s): %s", value, strerror(errno));
7534 if (!getcwd(cwd, sizeof(cwd)))
7535 die("Failed to get cwd path: %s", strerror(errno));
7536 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7537 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7538 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7539 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7540 opt_is_inside_work_tree = TRUE;
7543 static int
7544 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7546 if (!strcmp(name, "i18n.commitencoding"))
7547 string_ncopy(opt_encoding, value, valuelen);
7549 else if (!strcmp(name, "core.editor"))
7550 string_ncopy(opt_editor, value, valuelen);
7552 else if (!strcmp(name, "core.worktree"))
7553 set_work_tree(value);
7555 else if (!prefixcmp(name, "tig.color."))
7556 set_repo_config_option(name + 10, value, option_color_command);
7558 else if (!prefixcmp(name, "tig.bind."))
7559 set_repo_config_option(name + 9, value, option_bind_command);
7561 else if (!prefixcmp(name, "tig."))
7562 set_repo_config_option(name + 4, value, option_set_command);
7564 else if (*opt_head && !prefixcmp(name, "branch.") &&
7565 !strncmp(name + 7, opt_head, strlen(opt_head)))
7566 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7568 return OK;
7571 static int
7572 load_git_config(void)
7574 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7576 return run_io_load(config_list_argv, "=", read_repo_config_option);
7579 static int
7580 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7582 if (!opt_git_dir[0]) {
7583 string_ncopy(opt_git_dir, name, namelen);
7585 } else if (opt_is_inside_work_tree == -1) {
7586 /* This can be 3 different values depending on the
7587 * version of git being used. If git-rev-parse does not
7588 * understand --is-inside-work-tree it will simply echo
7589 * the option else either "true" or "false" is printed.
7590 * Default to true for the unknown case. */
7591 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7593 } else if (*name == '.') {
7594 string_ncopy(opt_cdup, name, namelen);
7596 } else {
7597 string_ncopy(opt_prefix, name, namelen);
7600 return OK;
7603 static int
7604 load_repo_info(void)
7606 const char *rev_parse_argv[] = {
7607 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7608 "--show-cdup", "--show-prefix", NULL
7611 return run_io_load(rev_parse_argv, "=", read_repo_info);
7616 * Main
7619 static const char usage[] =
7620 "tig " TIG_VERSION " (" __DATE__ ")\n"
7621 "\n"
7622 "Usage: tig [options] [revs] [--] [paths]\n"
7623 " or: tig show [options] [revs] [--] [paths]\n"
7624 " or: tig blame [rev] path\n"
7625 " or: tig status\n"
7626 " or: tig < [git command output]\n"
7627 "\n"
7628 "Options:\n"
7629 " -v, --version Show version and exit\n"
7630 " -h, --help Show help message and exit";
7632 static void __NORETURN
7633 quit(int sig)
7635 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7636 if (cursed)
7637 endwin();
7638 exit(0);
7641 static void __NORETURN
7642 die(const char *err, ...)
7644 va_list args;
7646 endwin();
7648 va_start(args, err);
7649 fputs("tig: ", stderr);
7650 vfprintf(stderr, err, args);
7651 fputs("\n", stderr);
7652 va_end(args);
7654 exit(1);
7657 static void
7658 warn(const char *msg, ...)
7660 va_list args;
7662 va_start(args, msg);
7663 fputs("tig warning: ", stderr);
7664 vfprintf(stderr, msg, args);
7665 fputs("\n", stderr);
7666 va_end(args);
7669 static enum request
7670 parse_options(int argc, const char *argv[])
7672 enum request request = REQ_VIEW_MAIN;
7673 const char *subcommand;
7674 bool seen_dashdash = FALSE;
7675 /* XXX: This is vulnerable to the user overriding options
7676 * required for the main view parser. */
7677 const char *custom_argv[SIZEOF_ARG] = {
7678 "git", "log", "--no-color", "--pretty=raw", "--parents",
7679 "--topo-order", NULL
7681 int i, j = 6;
7683 if (!isatty(STDIN_FILENO)) {
7684 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7685 return REQ_VIEW_PAGER;
7688 if (argc <= 1)
7689 return REQ_NONE;
7691 subcommand = argv[1];
7692 if (!strcmp(subcommand, "status")) {
7693 if (argc > 2)
7694 warn("ignoring arguments after `%s'", subcommand);
7695 return REQ_VIEW_STATUS;
7697 } else if (!strcmp(subcommand, "blame")) {
7698 if (argc <= 2 || argc > 4)
7699 die("invalid number of options to blame\n\n%s", usage);
7701 i = 2;
7702 if (argc == 4) {
7703 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7704 i++;
7707 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7708 return REQ_VIEW_BLAME;
7710 } else if (!strcmp(subcommand, "show")) {
7711 request = REQ_VIEW_DIFF;
7713 } else {
7714 subcommand = NULL;
7717 if (subcommand) {
7718 custom_argv[1] = subcommand;
7719 j = 2;
7722 for (i = 1 + !!subcommand; i < argc; i++) {
7723 const char *opt = argv[i];
7725 if (seen_dashdash || !strcmp(opt, "--")) {
7726 seen_dashdash = TRUE;
7728 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7729 printf("tig version %s\n", TIG_VERSION);
7730 quit(0);
7732 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7733 printf("%s\n", usage);
7734 quit(0);
7737 custom_argv[j++] = opt;
7738 if (j >= ARRAY_SIZE(custom_argv))
7739 die("command too long");
7742 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7743 die("Failed to format arguments");
7745 return request;
7749 main(int argc, const char *argv[])
7751 enum request request = parse_options(argc, argv);
7752 struct view *view;
7753 size_t i;
7755 signal(SIGINT, quit);
7756 signal(SIGPIPE, SIG_IGN);
7758 if (setlocale(LC_ALL, "")) {
7759 char *codeset = nl_langinfo(CODESET);
7761 string_ncopy(opt_codeset, codeset, strlen(codeset));
7764 if (load_repo_info() == ERR)
7765 die("Failed to load repo info.");
7767 if (load_options() == ERR)
7768 die("Failed to load user config.");
7770 if (load_git_config() == ERR)
7771 die("Failed to load repo config.");
7773 /* Require a git repository unless when running in pager mode. */
7774 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7775 die("Not a git repository");
7777 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7778 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7779 if (opt_iconv_in == ICONV_NONE)
7780 die("Failed to initialize character set conversion");
7783 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7784 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7785 if (opt_iconv_out == ICONV_NONE)
7786 die("Failed to initialize character set conversion");
7789 if (load_refs() == ERR)
7790 die("Failed to load refs.");
7792 foreach_view (view, i)
7793 argv_from_env(view->ops->argv, view->cmd_env);
7795 init_display();
7797 if (request != REQ_NONE)
7798 open_view(NULL, request, OPEN_PREPARED);
7799 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7801 while (view_driver(display[current_view], request)) {
7802 int key = get_input(0);
7804 view = display[current_view];
7805 request = get_keybinding(view->keymap, key);
7807 /* Some low-level request handling. This keeps access to
7808 * status_win restricted. */
7809 switch (request) {
7810 case REQ_PROMPT:
7812 char *cmd = read_prompt(":");
7814 if (cmd && isdigit(*cmd)) {
7815 int lineno = view->lineno + 1;
7817 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7818 select_view_line(view, lineno - 1);
7819 report("");
7820 } else {
7821 report("Unable to parse '%s' as a line number", cmd);
7824 } else if (cmd) {
7825 struct view *next = VIEW(REQ_VIEW_PAGER);
7826 const char *argv[SIZEOF_ARG] = { "git" };
7827 int argc = 1;
7829 /* When running random commands, initially show the
7830 * command in the title. However, it maybe later be
7831 * overwritten if a commit line is selected. */
7832 string_ncopy(next->ref, cmd, strlen(cmd));
7834 if (!argv_from_string(argv, &argc, cmd)) {
7835 report("Too many arguments");
7836 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7837 report("Failed to format command");
7838 } else {
7839 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7843 request = REQ_NONE;
7844 break;
7846 case REQ_SEARCH:
7847 case REQ_SEARCH_BACK:
7849 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7850 char *search = read_prompt(prompt);
7852 if (search)
7853 string_ncopy(opt_search, search, strlen(search));
7854 else if (*opt_search)
7855 request = request == REQ_SEARCH ?
7856 REQ_FIND_NEXT :
7857 REQ_FIND_PREV;
7858 else
7859 request = REQ_NONE;
7860 break;
7862 default:
7863 break;
7867 quit(0);
7869 return 0;