Refactor format variable lookup and expansion
[tig.git] / tig.c
blobd2dff15fd17bcd60c6a56209a420f33c9fc37a5a
1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static void set_nonblocking_input(bool loading);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS 8
111 #define MIN_VIEW_HEIGHT 4
113 #define NULL_ID "0000000000000000000000000000000000000000"
115 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
117 /* Some ASCII-shorthands fitted into the ncurses namespace. */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164 int hotkey;
165 const char *text;
166 void *data;
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181 type *tmp = *mem; \
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185 if (tmp) \
186 *mem = tmp; \
189 return tmp; \
193 * String helpers
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223 size_t size, pos;
225 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226 if (src[pos] == '\t') {
227 size_t expanded = tabsize - (size % tabsize);
229 if (expanded + size >= dstlen - 1)
230 expanded = dstlen - size - 1;
231 memcpy(dst + size, " ", expanded);
232 size += expanded;
233 } else {
234 dst[size++] = src[pos];
238 dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244 int namelen;
246 while (isspace(*name))
247 name++;
249 namelen = strlen(name) - 1;
250 while (namelen > 0 && isspace(name[namelen]))
251 name[namelen--] = 0;
253 return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259 va_list args;
260 size_t pos = bufpos ? *bufpos : 0;
262 va_start(args, fmt);
263 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264 va_end(args);
266 if (bufpos)
267 *bufpos = pos;
269 return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281 size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i = 0; i < len; i++) {
287 if (toupper(str1[i]) == toupper(str2[i]))
288 continue;
290 if (string_enum_sep(str1[i]) &&
291 string_enum_sep(str2[i]))
292 continue;
294 return str1[i] - str2[i];
297 return 0;
300 #define enum_equals(entry, str, len) \
301 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304 const char *name;
305 int namelen;
306 int value;
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314 static char buf[SIZEOF_STR];
315 int bufpos;
317 for (bufpos = 0; bufpos <= namelen; bufpos++) {
318 buf[bufpos] = tolower(name[bufpos]);
319 if (buf[bufpos] == '_')
320 buf[bufpos] = '-';
323 buf[bufpos] = 0;
324 return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332 size_t namelen = strlen(name);
333 int i;
335 for (i = 0; i < map_size; i++)
336 if (enum_equals(map[i], name, namelen)) {
337 *value = map[i].value;
338 return TRUE;
341 return FALSE;
344 #define map_enum(attr, map, name) \
345 map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348 strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353 size_t len = slen >= 0 ? slen : strlen(str);
354 size_t suffixlen = strlen(suffix);
356 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
361 * What value of "tz" was in effect back then at "time" in the
362 * local timezone?
364 static int local_tzoffset(time_t time)
366 time_t t, t_local;
367 struct tm tm;
368 int offset, eastwest;
370 t = time;
371 localtime_r(&t, &tm);
372 t_local = mktime(&tm);
374 if (t_local < t) {
375 eastwest = -1;
376 offset = t - t_local;
377 } else {
378 eastwest = 1;
379 offset = t_local - t;
381 offset /= 60; /* in minutes */
382 offset = (offset % 60) + ((offset / 60) * 100);
383 return offset * eastwest;
386 #define DATE_INFO \
387 DATE_(NO), \
388 DATE_(DEFAULT), \
389 DATE_(RELATIVE), \
390 DATE_(SHORT)
392 enum date {
393 #define DATE_(name) DATE_##name
394 DATE_INFO
395 #undef DATE_
398 static const struct enum_map date_map[] = {
399 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
400 DATE_INFO
401 #undef DATE_
404 static const char *
405 string_date(const time_t *time, enum date date)
407 static char buf[DATE_COLS + 1];
408 static const struct enum_map reldate[] = {
409 { "second", 1, 60 * 2 },
410 { "minute", 60, 60 * 60 * 2 },
411 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
412 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
413 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
414 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
416 struct tm tm;
418 if (date == DATE_RELATIVE) {
419 struct timeval now;
420 time_t date = *time + local_tzoffset(*time);
421 time_t seconds;
422 int i;
424 gettimeofday(&now, NULL);
425 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
426 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
427 if (seconds >= reldate[i].value)
428 continue;
430 seconds /= reldate[i].namelen;
431 if (!string_format(buf, "%ld %s%s %s",
432 seconds, reldate[i].name,
433 seconds > 1 ? "s" : "",
434 now.tv_sec >= date ? "ago" : "ahead"))
435 break;
436 return buf;
440 gmtime_r(time, &tm);
441 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
445 static bool
446 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
448 int valuelen;
450 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
451 bool advance = cmd[valuelen] != 0;
453 cmd[valuelen] = 0;
454 argv[(*argc)++] = chomp_string(cmd);
455 cmd = chomp_string(cmd + valuelen + advance);
458 if (*argc < SIZEOF_ARG)
459 argv[*argc] = NULL;
460 return *argc < SIZEOF_ARG;
463 static void
464 argv_from_env(const char **argv, const char *name)
466 char *env = argv ? getenv(name) : NULL;
467 int argc = 0;
469 if (env && *env)
470 env = strdup(env);
471 if (env && !argv_from_string(argv, &argc, env))
472 die("Too many arguments in the `%s` environment variable", name);
477 * Executing external commands.
480 enum io_type {
481 IO_FD, /* File descriptor based IO. */
482 IO_BG, /* Execute command in the background. */
483 IO_FG, /* Execute command with same std{in,out,err}. */
484 IO_RD, /* Read only fork+exec IO. */
485 IO_WR, /* Write only fork+exec IO. */
486 IO_AP, /* Append fork+exec output to file. */
489 struct io {
490 enum io_type type; /* The requested type of pipe. */
491 const char *dir; /* Directory from which to execute. */
492 pid_t pid; /* Pipe for reading or writing. */
493 int pipe; /* Pipe end for reading or writing. */
494 int error; /* Error status. */
495 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
496 char *buf; /* Read buffer. */
497 size_t bufalloc; /* Allocated buffer size. */
498 size_t bufsize; /* Buffer content size. */
499 char *bufpos; /* Current buffer position. */
500 unsigned int eof:1; /* Has end of file been reached. */
503 static void
504 reset_io(struct io *io)
506 io->pipe = -1;
507 io->pid = 0;
508 io->buf = io->bufpos = NULL;
509 io->bufalloc = io->bufsize = 0;
510 io->error = 0;
511 io->eof = 0;
514 static void
515 init_io(struct io *io, const char *dir, enum io_type type)
517 reset_io(io);
518 io->type = type;
519 io->dir = dir;
522 static bool
523 init_io_rd(struct io *io, const char *argv[], const char *dir,
524 enum format_flags flags)
526 init_io(io, dir, IO_RD);
527 return format_argv(io->argv, argv, flags);
530 static bool
531 io_open(struct io *io, const char *fmt, ...)
533 char name[SIZEOF_STR] = "";
534 bool fits;
535 va_list args;
537 init_io(io, NULL, IO_FD);
539 va_start(args, fmt);
540 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
541 va_end(args);
543 if (!fits) {
544 io->error = ENAMETOOLONG;
545 return FALSE;
547 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
548 if (io->pipe == -1)
549 io->error = errno;
550 return io->pipe != -1;
553 static bool
554 kill_io(struct io *io)
556 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
559 static bool
560 done_io(struct io *io)
562 pid_t pid = io->pid;
564 if (io->pipe != -1)
565 close(io->pipe);
566 free(io->buf);
567 reset_io(io);
569 while (pid > 0) {
570 int status;
571 pid_t waiting = waitpid(pid, &status, 0);
573 if (waiting < 0) {
574 if (errno == EINTR)
575 continue;
576 report("waitpid failed (%s)", strerror(errno));
577 return FALSE;
580 return waiting == pid &&
581 !WIFSIGNALED(status) &&
582 WIFEXITED(status) &&
583 !WEXITSTATUS(status);
586 return TRUE;
589 static bool
590 start_io(struct io *io)
592 int pipefds[2] = { -1, -1 };
594 if (io->type == IO_FD)
595 return TRUE;
597 if ((io->type == IO_RD || io->type == IO_WR) &&
598 pipe(pipefds) < 0)
599 return FALSE;
600 else if (io->type == IO_AP)
601 pipefds[1] = io->pipe;
603 if ((io->pid = fork())) {
604 if (pipefds[!(io->type == IO_WR)] != -1)
605 close(pipefds[!(io->type == IO_WR)]);
606 if (io->pid != -1) {
607 io->pipe = pipefds[!!(io->type == IO_WR)];
608 return TRUE;
611 } else {
612 if (io->type != IO_FG) {
613 int devnull = open("/dev/null", O_RDWR);
614 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
615 int writefd = (io->type == IO_RD || io->type == IO_AP)
616 ? pipefds[1] : devnull;
618 dup2(readfd, STDIN_FILENO);
619 dup2(writefd, STDOUT_FILENO);
620 dup2(devnull, STDERR_FILENO);
622 close(devnull);
623 if (pipefds[0] != -1)
624 close(pipefds[0]);
625 if (pipefds[1] != -1)
626 close(pipefds[1]);
629 if (io->dir && *io->dir && chdir(io->dir) == -1)
630 die("Failed to change directory: %s", strerror(errno));
632 execvp(io->argv[0], (char *const*) io->argv);
633 die("Failed to execute program: %s", strerror(errno));
636 if (pipefds[!!(io->type == IO_WR)] != -1)
637 close(pipefds[!!(io->type == IO_WR)]);
638 return FALSE;
641 static bool
642 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
644 init_io(io, dir, type);
645 if (!format_argv(io->argv, argv, FORMAT_NONE))
646 return FALSE;
647 return start_io(io);
650 static int
651 run_io_do(struct io *io)
653 return start_io(io) && done_io(io);
656 static int
657 run_io_bg(const char **argv)
659 struct io io = {};
661 init_io(&io, NULL, IO_BG);
662 if (!format_argv(io.argv, argv, FORMAT_NONE))
663 return FALSE;
664 return run_io_do(&io);
667 static bool
668 run_io_fg(const char **argv, const char *dir)
670 struct io io = {};
672 init_io(&io, dir, IO_FG);
673 if (!format_argv(io.argv, argv, FORMAT_NONE))
674 return FALSE;
675 return run_io_do(&io);
678 static bool
679 run_io_append(const char **argv, enum format_flags flags, int fd)
681 struct io io = {};
683 init_io(&io, NULL, IO_AP);
684 io.pipe = fd;
685 if (format_argv(io.argv, argv, flags))
686 return run_io_do(&io);
687 close(fd);
688 return FALSE;
691 static bool
692 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
694 return init_io_rd(io, argv, dir, flags) && start_io(io);
697 static bool
698 io_eof(struct io *io)
700 return io->eof;
703 static int
704 io_error(struct io *io)
706 return io->error;
709 static char *
710 io_strerror(struct io *io)
712 return strerror(io->error);
715 static bool
716 io_can_read(struct io *io)
718 struct timeval tv = { 0, 500 };
719 fd_set fds;
721 FD_ZERO(&fds);
722 FD_SET(io->pipe, &fds);
724 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
727 static ssize_t
728 io_read(struct io *io, void *buf, size_t bufsize)
730 do {
731 ssize_t readsize = read(io->pipe, buf, bufsize);
733 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
734 continue;
735 else if (readsize == -1)
736 io->error = errno;
737 else if (readsize == 0)
738 io->eof = 1;
739 return readsize;
740 } while (1);
743 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
745 static char *
746 io_get(struct io *io, int c, bool can_read)
748 char *eol;
749 ssize_t readsize;
751 while (TRUE) {
752 if (io->bufsize > 0) {
753 eol = memchr(io->bufpos, c, io->bufsize);
754 if (eol) {
755 char *line = io->bufpos;
757 *eol = 0;
758 io->bufpos = eol + 1;
759 io->bufsize -= io->bufpos - line;
760 return line;
764 if (io_eof(io)) {
765 if (io->bufsize) {
766 io->bufpos[io->bufsize] = 0;
767 io->bufsize = 0;
768 return io->bufpos;
770 return NULL;
773 if (!can_read)
774 return NULL;
776 if (io->bufsize > 0 && io->bufpos > io->buf)
777 memmove(io->buf, io->bufpos, io->bufsize);
779 if (io->bufalloc == io->bufsize) {
780 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
781 return NULL;
782 io->bufalloc += BUFSIZ;
785 io->bufpos = io->buf;
786 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
787 if (io_error(io))
788 return NULL;
789 io->bufsize += readsize;
793 static bool
794 io_write(struct io *io, const void *buf, size_t bufsize)
796 size_t written = 0;
798 while (!io_error(io) && written < bufsize) {
799 ssize_t size;
801 size = write(io->pipe, buf + written, bufsize - written);
802 if (size < 0 && (errno == EAGAIN || errno == EINTR))
803 continue;
804 else if (size == -1)
805 io->error = errno;
806 else
807 written += size;
810 return written == bufsize;
813 static bool
814 io_read_buf(struct io *io, char buf[], size_t bufsize)
816 char *result = io_get(io, '\n', TRUE);
818 if (result) {
819 result = chomp_string(result);
820 string_ncopy_do(buf, bufsize, result, strlen(result));
823 return done_io(io) && result;
826 static bool
827 run_io_buf(const char **argv, char buf[], size_t bufsize)
829 struct io io = {};
831 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
832 && io_read_buf(&io, buf, bufsize);
835 static int
836 io_load(struct io *io, const char *separators,
837 int (*read_property)(char *, size_t, char *, size_t))
839 char *name;
840 int state = OK;
842 if (!start_io(io))
843 return ERR;
845 while (state == OK && (name = io_get(io, '\n', TRUE))) {
846 char *value;
847 size_t namelen;
848 size_t valuelen;
850 name = chomp_string(name);
851 namelen = strcspn(name, separators);
853 if (name[namelen]) {
854 name[namelen] = 0;
855 value = chomp_string(name + namelen + 1);
856 valuelen = strlen(value);
858 } else {
859 value = "";
860 valuelen = 0;
863 state = read_property(name, namelen, value, valuelen);
866 if (state != ERR && io_error(io))
867 state = ERR;
868 done_io(io);
870 return state;
873 static int
874 run_io_load(const char **argv, const char *separators,
875 int (*read_property)(char *, size_t, char *, size_t))
877 struct io io = {};
879 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
880 ? io_load(&io, separators, read_property) : ERR;
885 * User requests
888 #define REQ_INFO \
889 /* XXX: Keep the view request first and in sync with views[]. */ \
890 REQ_GROUP("View switching") \
891 REQ_(VIEW_MAIN, "Show main view"), \
892 REQ_(VIEW_DIFF, "Show diff view"), \
893 REQ_(VIEW_LOG, "Show log view"), \
894 REQ_(VIEW_TREE, "Show tree view"), \
895 REQ_(VIEW_BLOB, "Show blob view"), \
896 REQ_(VIEW_BLAME, "Show blame view"), \
897 REQ_(VIEW_BRANCH, "Show branch view"), \
898 REQ_(VIEW_HELP, "Show help page"), \
899 REQ_(VIEW_PAGER, "Show pager view"), \
900 REQ_(VIEW_STATUS, "Show status view"), \
901 REQ_(VIEW_STAGE, "Show stage view"), \
903 REQ_GROUP("View manipulation") \
904 REQ_(ENTER, "Enter current line and scroll"), \
905 REQ_(NEXT, "Move to next"), \
906 REQ_(PREVIOUS, "Move to previous"), \
907 REQ_(PARENT, "Move to parent"), \
908 REQ_(VIEW_NEXT, "Move focus to next view"), \
909 REQ_(REFRESH, "Reload and refresh"), \
910 REQ_(MAXIMIZE, "Maximize the current view"), \
911 REQ_(VIEW_CLOSE, "Close the current view"), \
912 REQ_(QUIT, "Close all views and quit"), \
914 REQ_GROUP("View specific requests") \
915 REQ_(STATUS_UPDATE, "Update file status"), \
916 REQ_(STATUS_REVERT, "Revert file changes"), \
917 REQ_(STATUS_MERGE, "Merge file using external tool"), \
918 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
920 REQ_GROUP("Cursor navigation") \
921 REQ_(MOVE_UP, "Move cursor one line up"), \
922 REQ_(MOVE_DOWN, "Move cursor one line down"), \
923 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
924 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
925 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
926 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
928 REQ_GROUP("Scrolling") \
929 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
930 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
931 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
932 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
933 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
934 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
936 REQ_GROUP("Searching") \
937 REQ_(SEARCH, "Search the view"), \
938 REQ_(SEARCH_BACK, "Search backwards in the view"), \
939 REQ_(FIND_NEXT, "Find next search match"), \
940 REQ_(FIND_PREV, "Find previous search match"), \
942 REQ_GROUP("Option manipulation") \
943 REQ_(OPTIONS, "Open option menu"), \
944 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
945 REQ_(TOGGLE_DATE, "Toggle date display"), \
946 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
947 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
948 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
949 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
950 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
951 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
953 REQ_GROUP("Misc") \
954 REQ_(PROMPT, "Bring up the prompt"), \
955 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
956 REQ_(SHOW_VERSION, "Show version information"), \
957 REQ_(STOP_LOADING, "Stop all loading views"), \
958 REQ_(EDIT, "Open in editor"), \
959 REQ_(NONE, "Do nothing")
962 /* User action requests. */
963 enum request {
964 #define REQ_GROUP(help)
965 #define REQ_(req, help) REQ_##req
967 /* Offset all requests to avoid conflicts with ncurses getch values. */
968 REQ_OFFSET = KEY_MAX + 1,
969 REQ_INFO
971 #undef REQ_GROUP
972 #undef REQ_
975 struct request_info {
976 enum request request;
977 const char *name;
978 int namelen;
979 const char *help;
982 static const struct request_info req_info[] = {
983 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
984 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
985 REQ_INFO
986 #undef REQ_GROUP
987 #undef REQ_
990 static enum request
991 get_request(const char *name)
993 int namelen = strlen(name);
994 int i;
996 for (i = 0; i < ARRAY_SIZE(req_info); i++)
997 if (enum_equals(req_info[i], name, namelen))
998 return req_info[i].request;
1000 return REQ_NONE;
1005 * Options
1008 /* Option and state variables. */
1009 static enum date opt_date = DATE_DEFAULT;
1010 static bool opt_author = TRUE;
1011 static bool opt_line_number = FALSE;
1012 static bool opt_line_graphics = TRUE;
1013 static bool opt_rev_graph = FALSE;
1014 static bool opt_show_refs = TRUE;
1015 static int opt_num_interval = 5;
1016 static double opt_hscroll = 0.50;
1017 static double opt_scale_split_view = 2.0 / 3.0;
1018 static int opt_tab_size = 8;
1019 static int opt_author_cols = 19;
1020 static char opt_path[SIZEOF_STR] = "";
1021 static char opt_file[SIZEOF_STR] = "";
1022 static char opt_ref[SIZEOF_REF] = "";
1023 static char opt_head[SIZEOF_REF] = "";
1024 static char opt_head_rev[SIZEOF_REV] = "";
1025 static char opt_remote[SIZEOF_REF] = "";
1026 static char opt_encoding[20] = "UTF-8";
1027 static char opt_codeset[20] = "UTF-8";
1028 static iconv_t opt_iconv_in = ICONV_NONE;
1029 static iconv_t opt_iconv_out = ICONV_NONE;
1030 static char opt_search[SIZEOF_STR] = "";
1031 static char opt_cdup[SIZEOF_STR] = "";
1032 static char opt_prefix[SIZEOF_STR] = "";
1033 static char opt_git_dir[SIZEOF_STR] = "";
1034 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1035 static char opt_editor[SIZEOF_STR] = "";
1036 static FILE *opt_tty = NULL;
1038 #define is_initial_commit() (!*opt_head_rev)
1039 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
1040 #define mkdate(time) string_date(time, opt_date)
1044 * Line-oriented content detection.
1047 #define LINE_INFO \
1048 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1049 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1050 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1051 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1052 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1053 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1054 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1055 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1056 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1057 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1058 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1059 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1060 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1061 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1062 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1063 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1064 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1065 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1066 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1067 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1068 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1069 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1070 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1071 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1072 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1073 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1074 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1075 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1076 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1077 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1078 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1079 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1080 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1081 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1082 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1083 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1084 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1085 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1086 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1087 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1088 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1090 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1091 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1092 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1093 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1094 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1095 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1096 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1098 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1099 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1100 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1101 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1102 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1104 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1106 enum line_type {
1107 #define LINE(type, line, fg, bg, attr) \
1108 LINE_##type
1109 LINE_INFO,
1110 LINE_NONE
1111 #undef LINE
1114 struct line_info {
1115 const char *name; /* Option name. */
1116 int namelen; /* Size of option name. */
1117 const char *line; /* The start of line to match. */
1118 int linelen; /* Size of string to match. */
1119 int fg, bg, attr; /* Color and text attributes for the lines. */
1122 static struct line_info line_info[] = {
1123 #define LINE(type, line, fg, bg, attr) \
1124 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1125 LINE_INFO
1126 #undef LINE
1129 static enum line_type
1130 get_line_type(const char *line)
1132 int linelen = strlen(line);
1133 enum line_type type;
1135 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1136 /* Case insensitive search matches Signed-off-by lines better. */
1137 if (linelen >= line_info[type].linelen &&
1138 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1139 return type;
1141 return LINE_DEFAULT;
1144 static inline int
1145 get_line_attr(enum line_type type)
1147 assert(type < ARRAY_SIZE(line_info));
1148 return COLOR_PAIR(type) | line_info[type].attr;
1151 static struct line_info *
1152 get_line_info(const char *name)
1154 size_t namelen = strlen(name);
1155 enum line_type type;
1157 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1158 if (enum_equals(line_info[type], name, namelen))
1159 return &line_info[type];
1161 return NULL;
1164 static void
1165 init_colors(void)
1167 int default_bg = line_info[LINE_DEFAULT].bg;
1168 int default_fg = line_info[LINE_DEFAULT].fg;
1169 enum line_type type;
1171 start_color();
1173 if (assume_default_colors(default_fg, default_bg) == ERR) {
1174 default_bg = COLOR_BLACK;
1175 default_fg = COLOR_WHITE;
1178 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1179 struct line_info *info = &line_info[type];
1180 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1181 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1183 init_pair(type, fg, bg);
1187 struct line {
1188 enum line_type type;
1190 /* State flags */
1191 unsigned int selected:1;
1192 unsigned int dirty:1;
1193 unsigned int cleareol:1;
1194 unsigned int other:16;
1196 void *data; /* User data */
1201 * Keys
1204 struct keybinding {
1205 int alias;
1206 enum request request;
1209 static const struct keybinding default_keybindings[] = {
1210 /* View switching */
1211 { 'm', REQ_VIEW_MAIN },
1212 { 'd', REQ_VIEW_DIFF },
1213 { 'l', REQ_VIEW_LOG },
1214 { 't', REQ_VIEW_TREE },
1215 { 'f', REQ_VIEW_BLOB },
1216 { 'B', REQ_VIEW_BLAME },
1217 { 'H', REQ_VIEW_BRANCH },
1218 { 'p', REQ_VIEW_PAGER },
1219 { 'h', REQ_VIEW_HELP },
1220 { 'S', REQ_VIEW_STATUS },
1221 { 'c', REQ_VIEW_STAGE },
1223 /* View manipulation */
1224 { 'q', REQ_VIEW_CLOSE },
1225 { KEY_TAB, REQ_VIEW_NEXT },
1226 { KEY_RETURN, REQ_ENTER },
1227 { KEY_UP, REQ_PREVIOUS },
1228 { KEY_DOWN, REQ_NEXT },
1229 { 'R', REQ_REFRESH },
1230 { KEY_F(5), REQ_REFRESH },
1231 { 'O', REQ_MAXIMIZE },
1233 /* Cursor navigation */
1234 { 'k', REQ_MOVE_UP },
1235 { 'j', REQ_MOVE_DOWN },
1236 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1237 { KEY_END, REQ_MOVE_LAST_LINE },
1238 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1239 { ' ', REQ_MOVE_PAGE_DOWN },
1240 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1241 { 'b', REQ_MOVE_PAGE_UP },
1242 { '-', REQ_MOVE_PAGE_UP },
1244 /* Scrolling */
1245 { KEY_LEFT, REQ_SCROLL_LEFT },
1246 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1247 { KEY_IC, REQ_SCROLL_LINE_UP },
1248 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1249 { 'w', REQ_SCROLL_PAGE_UP },
1250 { 's', REQ_SCROLL_PAGE_DOWN },
1252 /* Searching */
1253 { '/', REQ_SEARCH },
1254 { '?', REQ_SEARCH_BACK },
1255 { 'n', REQ_FIND_NEXT },
1256 { 'N', REQ_FIND_PREV },
1258 /* Misc */
1259 { 'Q', REQ_QUIT },
1260 { 'z', REQ_STOP_LOADING },
1261 { 'v', REQ_SHOW_VERSION },
1262 { 'r', REQ_SCREEN_REDRAW },
1263 { 'o', REQ_OPTIONS },
1264 { '.', REQ_TOGGLE_LINENO },
1265 { 'D', REQ_TOGGLE_DATE },
1266 { 'A', REQ_TOGGLE_AUTHOR },
1267 { 'g', REQ_TOGGLE_REV_GRAPH },
1268 { 'F', REQ_TOGGLE_REFS },
1269 { 'I', REQ_TOGGLE_SORT_ORDER },
1270 { 'i', REQ_TOGGLE_SORT_FIELD },
1271 { ':', REQ_PROMPT },
1272 { 'u', REQ_STATUS_UPDATE },
1273 { '!', REQ_STATUS_REVERT },
1274 { 'M', REQ_STATUS_MERGE },
1275 { '@', REQ_STAGE_NEXT },
1276 { ',', REQ_PARENT },
1277 { 'e', REQ_EDIT },
1280 #define KEYMAP_INFO \
1281 KEYMAP_(GENERIC), \
1282 KEYMAP_(MAIN), \
1283 KEYMAP_(DIFF), \
1284 KEYMAP_(LOG), \
1285 KEYMAP_(TREE), \
1286 KEYMAP_(BLOB), \
1287 KEYMAP_(BLAME), \
1288 KEYMAP_(BRANCH), \
1289 KEYMAP_(PAGER), \
1290 KEYMAP_(HELP), \
1291 KEYMAP_(STATUS), \
1292 KEYMAP_(STAGE)
1294 enum keymap {
1295 #define KEYMAP_(name) KEYMAP_##name
1296 KEYMAP_INFO
1297 #undef KEYMAP_
1300 static const struct enum_map keymap_table[] = {
1301 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1302 KEYMAP_INFO
1303 #undef KEYMAP_
1306 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1308 struct keybinding_table {
1309 struct keybinding *data;
1310 size_t size;
1313 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1315 static void
1316 add_keybinding(enum keymap keymap, enum request request, int key)
1318 struct keybinding_table *table = &keybindings[keymap];
1320 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1321 if (!table->data)
1322 die("Failed to allocate keybinding");
1323 table->data[table->size].alias = key;
1324 table->data[table->size++].request = request;
1327 /* Looks for a key binding first in the given map, then in the generic map, and
1328 * lastly in the default keybindings. */
1329 static enum request
1330 get_keybinding(enum keymap keymap, int key)
1332 size_t i;
1334 for (i = 0; i < keybindings[keymap].size; i++)
1335 if (keybindings[keymap].data[i].alias == key)
1336 return keybindings[keymap].data[i].request;
1338 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1339 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1340 return keybindings[KEYMAP_GENERIC].data[i].request;
1342 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1343 if (default_keybindings[i].alias == key)
1344 return default_keybindings[i].request;
1346 return (enum request) key;
1350 struct key {
1351 const char *name;
1352 int value;
1355 static const struct key key_table[] = {
1356 { "Enter", KEY_RETURN },
1357 { "Space", ' ' },
1358 { "Backspace", KEY_BACKSPACE },
1359 { "Tab", KEY_TAB },
1360 { "Escape", KEY_ESC },
1361 { "Left", KEY_LEFT },
1362 { "Right", KEY_RIGHT },
1363 { "Up", KEY_UP },
1364 { "Down", KEY_DOWN },
1365 { "Insert", KEY_IC },
1366 { "Delete", KEY_DC },
1367 { "Hash", '#' },
1368 { "Home", KEY_HOME },
1369 { "End", KEY_END },
1370 { "PageUp", KEY_PPAGE },
1371 { "PageDown", KEY_NPAGE },
1372 { "F1", KEY_F(1) },
1373 { "F2", KEY_F(2) },
1374 { "F3", KEY_F(3) },
1375 { "F4", KEY_F(4) },
1376 { "F5", KEY_F(5) },
1377 { "F6", KEY_F(6) },
1378 { "F7", KEY_F(7) },
1379 { "F8", KEY_F(8) },
1380 { "F9", KEY_F(9) },
1381 { "F10", KEY_F(10) },
1382 { "F11", KEY_F(11) },
1383 { "F12", KEY_F(12) },
1386 static int
1387 get_key_value(const char *name)
1389 int i;
1391 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1392 if (!strcasecmp(key_table[i].name, name))
1393 return key_table[i].value;
1395 if (strlen(name) == 1 && isprint(*name))
1396 return (int) *name;
1398 return ERR;
1401 static const char *
1402 get_key_name(int key_value)
1404 static char key_char[] = "'X'";
1405 const char *seq = NULL;
1406 int key;
1408 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1409 if (key_table[key].value == key_value)
1410 seq = key_table[key].name;
1412 if (seq == NULL &&
1413 key_value < 127 &&
1414 isprint(key_value)) {
1415 key_char[1] = (char) key_value;
1416 seq = key_char;
1419 return seq ? seq : "(no key)";
1422 static bool
1423 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1425 const char *sep = *pos > 0 ? ", " : "";
1426 const char *keyname = get_key_name(keybinding->alias);
1428 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1431 static bool
1432 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1433 enum keymap keymap, bool all)
1435 int i;
1437 for (i = 0; i < keybindings[keymap].size; i++) {
1438 if (keybindings[keymap].data[i].request == request) {
1439 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1440 return FALSE;
1441 if (!all)
1442 break;
1446 return TRUE;
1449 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1451 static const char *
1452 get_keys(enum keymap keymap, enum request request, bool all)
1454 static char buf[BUFSIZ];
1455 size_t pos = 0;
1456 int i;
1458 buf[pos] = 0;
1460 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1461 return "Too many keybindings!";
1462 if (pos > 0 && !all)
1463 return buf;
1465 if (keymap != KEYMAP_GENERIC) {
1466 /* Only the generic keymap includes the default keybindings when
1467 * listing all keys. */
1468 if (all)
1469 return buf;
1471 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1472 return "Too many keybindings!";
1473 if (pos)
1474 return buf;
1477 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1478 if (default_keybindings[i].request == request) {
1479 if (!append_key(buf, &pos, &default_keybindings[i]))
1480 return "Too many keybindings!";
1481 if (!all)
1482 return buf;
1486 return buf;
1489 struct run_request {
1490 enum keymap keymap;
1491 int key;
1492 const char *argv[SIZEOF_ARG];
1495 static struct run_request *run_request;
1496 static size_t run_requests;
1498 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1500 static enum request
1501 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1503 struct run_request *req;
1505 if (argc >= ARRAY_SIZE(req->argv) - 1)
1506 return REQ_NONE;
1508 if (!realloc_run_requests(&run_request, run_requests, 1))
1509 return REQ_NONE;
1511 req = &run_request[run_requests];
1512 req->keymap = keymap;
1513 req->key = key;
1514 req->argv[0] = NULL;
1516 if (!format_argv(req->argv, argv, FORMAT_NONE))
1517 return REQ_NONE;
1519 return REQ_NONE + ++run_requests;
1522 static struct run_request *
1523 get_run_request(enum request request)
1525 if (request <= REQ_NONE)
1526 return NULL;
1527 return &run_request[request - REQ_NONE - 1];
1530 static void
1531 add_builtin_run_requests(void)
1533 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1534 const char *commit[] = { "git", "commit", NULL };
1535 const char *gc[] = { "git", "gc", NULL };
1536 struct {
1537 enum keymap keymap;
1538 int key;
1539 int argc;
1540 const char **argv;
1541 } reqs[] = {
1542 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1543 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1544 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1546 int i;
1548 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1549 enum request req;
1551 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1552 if (req != REQ_NONE)
1553 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1558 * User config file handling.
1561 static int config_lineno;
1562 static bool config_errors;
1563 static const char *config_msg;
1565 static const struct enum_map color_map[] = {
1566 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1567 COLOR_MAP(DEFAULT),
1568 COLOR_MAP(BLACK),
1569 COLOR_MAP(BLUE),
1570 COLOR_MAP(CYAN),
1571 COLOR_MAP(GREEN),
1572 COLOR_MAP(MAGENTA),
1573 COLOR_MAP(RED),
1574 COLOR_MAP(WHITE),
1575 COLOR_MAP(YELLOW),
1578 static const struct enum_map attr_map[] = {
1579 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1580 ATTR_MAP(NORMAL),
1581 ATTR_MAP(BLINK),
1582 ATTR_MAP(BOLD),
1583 ATTR_MAP(DIM),
1584 ATTR_MAP(REVERSE),
1585 ATTR_MAP(STANDOUT),
1586 ATTR_MAP(UNDERLINE),
1589 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1591 static int parse_step(double *opt, const char *arg)
1593 *opt = atoi(arg);
1594 if (!strchr(arg, '%'))
1595 return OK;
1597 /* "Shift down" so 100% and 1 does not conflict. */
1598 *opt = (*opt - 1) / 100;
1599 if (*opt >= 1.0) {
1600 *opt = 0.99;
1601 config_msg = "Step value larger than 100%";
1602 return ERR;
1604 if (*opt < 0.0) {
1605 *opt = 1;
1606 config_msg = "Invalid step value";
1607 return ERR;
1609 return OK;
1612 static int
1613 parse_int(int *opt, const char *arg, int min, int max)
1615 int value = atoi(arg);
1617 if (min <= value && value <= max) {
1618 *opt = value;
1619 return OK;
1622 config_msg = "Integer value out of bound";
1623 return ERR;
1626 static bool
1627 set_color(int *color, const char *name)
1629 if (map_enum(color, color_map, name))
1630 return TRUE;
1631 if (!prefixcmp(name, "color"))
1632 return parse_int(color, name + 5, 0, 255) == OK;
1633 return FALSE;
1636 /* Wants: object fgcolor bgcolor [attribute] */
1637 static int
1638 option_color_command(int argc, const char *argv[])
1640 struct line_info *info;
1642 if (argc < 3) {
1643 config_msg = "Wrong number of arguments given to color command";
1644 return ERR;
1647 info = get_line_info(argv[0]);
1648 if (!info) {
1649 static const struct enum_map obsolete[] = {
1650 ENUM_MAP("main-delim", LINE_DELIMITER),
1651 ENUM_MAP("main-date", LINE_DATE),
1652 ENUM_MAP("main-author", LINE_AUTHOR),
1654 int index;
1656 if (!map_enum(&index, obsolete, argv[0])) {
1657 config_msg = "Unknown color name";
1658 return ERR;
1660 info = &line_info[index];
1663 if (!set_color(&info->fg, argv[1]) ||
1664 !set_color(&info->bg, argv[2])) {
1665 config_msg = "Unknown color";
1666 return ERR;
1669 info->attr = 0;
1670 while (argc-- > 3) {
1671 int attr;
1673 if (!set_attribute(&attr, argv[argc])) {
1674 config_msg = "Unknown attribute";
1675 return ERR;
1677 info->attr |= attr;
1680 return OK;
1683 static int parse_bool(bool *opt, const char *arg)
1685 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1686 ? TRUE : FALSE;
1687 return OK;
1690 static int parse_enum_do(unsigned int *opt, const char *arg,
1691 const struct enum_map *map, size_t map_size)
1693 bool is_true;
1695 assert(map_size > 1);
1697 if (map_enum_do(map, map_size, (int *) opt, arg))
1698 return OK;
1700 if (parse_bool(&is_true, arg) != OK)
1701 return ERR;
1703 *opt = is_true ? map[1].value : map[0].value;
1704 return OK;
1707 #define parse_enum(opt, arg, map) \
1708 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1710 static int
1711 parse_string(char *opt, const char *arg, size_t optsize)
1713 int arglen = strlen(arg);
1715 switch (arg[0]) {
1716 case '\"':
1717 case '\'':
1718 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1719 config_msg = "Unmatched quotation";
1720 return ERR;
1722 arg += 1; arglen -= 2;
1723 default:
1724 string_ncopy_do(opt, optsize, arg, arglen);
1725 return OK;
1729 /* Wants: name = value */
1730 static int
1731 option_set_command(int argc, const char *argv[])
1733 if (argc != 3) {
1734 config_msg = "Wrong number of arguments given to set command";
1735 return ERR;
1738 if (strcmp(argv[1], "=")) {
1739 config_msg = "No value assigned";
1740 return ERR;
1743 if (!strcmp(argv[0], "show-author"))
1744 return parse_bool(&opt_author, argv[2]);
1746 if (!strcmp(argv[0], "show-date"))
1747 return parse_enum(&opt_date, argv[2], date_map);
1749 if (!strcmp(argv[0], "show-rev-graph"))
1750 return parse_bool(&opt_rev_graph, argv[2]);
1752 if (!strcmp(argv[0], "show-refs"))
1753 return parse_bool(&opt_show_refs, argv[2]);
1755 if (!strcmp(argv[0], "show-line-numbers"))
1756 return parse_bool(&opt_line_number, argv[2]);
1758 if (!strcmp(argv[0], "line-graphics"))
1759 return parse_bool(&opt_line_graphics, argv[2]);
1761 if (!strcmp(argv[0], "line-number-interval"))
1762 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1764 if (!strcmp(argv[0], "author-width"))
1765 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1767 if (!strcmp(argv[0], "horizontal-scroll"))
1768 return parse_step(&opt_hscroll, argv[2]);
1770 if (!strcmp(argv[0], "split-view-height"))
1771 return parse_step(&opt_scale_split_view, argv[2]);
1773 if (!strcmp(argv[0], "tab-size"))
1774 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1776 if (!strcmp(argv[0], "commit-encoding"))
1777 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1779 config_msg = "Unknown variable name";
1780 return ERR;
1783 /* Wants: mode request key */
1784 static int
1785 option_bind_command(int argc, const char *argv[])
1787 enum request request;
1788 int keymap = -1;
1789 int key;
1791 if (argc < 3) {
1792 config_msg = "Wrong number of arguments given to bind command";
1793 return ERR;
1796 if (set_keymap(&keymap, argv[0]) == ERR) {
1797 config_msg = "Unknown key map";
1798 return ERR;
1801 key = get_key_value(argv[1]);
1802 if (key == ERR) {
1803 config_msg = "Unknown key";
1804 return ERR;
1807 request = get_request(argv[2]);
1808 if (request == REQ_NONE) {
1809 static const struct enum_map obsolete[] = {
1810 ENUM_MAP("cherry-pick", REQ_NONE),
1811 ENUM_MAP("screen-resize", REQ_NONE),
1812 ENUM_MAP("tree-parent", REQ_PARENT),
1814 int alias;
1816 if (map_enum(&alias, obsolete, argv[2])) {
1817 if (alias != REQ_NONE)
1818 add_keybinding(keymap, alias, key);
1819 config_msg = "Obsolete request name";
1820 return ERR;
1823 if (request == REQ_NONE && *argv[2]++ == '!')
1824 request = add_run_request(keymap, key, argc - 2, argv + 2);
1825 if (request == REQ_NONE) {
1826 config_msg = "Unknown request name";
1827 return ERR;
1830 add_keybinding(keymap, request, key);
1832 return OK;
1835 static int
1836 set_option(const char *opt, char *value)
1838 const char *argv[SIZEOF_ARG];
1839 int argc = 0;
1841 if (!argv_from_string(argv, &argc, value)) {
1842 config_msg = "Too many option arguments";
1843 return ERR;
1846 if (!strcmp(opt, "color"))
1847 return option_color_command(argc, argv);
1849 if (!strcmp(opt, "set"))
1850 return option_set_command(argc, argv);
1852 if (!strcmp(opt, "bind"))
1853 return option_bind_command(argc, argv);
1855 config_msg = "Unknown option command";
1856 return ERR;
1859 static int
1860 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1862 int status = OK;
1864 config_lineno++;
1865 config_msg = "Internal error";
1867 /* Check for comment markers, since read_properties() will
1868 * only ensure opt and value are split at first " \t". */
1869 optlen = strcspn(opt, "#");
1870 if (optlen == 0)
1871 return OK;
1873 if (opt[optlen] != 0) {
1874 config_msg = "No option value";
1875 status = ERR;
1877 } else {
1878 /* Look for comment endings in the value. */
1879 size_t len = strcspn(value, "#");
1881 if (len < valuelen) {
1882 valuelen = len;
1883 value[valuelen] = 0;
1886 status = set_option(opt, value);
1889 if (status == ERR) {
1890 warn("Error on line %d, near '%.*s': %s",
1891 config_lineno, (int) optlen, opt, config_msg);
1892 config_errors = TRUE;
1895 /* Always keep going if errors are encountered. */
1896 return OK;
1899 static void
1900 load_option_file(const char *path)
1902 struct io io = {};
1904 /* It's OK that the file doesn't exist. */
1905 if (!io_open(&io, "%s", path))
1906 return;
1908 config_lineno = 0;
1909 config_errors = FALSE;
1911 if (io_load(&io, " \t", read_option) == ERR ||
1912 config_errors == TRUE)
1913 warn("Errors while loading %s.", path);
1916 static int
1917 load_options(void)
1919 const char *home = getenv("HOME");
1920 const char *tigrc_user = getenv("TIGRC_USER");
1921 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1922 char buf[SIZEOF_STR];
1924 add_builtin_run_requests();
1926 if (!tigrc_system)
1927 tigrc_system = SYSCONFDIR "/tigrc";
1928 load_option_file(tigrc_system);
1930 if (!tigrc_user) {
1931 if (!home || !string_format(buf, "%s/.tigrc", home))
1932 return ERR;
1933 tigrc_user = buf;
1935 load_option_file(tigrc_user);
1937 return OK;
1942 * The viewer
1945 struct view;
1946 struct view_ops;
1948 /* The display array of active views and the index of the current view. */
1949 static struct view *display[2];
1950 static unsigned int current_view;
1952 #define foreach_displayed_view(view, i) \
1953 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1955 #define displayed_views() (display[1] != NULL ? 2 : 1)
1957 /* Current head and commit ID */
1958 static char ref_blob[SIZEOF_REF] = "";
1959 static char ref_commit[SIZEOF_REF] = "HEAD";
1960 static char ref_head[SIZEOF_REF] = "HEAD";
1962 struct view {
1963 const char *name; /* View name */
1964 const char *cmd_env; /* Command line set via environment */
1965 const char *id; /* Points to either of ref_{head,commit,blob} */
1967 struct view_ops *ops; /* View operations */
1969 enum keymap keymap; /* What keymap does this view have */
1970 bool git_dir; /* Whether the view requires a git directory. */
1972 char ref[SIZEOF_REF]; /* Hovered commit reference */
1973 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1975 int height, width; /* The width and height of the main window */
1976 WINDOW *win; /* The main window */
1977 WINDOW *title; /* The title window living below the main window */
1979 /* Navigation */
1980 unsigned long offset; /* Offset of the window top */
1981 unsigned long yoffset; /* Offset from the window side. */
1982 unsigned long lineno; /* Current line number */
1983 unsigned long p_offset; /* Previous offset of the window top */
1984 unsigned long p_yoffset;/* Previous offset from the window side */
1985 unsigned long p_lineno; /* Previous current line number */
1986 bool p_restore; /* Should the previous position be restored. */
1988 /* Searching */
1989 char grep[SIZEOF_STR]; /* Search string */
1990 regex_t *regex; /* Pre-compiled regexp */
1992 /* If non-NULL, points to the view that opened this view. If this view
1993 * is closed tig will switch back to the parent view. */
1994 struct view *parent;
1996 /* Buffering */
1997 size_t lines; /* Total number of lines */
1998 struct line *line; /* Line index */
1999 unsigned int digits; /* Number of digits in the lines member. */
2001 /* Drawing */
2002 struct line *curline; /* Line currently being drawn. */
2003 enum line_type curtype; /* Attribute currently used for drawing. */
2004 unsigned long col; /* Column when drawing. */
2005 bool has_scrolled; /* View was scrolled. */
2007 /* Loading */
2008 struct io io;
2009 struct io *pipe;
2010 time_t start_time;
2011 time_t update_secs;
2014 struct view_ops {
2015 /* What type of content being displayed. Used in the title bar. */
2016 const char *type;
2017 /* Default command arguments. */
2018 const char **argv;
2019 /* Open and reads in all view content. */
2020 bool (*open)(struct view *view);
2021 /* Read one line; updates view->line. */
2022 bool (*read)(struct view *view, char *data);
2023 /* Draw one line; @lineno must be < view->height. */
2024 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2025 /* Depending on view handle a special requests. */
2026 enum request (*request)(struct view *view, enum request request, struct line *line);
2027 /* Search for regexp in a line. */
2028 bool (*grep)(struct view *view, struct line *line);
2029 /* Select line */
2030 void (*select)(struct view *view, struct line *line);
2031 /* Prepare view for loading */
2032 bool (*prepare)(struct view *view);
2035 static struct view_ops blame_ops;
2036 static struct view_ops blob_ops;
2037 static struct view_ops diff_ops;
2038 static struct view_ops help_ops;
2039 static struct view_ops log_ops;
2040 static struct view_ops main_ops;
2041 static struct view_ops pager_ops;
2042 static struct view_ops stage_ops;
2043 static struct view_ops status_ops;
2044 static struct view_ops tree_ops;
2045 static struct view_ops branch_ops;
2047 #define VIEW_STR(name, env, ref, ops, map, git) \
2048 { name, #env, ref, ops, map, git }
2050 #define VIEW_(id, name, ops, git, ref) \
2051 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2054 static struct view views[] = {
2055 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2056 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2057 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2058 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2059 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2060 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2061 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2062 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2063 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2064 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2065 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2068 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2069 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2071 #define foreach_view(view, i) \
2072 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2074 #define view_is_displayed(view) \
2075 (view == display[0] || view == display[1])
2078 enum line_graphic {
2079 LINE_GRAPHIC_VLINE
2082 static chtype line_graphics[] = {
2083 /* LINE_GRAPHIC_VLINE: */ '|'
2086 static inline void
2087 set_view_attr(struct view *view, enum line_type type)
2089 if (!view->curline->selected && view->curtype != type) {
2090 wattrset(view->win, get_line_attr(type));
2091 wchgat(view->win, -1, 0, type, NULL);
2092 view->curtype = type;
2096 static int
2097 draw_chars(struct view *view, enum line_type type, const char *string,
2098 int max_len, bool use_tilde)
2100 static char out_buffer[BUFSIZ * 2];
2101 int len = 0;
2102 int col = 0;
2103 int trimmed = FALSE;
2104 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2106 if (max_len <= 0)
2107 return 0;
2109 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
2111 set_view_attr(view, type);
2112 if (len > 0) {
2113 if (opt_iconv_out != ICONV_NONE) {
2114 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2115 size_t inlen = len + 1;
2117 char *outbuf = out_buffer;
2118 size_t outlen = sizeof(out_buffer);
2120 size_t ret;
2122 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2123 if (ret != (size_t) -1) {
2124 string = out_buffer;
2125 len = sizeof(out_buffer) - outlen;
2129 waddnstr(view->win, string, len);
2131 if (trimmed && use_tilde) {
2132 set_view_attr(view, LINE_DELIMITER);
2133 waddch(view->win, '~');
2134 col++;
2137 return col;
2140 static int
2141 draw_space(struct view *view, enum line_type type, int max, int spaces)
2143 static char space[] = " ";
2144 int col = 0;
2146 spaces = MIN(max, spaces);
2148 while (spaces > 0) {
2149 int len = MIN(spaces, sizeof(space) - 1);
2151 col += draw_chars(view, type, space, len, FALSE);
2152 spaces -= len;
2155 return col;
2158 static bool
2159 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2161 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2162 return view->width + view->yoffset <= view->col;
2165 static bool
2166 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2168 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2169 int max = view->width + view->yoffset - view->col;
2170 int i;
2172 if (max < size)
2173 size = max;
2175 set_view_attr(view, type);
2176 /* Using waddch() instead of waddnstr() ensures that
2177 * they'll be rendered correctly for the cursor line. */
2178 for (i = skip; i < size; i++)
2179 waddch(view->win, graphic[i]);
2181 view->col += size;
2182 if (size < max && skip <= size)
2183 waddch(view->win, ' ');
2184 view->col++;
2186 return view->width + view->yoffset <= view->col;
2189 static bool
2190 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2192 int max = MIN(view->width + view->yoffset - view->col, len);
2193 int col;
2195 if (text)
2196 col = draw_chars(view, type, text, max - 1, trim);
2197 else
2198 col = draw_space(view, type, max - 1, max - 1);
2200 view->col += col;
2201 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2202 return view->width + view->yoffset <= view->col;
2205 static bool
2206 draw_date(struct view *view, time_t *time)
2208 const char *date = time ? mkdate(time) : "";
2209 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2211 return draw_field(view, LINE_DATE, date, cols, FALSE);
2214 static bool
2215 draw_author(struct view *view, const char *author)
2217 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2219 if (!trim) {
2220 static char initials[10];
2221 size_t pos;
2223 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2225 memset(initials, 0, sizeof(initials));
2226 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2227 while (is_initial_sep(*author))
2228 author++;
2229 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2230 while (*author && !is_initial_sep(author[1]))
2231 author++;
2234 author = initials;
2237 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2240 static bool
2241 draw_mode(struct view *view, mode_t mode)
2243 const char *str;
2245 if (S_ISDIR(mode))
2246 str = "drwxr-xr-x";
2247 else if (S_ISLNK(mode))
2248 str = "lrwxrwxrwx";
2249 else if (S_ISGITLINK(mode))
2250 str = "m---------";
2251 else if (S_ISREG(mode) && mode & S_IXUSR)
2252 str = "-rwxr-xr-x";
2253 else if (S_ISREG(mode))
2254 str = "-rw-r--r--";
2255 else
2256 str = "----------";
2258 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2261 static bool
2262 draw_lineno(struct view *view, unsigned int lineno)
2264 char number[10];
2265 int digits3 = view->digits < 3 ? 3 : view->digits;
2266 int max = MIN(view->width + view->yoffset - view->col, digits3);
2267 char *text = NULL;
2269 lineno += view->offset + 1;
2270 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2271 static char fmt[] = "%1ld";
2273 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2274 if (string_format(number, fmt, lineno))
2275 text = number;
2277 if (text)
2278 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2279 else
2280 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2281 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2284 static bool
2285 draw_view_line(struct view *view, unsigned int lineno)
2287 struct line *line;
2288 bool selected = (view->offset + lineno == view->lineno);
2290 assert(view_is_displayed(view));
2292 if (view->offset + lineno >= view->lines)
2293 return FALSE;
2295 line = &view->line[view->offset + lineno];
2297 wmove(view->win, lineno, 0);
2298 if (line->cleareol)
2299 wclrtoeol(view->win);
2300 view->col = 0;
2301 view->curline = line;
2302 view->curtype = LINE_NONE;
2303 line->selected = FALSE;
2304 line->dirty = line->cleareol = 0;
2306 if (selected) {
2307 set_view_attr(view, LINE_CURSOR);
2308 line->selected = TRUE;
2309 view->ops->select(view, line);
2312 return view->ops->draw(view, line, lineno);
2315 static void
2316 redraw_view_dirty(struct view *view)
2318 bool dirty = FALSE;
2319 int lineno;
2321 for (lineno = 0; lineno < view->height; lineno++) {
2322 if (view->offset + lineno >= view->lines)
2323 break;
2324 if (!view->line[view->offset + lineno].dirty)
2325 continue;
2326 dirty = TRUE;
2327 if (!draw_view_line(view, lineno))
2328 break;
2331 if (!dirty)
2332 return;
2333 wnoutrefresh(view->win);
2336 static void
2337 redraw_view_from(struct view *view, int lineno)
2339 assert(0 <= lineno && lineno < view->height);
2341 for (; lineno < view->height; lineno++) {
2342 if (!draw_view_line(view, lineno))
2343 break;
2346 wnoutrefresh(view->win);
2349 static void
2350 redraw_view(struct view *view)
2352 werase(view->win);
2353 redraw_view_from(view, 0);
2357 static void
2358 update_view_title(struct view *view)
2360 char buf[SIZEOF_STR];
2361 char state[SIZEOF_STR];
2362 size_t bufpos = 0, statelen = 0;
2364 assert(view_is_displayed(view));
2366 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2367 unsigned int view_lines = view->offset + view->height;
2368 unsigned int lines = view->lines
2369 ? MIN(view_lines, view->lines) * 100 / view->lines
2370 : 0;
2372 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2373 view->ops->type,
2374 view->lineno + 1,
2375 view->lines,
2376 lines);
2380 if (view->pipe) {
2381 time_t secs = time(NULL) - view->start_time;
2383 /* Three git seconds are a long time ... */
2384 if (secs > 2)
2385 string_format_from(state, &statelen, " loading %lds", secs);
2388 string_format_from(buf, &bufpos, "[%s]", view->name);
2389 if (*view->ref && bufpos < view->width) {
2390 size_t refsize = strlen(view->ref);
2391 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2393 if (minsize < view->width)
2394 refsize = view->width - minsize + 7;
2395 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2398 if (statelen && bufpos < view->width) {
2399 string_format_from(buf, &bufpos, "%s", state);
2402 if (view == display[current_view])
2403 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2404 else
2405 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2407 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2408 wclrtoeol(view->title);
2409 wnoutrefresh(view->title);
2412 static int
2413 apply_step(double step, int value)
2415 if (step >= 1)
2416 return (int) step;
2417 value *= step + 0.01;
2418 return value ? value : 1;
2421 static void
2422 resize_display(void)
2424 int offset, i;
2425 struct view *base = display[0];
2426 struct view *view = display[1] ? display[1] : display[0];
2428 /* Setup window dimensions */
2430 getmaxyx(stdscr, base->height, base->width);
2432 /* Make room for the status window. */
2433 base->height -= 1;
2435 if (view != base) {
2436 /* Horizontal split. */
2437 view->width = base->width;
2438 view->height = apply_step(opt_scale_split_view, base->height);
2439 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2440 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2441 base->height -= view->height;
2443 /* Make room for the title bar. */
2444 view->height -= 1;
2447 /* Make room for the title bar. */
2448 base->height -= 1;
2450 offset = 0;
2452 foreach_displayed_view (view, i) {
2453 if (!view->win) {
2454 view->win = newwin(view->height, 0, offset, 0);
2455 if (!view->win)
2456 die("Failed to create %s view", view->name);
2458 scrollok(view->win, FALSE);
2460 view->title = newwin(1, 0, offset + view->height, 0);
2461 if (!view->title)
2462 die("Failed to create title window");
2464 } else {
2465 wresize(view->win, view->height, view->width);
2466 mvwin(view->win, offset, 0);
2467 mvwin(view->title, offset + view->height, 0);
2470 offset += view->height + 1;
2474 static void
2475 redraw_display(bool clear)
2477 struct view *view;
2478 int i;
2480 foreach_displayed_view (view, i) {
2481 if (clear)
2482 wclear(view->win);
2483 redraw_view(view);
2484 update_view_title(view);
2488 static void
2489 toggle_enum_option_do(unsigned int *opt, const char *help,
2490 const struct enum_map *map, size_t size)
2492 *opt = (*opt + 1) % size;
2493 redraw_display(FALSE);
2494 report("Displaying %s %s", enum_name(map[*opt]), help);
2497 #define toggle_enum_option(opt, help, map) \
2498 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2500 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2502 static void
2503 toggle_view_option(bool *option, const char *help)
2505 *option = !*option;
2506 redraw_display(FALSE);
2507 report("%sabling %s", *option ? "En" : "Dis", help);
2510 static void
2511 open_option_menu(void)
2513 const struct menu_item menu[] = {
2514 { '.', "line numbers", &opt_line_number },
2515 { 'D', "date display", &opt_date },
2516 { 'A', "author display", &opt_author },
2517 { 'g', "revision graph display", &opt_rev_graph },
2518 { 'F', "reference display", &opt_show_refs },
2519 { 0 }
2521 int selected = 0;
2523 if (prompt_menu("Toggle option", menu, &selected)) {
2524 if (menu[selected].data == &opt_date)
2525 toggle_date();
2526 else
2527 toggle_view_option(menu[selected].data, menu[selected].text);
2531 static void
2532 maximize_view(struct view *view)
2534 memset(display, 0, sizeof(display));
2535 current_view = 0;
2536 display[current_view] = view;
2537 resize_display();
2538 redraw_display(FALSE);
2539 report("");
2544 * Navigation
2547 static bool
2548 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2550 if (lineno >= view->lines)
2551 lineno = view->lines > 0 ? view->lines - 1 : 0;
2553 if (offset > lineno || offset + view->height <= lineno) {
2554 unsigned long half = view->height / 2;
2556 if (lineno > half)
2557 offset = lineno - half;
2558 else
2559 offset = 0;
2562 if (offset != view->offset || lineno != view->lineno) {
2563 view->offset = offset;
2564 view->lineno = lineno;
2565 return TRUE;
2568 return FALSE;
2571 /* Scrolling backend */
2572 static void
2573 do_scroll_view(struct view *view, int lines)
2575 bool redraw_current_line = FALSE;
2577 /* The rendering expects the new offset. */
2578 view->offset += lines;
2580 assert(0 <= view->offset && view->offset < view->lines);
2581 assert(lines);
2583 /* Move current line into the view. */
2584 if (view->lineno < view->offset) {
2585 view->lineno = view->offset;
2586 redraw_current_line = TRUE;
2587 } else if (view->lineno >= view->offset + view->height) {
2588 view->lineno = view->offset + view->height - 1;
2589 redraw_current_line = TRUE;
2592 assert(view->offset <= view->lineno && view->lineno < view->lines);
2594 /* Redraw the whole screen if scrolling is pointless. */
2595 if (view->height < ABS(lines)) {
2596 redraw_view(view);
2598 } else {
2599 int line = lines > 0 ? view->height - lines : 0;
2600 int end = line + ABS(lines);
2602 scrollok(view->win, TRUE);
2603 wscrl(view->win, lines);
2604 scrollok(view->win, FALSE);
2606 while (line < end && draw_view_line(view, line))
2607 line++;
2609 if (redraw_current_line)
2610 draw_view_line(view, view->lineno - view->offset);
2611 wnoutrefresh(view->win);
2614 view->has_scrolled = TRUE;
2615 report("");
2618 /* Scroll frontend */
2619 static void
2620 scroll_view(struct view *view, enum request request)
2622 int lines = 1;
2624 assert(view_is_displayed(view));
2626 switch (request) {
2627 case REQ_SCROLL_LEFT:
2628 if (view->yoffset == 0) {
2629 report("Cannot scroll beyond the first column");
2630 return;
2632 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2633 view->yoffset = 0;
2634 else
2635 view->yoffset -= apply_step(opt_hscroll, view->width);
2636 redraw_view_from(view, 0);
2637 report("");
2638 return;
2639 case REQ_SCROLL_RIGHT:
2640 view->yoffset += apply_step(opt_hscroll, view->width);
2641 redraw_view(view);
2642 report("");
2643 return;
2644 case REQ_SCROLL_PAGE_DOWN:
2645 lines = view->height;
2646 case REQ_SCROLL_LINE_DOWN:
2647 if (view->offset + lines > view->lines)
2648 lines = view->lines - view->offset;
2650 if (lines == 0 || view->offset + view->height >= view->lines) {
2651 report("Cannot scroll beyond the last line");
2652 return;
2654 break;
2656 case REQ_SCROLL_PAGE_UP:
2657 lines = view->height;
2658 case REQ_SCROLL_LINE_UP:
2659 if (lines > view->offset)
2660 lines = view->offset;
2662 if (lines == 0) {
2663 report("Cannot scroll beyond the first line");
2664 return;
2667 lines = -lines;
2668 break;
2670 default:
2671 die("request %d not handled in switch", request);
2674 do_scroll_view(view, lines);
2677 /* Cursor moving */
2678 static void
2679 move_view(struct view *view, enum request request)
2681 int scroll_steps = 0;
2682 int steps;
2684 switch (request) {
2685 case REQ_MOVE_FIRST_LINE:
2686 steps = -view->lineno;
2687 break;
2689 case REQ_MOVE_LAST_LINE:
2690 steps = view->lines - view->lineno - 1;
2691 break;
2693 case REQ_MOVE_PAGE_UP:
2694 steps = view->height > view->lineno
2695 ? -view->lineno : -view->height;
2696 break;
2698 case REQ_MOVE_PAGE_DOWN:
2699 steps = view->lineno + view->height >= view->lines
2700 ? view->lines - view->lineno - 1 : view->height;
2701 break;
2703 case REQ_MOVE_UP:
2704 steps = -1;
2705 break;
2707 case REQ_MOVE_DOWN:
2708 steps = 1;
2709 break;
2711 default:
2712 die("request %d not handled in switch", request);
2715 if (steps <= 0 && view->lineno == 0) {
2716 report("Cannot move beyond the first line");
2717 return;
2719 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2720 report("Cannot move beyond the last line");
2721 return;
2724 /* Move the current line */
2725 view->lineno += steps;
2726 assert(0 <= view->lineno && view->lineno < view->lines);
2728 /* Check whether the view needs to be scrolled */
2729 if (view->lineno < view->offset ||
2730 view->lineno >= view->offset + view->height) {
2731 scroll_steps = steps;
2732 if (steps < 0 && -steps > view->offset) {
2733 scroll_steps = -view->offset;
2735 } else if (steps > 0) {
2736 if (view->lineno == view->lines - 1 &&
2737 view->lines > view->height) {
2738 scroll_steps = view->lines - view->offset - 1;
2739 if (scroll_steps >= view->height)
2740 scroll_steps -= view->height - 1;
2745 if (!view_is_displayed(view)) {
2746 view->offset += scroll_steps;
2747 assert(0 <= view->offset && view->offset < view->lines);
2748 view->ops->select(view, &view->line[view->lineno]);
2749 return;
2752 /* Repaint the old "current" line if we be scrolling */
2753 if (ABS(steps) < view->height)
2754 draw_view_line(view, view->lineno - steps - view->offset);
2756 if (scroll_steps) {
2757 do_scroll_view(view, scroll_steps);
2758 return;
2761 /* Draw the current line */
2762 draw_view_line(view, view->lineno - view->offset);
2764 wnoutrefresh(view->win);
2765 report("");
2770 * Searching
2773 static void search_view(struct view *view, enum request request);
2775 static bool
2776 grep_text(struct view *view, const char *text[])
2778 regmatch_t pmatch;
2779 size_t i;
2781 for (i = 0; text[i]; i++)
2782 if (*text[i] &&
2783 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2784 return TRUE;
2785 return FALSE;
2788 static void
2789 select_view_line(struct view *view, unsigned long lineno)
2791 unsigned long old_lineno = view->lineno;
2792 unsigned long old_offset = view->offset;
2794 if (goto_view_line(view, view->offset, lineno)) {
2795 if (view_is_displayed(view)) {
2796 if (old_offset != view->offset) {
2797 redraw_view(view);
2798 } else {
2799 draw_view_line(view, old_lineno - view->offset);
2800 draw_view_line(view, view->lineno - view->offset);
2801 wnoutrefresh(view->win);
2803 } else {
2804 view->ops->select(view, &view->line[view->lineno]);
2809 static void
2810 find_next(struct view *view, enum request request)
2812 unsigned long lineno = view->lineno;
2813 int direction;
2815 if (!*view->grep) {
2816 if (!*opt_search)
2817 report("No previous search");
2818 else
2819 search_view(view, request);
2820 return;
2823 switch (request) {
2824 case REQ_SEARCH:
2825 case REQ_FIND_NEXT:
2826 direction = 1;
2827 break;
2829 case REQ_SEARCH_BACK:
2830 case REQ_FIND_PREV:
2831 direction = -1;
2832 break;
2834 default:
2835 return;
2838 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2839 lineno += direction;
2841 /* Note, lineno is unsigned long so will wrap around in which case it
2842 * will become bigger than view->lines. */
2843 for (; lineno < view->lines; lineno += direction) {
2844 if (view->ops->grep(view, &view->line[lineno])) {
2845 select_view_line(view, lineno);
2846 report("Line %ld matches '%s'", lineno + 1, view->grep);
2847 return;
2851 report("No match found for '%s'", view->grep);
2854 static void
2855 search_view(struct view *view, enum request request)
2857 int regex_err;
2859 if (view->regex) {
2860 regfree(view->regex);
2861 *view->grep = 0;
2862 } else {
2863 view->regex = calloc(1, sizeof(*view->regex));
2864 if (!view->regex)
2865 return;
2868 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2869 if (regex_err != 0) {
2870 char buf[SIZEOF_STR] = "unknown error";
2872 regerror(regex_err, view->regex, buf, sizeof(buf));
2873 report("Search failed: %s", buf);
2874 return;
2877 string_copy(view->grep, opt_search);
2879 find_next(view, request);
2883 * Incremental updating
2886 static void
2887 reset_view(struct view *view)
2889 int i;
2891 for (i = 0; i < view->lines; i++)
2892 free(view->line[i].data);
2893 free(view->line);
2895 view->p_offset = view->offset;
2896 view->p_yoffset = view->yoffset;
2897 view->p_lineno = view->lineno;
2899 view->line = NULL;
2900 view->offset = 0;
2901 view->yoffset = 0;
2902 view->lines = 0;
2903 view->lineno = 0;
2904 view->vid[0] = 0;
2905 view->update_secs = 0;
2908 static void
2909 free_argv(const char *argv[])
2911 int argc;
2913 for (argc = 0; argv[argc]; argc++)
2914 free((void *) argv[argc]);
2917 static const char *
2918 format_arg(const char *name)
2920 static struct {
2921 const char *name;
2922 size_t namelen;
2923 const char *value;
2924 const char *value_if_empty;
2925 } vars[] = {
2926 #define FORMAT_VAR(name, value, value_if_empty) \
2927 { name, STRING_SIZE(name), value, value_if_empty }
2928 FORMAT_VAR("%(directory)", opt_path, ""),
2929 FORMAT_VAR("%(file)", opt_file, ""),
2930 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2931 FORMAT_VAR("%(head)", ref_head, ""),
2932 FORMAT_VAR("%(commit)", ref_commit, ""),
2933 FORMAT_VAR("%(blob)", ref_blob, ""),
2935 int i;
2937 for (i = 0; i < ARRAY_SIZE(vars); i++)
2938 if (!strncmp(name, vars[i].name, vars[i].namelen))
2939 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2941 return NULL;
2943 static bool
2944 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2946 char buf[SIZEOF_STR];
2947 int argc;
2948 bool noreplace = flags == FORMAT_NONE;
2950 free_argv(dst_argv);
2952 for (argc = 0; src_argv[argc]; argc++) {
2953 const char *arg = src_argv[argc];
2954 size_t bufpos = 0;
2956 while (arg) {
2957 char *next = strstr(arg, "%(");
2958 int len = next - arg;
2959 const char *value;
2961 if (!next || noreplace) {
2962 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2963 noreplace = TRUE;
2964 len = strlen(arg);
2965 value = "";
2967 } else {
2968 value = format_arg(next);
2970 if (!value) {
2971 report("Unknown replacement: `%s`", next);
2972 return FALSE;
2976 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2977 return FALSE;
2979 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2982 dst_argv[argc] = strdup(buf);
2983 if (!dst_argv[argc])
2984 break;
2987 dst_argv[argc] = NULL;
2989 return src_argv[argc] == NULL;
2992 static bool
2993 restore_view_position(struct view *view)
2995 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2996 return FALSE;
2998 /* Changing the view position cancels the restoring. */
2999 /* FIXME: Changing back to the first line is not detected. */
3000 if (view->offset != 0 || view->lineno != 0) {
3001 view->p_restore = FALSE;
3002 return FALSE;
3005 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3006 view_is_displayed(view))
3007 werase(view->win);
3009 view->yoffset = view->p_yoffset;
3010 view->p_restore = FALSE;
3012 return TRUE;
3015 static void
3016 end_update(struct view *view, bool force)
3018 if (!view->pipe)
3019 return;
3020 while (!view->ops->read(view, NULL))
3021 if (!force)
3022 return;
3023 set_nonblocking_input(FALSE);
3024 if (force)
3025 kill_io(view->pipe);
3026 done_io(view->pipe);
3027 view->pipe = NULL;
3030 static void
3031 setup_update(struct view *view, const char *vid)
3033 set_nonblocking_input(TRUE);
3034 reset_view(view);
3035 string_copy_rev(view->vid, vid);
3036 view->pipe = &view->io;
3037 view->start_time = time(NULL);
3040 static bool
3041 prepare_update(struct view *view, const char *argv[], const char *dir,
3042 enum format_flags flags)
3044 if (view->pipe)
3045 end_update(view, TRUE);
3046 return init_io_rd(&view->io, argv, dir, flags);
3049 static bool
3050 prepare_update_file(struct view *view, const char *name)
3052 if (view->pipe)
3053 end_update(view, TRUE);
3054 return io_open(&view->io, "%s", name);
3057 static bool
3058 begin_update(struct view *view, bool refresh)
3060 if (view->pipe)
3061 end_update(view, TRUE);
3063 if (!refresh) {
3064 if (view->ops->prepare) {
3065 if (!view->ops->prepare(view))
3066 return FALSE;
3067 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3068 return FALSE;
3071 /* Put the current ref_* value to the view title ref
3072 * member. This is needed by the blob view. Most other
3073 * views sets it automatically after loading because the
3074 * first line is a commit line. */
3075 string_copy_rev(view->ref, view->id);
3078 if (!start_io(&view->io))
3079 return FALSE;
3081 setup_update(view, view->id);
3083 return TRUE;
3086 static bool
3087 update_view(struct view *view)
3089 char out_buffer[BUFSIZ * 2];
3090 char *line;
3091 /* Clear the view and redraw everything since the tree sorting
3092 * might have rearranged things. */
3093 bool redraw = view->lines == 0;
3094 bool can_read = TRUE;
3096 if (!view->pipe)
3097 return TRUE;
3099 if (!io_can_read(view->pipe)) {
3100 if (view->lines == 0 && view_is_displayed(view)) {
3101 time_t secs = time(NULL) - view->start_time;
3103 if (secs > 1 && secs > view->update_secs) {
3104 if (view->update_secs == 0)
3105 redraw_view(view);
3106 update_view_title(view);
3107 view->update_secs = secs;
3110 return TRUE;
3113 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3114 if (opt_iconv_in != ICONV_NONE) {
3115 ICONV_CONST char *inbuf = line;
3116 size_t inlen = strlen(line) + 1;
3118 char *outbuf = out_buffer;
3119 size_t outlen = sizeof(out_buffer);
3121 size_t ret;
3123 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3124 if (ret != (size_t) -1)
3125 line = out_buffer;
3128 if (!view->ops->read(view, line)) {
3129 report("Allocation failure");
3130 end_update(view, TRUE);
3131 return FALSE;
3136 unsigned long lines = view->lines;
3137 int digits;
3139 for (digits = 0; lines; digits++)
3140 lines /= 10;
3142 /* Keep the displayed view in sync with line number scaling. */
3143 if (digits != view->digits) {
3144 view->digits = digits;
3145 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3146 redraw = TRUE;
3150 if (io_error(view->pipe)) {
3151 report("Failed to read: %s", io_strerror(view->pipe));
3152 end_update(view, TRUE);
3154 } else if (io_eof(view->pipe)) {
3155 report("");
3156 end_update(view, FALSE);
3159 if (restore_view_position(view))
3160 redraw = TRUE;
3162 if (!view_is_displayed(view))
3163 return TRUE;
3165 if (redraw)
3166 redraw_view_from(view, 0);
3167 else
3168 redraw_view_dirty(view);
3170 /* Update the title _after_ the redraw so that if the redraw picks up a
3171 * commit reference in view->ref it'll be available here. */
3172 update_view_title(view);
3173 return TRUE;
3176 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3178 static struct line *
3179 add_line_data(struct view *view, void *data, enum line_type type)
3181 struct line *line;
3183 if (!realloc_lines(&view->line, view->lines, 1))
3184 return NULL;
3186 line = &view->line[view->lines++];
3187 memset(line, 0, sizeof(*line));
3188 line->type = type;
3189 line->data = data;
3190 line->dirty = 1;
3192 return line;
3195 static struct line *
3196 add_line_text(struct view *view, const char *text, enum line_type type)
3198 char *data = text ? strdup(text) : NULL;
3200 return data ? add_line_data(view, data, type) : NULL;
3203 static struct line *
3204 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3206 char buf[SIZEOF_STR];
3207 va_list args;
3209 va_start(args, fmt);
3210 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3211 buf[0] = 0;
3212 va_end(args);
3214 return buf[0] ? add_line_text(view, buf, type) : NULL;
3218 * View opening
3221 enum open_flags {
3222 OPEN_DEFAULT = 0, /* Use default view switching. */
3223 OPEN_SPLIT = 1, /* Split current view. */
3224 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3225 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3226 OPEN_PREPARED = 32, /* Open already prepared command. */
3229 static void
3230 open_view(struct view *prev, enum request request, enum open_flags flags)
3232 bool split = !!(flags & OPEN_SPLIT);
3233 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3234 bool nomaximize = !!(flags & OPEN_REFRESH);
3235 struct view *view = VIEW(request);
3236 int nviews = displayed_views();
3237 struct view *base_view = display[0];
3239 if (view == prev && nviews == 1 && !reload) {
3240 report("Already in %s view", view->name);
3241 return;
3244 if (view->git_dir && !opt_git_dir[0]) {
3245 report("The %s view is disabled in pager view", view->name);
3246 return;
3249 if (split) {
3250 display[1] = view;
3251 current_view = 1;
3252 } else if (!nomaximize) {
3253 /* Maximize the current view. */
3254 memset(display, 0, sizeof(display));
3255 current_view = 0;
3256 display[current_view] = view;
3259 /* No parent signals that this is the first loaded view. */
3260 if (prev && view != prev) {
3261 view->parent = prev;
3264 /* Resize the view when switching between split- and full-screen,
3265 * or when switching between two different full-screen views. */
3266 if (nviews != displayed_views() ||
3267 (nviews == 1 && base_view != display[0]))
3268 resize_display();
3270 if (view->ops->open) {
3271 if (view->pipe)
3272 end_update(view, TRUE);
3273 if (!view->ops->open(view)) {
3274 report("Failed to load %s view", view->name);
3275 return;
3277 restore_view_position(view);
3279 } else if ((reload || strcmp(view->vid, view->id)) &&
3280 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3281 report("Failed to load %s view", view->name);
3282 return;
3285 if (split && prev->lineno - prev->offset >= prev->height) {
3286 /* Take the title line into account. */
3287 int lines = prev->lineno - prev->offset - prev->height + 1;
3289 /* Scroll the view that was split if the current line is
3290 * outside the new limited view. */
3291 do_scroll_view(prev, lines);
3294 if (prev && view != prev && split && view_is_displayed(prev)) {
3295 /* "Blur" the previous view. */
3296 update_view_title(prev);
3299 if (view->pipe && view->lines == 0) {
3300 /* Clear the old view and let the incremental updating refill
3301 * the screen. */
3302 werase(view->win);
3303 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3304 report("");
3305 } else if (view_is_displayed(view)) {
3306 redraw_view(view);
3307 report("");
3311 static void
3312 open_external_viewer(const char *argv[], const char *dir)
3314 def_prog_mode(); /* save current tty modes */
3315 endwin(); /* restore original tty modes */
3316 run_io_fg(argv, dir);
3317 fprintf(stderr, "Press Enter to continue");
3318 getc(opt_tty);
3319 reset_prog_mode();
3320 redraw_display(TRUE);
3323 static void
3324 open_mergetool(const char *file)
3326 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3328 open_external_viewer(mergetool_argv, opt_cdup);
3331 static void
3332 open_editor(bool from_root, const char *file)
3334 const char *editor_argv[] = { "vi", file, NULL };
3335 const char *editor;
3337 editor = getenv("GIT_EDITOR");
3338 if (!editor && *opt_editor)
3339 editor = opt_editor;
3340 if (!editor)
3341 editor = getenv("VISUAL");
3342 if (!editor)
3343 editor = getenv("EDITOR");
3344 if (!editor)
3345 editor = "vi";
3347 editor_argv[0] = editor;
3348 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3351 static void
3352 open_run_request(enum request request)
3354 struct run_request *req = get_run_request(request);
3355 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3357 if (!req) {
3358 report("Unknown run request");
3359 return;
3362 if (format_argv(argv, req->argv, FORMAT_ALL))
3363 open_external_viewer(argv, NULL);
3364 free_argv(argv);
3368 * User request switch noodle
3371 static int
3372 view_driver(struct view *view, enum request request)
3374 int i;
3376 if (request == REQ_NONE)
3377 return TRUE;
3379 if (request > REQ_NONE) {
3380 open_run_request(request);
3381 /* FIXME: When all views can refresh always do this. */
3382 if (view == VIEW(REQ_VIEW_STATUS) ||
3383 view == VIEW(REQ_VIEW_MAIN) ||
3384 view == VIEW(REQ_VIEW_LOG) ||
3385 view == VIEW(REQ_VIEW_BRANCH) ||
3386 view == VIEW(REQ_VIEW_STAGE))
3387 request = REQ_REFRESH;
3388 else
3389 return TRUE;
3392 if (view && view->lines) {
3393 request = view->ops->request(view, request, &view->line[view->lineno]);
3394 if (request == REQ_NONE)
3395 return TRUE;
3398 switch (request) {
3399 case REQ_MOVE_UP:
3400 case REQ_MOVE_DOWN:
3401 case REQ_MOVE_PAGE_UP:
3402 case REQ_MOVE_PAGE_DOWN:
3403 case REQ_MOVE_FIRST_LINE:
3404 case REQ_MOVE_LAST_LINE:
3405 move_view(view, request);
3406 break;
3408 case REQ_SCROLL_LEFT:
3409 case REQ_SCROLL_RIGHT:
3410 case REQ_SCROLL_LINE_DOWN:
3411 case REQ_SCROLL_LINE_UP:
3412 case REQ_SCROLL_PAGE_DOWN:
3413 case REQ_SCROLL_PAGE_UP:
3414 scroll_view(view, request);
3415 break;
3417 case REQ_VIEW_BLAME:
3418 if (!opt_file[0]) {
3419 report("No file chosen, press %s to open tree view",
3420 get_key(view->keymap, REQ_VIEW_TREE));
3421 break;
3423 open_view(view, request, OPEN_DEFAULT);
3424 break;
3426 case REQ_VIEW_BLOB:
3427 if (!ref_blob[0]) {
3428 report("No file chosen, press %s to open tree view",
3429 get_key(view->keymap, REQ_VIEW_TREE));
3430 break;
3432 open_view(view, request, OPEN_DEFAULT);
3433 break;
3435 case REQ_VIEW_PAGER:
3436 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3437 report("No pager content, press %s to run command from prompt",
3438 get_key(view->keymap, REQ_PROMPT));
3439 break;
3441 open_view(view, request, OPEN_DEFAULT);
3442 break;
3444 case REQ_VIEW_STAGE:
3445 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3446 report("No stage content, press %s to open the status view and choose file",
3447 get_key(view->keymap, REQ_VIEW_STATUS));
3448 break;
3450 open_view(view, request, OPEN_DEFAULT);
3451 break;
3453 case REQ_VIEW_STATUS:
3454 if (opt_is_inside_work_tree == FALSE) {
3455 report("The status view requires a working tree");
3456 break;
3458 open_view(view, request, OPEN_DEFAULT);
3459 break;
3461 case REQ_VIEW_MAIN:
3462 case REQ_VIEW_DIFF:
3463 case REQ_VIEW_LOG:
3464 case REQ_VIEW_TREE:
3465 case REQ_VIEW_HELP:
3466 case REQ_VIEW_BRANCH:
3467 open_view(view, request, OPEN_DEFAULT);
3468 break;
3470 case REQ_NEXT:
3471 case REQ_PREVIOUS:
3472 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3474 if ((view == VIEW(REQ_VIEW_DIFF) &&
3475 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3476 (view == VIEW(REQ_VIEW_DIFF) &&
3477 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3478 (view == VIEW(REQ_VIEW_STAGE) &&
3479 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3480 (view == VIEW(REQ_VIEW_BLOB) &&
3481 view->parent == VIEW(REQ_VIEW_TREE)) ||
3482 (view == VIEW(REQ_VIEW_MAIN) &&
3483 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3484 int line;
3486 view = view->parent;
3487 line = view->lineno;
3488 move_view(view, request);
3489 if (view_is_displayed(view))
3490 update_view_title(view);
3491 if (line != view->lineno)
3492 view->ops->request(view, REQ_ENTER,
3493 &view->line[view->lineno]);
3495 } else {
3496 move_view(view, request);
3498 break;
3500 case REQ_VIEW_NEXT:
3502 int nviews = displayed_views();
3503 int next_view = (current_view + 1) % nviews;
3505 if (next_view == current_view) {
3506 report("Only one view is displayed");
3507 break;
3510 current_view = next_view;
3511 /* Blur out the title of the previous view. */
3512 update_view_title(view);
3513 report("");
3514 break;
3516 case REQ_REFRESH:
3517 report("Refreshing is not yet supported for the %s view", view->name);
3518 break;
3520 case REQ_MAXIMIZE:
3521 if (displayed_views() == 2)
3522 maximize_view(view);
3523 break;
3525 case REQ_OPTIONS:
3526 open_option_menu();
3527 break;
3529 case REQ_TOGGLE_LINENO:
3530 toggle_view_option(&opt_line_number, "line numbers");
3531 break;
3533 case REQ_TOGGLE_DATE:
3534 toggle_date();
3535 break;
3537 case REQ_TOGGLE_AUTHOR:
3538 toggle_view_option(&opt_author, "author display");
3539 break;
3541 case REQ_TOGGLE_REV_GRAPH:
3542 toggle_view_option(&opt_rev_graph, "revision graph display");
3543 break;
3545 case REQ_TOGGLE_REFS:
3546 toggle_view_option(&opt_show_refs, "reference display");
3547 break;
3549 case REQ_TOGGLE_SORT_FIELD:
3550 case REQ_TOGGLE_SORT_ORDER:
3551 report("Sorting is not yet supported for the %s view", view->name);
3552 break;
3554 case REQ_SEARCH:
3555 case REQ_SEARCH_BACK:
3556 search_view(view, request);
3557 break;
3559 case REQ_FIND_NEXT:
3560 case REQ_FIND_PREV:
3561 find_next(view, request);
3562 break;
3564 case REQ_STOP_LOADING:
3565 for (i = 0; i < ARRAY_SIZE(views); i++) {
3566 view = &views[i];
3567 if (view->pipe)
3568 report("Stopped loading the %s view", view->name),
3569 end_update(view, TRUE);
3571 break;
3573 case REQ_SHOW_VERSION:
3574 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3575 return TRUE;
3577 case REQ_SCREEN_REDRAW:
3578 redraw_display(TRUE);
3579 break;
3581 case REQ_EDIT:
3582 report("Nothing to edit");
3583 break;
3585 case REQ_ENTER:
3586 report("Nothing to enter");
3587 break;
3589 case REQ_VIEW_CLOSE:
3590 /* XXX: Mark closed views by letting view->parent point to the
3591 * view itself. Parents to closed view should never be
3592 * followed. */
3593 if (view->parent &&
3594 view->parent->parent != view->parent) {
3595 maximize_view(view->parent);
3596 view->parent = view;
3597 break;
3599 /* Fall-through */
3600 case REQ_QUIT:
3601 return FALSE;
3603 default:
3604 report("Unknown key, press %s for help",
3605 get_key(view->keymap, REQ_VIEW_HELP));
3606 return TRUE;
3609 return TRUE;
3614 * View backend utilities
3617 enum sort_field {
3618 ORDERBY_NAME,
3619 ORDERBY_DATE,
3620 ORDERBY_AUTHOR,
3623 struct sort_state {
3624 const enum sort_field *fields;
3625 size_t size, current;
3626 bool reverse;
3629 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3630 #define get_sort_field(state) ((state).fields[(state).current])
3631 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3633 static void
3634 sort_view(struct view *view, enum request request, struct sort_state *state,
3635 int (*compare)(const void *, const void *))
3637 switch (request) {
3638 case REQ_TOGGLE_SORT_FIELD:
3639 state->current = (state->current + 1) % state->size;
3640 break;
3642 case REQ_TOGGLE_SORT_ORDER:
3643 state->reverse = !state->reverse;
3644 break;
3645 default:
3646 die("Not a sort request");
3649 qsort(view->line, view->lines, sizeof(*view->line), compare);
3650 redraw_view(view);
3653 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3655 /* Small author cache to reduce memory consumption. It uses binary
3656 * search to lookup or find place to position new entries. No entries
3657 * are ever freed. */
3658 static const char *
3659 get_author(const char *name)
3661 static const char **authors;
3662 static size_t authors_size;
3663 int from = 0, to = authors_size - 1;
3665 while (from <= to) {
3666 size_t pos = (to + from) / 2;
3667 int cmp = strcmp(name, authors[pos]);
3669 if (!cmp)
3670 return authors[pos];
3672 if (cmp < 0)
3673 to = pos - 1;
3674 else
3675 from = pos + 1;
3678 if (!realloc_authors(&authors, authors_size, 1))
3679 return NULL;
3680 name = strdup(name);
3681 if (!name)
3682 return NULL;
3684 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3685 authors[from] = name;
3686 authors_size++;
3688 return name;
3691 static void
3692 parse_timezone(time_t *time, const char *zone)
3694 long tz;
3696 tz = ('0' - zone[1]) * 60 * 60 * 10;
3697 tz += ('0' - zone[2]) * 60 * 60;
3698 tz += ('0' - zone[3]) * 60;
3699 tz += ('0' - zone[4]);
3701 if (zone[0] == '-')
3702 tz = -tz;
3704 *time -= tz;
3707 /* Parse author lines where the name may be empty:
3708 * author <email@address.tld> 1138474660 +0100
3710 static void
3711 parse_author_line(char *ident, const char **author, time_t *time)
3713 char *nameend = strchr(ident, '<');
3714 char *emailend = strchr(ident, '>');
3716 if (nameend && emailend)
3717 *nameend = *emailend = 0;
3718 ident = chomp_string(ident);
3719 if (!*ident) {
3720 if (nameend)
3721 ident = chomp_string(nameend + 1);
3722 if (!*ident)
3723 ident = "Unknown";
3726 *author = get_author(ident);
3728 /* Parse epoch and timezone */
3729 if (emailend && emailend[1] == ' ') {
3730 char *secs = emailend + 2;
3731 char *zone = strchr(secs, ' ');
3733 *time = (time_t) atol(secs);
3735 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3736 parse_timezone(time, zone + 1);
3740 static bool
3741 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3743 char rev[SIZEOF_REV];
3744 const char *revlist_argv[] = {
3745 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3747 struct menu_item *items;
3748 char text[SIZEOF_STR];
3749 bool ok = TRUE;
3750 int i;
3752 items = calloc(*parents + 1, sizeof(*items));
3753 if (!items)
3754 return FALSE;
3756 for (i = 0; i < *parents; i++) {
3757 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3758 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3759 !(items[i].text = strdup(text))) {
3760 ok = FALSE;
3761 break;
3765 if (ok) {
3766 *parents = 0;
3767 ok = prompt_menu("Select parent", items, parents);
3769 for (i = 0; items[i].text; i++)
3770 free((char *) items[i].text);
3771 free(items);
3772 return ok;
3775 static bool
3776 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3778 char buf[SIZEOF_STR * 4];
3779 const char *revlist_argv[] = {
3780 "git", "log", "--no-color", "-1",
3781 "--pretty=format:%P", id, "--", path, NULL
3783 int parents;
3785 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3786 (parents = strlen(buf) / 40) < 0) {
3787 report("Failed to get parent information");
3788 return FALSE;
3790 } else if (parents == 0) {
3791 if (path)
3792 report("Path '%s' does not exist in the parent", path);
3793 else
3794 report("The selected commit has no parents");
3795 return FALSE;
3798 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3799 return FALSE;
3801 string_copy_rev(rev, &buf[41 * parents]);
3802 return TRUE;
3806 * Pager backend
3809 static bool
3810 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3812 char text[SIZEOF_STR];
3814 if (opt_line_number && draw_lineno(view, lineno))
3815 return TRUE;
3817 string_expand(text, sizeof(text), line->data, opt_tab_size);
3818 draw_text(view, line->type, text, TRUE);
3819 return TRUE;
3822 static bool
3823 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3825 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3826 char ref[SIZEOF_STR];
3828 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3829 return TRUE;
3831 /* This is the only fatal call, since it can "corrupt" the buffer. */
3832 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3833 return FALSE;
3835 return TRUE;
3838 static void
3839 add_pager_refs(struct view *view, struct line *line)
3841 char buf[SIZEOF_STR];
3842 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3843 struct ref_list *list;
3844 size_t bufpos = 0, i;
3845 const char *sep = "Refs: ";
3846 bool is_tag = FALSE;
3848 assert(line->type == LINE_COMMIT);
3850 list = get_ref_list(commit_id);
3851 if (!list) {
3852 if (view == VIEW(REQ_VIEW_DIFF))
3853 goto try_add_describe_ref;
3854 return;
3857 for (i = 0; i < list->size; i++) {
3858 struct ref *ref = list->refs[i];
3859 const char *fmt = ref->tag ? "%s[%s]" :
3860 ref->remote ? "%s<%s>" : "%s%s";
3862 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3863 return;
3864 sep = ", ";
3865 if (ref->tag)
3866 is_tag = TRUE;
3869 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3870 try_add_describe_ref:
3871 /* Add <tag>-g<commit_id> "fake" reference. */
3872 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3873 return;
3876 if (bufpos == 0)
3877 return;
3879 add_line_text(view, buf, LINE_PP_REFS);
3882 static bool
3883 pager_read(struct view *view, char *data)
3885 struct line *line;
3887 if (!data)
3888 return TRUE;
3890 line = add_line_text(view, data, get_line_type(data));
3891 if (!line)
3892 return FALSE;
3894 if (line->type == LINE_COMMIT &&
3895 (view == VIEW(REQ_VIEW_DIFF) ||
3896 view == VIEW(REQ_VIEW_LOG)))
3897 add_pager_refs(view, line);
3899 return TRUE;
3902 static enum request
3903 pager_request(struct view *view, enum request request, struct line *line)
3905 int split = 0;
3907 if (request != REQ_ENTER)
3908 return request;
3910 if (line->type == LINE_COMMIT &&
3911 (view == VIEW(REQ_VIEW_LOG) ||
3912 view == VIEW(REQ_VIEW_PAGER))) {
3913 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3914 split = 1;
3917 /* Always scroll the view even if it was split. That way
3918 * you can use Enter to scroll through the log view and
3919 * split open each commit diff. */
3920 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3922 /* FIXME: A minor workaround. Scrolling the view will call report("")
3923 * but if we are scrolling a non-current view this won't properly
3924 * update the view title. */
3925 if (split)
3926 update_view_title(view);
3928 return REQ_NONE;
3931 static bool
3932 pager_grep(struct view *view, struct line *line)
3934 const char *text[] = { line->data, NULL };
3936 return grep_text(view, text);
3939 static void
3940 pager_select(struct view *view, struct line *line)
3942 if (line->type == LINE_COMMIT) {
3943 char *text = (char *)line->data + STRING_SIZE("commit ");
3945 if (view != VIEW(REQ_VIEW_PAGER))
3946 string_copy_rev(view->ref, text);
3947 string_copy_rev(ref_commit, text);
3951 static struct view_ops pager_ops = {
3952 "line",
3953 NULL,
3954 NULL,
3955 pager_read,
3956 pager_draw,
3957 pager_request,
3958 pager_grep,
3959 pager_select,
3962 static const char *log_argv[SIZEOF_ARG] = {
3963 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3966 static enum request
3967 log_request(struct view *view, enum request request, struct line *line)
3969 switch (request) {
3970 case REQ_REFRESH:
3971 load_refs();
3972 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3973 return REQ_NONE;
3974 default:
3975 return pager_request(view, request, line);
3979 static struct view_ops log_ops = {
3980 "line",
3981 log_argv,
3982 NULL,
3983 pager_read,
3984 pager_draw,
3985 log_request,
3986 pager_grep,
3987 pager_select,
3990 static const char *diff_argv[SIZEOF_ARG] = {
3991 "git", "show", "--pretty=fuller", "--no-color", "--root",
3992 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3995 static struct view_ops diff_ops = {
3996 "line",
3997 diff_argv,
3998 NULL,
3999 pager_read,
4000 pager_draw,
4001 pager_request,
4002 pager_grep,
4003 pager_select,
4007 * Help backend
4010 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4012 static bool
4013 help_open_keymap_title(struct view *view, enum keymap keymap)
4015 struct line *line;
4017 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4018 help_keymap_hidden[keymap] ? '+' : '-',
4019 enum_name(keymap_table[keymap]));
4020 if (line)
4021 line->other = keymap;
4023 return help_keymap_hidden[keymap];
4026 static void
4027 help_open_keymap(struct view *view, enum keymap keymap)
4029 const char *group = NULL;
4030 char buf[SIZEOF_STR];
4031 size_t bufpos;
4032 bool add_title = TRUE;
4033 int i;
4035 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4036 const char *key = NULL;
4038 if (req_info[i].request == REQ_NONE)
4039 continue;
4041 if (!req_info[i].request) {
4042 group = req_info[i].help;
4043 continue;
4046 key = get_keys(keymap, req_info[i].request, TRUE);
4047 if (!key || !*key)
4048 continue;
4050 if (add_title && help_open_keymap_title(view, keymap))
4051 return;
4052 add_title = false;
4054 if (group) {
4055 add_line_text(view, group, LINE_HELP_GROUP);
4056 group = NULL;
4059 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4060 enum_name(req_info[i]), req_info[i].help);
4063 group = "External commands:";
4065 for (i = 0; i < run_requests; i++) {
4066 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4067 const char *key;
4068 int argc;
4070 if (!req || req->keymap != keymap)
4071 continue;
4073 key = get_key_name(req->key);
4074 if (!*key)
4075 key = "(no key defined)";
4077 if (add_title && help_open_keymap_title(view, keymap))
4078 return;
4079 if (group) {
4080 add_line_text(view, group, LINE_HELP_GROUP);
4081 group = NULL;
4084 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4085 if (!string_format_from(buf, &bufpos, "%s%s",
4086 argc ? " " : "", req->argv[argc]))
4087 return;
4089 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4093 static bool
4094 help_open(struct view *view)
4096 enum keymap keymap;
4098 reset_view(view);
4099 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4100 add_line_text(view, "", LINE_DEFAULT);
4102 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4103 help_open_keymap(view, keymap);
4105 return TRUE;
4108 static enum request
4109 help_request(struct view *view, enum request request, struct line *line)
4111 switch (request) {
4112 case REQ_ENTER:
4113 if (line->type == LINE_HELP_KEYMAP) {
4114 help_keymap_hidden[line->other] =
4115 !help_keymap_hidden[line->other];
4116 view->p_restore = TRUE;
4117 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4120 return REQ_NONE;
4121 default:
4122 return pager_request(view, request, line);
4126 static struct view_ops help_ops = {
4127 "line",
4128 NULL,
4129 help_open,
4130 NULL,
4131 pager_draw,
4132 help_request,
4133 pager_grep,
4134 pager_select,
4139 * Tree backend
4142 struct tree_stack_entry {
4143 struct tree_stack_entry *prev; /* Entry below this in the stack */
4144 unsigned long lineno; /* Line number to restore */
4145 char *name; /* Position of name in opt_path */
4148 /* The top of the path stack. */
4149 static struct tree_stack_entry *tree_stack = NULL;
4150 unsigned long tree_lineno = 0;
4152 static void
4153 pop_tree_stack_entry(void)
4155 struct tree_stack_entry *entry = tree_stack;
4157 tree_lineno = entry->lineno;
4158 entry->name[0] = 0;
4159 tree_stack = entry->prev;
4160 free(entry);
4163 static void
4164 push_tree_stack_entry(const char *name, unsigned long lineno)
4166 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4167 size_t pathlen = strlen(opt_path);
4169 if (!entry)
4170 return;
4172 entry->prev = tree_stack;
4173 entry->name = opt_path + pathlen;
4174 tree_stack = entry;
4176 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4177 pop_tree_stack_entry();
4178 return;
4181 /* Move the current line to the first tree entry. */
4182 tree_lineno = 1;
4183 entry->lineno = lineno;
4186 /* Parse output from git-ls-tree(1):
4188 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4191 #define SIZEOF_TREE_ATTR \
4192 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4194 #define SIZEOF_TREE_MODE \
4195 STRING_SIZE("100644 ")
4197 #define TREE_ID_OFFSET \
4198 STRING_SIZE("100644 blob ")
4200 struct tree_entry {
4201 char id[SIZEOF_REV];
4202 mode_t mode;
4203 time_t time; /* Date from the author ident. */
4204 const char *author; /* Author of the commit. */
4205 char name[1];
4208 static const char *
4209 tree_path(const struct line *line)
4211 return ((struct tree_entry *) line->data)->name;
4214 static int
4215 tree_compare_entry(const struct line *line1, const struct line *line2)
4217 if (line1->type != line2->type)
4218 return line1->type == LINE_TREE_DIR ? -1 : 1;
4219 return strcmp(tree_path(line1), tree_path(line2));
4222 static const enum sort_field tree_sort_fields[] = {
4223 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4225 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4227 static int
4228 tree_compare(const void *l1, const void *l2)
4230 const struct line *line1 = (const struct line *) l1;
4231 const struct line *line2 = (const struct line *) l2;
4232 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4233 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4235 if (line1->type == LINE_TREE_HEAD)
4236 return -1;
4237 if (line2->type == LINE_TREE_HEAD)
4238 return 1;
4240 switch (get_sort_field(tree_sort_state)) {
4241 case ORDERBY_DATE:
4242 return sort_order(tree_sort_state, entry1->time - entry2->time);
4244 case ORDERBY_AUTHOR:
4245 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4247 case ORDERBY_NAME:
4248 default:
4249 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4254 static struct line *
4255 tree_entry(struct view *view, enum line_type type, const char *path,
4256 const char *mode, const char *id)
4258 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4259 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4261 if (!entry || !line) {
4262 free(entry);
4263 return NULL;
4266 strncpy(entry->name, path, strlen(path));
4267 if (mode)
4268 entry->mode = strtoul(mode, NULL, 8);
4269 if (id)
4270 string_copy_rev(entry->id, id);
4272 return line;
4275 static bool
4276 tree_read_date(struct view *view, char *text, bool *read_date)
4278 static const char *author_name;
4279 static time_t author_time;
4281 if (!text && *read_date) {
4282 *read_date = FALSE;
4283 return TRUE;
4285 } else if (!text) {
4286 char *path = *opt_path ? opt_path : ".";
4287 /* Find next entry to process */
4288 const char *log_file[] = {
4289 "git", "log", "--no-color", "--pretty=raw",
4290 "--cc", "--raw", view->id, "--", path, NULL
4292 struct io io = {};
4294 if (!view->lines) {
4295 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4296 report("Tree is empty");
4297 return TRUE;
4300 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4301 report("Failed to load tree data");
4302 return TRUE;
4305 done_io(view->pipe);
4306 view->io = io;
4307 *read_date = TRUE;
4308 return FALSE;
4310 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4311 parse_author_line(text + STRING_SIZE("author "),
4312 &author_name, &author_time);
4314 } else if (*text == ':') {
4315 char *pos;
4316 size_t annotated = 1;
4317 size_t i;
4319 pos = strchr(text, '\t');
4320 if (!pos)
4321 return TRUE;
4322 text = pos + 1;
4323 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4324 text += strlen(opt_path);
4325 pos = strchr(text, '/');
4326 if (pos)
4327 *pos = 0;
4329 for (i = 1; i < view->lines; i++) {
4330 struct line *line = &view->line[i];
4331 struct tree_entry *entry = line->data;
4333 annotated += !!entry->author;
4334 if (entry->author || strcmp(entry->name, text))
4335 continue;
4337 entry->author = author_name;
4338 entry->time = author_time;
4339 line->dirty = 1;
4340 break;
4343 if (annotated == view->lines)
4344 kill_io(view->pipe);
4346 return TRUE;
4349 static bool
4350 tree_read(struct view *view, char *text)
4352 static bool read_date = FALSE;
4353 struct tree_entry *data;
4354 struct line *entry, *line;
4355 enum line_type type;
4356 size_t textlen = text ? strlen(text) : 0;
4357 char *path = text + SIZEOF_TREE_ATTR;
4359 if (read_date || !text)
4360 return tree_read_date(view, text, &read_date);
4362 if (textlen <= SIZEOF_TREE_ATTR)
4363 return FALSE;
4364 if (view->lines == 0 &&
4365 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4366 return FALSE;
4368 /* Strip the path part ... */
4369 if (*opt_path) {
4370 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4371 size_t striplen = strlen(opt_path);
4373 if (pathlen > striplen)
4374 memmove(path, path + striplen,
4375 pathlen - striplen + 1);
4377 /* Insert "link" to parent directory. */
4378 if (view->lines == 1 &&
4379 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4380 return FALSE;
4383 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4384 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4385 if (!entry)
4386 return FALSE;
4387 data = entry->data;
4389 /* Skip "Directory ..." and ".." line. */
4390 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4391 if (tree_compare_entry(line, entry) <= 0)
4392 continue;
4394 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4396 line->data = data;
4397 line->type = type;
4398 for (; line <= entry; line++)
4399 line->dirty = line->cleareol = 1;
4400 return TRUE;
4403 if (tree_lineno > view->lineno) {
4404 view->lineno = tree_lineno;
4405 tree_lineno = 0;
4408 return TRUE;
4411 static bool
4412 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4414 struct tree_entry *entry = line->data;
4416 if (line->type == LINE_TREE_HEAD) {
4417 if (draw_text(view, line->type, "Directory path /", TRUE))
4418 return TRUE;
4419 } else {
4420 if (draw_mode(view, entry->mode))
4421 return TRUE;
4423 if (opt_author && draw_author(view, entry->author))
4424 return TRUE;
4426 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4427 return TRUE;
4429 if (draw_text(view, line->type, entry->name, TRUE))
4430 return TRUE;
4431 return TRUE;
4434 static void
4435 open_blob_editor()
4437 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4438 int fd = mkstemp(file);
4440 if (fd == -1)
4441 report("Failed to create temporary file");
4442 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4443 report("Failed to save blob data to file");
4444 else
4445 open_editor(FALSE, file);
4446 if (fd != -1)
4447 unlink(file);
4450 static enum request
4451 tree_request(struct view *view, enum request request, struct line *line)
4453 enum open_flags flags;
4455 switch (request) {
4456 case REQ_VIEW_BLAME:
4457 if (line->type != LINE_TREE_FILE) {
4458 report("Blame only supported for files");
4459 return REQ_NONE;
4462 string_copy(opt_ref, view->vid);
4463 return request;
4465 case REQ_EDIT:
4466 if (line->type != LINE_TREE_FILE) {
4467 report("Edit only supported for files");
4468 } else if (!is_head_commit(view->vid)) {
4469 open_blob_editor();
4470 } else {
4471 open_editor(TRUE, opt_file);
4473 return REQ_NONE;
4475 case REQ_TOGGLE_SORT_FIELD:
4476 case REQ_TOGGLE_SORT_ORDER:
4477 sort_view(view, request, &tree_sort_state, tree_compare);
4478 return REQ_NONE;
4480 case REQ_PARENT:
4481 if (!*opt_path) {
4482 /* quit view if at top of tree */
4483 return REQ_VIEW_CLOSE;
4485 /* fake 'cd ..' */
4486 line = &view->line[1];
4487 break;
4489 case REQ_ENTER:
4490 break;
4492 default:
4493 return request;
4496 /* Cleanup the stack if the tree view is at a different tree. */
4497 while (!*opt_path && tree_stack)
4498 pop_tree_stack_entry();
4500 switch (line->type) {
4501 case LINE_TREE_DIR:
4502 /* Depending on whether it is a subdirectory or parent link
4503 * mangle the path buffer. */
4504 if (line == &view->line[1] && *opt_path) {
4505 pop_tree_stack_entry();
4507 } else {
4508 const char *basename = tree_path(line);
4510 push_tree_stack_entry(basename, view->lineno);
4513 /* Trees and subtrees share the same ID, so they are not not
4514 * unique like blobs. */
4515 flags = OPEN_RELOAD;
4516 request = REQ_VIEW_TREE;
4517 break;
4519 case LINE_TREE_FILE:
4520 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4521 request = REQ_VIEW_BLOB;
4522 break;
4524 default:
4525 return REQ_NONE;
4528 open_view(view, request, flags);
4529 if (request == REQ_VIEW_TREE)
4530 view->lineno = tree_lineno;
4532 return REQ_NONE;
4535 static bool
4536 tree_grep(struct view *view, struct line *line)
4538 struct tree_entry *entry = line->data;
4539 const char *text[] = {
4540 entry->name,
4541 opt_author ? entry->author : "",
4542 opt_date ? mkdate(&entry->time) : "",
4543 NULL
4546 return grep_text(view, text);
4549 static void
4550 tree_select(struct view *view, struct line *line)
4552 struct tree_entry *entry = line->data;
4554 if (line->type == LINE_TREE_FILE) {
4555 string_copy_rev(ref_blob, entry->id);
4556 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4558 } else if (line->type != LINE_TREE_DIR) {
4559 return;
4562 string_copy_rev(view->ref, entry->id);
4565 static bool
4566 tree_prepare(struct view *view)
4568 if (view->lines == 0 && opt_prefix[0]) {
4569 char *pos = opt_prefix;
4571 while (pos && *pos) {
4572 char *end = strchr(pos, '/');
4574 if (end)
4575 *end = 0;
4576 push_tree_stack_entry(pos, 0);
4577 pos = end;
4578 if (end) {
4579 *end = '/';
4580 pos++;
4584 } else if (strcmp(view->vid, view->id)) {
4585 opt_path[0] = 0;
4588 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4591 static const char *tree_argv[SIZEOF_ARG] = {
4592 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4595 static struct view_ops tree_ops = {
4596 "file",
4597 tree_argv,
4598 NULL,
4599 tree_read,
4600 tree_draw,
4601 tree_request,
4602 tree_grep,
4603 tree_select,
4604 tree_prepare,
4607 static bool
4608 blob_read(struct view *view, char *line)
4610 if (!line)
4611 return TRUE;
4612 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4615 static enum request
4616 blob_request(struct view *view, enum request request, struct line *line)
4618 switch (request) {
4619 case REQ_EDIT:
4620 open_blob_editor();
4621 return REQ_NONE;
4622 default:
4623 return pager_request(view, request, line);
4627 static const char *blob_argv[SIZEOF_ARG] = {
4628 "git", "cat-file", "blob", "%(blob)", NULL
4631 static struct view_ops blob_ops = {
4632 "line",
4633 blob_argv,
4634 NULL,
4635 blob_read,
4636 pager_draw,
4637 blob_request,
4638 pager_grep,
4639 pager_select,
4643 * Blame backend
4645 * Loading the blame view is a two phase job:
4647 * 1. File content is read either using opt_file from the
4648 * filesystem or using git-cat-file.
4649 * 2. Then blame information is incrementally added by
4650 * reading output from git-blame.
4653 static const char *blame_head_argv[] = {
4654 "git", "blame", "--incremental", "--", "%(file)", NULL
4657 static const char *blame_ref_argv[] = {
4658 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4661 static const char *blame_cat_file_argv[] = {
4662 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4665 struct blame_commit {
4666 char id[SIZEOF_REV]; /* SHA1 ID. */
4667 char title[128]; /* First line of the commit message. */
4668 const char *author; /* Author of the commit. */
4669 time_t time; /* Date from the author ident. */
4670 char filename[128]; /* Name of file. */
4671 bool has_previous; /* Was a "previous" line detected. */
4674 struct blame {
4675 struct blame_commit *commit;
4676 unsigned long lineno;
4677 char text[1];
4680 static bool
4681 blame_open(struct view *view)
4683 char path[SIZEOF_STR];
4685 if (!view->parent && *opt_prefix) {
4686 string_copy(path, opt_file);
4687 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4688 return FALSE;
4691 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4692 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4693 return FALSE;
4696 setup_update(view, opt_file);
4697 string_format(view->ref, "%s ...", opt_file);
4699 return TRUE;
4702 static struct blame_commit *
4703 get_blame_commit(struct view *view, const char *id)
4705 size_t i;
4707 for (i = 0; i < view->lines; i++) {
4708 struct blame *blame = view->line[i].data;
4710 if (!blame->commit)
4711 continue;
4713 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4714 return blame->commit;
4718 struct blame_commit *commit = calloc(1, sizeof(*commit));
4720 if (commit)
4721 string_ncopy(commit->id, id, SIZEOF_REV);
4722 return commit;
4726 static bool
4727 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4729 const char *pos = *posref;
4731 *posref = NULL;
4732 pos = strchr(pos + 1, ' ');
4733 if (!pos || !isdigit(pos[1]))
4734 return FALSE;
4735 *number = atoi(pos + 1);
4736 if (*number < min || *number > max)
4737 return FALSE;
4739 *posref = pos;
4740 return TRUE;
4743 static struct blame_commit *
4744 parse_blame_commit(struct view *view, const char *text, int *blamed)
4746 struct blame_commit *commit;
4747 struct blame *blame;
4748 const char *pos = text + SIZEOF_REV - 2;
4749 size_t orig_lineno = 0;
4750 size_t lineno;
4751 size_t group;
4753 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4754 return NULL;
4756 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4757 !parse_number(&pos, &lineno, 1, view->lines) ||
4758 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4759 return NULL;
4761 commit = get_blame_commit(view, text);
4762 if (!commit)
4763 return NULL;
4765 *blamed += group;
4766 while (group--) {
4767 struct line *line = &view->line[lineno + group - 1];
4769 blame = line->data;
4770 blame->commit = commit;
4771 blame->lineno = orig_lineno + group - 1;
4772 line->dirty = 1;
4775 return commit;
4778 static bool
4779 blame_read_file(struct view *view, const char *line, bool *read_file)
4781 if (!line) {
4782 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4783 struct io io = {};
4785 if (view->lines == 0 && !view->parent)
4786 die("No blame exist for %s", view->vid);
4788 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4789 report("Failed to load blame data");
4790 return TRUE;
4793 done_io(view->pipe);
4794 view->io = io;
4795 *read_file = FALSE;
4796 return FALSE;
4798 } else {
4799 size_t linelen = strlen(line);
4800 struct blame *blame = malloc(sizeof(*blame) + linelen);
4802 if (!blame)
4803 return FALSE;
4805 blame->commit = NULL;
4806 strncpy(blame->text, line, linelen);
4807 blame->text[linelen] = 0;
4808 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4812 static bool
4813 match_blame_header(const char *name, char **line)
4815 size_t namelen = strlen(name);
4816 bool matched = !strncmp(name, *line, namelen);
4818 if (matched)
4819 *line += namelen;
4821 return matched;
4824 static bool
4825 blame_read(struct view *view, char *line)
4827 static struct blame_commit *commit = NULL;
4828 static int blamed = 0;
4829 static bool read_file = TRUE;
4831 if (read_file)
4832 return blame_read_file(view, line, &read_file);
4834 if (!line) {
4835 /* Reset all! */
4836 commit = NULL;
4837 blamed = 0;
4838 read_file = TRUE;
4839 string_format(view->ref, "%s", view->vid);
4840 if (view_is_displayed(view)) {
4841 update_view_title(view);
4842 redraw_view_from(view, 0);
4844 return TRUE;
4847 if (!commit) {
4848 commit = parse_blame_commit(view, line, &blamed);
4849 string_format(view->ref, "%s %2d%%", view->vid,
4850 view->lines ? blamed * 100 / view->lines : 0);
4852 } else if (match_blame_header("author ", &line)) {
4853 commit->author = get_author(line);
4855 } else if (match_blame_header("author-time ", &line)) {
4856 commit->time = (time_t) atol(line);
4858 } else if (match_blame_header("author-tz ", &line)) {
4859 parse_timezone(&commit->time, line);
4861 } else if (match_blame_header("summary ", &line)) {
4862 string_ncopy(commit->title, line, strlen(line));
4864 } else if (match_blame_header("previous ", &line)) {
4865 commit->has_previous = TRUE;
4867 } else if (match_blame_header("filename ", &line)) {
4868 string_ncopy(commit->filename, line, strlen(line));
4869 commit = NULL;
4872 return TRUE;
4875 static bool
4876 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4878 struct blame *blame = line->data;
4879 time_t *time = NULL;
4880 const char *id = NULL, *author = NULL;
4881 char text[SIZEOF_STR];
4883 if (blame->commit && *blame->commit->filename) {
4884 id = blame->commit->id;
4885 author = blame->commit->author;
4886 time = &blame->commit->time;
4889 if (opt_date && draw_date(view, time))
4890 return TRUE;
4892 if (opt_author && draw_author(view, author))
4893 return TRUE;
4895 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4896 return TRUE;
4898 if (draw_lineno(view, lineno))
4899 return TRUE;
4901 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4902 draw_text(view, LINE_DEFAULT, text, TRUE);
4903 return TRUE;
4906 static bool
4907 check_blame_commit(struct blame *blame, bool check_null_id)
4909 if (!blame->commit)
4910 report("Commit data not loaded yet");
4911 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4912 report("No commit exist for the selected line");
4913 else
4914 return TRUE;
4915 return FALSE;
4918 static void
4919 setup_blame_parent_line(struct view *view, struct blame *blame)
4921 const char *diff_tree_argv[] = {
4922 "git", "diff-tree", "-U0", blame->commit->id,
4923 "--", blame->commit->filename, NULL
4925 struct io io = {};
4926 int parent_lineno = -1;
4927 int blamed_lineno = -1;
4928 char *line;
4930 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4931 return;
4933 while ((line = io_get(&io, '\n', TRUE))) {
4934 if (*line == '@') {
4935 char *pos = strchr(line, '+');
4937 parent_lineno = atoi(line + 4);
4938 if (pos)
4939 blamed_lineno = atoi(pos + 1);
4941 } else if (*line == '+' && parent_lineno != -1) {
4942 if (blame->lineno == blamed_lineno - 1 &&
4943 !strcmp(blame->text, line + 1)) {
4944 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4945 break;
4947 blamed_lineno++;
4951 done_io(&io);
4954 static enum request
4955 blame_request(struct view *view, enum request request, struct line *line)
4957 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4958 struct blame *blame = line->data;
4960 switch (request) {
4961 case REQ_VIEW_BLAME:
4962 if (check_blame_commit(blame, TRUE)) {
4963 string_copy(opt_ref, blame->commit->id);
4964 string_copy(opt_file, blame->commit->filename);
4965 if (blame->lineno)
4966 view->lineno = blame->lineno;
4967 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4969 break;
4971 case REQ_PARENT:
4972 if (check_blame_commit(blame, TRUE) &&
4973 select_commit_parent(blame->commit->id, opt_ref,
4974 blame->commit->filename)) {
4975 string_copy(opt_file, blame->commit->filename);
4976 setup_blame_parent_line(view, blame);
4977 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4979 break;
4981 case REQ_ENTER:
4982 if (!check_blame_commit(blame, FALSE))
4983 break;
4985 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4986 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4987 break;
4989 if (!strcmp(blame->commit->id, NULL_ID)) {
4990 struct view *diff = VIEW(REQ_VIEW_DIFF);
4991 const char *diff_index_argv[] = {
4992 "git", "diff-index", "--root", "--patch-with-stat",
4993 "-C", "-M", "HEAD", "--", view->vid, NULL
4996 if (!blame->commit->has_previous) {
4997 diff_index_argv[1] = "diff";
4998 diff_index_argv[2] = "--no-color";
4999 diff_index_argv[6] = "--";
5000 diff_index_argv[7] = "/dev/null";
5003 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5004 report("Failed to allocate diff command");
5005 break;
5007 flags |= OPEN_PREPARED;
5010 open_view(view, REQ_VIEW_DIFF, flags);
5011 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5012 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5013 break;
5015 default:
5016 return request;
5019 return REQ_NONE;
5022 static bool
5023 blame_grep(struct view *view, struct line *line)
5025 struct blame *blame = line->data;
5026 struct blame_commit *commit = blame->commit;
5027 const char *text[] = {
5028 blame->text,
5029 commit ? commit->title : "",
5030 commit ? commit->id : "",
5031 commit && opt_author ? commit->author : "",
5032 commit && opt_date ? mkdate(&commit->time) : "",
5033 NULL
5036 return grep_text(view, text);
5039 static void
5040 blame_select(struct view *view, struct line *line)
5042 struct blame *blame = line->data;
5043 struct blame_commit *commit = blame->commit;
5045 if (!commit)
5046 return;
5048 if (!strcmp(commit->id, NULL_ID))
5049 string_ncopy(ref_commit, "HEAD", 4);
5050 else
5051 string_copy_rev(ref_commit, commit->id);
5054 static struct view_ops blame_ops = {
5055 "line",
5056 NULL,
5057 blame_open,
5058 blame_read,
5059 blame_draw,
5060 blame_request,
5061 blame_grep,
5062 blame_select,
5066 * Branch backend
5069 struct branch {
5070 const char *author; /* Author of the last commit. */
5071 time_t time; /* Date of the last activity. */
5072 const struct ref *ref; /* Name and commit ID information. */
5075 static const struct ref branch_all;
5077 static const enum sort_field branch_sort_fields[] = {
5078 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5080 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5082 static int
5083 branch_compare(const void *l1, const void *l2)
5085 const struct branch *branch1 = ((const struct line *) l1)->data;
5086 const struct branch *branch2 = ((const struct line *) l2)->data;
5088 switch (get_sort_field(branch_sort_state)) {
5089 case ORDERBY_DATE:
5090 return sort_order(branch_sort_state, branch1->time - branch2->time);
5092 case ORDERBY_AUTHOR:
5093 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5095 case ORDERBY_NAME:
5096 default:
5097 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5101 static bool
5102 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5104 struct branch *branch = line->data;
5105 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5107 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
5108 return TRUE;
5110 if (opt_author && draw_author(view, branch->author))
5111 return TRUE;
5113 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5114 return TRUE;
5117 static enum request
5118 branch_request(struct view *view, enum request request, struct line *line)
5120 struct branch *branch = line->data;
5122 switch (request) {
5123 case REQ_REFRESH:
5124 load_refs();
5125 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5126 return REQ_NONE;
5128 case REQ_TOGGLE_SORT_FIELD:
5129 case REQ_TOGGLE_SORT_ORDER:
5130 sort_view(view, request, &branch_sort_state, branch_compare);
5131 return REQ_NONE;
5133 case REQ_ENTER:
5134 if (branch->ref == &branch_all) {
5135 const char *all_branches_argv[] = {
5136 "git", "log", "--no-color", "--pretty=raw", "--parents",
5137 "--topo-order", "--all", NULL
5139 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5141 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5142 report("Failed to load view of all branches");
5143 return REQ_NONE;
5145 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5146 } else {
5147 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5149 return REQ_NONE;
5151 default:
5152 return request;
5156 static bool
5157 branch_read(struct view *view, char *line)
5159 static char id[SIZEOF_REV];
5160 struct branch *reference;
5161 size_t i;
5163 if (!line)
5164 return TRUE;
5166 switch (get_line_type(line)) {
5167 case LINE_COMMIT:
5168 string_copy_rev(id, line + STRING_SIZE("commit "));
5169 return TRUE;
5171 case LINE_AUTHOR:
5172 for (i = 0, reference = NULL; i < view->lines; i++) {
5173 struct branch *branch = view->line[i].data;
5175 if (strcmp(branch->ref->id, id))
5176 continue;
5178 view->line[i].dirty = TRUE;
5179 if (reference) {
5180 branch->author = reference->author;
5181 branch->time = reference->time;
5182 continue;
5185 parse_author_line(line + STRING_SIZE("author "),
5186 &branch->author, &branch->time);
5187 reference = branch;
5189 return TRUE;
5191 default:
5192 return TRUE;
5197 static bool
5198 branch_open_visitor(void *data, const struct ref *ref)
5200 struct view *view = data;
5201 struct branch *branch;
5203 if (ref->tag || ref->ltag || ref->remote)
5204 return TRUE;
5206 branch = calloc(1, sizeof(*branch));
5207 if (!branch)
5208 return FALSE;
5210 branch->ref = ref;
5211 return !!add_line_data(view, branch, LINE_DEFAULT);
5214 static bool
5215 branch_open(struct view *view)
5217 const char *branch_log[] = {
5218 "git", "log", "--no-color", "--pretty=raw",
5219 "--simplify-by-decoration", "--all", NULL
5222 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5223 report("Failed to load branch data");
5224 return TRUE;
5227 setup_update(view, view->id);
5228 branch_open_visitor(view, &branch_all);
5229 foreach_ref(branch_open_visitor, view);
5230 view->p_restore = TRUE;
5232 return TRUE;
5235 static bool
5236 branch_grep(struct view *view, struct line *line)
5238 struct branch *branch = line->data;
5239 const char *text[] = {
5240 branch->ref->name,
5241 branch->author,
5242 NULL
5245 return grep_text(view, text);
5248 static void
5249 branch_select(struct view *view, struct line *line)
5251 struct branch *branch = line->data;
5253 string_copy_rev(view->ref, branch->ref->id);
5254 string_copy_rev(ref_commit, branch->ref->id);
5255 string_copy_rev(ref_head, branch->ref->id);
5258 static struct view_ops branch_ops = {
5259 "branch",
5260 NULL,
5261 branch_open,
5262 branch_read,
5263 branch_draw,
5264 branch_request,
5265 branch_grep,
5266 branch_select,
5270 * Status backend
5273 struct status {
5274 char status;
5275 struct {
5276 mode_t mode;
5277 char rev[SIZEOF_REV];
5278 char name[SIZEOF_STR];
5279 } old;
5280 struct {
5281 mode_t mode;
5282 char rev[SIZEOF_REV];
5283 char name[SIZEOF_STR];
5284 } new;
5287 static char status_onbranch[SIZEOF_STR];
5288 static struct status stage_status;
5289 static enum line_type stage_line_type;
5290 static size_t stage_chunks;
5291 static int *stage_chunk;
5293 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5295 /* This should work even for the "On branch" line. */
5296 static inline bool
5297 status_has_none(struct view *view, struct line *line)
5299 return line < view->line + view->lines && !line[1].data;
5302 /* Get fields from the diff line:
5303 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5305 static inline bool
5306 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5308 const char *old_mode = buf + 1;
5309 const char *new_mode = buf + 8;
5310 const char *old_rev = buf + 15;
5311 const char *new_rev = buf + 56;
5312 const char *status = buf + 97;
5314 if (bufsize < 98 ||
5315 old_mode[-1] != ':' ||
5316 new_mode[-1] != ' ' ||
5317 old_rev[-1] != ' ' ||
5318 new_rev[-1] != ' ' ||
5319 status[-1] != ' ')
5320 return FALSE;
5322 file->status = *status;
5324 string_copy_rev(file->old.rev, old_rev);
5325 string_copy_rev(file->new.rev, new_rev);
5327 file->old.mode = strtoul(old_mode, NULL, 8);
5328 file->new.mode = strtoul(new_mode, NULL, 8);
5330 file->old.name[0] = file->new.name[0] = 0;
5332 return TRUE;
5335 static bool
5336 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5338 struct status *unmerged = NULL;
5339 char *buf;
5340 struct io io = {};
5342 if (!run_io(&io, argv, opt_cdup, IO_RD))
5343 return FALSE;
5345 add_line_data(view, NULL, type);
5347 while ((buf = io_get(&io, 0, TRUE))) {
5348 struct status *file = unmerged;
5350 if (!file) {
5351 file = calloc(1, sizeof(*file));
5352 if (!file || !add_line_data(view, file, type))
5353 goto error_out;
5356 /* Parse diff info part. */
5357 if (status) {
5358 file->status = status;
5359 if (status == 'A')
5360 string_copy(file->old.rev, NULL_ID);
5362 } else if (!file->status || file == unmerged) {
5363 if (!status_get_diff(file, buf, strlen(buf)))
5364 goto error_out;
5366 buf = io_get(&io, 0, TRUE);
5367 if (!buf)
5368 break;
5370 /* Collapse all modified entries that follow an
5371 * associated unmerged entry. */
5372 if (unmerged == file) {
5373 unmerged->status = 'U';
5374 unmerged = NULL;
5375 } else if (file->status == 'U') {
5376 unmerged = file;
5380 /* Grab the old name for rename/copy. */
5381 if (!*file->old.name &&
5382 (file->status == 'R' || file->status == 'C')) {
5383 string_ncopy(file->old.name, buf, strlen(buf));
5385 buf = io_get(&io, 0, TRUE);
5386 if (!buf)
5387 break;
5390 /* git-ls-files just delivers a NUL separated list of
5391 * file names similar to the second half of the
5392 * git-diff-* output. */
5393 string_ncopy(file->new.name, buf, strlen(buf));
5394 if (!*file->old.name)
5395 string_copy(file->old.name, file->new.name);
5396 file = NULL;
5399 if (io_error(&io)) {
5400 error_out:
5401 done_io(&io);
5402 return FALSE;
5405 if (!view->line[view->lines - 1].data)
5406 add_line_data(view, NULL, LINE_STAT_NONE);
5408 done_io(&io);
5409 return TRUE;
5412 /* Don't show unmerged entries in the staged section. */
5413 static const char *status_diff_index_argv[] = {
5414 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5415 "--cached", "-M", "HEAD", NULL
5418 static const char *status_diff_files_argv[] = {
5419 "git", "diff-files", "-z", NULL
5422 static const char *status_list_other_argv[] = {
5423 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5426 static const char *status_list_no_head_argv[] = {
5427 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5430 static const char *update_index_argv[] = {
5431 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5434 /* Restore the previous line number to stay in the context or select a
5435 * line with something that can be updated. */
5436 static void
5437 status_restore(struct view *view)
5439 if (view->p_lineno >= view->lines)
5440 view->p_lineno = view->lines - 1;
5441 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5442 view->p_lineno++;
5443 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5444 view->p_lineno--;
5446 /* If the above fails, always skip the "On branch" line. */
5447 if (view->p_lineno < view->lines)
5448 view->lineno = view->p_lineno;
5449 else
5450 view->lineno = 1;
5452 if (view->lineno < view->offset)
5453 view->offset = view->lineno;
5454 else if (view->offset + view->height <= view->lineno)
5455 view->offset = view->lineno - view->height + 1;
5457 view->p_restore = FALSE;
5460 static void
5461 status_update_onbranch(void)
5463 static const char *paths[][2] = {
5464 { "rebase-apply/rebasing", "Rebasing" },
5465 { "rebase-apply/applying", "Applying mailbox" },
5466 { "rebase-apply/", "Rebasing mailbox" },
5467 { "rebase-merge/interactive", "Interactive rebase" },
5468 { "rebase-merge/", "Rebase merge" },
5469 { "MERGE_HEAD", "Merging" },
5470 { "BISECT_LOG", "Bisecting" },
5471 { "HEAD", "On branch" },
5473 char buf[SIZEOF_STR];
5474 struct stat stat;
5475 int i;
5477 if (is_initial_commit()) {
5478 string_copy(status_onbranch, "Initial commit");
5479 return;
5482 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5483 char *head = opt_head;
5485 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5486 lstat(buf, &stat) < 0)
5487 continue;
5489 if (!*opt_head) {
5490 struct io io = {};
5492 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5493 io_read_buf(&io, buf, sizeof(buf))) {
5494 head = buf;
5495 if (!prefixcmp(head, "refs/heads/"))
5496 head += STRING_SIZE("refs/heads/");
5500 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5501 string_copy(status_onbranch, opt_head);
5502 return;
5505 string_copy(status_onbranch, "Not currently on any branch");
5508 /* First parse staged info using git-diff-index(1), then parse unstaged
5509 * info using git-diff-files(1), and finally untracked files using
5510 * git-ls-files(1). */
5511 static bool
5512 status_open(struct view *view)
5514 reset_view(view);
5516 add_line_data(view, NULL, LINE_STAT_HEAD);
5517 status_update_onbranch();
5519 run_io_bg(update_index_argv);
5521 if (is_initial_commit()) {
5522 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5523 return FALSE;
5524 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5525 return FALSE;
5528 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5529 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5530 return FALSE;
5532 /* Restore the exact position or use the specialized restore
5533 * mode? */
5534 if (!view->p_restore)
5535 status_restore(view);
5536 return TRUE;
5539 static bool
5540 status_draw(struct view *view, struct line *line, unsigned int lineno)
5542 struct status *status = line->data;
5543 enum line_type type;
5544 const char *text;
5546 if (!status) {
5547 switch (line->type) {
5548 case LINE_STAT_STAGED:
5549 type = LINE_STAT_SECTION;
5550 text = "Changes to be committed:";
5551 break;
5553 case LINE_STAT_UNSTAGED:
5554 type = LINE_STAT_SECTION;
5555 text = "Changed but not updated:";
5556 break;
5558 case LINE_STAT_UNTRACKED:
5559 type = LINE_STAT_SECTION;
5560 text = "Untracked files:";
5561 break;
5563 case LINE_STAT_NONE:
5564 type = LINE_DEFAULT;
5565 text = " (no files)";
5566 break;
5568 case LINE_STAT_HEAD:
5569 type = LINE_STAT_HEAD;
5570 text = status_onbranch;
5571 break;
5573 default:
5574 return FALSE;
5576 } else {
5577 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5579 buf[0] = status->status;
5580 if (draw_text(view, line->type, buf, TRUE))
5581 return TRUE;
5582 type = LINE_DEFAULT;
5583 text = status->new.name;
5586 draw_text(view, type, text, TRUE);
5587 return TRUE;
5590 static enum request
5591 status_load_error(struct view *view, struct view *stage, const char *path)
5593 if (displayed_views() == 2 || display[current_view] != view)
5594 maximize_view(view);
5595 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5596 return REQ_NONE;
5599 static enum request
5600 status_enter(struct view *view, struct line *line)
5602 struct status *status = line->data;
5603 const char *oldpath = status ? status->old.name : NULL;
5604 /* Diffs for unmerged entries are empty when passing the new
5605 * path, so leave it empty. */
5606 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5607 const char *info;
5608 enum open_flags split;
5609 struct view *stage = VIEW(REQ_VIEW_STAGE);
5611 if (line->type == LINE_STAT_NONE ||
5612 (!status && line[1].type == LINE_STAT_NONE)) {
5613 report("No file to diff");
5614 return REQ_NONE;
5617 switch (line->type) {
5618 case LINE_STAT_STAGED:
5619 if (is_initial_commit()) {
5620 const char *no_head_diff_argv[] = {
5621 "git", "diff", "--no-color", "--patch-with-stat",
5622 "--", "/dev/null", newpath, NULL
5625 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5626 return status_load_error(view, stage, newpath);
5627 } else {
5628 const char *index_show_argv[] = {
5629 "git", "diff-index", "--root", "--patch-with-stat",
5630 "-C", "-M", "--cached", "HEAD", "--",
5631 oldpath, newpath, NULL
5634 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5635 return status_load_error(view, stage, newpath);
5638 if (status)
5639 info = "Staged changes to %s";
5640 else
5641 info = "Staged changes";
5642 break;
5644 case LINE_STAT_UNSTAGED:
5646 const char *files_show_argv[] = {
5647 "git", "diff-files", "--root", "--patch-with-stat",
5648 "-C", "-M", "--", oldpath, newpath, NULL
5651 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5652 return status_load_error(view, stage, newpath);
5653 if (status)
5654 info = "Unstaged changes to %s";
5655 else
5656 info = "Unstaged changes";
5657 break;
5659 case LINE_STAT_UNTRACKED:
5660 if (!newpath) {
5661 report("No file to show");
5662 return REQ_NONE;
5665 if (!suffixcmp(status->new.name, -1, "/")) {
5666 report("Cannot display a directory");
5667 return REQ_NONE;
5670 if (!prepare_update_file(stage, newpath))
5671 return status_load_error(view, stage, newpath);
5672 info = "Untracked file %s";
5673 break;
5675 case LINE_STAT_HEAD:
5676 return REQ_NONE;
5678 default:
5679 die("line type %d not handled in switch", line->type);
5682 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5683 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5684 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5685 if (status) {
5686 stage_status = *status;
5687 } else {
5688 memset(&stage_status, 0, sizeof(stage_status));
5691 stage_line_type = line->type;
5692 stage_chunks = 0;
5693 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5696 return REQ_NONE;
5699 static bool
5700 status_exists(struct status *status, enum line_type type)
5702 struct view *view = VIEW(REQ_VIEW_STATUS);
5703 unsigned long lineno;
5705 for (lineno = 0; lineno < view->lines; lineno++) {
5706 struct line *line = &view->line[lineno];
5707 struct status *pos = line->data;
5709 if (line->type != type)
5710 continue;
5711 if (!pos && (!status || !status->status) && line[1].data) {
5712 select_view_line(view, lineno);
5713 return TRUE;
5715 if (pos && !strcmp(status->new.name, pos->new.name)) {
5716 select_view_line(view, lineno);
5717 return TRUE;
5721 return FALSE;
5725 static bool
5726 status_update_prepare(struct io *io, enum line_type type)
5728 const char *staged_argv[] = {
5729 "git", "update-index", "-z", "--index-info", NULL
5731 const char *others_argv[] = {
5732 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5735 switch (type) {
5736 case LINE_STAT_STAGED:
5737 return run_io(io, staged_argv, opt_cdup, IO_WR);
5739 case LINE_STAT_UNSTAGED:
5740 case LINE_STAT_UNTRACKED:
5741 return run_io(io, others_argv, opt_cdup, IO_WR);
5743 default:
5744 die("line type %d not handled in switch", type);
5745 return FALSE;
5749 static bool
5750 status_update_write(struct io *io, struct status *status, enum line_type type)
5752 char buf[SIZEOF_STR];
5753 size_t bufsize = 0;
5755 switch (type) {
5756 case LINE_STAT_STAGED:
5757 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5758 status->old.mode,
5759 status->old.rev,
5760 status->old.name, 0))
5761 return FALSE;
5762 break;
5764 case LINE_STAT_UNSTAGED:
5765 case LINE_STAT_UNTRACKED:
5766 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5767 return FALSE;
5768 break;
5770 default:
5771 die("line type %d not handled in switch", type);
5774 return io_write(io, buf, bufsize);
5777 static bool
5778 status_update_file(struct status *status, enum line_type type)
5780 struct io io = {};
5781 bool result;
5783 if (!status_update_prepare(&io, type))
5784 return FALSE;
5786 result = status_update_write(&io, status, type);
5787 return done_io(&io) && result;
5790 static bool
5791 status_update_files(struct view *view, struct line *line)
5793 char buf[sizeof(view->ref)];
5794 struct io io = {};
5795 bool result = TRUE;
5796 struct line *pos = view->line + view->lines;
5797 int files = 0;
5798 int file, done;
5799 int cursor_y = -1, cursor_x = -1;
5801 if (!status_update_prepare(&io, line->type))
5802 return FALSE;
5804 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5805 files++;
5807 string_copy(buf, view->ref);
5808 getsyx(cursor_y, cursor_x);
5809 for (file = 0, done = 5; result && file < files; line++, file++) {
5810 int almost_done = file * 100 / files;
5812 if (almost_done > done) {
5813 done = almost_done;
5814 string_format(view->ref, "updating file %u of %u (%d%% done)",
5815 file, files, done);
5816 update_view_title(view);
5817 setsyx(cursor_y, cursor_x);
5818 doupdate();
5820 result = status_update_write(&io, line->data, line->type);
5822 string_copy(view->ref, buf);
5824 return done_io(&io) && result;
5827 static bool
5828 status_update(struct view *view)
5830 struct line *line = &view->line[view->lineno];
5832 assert(view->lines);
5834 if (!line->data) {
5835 /* This should work even for the "On branch" line. */
5836 if (line < view->line + view->lines && !line[1].data) {
5837 report("Nothing to update");
5838 return FALSE;
5841 if (!status_update_files(view, line + 1)) {
5842 report("Failed to update file status");
5843 return FALSE;
5846 } else if (!status_update_file(line->data, line->type)) {
5847 report("Failed to update file status");
5848 return FALSE;
5851 return TRUE;
5854 static bool
5855 status_revert(struct status *status, enum line_type type, bool has_none)
5857 if (!status || type != LINE_STAT_UNSTAGED) {
5858 if (type == LINE_STAT_STAGED) {
5859 report("Cannot revert changes to staged files");
5860 } else if (type == LINE_STAT_UNTRACKED) {
5861 report("Cannot revert changes to untracked files");
5862 } else if (has_none) {
5863 report("Nothing to revert");
5864 } else {
5865 report("Cannot revert changes to multiple files");
5868 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5869 char mode[10] = "100644";
5870 const char *reset_argv[] = {
5871 "git", "update-index", "--cacheinfo", mode,
5872 status->old.rev, status->old.name, NULL
5874 const char *checkout_argv[] = {
5875 "git", "checkout", "--", status->old.name, NULL
5878 if (status->status == 'U') {
5879 string_format(mode, "%5o", status->old.mode);
5881 if (status->old.mode == 0 && status->new.mode == 0) {
5882 reset_argv[2] = "--force-remove";
5883 reset_argv[3] = status->old.name;
5884 reset_argv[4] = NULL;
5887 if (!run_io_fg(reset_argv, opt_cdup))
5888 return FALSE;
5889 if (status->old.mode == 0 && status->new.mode == 0)
5890 return TRUE;
5893 return run_io_fg(checkout_argv, opt_cdup);
5896 return FALSE;
5899 static enum request
5900 status_request(struct view *view, enum request request, struct line *line)
5902 struct status *status = line->data;
5904 switch (request) {
5905 case REQ_STATUS_UPDATE:
5906 if (!status_update(view))
5907 return REQ_NONE;
5908 break;
5910 case REQ_STATUS_REVERT:
5911 if (!status_revert(status, line->type, status_has_none(view, line)))
5912 return REQ_NONE;
5913 break;
5915 case REQ_STATUS_MERGE:
5916 if (!status || status->status != 'U') {
5917 report("Merging only possible for files with unmerged status ('U').");
5918 return REQ_NONE;
5920 open_mergetool(status->new.name);
5921 break;
5923 case REQ_EDIT:
5924 if (!status)
5925 return request;
5926 if (status->status == 'D') {
5927 report("File has been deleted.");
5928 return REQ_NONE;
5931 open_editor(status->status != '?', status->new.name);
5932 break;
5934 case REQ_VIEW_BLAME:
5935 if (status)
5936 opt_ref[0] = 0;
5937 return request;
5939 case REQ_ENTER:
5940 /* After returning the status view has been split to
5941 * show the stage view. No further reloading is
5942 * necessary. */
5943 return status_enter(view, line);
5945 case REQ_REFRESH:
5946 /* Simply reload the view. */
5947 break;
5949 default:
5950 return request;
5953 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5955 return REQ_NONE;
5958 static void
5959 status_select(struct view *view, struct line *line)
5961 struct status *status = line->data;
5962 char file[SIZEOF_STR] = "all files";
5963 const char *text;
5964 const char *key;
5966 if (status && !string_format(file, "'%s'", status->new.name))
5967 return;
5969 if (!status && line[1].type == LINE_STAT_NONE)
5970 line++;
5972 switch (line->type) {
5973 case LINE_STAT_STAGED:
5974 text = "Press %s to unstage %s for commit";
5975 break;
5977 case LINE_STAT_UNSTAGED:
5978 text = "Press %s to stage %s for commit";
5979 break;
5981 case LINE_STAT_UNTRACKED:
5982 text = "Press %s to stage %s for addition";
5983 break;
5985 case LINE_STAT_HEAD:
5986 case LINE_STAT_NONE:
5987 text = "Nothing to update";
5988 break;
5990 default:
5991 die("line type %d not handled in switch", line->type);
5994 if (status && status->status == 'U') {
5995 text = "Press %s to resolve conflict in %s";
5996 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
5998 } else {
5999 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6002 string_format(view->ref, text, key, file);
6003 if (status)
6004 string_copy(opt_file, status->new.name);
6007 static bool
6008 status_grep(struct view *view, struct line *line)
6010 struct status *status = line->data;
6012 if (status) {
6013 const char buf[2] = { status->status, 0 };
6014 const char *text[] = { status->new.name, buf, NULL };
6016 return grep_text(view, text);
6019 return FALSE;
6022 static struct view_ops status_ops = {
6023 "file",
6024 NULL,
6025 status_open,
6026 NULL,
6027 status_draw,
6028 status_request,
6029 status_grep,
6030 status_select,
6034 static bool
6035 stage_diff_write(struct io *io, struct line *line, struct line *end)
6037 while (line < end) {
6038 if (!io_write(io, line->data, strlen(line->data)) ||
6039 !io_write(io, "\n", 1))
6040 return FALSE;
6041 line++;
6042 if (line->type == LINE_DIFF_CHUNK ||
6043 line->type == LINE_DIFF_HEADER)
6044 break;
6047 return TRUE;
6050 static struct line *
6051 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6053 for (; view->line < line; line--)
6054 if (line->type == type)
6055 return line;
6057 return NULL;
6060 static bool
6061 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6063 const char *apply_argv[SIZEOF_ARG] = {
6064 "git", "apply", "--whitespace=nowarn", NULL
6066 struct line *diff_hdr;
6067 struct io io = {};
6068 int argc = 3;
6070 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6071 if (!diff_hdr)
6072 return FALSE;
6074 if (!revert)
6075 apply_argv[argc++] = "--cached";
6076 if (revert || stage_line_type == LINE_STAT_STAGED)
6077 apply_argv[argc++] = "-R";
6078 apply_argv[argc++] = "-";
6079 apply_argv[argc++] = NULL;
6080 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6081 return FALSE;
6083 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6084 !stage_diff_write(&io, chunk, view->line + view->lines))
6085 chunk = NULL;
6087 done_io(&io);
6088 run_io_bg(update_index_argv);
6090 return chunk ? TRUE : FALSE;
6093 static bool
6094 stage_update(struct view *view, struct line *line)
6096 struct line *chunk = NULL;
6098 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6099 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6101 if (chunk) {
6102 if (!stage_apply_chunk(view, chunk, FALSE)) {
6103 report("Failed to apply chunk");
6104 return FALSE;
6107 } else if (!stage_status.status) {
6108 view = VIEW(REQ_VIEW_STATUS);
6110 for (line = view->line; line < view->line + view->lines; line++)
6111 if (line->type == stage_line_type)
6112 break;
6114 if (!status_update_files(view, line + 1)) {
6115 report("Failed to update files");
6116 return FALSE;
6119 } else if (!status_update_file(&stage_status, stage_line_type)) {
6120 report("Failed to update file");
6121 return FALSE;
6124 return TRUE;
6127 static bool
6128 stage_revert(struct view *view, struct line *line)
6130 struct line *chunk = NULL;
6132 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6133 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6135 if (chunk) {
6136 if (!prompt_yesno("Are you sure you want to revert changes?"))
6137 return FALSE;
6139 if (!stage_apply_chunk(view, chunk, TRUE)) {
6140 report("Failed to revert chunk");
6141 return FALSE;
6143 return TRUE;
6145 } else {
6146 return status_revert(stage_status.status ? &stage_status : NULL,
6147 stage_line_type, FALSE);
6152 static void
6153 stage_next(struct view *view, struct line *line)
6155 int i;
6157 if (!stage_chunks) {
6158 for (line = view->line; line < view->line + view->lines; line++) {
6159 if (line->type != LINE_DIFF_CHUNK)
6160 continue;
6162 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6163 report("Allocation failure");
6164 return;
6167 stage_chunk[stage_chunks++] = line - view->line;
6171 for (i = 0; i < stage_chunks; i++) {
6172 if (stage_chunk[i] > view->lineno) {
6173 do_scroll_view(view, stage_chunk[i] - view->lineno);
6174 report("Chunk %d of %d", i + 1, stage_chunks);
6175 return;
6179 report("No next chunk found");
6182 static enum request
6183 stage_request(struct view *view, enum request request, struct line *line)
6185 switch (request) {
6186 case REQ_STATUS_UPDATE:
6187 if (!stage_update(view, line))
6188 return REQ_NONE;
6189 break;
6191 case REQ_STATUS_REVERT:
6192 if (!stage_revert(view, line))
6193 return REQ_NONE;
6194 break;
6196 case REQ_STAGE_NEXT:
6197 if (stage_line_type == LINE_STAT_UNTRACKED) {
6198 report("File is untracked; press %s to add",
6199 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6200 return REQ_NONE;
6202 stage_next(view, line);
6203 return REQ_NONE;
6205 case REQ_EDIT:
6206 if (!stage_status.new.name[0])
6207 return request;
6208 if (stage_status.status == 'D') {
6209 report("File has been deleted.");
6210 return REQ_NONE;
6213 open_editor(stage_status.status != '?', stage_status.new.name);
6214 break;
6216 case REQ_REFRESH:
6217 /* Reload everything ... */
6218 break;
6220 case REQ_VIEW_BLAME:
6221 if (stage_status.new.name[0]) {
6222 string_copy(opt_file, stage_status.new.name);
6223 opt_ref[0] = 0;
6225 return request;
6227 case REQ_ENTER:
6228 return pager_request(view, request, line);
6230 default:
6231 return request;
6234 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6235 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6237 /* Check whether the staged entry still exists, and close the
6238 * stage view if it doesn't. */
6239 if (!status_exists(&stage_status, stage_line_type)) {
6240 status_restore(VIEW(REQ_VIEW_STATUS));
6241 return REQ_VIEW_CLOSE;
6244 if (stage_line_type == LINE_STAT_UNTRACKED) {
6245 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6246 report("Cannot display a directory");
6247 return REQ_NONE;
6250 if (!prepare_update_file(view, stage_status.new.name)) {
6251 report("Failed to open file: %s", strerror(errno));
6252 return REQ_NONE;
6255 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6257 return REQ_NONE;
6260 static struct view_ops stage_ops = {
6261 "line",
6262 NULL,
6263 NULL,
6264 pager_read,
6265 pager_draw,
6266 stage_request,
6267 pager_grep,
6268 pager_select,
6273 * Revision graph
6276 struct commit {
6277 char id[SIZEOF_REV]; /* SHA1 ID. */
6278 char title[128]; /* First line of the commit message. */
6279 const char *author; /* Author of the commit. */
6280 time_t time; /* Date from the author ident. */
6281 struct ref_list *refs; /* Repository references. */
6282 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6283 size_t graph_size; /* The width of the graph array. */
6284 bool has_parents; /* Rewritten --parents seen. */
6287 /* Size of rev graph with no "padding" columns */
6288 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6290 struct rev_graph {
6291 struct rev_graph *prev, *next, *parents;
6292 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6293 size_t size;
6294 struct commit *commit;
6295 size_t pos;
6296 unsigned int boundary:1;
6299 /* Parents of the commit being visualized. */
6300 static struct rev_graph graph_parents[4];
6302 /* The current stack of revisions on the graph. */
6303 static struct rev_graph graph_stacks[4] = {
6304 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6305 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6306 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6307 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6310 static inline bool
6311 graph_parent_is_merge(struct rev_graph *graph)
6313 return graph->parents->size > 1;
6316 static inline void
6317 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6319 struct commit *commit = graph->commit;
6321 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6322 commit->graph[commit->graph_size++] = symbol;
6325 static void
6326 clear_rev_graph(struct rev_graph *graph)
6328 graph->boundary = 0;
6329 graph->size = graph->pos = 0;
6330 graph->commit = NULL;
6331 memset(graph->parents, 0, sizeof(*graph->parents));
6334 static void
6335 done_rev_graph(struct rev_graph *graph)
6337 if (graph_parent_is_merge(graph) &&
6338 graph->pos < graph->size - 1 &&
6339 graph->next->size == graph->size + graph->parents->size - 1) {
6340 size_t i = graph->pos + graph->parents->size - 1;
6342 graph->commit->graph_size = i * 2;
6343 while (i < graph->next->size - 1) {
6344 append_to_rev_graph(graph, ' ');
6345 append_to_rev_graph(graph, '\\');
6346 i++;
6350 clear_rev_graph(graph);
6353 static void
6354 push_rev_graph(struct rev_graph *graph, const char *parent)
6356 int i;
6358 /* "Collapse" duplicate parents lines.
6360 * FIXME: This needs to also update update the drawn graph but
6361 * for now it just serves as a method for pruning graph lines. */
6362 for (i = 0; i < graph->size; i++)
6363 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6364 return;
6366 if (graph->size < SIZEOF_REVITEMS) {
6367 string_copy_rev(graph->rev[graph->size++], parent);
6371 static chtype
6372 get_rev_graph_symbol(struct rev_graph *graph)
6374 chtype symbol;
6376 if (graph->boundary)
6377 symbol = REVGRAPH_BOUND;
6378 else if (graph->parents->size == 0)
6379 symbol = REVGRAPH_INIT;
6380 else if (graph_parent_is_merge(graph))
6381 symbol = REVGRAPH_MERGE;
6382 else if (graph->pos >= graph->size)
6383 symbol = REVGRAPH_BRANCH;
6384 else
6385 symbol = REVGRAPH_COMMIT;
6387 return symbol;
6390 static void
6391 draw_rev_graph(struct rev_graph *graph)
6393 struct rev_filler {
6394 chtype separator, line;
6396 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6397 static struct rev_filler fillers[] = {
6398 { ' ', '|' },
6399 { '`', '.' },
6400 { '\'', ' ' },
6401 { '/', ' ' },
6403 chtype symbol = get_rev_graph_symbol(graph);
6404 struct rev_filler *filler;
6405 size_t i;
6407 if (opt_line_graphics)
6408 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6410 filler = &fillers[DEFAULT];
6412 for (i = 0; i < graph->pos; i++) {
6413 append_to_rev_graph(graph, filler->line);
6414 if (graph_parent_is_merge(graph->prev) &&
6415 graph->prev->pos == i)
6416 filler = &fillers[RSHARP];
6418 append_to_rev_graph(graph, filler->separator);
6421 /* Place the symbol for this revision. */
6422 append_to_rev_graph(graph, symbol);
6424 if (graph->prev->size > graph->size)
6425 filler = &fillers[RDIAG];
6426 else
6427 filler = &fillers[DEFAULT];
6429 i++;
6431 for (; i < graph->size; i++) {
6432 append_to_rev_graph(graph, filler->separator);
6433 append_to_rev_graph(graph, filler->line);
6434 if (graph_parent_is_merge(graph->prev) &&
6435 i < graph->prev->pos + graph->parents->size)
6436 filler = &fillers[RSHARP];
6437 if (graph->prev->size > graph->size)
6438 filler = &fillers[LDIAG];
6441 if (graph->prev->size > graph->size) {
6442 append_to_rev_graph(graph, filler->separator);
6443 if (filler->line != ' ')
6444 append_to_rev_graph(graph, filler->line);
6448 /* Prepare the next rev graph */
6449 static void
6450 prepare_rev_graph(struct rev_graph *graph)
6452 size_t i;
6454 /* First, traverse all lines of revisions up to the active one. */
6455 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6456 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6457 break;
6459 push_rev_graph(graph->next, graph->rev[graph->pos]);
6462 /* Interleave the new revision parent(s). */
6463 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6464 push_rev_graph(graph->next, graph->parents->rev[i]);
6466 /* Lastly, put any remaining revisions. */
6467 for (i = graph->pos + 1; i < graph->size; i++)
6468 push_rev_graph(graph->next, graph->rev[i]);
6471 static void
6472 update_rev_graph(struct view *view, struct rev_graph *graph)
6474 /* If this is the finalizing update ... */
6475 if (graph->commit)
6476 prepare_rev_graph(graph);
6478 /* Graph visualization needs a one rev look-ahead,
6479 * so the first update doesn't visualize anything. */
6480 if (!graph->prev->commit)
6481 return;
6483 if (view->lines > 2)
6484 view->line[view->lines - 3].dirty = 1;
6485 if (view->lines > 1)
6486 view->line[view->lines - 2].dirty = 1;
6487 draw_rev_graph(graph->prev);
6488 done_rev_graph(graph->prev->prev);
6493 * Main view backend
6496 static const char *main_argv[SIZEOF_ARG] = {
6497 "git", "log", "--no-color", "--pretty=raw", "--parents",
6498 "--topo-order", "%(head)", NULL
6501 static bool
6502 main_draw(struct view *view, struct line *line, unsigned int lineno)
6504 struct commit *commit = line->data;
6506 if (!commit->author)
6507 return FALSE;
6509 if (opt_date && draw_date(view, &commit->time))
6510 return TRUE;
6512 if (opt_author && draw_author(view, commit->author))
6513 return TRUE;
6515 if (opt_rev_graph && commit->graph_size &&
6516 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6517 return TRUE;
6519 if (opt_show_refs && commit->refs) {
6520 size_t i;
6522 for (i = 0; i < commit->refs->size; i++) {
6523 struct ref *ref = commit->refs->refs[i];
6524 enum line_type type;
6526 if (ref->head)
6527 type = LINE_MAIN_HEAD;
6528 else if (ref->ltag)
6529 type = LINE_MAIN_LOCAL_TAG;
6530 else if (ref->tag)
6531 type = LINE_MAIN_TAG;
6532 else if (ref->tracked)
6533 type = LINE_MAIN_TRACKED;
6534 else if (ref->remote)
6535 type = LINE_MAIN_REMOTE;
6536 else
6537 type = LINE_MAIN_REF;
6539 if (draw_text(view, type, "[", TRUE) ||
6540 draw_text(view, type, ref->name, TRUE) ||
6541 draw_text(view, type, "]", TRUE))
6542 return TRUE;
6544 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6545 return TRUE;
6549 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6550 return TRUE;
6553 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6554 static bool
6555 main_read(struct view *view, char *line)
6557 static struct rev_graph *graph = graph_stacks;
6558 enum line_type type;
6559 struct commit *commit;
6561 if (!line) {
6562 int i;
6564 if (!view->lines && !view->parent)
6565 die("No revisions match the given arguments.");
6566 if (view->lines > 0) {
6567 commit = view->line[view->lines - 1].data;
6568 view->line[view->lines - 1].dirty = 1;
6569 if (!commit->author) {
6570 view->lines--;
6571 free(commit);
6572 graph->commit = NULL;
6575 update_rev_graph(view, graph);
6577 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6578 clear_rev_graph(&graph_stacks[i]);
6579 return TRUE;
6582 type = get_line_type(line);
6583 if (type == LINE_COMMIT) {
6584 commit = calloc(1, sizeof(struct commit));
6585 if (!commit)
6586 return FALSE;
6588 line += STRING_SIZE("commit ");
6589 if (*line == '-') {
6590 graph->boundary = 1;
6591 line++;
6594 string_copy_rev(commit->id, line);
6595 commit->refs = get_ref_list(commit->id);
6596 graph->commit = commit;
6597 add_line_data(view, commit, LINE_MAIN_COMMIT);
6599 while ((line = strchr(line, ' '))) {
6600 line++;
6601 push_rev_graph(graph->parents, line);
6602 commit->has_parents = TRUE;
6604 return TRUE;
6607 if (!view->lines)
6608 return TRUE;
6609 commit = view->line[view->lines - 1].data;
6611 switch (type) {
6612 case LINE_PARENT:
6613 if (commit->has_parents)
6614 break;
6615 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6616 break;
6618 case LINE_AUTHOR:
6619 parse_author_line(line + STRING_SIZE("author "),
6620 &commit->author, &commit->time);
6621 update_rev_graph(view, graph);
6622 graph = graph->next;
6623 break;
6625 default:
6626 /* Fill in the commit title if it has not already been set. */
6627 if (commit->title[0])
6628 break;
6630 /* Require titles to start with a non-space character at the
6631 * offset used by git log. */
6632 if (strncmp(line, " ", 4))
6633 break;
6634 line += 4;
6635 /* Well, if the title starts with a whitespace character,
6636 * try to be forgiving. Otherwise we end up with no title. */
6637 while (isspace(*line))
6638 line++;
6639 if (*line == '\0')
6640 break;
6641 /* FIXME: More graceful handling of titles; append "..." to
6642 * shortened titles, etc. */
6644 string_expand(commit->title, sizeof(commit->title), line, 1);
6645 view->line[view->lines - 1].dirty = 1;
6648 return TRUE;
6651 static enum request
6652 main_request(struct view *view, enum request request, struct line *line)
6654 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6656 switch (request) {
6657 case REQ_ENTER:
6658 open_view(view, REQ_VIEW_DIFF, flags);
6659 break;
6660 case REQ_REFRESH:
6661 load_refs();
6662 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6663 break;
6664 default:
6665 return request;
6668 return REQ_NONE;
6671 static bool
6672 grep_refs(struct ref_list *list, regex_t *regex)
6674 regmatch_t pmatch;
6675 size_t i;
6677 if (!opt_show_refs || !list)
6678 return FALSE;
6680 for (i = 0; i < list->size; i++) {
6681 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6682 return TRUE;
6685 return FALSE;
6688 static bool
6689 main_grep(struct view *view, struct line *line)
6691 struct commit *commit = line->data;
6692 const char *text[] = {
6693 commit->title,
6694 opt_author ? commit->author : "",
6695 opt_date ? mkdate(&commit->time) : "",
6696 NULL
6699 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6702 static void
6703 main_select(struct view *view, struct line *line)
6705 struct commit *commit = line->data;
6707 string_copy_rev(view->ref, commit->id);
6708 string_copy_rev(ref_commit, view->ref);
6711 static struct view_ops main_ops = {
6712 "commit",
6713 main_argv,
6714 NULL,
6715 main_read,
6716 main_draw,
6717 main_request,
6718 main_grep,
6719 main_select,
6724 * Unicode / UTF-8 handling
6726 * NOTE: Much of the following code for dealing with Unicode is derived from
6727 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6728 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6731 static inline int
6732 unicode_width(unsigned long c)
6734 if (c >= 0x1100 &&
6735 (c <= 0x115f /* Hangul Jamo */
6736 || c == 0x2329
6737 || c == 0x232a
6738 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6739 /* CJK ... Yi */
6740 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6741 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6742 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6743 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6744 || (c >= 0xffe0 && c <= 0xffe6)
6745 || (c >= 0x20000 && c <= 0x2fffd)
6746 || (c >= 0x30000 && c <= 0x3fffd)))
6747 return 2;
6749 if (c == '\t')
6750 return opt_tab_size;
6752 return 1;
6755 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6756 * Illegal bytes are set one. */
6757 static const unsigned char utf8_bytes[256] = {
6758 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,
6759 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,
6760 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,
6761 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,
6762 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,
6763 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,
6764 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,
6765 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,
6768 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6769 static inline unsigned long
6770 utf8_to_unicode(const char *string, size_t length)
6772 unsigned long unicode;
6774 switch (length) {
6775 case 1:
6776 unicode = string[0];
6777 break;
6778 case 2:
6779 unicode = (string[0] & 0x1f) << 6;
6780 unicode += (string[1] & 0x3f);
6781 break;
6782 case 3:
6783 unicode = (string[0] & 0x0f) << 12;
6784 unicode += ((string[1] & 0x3f) << 6);
6785 unicode += (string[2] & 0x3f);
6786 break;
6787 case 4:
6788 unicode = (string[0] & 0x0f) << 18;
6789 unicode += ((string[1] & 0x3f) << 12);
6790 unicode += ((string[2] & 0x3f) << 6);
6791 unicode += (string[3] & 0x3f);
6792 break;
6793 case 5:
6794 unicode = (string[0] & 0x0f) << 24;
6795 unicode += ((string[1] & 0x3f) << 18);
6796 unicode += ((string[2] & 0x3f) << 12);
6797 unicode += ((string[3] & 0x3f) << 6);
6798 unicode += (string[4] & 0x3f);
6799 break;
6800 case 6:
6801 unicode = (string[0] & 0x01) << 30;
6802 unicode += ((string[1] & 0x3f) << 24);
6803 unicode += ((string[2] & 0x3f) << 18);
6804 unicode += ((string[3] & 0x3f) << 12);
6805 unicode += ((string[4] & 0x3f) << 6);
6806 unicode += (string[5] & 0x3f);
6807 break;
6808 default:
6809 die("Invalid Unicode length");
6812 /* Invalid characters could return the special 0xfffd value but NUL
6813 * should be just as good. */
6814 return unicode > 0xffff ? 0 : unicode;
6817 /* Calculates how much of string can be shown within the given maximum width
6818 * and sets trimmed parameter to non-zero value if all of string could not be
6819 * shown. If the reserve flag is TRUE, it will reserve at least one
6820 * trailing character, which can be useful when drawing a delimiter.
6822 * Returns the number of bytes to output from string to satisfy max_width. */
6823 static size_t
6824 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6826 const char *string = *start;
6827 const char *end = strchr(string, '\0');
6828 unsigned char last_bytes = 0;
6829 size_t last_ucwidth = 0;
6831 *width = 0;
6832 *trimmed = 0;
6834 while (string < end) {
6835 int c = *(unsigned char *) string;
6836 unsigned char bytes = utf8_bytes[c];
6837 size_t ucwidth;
6838 unsigned long unicode;
6840 if (string + bytes > end)
6841 break;
6843 /* Change representation to figure out whether
6844 * it is a single- or double-width character. */
6846 unicode = utf8_to_unicode(string, bytes);
6847 /* FIXME: Graceful handling of invalid Unicode character. */
6848 if (!unicode)
6849 break;
6851 ucwidth = unicode_width(unicode);
6852 if (skip > 0) {
6853 skip -= ucwidth <= skip ? ucwidth : skip;
6854 *start += bytes;
6856 *width += ucwidth;
6857 if (*width > max_width) {
6858 *trimmed = 1;
6859 *width -= ucwidth;
6860 if (reserve && *width == max_width) {
6861 string -= last_bytes;
6862 *width -= last_ucwidth;
6864 break;
6867 string += bytes;
6868 last_bytes = ucwidth ? bytes : 0;
6869 last_ucwidth = ucwidth;
6872 return string - *start;
6877 * Status management
6880 /* Whether or not the curses interface has been initialized. */
6881 static bool cursed = FALSE;
6883 /* Terminal hacks and workarounds. */
6884 static bool use_scroll_redrawwin;
6885 static bool use_scroll_status_wclear;
6887 /* The status window is used for polling keystrokes. */
6888 static WINDOW *status_win;
6890 /* Reading from the prompt? */
6891 static bool input_mode = FALSE;
6893 static bool status_empty = FALSE;
6895 /* Update status and title window. */
6896 static void
6897 report(const char *msg, ...)
6899 struct view *view = display[current_view];
6901 if (input_mode)
6902 return;
6904 if (!view) {
6905 char buf[SIZEOF_STR];
6906 va_list args;
6908 va_start(args, msg);
6909 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6910 buf[sizeof(buf) - 1] = 0;
6911 buf[sizeof(buf) - 2] = '.';
6912 buf[sizeof(buf) - 3] = '.';
6913 buf[sizeof(buf) - 4] = '.';
6915 va_end(args);
6916 die("%s", buf);
6919 if (!status_empty || *msg) {
6920 va_list args;
6922 va_start(args, msg);
6924 wmove(status_win, 0, 0);
6925 if (view->has_scrolled && use_scroll_status_wclear)
6926 wclear(status_win);
6927 if (*msg) {
6928 vwprintw(status_win, msg, args);
6929 status_empty = FALSE;
6930 } else {
6931 status_empty = TRUE;
6933 wclrtoeol(status_win);
6934 wnoutrefresh(status_win);
6936 va_end(args);
6939 update_view_title(view);
6942 /* Controls when nodelay should be in effect when polling user input. */
6943 static void
6944 set_nonblocking_input(bool loading)
6946 static unsigned int loading_views;
6948 if ((loading == FALSE && loading_views-- == 1) ||
6949 (loading == TRUE && loading_views++ == 0))
6950 nodelay(status_win, loading);
6953 static void
6954 init_display(void)
6956 const char *term;
6957 int x, y;
6959 /* Initialize the curses library */
6960 if (isatty(STDIN_FILENO)) {
6961 cursed = !!initscr();
6962 opt_tty = stdin;
6963 } else {
6964 /* Leave stdin and stdout alone when acting as a pager. */
6965 opt_tty = fopen("/dev/tty", "r+");
6966 if (!opt_tty)
6967 die("Failed to open /dev/tty");
6968 cursed = !!newterm(NULL, opt_tty, opt_tty);
6971 if (!cursed)
6972 die("Failed to initialize curses");
6974 nonl(); /* Disable conversion and detect newlines from input. */
6975 cbreak(); /* Take input chars one at a time, no wait for \n */
6976 noecho(); /* Don't echo input */
6977 leaveok(stdscr, FALSE);
6979 if (has_colors())
6980 init_colors();
6982 getmaxyx(stdscr, y, x);
6983 status_win = newwin(1, 0, y - 1, 0);
6984 if (!status_win)
6985 die("Failed to create status window");
6987 /* Enable keyboard mapping */
6988 keypad(status_win, TRUE);
6989 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6991 TABSIZE = opt_tab_size;
6992 if (opt_line_graphics) {
6993 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6996 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6997 if (term && !strcmp(term, "gnome-terminal")) {
6998 /* In the gnome-terminal-emulator, the message from
6999 * scrolling up one line when impossible followed by
7000 * scrolling down one line causes corruption of the
7001 * status line. This is fixed by calling wclear. */
7002 use_scroll_status_wclear = TRUE;
7003 use_scroll_redrawwin = FALSE;
7005 } else if (term && !strcmp(term, "xrvt-xpm")) {
7006 /* No problems with full optimizations in xrvt-(unicode)
7007 * and aterm. */
7008 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7010 } else {
7011 /* When scrolling in (u)xterm the last line in the
7012 * scrolling direction will update slowly. */
7013 use_scroll_redrawwin = TRUE;
7014 use_scroll_status_wclear = FALSE;
7018 static int
7019 get_input(int prompt_position)
7021 struct view *view;
7022 int i, key, cursor_y, cursor_x;
7024 if (prompt_position)
7025 input_mode = TRUE;
7027 while (TRUE) {
7028 foreach_view (view, i) {
7029 update_view(view);
7030 if (view_is_displayed(view) && view->has_scrolled &&
7031 use_scroll_redrawwin)
7032 redrawwin(view->win);
7033 view->has_scrolled = FALSE;
7036 /* Update the cursor position. */
7037 if (prompt_position) {
7038 getbegyx(status_win, cursor_y, cursor_x);
7039 cursor_x = prompt_position;
7040 } else {
7041 view = display[current_view];
7042 getbegyx(view->win, cursor_y, cursor_x);
7043 cursor_x = view->width - 1;
7044 cursor_y += view->lineno - view->offset;
7046 setsyx(cursor_y, cursor_x);
7048 /* Refresh, accept single keystroke of input */
7049 doupdate();
7050 key = wgetch(status_win);
7052 /* wgetch() with nodelay() enabled returns ERR when
7053 * there's no input. */
7054 if (key == ERR) {
7056 } else if (key == KEY_RESIZE) {
7057 int height, width;
7059 getmaxyx(stdscr, height, width);
7061 wresize(status_win, 1, width);
7062 mvwin(status_win, height - 1, 0);
7063 wnoutrefresh(status_win);
7064 resize_display();
7065 redraw_display(TRUE);
7067 } else {
7068 input_mode = FALSE;
7069 return key;
7074 static char *
7075 prompt_input(const char *prompt, input_handler handler, void *data)
7077 enum input_status status = INPUT_OK;
7078 static char buf[SIZEOF_STR];
7079 size_t pos = 0;
7081 buf[pos] = 0;
7083 while (status == INPUT_OK || status == INPUT_SKIP) {
7084 int key;
7086 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7087 wclrtoeol(status_win);
7089 key = get_input(pos + 1);
7090 switch (key) {
7091 case KEY_RETURN:
7092 case KEY_ENTER:
7093 case '\n':
7094 status = pos ? INPUT_STOP : INPUT_CANCEL;
7095 break;
7097 case KEY_BACKSPACE:
7098 if (pos > 0)
7099 buf[--pos] = 0;
7100 else
7101 status = INPUT_CANCEL;
7102 break;
7104 case KEY_ESC:
7105 status = INPUT_CANCEL;
7106 break;
7108 default:
7109 if (pos >= sizeof(buf)) {
7110 report("Input string too long");
7111 return NULL;
7114 status = handler(data, buf, key);
7115 if (status == INPUT_OK)
7116 buf[pos++] = (char) key;
7120 /* Clear the status window */
7121 status_empty = FALSE;
7122 report("");
7124 if (status == INPUT_CANCEL)
7125 return NULL;
7127 buf[pos++] = 0;
7129 return buf;
7132 static enum input_status
7133 prompt_yesno_handler(void *data, char *buf, int c)
7135 if (c == 'y' || c == 'Y')
7136 return INPUT_STOP;
7137 if (c == 'n' || c == 'N')
7138 return INPUT_CANCEL;
7139 return INPUT_SKIP;
7142 static bool
7143 prompt_yesno(const char *prompt)
7145 char prompt2[SIZEOF_STR];
7147 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7148 return FALSE;
7150 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7153 static enum input_status
7154 read_prompt_handler(void *data, char *buf, int c)
7156 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7159 static char *
7160 read_prompt(const char *prompt)
7162 return prompt_input(prompt, read_prompt_handler, NULL);
7165 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7167 enum input_status status = INPUT_OK;
7168 int size = 0;
7170 while (items[size].text)
7171 size++;
7173 while (status == INPUT_OK) {
7174 const struct menu_item *item = &items[*selected];
7175 int key;
7176 int i;
7178 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7179 prompt, *selected + 1, size);
7180 if (item->hotkey)
7181 wprintw(status_win, "[%c] ", (char) item->hotkey);
7182 wprintw(status_win, "%s", item->text);
7183 wclrtoeol(status_win);
7185 key = get_input(COLS - 1);
7186 switch (key) {
7187 case KEY_RETURN:
7188 case KEY_ENTER:
7189 case '\n':
7190 status = INPUT_STOP;
7191 break;
7193 case KEY_LEFT:
7194 case KEY_UP:
7195 *selected = *selected - 1;
7196 if (*selected < 0)
7197 *selected = size - 1;
7198 break;
7200 case KEY_RIGHT:
7201 case KEY_DOWN:
7202 *selected = (*selected + 1) % size;
7203 break;
7205 case KEY_ESC:
7206 status = INPUT_CANCEL;
7207 break;
7209 default:
7210 for (i = 0; items[i].text; i++)
7211 if (items[i].hotkey == key) {
7212 *selected = i;
7213 status = INPUT_STOP;
7214 break;
7219 /* Clear the status window */
7220 status_empty = FALSE;
7221 report("");
7223 return status != INPUT_CANCEL;
7227 * Repository properties
7230 static struct ref **refs = NULL;
7231 static size_t refs_size = 0;
7233 static struct ref_list **ref_lists = NULL;
7234 static size_t ref_lists_size = 0;
7236 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7237 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7238 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7240 static int
7241 compare_refs(const void *ref1_, const void *ref2_)
7243 const struct ref *ref1 = *(const struct ref **)ref1_;
7244 const struct ref *ref2 = *(const struct ref **)ref2_;
7246 if (ref1->tag != ref2->tag)
7247 return ref2->tag - ref1->tag;
7248 if (ref1->ltag != ref2->ltag)
7249 return ref2->ltag - ref2->ltag;
7250 if (ref1->head != ref2->head)
7251 return ref2->head - ref1->head;
7252 if (ref1->tracked != ref2->tracked)
7253 return ref2->tracked - ref1->tracked;
7254 if (ref1->remote != ref2->remote)
7255 return ref2->remote - ref1->remote;
7256 return strcmp(ref1->name, ref2->name);
7259 static void
7260 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7262 size_t i;
7264 for (i = 0; i < refs_size; i++)
7265 if (!visitor(data, refs[i]))
7266 break;
7269 static struct ref_list *
7270 get_ref_list(const char *id)
7272 struct ref_list *list;
7273 size_t i;
7275 for (i = 0; i < ref_lists_size; i++)
7276 if (!strcmp(id, ref_lists[i]->id))
7277 return ref_lists[i];
7279 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7280 return NULL;
7281 list = calloc(1, sizeof(*list));
7282 if (!list)
7283 return NULL;
7285 for (i = 0; i < refs_size; i++) {
7286 if (!strcmp(id, refs[i]->id) &&
7287 realloc_refs_list(&list->refs, list->size, 1))
7288 list->refs[list->size++] = refs[i];
7291 if (!list->refs) {
7292 free(list);
7293 return NULL;
7296 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7297 ref_lists[ref_lists_size++] = list;
7298 return list;
7301 static int
7302 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7304 struct ref *ref = NULL;
7305 bool tag = FALSE;
7306 bool ltag = FALSE;
7307 bool remote = FALSE;
7308 bool tracked = FALSE;
7309 bool head = FALSE;
7310 int from = 0, to = refs_size - 1;
7312 if (!prefixcmp(name, "refs/tags/")) {
7313 if (!suffixcmp(name, namelen, "^{}")) {
7314 namelen -= 3;
7315 name[namelen] = 0;
7316 } else {
7317 ltag = TRUE;
7320 tag = TRUE;
7321 namelen -= STRING_SIZE("refs/tags/");
7322 name += STRING_SIZE("refs/tags/");
7324 } else if (!prefixcmp(name, "refs/remotes/")) {
7325 remote = TRUE;
7326 namelen -= STRING_SIZE("refs/remotes/");
7327 name += STRING_SIZE("refs/remotes/");
7328 tracked = !strcmp(opt_remote, name);
7330 } else if (!prefixcmp(name, "refs/heads/")) {
7331 namelen -= STRING_SIZE("refs/heads/");
7332 name += STRING_SIZE("refs/heads/");
7333 head = !strncmp(opt_head, name, namelen);
7335 } else if (!strcmp(name, "HEAD")) {
7336 string_ncopy(opt_head_rev, id, idlen);
7337 return OK;
7340 /* If we are reloading or it's an annotated tag, replace the
7341 * previous SHA1 with the resolved commit id; relies on the fact
7342 * git-ls-remote lists the commit id of an annotated tag right
7343 * before the commit id it points to. */
7344 while (from <= to) {
7345 size_t pos = (to + from) / 2;
7346 int cmp = strcmp(name, refs[pos]->name);
7348 if (!cmp) {
7349 ref = refs[pos];
7350 break;
7353 if (cmp < 0)
7354 to = pos - 1;
7355 else
7356 from = pos + 1;
7359 if (!ref) {
7360 if (!realloc_refs(&refs, refs_size, 1))
7361 return ERR;
7362 ref = calloc(1, sizeof(*ref) + namelen);
7363 if (!ref)
7364 return ERR;
7365 memmove(refs + from + 1, refs + from,
7366 (refs_size - from) * sizeof(*refs));
7367 refs[from] = ref;
7368 strncpy(ref->name, name, namelen);
7369 refs_size++;
7372 ref->head = head;
7373 ref->tag = tag;
7374 ref->ltag = ltag;
7375 ref->remote = remote;
7376 ref->tracked = tracked;
7377 string_copy_rev(ref->id, id);
7379 return OK;
7382 static int
7383 load_refs(void)
7385 const char *head_argv[] = {
7386 "git", "symbolic-ref", "HEAD", NULL
7388 static const char *ls_remote_argv[SIZEOF_ARG] = {
7389 "git", "ls-remote", opt_git_dir, NULL
7391 static bool init = FALSE;
7392 size_t i;
7394 if (!init) {
7395 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7396 init = TRUE;
7399 if (!*opt_git_dir)
7400 return OK;
7402 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7403 !prefixcmp(opt_head, "refs/heads/")) {
7404 char *offset = opt_head + STRING_SIZE("refs/heads/");
7406 memmove(opt_head, offset, strlen(offset) + 1);
7409 for (i = 0; i < refs_size; i++)
7410 refs[i]->id[0] = 0;
7412 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7413 return ERR;
7415 /* Update the ref lists to reflect changes. */
7416 for (i = 0; i < ref_lists_size; i++) {
7417 struct ref_list *list = ref_lists[i];
7418 size_t old, new;
7420 for (old = new = 0; old < list->size; old++)
7421 if (!strcmp(list->id, list->refs[old]->id))
7422 list->refs[new++] = list->refs[old];
7423 list->size = new;
7426 return OK;
7429 static void
7430 set_remote_branch(const char *name, const char *value, size_t valuelen)
7432 if (!strcmp(name, ".remote")) {
7433 string_ncopy(opt_remote, value, valuelen);
7435 } else if (*opt_remote && !strcmp(name, ".merge")) {
7436 size_t from = strlen(opt_remote);
7438 if (!prefixcmp(value, "refs/heads/"))
7439 value += STRING_SIZE("refs/heads/");
7441 if (!string_format_from(opt_remote, &from, "/%s", value))
7442 opt_remote[0] = 0;
7446 static void
7447 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7449 const char *argv[SIZEOF_ARG] = { name, "=" };
7450 int argc = 1 + (cmd == option_set_command);
7451 int error = ERR;
7453 if (!argv_from_string(argv, &argc, value))
7454 config_msg = "Too many option arguments";
7455 else
7456 error = cmd(argc, argv);
7458 if (error == ERR)
7459 warn("Option 'tig.%s': %s", name, config_msg);
7462 static bool
7463 set_environment_variable(const char *name, const char *value)
7465 size_t len = strlen(name) + 1 + strlen(value) + 1;
7466 char *env = malloc(len);
7468 if (env &&
7469 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7470 putenv(env) == 0)
7471 return TRUE;
7472 free(env);
7473 return FALSE;
7476 static void
7477 set_work_tree(const char *value)
7479 char cwd[SIZEOF_STR];
7481 if (!getcwd(cwd, sizeof(cwd)))
7482 die("Failed to get cwd path: %s", strerror(errno));
7483 if (chdir(opt_git_dir) < 0)
7484 die("Failed to chdir(%s): %s", strerror(errno));
7485 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7486 die("Failed to get git path: %s", strerror(errno));
7487 if (chdir(cwd) < 0)
7488 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7489 if (chdir(value) < 0)
7490 die("Failed to chdir(%s): %s", value, strerror(errno));
7491 if (!getcwd(cwd, sizeof(cwd)))
7492 die("Failed to get cwd path: %s", strerror(errno));
7493 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7494 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7495 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7496 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7497 opt_is_inside_work_tree = TRUE;
7500 static int
7501 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7503 if (!strcmp(name, "i18n.commitencoding"))
7504 string_ncopy(opt_encoding, value, valuelen);
7506 else if (!strcmp(name, "core.editor"))
7507 string_ncopy(opt_editor, value, valuelen);
7509 else if (!strcmp(name, "core.worktree"))
7510 set_work_tree(value);
7512 else if (!prefixcmp(name, "tig.color."))
7513 set_repo_config_option(name + 10, value, option_color_command);
7515 else if (!prefixcmp(name, "tig.bind."))
7516 set_repo_config_option(name + 9, value, option_bind_command);
7518 else if (!prefixcmp(name, "tig."))
7519 set_repo_config_option(name + 4, value, option_set_command);
7521 else if (*opt_head && !prefixcmp(name, "branch.") &&
7522 !strncmp(name + 7, opt_head, strlen(opt_head)))
7523 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7525 return OK;
7528 static int
7529 load_git_config(void)
7531 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7533 return run_io_load(config_list_argv, "=", read_repo_config_option);
7536 static int
7537 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7539 if (!opt_git_dir[0]) {
7540 string_ncopy(opt_git_dir, name, namelen);
7542 } else if (opt_is_inside_work_tree == -1) {
7543 /* This can be 3 different values depending on the
7544 * version of git being used. If git-rev-parse does not
7545 * understand --is-inside-work-tree it will simply echo
7546 * the option else either "true" or "false" is printed.
7547 * Default to true for the unknown case. */
7548 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7550 } else if (*name == '.') {
7551 string_ncopy(opt_cdup, name, namelen);
7553 } else {
7554 string_ncopy(opt_prefix, name, namelen);
7557 return OK;
7560 static int
7561 load_repo_info(void)
7563 const char *rev_parse_argv[] = {
7564 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7565 "--show-cdup", "--show-prefix", NULL
7568 return run_io_load(rev_parse_argv, "=", read_repo_info);
7573 * Main
7576 static const char usage[] =
7577 "tig " TIG_VERSION " (" __DATE__ ")\n"
7578 "\n"
7579 "Usage: tig [options] [revs] [--] [paths]\n"
7580 " or: tig show [options] [revs] [--] [paths]\n"
7581 " or: tig blame [rev] path\n"
7582 " or: tig status\n"
7583 " or: tig < [git command output]\n"
7584 "\n"
7585 "Options:\n"
7586 " -v, --version Show version and exit\n"
7587 " -h, --help Show help message and exit";
7589 static void __NORETURN
7590 quit(int sig)
7592 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7593 if (cursed)
7594 endwin();
7595 exit(0);
7598 static void __NORETURN
7599 die(const char *err, ...)
7601 va_list args;
7603 endwin();
7605 va_start(args, err);
7606 fputs("tig: ", stderr);
7607 vfprintf(stderr, err, args);
7608 fputs("\n", stderr);
7609 va_end(args);
7611 exit(1);
7614 static void
7615 warn(const char *msg, ...)
7617 va_list args;
7619 va_start(args, msg);
7620 fputs("tig warning: ", stderr);
7621 vfprintf(stderr, msg, args);
7622 fputs("\n", stderr);
7623 va_end(args);
7626 static enum request
7627 parse_options(int argc, const char *argv[])
7629 enum request request = REQ_VIEW_MAIN;
7630 const char *subcommand;
7631 bool seen_dashdash = FALSE;
7632 /* XXX: This is vulnerable to the user overriding options
7633 * required for the main view parser. */
7634 const char *custom_argv[SIZEOF_ARG] = {
7635 "git", "log", "--no-color", "--pretty=raw", "--parents",
7636 "--topo-order", NULL
7638 int i, j = 6;
7640 if (!isatty(STDIN_FILENO)) {
7641 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7642 return REQ_VIEW_PAGER;
7645 if (argc <= 1)
7646 return REQ_NONE;
7648 subcommand = argv[1];
7649 if (!strcmp(subcommand, "status")) {
7650 if (argc > 2)
7651 warn("ignoring arguments after `%s'", subcommand);
7652 return REQ_VIEW_STATUS;
7654 } else if (!strcmp(subcommand, "blame")) {
7655 if (argc <= 2 || argc > 4)
7656 die("invalid number of options to blame\n\n%s", usage);
7658 i = 2;
7659 if (argc == 4) {
7660 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7661 i++;
7664 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7665 return REQ_VIEW_BLAME;
7667 } else if (!strcmp(subcommand, "show")) {
7668 request = REQ_VIEW_DIFF;
7670 } else {
7671 subcommand = NULL;
7674 if (subcommand) {
7675 custom_argv[1] = subcommand;
7676 j = 2;
7679 for (i = 1 + !!subcommand; i < argc; i++) {
7680 const char *opt = argv[i];
7682 if (seen_dashdash || !strcmp(opt, "--")) {
7683 seen_dashdash = TRUE;
7685 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7686 printf("tig version %s\n", TIG_VERSION);
7687 quit(0);
7689 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7690 printf("%s\n", usage);
7691 quit(0);
7694 custom_argv[j++] = opt;
7695 if (j >= ARRAY_SIZE(custom_argv))
7696 die("command too long");
7699 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7700 die("Failed to format arguments");
7702 return request;
7706 main(int argc, const char *argv[])
7708 enum request request = parse_options(argc, argv);
7709 struct view *view;
7710 size_t i;
7712 signal(SIGINT, quit);
7713 signal(SIGPIPE, SIG_IGN);
7715 if (setlocale(LC_ALL, "")) {
7716 char *codeset = nl_langinfo(CODESET);
7718 string_ncopy(opt_codeset, codeset, strlen(codeset));
7721 if (load_repo_info() == ERR)
7722 die("Failed to load repo info.");
7724 if (load_options() == ERR)
7725 die("Failed to load user config.");
7727 if (load_git_config() == ERR)
7728 die("Failed to load repo config.");
7730 /* Require a git repository unless when running in pager mode. */
7731 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7732 die("Not a git repository");
7734 if (*opt_encoding && strcmp(opt_codeset, "UTF-8")) {
7735 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7736 if (opt_iconv_in == ICONV_NONE)
7737 die("Failed to initialize character set conversion");
7740 if (*opt_codeset && strcmp(opt_codeset, "UTF-8")) {
7741 opt_iconv_out = iconv_open(opt_codeset, "UTF-8");
7742 if (opt_iconv_out == ICONV_NONE)
7743 die("Failed to initialize character set conversion");
7746 if (load_refs() == ERR)
7747 die("Failed to load refs.");
7749 foreach_view (view, i)
7750 argv_from_env(view->ops->argv, view->cmd_env);
7752 init_display();
7754 if (request != REQ_NONE)
7755 open_view(NULL, request, OPEN_PREPARED);
7756 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7758 while (view_driver(display[current_view], request)) {
7759 int key = get_input(0);
7761 view = display[current_view];
7762 request = get_keybinding(view->keymap, key);
7764 /* Some low-level request handling. This keeps access to
7765 * status_win restricted. */
7766 switch (request) {
7767 case REQ_PROMPT:
7769 char *cmd = read_prompt(":");
7771 if (cmd && isdigit(*cmd)) {
7772 int lineno = view->lineno + 1;
7774 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7775 select_view_line(view, lineno - 1);
7776 report("");
7777 } else {
7778 report("Unable to parse '%s' as a line number", cmd);
7781 } else if (cmd) {
7782 struct view *next = VIEW(REQ_VIEW_PAGER);
7783 const char *argv[SIZEOF_ARG] = { "git" };
7784 int argc = 1;
7786 /* When running random commands, initially show the
7787 * command in the title. However, it maybe later be
7788 * overwritten if a commit line is selected. */
7789 string_ncopy(next->ref, cmd, strlen(cmd));
7791 if (!argv_from_string(argv, &argc, cmd)) {
7792 report("Too many arguments");
7793 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7794 report("Failed to format command");
7795 } else {
7796 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7800 request = REQ_NONE;
7801 break;
7803 case REQ_SEARCH:
7804 case REQ_SEARCH_BACK:
7806 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7807 char *search = read_prompt(prompt);
7809 if (search)
7810 string_ncopy(opt_search, search, strlen(search));
7811 else if (*opt_search)
7812 request = request == REQ_SEARCH ?
7813 REQ_FIND_NEXT :
7814 REQ_FIND_PREV;
7815 else
7816 request = REQ_NONE;
7817 break;
7819 default:
7820 break;
7824 quit(0);
7826 return 0;