Move nodelay logic to the get_input read loop
[tig.git] / tig.c
blob54d99a721378cee897c49df99e728b61357dba55
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
71 static size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size);
72 static inline unsigned char utf8_char_length(const char *string, const char *end);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS 8
110 #define AUTHOR_COLS 19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_TAB '\t'
120 #define KEY_RETURN '\r'
121 #define KEY_ESC 27
124 struct ref {
125 char id[SIZEOF_REV]; /* Commit SHA1 ID */
126 unsigned int head:1; /* Is it the current HEAD? */
127 unsigned int tag:1; /* Is it a tag? */
128 unsigned int ltag:1; /* If so, is the tag local? */
129 unsigned int remote:1; /* Is it a remote ref? */
130 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
131 char name[1]; /* Ref name; tag or head names are shortened. */
134 struct ref_list {
135 char id[SIZEOF_REV]; /* Commit SHA1 ID */
136 size_t size; /* Number of refs. */
137 struct ref **refs; /* References for this ID. */
140 static struct ref *get_ref_head();
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166 int hotkey;
167 const char *text;
168 void *data;
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
174 * Allocation helpers ... Entering macro hell to never be seen again.
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 static type * \
179 name(type **mem, size_t size, size_t increase) \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 type *tmp = *mem; \
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187 if (tmp) \
188 *mem = tmp; \
191 return tmp; \
195 * String helpers
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
201 if (srclen > dstlen - 1)
202 srclen = dstlen - 1;
204 strncpy(dst, src, srclen);
205 dst[srclen] = 0;
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
225 size_t size, pos;
227 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228 if (src[pos] == '\t') {
229 size_t expanded = tabsize - (size % tabsize);
231 if (expanded + size >= dstlen - 1)
232 expanded = dstlen - size - 1;
233 memcpy(dst + size, " ", expanded);
234 size += expanded;
235 } else {
236 dst[size++] = src[pos];
240 dst[size] = 0;
243 static char *
244 chomp_string(char *name)
246 int namelen;
248 while (isspace(*name))
249 name++;
251 namelen = strlen(name) - 1;
252 while (namelen > 0 && isspace(name[namelen]))
253 name[namelen--] = 0;
255 return name;
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
261 va_list args;
262 size_t pos = bufpos ? *bufpos : 0;
264 va_start(args, fmt);
265 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266 va_end(args);
268 if (bufpos)
269 *bufpos = pos;
271 return pos >= bufsize ? FALSE : TRUE;
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
283 size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i = 0; i < len; i++) {
289 if (toupper(str1[i]) == toupper(str2[i]))
290 continue;
292 if (string_enum_sep(str1[i]) &&
293 string_enum_sep(str2[i]))
294 continue;
296 return str1[i] - str2[i];
299 return 0;
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306 const char *name;
307 int namelen;
308 int value;
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
316 static char buf[SIZEOF_STR];
317 int bufpos;
319 for (bufpos = 0; bufpos <= namelen; bufpos++) {
320 buf[bufpos] = tolower(name[bufpos]);
321 if (buf[bufpos] == '_')
322 buf[bufpos] = '-';
325 buf[bufpos] = 0;
326 return buf;
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
334 size_t namelen = strlen(name);
335 int i;
337 for (i = 0; i < map_size; i++)
338 if (enum_equals(map[i], name, namelen)) {
339 *value = map[i].value;
340 return TRUE;
343 return FALSE;
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
355 size_t len = slen >= 0 ? slen : strlen(str);
356 size_t suffixlen = strlen(suffix);
358 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
362 #define DATE_INFO \
363 DATE_(NO), \
364 DATE_(DEFAULT), \
365 DATE_(RELATIVE), \
366 DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370 DATE_INFO
371 #undef DATE_
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376 DATE_INFO
377 #undef DATE_
380 struct time {
381 time_t sec;
382 int tz;
385 static inline int timecmp(const struct time *t1, const struct time *t2)
387 return t1->sec - t2->sec;
390 static const char *
391 string_date(const struct time *time, enum date date)
393 static char buf[DATE_COLS + 1];
394 static const struct enum_map reldate[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
402 struct tm tm;
404 if (date == DATE_RELATIVE) {
405 struct timeval now;
406 time_t date = time->sec + time->tz;
407 time_t seconds;
408 int i;
410 gettimeofday(&now, NULL);
411 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
412 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
413 if (seconds >= reldate[i].value)
414 continue;
416 seconds /= reldate[i].namelen;
417 if (!string_format(buf, "%ld %s%s %s",
418 seconds, reldate[i].name,
419 seconds > 1 ? "s" : "",
420 now.tv_sec >= date ? "ago" : "ahead"))
421 break;
422 return buf;
426 gmtime_r(&time->sec, &tm);
427 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
431 #define AUTHOR_VALUES \
432 AUTHOR_(NO), \
433 AUTHOR_(FULL), \
434 AUTHOR_(ABBREVIATED)
436 enum author {
437 #define AUTHOR_(name) AUTHOR_##name
438 AUTHOR_VALUES,
439 #undef AUTHOR_
440 AUTHOR_DEFAULT = AUTHOR_FULL
443 static const struct enum_map author_map[] = {
444 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
445 AUTHOR_VALUES
446 #undef AUTHOR_
449 static const char *
450 get_author_initials(const char *author)
452 static char initials[AUTHOR_COLS * 6 + 1];
453 size_t pos = 0;
454 const char *end = strchr(author, '\0');
456 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
458 memset(initials, 0, sizeof(initials));
459 while (author < end) {
460 unsigned char bytes;
461 size_t i;
463 while (is_initial_sep(*author))
464 author++;
466 bytes = utf8_char_length(author, end);
467 if (bytes < sizeof(initials) - 1 - pos) {
468 while (bytes--) {
469 initials[pos++] = *author++;
473 for (i = pos; author < end && !is_initial_sep(*author); author++) {
474 if (i < sizeof(initials) - 1)
475 initials[i++] = *author;
478 initials[i++] = 0;
481 return initials;
485 static bool
486 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
488 int valuelen;
490 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
491 bool advance = cmd[valuelen] != 0;
493 cmd[valuelen] = 0;
494 argv[(*argc)++] = chomp_string(cmd);
495 cmd = chomp_string(cmd + valuelen + advance);
498 if (*argc < SIZEOF_ARG)
499 argv[*argc] = NULL;
500 return *argc < SIZEOF_ARG;
503 static void
504 argv_from_env(const char **argv, const char *name)
506 char *env = argv ? getenv(name) : NULL;
507 int argc = 0;
509 if (env && *env)
510 env = strdup(env);
511 if (env && !argv_from_string(argv, &argc, env))
512 die("Too many arguments in the `%s` environment variable", name);
517 * Executing external commands.
520 enum io_type {
521 IO_FD, /* File descriptor based IO. */
522 IO_BG, /* Execute command in the background. */
523 IO_FG, /* Execute command with same std{in,out,err}. */
524 IO_RD, /* Read only fork+exec IO. */
525 IO_WR, /* Write only fork+exec IO. */
526 IO_AP, /* Append fork+exec output to file. */
529 struct io {
530 enum io_type type; /* The requested type of pipe. */
531 const char *dir; /* Directory from which to execute. */
532 pid_t pid; /* Pipe for reading or writing. */
533 int pipe; /* Pipe end for reading or writing. */
534 int error; /* Error status. */
535 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
536 char *buf; /* Read buffer. */
537 size_t bufalloc; /* Allocated buffer size. */
538 size_t bufsize; /* Buffer content size. */
539 char *bufpos; /* Current buffer position. */
540 unsigned int eof:1; /* Has end of file been reached. */
543 static void
544 reset_io(struct io *io)
546 io->pipe = -1;
547 io->pid = 0;
548 io->buf = io->bufpos = NULL;
549 io->bufalloc = io->bufsize = 0;
550 io->error = 0;
551 io->eof = 0;
554 static void
555 init_io(struct io *io, const char *dir, enum io_type type)
557 reset_io(io);
558 io->type = type;
559 io->dir = dir;
562 static bool
563 init_io_rd(struct io *io, const char *argv[], const char *dir,
564 enum format_flags flags)
566 init_io(io, dir, IO_RD);
567 return format_argv(io->argv, argv, flags);
570 static bool
571 io_open(struct io *io, const char *fmt, ...)
573 char name[SIZEOF_STR] = "";
574 bool fits;
575 va_list args;
577 init_io(io, NULL, IO_FD);
579 va_start(args, fmt);
580 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
581 va_end(args);
583 if (!fits) {
584 io->error = ENAMETOOLONG;
585 return FALSE;
587 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
588 if (io->pipe == -1)
589 io->error = errno;
590 return io->pipe != -1;
593 static bool
594 kill_io(struct io *io)
596 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
599 static bool
600 done_io(struct io *io)
602 pid_t pid = io->pid;
604 if (io->pipe != -1)
605 close(io->pipe);
606 free(io->buf);
607 reset_io(io);
609 while (pid > 0) {
610 int status;
611 pid_t waiting = waitpid(pid, &status, 0);
613 if (waiting < 0) {
614 if (errno == EINTR)
615 continue;
616 report("waitpid failed (%s)", strerror(errno));
617 return FALSE;
620 return waiting == pid &&
621 !WIFSIGNALED(status) &&
622 WIFEXITED(status) &&
623 !WEXITSTATUS(status);
626 return TRUE;
629 static bool
630 start_io(struct io *io)
632 int pipefds[2] = { -1, -1 };
634 if (io->type == IO_FD)
635 return TRUE;
637 if ((io->type == IO_RD || io->type == IO_WR) &&
638 pipe(pipefds) < 0)
639 return FALSE;
640 else if (io->type == IO_AP)
641 pipefds[1] = io->pipe;
643 if ((io->pid = fork())) {
644 if (pipefds[!(io->type == IO_WR)] != -1)
645 close(pipefds[!(io->type == IO_WR)]);
646 if (io->pid != -1) {
647 io->pipe = pipefds[!!(io->type == IO_WR)];
648 return TRUE;
651 } else {
652 if (io->type != IO_FG) {
653 int devnull = open("/dev/null", O_RDWR);
654 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
655 int writefd = (io->type == IO_RD || io->type == IO_AP)
656 ? pipefds[1] : devnull;
658 dup2(readfd, STDIN_FILENO);
659 dup2(writefd, STDOUT_FILENO);
660 dup2(devnull, STDERR_FILENO);
662 close(devnull);
663 if (pipefds[0] != -1)
664 close(pipefds[0]);
665 if (pipefds[1] != -1)
666 close(pipefds[1]);
669 if (io->dir && *io->dir && chdir(io->dir) == -1)
670 die("Failed to change directory: %s", strerror(errno));
672 execvp(io->argv[0], (char *const*) io->argv);
673 die("Failed to execute program: %s", strerror(errno));
676 if (pipefds[!!(io->type == IO_WR)] != -1)
677 close(pipefds[!!(io->type == IO_WR)]);
678 return FALSE;
681 static bool
682 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
684 init_io(io, dir, type);
685 if (!format_argv(io->argv, argv, FORMAT_NONE))
686 return FALSE;
687 return start_io(io);
690 static int
691 run_io_do(struct io *io)
693 return start_io(io) && done_io(io);
696 static int
697 run_io_bg(const char **argv)
699 struct io io = {};
701 init_io(&io, NULL, IO_BG);
702 if (!format_argv(io.argv, argv, FORMAT_NONE))
703 return FALSE;
704 return run_io_do(&io);
707 static bool
708 run_io_fg(const char **argv, const char *dir)
710 struct io io = {};
712 init_io(&io, dir, IO_FG);
713 if (!format_argv(io.argv, argv, FORMAT_NONE))
714 return FALSE;
715 return run_io_do(&io);
718 static bool
719 run_io_append(const char **argv, enum format_flags flags, int fd)
721 struct io io = {};
723 init_io(&io, NULL, IO_AP);
724 io.pipe = fd;
725 if (format_argv(io.argv, argv, flags))
726 return run_io_do(&io);
727 close(fd);
728 return FALSE;
731 static bool
732 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
734 return init_io_rd(io, argv, dir, flags) && start_io(io);
737 static bool
738 io_eof(struct io *io)
740 return io->eof;
743 static int
744 io_error(struct io *io)
746 return io->error;
749 static char *
750 io_strerror(struct io *io)
752 return strerror(io->error);
755 static bool
756 io_can_read(struct io *io)
758 struct timeval tv = { 0, 500 };
759 fd_set fds;
761 FD_ZERO(&fds);
762 FD_SET(io->pipe, &fds);
764 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
767 static ssize_t
768 io_read(struct io *io, void *buf, size_t bufsize)
770 do {
771 ssize_t readsize = read(io->pipe, buf, bufsize);
773 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
774 continue;
775 else if (readsize == -1)
776 io->error = errno;
777 else if (readsize == 0)
778 io->eof = 1;
779 return readsize;
780 } while (1);
783 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
785 static char *
786 io_get(struct io *io, int c, bool can_read)
788 char *eol;
789 ssize_t readsize;
791 while (TRUE) {
792 if (io->bufsize > 0) {
793 eol = memchr(io->bufpos, c, io->bufsize);
794 if (eol) {
795 char *line = io->bufpos;
797 *eol = 0;
798 io->bufpos = eol + 1;
799 io->bufsize -= io->bufpos - line;
800 return line;
804 if (io_eof(io)) {
805 if (io->bufsize) {
806 io->bufpos[io->bufsize] = 0;
807 io->bufsize = 0;
808 return io->bufpos;
810 return NULL;
813 if (!can_read)
814 return NULL;
816 if (io->bufsize > 0 && io->bufpos > io->buf)
817 memmove(io->buf, io->bufpos, io->bufsize);
819 if (io->bufalloc == io->bufsize) {
820 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
821 return NULL;
822 io->bufalloc += BUFSIZ;
825 io->bufpos = io->buf;
826 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
827 if (io_error(io))
828 return NULL;
829 io->bufsize += readsize;
833 static bool
834 io_write(struct io *io, const void *buf, size_t bufsize)
836 size_t written = 0;
838 while (!io_error(io) && written < bufsize) {
839 ssize_t size;
841 size = write(io->pipe, buf + written, bufsize - written);
842 if (size < 0 && (errno == EAGAIN || errno == EINTR))
843 continue;
844 else if (size == -1)
845 io->error = errno;
846 else
847 written += size;
850 return written == bufsize;
853 static bool
854 io_read_buf(struct io *io, char buf[], size_t bufsize)
856 char *result = io_get(io, '\n', TRUE);
858 if (result) {
859 result = chomp_string(result);
860 string_ncopy_do(buf, bufsize, result, strlen(result));
863 return done_io(io) && result;
866 static bool
867 run_io_buf(const char **argv, char buf[], size_t bufsize)
869 struct io io = {};
871 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
872 && io_read_buf(&io, buf, bufsize);
875 static int
876 io_load(struct io *io, const char *separators,
877 int (*read_property)(char *, size_t, char *, size_t))
879 char *name;
880 int state = OK;
882 if (!start_io(io))
883 return ERR;
885 while (state == OK && (name = io_get(io, '\n', TRUE))) {
886 char *value;
887 size_t namelen;
888 size_t valuelen;
890 name = chomp_string(name);
891 namelen = strcspn(name, separators);
893 if (name[namelen]) {
894 name[namelen] = 0;
895 value = chomp_string(name + namelen + 1);
896 valuelen = strlen(value);
898 } else {
899 value = "";
900 valuelen = 0;
903 state = read_property(name, namelen, value, valuelen);
906 if (state != ERR && io_error(io))
907 state = ERR;
908 done_io(io);
910 return state;
913 static int
914 run_io_load(const char **argv, const char *separators,
915 int (*read_property)(char *, size_t, char *, size_t))
917 struct io io = {};
919 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
920 ? io_load(&io, separators, read_property) : ERR;
925 * User requests
928 #define REQ_INFO \
929 /* XXX: Keep the view request first and in sync with views[]. */ \
930 REQ_GROUP("View switching") \
931 REQ_(VIEW_MAIN, "Show main view"), \
932 REQ_(VIEW_DIFF, "Show diff view"), \
933 REQ_(VIEW_LOG, "Show log view"), \
934 REQ_(VIEW_TREE, "Show tree view"), \
935 REQ_(VIEW_BLOB, "Show blob view"), \
936 REQ_(VIEW_BLAME, "Show blame view"), \
937 REQ_(VIEW_BRANCH, "Show branch view"), \
938 REQ_(VIEW_HELP, "Show help page"), \
939 REQ_(VIEW_PAGER, "Show pager view"), \
940 REQ_(VIEW_STATUS, "Show status view"), \
941 REQ_(VIEW_STAGE, "Show stage view"), \
943 REQ_GROUP("View manipulation") \
944 REQ_(ENTER, "Enter current line and scroll"), \
945 REQ_(NEXT, "Move to next"), \
946 REQ_(PREVIOUS, "Move to previous"), \
947 REQ_(PARENT, "Move to parent"), \
948 REQ_(VIEW_NEXT, "Move focus to next view"), \
949 REQ_(REFRESH, "Reload and refresh"), \
950 REQ_(MAXIMIZE, "Maximize the current view"), \
951 REQ_(VIEW_CLOSE, "Close the current view"), \
952 REQ_(QUIT, "Close all views and quit"), \
954 REQ_GROUP("View specific requests") \
955 REQ_(STATUS_UPDATE, "Update file status"), \
956 REQ_(STATUS_REVERT, "Revert file changes"), \
957 REQ_(STATUS_MERGE, "Merge file using external tool"), \
958 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
960 REQ_GROUP("Cursor navigation") \
961 REQ_(MOVE_UP, "Move cursor one line up"), \
962 REQ_(MOVE_DOWN, "Move cursor one line down"), \
963 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
964 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
965 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
966 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
968 REQ_GROUP("Scrolling") \
969 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
970 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
971 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
972 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
973 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
974 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
976 REQ_GROUP("Searching") \
977 REQ_(SEARCH, "Search the view"), \
978 REQ_(SEARCH_BACK, "Search backwards in the view"), \
979 REQ_(FIND_NEXT, "Find next search match"), \
980 REQ_(FIND_PREV, "Find previous search match"), \
982 REQ_GROUP("Option manipulation") \
983 REQ_(OPTIONS, "Open option menu"), \
984 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
985 REQ_(TOGGLE_DATE, "Toggle date display"), \
986 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
987 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
988 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
989 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
990 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
991 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
993 REQ_GROUP("Misc") \
994 REQ_(PROMPT, "Bring up the prompt"), \
995 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
996 REQ_(SHOW_VERSION, "Show version information"), \
997 REQ_(STOP_LOADING, "Stop all loading views"), \
998 REQ_(EDIT, "Open in editor"), \
999 REQ_(NONE, "Do nothing")
1002 /* User action requests. */
1003 enum request {
1004 #define REQ_GROUP(help)
1005 #define REQ_(req, help) REQ_##req
1007 /* Offset all requests to avoid conflicts with ncurses getch values. */
1008 REQ_OFFSET = KEY_MAX + 1,
1009 REQ_INFO
1011 #undef REQ_GROUP
1012 #undef REQ_
1015 struct request_info {
1016 enum request request;
1017 const char *name;
1018 int namelen;
1019 const char *help;
1022 static const struct request_info req_info[] = {
1023 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1024 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1025 REQ_INFO
1026 #undef REQ_GROUP
1027 #undef REQ_
1030 static enum request
1031 get_request(const char *name)
1033 int namelen = strlen(name);
1034 int i;
1036 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1037 if (enum_equals(req_info[i], name, namelen))
1038 return req_info[i].request;
1040 return REQ_NONE;
1045 * Options
1048 /* Option and state variables. */
1049 static enum date opt_date = DATE_DEFAULT;
1050 static enum author opt_author = AUTHOR_DEFAULT;
1051 static bool opt_line_number = FALSE;
1052 static bool opt_line_graphics = TRUE;
1053 static bool opt_rev_graph = FALSE;
1054 static bool opt_show_refs = TRUE;
1055 static int opt_num_interval = 5;
1056 static double opt_hscroll = 0.50;
1057 static double opt_scale_split_view = 2.0 / 3.0;
1058 static int opt_tab_size = 8;
1059 static int opt_author_cols = AUTHOR_COLS;
1060 static char opt_path[SIZEOF_STR] = "";
1061 static char opt_file[SIZEOF_STR] = "";
1062 static char opt_ref[SIZEOF_REF] = "";
1063 static char opt_head[SIZEOF_REF] = "";
1064 static char opt_remote[SIZEOF_REF] = "";
1065 static char opt_encoding[20] = "UTF-8";
1066 static iconv_t opt_iconv_in = ICONV_NONE;
1067 static iconv_t opt_iconv_out = ICONV_NONE;
1068 static char opt_search[SIZEOF_STR] = "";
1069 static char opt_cdup[SIZEOF_STR] = "";
1070 static char opt_prefix[SIZEOF_STR] = "";
1071 static char opt_git_dir[SIZEOF_STR] = "";
1072 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1073 static char opt_editor[SIZEOF_STR] = "";
1074 static FILE *opt_tty = NULL;
1076 #define is_initial_commit() (!get_ref_head())
1077 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1078 #define mkdate(time) string_date(time, opt_date)
1082 * Line-oriented content detection.
1085 #define LINE_INFO \
1086 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1087 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1088 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1100 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1101 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1102 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1103 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1104 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1105 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1107 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1108 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1109 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1110 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1111 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1112 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1113 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1114 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1115 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1116 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1117 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1118 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1119 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1120 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1121 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1122 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1123 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1124 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1125 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1127 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1128 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1131 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1132 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1133 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1134 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1135 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1136 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1137 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1138 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1139 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1141 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1142 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1144 enum line_type {
1145 #define LINE(type, line, fg, bg, attr) \
1146 LINE_##type
1147 LINE_INFO,
1148 LINE_NONE
1149 #undef LINE
1152 struct line_info {
1153 const char *name; /* Option name. */
1154 int namelen; /* Size of option name. */
1155 const char *line; /* The start of line to match. */
1156 int linelen; /* Size of string to match. */
1157 int fg, bg, attr; /* Color and text attributes for the lines. */
1160 static struct line_info line_info[] = {
1161 #define LINE(type, line, fg, bg, attr) \
1162 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1163 LINE_INFO
1164 #undef LINE
1167 static enum line_type
1168 get_line_type(const char *line)
1170 int linelen = strlen(line);
1171 enum line_type type;
1173 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1174 /* Case insensitive search matches Signed-off-by lines better. */
1175 if (linelen >= line_info[type].linelen &&
1176 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1177 return type;
1179 return LINE_DEFAULT;
1182 static inline int
1183 get_line_attr(enum line_type type)
1185 assert(type < ARRAY_SIZE(line_info));
1186 return COLOR_PAIR(type) | line_info[type].attr;
1189 static struct line_info *
1190 get_line_info(const char *name)
1192 size_t namelen = strlen(name);
1193 enum line_type type;
1195 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1196 if (enum_equals(line_info[type], name, namelen))
1197 return &line_info[type];
1199 return NULL;
1202 static void
1203 init_colors(void)
1205 int default_bg = line_info[LINE_DEFAULT].bg;
1206 int default_fg = line_info[LINE_DEFAULT].fg;
1207 enum line_type type;
1209 start_color();
1211 if (assume_default_colors(default_fg, default_bg) == ERR) {
1212 default_bg = COLOR_BLACK;
1213 default_fg = COLOR_WHITE;
1216 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1217 struct line_info *info = &line_info[type];
1218 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1219 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1221 init_pair(type, fg, bg);
1225 struct line {
1226 enum line_type type;
1228 /* State flags */
1229 unsigned int selected:1;
1230 unsigned int dirty:1;
1231 unsigned int cleareol:1;
1232 unsigned int other:16;
1234 void *data; /* User data */
1239 * Keys
1242 struct keybinding {
1243 int alias;
1244 enum request request;
1247 static const struct keybinding default_keybindings[] = {
1248 /* View switching */
1249 { 'm', REQ_VIEW_MAIN },
1250 { 'd', REQ_VIEW_DIFF },
1251 { 'l', REQ_VIEW_LOG },
1252 { 't', REQ_VIEW_TREE },
1253 { 'f', REQ_VIEW_BLOB },
1254 { 'B', REQ_VIEW_BLAME },
1255 { 'H', REQ_VIEW_BRANCH },
1256 { 'p', REQ_VIEW_PAGER },
1257 { 'h', REQ_VIEW_HELP },
1258 { 'S', REQ_VIEW_STATUS },
1259 { 'c', REQ_VIEW_STAGE },
1261 /* View manipulation */
1262 { 'q', REQ_VIEW_CLOSE },
1263 { KEY_TAB, REQ_VIEW_NEXT },
1264 { KEY_RETURN, REQ_ENTER },
1265 { KEY_UP, REQ_PREVIOUS },
1266 { KEY_DOWN, REQ_NEXT },
1267 { 'R', REQ_REFRESH },
1268 { KEY_F(5), REQ_REFRESH },
1269 { 'O', REQ_MAXIMIZE },
1271 /* Cursor navigation */
1272 { 'k', REQ_MOVE_UP },
1273 { 'j', REQ_MOVE_DOWN },
1274 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1275 { KEY_END, REQ_MOVE_LAST_LINE },
1276 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1277 { ' ', REQ_MOVE_PAGE_DOWN },
1278 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1279 { 'b', REQ_MOVE_PAGE_UP },
1280 { '-', REQ_MOVE_PAGE_UP },
1282 /* Scrolling */
1283 { KEY_LEFT, REQ_SCROLL_LEFT },
1284 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1285 { KEY_IC, REQ_SCROLL_LINE_UP },
1286 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1287 { 'w', REQ_SCROLL_PAGE_UP },
1288 { 's', REQ_SCROLL_PAGE_DOWN },
1290 /* Searching */
1291 { '/', REQ_SEARCH },
1292 { '?', REQ_SEARCH_BACK },
1293 { 'n', REQ_FIND_NEXT },
1294 { 'N', REQ_FIND_PREV },
1296 /* Misc */
1297 { 'Q', REQ_QUIT },
1298 { 'z', REQ_STOP_LOADING },
1299 { 'v', REQ_SHOW_VERSION },
1300 { 'r', REQ_SCREEN_REDRAW },
1301 { 'o', REQ_OPTIONS },
1302 { '.', REQ_TOGGLE_LINENO },
1303 { 'D', REQ_TOGGLE_DATE },
1304 { 'A', REQ_TOGGLE_AUTHOR },
1305 { 'g', REQ_TOGGLE_REV_GRAPH },
1306 { 'F', REQ_TOGGLE_REFS },
1307 { 'I', REQ_TOGGLE_SORT_ORDER },
1308 { 'i', REQ_TOGGLE_SORT_FIELD },
1309 { ':', REQ_PROMPT },
1310 { 'u', REQ_STATUS_UPDATE },
1311 { '!', REQ_STATUS_REVERT },
1312 { 'M', REQ_STATUS_MERGE },
1313 { '@', REQ_STAGE_NEXT },
1314 { ',', REQ_PARENT },
1315 { 'e', REQ_EDIT },
1318 #define KEYMAP_INFO \
1319 KEYMAP_(GENERIC), \
1320 KEYMAP_(MAIN), \
1321 KEYMAP_(DIFF), \
1322 KEYMAP_(LOG), \
1323 KEYMAP_(TREE), \
1324 KEYMAP_(BLOB), \
1325 KEYMAP_(BLAME), \
1326 KEYMAP_(BRANCH), \
1327 KEYMAP_(PAGER), \
1328 KEYMAP_(HELP), \
1329 KEYMAP_(STATUS), \
1330 KEYMAP_(STAGE)
1332 enum keymap {
1333 #define KEYMAP_(name) KEYMAP_##name
1334 KEYMAP_INFO
1335 #undef KEYMAP_
1338 static const struct enum_map keymap_table[] = {
1339 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1340 KEYMAP_INFO
1341 #undef KEYMAP_
1344 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1346 struct keybinding_table {
1347 struct keybinding *data;
1348 size_t size;
1351 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1353 static void
1354 add_keybinding(enum keymap keymap, enum request request, int key)
1356 struct keybinding_table *table = &keybindings[keymap];
1358 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1359 if (!table->data)
1360 die("Failed to allocate keybinding");
1361 table->data[table->size].alias = key;
1362 table->data[table->size++].request = request;
1365 /* Looks for a key binding first in the given map, then in the generic map, and
1366 * lastly in the default keybindings. */
1367 static enum request
1368 get_keybinding(enum keymap keymap, int key)
1370 size_t i;
1372 for (i = 0; i < keybindings[keymap].size; i++)
1373 if (keybindings[keymap].data[i].alias == key)
1374 return keybindings[keymap].data[i].request;
1376 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1377 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1378 return keybindings[KEYMAP_GENERIC].data[i].request;
1380 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1381 if (default_keybindings[i].alias == key)
1382 return default_keybindings[i].request;
1384 return (enum request) key;
1388 struct key {
1389 const char *name;
1390 int value;
1393 static const struct key key_table[] = {
1394 { "Enter", KEY_RETURN },
1395 { "Space", ' ' },
1396 { "Backspace", KEY_BACKSPACE },
1397 { "Tab", KEY_TAB },
1398 { "Escape", KEY_ESC },
1399 { "Left", KEY_LEFT },
1400 { "Right", KEY_RIGHT },
1401 { "Up", KEY_UP },
1402 { "Down", KEY_DOWN },
1403 { "Insert", KEY_IC },
1404 { "Delete", KEY_DC },
1405 { "Hash", '#' },
1406 { "Home", KEY_HOME },
1407 { "End", KEY_END },
1408 { "PageUp", KEY_PPAGE },
1409 { "PageDown", KEY_NPAGE },
1410 { "F1", KEY_F(1) },
1411 { "F2", KEY_F(2) },
1412 { "F3", KEY_F(3) },
1413 { "F4", KEY_F(4) },
1414 { "F5", KEY_F(5) },
1415 { "F6", KEY_F(6) },
1416 { "F7", KEY_F(7) },
1417 { "F8", KEY_F(8) },
1418 { "F9", KEY_F(9) },
1419 { "F10", KEY_F(10) },
1420 { "F11", KEY_F(11) },
1421 { "F12", KEY_F(12) },
1424 static int
1425 get_key_value(const char *name)
1427 int i;
1429 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1430 if (!strcasecmp(key_table[i].name, name))
1431 return key_table[i].value;
1433 if (strlen(name) == 1 && isprint(*name))
1434 return (int) *name;
1436 return ERR;
1439 static const char *
1440 get_key_name(int key_value)
1442 static char key_char[] = "'X'";
1443 const char *seq = NULL;
1444 int key;
1446 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1447 if (key_table[key].value == key_value)
1448 seq = key_table[key].name;
1450 if (seq == NULL &&
1451 key_value < 127 &&
1452 isprint(key_value)) {
1453 key_char[1] = (char) key_value;
1454 seq = key_char;
1457 return seq ? seq : "(no key)";
1460 static bool
1461 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1463 const char *sep = *pos > 0 ? ", " : "";
1464 const char *keyname = get_key_name(keybinding->alias);
1466 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1469 static bool
1470 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1471 enum keymap keymap, bool all)
1473 int i;
1475 for (i = 0; i < keybindings[keymap].size; i++) {
1476 if (keybindings[keymap].data[i].request == request) {
1477 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1478 return FALSE;
1479 if (!all)
1480 break;
1484 return TRUE;
1487 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1489 static const char *
1490 get_keys(enum keymap keymap, enum request request, bool all)
1492 static char buf[BUFSIZ];
1493 size_t pos = 0;
1494 int i;
1496 buf[pos] = 0;
1498 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1499 return "Too many keybindings!";
1500 if (pos > 0 && !all)
1501 return buf;
1503 if (keymap != KEYMAP_GENERIC) {
1504 /* Only the generic keymap includes the default keybindings when
1505 * listing all keys. */
1506 if (all)
1507 return buf;
1509 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1510 return "Too many keybindings!";
1511 if (pos)
1512 return buf;
1515 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1516 if (default_keybindings[i].request == request) {
1517 if (!append_key(buf, &pos, &default_keybindings[i]))
1518 return "Too many keybindings!";
1519 if (!all)
1520 return buf;
1524 return buf;
1527 struct run_request {
1528 enum keymap keymap;
1529 int key;
1530 const char *argv[SIZEOF_ARG];
1533 static struct run_request *run_request;
1534 static size_t run_requests;
1536 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1538 static enum request
1539 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1541 struct run_request *req;
1543 if (argc >= ARRAY_SIZE(req->argv) - 1)
1544 return REQ_NONE;
1546 if (!realloc_run_requests(&run_request, run_requests, 1))
1547 return REQ_NONE;
1549 req = &run_request[run_requests];
1550 req->keymap = keymap;
1551 req->key = key;
1552 req->argv[0] = NULL;
1554 if (!format_argv(req->argv, argv, FORMAT_NONE))
1555 return REQ_NONE;
1557 return REQ_NONE + ++run_requests;
1560 static struct run_request *
1561 get_run_request(enum request request)
1563 if (request <= REQ_NONE)
1564 return NULL;
1565 return &run_request[request - REQ_NONE - 1];
1568 static void
1569 add_builtin_run_requests(void)
1571 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1572 const char *commit[] = { "git", "commit", NULL };
1573 const char *gc[] = { "git", "gc", NULL };
1574 struct {
1575 enum keymap keymap;
1576 int key;
1577 int argc;
1578 const char **argv;
1579 } reqs[] = {
1580 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1581 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1582 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1584 int i;
1586 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1587 enum request req;
1589 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1590 if (req != REQ_NONE)
1591 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1596 * User config file handling.
1599 static int config_lineno;
1600 static bool config_errors;
1601 static const char *config_msg;
1603 static const struct enum_map color_map[] = {
1604 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1605 COLOR_MAP(DEFAULT),
1606 COLOR_MAP(BLACK),
1607 COLOR_MAP(BLUE),
1608 COLOR_MAP(CYAN),
1609 COLOR_MAP(GREEN),
1610 COLOR_MAP(MAGENTA),
1611 COLOR_MAP(RED),
1612 COLOR_MAP(WHITE),
1613 COLOR_MAP(YELLOW),
1616 static const struct enum_map attr_map[] = {
1617 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1618 ATTR_MAP(NORMAL),
1619 ATTR_MAP(BLINK),
1620 ATTR_MAP(BOLD),
1621 ATTR_MAP(DIM),
1622 ATTR_MAP(REVERSE),
1623 ATTR_MAP(STANDOUT),
1624 ATTR_MAP(UNDERLINE),
1627 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1629 static int parse_step(double *opt, const char *arg)
1631 *opt = atoi(arg);
1632 if (!strchr(arg, '%'))
1633 return OK;
1635 /* "Shift down" so 100% and 1 does not conflict. */
1636 *opt = (*opt - 1) / 100;
1637 if (*opt >= 1.0) {
1638 *opt = 0.99;
1639 config_msg = "Step value larger than 100%";
1640 return ERR;
1642 if (*opt < 0.0) {
1643 *opt = 1;
1644 config_msg = "Invalid step value";
1645 return ERR;
1647 return OK;
1650 static int
1651 parse_int(int *opt, const char *arg, int min, int max)
1653 int value = atoi(arg);
1655 if (min <= value && value <= max) {
1656 *opt = value;
1657 return OK;
1660 config_msg = "Integer value out of bound";
1661 return ERR;
1664 static bool
1665 set_color(int *color, const char *name)
1667 if (map_enum(color, color_map, name))
1668 return TRUE;
1669 if (!prefixcmp(name, "color"))
1670 return parse_int(color, name + 5, 0, 255) == OK;
1671 return FALSE;
1674 /* Wants: object fgcolor bgcolor [attribute] */
1675 static int
1676 option_color_command(int argc, const char *argv[])
1678 struct line_info *info;
1680 if (argc < 3) {
1681 config_msg = "Wrong number of arguments given to color command";
1682 return ERR;
1685 info = get_line_info(argv[0]);
1686 if (!info) {
1687 static const struct enum_map obsolete[] = {
1688 ENUM_MAP("main-delim", LINE_DELIMITER),
1689 ENUM_MAP("main-date", LINE_DATE),
1690 ENUM_MAP("main-author", LINE_AUTHOR),
1692 int index;
1694 if (!map_enum(&index, obsolete, argv[0])) {
1695 config_msg = "Unknown color name";
1696 return ERR;
1698 info = &line_info[index];
1701 if (!set_color(&info->fg, argv[1]) ||
1702 !set_color(&info->bg, argv[2])) {
1703 config_msg = "Unknown color";
1704 return ERR;
1707 info->attr = 0;
1708 while (argc-- > 3) {
1709 int attr;
1711 if (!set_attribute(&attr, argv[argc])) {
1712 config_msg = "Unknown attribute";
1713 return ERR;
1715 info->attr |= attr;
1718 return OK;
1721 static int parse_bool(bool *opt, const char *arg)
1723 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1724 ? TRUE : FALSE;
1725 return OK;
1728 static int parse_enum_do(unsigned int *opt, const char *arg,
1729 const struct enum_map *map, size_t map_size)
1731 bool is_true;
1733 assert(map_size > 1);
1735 if (map_enum_do(map, map_size, (int *) opt, arg))
1736 return OK;
1738 if (parse_bool(&is_true, arg) != OK)
1739 return ERR;
1741 *opt = is_true ? map[1].value : map[0].value;
1742 return OK;
1745 #define parse_enum(opt, arg, map) \
1746 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1748 static int
1749 parse_string(char *opt, const char *arg, size_t optsize)
1751 int arglen = strlen(arg);
1753 switch (arg[0]) {
1754 case '\"':
1755 case '\'':
1756 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1757 config_msg = "Unmatched quotation";
1758 return ERR;
1760 arg += 1; arglen -= 2;
1761 default:
1762 string_ncopy_do(opt, optsize, arg, arglen);
1763 return OK;
1767 /* Wants: name = value */
1768 static int
1769 option_set_command(int argc, const char *argv[])
1771 if (argc != 3) {
1772 config_msg = "Wrong number of arguments given to set command";
1773 return ERR;
1776 if (strcmp(argv[1], "=")) {
1777 config_msg = "No value assigned";
1778 return ERR;
1781 if (!strcmp(argv[0], "show-author"))
1782 return parse_enum(&opt_author, argv[2], author_map);
1784 if (!strcmp(argv[0], "show-date"))
1785 return parse_enum(&opt_date, argv[2], date_map);
1787 if (!strcmp(argv[0], "show-rev-graph"))
1788 return parse_bool(&opt_rev_graph, argv[2]);
1790 if (!strcmp(argv[0], "show-refs"))
1791 return parse_bool(&opt_show_refs, argv[2]);
1793 if (!strcmp(argv[0], "show-line-numbers"))
1794 return parse_bool(&opt_line_number, argv[2]);
1796 if (!strcmp(argv[0], "line-graphics"))
1797 return parse_bool(&opt_line_graphics, argv[2]);
1799 if (!strcmp(argv[0], "line-number-interval"))
1800 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1802 if (!strcmp(argv[0], "author-width"))
1803 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1805 if (!strcmp(argv[0], "horizontal-scroll"))
1806 return parse_step(&opt_hscroll, argv[2]);
1808 if (!strcmp(argv[0], "split-view-height"))
1809 return parse_step(&opt_scale_split_view, argv[2]);
1811 if (!strcmp(argv[0], "tab-size"))
1812 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1814 if (!strcmp(argv[0], "commit-encoding"))
1815 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1817 config_msg = "Unknown variable name";
1818 return ERR;
1821 /* Wants: mode request key */
1822 static int
1823 option_bind_command(int argc, const char *argv[])
1825 enum request request;
1826 int keymap = -1;
1827 int key;
1829 if (argc < 3) {
1830 config_msg = "Wrong number of arguments given to bind command";
1831 return ERR;
1834 if (set_keymap(&keymap, argv[0]) == ERR) {
1835 config_msg = "Unknown key map";
1836 return ERR;
1839 key = get_key_value(argv[1]);
1840 if (key == ERR) {
1841 config_msg = "Unknown key";
1842 return ERR;
1845 request = get_request(argv[2]);
1846 if (request == REQ_NONE) {
1847 static const struct enum_map obsolete[] = {
1848 ENUM_MAP("cherry-pick", REQ_NONE),
1849 ENUM_MAP("screen-resize", REQ_NONE),
1850 ENUM_MAP("tree-parent", REQ_PARENT),
1852 int alias;
1854 if (map_enum(&alias, obsolete, argv[2])) {
1855 if (alias != REQ_NONE)
1856 add_keybinding(keymap, alias, key);
1857 config_msg = "Obsolete request name";
1858 return ERR;
1861 if (request == REQ_NONE && *argv[2]++ == '!')
1862 request = add_run_request(keymap, key, argc - 2, argv + 2);
1863 if (request == REQ_NONE) {
1864 config_msg = "Unknown request name";
1865 return ERR;
1868 add_keybinding(keymap, request, key);
1870 return OK;
1873 static int
1874 set_option(const char *opt, char *value)
1876 const char *argv[SIZEOF_ARG];
1877 int argc = 0;
1879 if (!argv_from_string(argv, &argc, value)) {
1880 config_msg = "Too many option arguments";
1881 return ERR;
1884 if (!strcmp(opt, "color"))
1885 return option_color_command(argc, argv);
1887 if (!strcmp(opt, "set"))
1888 return option_set_command(argc, argv);
1890 if (!strcmp(opt, "bind"))
1891 return option_bind_command(argc, argv);
1893 config_msg = "Unknown option command";
1894 return ERR;
1897 static int
1898 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1900 int status = OK;
1902 config_lineno++;
1903 config_msg = "Internal error";
1905 /* Check for comment markers, since read_properties() will
1906 * only ensure opt and value are split at first " \t". */
1907 optlen = strcspn(opt, "#");
1908 if (optlen == 0)
1909 return OK;
1911 if (opt[optlen] != 0) {
1912 config_msg = "No option value";
1913 status = ERR;
1915 } else {
1916 /* Look for comment endings in the value. */
1917 size_t len = strcspn(value, "#");
1919 if (len < valuelen) {
1920 valuelen = len;
1921 value[valuelen] = 0;
1924 status = set_option(opt, value);
1927 if (status == ERR) {
1928 warn("Error on line %d, near '%.*s': %s",
1929 config_lineno, (int) optlen, opt, config_msg);
1930 config_errors = TRUE;
1933 /* Always keep going if errors are encountered. */
1934 return OK;
1937 static void
1938 load_option_file(const char *path)
1940 struct io io = {};
1942 /* It's OK that the file doesn't exist. */
1943 if (!io_open(&io, "%s", path))
1944 return;
1946 config_lineno = 0;
1947 config_errors = FALSE;
1949 if (io_load(&io, " \t", read_option) == ERR ||
1950 config_errors == TRUE)
1951 warn("Errors while loading %s.", path);
1954 static int
1955 load_options(void)
1957 const char *home = getenv("HOME");
1958 const char *tigrc_user = getenv("TIGRC_USER");
1959 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1960 char buf[SIZEOF_STR];
1962 add_builtin_run_requests();
1964 if (!tigrc_system)
1965 tigrc_system = SYSCONFDIR "/tigrc";
1966 load_option_file(tigrc_system);
1968 if (!tigrc_user) {
1969 if (!home || !string_format(buf, "%s/.tigrc", home))
1970 return ERR;
1971 tigrc_user = buf;
1973 load_option_file(tigrc_user);
1975 return OK;
1980 * The viewer
1983 struct view;
1984 struct view_ops;
1986 /* The display array of active views and the index of the current view. */
1987 static struct view *display[2];
1988 static unsigned int current_view;
1990 #define foreach_displayed_view(view, i) \
1991 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1993 #define displayed_views() (display[1] != NULL ? 2 : 1)
1995 /* Current head and commit ID */
1996 static char ref_blob[SIZEOF_REF] = "";
1997 static char ref_commit[SIZEOF_REF] = "HEAD";
1998 static char ref_head[SIZEOF_REF] = "HEAD";
2000 struct view {
2001 const char *name; /* View name */
2002 const char *cmd_env; /* Command line set via environment */
2003 const char *id; /* Points to either of ref_{head,commit,blob} */
2005 struct view_ops *ops; /* View operations */
2007 enum keymap keymap; /* What keymap does this view have */
2008 bool git_dir; /* Whether the view requires a git directory. */
2010 char ref[SIZEOF_REF]; /* Hovered commit reference */
2011 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2013 int height, width; /* The width and height of the main window */
2014 WINDOW *win; /* The main window */
2015 WINDOW *title; /* The title window living below the main window */
2017 /* Navigation */
2018 unsigned long offset; /* Offset of the window top */
2019 unsigned long yoffset; /* Offset from the window side. */
2020 unsigned long lineno; /* Current line number */
2021 unsigned long p_offset; /* Previous offset of the window top */
2022 unsigned long p_yoffset;/* Previous offset from the window side */
2023 unsigned long p_lineno; /* Previous current line number */
2024 bool p_restore; /* Should the previous position be restored. */
2026 /* Searching */
2027 char grep[SIZEOF_STR]; /* Search string */
2028 regex_t *regex; /* Pre-compiled regexp */
2030 /* If non-NULL, points to the view that opened this view. If this view
2031 * is closed tig will switch back to the parent view. */
2032 struct view *parent;
2034 /* Buffering */
2035 size_t lines; /* Total number of lines */
2036 struct line *line; /* Line index */
2037 unsigned int digits; /* Number of digits in the lines member. */
2039 /* Drawing */
2040 struct line *curline; /* Line currently being drawn. */
2041 enum line_type curtype; /* Attribute currently used for drawing. */
2042 unsigned long col; /* Column when drawing. */
2043 bool has_scrolled; /* View was scrolled. */
2045 /* Loading */
2046 struct io io;
2047 struct io *pipe;
2048 time_t start_time;
2049 time_t update_secs;
2052 struct view_ops {
2053 /* What type of content being displayed. Used in the title bar. */
2054 const char *type;
2055 /* Default command arguments. */
2056 const char **argv;
2057 /* Open and reads in all view content. */
2058 bool (*open)(struct view *view);
2059 /* Read one line; updates view->line. */
2060 bool (*read)(struct view *view, char *data);
2061 /* Draw one line; @lineno must be < view->height. */
2062 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2063 /* Depending on view handle a special requests. */
2064 enum request (*request)(struct view *view, enum request request, struct line *line);
2065 /* Search for regexp in a line. */
2066 bool (*grep)(struct view *view, struct line *line);
2067 /* Select line */
2068 void (*select)(struct view *view, struct line *line);
2069 /* Prepare view for loading */
2070 bool (*prepare)(struct view *view);
2073 static struct view_ops blame_ops;
2074 static struct view_ops blob_ops;
2075 static struct view_ops diff_ops;
2076 static struct view_ops help_ops;
2077 static struct view_ops log_ops;
2078 static struct view_ops main_ops;
2079 static struct view_ops pager_ops;
2080 static struct view_ops stage_ops;
2081 static struct view_ops status_ops;
2082 static struct view_ops tree_ops;
2083 static struct view_ops branch_ops;
2085 #define VIEW_STR(name, env, ref, ops, map, git) \
2086 { name, #env, ref, ops, map, git }
2088 #define VIEW_(id, name, ops, git, ref) \
2089 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2092 static struct view views[] = {
2093 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2094 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2095 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2096 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2097 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2098 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2099 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2100 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2101 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2102 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2103 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2106 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2107 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2109 #define foreach_view(view, i) \
2110 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2112 #define view_is_displayed(view) \
2113 (view == display[0] || view == display[1])
2116 static inline void
2117 set_view_attr(struct view *view, enum line_type type)
2119 if (!view->curline->selected && view->curtype != type) {
2120 (void) wattrset(view->win, get_line_attr(type));
2121 wchgat(view->win, -1, 0, type, NULL);
2122 view->curtype = type;
2126 static int
2127 draw_chars(struct view *view, enum line_type type, const char *string,
2128 int max_len, bool use_tilde)
2130 static char out_buffer[BUFSIZ * 2];
2131 int len = 0;
2132 int col = 0;
2133 int trimmed = FALSE;
2134 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2136 if (max_len <= 0)
2137 return 0;
2139 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2141 set_view_attr(view, type);
2142 if (len > 0) {
2143 if (opt_iconv_out != ICONV_NONE) {
2144 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2145 size_t inlen = len + 1;
2147 char *outbuf = out_buffer;
2148 size_t outlen = sizeof(out_buffer);
2150 size_t ret;
2152 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2153 if (ret != (size_t) -1) {
2154 string = out_buffer;
2155 len = sizeof(out_buffer) - outlen;
2159 waddnstr(view->win, string, len);
2161 if (trimmed && use_tilde) {
2162 set_view_attr(view, LINE_DELIMITER);
2163 waddch(view->win, '~');
2164 col++;
2167 return col;
2170 static int
2171 draw_space(struct view *view, enum line_type type, int max, int spaces)
2173 static char space[] = " ";
2174 int col = 0;
2176 spaces = MIN(max, spaces);
2178 while (spaces > 0) {
2179 int len = MIN(spaces, sizeof(space) - 1);
2181 col += draw_chars(view, type, space, len, FALSE);
2182 spaces -= len;
2185 return col;
2188 static bool
2189 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2191 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2192 return view->width + view->yoffset <= view->col;
2195 static bool
2196 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2198 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2199 int max = view->width + view->yoffset - view->col;
2200 int i;
2202 if (max < size)
2203 size = max;
2205 set_view_attr(view, type);
2206 /* Using waddch() instead of waddnstr() ensures that
2207 * they'll be rendered correctly for the cursor line. */
2208 for (i = skip; i < size; i++)
2209 waddch(view->win, graphic[i]);
2211 view->col += size;
2212 if (size < max && skip <= size)
2213 waddch(view->win, ' ');
2214 view->col++;
2216 return view->width + view->yoffset <= view->col;
2219 static bool
2220 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2222 int max = MIN(view->width + view->yoffset - view->col, len);
2223 int col;
2225 if (text)
2226 col = draw_chars(view, type, text, max - 1, trim);
2227 else
2228 col = draw_space(view, type, max - 1, max - 1);
2230 view->col += col;
2231 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2232 return view->width + view->yoffset <= view->col;
2235 static bool
2236 draw_date(struct view *view, struct time *time)
2238 const char *date = time && time->sec ? mkdate(time) : "";
2239 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2241 return draw_field(view, LINE_DATE, date, cols, FALSE);
2244 static bool
2245 draw_author(struct view *view, const char *author)
2247 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2248 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2250 if (abbreviate && author)
2251 author = get_author_initials(author);
2253 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2256 static bool
2257 draw_mode(struct view *view, mode_t mode)
2259 const char *str;
2261 if (S_ISDIR(mode))
2262 str = "drwxr-xr-x";
2263 else if (S_ISLNK(mode))
2264 str = "lrwxrwxrwx";
2265 else if (S_ISGITLINK(mode))
2266 str = "m---------";
2267 else if (S_ISREG(mode) && mode & S_IXUSR)
2268 str = "-rwxr-xr-x";
2269 else if (S_ISREG(mode))
2270 str = "-rw-r--r--";
2271 else
2272 str = "----------";
2274 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2277 static bool
2278 draw_lineno(struct view *view, unsigned int lineno)
2280 char number[10];
2281 int digits3 = view->digits < 3 ? 3 : view->digits;
2282 int max = MIN(view->width + view->yoffset - view->col, digits3);
2283 char *text = NULL;
2284 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2286 lineno += view->offset + 1;
2287 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2288 static char fmt[] = "%1ld";
2290 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2291 if (string_format(number, fmt, lineno))
2292 text = number;
2294 if (text)
2295 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2296 else
2297 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2298 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2301 static bool
2302 draw_view_line(struct view *view, unsigned int lineno)
2304 struct line *line;
2305 bool selected = (view->offset + lineno == view->lineno);
2307 assert(view_is_displayed(view));
2309 if (view->offset + lineno >= view->lines)
2310 return FALSE;
2312 line = &view->line[view->offset + lineno];
2314 wmove(view->win, lineno, 0);
2315 if (line->cleareol)
2316 wclrtoeol(view->win);
2317 view->col = 0;
2318 view->curline = line;
2319 view->curtype = LINE_NONE;
2320 line->selected = FALSE;
2321 line->dirty = line->cleareol = 0;
2323 if (selected) {
2324 set_view_attr(view, LINE_CURSOR);
2325 line->selected = TRUE;
2326 view->ops->select(view, line);
2329 return view->ops->draw(view, line, lineno);
2332 static void
2333 redraw_view_dirty(struct view *view)
2335 bool dirty = FALSE;
2336 int lineno;
2338 for (lineno = 0; lineno < view->height; lineno++) {
2339 if (view->offset + lineno >= view->lines)
2340 break;
2341 if (!view->line[view->offset + lineno].dirty)
2342 continue;
2343 dirty = TRUE;
2344 if (!draw_view_line(view, lineno))
2345 break;
2348 if (!dirty)
2349 return;
2350 wnoutrefresh(view->win);
2353 static void
2354 redraw_view_from(struct view *view, int lineno)
2356 assert(0 <= lineno && lineno < view->height);
2358 for (; lineno < view->height; lineno++) {
2359 if (!draw_view_line(view, lineno))
2360 break;
2363 wnoutrefresh(view->win);
2366 static void
2367 redraw_view(struct view *view)
2369 werase(view->win);
2370 redraw_view_from(view, 0);
2374 static void
2375 update_view_title(struct view *view)
2377 char buf[SIZEOF_STR];
2378 char state[SIZEOF_STR];
2379 size_t bufpos = 0, statelen = 0;
2381 assert(view_is_displayed(view));
2383 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2384 unsigned int view_lines = view->offset + view->height;
2385 unsigned int lines = view->lines
2386 ? MIN(view_lines, view->lines) * 100 / view->lines
2387 : 0;
2389 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2390 view->ops->type,
2391 view->lineno + 1,
2392 view->lines,
2393 lines);
2397 if (view->pipe) {
2398 time_t secs = time(NULL) - view->start_time;
2400 /* Three git seconds are a long time ... */
2401 if (secs > 2)
2402 string_format_from(state, &statelen, " loading %lds", secs);
2405 string_format_from(buf, &bufpos, "[%s]", view->name);
2406 if (*view->ref && bufpos < view->width) {
2407 size_t refsize = strlen(view->ref);
2408 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2410 if (minsize < view->width)
2411 refsize = view->width - minsize + 7;
2412 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2415 if (statelen && bufpos < view->width) {
2416 string_format_from(buf, &bufpos, "%s", state);
2419 if (view == display[current_view])
2420 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2421 else
2422 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2424 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2425 wclrtoeol(view->title);
2426 wnoutrefresh(view->title);
2429 static int
2430 apply_step(double step, int value)
2432 if (step >= 1)
2433 return (int) step;
2434 value *= step + 0.01;
2435 return value ? value : 1;
2438 static void
2439 resize_display(void)
2441 int offset, i;
2442 struct view *base = display[0];
2443 struct view *view = display[1] ? display[1] : display[0];
2445 /* Setup window dimensions */
2447 getmaxyx(stdscr, base->height, base->width);
2449 /* Make room for the status window. */
2450 base->height -= 1;
2452 if (view != base) {
2453 /* Horizontal split. */
2454 view->width = base->width;
2455 view->height = apply_step(opt_scale_split_view, base->height);
2456 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2457 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2458 base->height -= view->height;
2460 /* Make room for the title bar. */
2461 view->height -= 1;
2464 /* Make room for the title bar. */
2465 base->height -= 1;
2467 offset = 0;
2469 foreach_displayed_view (view, i) {
2470 if (!view->win) {
2471 view->win = newwin(view->height, 0, offset, 0);
2472 if (!view->win)
2473 die("Failed to create %s view", view->name);
2475 scrollok(view->win, FALSE);
2477 view->title = newwin(1, 0, offset + view->height, 0);
2478 if (!view->title)
2479 die("Failed to create title window");
2481 } else {
2482 wresize(view->win, view->height, view->width);
2483 mvwin(view->win, offset, 0);
2484 mvwin(view->title, offset + view->height, 0);
2487 offset += view->height + 1;
2491 static void
2492 redraw_display(bool clear)
2494 struct view *view;
2495 int i;
2497 foreach_displayed_view (view, i) {
2498 if (clear)
2499 wclear(view->win);
2500 redraw_view(view);
2501 update_view_title(view);
2505 static void
2506 toggle_enum_option_do(unsigned int *opt, const char *help,
2507 const struct enum_map *map, size_t size)
2509 *opt = (*opt + 1) % size;
2510 redraw_display(FALSE);
2511 report("Displaying %s %s", enum_name(map[*opt]), help);
2514 #define toggle_enum_option(opt, help, map) \
2515 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2517 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2518 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2520 static void
2521 toggle_view_option(bool *option, const char *help)
2523 *option = !*option;
2524 redraw_display(FALSE);
2525 report("%sabling %s", *option ? "En" : "Dis", help);
2528 static void
2529 open_option_menu(void)
2531 const struct menu_item menu[] = {
2532 { '.', "line numbers", &opt_line_number },
2533 { 'D', "date display", &opt_date },
2534 { 'A', "author display", &opt_author },
2535 { 'g', "revision graph display", &opt_rev_graph },
2536 { 'F', "reference display", &opt_show_refs },
2537 { 0 }
2539 int selected = 0;
2541 if (prompt_menu("Toggle option", menu, &selected)) {
2542 if (menu[selected].data == &opt_date)
2543 toggle_date();
2544 else if (menu[selected].data == &opt_author)
2545 toggle_author();
2546 else
2547 toggle_view_option(menu[selected].data, menu[selected].text);
2551 static void
2552 maximize_view(struct view *view)
2554 memset(display, 0, sizeof(display));
2555 current_view = 0;
2556 display[current_view] = view;
2557 resize_display();
2558 redraw_display(FALSE);
2559 report("");
2564 * Navigation
2567 static bool
2568 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2570 if (lineno >= view->lines)
2571 lineno = view->lines > 0 ? view->lines - 1 : 0;
2573 if (offset > lineno || offset + view->height <= lineno) {
2574 unsigned long half = view->height / 2;
2576 if (lineno > half)
2577 offset = lineno - half;
2578 else
2579 offset = 0;
2582 if (offset != view->offset || lineno != view->lineno) {
2583 view->offset = offset;
2584 view->lineno = lineno;
2585 return TRUE;
2588 return FALSE;
2591 /* Scrolling backend */
2592 static void
2593 do_scroll_view(struct view *view, int lines)
2595 bool redraw_current_line = FALSE;
2597 /* The rendering expects the new offset. */
2598 view->offset += lines;
2600 assert(0 <= view->offset && view->offset < view->lines);
2601 assert(lines);
2603 /* Move current line into the view. */
2604 if (view->lineno < view->offset) {
2605 view->lineno = view->offset;
2606 redraw_current_line = TRUE;
2607 } else if (view->lineno >= view->offset + view->height) {
2608 view->lineno = view->offset + view->height - 1;
2609 redraw_current_line = TRUE;
2612 assert(view->offset <= view->lineno && view->lineno < view->lines);
2614 /* Redraw the whole screen if scrolling is pointless. */
2615 if (view->height < ABS(lines)) {
2616 redraw_view(view);
2618 } else {
2619 int line = lines > 0 ? view->height - lines : 0;
2620 int end = line + ABS(lines);
2622 scrollok(view->win, TRUE);
2623 wscrl(view->win, lines);
2624 scrollok(view->win, FALSE);
2626 while (line < end && draw_view_line(view, line))
2627 line++;
2629 if (redraw_current_line)
2630 draw_view_line(view, view->lineno - view->offset);
2631 wnoutrefresh(view->win);
2634 view->has_scrolled = TRUE;
2635 report("");
2638 /* Scroll frontend */
2639 static void
2640 scroll_view(struct view *view, enum request request)
2642 int lines = 1;
2644 assert(view_is_displayed(view));
2646 switch (request) {
2647 case REQ_SCROLL_LEFT:
2648 if (view->yoffset == 0) {
2649 report("Cannot scroll beyond the first column");
2650 return;
2652 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2653 view->yoffset = 0;
2654 else
2655 view->yoffset -= apply_step(opt_hscroll, view->width);
2656 redraw_view_from(view, 0);
2657 report("");
2658 return;
2659 case REQ_SCROLL_RIGHT:
2660 view->yoffset += apply_step(opt_hscroll, view->width);
2661 redraw_view(view);
2662 report("");
2663 return;
2664 case REQ_SCROLL_PAGE_DOWN:
2665 lines = view->height;
2666 case REQ_SCROLL_LINE_DOWN:
2667 if (view->offset + lines > view->lines)
2668 lines = view->lines - view->offset;
2670 if (lines == 0 || view->offset + view->height >= view->lines) {
2671 report("Cannot scroll beyond the last line");
2672 return;
2674 break;
2676 case REQ_SCROLL_PAGE_UP:
2677 lines = view->height;
2678 case REQ_SCROLL_LINE_UP:
2679 if (lines > view->offset)
2680 lines = view->offset;
2682 if (lines == 0) {
2683 report("Cannot scroll beyond the first line");
2684 return;
2687 lines = -lines;
2688 break;
2690 default:
2691 die("request %d not handled in switch", request);
2694 do_scroll_view(view, lines);
2697 /* Cursor moving */
2698 static void
2699 move_view(struct view *view, enum request request)
2701 int scroll_steps = 0;
2702 int steps;
2704 switch (request) {
2705 case REQ_MOVE_FIRST_LINE:
2706 steps = -view->lineno;
2707 break;
2709 case REQ_MOVE_LAST_LINE:
2710 steps = view->lines - view->lineno - 1;
2711 break;
2713 case REQ_MOVE_PAGE_UP:
2714 steps = view->height > view->lineno
2715 ? -view->lineno : -view->height;
2716 break;
2718 case REQ_MOVE_PAGE_DOWN:
2719 steps = view->lineno + view->height >= view->lines
2720 ? view->lines - view->lineno - 1 : view->height;
2721 break;
2723 case REQ_MOVE_UP:
2724 steps = -1;
2725 break;
2727 case REQ_MOVE_DOWN:
2728 steps = 1;
2729 break;
2731 default:
2732 die("request %d not handled in switch", request);
2735 if (steps <= 0 && view->lineno == 0) {
2736 report("Cannot move beyond the first line");
2737 return;
2739 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2740 report("Cannot move beyond the last line");
2741 return;
2744 /* Move the current line */
2745 view->lineno += steps;
2746 assert(0 <= view->lineno && view->lineno < view->lines);
2748 /* Check whether the view needs to be scrolled */
2749 if (view->lineno < view->offset ||
2750 view->lineno >= view->offset + view->height) {
2751 scroll_steps = steps;
2752 if (steps < 0 && -steps > view->offset) {
2753 scroll_steps = -view->offset;
2755 } else if (steps > 0) {
2756 if (view->lineno == view->lines - 1 &&
2757 view->lines > view->height) {
2758 scroll_steps = view->lines - view->offset - 1;
2759 if (scroll_steps >= view->height)
2760 scroll_steps -= view->height - 1;
2765 if (!view_is_displayed(view)) {
2766 view->offset += scroll_steps;
2767 assert(0 <= view->offset && view->offset < view->lines);
2768 view->ops->select(view, &view->line[view->lineno]);
2769 return;
2772 /* Repaint the old "current" line if we be scrolling */
2773 if (ABS(steps) < view->height)
2774 draw_view_line(view, view->lineno - steps - view->offset);
2776 if (scroll_steps) {
2777 do_scroll_view(view, scroll_steps);
2778 return;
2781 /* Draw the current line */
2782 draw_view_line(view, view->lineno - view->offset);
2784 wnoutrefresh(view->win);
2785 report("");
2790 * Searching
2793 static void search_view(struct view *view, enum request request);
2795 static bool
2796 grep_text(struct view *view, const char *text[])
2798 regmatch_t pmatch;
2799 size_t i;
2801 for (i = 0; text[i]; i++)
2802 if (*text[i] &&
2803 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2804 return TRUE;
2805 return FALSE;
2808 static void
2809 select_view_line(struct view *view, unsigned long lineno)
2811 unsigned long old_lineno = view->lineno;
2812 unsigned long old_offset = view->offset;
2814 if (goto_view_line(view, view->offset, lineno)) {
2815 if (view_is_displayed(view)) {
2816 if (old_offset != view->offset) {
2817 redraw_view(view);
2818 } else {
2819 draw_view_line(view, old_lineno - view->offset);
2820 draw_view_line(view, view->lineno - view->offset);
2821 wnoutrefresh(view->win);
2823 } else {
2824 view->ops->select(view, &view->line[view->lineno]);
2829 static void
2830 find_next(struct view *view, enum request request)
2832 unsigned long lineno = view->lineno;
2833 int direction;
2835 if (!*view->grep) {
2836 if (!*opt_search)
2837 report("No previous search");
2838 else
2839 search_view(view, request);
2840 return;
2843 switch (request) {
2844 case REQ_SEARCH:
2845 case REQ_FIND_NEXT:
2846 direction = 1;
2847 break;
2849 case REQ_SEARCH_BACK:
2850 case REQ_FIND_PREV:
2851 direction = -1;
2852 break;
2854 default:
2855 return;
2858 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2859 lineno += direction;
2861 /* Note, lineno is unsigned long so will wrap around in which case it
2862 * will become bigger than view->lines. */
2863 for (; lineno < view->lines; lineno += direction) {
2864 if (view->ops->grep(view, &view->line[lineno])) {
2865 select_view_line(view, lineno);
2866 report("Line %ld matches '%s'", lineno + 1, view->grep);
2867 return;
2871 report("No match found for '%s'", view->grep);
2874 static void
2875 search_view(struct view *view, enum request request)
2877 int regex_err;
2879 if (view->regex) {
2880 regfree(view->regex);
2881 *view->grep = 0;
2882 } else {
2883 view->regex = calloc(1, sizeof(*view->regex));
2884 if (!view->regex)
2885 return;
2888 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2889 if (regex_err != 0) {
2890 char buf[SIZEOF_STR] = "unknown error";
2892 regerror(regex_err, view->regex, buf, sizeof(buf));
2893 report("Search failed: %s", buf);
2894 return;
2897 string_copy(view->grep, opt_search);
2899 find_next(view, request);
2903 * Incremental updating
2906 static void
2907 reset_view(struct view *view)
2909 int i;
2911 for (i = 0; i < view->lines; i++)
2912 free(view->line[i].data);
2913 free(view->line);
2915 view->p_offset = view->offset;
2916 view->p_yoffset = view->yoffset;
2917 view->p_lineno = view->lineno;
2919 view->line = NULL;
2920 view->offset = 0;
2921 view->yoffset = 0;
2922 view->lines = 0;
2923 view->lineno = 0;
2924 view->vid[0] = 0;
2925 view->update_secs = 0;
2928 static void
2929 free_argv(const char *argv[])
2931 int argc;
2933 for (argc = 0; argv[argc]; argc++)
2934 free((void *) argv[argc]);
2937 static const char *
2938 format_arg(const char *name)
2940 static struct {
2941 const char *name;
2942 size_t namelen;
2943 const char *value;
2944 const char *value_if_empty;
2945 } vars[] = {
2946 #define FORMAT_VAR(name, value, value_if_empty) \
2947 { name, STRING_SIZE(name), value, value_if_empty }
2948 FORMAT_VAR("%(directory)", opt_path, ""),
2949 FORMAT_VAR("%(file)", opt_file, ""),
2950 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2951 FORMAT_VAR("%(head)", ref_head, ""),
2952 FORMAT_VAR("%(commit)", ref_commit, ""),
2953 FORMAT_VAR("%(blob)", ref_blob, ""),
2955 int i;
2957 for (i = 0; i < ARRAY_SIZE(vars); i++)
2958 if (!strncmp(name, vars[i].name, vars[i].namelen))
2959 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2961 return NULL;
2963 static bool
2964 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2966 char buf[SIZEOF_STR];
2967 int argc;
2968 bool noreplace = flags == FORMAT_NONE;
2970 free_argv(dst_argv);
2972 for (argc = 0; src_argv[argc]; argc++) {
2973 const char *arg = src_argv[argc];
2974 size_t bufpos = 0;
2976 while (arg) {
2977 char *next = strstr(arg, "%(");
2978 int len = next - arg;
2979 const char *value;
2981 if (!next || noreplace) {
2982 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2983 noreplace = TRUE;
2984 len = strlen(arg);
2985 value = "";
2987 } else {
2988 value = format_arg(next);
2990 if (!value) {
2991 report("Unknown replacement: `%s`", next);
2992 return FALSE;
2996 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2997 return FALSE;
2999 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3002 dst_argv[argc] = strdup(buf);
3003 if (!dst_argv[argc])
3004 break;
3007 dst_argv[argc] = NULL;
3009 return src_argv[argc] == NULL;
3012 static bool
3013 restore_view_position(struct view *view)
3015 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3016 return FALSE;
3018 /* Changing the view position cancels the restoring. */
3019 /* FIXME: Changing back to the first line is not detected. */
3020 if (view->offset != 0 || view->lineno != 0) {
3021 view->p_restore = FALSE;
3022 return FALSE;
3025 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3026 view_is_displayed(view))
3027 werase(view->win);
3029 view->yoffset = view->p_yoffset;
3030 view->p_restore = FALSE;
3032 return TRUE;
3035 static void
3036 end_update(struct view *view, bool force)
3038 if (!view->pipe)
3039 return;
3040 while (!view->ops->read(view, NULL))
3041 if (!force)
3042 return;
3043 if (force)
3044 kill_io(view->pipe);
3045 done_io(view->pipe);
3046 view->pipe = NULL;
3049 static void
3050 setup_update(struct view *view, const char *vid)
3052 reset_view(view);
3053 string_copy_rev(view->vid, vid);
3054 view->pipe = &view->io;
3055 view->start_time = time(NULL);
3058 static bool
3059 prepare_update(struct view *view, const char *argv[], const char *dir,
3060 enum format_flags flags)
3062 if (view->pipe)
3063 end_update(view, TRUE);
3064 return init_io_rd(&view->io, argv, dir, flags);
3067 static bool
3068 prepare_update_file(struct view *view, const char *name)
3070 if (view->pipe)
3071 end_update(view, TRUE);
3072 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3075 static bool
3076 begin_update(struct view *view, bool refresh)
3078 if (view->pipe)
3079 end_update(view, TRUE);
3081 if (!refresh) {
3082 if (view->ops->prepare) {
3083 if (!view->ops->prepare(view))
3084 return FALSE;
3085 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3086 return FALSE;
3089 /* Put the current ref_* value to the view title ref
3090 * member. This is needed by the blob view. Most other
3091 * views sets it automatically after loading because the
3092 * first line is a commit line. */
3093 string_copy_rev(view->ref, view->id);
3096 if (!start_io(&view->io))
3097 return FALSE;
3099 setup_update(view, view->id);
3101 return TRUE;
3104 static bool
3105 update_view(struct view *view)
3107 char out_buffer[BUFSIZ * 2];
3108 char *line;
3109 /* Clear the view and redraw everything since the tree sorting
3110 * might have rearranged things. */
3111 bool redraw = view->lines == 0;
3112 bool can_read = TRUE;
3114 if (!view->pipe)
3115 return TRUE;
3117 if (!io_can_read(view->pipe)) {
3118 if (view->lines == 0 && view_is_displayed(view)) {
3119 time_t secs = time(NULL) - view->start_time;
3121 if (secs > 1 && secs > view->update_secs) {
3122 if (view->update_secs == 0)
3123 redraw_view(view);
3124 update_view_title(view);
3125 view->update_secs = secs;
3128 return TRUE;
3131 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3132 if (opt_iconv_in != ICONV_NONE) {
3133 ICONV_CONST char *inbuf = line;
3134 size_t inlen = strlen(line) + 1;
3136 char *outbuf = out_buffer;
3137 size_t outlen = sizeof(out_buffer);
3139 size_t ret;
3141 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3142 if (ret != (size_t) -1)
3143 line = out_buffer;
3146 if (!view->ops->read(view, line)) {
3147 report("Allocation failure");
3148 end_update(view, TRUE);
3149 return FALSE;
3154 unsigned long lines = view->lines;
3155 int digits;
3157 for (digits = 0; lines; digits++)
3158 lines /= 10;
3160 /* Keep the displayed view in sync with line number scaling. */
3161 if (digits != view->digits) {
3162 view->digits = digits;
3163 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3164 redraw = TRUE;
3168 if (io_error(view->pipe)) {
3169 report("Failed to read: %s", io_strerror(view->pipe));
3170 end_update(view, TRUE);
3172 } else if (io_eof(view->pipe)) {
3173 report("");
3174 end_update(view, FALSE);
3177 if (restore_view_position(view))
3178 redraw = TRUE;
3180 if (!view_is_displayed(view))
3181 return TRUE;
3183 if (redraw)
3184 redraw_view_from(view, 0);
3185 else
3186 redraw_view_dirty(view);
3188 /* Update the title _after_ the redraw so that if the redraw picks up a
3189 * commit reference in view->ref it'll be available here. */
3190 update_view_title(view);
3191 return TRUE;
3194 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3196 static struct line *
3197 add_line_data(struct view *view, void *data, enum line_type type)
3199 struct line *line;
3201 if (!realloc_lines(&view->line, view->lines, 1))
3202 return NULL;
3204 line = &view->line[view->lines++];
3205 memset(line, 0, sizeof(*line));
3206 line->type = type;
3207 line->data = data;
3208 line->dirty = 1;
3210 return line;
3213 static struct line *
3214 add_line_text(struct view *view, const char *text, enum line_type type)
3216 char *data = text ? strdup(text) : NULL;
3218 return data ? add_line_data(view, data, type) : NULL;
3221 static struct line *
3222 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3224 char buf[SIZEOF_STR];
3225 va_list args;
3227 va_start(args, fmt);
3228 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3229 buf[0] = 0;
3230 va_end(args);
3232 return buf[0] ? add_line_text(view, buf, type) : NULL;
3236 * View opening
3239 enum open_flags {
3240 OPEN_DEFAULT = 0, /* Use default view switching. */
3241 OPEN_SPLIT = 1, /* Split current view. */
3242 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3243 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3244 OPEN_PREPARED = 32, /* Open already prepared command. */
3247 static void
3248 open_view(struct view *prev, enum request request, enum open_flags flags)
3250 bool split = !!(flags & OPEN_SPLIT);
3251 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3252 bool nomaximize = !!(flags & OPEN_REFRESH);
3253 struct view *view = VIEW(request);
3254 int nviews = displayed_views();
3255 struct view *base_view = display[0];
3257 if (view == prev && nviews == 1 && !reload) {
3258 report("Already in %s view", view->name);
3259 return;
3262 if (view->git_dir && !opt_git_dir[0]) {
3263 report("The %s view is disabled in pager view", view->name);
3264 return;
3267 if (split) {
3268 display[1] = view;
3269 current_view = 1;
3270 } else if (!nomaximize) {
3271 /* Maximize the current view. */
3272 memset(display, 0, sizeof(display));
3273 current_view = 0;
3274 display[current_view] = view;
3277 /* No parent signals that this is the first loaded view. */
3278 if (prev && view != prev) {
3279 view->parent = prev;
3282 /* Resize the view when switching between split- and full-screen,
3283 * or when switching between two different full-screen views. */
3284 if (nviews != displayed_views() ||
3285 (nviews == 1 && base_view != display[0]))
3286 resize_display();
3288 if (view->ops->open) {
3289 if (view->pipe)
3290 end_update(view, TRUE);
3291 if (!view->ops->open(view)) {
3292 report("Failed to load %s view", view->name);
3293 return;
3295 restore_view_position(view);
3297 } else if ((reload || strcmp(view->vid, view->id)) &&
3298 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3299 report("Failed to load %s view", view->name);
3300 return;
3303 if (split && prev->lineno - prev->offset >= prev->height) {
3304 /* Take the title line into account. */
3305 int lines = prev->lineno - prev->offset - prev->height + 1;
3307 /* Scroll the view that was split if the current line is
3308 * outside the new limited view. */
3309 do_scroll_view(prev, lines);
3312 if (prev && view != prev && split && view_is_displayed(prev)) {
3313 /* "Blur" the previous view. */
3314 update_view_title(prev);
3317 if (view->pipe && view->lines == 0) {
3318 /* Clear the old view and let the incremental updating refill
3319 * the screen. */
3320 werase(view->win);
3321 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3322 report("");
3323 } else if (view_is_displayed(view)) {
3324 redraw_view(view);
3325 report("");
3329 static void
3330 open_external_viewer(const char *argv[], const char *dir)
3332 def_prog_mode(); /* save current tty modes */
3333 endwin(); /* restore original tty modes */
3334 run_io_fg(argv, dir);
3335 fprintf(stderr, "Press Enter to continue");
3336 getc(opt_tty);
3337 reset_prog_mode();
3338 redraw_display(TRUE);
3341 static void
3342 open_mergetool(const char *file)
3344 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3346 open_external_viewer(mergetool_argv, opt_cdup);
3349 static void
3350 open_editor(const char *file)
3352 const char *editor_argv[] = { "vi", file, NULL };
3353 const char *editor;
3355 editor = getenv("GIT_EDITOR");
3356 if (!editor && *opt_editor)
3357 editor = opt_editor;
3358 if (!editor)
3359 editor = getenv("VISUAL");
3360 if (!editor)
3361 editor = getenv("EDITOR");
3362 if (!editor)
3363 editor = "vi";
3365 editor_argv[0] = editor;
3366 open_external_viewer(editor_argv, opt_cdup);
3369 static void
3370 open_run_request(enum request request)
3372 struct run_request *req = get_run_request(request);
3373 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3375 if (!req) {
3376 report("Unknown run request");
3377 return;
3380 if (format_argv(argv, req->argv, FORMAT_ALL))
3381 open_external_viewer(argv, NULL);
3382 free_argv(argv);
3386 * User request switch noodle
3389 static int
3390 view_driver(struct view *view, enum request request)
3392 int i;
3394 if (request == REQ_NONE)
3395 return TRUE;
3397 if (request > REQ_NONE) {
3398 open_run_request(request);
3399 /* FIXME: When all views can refresh always do this. */
3400 if (view == VIEW(REQ_VIEW_STATUS) ||
3401 view == VIEW(REQ_VIEW_MAIN) ||
3402 view == VIEW(REQ_VIEW_LOG) ||
3403 view == VIEW(REQ_VIEW_BRANCH) ||
3404 view == VIEW(REQ_VIEW_STAGE))
3405 request = REQ_REFRESH;
3406 else
3407 return TRUE;
3410 if (view && view->lines) {
3411 request = view->ops->request(view, request, &view->line[view->lineno]);
3412 if (request == REQ_NONE)
3413 return TRUE;
3416 switch (request) {
3417 case REQ_MOVE_UP:
3418 case REQ_MOVE_DOWN:
3419 case REQ_MOVE_PAGE_UP:
3420 case REQ_MOVE_PAGE_DOWN:
3421 case REQ_MOVE_FIRST_LINE:
3422 case REQ_MOVE_LAST_LINE:
3423 move_view(view, request);
3424 break;
3426 case REQ_SCROLL_LEFT:
3427 case REQ_SCROLL_RIGHT:
3428 case REQ_SCROLL_LINE_DOWN:
3429 case REQ_SCROLL_LINE_UP:
3430 case REQ_SCROLL_PAGE_DOWN:
3431 case REQ_SCROLL_PAGE_UP:
3432 scroll_view(view, request);
3433 break;
3435 case REQ_VIEW_BLAME:
3436 if (!opt_file[0]) {
3437 report("No file chosen, press %s to open tree view",
3438 get_key(view->keymap, REQ_VIEW_TREE));
3439 break;
3441 open_view(view, request, OPEN_DEFAULT);
3442 break;
3444 case REQ_VIEW_BLOB:
3445 if (!ref_blob[0]) {
3446 report("No file chosen, press %s to open tree view",
3447 get_key(view->keymap, REQ_VIEW_TREE));
3448 break;
3450 open_view(view, request, OPEN_DEFAULT);
3451 break;
3453 case REQ_VIEW_PAGER:
3454 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3455 report("No pager content, press %s to run command from prompt",
3456 get_key(view->keymap, REQ_PROMPT));
3457 break;
3459 open_view(view, request, OPEN_DEFAULT);
3460 break;
3462 case REQ_VIEW_STAGE:
3463 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3464 report("No stage content, press %s to open the status view and choose file",
3465 get_key(view->keymap, REQ_VIEW_STATUS));
3466 break;
3468 open_view(view, request, OPEN_DEFAULT);
3469 break;
3471 case REQ_VIEW_STATUS:
3472 if (opt_is_inside_work_tree == FALSE) {
3473 report("The status view requires a working tree");
3474 break;
3476 open_view(view, request, OPEN_DEFAULT);
3477 break;
3479 case REQ_VIEW_MAIN:
3480 case REQ_VIEW_DIFF:
3481 case REQ_VIEW_LOG:
3482 case REQ_VIEW_TREE:
3483 case REQ_VIEW_HELP:
3484 case REQ_VIEW_BRANCH:
3485 open_view(view, request, OPEN_DEFAULT);
3486 break;
3488 case REQ_NEXT:
3489 case REQ_PREVIOUS:
3490 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3492 if ((view == VIEW(REQ_VIEW_DIFF) &&
3493 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3494 (view == VIEW(REQ_VIEW_DIFF) &&
3495 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3496 (view == VIEW(REQ_VIEW_STAGE) &&
3497 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3498 (view == VIEW(REQ_VIEW_BLOB) &&
3499 view->parent == VIEW(REQ_VIEW_TREE)) ||
3500 (view == VIEW(REQ_VIEW_MAIN) &&
3501 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3502 int line;
3504 view = view->parent;
3505 line = view->lineno;
3506 move_view(view, request);
3507 if (view_is_displayed(view))
3508 update_view_title(view);
3509 if (line != view->lineno)
3510 view->ops->request(view, REQ_ENTER,
3511 &view->line[view->lineno]);
3513 } else {
3514 move_view(view, request);
3516 break;
3518 case REQ_VIEW_NEXT:
3520 int nviews = displayed_views();
3521 int next_view = (current_view + 1) % nviews;
3523 if (next_view == current_view) {
3524 report("Only one view is displayed");
3525 break;
3528 current_view = next_view;
3529 /* Blur out the title of the previous view. */
3530 update_view_title(view);
3531 report("");
3532 break;
3534 case REQ_REFRESH:
3535 report("Refreshing is not yet supported for the %s view", view->name);
3536 break;
3538 case REQ_MAXIMIZE:
3539 if (displayed_views() == 2)
3540 maximize_view(view);
3541 break;
3543 case REQ_OPTIONS:
3544 open_option_menu();
3545 break;
3547 case REQ_TOGGLE_LINENO:
3548 toggle_view_option(&opt_line_number, "line numbers");
3549 break;
3551 case REQ_TOGGLE_DATE:
3552 toggle_date();
3553 break;
3555 case REQ_TOGGLE_AUTHOR:
3556 toggle_author();
3557 break;
3559 case REQ_TOGGLE_REV_GRAPH:
3560 toggle_view_option(&opt_rev_graph, "revision graph display");
3561 break;
3563 case REQ_TOGGLE_REFS:
3564 toggle_view_option(&opt_show_refs, "reference display");
3565 break;
3567 case REQ_TOGGLE_SORT_FIELD:
3568 case REQ_TOGGLE_SORT_ORDER:
3569 report("Sorting is not yet supported for the %s view", view->name);
3570 break;
3572 case REQ_SEARCH:
3573 case REQ_SEARCH_BACK:
3574 search_view(view, request);
3575 break;
3577 case REQ_FIND_NEXT:
3578 case REQ_FIND_PREV:
3579 find_next(view, request);
3580 break;
3582 case REQ_STOP_LOADING:
3583 for (i = 0; i < ARRAY_SIZE(views); i++) {
3584 view = &views[i];
3585 if (view->pipe)
3586 report("Stopped loading the %s view", view->name),
3587 end_update(view, TRUE);
3589 break;
3591 case REQ_SHOW_VERSION:
3592 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3593 return TRUE;
3595 case REQ_SCREEN_REDRAW:
3596 redraw_display(TRUE);
3597 break;
3599 case REQ_EDIT:
3600 report("Nothing to edit");
3601 break;
3603 case REQ_ENTER:
3604 report("Nothing to enter");
3605 break;
3607 case REQ_VIEW_CLOSE:
3608 /* XXX: Mark closed views by letting view->parent point to the
3609 * view itself. Parents to closed view should never be
3610 * followed. */
3611 if (view->parent &&
3612 view->parent->parent != view->parent) {
3613 maximize_view(view->parent);
3614 view->parent = view;
3615 break;
3617 /* Fall-through */
3618 case REQ_QUIT:
3619 return FALSE;
3621 default:
3622 report("Unknown key, press %s for help",
3623 get_key(view->keymap, REQ_VIEW_HELP));
3624 return TRUE;
3627 return TRUE;
3632 * View backend utilities
3635 enum sort_field {
3636 ORDERBY_NAME,
3637 ORDERBY_DATE,
3638 ORDERBY_AUTHOR,
3641 struct sort_state {
3642 const enum sort_field *fields;
3643 size_t size, current;
3644 bool reverse;
3647 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3648 #define get_sort_field(state) ((state).fields[(state).current])
3649 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3651 static void
3652 sort_view(struct view *view, enum request request, struct sort_state *state,
3653 int (*compare)(const void *, const void *))
3655 switch (request) {
3656 case REQ_TOGGLE_SORT_FIELD:
3657 state->current = (state->current + 1) % state->size;
3658 break;
3660 case REQ_TOGGLE_SORT_ORDER:
3661 state->reverse = !state->reverse;
3662 break;
3663 default:
3664 die("Not a sort request");
3667 qsort(view->line, view->lines, sizeof(*view->line), compare);
3668 redraw_view(view);
3671 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3673 /* Small author cache to reduce memory consumption. It uses binary
3674 * search to lookup or find place to position new entries. No entries
3675 * are ever freed. */
3676 static const char *
3677 get_author(const char *name)
3679 static const char **authors;
3680 static size_t authors_size;
3681 int from = 0, to = authors_size - 1;
3683 while (from <= to) {
3684 size_t pos = (to + from) / 2;
3685 int cmp = strcmp(name, authors[pos]);
3687 if (!cmp)
3688 return authors[pos];
3690 if (cmp < 0)
3691 to = pos - 1;
3692 else
3693 from = pos + 1;
3696 if (!realloc_authors(&authors, authors_size, 1))
3697 return NULL;
3698 name = strdup(name);
3699 if (!name)
3700 return NULL;
3702 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3703 authors[from] = name;
3704 authors_size++;
3706 return name;
3709 static void
3710 parse_timesec(struct time *time, const char *sec)
3712 time->sec = (time_t) atol(sec);
3715 static void
3716 parse_timezone(struct time *time, const char *zone)
3718 long tz;
3720 tz = ('0' - zone[1]) * 60 * 60 * 10;
3721 tz += ('0' - zone[2]) * 60 * 60;
3722 tz += ('0' - zone[3]) * 60;
3723 tz += ('0' - zone[4]);
3725 if (zone[0] == '-')
3726 tz = -tz;
3728 time->tz = tz;
3729 time->sec -= tz;
3732 /* Parse author lines where the name may be empty:
3733 * author <email@address.tld> 1138474660 +0100
3735 static void
3736 parse_author_line(char *ident, const char **author, struct time *time)
3738 char *nameend = strchr(ident, '<');
3739 char *emailend = strchr(ident, '>');
3741 if (nameend && emailend)
3742 *nameend = *emailend = 0;
3743 ident = chomp_string(ident);
3744 if (!*ident) {
3745 if (nameend)
3746 ident = chomp_string(nameend + 1);
3747 if (!*ident)
3748 ident = "Unknown";
3751 *author = get_author(ident);
3753 /* Parse epoch and timezone */
3754 if (emailend && emailend[1] == ' ') {
3755 char *secs = emailend + 2;
3756 char *zone = strchr(secs, ' ');
3758 parse_timesec(time, secs);
3760 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3761 parse_timezone(time, zone + 1);
3765 static bool
3766 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3768 char rev[SIZEOF_REV];
3769 const char *revlist_argv[] = {
3770 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3772 struct menu_item *items;
3773 char text[SIZEOF_STR];
3774 bool ok = TRUE;
3775 int i;
3777 items = calloc(*parents + 1, sizeof(*items));
3778 if (!items)
3779 return FALSE;
3781 for (i = 0; i < *parents; i++) {
3782 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3783 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3784 !(items[i].text = strdup(text))) {
3785 ok = FALSE;
3786 break;
3790 if (ok) {
3791 *parents = 0;
3792 ok = prompt_menu("Select parent", items, parents);
3794 for (i = 0; items[i].text; i++)
3795 free((char *) items[i].text);
3796 free(items);
3797 return ok;
3800 static bool
3801 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3803 char buf[SIZEOF_STR * 4];
3804 const char *revlist_argv[] = {
3805 "git", "log", "--no-color", "-1",
3806 "--pretty=format:%P", id, "--", path, NULL
3808 int parents;
3810 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3811 (parents = strlen(buf) / 40) < 0) {
3812 report("Failed to get parent information");
3813 return FALSE;
3815 } else if (parents == 0) {
3816 if (path)
3817 report("Path '%s' does not exist in the parent", path);
3818 else
3819 report("The selected commit has no parents");
3820 return FALSE;
3823 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3824 return FALSE;
3826 string_copy_rev(rev, &buf[41 * parents]);
3827 return TRUE;
3831 * Pager backend
3834 static bool
3835 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3837 char text[SIZEOF_STR];
3839 if (opt_line_number && draw_lineno(view, lineno))
3840 return TRUE;
3842 string_expand(text, sizeof(text), line->data, opt_tab_size);
3843 draw_text(view, line->type, text, TRUE);
3844 return TRUE;
3847 static bool
3848 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3850 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3851 char ref[SIZEOF_STR];
3853 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3854 return TRUE;
3856 /* This is the only fatal call, since it can "corrupt" the buffer. */
3857 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3858 return FALSE;
3860 return TRUE;
3863 static void
3864 add_pager_refs(struct view *view, struct line *line)
3866 char buf[SIZEOF_STR];
3867 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3868 struct ref_list *list;
3869 size_t bufpos = 0, i;
3870 const char *sep = "Refs: ";
3871 bool is_tag = FALSE;
3873 assert(line->type == LINE_COMMIT);
3875 list = get_ref_list(commit_id);
3876 if (!list) {
3877 if (view == VIEW(REQ_VIEW_DIFF))
3878 goto try_add_describe_ref;
3879 return;
3882 for (i = 0; i < list->size; i++) {
3883 struct ref *ref = list->refs[i];
3884 const char *fmt = ref->tag ? "%s[%s]" :
3885 ref->remote ? "%s<%s>" : "%s%s";
3887 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3888 return;
3889 sep = ", ";
3890 if (ref->tag)
3891 is_tag = TRUE;
3894 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3895 try_add_describe_ref:
3896 /* Add <tag>-g<commit_id> "fake" reference. */
3897 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3898 return;
3901 if (bufpos == 0)
3902 return;
3904 add_line_text(view, buf, LINE_PP_REFS);
3907 static bool
3908 pager_read(struct view *view, char *data)
3910 struct line *line;
3912 if (!data)
3913 return TRUE;
3915 line = add_line_text(view, data, get_line_type(data));
3916 if (!line)
3917 return FALSE;
3919 if (line->type == LINE_COMMIT &&
3920 (view == VIEW(REQ_VIEW_DIFF) ||
3921 view == VIEW(REQ_VIEW_LOG)))
3922 add_pager_refs(view, line);
3924 return TRUE;
3927 static enum request
3928 pager_request(struct view *view, enum request request, struct line *line)
3930 int split = 0;
3932 if (request != REQ_ENTER)
3933 return request;
3935 if (line->type == LINE_COMMIT &&
3936 (view == VIEW(REQ_VIEW_LOG) ||
3937 view == VIEW(REQ_VIEW_PAGER))) {
3938 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3939 split = 1;
3942 /* Always scroll the view even if it was split. That way
3943 * you can use Enter to scroll through the log view and
3944 * split open each commit diff. */
3945 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3947 /* FIXME: A minor workaround. Scrolling the view will call report("")
3948 * but if we are scrolling a non-current view this won't properly
3949 * update the view title. */
3950 if (split)
3951 update_view_title(view);
3953 return REQ_NONE;
3956 static bool
3957 pager_grep(struct view *view, struct line *line)
3959 const char *text[] = { line->data, NULL };
3961 return grep_text(view, text);
3964 static void
3965 pager_select(struct view *view, struct line *line)
3967 if (line->type == LINE_COMMIT) {
3968 char *text = (char *)line->data + STRING_SIZE("commit ");
3970 if (view != VIEW(REQ_VIEW_PAGER))
3971 string_copy_rev(view->ref, text);
3972 string_copy_rev(ref_commit, text);
3976 static struct view_ops pager_ops = {
3977 "line",
3978 NULL,
3979 NULL,
3980 pager_read,
3981 pager_draw,
3982 pager_request,
3983 pager_grep,
3984 pager_select,
3987 static const char *log_argv[SIZEOF_ARG] = {
3988 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3991 static enum request
3992 log_request(struct view *view, enum request request, struct line *line)
3994 switch (request) {
3995 case REQ_REFRESH:
3996 load_refs();
3997 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3998 return REQ_NONE;
3999 default:
4000 return pager_request(view, request, line);
4004 static struct view_ops log_ops = {
4005 "line",
4006 log_argv,
4007 NULL,
4008 pager_read,
4009 pager_draw,
4010 log_request,
4011 pager_grep,
4012 pager_select,
4015 static const char *diff_argv[SIZEOF_ARG] = {
4016 "git", "show", "--pretty=fuller", "--no-color", "--root",
4017 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4020 static struct view_ops diff_ops = {
4021 "line",
4022 diff_argv,
4023 NULL,
4024 pager_read,
4025 pager_draw,
4026 pager_request,
4027 pager_grep,
4028 pager_select,
4032 * Help backend
4035 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4037 static bool
4038 help_open_keymap_title(struct view *view, enum keymap keymap)
4040 struct line *line;
4042 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4043 help_keymap_hidden[keymap] ? '+' : '-',
4044 enum_name(keymap_table[keymap]));
4045 if (line)
4046 line->other = keymap;
4048 return help_keymap_hidden[keymap];
4051 static void
4052 help_open_keymap(struct view *view, enum keymap keymap)
4054 const char *group = NULL;
4055 char buf[SIZEOF_STR];
4056 size_t bufpos;
4057 bool add_title = TRUE;
4058 int i;
4060 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4061 const char *key = NULL;
4063 if (req_info[i].request == REQ_NONE)
4064 continue;
4066 if (!req_info[i].request) {
4067 group = req_info[i].help;
4068 continue;
4071 key = get_keys(keymap, req_info[i].request, TRUE);
4072 if (!key || !*key)
4073 continue;
4075 if (add_title && help_open_keymap_title(view, keymap))
4076 return;
4077 add_title = FALSE;
4079 if (group) {
4080 add_line_text(view, group, LINE_HELP_GROUP);
4081 group = NULL;
4084 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4085 enum_name(req_info[i]), req_info[i].help);
4088 group = "External commands:";
4090 for (i = 0; i < run_requests; i++) {
4091 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4092 const char *key;
4093 int argc;
4095 if (!req || req->keymap != keymap)
4096 continue;
4098 key = get_key_name(req->key);
4099 if (!*key)
4100 key = "(no key defined)";
4102 if (add_title && help_open_keymap_title(view, keymap))
4103 return;
4104 if (group) {
4105 add_line_text(view, group, LINE_HELP_GROUP);
4106 group = NULL;
4109 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4110 if (!string_format_from(buf, &bufpos, "%s%s",
4111 argc ? " " : "", req->argv[argc]))
4112 return;
4114 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4118 static bool
4119 help_open(struct view *view)
4121 enum keymap keymap;
4123 reset_view(view);
4124 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4125 add_line_text(view, "", LINE_DEFAULT);
4127 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4128 help_open_keymap(view, keymap);
4130 return TRUE;
4133 static enum request
4134 help_request(struct view *view, enum request request, struct line *line)
4136 switch (request) {
4137 case REQ_ENTER:
4138 if (line->type == LINE_HELP_KEYMAP) {
4139 help_keymap_hidden[line->other] =
4140 !help_keymap_hidden[line->other];
4141 view->p_restore = TRUE;
4142 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4145 return REQ_NONE;
4146 default:
4147 return pager_request(view, request, line);
4151 static struct view_ops help_ops = {
4152 "line",
4153 NULL,
4154 help_open,
4155 NULL,
4156 pager_draw,
4157 help_request,
4158 pager_grep,
4159 pager_select,
4164 * Tree backend
4167 struct tree_stack_entry {
4168 struct tree_stack_entry *prev; /* Entry below this in the stack */
4169 unsigned long lineno; /* Line number to restore */
4170 char *name; /* Position of name in opt_path */
4173 /* The top of the path stack. */
4174 static struct tree_stack_entry *tree_stack = NULL;
4175 unsigned long tree_lineno = 0;
4177 static void
4178 pop_tree_stack_entry(void)
4180 struct tree_stack_entry *entry = tree_stack;
4182 tree_lineno = entry->lineno;
4183 entry->name[0] = 0;
4184 tree_stack = entry->prev;
4185 free(entry);
4188 static void
4189 push_tree_stack_entry(const char *name, unsigned long lineno)
4191 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4192 size_t pathlen = strlen(opt_path);
4194 if (!entry)
4195 return;
4197 entry->prev = tree_stack;
4198 entry->name = opt_path + pathlen;
4199 tree_stack = entry;
4201 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4202 pop_tree_stack_entry();
4203 return;
4206 /* Move the current line to the first tree entry. */
4207 tree_lineno = 1;
4208 entry->lineno = lineno;
4211 /* Parse output from git-ls-tree(1):
4213 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4216 #define SIZEOF_TREE_ATTR \
4217 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4219 #define SIZEOF_TREE_MODE \
4220 STRING_SIZE("100644 ")
4222 #define TREE_ID_OFFSET \
4223 STRING_SIZE("100644 blob ")
4225 struct tree_entry {
4226 char id[SIZEOF_REV];
4227 mode_t mode;
4228 struct time time; /* Date from the author ident. */
4229 const char *author; /* Author of the commit. */
4230 char name[1];
4233 static const char *
4234 tree_path(const struct line *line)
4236 return ((struct tree_entry *) line->data)->name;
4239 static int
4240 tree_compare_entry(const struct line *line1, const struct line *line2)
4242 if (line1->type != line2->type)
4243 return line1->type == LINE_TREE_DIR ? -1 : 1;
4244 return strcmp(tree_path(line1), tree_path(line2));
4247 static const enum sort_field tree_sort_fields[] = {
4248 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4250 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4252 static int
4253 tree_compare(const void *l1, const void *l2)
4255 const struct line *line1 = (const struct line *) l1;
4256 const struct line *line2 = (const struct line *) l2;
4257 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4258 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4260 if (line1->type == LINE_TREE_HEAD)
4261 return -1;
4262 if (line2->type == LINE_TREE_HEAD)
4263 return 1;
4265 switch (get_sort_field(tree_sort_state)) {
4266 case ORDERBY_DATE:
4267 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4269 case ORDERBY_AUTHOR:
4270 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4272 case ORDERBY_NAME:
4273 default:
4274 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4279 static struct line *
4280 tree_entry(struct view *view, enum line_type type, const char *path,
4281 const char *mode, const char *id)
4283 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4284 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4286 if (!entry || !line) {
4287 free(entry);
4288 return NULL;
4291 strncpy(entry->name, path, strlen(path));
4292 if (mode)
4293 entry->mode = strtoul(mode, NULL, 8);
4294 if (id)
4295 string_copy_rev(entry->id, id);
4297 return line;
4300 static bool
4301 tree_read_date(struct view *view, char *text, bool *read_date)
4303 static const char *author_name;
4304 static struct time author_time;
4306 if (!text && *read_date) {
4307 *read_date = FALSE;
4308 return TRUE;
4310 } else if (!text) {
4311 char *path = *opt_path ? opt_path : ".";
4312 /* Find next entry to process */
4313 const char *log_file[] = {
4314 "git", "log", "--no-color", "--pretty=raw",
4315 "--cc", "--raw", view->id, "--", path, NULL
4317 struct io io = {};
4319 if (!view->lines) {
4320 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4321 report("Tree is empty");
4322 return TRUE;
4325 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4326 report("Failed to load tree data");
4327 return TRUE;
4330 done_io(view->pipe);
4331 view->io = io;
4332 *read_date = TRUE;
4333 return FALSE;
4335 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4336 parse_author_line(text + STRING_SIZE("author "),
4337 &author_name, &author_time);
4339 } else if (*text == ':') {
4340 char *pos;
4341 size_t annotated = 1;
4342 size_t i;
4344 pos = strchr(text, '\t');
4345 if (!pos)
4346 return TRUE;
4347 text = pos + 1;
4348 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4349 text += strlen(opt_path);
4350 pos = strchr(text, '/');
4351 if (pos)
4352 *pos = 0;
4354 for (i = 1; i < view->lines; i++) {
4355 struct line *line = &view->line[i];
4356 struct tree_entry *entry = line->data;
4358 annotated += !!entry->author;
4359 if (entry->author || strcmp(entry->name, text))
4360 continue;
4362 entry->author = author_name;
4363 entry->time = author_time;
4364 line->dirty = 1;
4365 break;
4368 if (annotated == view->lines)
4369 kill_io(view->pipe);
4371 return TRUE;
4374 static bool
4375 tree_read(struct view *view, char *text)
4377 static bool read_date = FALSE;
4378 struct tree_entry *data;
4379 struct line *entry, *line;
4380 enum line_type type;
4381 size_t textlen = text ? strlen(text) : 0;
4382 char *path = text + SIZEOF_TREE_ATTR;
4384 if (read_date || !text)
4385 return tree_read_date(view, text, &read_date);
4387 if (textlen <= SIZEOF_TREE_ATTR)
4388 return FALSE;
4389 if (view->lines == 0 &&
4390 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4391 return FALSE;
4393 /* Strip the path part ... */
4394 if (*opt_path) {
4395 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4396 size_t striplen = strlen(opt_path);
4398 if (pathlen > striplen)
4399 memmove(path, path + striplen,
4400 pathlen - striplen + 1);
4402 /* Insert "link" to parent directory. */
4403 if (view->lines == 1 &&
4404 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4405 return FALSE;
4408 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4409 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4410 if (!entry)
4411 return FALSE;
4412 data = entry->data;
4414 /* Skip "Directory ..." and ".." line. */
4415 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4416 if (tree_compare_entry(line, entry) <= 0)
4417 continue;
4419 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4421 line->data = data;
4422 line->type = type;
4423 for (; line <= entry; line++)
4424 line->dirty = line->cleareol = 1;
4425 return TRUE;
4428 if (tree_lineno > view->lineno) {
4429 view->lineno = tree_lineno;
4430 tree_lineno = 0;
4433 return TRUE;
4436 static bool
4437 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4439 struct tree_entry *entry = line->data;
4441 if (line->type == LINE_TREE_HEAD) {
4442 if (draw_text(view, line->type, "Directory path /", TRUE))
4443 return TRUE;
4444 } else {
4445 if (draw_mode(view, entry->mode))
4446 return TRUE;
4448 if (opt_author && draw_author(view, entry->author))
4449 return TRUE;
4451 if (opt_date && draw_date(view, &entry->time))
4452 return TRUE;
4454 if (draw_text(view, line->type, entry->name, TRUE))
4455 return TRUE;
4456 return TRUE;
4459 static void
4460 open_blob_editor()
4462 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4463 int fd = mkstemp(file);
4465 if (fd == -1)
4466 report("Failed to create temporary file");
4467 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4468 report("Failed to save blob data to file");
4469 else
4470 open_editor(file);
4471 if (fd != -1)
4472 unlink(file);
4475 static enum request
4476 tree_request(struct view *view, enum request request, struct line *line)
4478 enum open_flags flags;
4480 switch (request) {
4481 case REQ_VIEW_BLAME:
4482 if (line->type != LINE_TREE_FILE) {
4483 report("Blame only supported for files");
4484 return REQ_NONE;
4487 string_copy(opt_ref, view->vid);
4488 return request;
4490 case REQ_EDIT:
4491 if (line->type != LINE_TREE_FILE) {
4492 report("Edit only supported for files");
4493 } else if (!is_head_commit(view->vid)) {
4494 open_blob_editor();
4495 } else {
4496 open_editor(opt_file);
4498 return REQ_NONE;
4500 case REQ_TOGGLE_SORT_FIELD:
4501 case REQ_TOGGLE_SORT_ORDER:
4502 sort_view(view, request, &tree_sort_state, tree_compare);
4503 return REQ_NONE;
4505 case REQ_PARENT:
4506 if (!*opt_path) {
4507 /* quit view if at top of tree */
4508 return REQ_VIEW_CLOSE;
4510 /* fake 'cd ..' */
4511 line = &view->line[1];
4512 break;
4514 case REQ_ENTER:
4515 break;
4517 default:
4518 return request;
4521 /* Cleanup the stack if the tree view is at a different tree. */
4522 while (!*opt_path && tree_stack)
4523 pop_tree_stack_entry();
4525 switch (line->type) {
4526 case LINE_TREE_DIR:
4527 /* Depending on whether it is a subdirectory or parent link
4528 * mangle the path buffer. */
4529 if (line == &view->line[1] && *opt_path) {
4530 pop_tree_stack_entry();
4532 } else {
4533 const char *basename = tree_path(line);
4535 push_tree_stack_entry(basename, view->lineno);
4538 /* Trees and subtrees share the same ID, so they are not not
4539 * unique like blobs. */
4540 flags = OPEN_RELOAD;
4541 request = REQ_VIEW_TREE;
4542 break;
4544 case LINE_TREE_FILE:
4545 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4546 request = REQ_VIEW_BLOB;
4547 break;
4549 default:
4550 return REQ_NONE;
4553 open_view(view, request, flags);
4554 if (request == REQ_VIEW_TREE)
4555 view->lineno = tree_lineno;
4557 return REQ_NONE;
4560 static bool
4561 tree_grep(struct view *view, struct line *line)
4563 struct tree_entry *entry = line->data;
4564 const char *text[] = {
4565 entry->name,
4566 opt_author ? entry->author : "",
4567 opt_date ? mkdate(&entry->time) : "",
4568 NULL
4571 return grep_text(view, text);
4574 static void
4575 tree_select(struct view *view, struct line *line)
4577 struct tree_entry *entry = line->data;
4579 if (line->type == LINE_TREE_FILE) {
4580 string_copy_rev(ref_blob, entry->id);
4581 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4583 } else if (line->type != LINE_TREE_DIR) {
4584 return;
4587 string_copy_rev(view->ref, entry->id);
4590 static bool
4591 tree_prepare(struct view *view)
4593 if (view->lines == 0 && opt_prefix[0]) {
4594 char *pos = opt_prefix;
4596 while (pos && *pos) {
4597 char *end = strchr(pos, '/');
4599 if (end)
4600 *end = 0;
4601 push_tree_stack_entry(pos, 0);
4602 pos = end;
4603 if (end) {
4604 *end = '/';
4605 pos++;
4609 } else if (strcmp(view->vid, view->id)) {
4610 opt_path[0] = 0;
4613 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4616 static const char *tree_argv[SIZEOF_ARG] = {
4617 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4620 static struct view_ops tree_ops = {
4621 "file",
4622 tree_argv,
4623 NULL,
4624 tree_read,
4625 tree_draw,
4626 tree_request,
4627 tree_grep,
4628 tree_select,
4629 tree_prepare,
4632 static bool
4633 blob_read(struct view *view, char *line)
4635 if (!line)
4636 return TRUE;
4637 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4640 static enum request
4641 blob_request(struct view *view, enum request request, struct line *line)
4643 switch (request) {
4644 case REQ_EDIT:
4645 open_blob_editor();
4646 return REQ_NONE;
4647 default:
4648 return pager_request(view, request, line);
4652 static const char *blob_argv[SIZEOF_ARG] = {
4653 "git", "cat-file", "blob", "%(blob)", NULL
4656 static struct view_ops blob_ops = {
4657 "line",
4658 blob_argv,
4659 NULL,
4660 blob_read,
4661 pager_draw,
4662 blob_request,
4663 pager_grep,
4664 pager_select,
4668 * Blame backend
4670 * Loading the blame view is a two phase job:
4672 * 1. File content is read either using opt_file from the
4673 * filesystem or using git-cat-file.
4674 * 2. Then blame information is incrementally added by
4675 * reading output from git-blame.
4678 static const char *blame_head_argv[] = {
4679 "git", "blame", "--incremental", "--", "%(file)", NULL
4682 static const char *blame_ref_argv[] = {
4683 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4686 static const char *blame_cat_file_argv[] = {
4687 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4690 struct blame_commit {
4691 char id[SIZEOF_REV]; /* SHA1 ID. */
4692 char title[128]; /* First line of the commit message. */
4693 const char *author; /* Author of the commit. */
4694 struct time time; /* Date from the author ident. */
4695 char filename[128]; /* Name of file. */
4696 bool has_previous; /* Was a "previous" line detected. */
4699 struct blame {
4700 struct blame_commit *commit;
4701 unsigned long lineno;
4702 char text[1];
4705 static bool
4706 blame_open(struct view *view)
4708 char path[SIZEOF_STR];
4710 if (!view->parent && *opt_prefix) {
4711 string_copy(path, opt_file);
4712 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4713 return FALSE;
4716 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4717 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4718 return FALSE;
4721 setup_update(view, opt_file);
4722 string_format(view->ref, "%s ...", opt_file);
4724 return TRUE;
4727 static struct blame_commit *
4728 get_blame_commit(struct view *view, const char *id)
4730 size_t i;
4732 for (i = 0; i < view->lines; i++) {
4733 struct blame *blame = view->line[i].data;
4735 if (!blame->commit)
4736 continue;
4738 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4739 return blame->commit;
4743 struct blame_commit *commit = calloc(1, sizeof(*commit));
4745 if (commit)
4746 string_ncopy(commit->id, id, SIZEOF_REV);
4747 return commit;
4751 static bool
4752 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4754 const char *pos = *posref;
4756 *posref = NULL;
4757 pos = strchr(pos + 1, ' ');
4758 if (!pos || !isdigit(pos[1]))
4759 return FALSE;
4760 *number = atoi(pos + 1);
4761 if (*number < min || *number > max)
4762 return FALSE;
4764 *posref = pos;
4765 return TRUE;
4768 static struct blame_commit *
4769 parse_blame_commit(struct view *view, const char *text, int *blamed)
4771 struct blame_commit *commit;
4772 struct blame *blame;
4773 const char *pos = text + SIZEOF_REV - 2;
4774 size_t orig_lineno = 0;
4775 size_t lineno;
4776 size_t group;
4778 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4779 return NULL;
4781 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4782 !parse_number(&pos, &lineno, 1, view->lines) ||
4783 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4784 return NULL;
4786 commit = get_blame_commit(view, text);
4787 if (!commit)
4788 return NULL;
4790 *blamed += group;
4791 while (group--) {
4792 struct line *line = &view->line[lineno + group - 1];
4794 blame = line->data;
4795 blame->commit = commit;
4796 blame->lineno = orig_lineno + group - 1;
4797 line->dirty = 1;
4800 return commit;
4803 static bool
4804 blame_read_file(struct view *view, const char *line, bool *read_file)
4806 if (!line) {
4807 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4808 struct io io = {};
4810 if (view->lines == 0 && !view->parent)
4811 die("No blame exist for %s", view->vid);
4813 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4814 report("Failed to load blame data");
4815 return TRUE;
4818 done_io(view->pipe);
4819 view->io = io;
4820 *read_file = FALSE;
4821 return FALSE;
4823 } else {
4824 size_t linelen = strlen(line);
4825 struct blame *blame = malloc(sizeof(*blame) + linelen);
4827 if (!blame)
4828 return FALSE;
4830 blame->commit = NULL;
4831 strncpy(blame->text, line, linelen);
4832 blame->text[linelen] = 0;
4833 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4837 static bool
4838 match_blame_header(const char *name, char **line)
4840 size_t namelen = strlen(name);
4841 bool matched = !strncmp(name, *line, namelen);
4843 if (matched)
4844 *line += namelen;
4846 return matched;
4849 static bool
4850 blame_read(struct view *view, char *line)
4852 static struct blame_commit *commit = NULL;
4853 static int blamed = 0;
4854 static bool read_file = TRUE;
4856 if (read_file)
4857 return blame_read_file(view, line, &read_file);
4859 if (!line) {
4860 /* Reset all! */
4861 commit = NULL;
4862 blamed = 0;
4863 read_file = TRUE;
4864 string_format(view->ref, "%s", view->vid);
4865 if (view_is_displayed(view)) {
4866 update_view_title(view);
4867 redraw_view_from(view, 0);
4869 return TRUE;
4872 if (!commit) {
4873 commit = parse_blame_commit(view, line, &blamed);
4874 string_format(view->ref, "%s %2d%%", view->vid,
4875 view->lines ? blamed * 100 / view->lines : 0);
4877 } else if (match_blame_header("author ", &line)) {
4878 commit->author = get_author(line);
4880 } else if (match_blame_header("author-time ", &line)) {
4881 parse_timesec(&commit->time, line);
4883 } else if (match_blame_header("author-tz ", &line)) {
4884 parse_timezone(&commit->time, line);
4886 } else if (match_blame_header("summary ", &line)) {
4887 string_ncopy(commit->title, line, strlen(line));
4889 } else if (match_blame_header("previous ", &line)) {
4890 commit->has_previous = TRUE;
4892 } else if (match_blame_header("filename ", &line)) {
4893 string_ncopy(commit->filename, line, strlen(line));
4894 commit = NULL;
4897 return TRUE;
4900 static bool
4901 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4903 struct blame *blame = line->data;
4904 struct time *time = NULL;
4905 const char *id = NULL, *author = NULL;
4906 char text[SIZEOF_STR];
4908 if (blame->commit && *blame->commit->filename) {
4909 id = blame->commit->id;
4910 author = blame->commit->author;
4911 time = &blame->commit->time;
4914 if (opt_date && draw_date(view, time))
4915 return TRUE;
4917 if (opt_author && draw_author(view, author))
4918 return TRUE;
4920 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4921 return TRUE;
4923 if (draw_lineno(view, lineno))
4924 return TRUE;
4926 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4927 draw_text(view, LINE_DEFAULT, text, TRUE);
4928 return TRUE;
4931 static bool
4932 check_blame_commit(struct blame *blame, bool check_null_id)
4934 if (!blame->commit)
4935 report("Commit data not loaded yet");
4936 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4937 report("No commit exist for the selected line");
4938 else
4939 return TRUE;
4940 return FALSE;
4943 static void
4944 setup_blame_parent_line(struct view *view, struct blame *blame)
4946 const char *diff_tree_argv[] = {
4947 "git", "diff-tree", "-U0", blame->commit->id,
4948 "--", blame->commit->filename, NULL
4950 struct io io = {};
4951 int parent_lineno = -1;
4952 int blamed_lineno = -1;
4953 char *line;
4955 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4956 return;
4958 while ((line = io_get(&io, '\n', TRUE))) {
4959 if (*line == '@') {
4960 char *pos = strchr(line, '+');
4962 parent_lineno = atoi(line + 4);
4963 if (pos)
4964 blamed_lineno = atoi(pos + 1);
4966 } else if (*line == '+' && parent_lineno != -1) {
4967 if (blame->lineno == blamed_lineno - 1 &&
4968 !strcmp(blame->text, line + 1)) {
4969 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4970 break;
4972 blamed_lineno++;
4976 done_io(&io);
4979 static enum request
4980 blame_request(struct view *view, enum request request, struct line *line)
4982 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4983 struct blame *blame = line->data;
4985 switch (request) {
4986 case REQ_VIEW_BLAME:
4987 if (check_blame_commit(blame, TRUE)) {
4988 string_copy(opt_ref, blame->commit->id);
4989 string_copy(opt_file, blame->commit->filename);
4990 if (blame->lineno)
4991 view->lineno = blame->lineno;
4992 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4994 break;
4996 case REQ_PARENT:
4997 if (check_blame_commit(blame, TRUE) &&
4998 select_commit_parent(blame->commit->id, opt_ref,
4999 blame->commit->filename)) {
5000 string_copy(opt_file, blame->commit->filename);
5001 setup_blame_parent_line(view, blame);
5002 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5004 break;
5006 case REQ_ENTER:
5007 if (!check_blame_commit(blame, FALSE))
5008 break;
5010 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5011 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5012 break;
5014 if (!strcmp(blame->commit->id, NULL_ID)) {
5015 struct view *diff = VIEW(REQ_VIEW_DIFF);
5016 const char *diff_index_argv[] = {
5017 "git", "diff-index", "--root", "--patch-with-stat",
5018 "-C", "-M", "HEAD", "--", view->vid, NULL
5021 if (!blame->commit->has_previous) {
5022 diff_index_argv[1] = "diff";
5023 diff_index_argv[2] = "--no-color";
5024 diff_index_argv[6] = "--";
5025 diff_index_argv[7] = "/dev/null";
5028 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5029 report("Failed to allocate diff command");
5030 break;
5032 flags |= OPEN_PREPARED;
5035 open_view(view, REQ_VIEW_DIFF, flags);
5036 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5037 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5038 break;
5040 default:
5041 return request;
5044 return REQ_NONE;
5047 static bool
5048 blame_grep(struct view *view, struct line *line)
5050 struct blame *blame = line->data;
5051 struct blame_commit *commit = blame->commit;
5052 const char *text[] = {
5053 blame->text,
5054 commit ? commit->title : "",
5055 commit ? commit->id : "",
5056 commit && opt_author ? commit->author : "",
5057 commit && opt_date ? mkdate(&commit->time) : "",
5058 NULL
5061 return grep_text(view, text);
5064 static void
5065 blame_select(struct view *view, struct line *line)
5067 struct blame *blame = line->data;
5068 struct blame_commit *commit = blame->commit;
5070 if (!commit)
5071 return;
5073 if (!strcmp(commit->id, NULL_ID))
5074 string_ncopy(ref_commit, "HEAD", 4);
5075 else
5076 string_copy_rev(ref_commit, commit->id);
5079 static struct view_ops blame_ops = {
5080 "line",
5081 NULL,
5082 blame_open,
5083 blame_read,
5084 blame_draw,
5085 blame_request,
5086 blame_grep,
5087 blame_select,
5091 * Branch backend
5094 struct branch {
5095 const char *author; /* Author of the last commit. */
5096 struct time time; /* Date of the last activity. */
5097 const struct ref *ref; /* Name and commit ID information. */
5100 static const struct ref branch_all;
5102 static const enum sort_field branch_sort_fields[] = {
5103 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5105 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5107 static int
5108 branch_compare(const void *l1, const void *l2)
5110 const struct branch *branch1 = ((const struct line *) l1)->data;
5111 const struct branch *branch2 = ((const struct line *) l2)->data;
5113 switch (get_sort_field(branch_sort_state)) {
5114 case ORDERBY_DATE:
5115 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5117 case ORDERBY_AUTHOR:
5118 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5120 case ORDERBY_NAME:
5121 default:
5122 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5126 static bool
5127 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5129 struct branch *branch = line->data;
5130 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5132 if (opt_date && draw_date(view, &branch->time))
5133 return TRUE;
5135 if (opt_author && draw_author(view, branch->author))
5136 return TRUE;
5138 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5139 return TRUE;
5142 static enum request
5143 branch_request(struct view *view, enum request request, struct line *line)
5145 struct branch *branch = line->data;
5147 switch (request) {
5148 case REQ_REFRESH:
5149 load_refs();
5150 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5151 return REQ_NONE;
5153 case REQ_TOGGLE_SORT_FIELD:
5154 case REQ_TOGGLE_SORT_ORDER:
5155 sort_view(view, request, &branch_sort_state, branch_compare);
5156 return REQ_NONE;
5158 case REQ_ENTER:
5159 if (branch->ref == &branch_all) {
5160 const char *all_branches_argv[] = {
5161 "git", "log", "--no-color", "--pretty=raw", "--parents",
5162 "--topo-order", "--all", NULL
5164 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5166 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5167 report("Failed to load view of all branches");
5168 return REQ_NONE;
5170 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5171 } else {
5172 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5174 return REQ_NONE;
5176 default:
5177 return request;
5181 static bool
5182 branch_read(struct view *view, char *line)
5184 static char id[SIZEOF_REV];
5185 struct branch *reference;
5186 size_t i;
5188 if (!line)
5189 return TRUE;
5191 switch (get_line_type(line)) {
5192 case LINE_COMMIT:
5193 string_copy_rev(id, line + STRING_SIZE("commit "));
5194 return TRUE;
5196 case LINE_AUTHOR:
5197 for (i = 0, reference = NULL; i < view->lines; i++) {
5198 struct branch *branch = view->line[i].data;
5200 if (strcmp(branch->ref->id, id))
5201 continue;
5203 view->line[i].dirty = TRUE;
5204 if (reference) {
5205 branch->author = reference->author;
5206 branch->time = reference->time;
5207 continue;
5210 parse_author_line(line + STRING_SIZE("author "),
5211 &branch->author, &branch->time);
5212 reference = branch;
5214 return TRUE;
5216 default:
5217 return TRUE;
5222 static bool
5223 branch_open_visitor(void *data, const struct ref *ref)
5225 struct view *view = data;
5226 struct branch *branch;
5228 if (ref->tag || ref->ltag || ref->remote)
5229 return TRUE;
5231 branch = calloc(1, sizeof(*branch));
5232 if (!branch)
5233 return FALSE;
5235 branch->ref = ref;
5236 return !!add_line_data(view, branch, LINE_DEFAULT);
5239 static bool
5240 branch_open(struct view *view)
5242 const char *branch_log[] = {
5243 "git", "log", "--no-color", "--pretty=raw",
5244 "--simplify-by-decoration", "--all", NULL
5247 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5248 report("Failed to load branch data");
5249 return TRUE;
5252 setup_update(view, view->id);
5253 branch_open_visitor(view, &branch_all);
5254 foreach_ref(branch_open_visitor, view);
5255 view->p_restore = TRUE;
5257 return TRUE;
5260 static bool
5261 branch_grep(struct view *view, struct line *line)
5263 struct branch *branch = line->data;
5264 const char *text[] = {
5265 branch->ref->name,
5266 branch->author,
5267 NULL
5270 return grep_text(view, text);
5273 static void
5274 branch_select(struct view *view, struct line *line)
5276 struct branch *branch = line->data;
5278 string_copy_rev(view->ref, branch->ref->id);
5279 string_copy_rev(ref_commit, branch->ref->id);
5280 string_copy_rev(ref_head, branch->ref->id);
5283 static struct view_ops branch_ops = {
5284 "branch",
5285 NULL,
5286 branch_open,
5287 branch_read,
5288 branch_draw,
5289 branch_request,
5290 branch_grep,
5291 branch_select,
5295 * Status backend
5298 struct status {
5299 char status;
5300 struct {
5301 mode_t mode;
5302 char rev[SIZEOF_REV];
5303 char name[SIZEOF_STR];
5304 } old;
5305 struct {
5306 mode_t mode;
5307 char rev[SIZEOF_REV];
5308 char name[SIZEOF_STR];
5309 } new;
5312 static char status_onbranch[SIZEOF_STR];
5313 static struct status stage_status;
5314 static enum line_type stage_line_type;
5315 static size_t stage_chunks;
5316 static int *stage_chunk;
5318 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5320 /* This should work even for the "On branch" line. */
5321 static inline bool
5322 status_has_none(struct view *view, struct line *line)
5324 return line < view->line + view->lines && !line[1].data;
5327 /* Get fields from the diff line:
5328 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5330 static inline bool
5331 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5333 const char *old_mode = buf + 1;
5334 const char *new_mode = buf + 8;
5335 const char *old_rev = buf + 15;
5336 const char *new_rev = buf + 56;
5337 const char *status = buf + 97;
5339 if (bufsize < 98 ||
5340 old_mode[-1] != ':' ||
5341 new_mode[-1] != ' ' ||
5342 old_rev[-1] != ' ' ||
5343 new_rev[-1] != ' ' ||
5344 status[-1] != ' ')
5345 return FALSE;
5347 file->status = *status;
5349 string_copy_rev(file->old.rev, old_rev);
5350 string_copy_rev(file->new.rev, new_rev);
5352 file->old.mode = strtoul(old_mode, NULL, 8);
5353 file->new.mode = strtoul(new_mode, NULL, 8);
5355 file->old.name[0] = file->new.name[0] = 0;
5357 return TRUE;
5360 static bool
5361 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5363 struct status *unmerged = NULL;
5364 char *buf;
5365 struct io io = {};
5367 if (!run_io(&io, argv, opt_cdup, IO_RD))
5368 return FALSE;
5370 add_line_data(view, NULL, type);
5372 while ((buf = io_get(&io, 0, TRUE))) {
5373 struct status *file = unmerged;
5375 if (!file) {
5376 file = calloc(1, sizeof(*file));
5377 if (!file || !add_line_data(view, file, type))
5378 goto error_out;
5381 /* Parse diff info part. */
5382 if (status) {
5383 file->status = status;
5384 if (status == 'A')
5385 string_copy(file->old.rev, NULL_ID);
5387 } else if (!file->status || file == unmerged) {
5388 if (!status_get_diff(file, buf, strlen(buf)))
5389 goto error_out;
5391 buf = io_get(&io, 0, TRUE);
5392 if (!buf)
5393 break;
5395 /* Collapse all modified entries that follow an
5396 * associated unmerged entry. */
5397 if (unmerged == file) {
5398 unmerged->status = 'U';
5399 unmerged = NULL;
5400 } else if (file->status == 'U') {
5401 unmerged = file;
5405 /* Grab the old name for rename/copy. */
5406 if (!*file->old.name &&
5407 (file->status == 'R' || file->status == 'C')) {
5408 string_ncopy(file->old.name, buf, strlen(buf));
5410 buf = io_get(&io, 0, TRUE);
5411 if (!buf)
5412 break;
5415 /* git-ls-files just delivers a NUL separated list of
5416 * file names similar to the second half of the
5417 * git-diff-* output. */
5418 string_ncopy(file->new.name, buf, strlen(buf));
5419 if (!*file->old.name)
5420 string_copy(file->old.name, file->new.name);
5421 file = NULL;
5424 if (io_error(&io)) {
5425 error_out:
5426 done_io(&io);
5427 return FALSE;
5430 if (!view->line[view->lines - 1].data)
5431 add_line_data(view, NULL, LINE_STAT_NONE);
5433 done_io(&io);
5434 return TRUE;
5437 /* Don't show unmerged entries in the staged section. */
5438 static const char *status_diff_index_argv[] = {
5439 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5440 "--cached", "-M", "HEAD", NULL
5443 static const char *status_diff_files_argv[] = {
5444 "git", "diff-files", "-z", NULL
5447 static const char *status_list_other_argv[] = {
5448 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5451 static const char *status_list_no_head_argv[] = {
5452 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5455 static const char *update_index_argv[] = {
5456 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5459 /* Restore the previous line number to stay in the context or select a
5460 * line with something that can be updated. */
5461 static void
5462 status_restore(struct view *view)
5464 if (view->p_lineno >= view->lines)
5465 view->p_lineno = view->lines - 1;
5466 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5467 view->p_lineno++;
5468 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5469 view->p_lineno--;
5471 /* If the above fails, always skip the "On branch" line. */
5472 if (view->p_lineno < view->lines)
5473 view->lineno = view->p_lineno;
5474 else
5475 view->lineno = 1;
5477 if (view->lineno < view->offset)
5478 view->offset = view->lineno;
5479 else if (view->offset + view->height <= view->lineno)
5480 view->offset = view->lineno - view->height + 1;
5482 view->p_restore = FALSE;
5485 static void
5486 status_update_onbranch(void)
5488 static const char *paths[][2] = {
5489 { "rebase-apply/rebasing", "Rebasing" },
5490 { "rebase-apply/applying", "Applying mailbox" },
5491 { "rebase-apply/", "Rebasing mailbox" },
5492 { "rebase-merge/interactive", "Interactive rebase" },
5493 { "rebase-merge/", "Rebase merge" },
5494 { "MERGE_HEAD", "Merging" },
5495 { "BISECT_LOG", "Bisecting" },
5496 { "HEAD", "On branch" },
5498 char buf[SIZEOF_STR];
5499 struct stat stat;
5500 int i;
5502 if (is_initial_commit()) {
5503 string_copy(status_onbranch, "Initial commit");
5504 return;
5507 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5508 char *head = opt_head;
5510 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5511 lstat(buf, &stat) < 0)
5512 continue;
5514 if (!*opt_head) {
5515 struct io io = {};
5517 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5518 io_read_buf(&io, buf, sizeof(buf))) {
5519 head = buf;
5520 if (!prefixcmp(head, "refs/heads/"))
5521 head += STRING_SIZE("refs/heads/");
5525 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5526 string_copy(status_onbranch, opt_head);
5527 return;
5530 string_copy(status_onbranch, "Not currently on any branch");
5533 /* First parse staged info using git-diff-index(1), then parse unstaged
5534 * info using git-diff-files(1), and finally untracked files using
5535 * git-ls-files(1). */
5536 static bool
5537 status_open(struct view *view)
5539 reset_view(view);
5541 add_line_data(view, NULL, LINE_STAT_HEAD);
5542 status_update_onbranch();
5544 run_io_bg(update_index_argv);
5546 if (is_initial_commit()) {
5547 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5548 return FALSE;
5549 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5550 return FALSE;
5553 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5554 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5555 return FALSE;
5557 /* Restore the exact position or use the specialized restore
5558 * mode? */
5559 if (!view->p_restore)
5560 status_restore(view);
5561 return TRUE;
5564 static bool
5565 status_draw(struct view *view, struct line *line, unsigned int lineno)
5567 struct status *status = line->data;
5568 enum line_type type;
5569 const char *text;
5571 if (!status) {
5572 switch (line->type) {
5573 case LINE_STAT_STAGED:
5574 type = LINE_STAT_SECTION;
5575 text = "Changes to be committed:";
5576 break;
5578 case LINE_STAT_UNSTAGED:
5579 type = LINE_STAT_SECTION;
5580 text = "Changed but not updated:";
5581 break;
5583 case LINE_STAT_UNTRACKED:
5584 type = LINE_STAT_SECTION;
5585 text = "Untracked files:";
5586 break;
5588 case LINE_STAT_NONE:
5589 type = LINE_DEFAULT;
5590 text = " (no files)";
5591 break;
5593 case LINE_STAT_HEAD:
5594 type = LINE_STAT_HEAD;
5595 text = status_onbranch;
5596 break;
5598 default:
5599 return FALSE;
5601 } else {
5602 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5604 buf[0] = status->status;
5605 if (draw_text(view, line->type, buf, TRUE))
5606 return TRUE;
5607 type = LINE_DEFAULT;
5608 text = status->new.name;
5611 draw_text(view, type, text, TRUE);
5612 return TRUE;
5615 static enum request
5616 status_load_error(struct view *view, struct view *stage, const char *path)
5618 if (displayed_views() == 2 || display[current_view] != view)
5619 maximize_view(view);
5620 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5621 return REQ_NONE;
5624 static enum request
5625 status_enter(struct view *view, struct line *line)
5627 struct status *status = line->data;
5628 const char *oldpath = status ? status->old.name : NULL;
5629 /* Diffs for unmerged entries are empty when passing the new
5630 * path, so leave it empty. */
5631 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5632 const char *info;
5633 enum open_flags split;
5634 struct view *stage = VIEW(REQ_VIEW_STAGE);
5636 if (line->type == LINE_STAT_NONE ||
5637 (!status && line[1].type == LINE_STAT_NONE)) {
5638 report("No file to diff");
5639 return REQ_NONE;
5642 switch (line->type) {
5643 case LINE_STAT_STAGED:
5644 if (is_initial_commit()) {
5645 const char *no_head_diff_argv[] = {
5646 "git", "diff", "--no-color", "--patch-with-stat",
5647 "--", "/dev/null", newpath, NULL
5650 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5651 return status_load_error(view, stage, newpath);
5652 } else {
5653 const char *index_show_argv[] = {
5654 "git", "diff-index", "--root", "--patch-with-stat",
5655 "-C", "-M", "--cached", "HEAD", "--",
5656 oldpath, newpath, NULL
5659 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5660 return status_load_error(view, stage, newpath);
5663 if (status)
5664 info = "Staged changes to %s";
5665 else
5666 info = "Staged changes";
5667 break;
5669 case LINE_STAT_UNSTAGED:
5671 const char *files_show_argv[] = {
5672 "git", "diff-files", "--root", "--patch-with-stat",
5673 "-C", "-M", "--", oldpath, newpath, NULL
5676 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5677 return status_load_error(view, stage, newpath);
5678 if (status)
5679 info = "Unstaged changes to %s";
5680 else
5681 info = "Unstaged changes";
5682 break;
5684 case LINE_STAT_UNTRACKED:
5685 if (!newpath) {
5686 report("No file to show");
5687 return REQ_NONE;
5690 if (!suffixcmp(status->new.name, -1, "/")) {
5691 report("Cannot display a directory");
5692 return REQ_NONE;
5695 if (!prepare_update_file(stage, newpath))
5696 return status_load_error(view, stage, newpath);
5697 info = "Untracked file %s";
5698 break;
5700 case LINE_STAT_HEAD:
5701 return REQ_NONE;
5703 default:
5704 die("line type %d not handled in switch", line->type);
5707 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5708 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5709 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5710 if (status) {
5711 stage_status = *status;
5712 } else {
5713 memset(&stage_status, 0, sizeof(stage_status));
5716 stage_line_type = line->type;
5717 stage_chunks = 0;
5718 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5721 return REQ_NONE;
5724 static bool
5725 status_exists(struct status *status, enum line_type type)
5727 struct view *view = VIEW(REQ_VIEW_STATUS);
5728 unsigned long lineno;
5730 for (lineno = 0; lineno < view->lines; lineno++) {
5731 struct line *line = &view->line[lineno];
5732 struct status *pos = line->data;
5734 if (line->type != type)
5735 continue;
5736 if (!pos && (!status || !status->status) && line[1].data) {
5737 select_view_line(view, lineno);
5738 return TRUE;
5740 if (pos && !strcmp(status->new.name, pos->new.name)) {
5741 select_view_line(view, lineno);
5742 return TRUE;
5746 return FALSE;
5750 static bool
5751 status_update_prepare(struct io *io, enum line_type type)
5753 const char *staged_argv[] = {
5754 "git", "update-index", "-z", "--index-info", NULL
5756 const char *others_argv[] = {
5757 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5760 switch (type) {
5761 case LINE_STAT_STAGED:
5762 return run_io(io, staged_argv, opt_cdup, IO_WR);
5764 case LINE_STAT_UNSTAGED:
5765 case LINE_STAT_UNTRACKED:
5766 return run_io(io, others_argv, opt_cdup, IO_WR);
5768 default:
5769 die("line type %d not handled in switch", type);
5770 return FALSE;
5774 static bool
5775 status_update_write(struct io *io, struct status *status, enum line_type type)
5777 char buf[SIZEOF_STR];
5778 size_t bufsize = 0;
5780 switch (type) {
5781 case LINE_STAT_STAGED:
5782 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5783 status->old.mode,
5784 status->old.rev,
5785 status->old.name, 0))
5786 return FALSE;
5787 break;
5789 case LINE_STAT_UNSTAGED:
5790 case LINE_STAT_UNTRACKED:
5791 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5792 return FALSE;
5793 break;
5795 default:
5796 die("line type %d not handled in switch", type);
5799 return io_write(io, buf, bufsize);
5802 static bool
5803 status_update_file(struct status *status, enum line_type type)
5805 struct io io = {};
5806 bool result;
5808 if (!status_update_prepare(&io, type))
5809 return FALSE;
5811 result = status_update_write(&io, status, type);
5812 return done_io(&io) && result;
5815 static bool
5816 status_update_files(struct view *view, struct line *line)
5818 char buf[sizeof(view->ref)];
5819 struct io io = {};
5820 bool result = TRUE;
5821 struct line *pos = view->line + view->lines;
5822 int files = 0;
5823 int file, done;
5824 int cursor_y = -1, cursor_x = -1;
5826 if (!status_update_prepare(&io, line->type))
5827 return FALSE;
5829 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5830 files++;
5832 string_copy(buf, view->ref);
5833 getsyx(cursor_y, cursor_x);
5834 for (file = 0, done = 5; result && file < files; line++, file++) {
5835 int almost_done = file * 100 / files;
5837 if (almost_done > done) {
5838 done = almost_done;
5839 string_format(view->ref, "updating file %u of %u (%d%% done)",
5840 file, files, done);
5841 update_view_title(view);
5842 setsyx(cursor_y, cursor_x);
5843 doupdate();
5845 result = status_update_write(&io, line->data, line->type);
5847 string_copy(view->ref, buf);
5849 return done_io(&io) && result;
5852 static bool
5853 status_update(struct view *view)
5855 struct line *line = &view->line[view->lineno];
5857 assert(view->lines);
5859 if (!line->data) {
5860 /* This should work even for the "On branch" line. */
5861 if (line < view->line + view->lines && !line[1].data) {
5862 report("Nothing to update");
5863 return FALSE;
5866 if (!status_update_files(view, line + 1)) {
5867 report("Failed to update file status");
5868 return FALSE;
5871 } else if (!status_update_file(line->data, line->type)) {
5872 report("Failed to update file status");
5873 return FALSE;
5876 return TRUE;
5879 static bool
5880 status_revert(struct status *status, enum line_type type, bool has_none)
5882 if (!status || type != LINE_STAT_UNSTAGED) {
5883 if (type == LINE_STAT_STAGED) {
5884 report("Cannot revert changes to staged files");
5885 } else if (type == LINE_STAT_UNTRACKED) {
5886 report("Cannot revert changes to untracked files");
5887 } else if (has_none) {
5888 report("Nothing to revert");
5889 } else {
5890 report("Cannot revert changes to multiple files");
5893 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5894 char mode[10] = "100644";
5895 const char *reset_argv[] = {
5896 "git", "update-index", "--cacheinfo", mode,
5897 status->old.rev, status->old.name, NULL
5899 const char *checkout_argv[] = {
5900 "git", "checkout", "--", status->old.name, NULL
5903 if (status->status == 'U') {
5904 string_format(mode, "%5o", status->old.mode);
5906 if (status->old.mode == 0 && status->new.mode == 0) {
5907 reset_argv[2] = "--force-remove";
5908 reset_argv[3] = status->old.name;
5909 reset_argv[4] = NULL;
5912 if (!run_io_fg(reset_argv, opt_cdup))
5913 return FALSE;
5914 if (status->old.mode == 0 && status->new.mode == 0)
5915 return TRUE;
5918 return run_io_fg(checkout_argv, opt_cdup);
5921 return FALSE;
5924 static enum request
5925 status_request(struct view *view, enum request request, struct line *line)
5927 struct status *status = line->data;
5929 switch (request) {
5930 case REQ_STATUS_UPDATE:
5931 if (!status_update(view))
5932 return REQ_NONE;
5933 break;
5935 case REQ_STATUS_REVERT:
5936 if (!status_revert(status, line->type, status_has_none(view, line)))
5937 return REQ_NONE;
5938 break;
5940 case REQ_STATUS_MERGE:
5941 if (!status || status->status != 'U') {
5942 report("Merging only possible for files with unmerged status ('U').");
5943 return REQ_NONE;
5945 open_mergetool(status->new.name);
5946 break;
5948 case REQ_EDIT:
5949 if (!status)
5950 return request;
5951 if (status->status == 'D') {
5952 report("File has been deleted.");
5953 return REQ_NONE;
5956 open_editor(status->new.name);
5957 break;
5959 case REQ_VIEW_BLAME:
5960 if (status)
5961 opt_ref[0] = 0;
5962 return request;
5964 case REQ_ENTER:
5965 /* After returning the status view has been split to
5966 * show the stage view. No further reloading is
5967 * necessary. */
5968 return status_enter(view, line);
5970 case REQ_REFRESH:
5971 /* Simply reload the view. */
5972 break;
5974 default:
5975 return request;
5978 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5980 return REQ_NONE;
5983 static void
5984 status_select(struct view *view, struct line *line)
5986 struct status *status = line->data;
5987 char file[SIZEOF_STR] = "all files";
5988 const char *text;
5989 const char *key;
5991 if (status && !string_format(file, "'%s'", status->new.name))
5992 return;
5994 if (!status && line[1].type == LINE_STAT_NONE)
5995 line++;
5997 switch (line->type) {
5998 case LINE_STAT_STAGED:
5999 text = "Press %s to unstage %s for commit";
6000 break;
6002 case LINE_STAT_UNSTAGED:
6003 text = "Press %s to stage %s for commit";
6004 break;
6006 case LINE_STAT_UNTRACKED:
6007 text = "Press %s to stage %s for addition";
6008 break;
6010 case LINE_STAT_HEAD:
6011 case LINE_STAT_NONE:
6012 text = "Nothing to update";
6013 break;
6015 default:
6016 die("line type %d not handled in switch", line->type);
6019 if (status && status->status == 'U') {
6020 text = "Press %s to resolve conflict in %s";
6021 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6023 } else {
6024 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6027 string_format(view->ref, text, key, file);
6028 if (status)
6029 string_copy(opt_file, status->new.name);
6032 static bool
6033 status_grep(struct view *view, struct line *line)
6035 struct status *status = line->data;
6037 if (status) {
6038 const char buf[2] = { status->status, 0 };
6039 const char *text[] = { status->new.name, buf, NULL };
6041 return grep_text(view, text);
6044 return FALSE;
6047 static struct view_ops status_ops = {
6048 "file",
6049 NULL,
6050 status_open,
6051 NULL,
6052 status_draw,
6053 status_request,
6054 status_grep,
6055 status_select,
6059 static bool
6060 stage_diff_write(struct io *io, struct line *line, struct line *end)
6062 while (line < end) {
6063 if (!io_write(io, line->data, strlen(line->data)) ||
6064 !io_write(io, "\n", 1))
6065 return FALSE;
6066 line++;
6067 if (line->type == LINE_DIFF_CHUNK ||
6068 line->type == LINE_DIFF_HEADER)
6069 break;
6072 return TRUE;
6075 static struct line *
6076 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6078 for (; view->line < line; line--)
6079 if (line->type == type)
6080 return line;
6082 return NULL;
6085 static bool
6086 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6088 const char *apply_argv[SIZEOF_ARG] = {
6089 "git", "apply", "--whitespace=nowarn", NULL
6091 struct line *diff_hdr;
6092 struct io io = {};
6093 int argc = 3;
6095 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6096 if (!diff_hdr)
6097 return FALSE;
6099 if (!revert)
6100 apply_argv[argc++] = "--cached";
6101 if (revert || stage_line_type == LINE_STAT_STAGED)
6102 apply_argv[argc++] = "-R";
6103 apply_argv[argc++] = "-";
6104 apply_argv[argc++] = NULL;
6105 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6106 return FALSE;
6108 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6109 !stage_diff_write(&io, chunk, view->line + view->lines))
6110 chunk = NULL;
6112 done_io(&io);
6113 run_io_bg(update_index_argv);
6115 return chunk ? TRUE : FALSE;
6118 static bool
6119 stage_update(struct view *view, struct line *line)
6121 struct line *chunk = NULL;
6123 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6124 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6126 if (chunk) {
6127 if (!stage_apply_chunk(view, chunk, FALSE)) {
6128 report("Failed to apply chunk");
6129 return FALSE;
6132 } else if (!stage_status.status) {
6133 view = VIEW(REQ_VIEW_STATUS);
6135 for (line = view->line; line < view->line + view->lines; line++)
6136 if (line->type == stage_line_type)
6137 break;
6139 if (!status_update_files(view, line + 1)) {
6140 report("Failed to update files");
6141 return FALSE;
6144 } else if (!status_update_file(&stage_status, stage_line_type)) {
6145 report("Failed to update file");
6146 return FALSE;
6149 return TRUE;
6152 static bool
6153 stage_revert(struct view *view, struct line *line)
6155 struct line *chunk = NULL;
6157 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6158 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6160 if (chunk) {
6161 if (!prompt_yesno("Are you sure you want to revert changes?"))
6162 return FALSE;
6164 if (!stage_apply_chunk(view, chunk, TRUE)) {
6165 report("Failed to revert chunk");
6166 return FALSE;
6168 return TRUE;
6170 } else {
6171 return status_revert(stage_status.status ? &stage_status : NULL,
6172 stage_line_type, FALSE);
6177 static void
6178 stage_next(struct view *view, struct line *line)
6180 int i;
6182 if (!stage_chunks) {
6183 for (line = view->line; line < view->line + view->lines; line++) {
6184 if (line->type != LINE_DIFF_CHUNK)
6185 continue;
6187 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6188 report("Allocation failure");
6189 return;
6192 stage_chunk[stage_chunks++] = line - view->line;
6196 for (i = 0; i < stage_chunks; i++) {
6197 if (stage_chunk[i] > view->lineno) {
6198 do_scroll_view(view, stage_chunk[i] - view->lineno);
6199 report("Chunk %d of %d", i + 1, stage_chunks);
6200 return;
6204 report("No next chunk found");
6207 static enum request
6208 stage_request(struct view *view, enum request request, struct line *line)
6210 switch (request) {
6211 case REQ_STATUS_UPDATE:
6212 if (!stage_update(view, line))
6213 return REQ_NONE;
6214 break;
6216 case REQ_STATUS_REVERT:
6217 if (!stage_revert(view, line))
6218 return REQ_NONE;
6219 break;
6221 case REQ_STAGE_NEXT:
6222 if (stage_line_type == LINE_STAT_UNTRACKED) {
6223 report("File is untracked; press %s to add",
6224 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6225 return REQ_NONE;
6227 stage_next(view, line);
6228 return REQ_NONE;
6230 case REQ_EDIT:
6231 if (!stage_status.new.name[0])
6232 return request;
6233 if (stage_status.status == 'D') {
6234 report("File has been deleted.");
6235 return REQ_NONE;
6238 open_editor(stage_status.new.name);
6239 break;
6241 case REQ_REFRESH:
6242 /* Reload everything ... */
6243 break;
6245 case REQ_VIEW_BLAME:
6246 if (stage_status.new.name[0]) {
6247 string_copy(opt_file, stage_status.new.name);
6248 opt_ref[0] = 0;
6250 return request;
6252 case REQ_ENTER:
6253 return pager_request(view, request, line);
6255 default:
6256 return request;
6259 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6260 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6262 /* Check whether the staged entry still exists, and close the
6263 * stage view if it doesn't. */
6264 if (!status_exists(&stage_status, stage_line_type)) {
6265 status_restore(VIEW(REQ_VIEW_STATUS));
6266 return REQ_VIEW_CLOSE;
6269 if (stage_line_type == LINE_STAT_UNTRACKED) {
6270 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6271 report("Cannot display a directory");
6272 return REQ_NONE;
6275 if (!prepare_update_file(view, stage_status.new.name)) {
6276 report("Failed to open file: %s", strerror(errno));
6277 return REQ_NONE;
6280 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6282 return REQ_NONE;
6285 static struct view_ops stage_ops = {
6286 "line",
6287 NULL,
6288 NULL,
6289 pager_read,
6290 pager_draw,
6291 stage_request,
6292 pager_grep,
6293 pager_select,
6298 * Revision graph
6301 struct commit {
6302 char id[SIZEOF_REV]; /* SHA1 ID. */
6303 char title[128]; /* First line of the commit message. */
6304 const char *author; /* Author of the commit. */
6305 struct time time; /* Date from the author ident. */
6306 struct ref_list *refs; /* Repository references. */
6307 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6308 size_t graph_size; /* The width of the graph array. */
6309 bool has_parents; /* Rewritten --parents seen. */
6312 /* Size of rev graph with no "padding" columns */
6313 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6315 struct rev_graph {
6316 struct rev_graph *prev, *next, *parents;
6317 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6318 size_t size;
6319 struct commit *commit;
6320 size_t pos;
6321 unsigned int boundary:1;
6324 /* Parents of the commit being visualized. */
6325 static struct rev_graph graph_parents[4];
6327 /* The current stack of revisions on the graph. */
6328 static struct rev_graph graph_stacks[4] = {
6329 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6330 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6331 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6332 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6335 static inline bool
6336 graph_parent_is_merge(struct rev_graph *graph)
6338 return graph->parents->size > 1;
6341 static inline void
6342 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6344 struct commit *commit = graph->commit;
6346 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6347 commit->graph[commit->graph_size++] = symbol;
6350 static void
6351 clear_rev_graph(struct rev_graph *graph)
6353 graph->boundary = 0;
6354 graph->size = graph->pos = 0;
6355 graph->commit = NULL;
6356 memset(graph->parents, 0, sizeof(*graph->parents));
6359 static void
6360 done_rev_graph(struct rev_graph *graph)
6362 if (graph_parent_is_merge(graph) &&
6363 graph->pos < graph->size - 1 &&
6364 graph->next->size == graph->size + graph->parents->size - 1) {
6365 size_t i = graph->pos + graph->parents->size - 1;
6367 graph->commit->graph_size = i * 2;
6368 while (i < graph->next->size - 1) {
6369 append_to_rev_graph(graph, ' ');
6370 append_to_rev_graph(graph, '\\');
6371 i++;
6375 clear_rev_graph(graph);
6378 static void
6379 push_rev_graph(struct rev_graph *graph, const char *parent)
6381 int i;
6383 /* "Collapse" duplicate parents lines.
6385 * FIXME: This needs to also update update the drawn graph but
6386 * for now it just serves as a method for pruning graph lines. */
6387 for (i = 0; i < graph->size; i++)
6388 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6389 return;
6391 if (graph->size < SIZEOF_REVITEMS) {
6392 string_copy_rev(graph->rev[graph->size++], parent);
6396 static chtype
6397 get_rev_graph_symbol(struct rev_graph *graph)
6399 chtype symbol;
6401 if (graph->boundary)
6402 symbol = REVGRAPH_BOUND;
6403 else if (graph->parents->size == 0)
6404 symbol = REVGRAPH_INIT;
6405 else if (graph_parent_is_merge(graph))
6406 symbol = REVGRAPH_MERGE;
6407 else if (graph->pos >= graph->size)
6408 symbol = REVGRAPH_BRANCH;
6409 else
6410 symbol = REVGRAPH_COMMIT;
6412 return symbol;
6415 static void
6416 draw_rev_graph(struct rev_graph *graph)
6418 struct rev_filler {
6419 chtype separator, line;
6421 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6422 static struct rev_filler fillers[] = {
6423 { ' ', '|' },
6424 { '`', '.' },
6425 { '\'', ' ' },
6426 { '/', ' ' },
6428 chtype symbol = get_rev_graph_symbol(graph);
6429 struct rev_filler *filler;
6430 size_t i;
6432 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6433 filler = &fillers[DEFAULT];
6435 for (i = 0; i < graph->pos; i++) {
6436 append_to_rev_graph(graph, filler->line);
6437 if (graph_parent_is_merge(graph->prev) &&
6438 graph->prev->pos == i)
6439 filler = &fillers[RSHARP];
6441 append_to_rev_graph(graph, filler->separator);
6444 /* Place the symbol for this revision. */
6445 append_to_rev_graph(graph, symbol);
6447 if (graph->prev->size > graph->size)
6448 filler = &fillers[RDIAG];
6449 else
6450 filler = &fillers[DEFAULT];
6452 i++;
6454 for (; i < graph->size; i++) {
6455 append_to_rev_graph(graph, filler->separator);
6456 append_to_rev_graph(graph, filler->line);
6457 if (graph_parent_is_merge(graph->prev) &&
6458 i < graph->prev->pos + graph->parents->size)
6459 filler = &fillers[RSHARP];
6460 if (graph->prev->size > graph->size)
6461 filler = &fillers[LDIAG];
6464 if (graph->prev->size > graph->size) {
6465 append_to_rev_graph(graph, filler->separator);
6466 if (filler->line != ' ')
6467 append_to_rev_graph(graph, filler->line);
6471 /* Prepare the next rev graph */
6472 static void
6473 prepare_rev_graph(struct rev_graph *graph)
6475 size_t i;
6477 /* First, traverse all lines of revisions up to the active one. */
6478 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6479 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6480 break;
6482 push_rev_graph(graph->next, graph->rev[graph->pos]);
6485 /* Interleave the new revision parent(s). */
6486 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6487 push_rev_graph(graph->next, graph->parents->rev[i]);
6489 /* Lastly, put any remaining revisions. */
6490 for (i = graph->pos + 1; i < graph->size; i++)
6491 push_rev_graph(graph->next, graph->rev[i]);
6494 static void
6495 update_rev_graph(struct view *view, struct rev_graph *graph)
6497 /* If this is the finalizing update ... */
6498 if (graph->commit)
6499 prepare_rev_graph(graph);
6501 /* Graph visualization needs a one rev look-ahead,
6502 * so the first update doesn't visualize anything. */
6503 if (!graph->prev->commit)
6504 return;
6506 if (view->lines > 2)
6507 view->line[view->lines - 3].dirty = 1;
6508 if (view->lines > 1)
6509 view->line[view->lines - 2].dirty = 1;
6510 draw_rev_graph(graph->prev);
6511 done_rev_graph(graph->prev->prev);
6516 * Main view backend
6519 static const char *main_argv[SIZEOF_ARG] = {
6520 "git", "log", "--no-color", "--pretty=raw", "--parents",
6521 "--topo-order", "%(head)", NULL
6524 static bool
6525 main_draw(struct view *view, struct line *line, unsigned int lineno)
6527 struct commit *commit = line->data;
6529 if (!commit->author)
6530 return FALSE;
6532 if (opt_date && draw_date(view, &commit->time))
6533 return TRUE;
6535 if (opt_author && draw_author(view, commit->author))
6536 return TRUE;
6538 if (opt_rev_graph && commit->graph_size &&
6539 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6540 return TRUE;
6542 if (opt_show_refs && commit->refs) {
6543 size_t i;
6545 for (i = 0; i < commit->refs->size; i++) {
6546 struct ref *ref = commit->refs->refs[i];
6547 enum line_type type;
6549 if (ref->head)
6550 type = LINE_MAIN_HEAD;
6551 else if (ref->ltag)
6552 type = LINE_MAIN_LOCAL_TAG;
6553 else if (ref->tag)
6554 type = LINE_MAIN_TAG;
6555 else if (ref->tracked)
6556 type = LINE_MAIN_TRACKED;
6557 else if (ref->remote)
6558 type = LINE_MAIN_REMOTE;
6559 else
6560 type = LINE_MAIN_REF;
6562 if (draw_text(view, type, "[", TRUE) ||
6563 draw_text(view, type, ref->name, TRUE) ||
6564 draw_text(view, type, "]", TRUE))
6565 return TRUE;
6567 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6568 return TRUE;
6572 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6573 return TRUE;
6576 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6577 static bool
6578 main_read(struct view *view, char *line)
6580 static struct rev_graph *graph = graph_stacks;
6581 enum line_type type;
6582 struct commit *commit;
6584 if (!line) {
6585 int i;
6587 if (!view->lines && !view->parent)
6588 die("No revisions match the given arguments.");
6589 if (view->lines > 0) {
6590 commit = view->line[view->lines - 1].data;
6591 view->line[view->lines - 1].dirty = 1;
6592 if (!commit->author) {
6593 view->lines--;
6594 free(commit);
6595 graph->commit = NULL;
6598 update_rev_graph(view, graph);
6600 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6601 clear_rev_graph(&graph_stacks[i]);
6602 return TRUE;
6605 type = get_line_type(line);
6606 if (type == LINE_COMMIT) {
6607 commit = calloc(1, sizeof(struct commit));
6608 if (!commit)
6609 return FALSE;
6611 line += STRING_SIZE("commit ");
6612 if (*line == '-') {
6613 graph->boundary = 1;
6614 line++;
6617 string_copy_rev(commit->id, line);
6618 commit->refs = get_ref_list(commit->id);
6619 graph->commit = commit;
6620 add_line_data(view, commit, LINE_MAIN_COMMIT);
6622 while ((line = strchr(line, ' '))) {
6623 line++;
6624 push_rev_graph(graph->parents, line);
6625 commit->has_parents = TRUE;
6627 return TRUE;
6630 if (!view->lines)
6631 return TRUE;
6632 commit = view->line[view->lines - 1].data;
6634 switch (type) {
6635 case LINE_PARENT:
6636 if (commit->has_parents)
6637 break;
6638 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6639 break;
6641 case LINE_AUTHOR:
6642 parse_author_line(line + STRING_SIZE("author "),
6643 &commit->author, &commit->time);
6644 update_rev_graph(view, graph);
6645 graph = graph->next;
6646 break;
6648 default:
6649 /* Fill in the commit title if it has not already been set. */
6650 if (commit->title[0])
6651 break;
6653 /* Require titles to start with a non-space character at the
6654 * offset used by git log. */
6655 if (strncmp(line, " ", 4))
6656 break;
6657 line += 4;
6658 /* Well, if the title starts with a whitespace character,
6659 * try to be forgiving. Otherwise we end up with no title. */
6660 while (isspace(*line))
6661 line++;
6662 if (*line == '\0')
6663 break;
6664 /* FIXME: More graceful handling of titles; append "..." to
6665 * shortened titles, etc. */
6667 string_expand(commit->title, sizeof(commit->title), line, 1);
6668 view->line[view->lines - 1].dirty = 1;
6671 return TRUE;
6674 static enum request
6675 main_request(struct view *view, enum request request, struct line *line)
6677 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6679 switch (request) {
6680 case REQ_ENTER:
6681 open_view(view, REQ_VIEW_DIFF, flags);
6682 break;
6683 case REQ_REFRESH:
6684 load_refs();
6685 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6686 break;
6687 default:
6688 return request;
6691 return REQ_NONE;
6694 static bool
6695 grep_refs(struct ref_list *list, regex_t *regex)
6697 regmatch_t pmatch;
6698 size_t i;
6700 if (!opt_show_refs || !list)
6701 return FALSE;
6703 for (i = 0; i < list->size; i++) {
6704 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6705 return TRUE;
6708 return FALSE;
6711 static bool
6712 main_grep(struct view *view, struct line *line)
6714 struct commit *commit = line->data;
6715 const char *text[] = {
6716 commit->title,
6717 opt_author ? commit->author : "",
6718 opt_date ? mkdate(&commit->time) : "",
6719 NULL
6722 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6725 static void
6726 main_select(struct view *view, struct line *line)
6728 struct commit *commit = line->data;
6730 string_copy_rev(view->ref, commit->id);
6731 string_copy_rev(ref_commit, view->ref);
6734 static struct view_ops main_ops = {
6735 "commit",
6736 main_argv,
6737 NULL,
6738 main_read,
6739 main_draw,
6740 main_request,
6741 main_grep,
6742 main_select,
6747 * Unicode / UTF-8 handling
6749 * NOTE: Much of the following code for dealing with Unicode is derived from
6750 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6751 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6754 static inline int
6755 unicode_width(unsigned long c, int tab_size)
6757 if (c >= 0x1100 &&
6758 (c <= 0x115f /* Hangul Jamo */
6759 || c == 0x2329
6760 || c == 0x232a
6761 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6762 /* CJK ... Yi */
6763 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6764 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6765 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6766 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6767 || (c >= 0xffe0 && c <= 0xffe6)
6768 || (c >= 0x20000 && c <= 0x2fffd)
6769 || (c >= 0x30000 && c <= 0x3fffd)))
6770 return 2;
6772 if (c == '\t')
6773 return tab_size;
6775 return 1;
6778 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6779 * Illegal bytes are set one. */
6780 static const unsigned char utf8_bytes[256] = {
6781 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,
6782 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,
6783 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,
6784 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,
6785 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,
6786 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,
6787 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,
6788 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,
6791 static inline unsigned char
6792 utf8_char_length(const char *string, const char *end)
6794 int c = *(unsigned char *) string;
6796 return utf8_bytes[c];
6799 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6800 static inline unsigned long
6801 utf8_to_unicode(const char *string, size_t length)
6803 unsigned long unicode;
6805 switch (length) {
6806 case 1:
6807 unicode = string[0];
6808 break;
6809 case 2:
6810 unicode = (string[0] & 0x1f) << 6;
6811 unicode += (string[1] & 0x3f);
6812 break;
6813 case 3:
6814 unicode = (string[0] & 0x0f) << 12;
6815 unicode += ((string[1] & 0x3f) << 6);
6816 unicode += (string[2] & 0x3f);
6817 break;
6818 case 4:
6819 unicode = (string[0] & 0x0f) << 18;
6820 unicode += ((string[1] & 0x3f) << 12);
6821 unicode += ((string[2] & 0x3f) << 6);
6822 unicode += (string[3] & 0x3f);
6823 break;
6824 case 5:
6825 unicode = (string[0] & 0x0f) << 24;
6826 unicode += ((string[1] & 0x3f) << 18);
6827 unicode += ((string[2] & 0x3f) << 12);
6828 unicode += ((string[3] & 0x3f) << 6);
6829 unicode += (string[4] & 0x3f);
6830 break;
6831 case 6:
6832 unicode = (string[0] & 0x01) << 30;
6833 unicode += ((string[1] & 0x3f) << 24);
6834 unicode += ((string[2] & 0x3f) << 18);
6835 unicode += ((string[3] & 0x3f) << 12);
6836 unicode += ((string[4] & 0x3f) << 6);
6837 unicode += (string[5] & 0x3f);
6838 break;
6839 default:
6840 die("Invalid Unicode length");
6843 /* Invalid characters could return the special 0xfffd value but NUL
6844 * should be just as good. */
6845 return unicode > 0xffff ? 0 : unicode;
6848 /* Calculates how much of string can be shown within the given maximum width
6849 * and sets trimmed parameter to non-zero value if all of string could not be
6850 * shown. If the reserve flag is TRUE, it will reserve at least one
6851 * trailing character, which can be useful when drawing a delimiter.
6853 * Returns the number of bytes to output from string to satisfy max_width. */
6854 static size_t
6855 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6857 const char *string = *start;
6858 const char *end = strchr(string, '\0');
6859 unsigned char last_bytes = 0;
6860 size_t last_ucwidth = 0;
6862 *width = 0;
6863 *trimmed = 0;
6865 while (string < end) {
6866 unsigned char bytes = utf8_char_length(string, end);
6867 size_t ucwidth;
6868 unsigned long unicode;
6870 if (string + bytes > end)
6871 break;
6873 /* Change representation to figure out whether
6874 * it is a single- or double-width character. */
6876 unicode = utf8_to_unicode(string, bytes);
6877 /* FIXME: Graceful handling of invalid Unicode character. */
6878 if (!unicode)
6879 break;
6881 ucwidth = unicode_width(unicode, tab_size);
6882 if (skip > 0) {
6883 skip -= ucwidth <= skip ? ucwidth : skip;
6884 *start += bytes;
6886 *width += ucwidth;
6887 if (*width > max_width) {
6888 *trimmed = 1;
6889 *width -= ucwidth;
6890 if (reserve && *width == max_width) {
6891 string -= last_bytes;
6892 *width -= last_ucwidth;
6894 break;
6897 string += bytes;
6898 last_bytes = ucwidth ? bytes : 0;
6899 last_ucwidth = ucwidth;
6902 return string - *start;
6907 * Status management
6910 /* Whether or not the curses interface has been initialized. */
6911 static bool cursed = FALSE;
6913 /* Terminal hacks and workarounds. */
6914 static bool use_scroll_redrawwin;
6915 static bool use_scroll_status_wclear;
6917 /* The status window is used for polling keystrokes. */
6918 static WINDOW *status_win;
6920 /* Reading from the prompt? */
6921 static bool input_mode = FALSE;
6923 static bool status_empty = FALSE;
6925 /* Update status and title window. */
6926 static void
6927 report(const char *msg, ...)
6929 struct view *view = display[current_view];
6931 if (input_mode)
6932 return;
6934 if (!view) {
6935 char buf[SIZEOF_STR];
6936 va_list args;
6938 va_start(args, msg);
6939 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6940 buf[sizeof(buf) - 1] = 0;
6941 buf[sizeof(buf) - 2] = '.';
6942 buf[sizeof(buf) - 3] = '.';
6943 buf[sizeof(buf) - 4] = '.';
6945 va_end(args);
6946 die("%s", buf);
6949 if (!status_empty || *msg) {
6950 va_list args;
6952 va_start(args, msg);
6954 wmove(status_win, 0, 0);
6955 if (view->has_scrolled && use_scroll_status_wclear)
6956 wclear(status_win);
6957 if (*msg) {
6958 vwprintw(status_win, msg, args);
6959 status_empty = FALSE;
6960 } else {
6961 status_empty = TRUE;
6963 wclrtoeol(status_win);
6964 wnoutrefresh(status_win);
6966 va_end(args);
6969 update_view_title(view);
6972 static void
6973 init_display(void)
6975 const char *term;
6976 int x, y;
6978 /* Initialize the curses library */
6979 if (isatty(STDIN_FILENO)) {
6980 cursed = !!initscr();
6981 opt_tty = stdin;
6982 } else {
6983 /* Leave stdin and stdout alone when acting as a pager. */
6984 opt_tty = fopen("/dev/tty", "r+");
6985 if (!opt_tty)
6986 die("Failed to open /dev/tty");
6987 cursed = !!newterm(NULL, opt_tty, opt_tty);
6990 if (!cursed)
6991 die("Failed to initialize curses");
6993 nonl(); /* Disable conversion and detect newlines from input. */
6994 cbreak(); /* Take input chars one at a time, no wait for \n */
6995 noecho(); /* Don't echo input */
6996 leaveok(stdscr, FALSE);
6998 if (has_colors())
6999 init_colors();
7001 getmaxyx(stdscr, y, x);
7002 status_win = newwin(1, 0, y - 1, 0);
7003 if (!status_win)
7004 die("Failed to create status window");
7006 /* Enable keyboard mapping */
7007 keypad(status_win, TRUE);
7008 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7010 TABSIZE = opt_tab_size;
7012 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7013 if (term && !strcmp(term, "gnome-terminal")) {
7014 /* In the gnome-terminal-emulator, the message from
7015 * scrolling up one line when impossible followed by
7016 * scrolling down one line causes corruption of the
7017 * status line. This is fixed by calling wclear. */
7018 use_scroll_status_wclear = TRUE;
7019 use_scroll_redrawwin = FALSE;
7021 } else if (term && !strcmp(term, "xrvt-xpm")) {
7022 /* No problems with full optimizations in xrvt-(unicode)
7023 * and aterm. */
7024 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7026 } else {
7027 /* When scrolling in (u)xterm the last line in the
7028 * scrolling direction will update slowly. */
7029 use_scroll_redrawwin = TRUE;
7030 use_scroll_status_wclear = FALSE;
7034 static int
7035 get_input(int prompt_position)
7037 struct view *view;
7038 int i, key, cursor_y, cursor_x;
7039 bool loading = FALSE;
7041 if (prompt_position)
7042 input_mode = TRUE;
7044 while (TRUE) {
7045 foreach_view (view, i) {
7046 update_view(view);
7047 if (view_is_displayed(view) && view->has_scrolled &&
7048 use_scroll_redrawwin)
7049 redrawwin(view->win);
7050 view->has_scrolled = FALSE;
7051 if (view->pipe)
7052 loading = TRUE;
7055 /* Update the cursor position. */
7056 if (prompt_position) {
7057 getbegyx(status_win, cursor_y, cursor_x);
7058 cursor_x = prompt_position;
7059 } else {
7060 view = display[current_view];
7061 getbegyx(view->win, cursor_y, cursor_x);
7062 cursor_x = view->width - 1;
7063 cursor_y += view->lineno - view->offset;
7065 setsyx(cursor_y, cursor_x);
7067 /* Refresh, accept single keystroke of input */
7068 doupdate();
7069 nodelay(status_win, loading);
7070 key = wgetch(status_win);
7072 /* wgetch() with nodelay() enabled returns ERR when
7073 * there's no input. */
7074 if (key == ERR) {
7076 } else if (key == KEY_RESIZE) {
7077 int height, width;
7079 getmaxyx(stdscr, height, width);
7081 wresize(status_win, 1, width);
7082 mvwin(status_win, height - 1, 0);
7083 wnoutrefresh(status_win);
7084 resize_display();
7085 redraw_display(TRUE);
7087 } else {
7088 input_mode = FALSE;
7089 return key;
7094 static char *
7095 prompt_input(const char *prompt, input_handler handler, void *data)
7097 enum input_status status = INPUT_OK;
7098 static char buf[SIZEOF_STR];
7099 size_t pos = 0;
7101 buf[pos] = 0;
7103 while (status == INPUT_OK || status == INPUT_SKIP) {
7104 int key;
7106 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7107 wclrtoeol(status_win);
7109 key = get_input(pos + 1);
7110 switch (key) {
7111 case KEY_RETURN:
7112 case KEY_ENTER:
7113 case '\n':
7114 status = pos ? INPUT_STOP : INPUT_CANCEL;
7115 break;
7117 case KEY_BACKSPACE:
7118 if (pos > 0)
7119 buf[--pos] = 0;
7120 else
7121 status = INPUT_CANCEL;
7122 break;
7124 case KEY_ESC:
7125 status = INPUT_CANCEL;
7126 break;
7128 default:
7129 if (pos >= sizeof(buf)) {
7130 report("Input string too long");
7131 return NULL;
7134 status = handler(data, buf, key);
7135 if (status == INPUT_OK)
7136 buf[pos++] = (char) key;
7140 /* Clear the status window */
7141 status_empty = FALSE;
7142 report("");
7144 if (status == INPUT_CANCEL)
7145 return NULL;
7147 buf[pos++] = 0;
7149 return buf;
7152 static enum input_status
7153 prompt_yesno_handler(void *data, char *buf, int c)
7155 if (c == 'y' || c == 'Y')
7156 return INPUT_STOP;
7157 if (c == 'n' || c == 'N')
7158 return INPUT_CANCEL;
7159 return INPUT_SKIP;
7162 static bool
7163 prompt_yesno(const char *prompt)
7165 char prompt2[SIZEOF_STR];
7167 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7168 return FALSE;
7170 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7173 static enum input_status
7174 read_prompt_handler(void *data, char *buf, int c)
7176 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7179 static char *
7180 read_prompt(const char *prompt)
7182 return prompt_input(prompt, read_prompt_handler, NULL);
7185 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7187 enum input_status status = INPUT_OK;
7188 int size = 0;
7190 while (items[size].text)
7191 size++;
7193 while (status == INPUT_OK) {
7194 const struct menu_item *item = &items[*selected];
7195 int key;
7196 int i;
7198 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7199 prompt, *selected + 1, size);
7200 if (item->hotkey)
7201 wprintw(status_win, "[%c] ", (char) item->hotkey);
7202 wprintw(status_win, "%s", item->text);
7203 wclrtoeol(status_win);
7205 key = get_input(COLS - 1);
7206 switch (key) {
7207 case KEY_RETURN:
7208 case KEY_ENTER:
7209 case '\n':
7210 status = INPUT_STOP;
7211 break;
7213 case KEY_LEFT:
7214 case KEY_UP:
7215 *selected = *selected - 1;
7216 if (*selected < 0)
7217 *selected = size - 1;
7218 break;
7220 case KEY_RIGHT:
7221 case KEY_DOWN:
7222 *selected = (*selected + 1) % size;
7223 break;
7225 case KEY_ESC:
7226 status = INPUT_CANCEL;
7227 break;
7229 default:
7230 for (i = 0; items[i].text; i++)
7231 if (items[i].hotkey == key) {
7232 *selected = i;
7233 status = INPUT_STOP;
7234 break;
7239 /* Clear the status window */
7240 status_empty = FALSE;
7241 report("");
7243 return status != INPUT_CANCEL;
7247 * Repository properties
7250 static struct ref **refs = NULL;
7251 static size_t refs_size = 0;
7252 static struct ref *refs_head = NULL;
7254 static struct ref_list **ref_lists = NULL;
7255 static size_t ref_lists_size = 0;
7257 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7258 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7259 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7261 static int
7262 compare_refs(const void *ref1_, const void *ref2_)
7264 const struct ref *ref1 = *(const struct ref **)ref1_;
7265 const struct ref *ref2 = *(const struct ref **)ref2_;
7267 if (ref1->tag != ref2->tag)
7268 return ref2->tag - ref1->tag;
7269 if (ref1->ltag != ref2->ltag)
7270 return ref2->ltag - ref2->ltag;
7271 if (ref1->head != ref2->head)
7272 return ref2->head - ref1->head;
7273 if (ref1->tracked != ref2->tracked)
7274 return ref2->tracked - ref1->tracked;
7275 if (ref1->remote != ref2->remote)
7276 return ref2->remote - ref1->remote;
7277 return strcmp(ref1->name, ref2->name);
7280 static void
7281 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7283 size_t i;
7285 for (i = 0; i < refs_size; i++)
7286 if (!visitor(data, refs[i]))
7287 break;
7290 static struct ref *
7291 get_ref_head()
7293 return refs_head;
7296 static struct ref_list *
7297 get_ref_list(const char *id)
7299 struct ref_list *list;
7300 size_t i;
7302 for (i = 0; i < ref_lists_size; i++)
7303 if (!strcmp(id, ref_lists[i]->id))
7304 return ref_lists[i];
7306 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7307 return NULL;
7308 list = calloc(1, sizeof(*list));
7309 if (!list)
7310 return NULL;
7312 for (i = 0; i < refs_size; i++) {
7313 if (!strcmp(id, refs[i]->id) &&
7314 realloc_refs_list(&list->refs, list->size, 1))
7315 list->refs[list->size++] = refs[i];
7318 if (!list->refs) {
7319 free(list);
7320 return NULL;
7323 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7324 ref_lists[ref_lists_size++] = list;
7325 return list;
7328 static int
7329 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7331 struct ref *ref = NULL;
7332 bool tag = FALSE;
7333 bool ltag = FALSE;
7334 bool remote = FALSE;
7335 bool tracked = FALSE;
7336 bool head = FALSE;
7337 int from = 0, to = refs_size - 1;
7339 if (!prefixcmp(name, "refs/tags/")) {
7340 if (!suffixcmp(name, namelen, "^{}")) {
7341 namelen -= 3;
7342 name[namelen] = 0;
7343 } else {
7344 ltag = TRUE;
7347 tag = TRUE;
7348 namelen -= STRING_SIZE("refs/tags/");
7349 name += STRING_SIZE("refs/tags/");
7351 } else if (!prefixcmp(name, "refs/remotes/")) {
7352 remote = TRUE;
7353 namelen -= STRING_SIZE("refs/remotes/");
7354 name += STRING_SIZE("refs/remotes/");
7355 tracked = !strcmp(opt_remote, name);
7357 } else if (!prefixcmp(name, "refs/heads/")) {
7358 namelen -= STRING_SIZE("refs/heads/");
7359 name += STRING_SIZE("refs/heads/");
7360 if (!strncmp(opt_head, name, namelen))
7361 return OK;
7363 } else if (!strcmp(name, "HEAD")) {
7364 head = TRUE;
7365 if (*opt_head) {
7366 namelen = strlen(opt_head);
7367 name = opt_head;
7371 /* If we are reloading or it's an annotated tag, replace the
7372 * previous SHA1 with the resolved commit id; relies on the fact
7373 * git-ls-remote lists the commit id of an annotated tag right
7374 * before the commit id it points to. */
7375 while (from <= to) {
7376 size_t pos = (to + from) / 2;
7377 int cmp = strcmp(name, refs[pos]->name);
7379 if (!cmp) {
7380 ref = refs[pos];
7381 break;
7384 if (cmp < 0)
7385 to = pos - 1;
7386 else
7387 from = pos + 1;
7390 if (!ref) {
7391 if (!realloc_refs(&refs, refs_size, 1))
7392 return ERR;
7393 ref = calloc(1, sizeof(*ref) + namelen);
7394 if (!ref)
7395 return ERR;
7396 memmove(refs + from + 1, refs + from,
7397 (refs_size - from) * sizeof(*refs));
7398 refs[from] = ref;
7399 strncpy(ref->name, name, namelen);
7400 refs_size++;
7403 ref->head = head;
7404 ref->tag = tag;
7405 ref->ltag = ltag;
7406 ref->remote = remote;
7407 ref->tracked = tracked;
7408 string_copy_rev(ref->id, id);
7410 if (head)
7411 refs_head = ref;
7412 return OK;
7415 static int
7416 load_refs(void)
7418 const char *head_argv[] = {
7419 "git", "symbolic-ref", "HEAD", NULL
7421 static const char *ls_remote_argv[SIZEOF_ARG] = {
7422 "git", "ls-remote", opt_git_dir, NULL
7424 static bool init = FALSE;
7425 size_t i;
7427 if (!init) {
7428 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7429 init = TRUE;
7432 if (!*opt_git_dir)
7433 return OK;
7435 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7436 !prefixcmp(opt_head, "refs/heads/")) {
7437 char *offset = opt_head + STRING_SIZE("refs/heads/");
7439 memmove(opt_head, offset, strlen(offset) + 1);
7442 refs_head = NULL;
7443 for (i = 0; i < refs_size; i++)
7444 refs[i]->id[0] = 0;
7446 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7447 return ERR;
7449 /* Update the ref lists to reflect changes. */
7450 for (i = 0; i < ref_lists_size; i++) {
7451 struct ref_list *list = ref_lists[i];
7452 size_t old, new;
7454 for (old = new = 0; old < list->size; old++)
7455 if (!strcmp(list->id, list->refs[old]->id))
7456 list->refs[new++] = list->refs[old];
7457 list->size = new;
7460 return OK;
7463 static void
7464 set_remote_branch(const char *name, const char *value, size_t valuelen)
7466 if (!strcmp(name, ".remote")) {
7467 string_ncopy(opt_remote, value, valuelen);
7469 } else if (*opt_remote && !strcmp(name, ".merge")) {
7470 size_t from = strlen(opt_remote);
7472 if (!prefixcmp(value, "refs/heads/"))
7473 value += STRING_SIZE("refs/heads/");
7475 if (!string_format_from(opt_remote, &from, "/%s", value))
7476 opt_remote[0] = 0;
7480 static void
7481 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7483 const char *argv[SIZEOF_ARG] = { name, "=" };
7484 int argc = 1 + (cmd == option_set_command);
7485 int error = ERR;
7487 if (!argv_from_string(argv, &argc, value))
7488 config_msg = "Too many option arguments";
7489 else
7490 error = cmd(argc, argv);
7492 if (error == ERR)
7493 warn("Option 'tig.%s': %s", name, config_msg);
7496 static bool
7497 set_environment_variable(const char *name, const char *value)
7499 size_t len = strlen(name) + 1 + strlen(value) + 1;
7500 char *env = malloc(len);
7502 if (env &&
7503 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7504 putenv(env) == 0)
7505 return TRUE;
7506 free(env);
7507 return FALSE;
7510 static void
7511 set_work_tree(const char *value)
7513 char cwd[SIZEOF_STR];
7515 if (!getcwd(cwd, sizeof(cwd)))
7516 die("Failed to get cwd path: %s", strerror(errno));
7517 if (chdir(opt_git_dir) < 0)
7518 die("Failed to chdir(%s): %s", strerror(errno));
7519 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7520 die("Failed to get git path: %s", strerror(errno));
7521 if (chdir(cwd) < 0)
7522 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7523 if (chdir(value) < 0)
7524 die("Failed to chdir(%s): %s", value, strerror(errno));
7525 if (!getcwd(cwd, sizeof(cwd)))
7526 die("Failed to get cwd path: %s", strerror(errno));
7527 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7528 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7529 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7530 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7531 opt_is_inside_work_tree = TRUE;
7534 static int
7535 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7537 if (!strcmp(name, "i18n.commitencoding"))
7538 string_ncopy(opt_encoding, value, valuelen);
7540 else if (!strcmp(name, "core.editor"))
7541 string_ncopy(opt_editor, value, valuelen);
7543 else if (!strcmp(name, "core.worktree"))
7544 set_work_tree(value);
7546 else if (!prefixcmp(name, "tig.color."))
7547 set_repo_config_option(name + 10, value, option_color_command);
7549 else if (!prefixcmp(name, "tig.bind."))
7550 set_repo_config_option(name + 9, value, option_bind_command);
7552 else if (!prefixcmp(name, "tig."))
7553 set_repo_config_option(name + 4, value, option_set_command);
7555 else if (*opt_head && !prefixcmp(name, "branch.") &&
7556 !strncmp(name + 7, opt_head, strlen(opt_head)))
7557 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7559 return OK;
7562 static int
7563 load_git_config(void)
7565 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7567 return run_io_load(config_list_argv, "=", read_repo_config_option);
7570 static int
7571 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7573 if (!opt_git_dir[0]) {
7574 string_ncopy(opt_git_dir, name, namelen);
7576 } else if (opt_is_inside_work_tree == -1) {
7577 /* This can be 3 different values depending on the
7578 * version of git being used. If git-rev-parse does not
7579 * understand --is-inside-work-tree it will simply echo
7580 * the option else either "true" or "false" is printed.
7581 * Default to true for the unknown case. */
7582 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7584 } else if (*name == '.') {
7585 string_ncopy(opt_cdup, name, namelen);
7587 } else {
7588 string_ncopy(opt_prefix, name, namelen);
7591 return OK;
7594 static int
7595 load_repo_info(void)
7597 const char *rev_parse_argv[] = {
7598 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7599 "--show-cdup", "--show-prefix", NULL
7602 return run_io_load(rev_parse_argv, "=", read_repo_info);
7607 * Main
7610 static const char usage[] =
7611 "tig " TIG_VERSION " (" __DATE__ ")\n"
7612 "\n"
7613 "Usage: tig [options] [revs] [--] [paths]\n"
7614 " or: tig show [options] [revs] [--] [paths]\n"
7615 " or: tig blame [rev] path\n"
7616 " or: tig status\n"
7617 " or: tig < [git command output]\n"
7618 "\n"
7619 "Options:\n"
7620 " -v, --version Show version and exit\n"
7621 " -h, --help Show help message and exit";
7623 static void __NORETURN
7624 quit(int sig)
7626 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7627 if (cursed)
7628 endwin();
7629 exit(0);
7632 static void __NORETURN
7633 die(const char *err, ...)
7635 va_list args;
7637 endwin();
7639 va_start(args, err);
7640 fputs("tig: ", stderr);
7641 vfprintf(stderr, err, args);
7642 fputs("\n", stderr);
7643 va_end(args);
7645 exit(1);
7648 static void
7649 warn(const char *msg, ...)
7651 va_list args;
7653 va_start(args, msg);
7654 fputs("tig warning: ", stderr);
7655 vfprintf(stderr, msg, args);
7656 fputs("\n", stderr);
7657 va_end(args);
7660 static enum request
7661 parse_options(int argc, const char *argv[])
7663 enum request request = REQ_VIEW_MAIN;
7664 const char *subcommand;
7665 bool seen_dashdash = FALSE;
7666 /* XXX: This is vulnerable to the user overriding options
7667 * required for the main view parser. */
7668 const char *custom_argv[SIZEOF_ARG] = {
7669 "git", "log", "--no-color", "--pretty=raw", "--parents",
7670 "--topo-order", NULL
7672 int i, j = 6;
7674 if (!isatty(STDIN_FILENO)) {
7675 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7676 return REQ_VIEW_PAGER;
7679 if (argc <= 1)
7680 return REQ_NONE;
7682 subcommand = argv[1];
7683 if (!strcmp(subcommand, "status")) {
7684 if (argc > 2)
7685 warn("ignoring arguments after `%s'", subcommand);
7686 return REQ_VIEW_STATUS;
7688 } else if (!strcmp(subcommand, "blame")) {
7689 if (argc <= 2 || argc > 4)
7690 die("invalid number of options to blame\n\n%s", usage);
7692 i = 2;
7693 if (argc == 4) {
7694 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7695 i++;
7698 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7699 return REQ_VIEW_BLAME;
7701 } else if (!strcmp(subcommand, "show")) {
7702 request = REQ_VIEW_DIFF;
7704 } else {
7705 subcommand = NULL;
7708 if (subcommand) {
7709 custom_argv[1] = subcommand;
7710 j = 2;
7713 for (i = 1 + !!subcommand; i < argc; i++) {
7714 const char *opt = argv[i];
7716 if (seen_dashdash || !strcmp(opt, "--")) {
7717 seen_dashdash = TRUE;
7719 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7720 printf("tig version %s\n", TIG_VERSION);
7721 quit(0);
7723 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7724 printf("%s\n", usage);
7725 quit(0);
7728 custom_argv[j++] = opt;
7729 if (j >= ARRAY_SIZE(custom_argv))
7730 die("command too long");
7733 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7734 die("Failed to format arguments");
7736 return request;
7740 main(int argc, const char *argv[])
7742 const char *codeset = "UTF-8";
7743 enum request request = parse_options(argc, argv);
7744 struct view *view;
7745 size_t i;
7747 signal(SIGINT, quit);
7748 signal(SIGPIPE, SIG_IGN);
7750 if (setlocale(LC_ALL, "")) {
7751 codeset = nl_langinfo(CODESET);
7754 if (load_repo_info() == ERR)
7755 die("Failed to load repo info.");
7757 if (load_options() == ERR)
7758 die("Failed to load user config.");
7760 if (load_git_config() == ERR)
7761 die("Failed to load repo config.");
7763 /* Require a git repository unless when running in pager mode. */
7764 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7765 die("Not a git repository");
7767 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7768 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7769 if (opt_iconv_in == ICONV_NONE)
7770 die("Failed to initialize character set conversion");
7773 if (codeset && strcmp(codeset, "UTF-8")) {
7774 opt_iconv_out = iconv_open(codeset, "UTF-8");
7775 if (opt_iconv_out == ICONV_NONE)
7776 die("Failed to initialize character set conversion");
7779 if (load_refs() == ERR)
7780 die("Failed to load refs.");
7782 foreach_view (view, i)
7783 argv_from_env(view->ops->argv, view->cmd_env);
7785 init_display();
7787 if (request != REQ_NONE)
7788 open_view(NULL, request, OPEN_PREPARED);
7789 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7791 while (view_driver(display[current_view], request)) {
7792 int key = get_input(0);
7794 view = display[current_view];
7795 request = get_keybinding(view->keymap, key);
7797 /* Some low-level request handling. This keeps access to
7798 * status_win restricted. */
7799 switch (request) {
7800 case REQ_PROMPT:
7802 char *cmd = read_prompt(":");
7804 if (cmd && isdigit(*cmd)) {
7805 int lineno = view->lineno + 1;
7807 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7808 select_view_line(view, lineno - 1);
7809 report("");
7810 } else {
7811 report("Unable to parse '%s' as a line number", cmd);
7814 } else if (cmd) {
7815 struct view *next = VIEW(REQ_VIEW_PAGER);
7816 const char *argv[SIZEOF_ARG] = { "git" };
7817 int argc = 1;
7819 /* When running random commands, initially show the
7820 * command in the title. However, it maybe later be
7821 * overwritten if a commit line is selected. */
7822 string_ncopy(next->ref, cmd, strlen(cmd));
7824 if (!argv_from_string(argv, &argc, cmd)) {
7825 report("Too many arguments");
7826 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7827 report("Failed to format command");
7828 } else {
7829 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7833 request = REQ_NONE;
7834 break;
7836 case REQ_SEARCH:
7837 case REQ_SEARCH_BACK:
7839 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7840 char *search = read_prompt(prompt);
7842 if (search)
7843 string_ncopy(opt_search, search, strlen(search));
7844 else if (*opt_search)
7845 request = request == REQ_SEARCH ?
7846 REQ_FIND_NEXT :
7847 REQ_FIND_PREV;
7848 else
7849 request = REQ_NONE;
7850 break;
7852 default:
7853 break;
7857 quit(0);
7859 return 0;