io: fix comment in io struct
[tig.git] / tig.c
blob4a07a515b79532aafcefbc96e7b6013043e1e536
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 mkdate(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 || !time || !time->sec)
405 return "";
407 if (date == DATE_RELATIVE) {
408 struct timeval now;
409 time_t date = time->sec + time->tz;
410 time_t seconds;
411 int i;
413 gettimeofday(&now, NULL);
414 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
415 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
416 if (seconds >= reldate[i].value)
417 continue;
419 seconds /= reldate[i].namelen;
420 if (!string_format(buf, "%ld %s%s %s",
421 seconds, reldate[i].name,
422 seconds > 1 ? "s" : "",
423 now.tv_sec >= date ? "ago" : "ahead"))
424 break;
425 return buf;
429 gmtime_r(&time->sec, &tm);
430 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
434 #define AUTHOR_VALUES \
435 AUTHOR_(NO), \
436 AUTHOR_(FULL), \
437 AUTHOR_(ABBREVIATED)
439 enum author {
440 #define AUTHOR_(name) AUTHOR_##name
441 AUTHOR_VALUES,
442 #undef AUTHOR_
443 AUTHOR_DEFAULT = AUTHOR_FULL
446 static const struct enum_map author_map[] = {
447 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
448 AUTHOR_VALUES
449 #undef AUTHOR_
452 static const char *
453 get_author_initials(const char *author)
455 static char initials[AUTHOR_COLS * 6 + 1];
456 size_t pos = 0;
457 const char *end = strchr(author, '\0');
459 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
461 memset(initials, 0, sizeof(initials));
462 while (author < end) {
463 unsigned char bytes;
464 size_t i;
466 while (is_initial_sep(*author))
467 author++;
469 bytes = utf8_char_length(author, end);
470 if (bytes < sizeof(initials) - 1 - pos) {
471 while (bytes--) {
472 initials[pos++] = *author++;
476 for (i = pos; author < end && !is_initial_sep(*author); author++) {
477 if (i < sizeof(initials) - 1)
478 initials[i++] = *author;
481 initials[i++] = 0;
484 return initials;
488 static bool
489 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
491 int valuelen;
493 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
494 bool advance = cmd[valuelen] != 0;
496 cmd[valuelen] = 0;
497 argv[(*argc)++] = chomp_string(cmd);
498 cmd = chomp_string(cmd + valuelen + advance);
501 if (*argc < SIZEOF_ARG)
502 argv[*argc] = NULL;
503 return *argc < SIZEOF_ARG;
506 static void
507 argv_from_env(const char **argv, const char *name)
509 char *env = argv ? getenv(name) : NULL;
510 int argc = 0;
512 if (env && *env)
513 env = strdup(env);
514 if (env && !argv_from_string(argv, &argc, env))
515 die("Too many arguments in the `%s` environment variable", name);
520 * Executing external commands.
523 enum io_type {
524 IO_FD, /* File descriptor based IO. */
525 IO_BG, /* Execute command in the background. */
526 IO_FG, /* Execute command with same std{in,out,err}. */
527 IO_RD, /* Read only fork+exec IO. */
528 IO_WR, /* Write only fork+exec IO. */
529 IO_AP, /* Append fork+exec output to file. */
532 struct io {
533 enum io_type type; /* The requested type of pipe. */
534 const char *dir; /* Directory from which to execute. */
535 pid_t pid; /* PID of spawned process. */
536 int pipe; /* Pipe end for reading or writing. */
537 int error; /* Error status. */
538 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
539 char *buf; /* Read buffer. */
540 size_t bufalloc; /* Allocated buffer size. */
541 size_t bufsize; /* Buffer content size. */
542 char *bufpos; /* Current buffer position. */
543 unsigned int eof:1; /* Has end of file been reached. */
546 static void
547 reset_io(struct io *io)
549 io->pipe = -1;
550 io->pid = 0;
551 io->buf = io->bufpos = NULL;
552 io->bufalloc = io->bufsize = 0;
553 io->error = 0;
554 io->eof = 0;
557 static void
558 init_io(struct io *io, const char *dir, enum io_type type)
560 reset_io(io);
561 io->type = type;
562 io->dir = dir;
565 static bool
566 init_io_rd(struct io *io, const char *argv[], const char *dir,
567 enum format_flags flags)
569 init_io(io, dir, IO_RD);
570 return format_argv(io->argv, argv, flags);
573 static bool
574 io_open(struct io *io, const char *fmt, ...)
576 char name[SIZEOF_STR] = "";
577 bool fits;
578 va_list args;
580 init_io(io, NULL, IO_FD);
582 va_start(args, fmt);
583 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
584 va_end(args);
586 if (!fits) {
587 io->error = ENAMETOOLONG;
588 return FALSE;
590 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
591 if (io->pipe == -1)
592 io->error = errno;
593 return io->pipe != -1;
596 static bool
597 kill_io(struct io *io)
599 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
602 static bool
603 done_io(struct io *io)
605 pid_t pid = io->pid;
607 if (io->pipe != -1)
608 close(io->pipe);
609 free(io->buf);
610 reset_io(io);
612 while (pid > 0) {
613 int status;
614 pid_t waiting = waitpid(pid, &status, 0);
616 if (waiting < 0) {
617 if (errno == EINTR)
618 continue;
619 report("waitpid failed (%s)", strerror(errno));
620 return FALSE;
623 return waiting == pid &&
624 !WIFSIGNALED(status) &&
625 WIFEXITED(status) &&
626 !WEXITSTATUS(status);
629 return TRUE;
632 static bool
633 start_io(struct io *io)
635 int pipefds[2] = { -1, -1 };
637 if (io->type == IO_FD)
638 return TRUE;
640 if ((io->type == IO_RD || io->type == IO_WR) &&
641 pipe(pipefds) < 0)
642 return FALSE;
643 else if (io->type == IO_AP)
644 pipefds[1] = io->pipe;
646 if ((io->pid = fork())) {
647 if (pipefds[!(io->type == IO_WR)] != -1)
648 close(pipefds[!(io->type == IO_WR)]);
649 if (io->pid != -1) {
650 io->pipe = pipefds[!!(io->type == IO_WR)];
651 return TRUE;
654 } else {
655 if (io->type != IO_FG) {
656 int devnull = open("/dev/null", O_RDWR);
657 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
658 int writefd = (io->type == IO_RD || io->type == IO_AP)
659 ? pipefds[1] : devnull;
661 dup2(readfd, STDIN_FILENO);
662 dup2(writefd, STDOUT_FILENO);
663 dup2(devnull, STDERR_FILENO);
665 close(devnull);
666 if (pipefds[0] != -1)
667 close(pipefds[0]);
668 if (pipefds[1] != -1)
669 close(pipefds[1]);
672 if (io->dir && *io->dir && chdir(io->dir) == -1)
673 die("Failed to change directory: %s", strerror(errno));
675 execvp(io->argv[0], (char *const*) io->argv);
676 die("Failed to execute program: %s", strerror(errno));
679 if (pipefds[!!(io->type == IO_WR)] != -1)
680 close(pipefds[!!(io->type == IO_WR)]);
681 return FALSE;
684 static bool
685 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
687 init_io(io, dir, type);
688 if (!format_argv(io->argv, argv, FORMAT_NONE))
689 return FALSE;
690 return start_io(io);
693 static int
694 run_io_do(struct io *io)
696 return start_io(io) && done_io(io);
699 static int
700 run_io_bg(const char **argv)
702 struct io io = {};
704 init_io(&io, NULL, IO_BG);
705 if (!format_argv(io.argv, argv, FORMAT_NONE))
706 return FALSE;
707 return run_io_do(&io);
710 static bool
711 run_io_fg(const char **argv, const char *dir)
713 struct io io = {};
715 init_io(&io, dir, IO_FG);
716 if (!format_argv(io.argv, argv, FORMAT_NONE))
717 return FALSE;
718 return run_io_do(&io);
721 static bool
722 run_io_append(const char **argv, enum format_flags flags, int fd)
724 struct io io = {};
726 init_io(&io, NULL, IO_AP);
727 io.pipe = fd;
728 if (format_argv(io.argv, argv, flags))
729 return run_io_do(&io);
730 close(fd);
731 return FALSE;
734 static bool
735 run_io_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
737 return init_io_rd(io, argv, dir, flags) && start_io(io);
740 static bool
741 io_eof(struct io *io)
743 return io->eof;
746 static int
747 io_error(struct io *io)
749 return io->error;
752 static char *
753 io_strerror(struct io *io)
755 return strerror(io->error);
758 static bool
759 io_can_read(struct io *io)
761 struct timeval tv = { 0, 500 };
762 fd_set fds;
764 FD_ZERO(&fds);
765 FD_SET(io->pipe, &fds);
767 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
770 static ssize_t
771 io_read(struct io *io, void *buf, size_t bufsize)
773 do {
774 ssize_t readsize = read(io->pipe, buf, bufsize);
776 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
777 continue;
778 else if (readsize == -1)
779 io->error = errno;
780 else if (readsize == 0)
781 io->eof = 1;
782 return readsize;
783 } while (1);
786 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
788 static char *
789 io_get(struct io *io, int c, bool can_read)
791 char *eol;
792 ssize_t readsize;
794 while (TRUE) {
795 if (io->bufsize > 0) {
796 eol = memchr(io->bufpos, c, io->bufsize);
797 if (eol) {
798 char *line = io->bufpos;
800 *eol = 0;
801 io->bufpos = eol + 1;
802 io->bufsize -= io->bufpos - line;
803 return line;
807 if (io_eof(io)) {
808 if (io->bufsize) {
809 io->bufpos[io->bufsize] = 0;
810 io->bufsize = 0;
811 return io->bufpos;
813 return NULL;
816 if (!can_read)
817 return NULL;
819 if (io->bufsize > 0 && io->bufpos > io->buf)
820 memmove(io->buf, io->bufpos, io->bufsize);
822 if (io->bufalloc == io->bufsize) {
823 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
824 return NULL;
825 io->bufalloc += BUFSIZ;
828 io->bufpos = io->buf;
829 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
830 if (io_error(io))
831 return NULL;
832 io->bufsize += readsize;
836 static bool
837 io_write(struct io *io, const void *buf, size_t bufsize)
839 size_t written = 0;
841 while (!io_error(io) && written < bufsize) {
842 ssize_t size;
844 size = write(io->pipe, buf + written, bufsize - written);
845 if (size < 0 && (errno == EAGAIN || errno == EINTR))
846 continue;
847 else if (size == -1)
848 io->error = errno;
849 else
850 written += size;
853 return written == bufsize;
856 static bool
857 io_read_buf(struct io *io, char buf[], size_t bufsize)
859 char *result = io_get(io, '\n', TRUE);
861 if (result) {
862 result = chomp_string(result);
863 string_ncopy_do(buf, bufsize, result, strlen(result));
866 return done_io(io) && result;
869 static bool
870 run_io_buf(const char **argv, char buf[], size_t bufsize)
872 struct io io = {};
874 return run_io_rd(&io, argv, NULL, FORMAT_NONE)
875 && io_read_buf(&io, buf, bufsize);
878 static int
879 io_load(struct io *io, const char *separators,
880 int (*read_property)(char *, size_t, char *, size_t))
882 char *name;
883 int state = OK;
885 if (!start_io(io))
886 return ERR;
888 while (state == OK && (name = io_get(io, '\n', TRUE))) {
889 char *value;
890 size_t namelen;
891 size_t valuelen;
893 name = chomp_string(name);
894 namelen = strcspn(name, separators);
896 if (name[namelen]) {
897 name[namelen] = 0;
898 value = chomp_string(name + namelen + 1);
899 valuelen = strlen(value);
901 } else {
902 value = "";
903 valuelen = 0;
906 state = read_property(name, namelen, value, valuelen);
909 if (state != ERR && io_error(io))
910 state = ERR;
911 done_io(io);
913 return state;
916 static int
917 run_io_load(const char **argv, const char *separators,
918 int (*read_property)(char *, size_t, char *, size_t))
920 struct io io = {};
922 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
923 ? io_load(&io, separators, read_property) : ERR;
928 * User requests
931 #define REQ_INFO \
932 /* XXX: Keep the view request first and in sync with views[]. */ \
933 REQ_GROUP("View switching") \
934 REQ_(VIEW_MAIN, "Show main view"), \
935 REQ_(VIEW_DIFF, "Show diff view"), \
936 REQ_(VIEW_LOG, "Show log view"), \
937 REQ_(VIEW_TREE, "Show tree view"), \
938 REQ_(VIEW_BLOB, "Show blob view"), \
939 REQ_(VIEW_BLAME, "Show blame view"), \
940 REQ_(VIEW_BRANCH, "Show branch view"), \
941 REQ_(VIEW_HELP, "Show help page"), \
942 REQ_(VIEW_PAGER, "Show pager view"), \
943 REQ_(VIEW_STATUS, "Show status view"), \
944 REQ_(VIEW_STAGE, "Show stage view"), \
946 REQ_GROUP("View manipulation") \
947 REQ_(ENTER, "Enter current line and scroll"), \
948 REQ_(NEXT, "Move to next"), \
949 REQ_(PREVIOUS, "Move to previous"), \
950 REQ_(PARENT, "Move to parent"), \
951 REQ_(VIEW_NEXT, "Move focus to next view"), \
952 REQ_(REFRESH, "Reload and refresh"), \
953 REQ_(MAXIMIZE, "Maximize the current view"), \
954 REQ_(VIEW_CLOSE, "Close the current view"), \
955 REQ_(QUIT, "Close all views and quit"), \
957 REQ_GROUP("View specific requests") \
958 REQ_(STATUS_UPDATE, "Update file status"), \
959 REQ_(STATUS_REVERT, "Revert file changes"), \
960 REQ_(STATUS_MERGE, "Merge file using external tool"), \
961 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
963 REQ_GROUP("Cursor navigation") \
964 REQ_(MOVE_UP, "Move cursor one line up"), \
965 REQ_(MOVE_DOWN, "Move cursor one line down"), \
966 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
967 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
968 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
969 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
971 REQ_GROUP("Scrolling") \
972 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
973 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
974 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
975 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
976 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
977 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
979 REQ_GROUP("Searching") \
980 REQ_(SEARCH, "Search the view"), \
981 REQ_(SEARCH_BACK, "Search backwards in the view"), \
982 REQ_(FIND_NEXT, "Find next search match"), \
983 REQ_(FIND_PREV, "Find previous search match"), \
985 REQ_GROUP("Option manipulation") \
986 REQ_(OPTIONS, "Open option menu"), \
987 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
988 REQ_(TOGGLE_DATE, "Toggle date display"), \
989 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
990 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
991 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
992 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
993 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
994 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
996 REQ_GROUP("Misc") \
997 REQ_(PROMPT, "Bring up the prompt"), \
998 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
999 REQ_(SHOW_VERSION, "Show version information"), \
1000 REQ_(STOP_LOADING, "Stop all loading views"), \
1001 REQ_(EDIT, "Open in editor"), \
1002 REQ_(NONE, "Do nothing")
1005 /* User action requests. */
1006 enum request {
1007 #define REQ_GROUP(help)
1008 #define REQ_(req, help) REQ_##req
1010 /* Offset all requests to avoid conflicts with ncurses getch values. */
1011 REQ_OFFSET = KEY_MAX + 1,
1012 REQ_INFO
1014 #undef REQ_GROUP
1015 #undef REQ_
1018 struct request_info {
1019 enum request request;
1020 const char *name;
1021 int namelen;
1022 const char *help;
1025 static const struct request_info req_info[] = {
1026 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1027 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1028 REQ_INFO
1029 #undef REQ_GROUP
1030 #undef REQ_
1033 static enum request
1034 get_request(const char *name)
1036 int namelen = strlen(name);
1037 int i;
1039 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1040 if (enum_equals(req_info[i], name, namelen))
1041 return req_info[i].request;
1043 return REQ_NONE;
1048 * Options
1051 /* Option and state variables. */
1052 static enum date opt_date = DATE_DEFAULT;
1053 static enum author opt_author = AUTHOR_DEFAULT;
1054 static bool opt_line_number = FALSE;
1055 static bool opt_line_graphics = TRUE;
1056 static bool opt_rev_graph = FALSE;
1057 static bool opt_show_refs = TRUE;
1058 static int opt_num_interval = 5;
1059 static double opt_hscroll = 0.50;
1060 static double opt_scale_split_view = 2.0 / 3.0;
1061 static int opt_tab_size = 8;
1062 static int opt_author_cols = AUTHOR_COLS;
1063 static char opt_path[SIZEOF_STR] = "";
1064 static char opt_file[SIZEOF_STR] = "";
1065 static char opt_ref[SIZEOF_REF] = "";
1066 static char opt_head[SIZEOF_REF] = "";
1067 static char opt_remote[SIZEOF_REF] = "";
1068 static char opt_encoding[20] = "UTF-8";
1069 static iconv_t opt_iconv_in = ICONV_NONE;
1070 static iconv_t opt_iconv_out = ICONV_NONE;
1071 static char opt_search[SIZEOF_STR] = "";
1072 static char opt_cdup[SIZEOF_STR] = "";
1073 static char opt_prefix[SIZEOF_STR] = "";
1074 static char opt_git_dir[SIZEOF_STR] = "";
1075 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1076 static char opt_editor[SIZEOF_STR] = "";
1077 static FILE *opt_tty = NULL;
1079 #define is_initial_commit() (!get_ref_head())
1080 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1084 * Line-oriented content detection.
1087 #define LINE_INFO \
1088 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1089 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1090 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1102 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1103 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1104 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1106 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1107 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1109 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1110 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1111 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1112 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1114 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1115 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1116 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1117 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1118 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1119 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1120 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1121 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1122 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1123 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1124 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1125 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1126 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1127 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1133 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1134 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1135 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1136 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1137 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1138 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1139 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1140 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1141 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1143 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1144 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1146 enum line_type {
1147 #define LINE(type, line, fg, bg, attr) \
1148 LINE_##type
1149 LINE_INFO,
1150 LINE_NONE
1151 #undef LINE
1154 struct line_info {
1155 const char *name; /* Option name. */
1156 int namelen; /* Size of option name. */
1157 const char *line; /* The start of line to match. */
1158 int linelen; /* Size of string to match. */
1159 int fg, bg, attr; /* Color and text attributes for the lines. */
1162 static struct line_info line_info[] = {
1163 #define LINE(type, line, fg, bg, attr) \
1164 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1165 LINE_INFO
1166 #undef LINE
1169 static enum line_type
1170 get_line_type(const char *line)
1172 int linelen = strlen(line);
1173 enum line_type type;
1175 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1176 /* Case insensitive search matches Signed-off-by lines better. */
1177 if (linelen >= line_info[type].linelen &&
1178 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1179 return type;
1181 return LINE_DEFAULT;
1184 static inline int
1185 get_line_attr(enum line_type type)
1187 assert(type < ARRAY_SIZE(line_info));
1188 return COLOR_PAIR(type) | line_info[type].attr;
1191 static struct line_info *
1192 get_line_info(const char *name)
1194 size_t namelen = strlen(name);
1195 enum line_type type;
1197 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1198 if (enum_equals(line_info[type], name, namelen))
1199 return &line_info[type];
1201 return NULL;
1204 static void
1205 init_colors(void)
1207 int default_bg = line_info[LINE_DEFAULT].bg;
1208 int default_fg = line_info[LINE_DEFAULT].fg;
1209 enum line_type type;
1211 start_color();
1213 if (assume_default_colors(default_fg, default_bg) == ERR) {
1214 default_bg = COLOR_BLACK;
1215 default_fg = COLOR_WHITE;
1218 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1219 struct line_info *info = &line_info[type];
1220 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1221 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1223 init_pair(type, fg, bg);
1227 struct line {
1228 enum line_type type;
1230 /* State flags */
1231 unsigned int selected:1;
1232 unsigned int dirty:1;
1233 unsigned int cleareol:1;
1234 unsigned int other:16;
1236 void *data; /* User data */
1241 * Keys
1244 struct keybinding {
1245 int alias;
1246 enum request request;
1249 static const struct keybinding default_keybindings[] = {
1250 /* View switching */
1251 { 'm', REQ_VIEW_MAIN },
1252 { 'd', REQ_VIEW_DIFF },
1253 { 'l', REQ_VIEW_LOG },
1254 { 't', REQ_VIEW_TREE },
1255 { 'f', REQ_VIEW_BLOB },
1256 { 'B', REQ_VIEW_BLAME },
1257 { 'H', REQ_VIEW_BRANCH },
1258 { 'p', REQ_VIEW_PAGER },
1259 { 'h', REQ_VIEW_HELP },
1260 { 'S', REQ_VIEW_STATUS },
1261 { 'c', REQ_VIEW_STAGE },
1263 /* View manipulation */
1264 { 'q', REQ_VIEW_CLOSE },
1265 { KEY_TAB, REQ_VIEW_NEXT },
1266 { KEY_RETURN, REQ_ENTER },
1267 { KEY_UP, REQ_PREVIOUS },
1268 { KEY_DOWN, REQ_NEXT },
1269 { 'R', REQ_REFRESH },
1270 { KEY_F(5), REQ_REFRESH },
1271 { 'O', REQ_MAXIMIZE },
1273 /* Cursor navigation */
1274 { 'k', REQ_MOVE_UP },
1275 { 'j', REQ_MOVE_DOWN },
1276 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1277 { KEY_END, REQ_MOVE_LAST_LINE },
1278 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1279 { ' ', REQ_MOVE_PAGE_DOWN },
1280 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1281 { 'b', REQ_MOVE_PAGE_UP },
1282 { '-', REQ_MOVE_PAGE_UP },
1284 /* Scrolling */
1285 { KEY_LEFT, REQ_SCROLL_LEFT },
1286 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1287 { KEY_IC, REQ_SCROLL_LINE_UP },
1288 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1289 { 'w', REQ_SCROLL_PAGE_UP },
1290 { 's', REQ_SCROLL_PAGE_DOWN },
1292 /* Searching */
1293 { '/', REQ_SEARCH },
1294 { '?', REQ_SEARCH_BACK },
1295 { 'n', REQ_FIND_NEXT },
1296 { 'N', REQ_FIND_PREV },
1298 /* Misc */
1299 { 'Q', REQ_QUIT },
1300 { 'z', REQ_STOP_LOADING },
1301 { 'v', REQ_SHOW_VERSION },
1302 { 'r', REQ_SCREEN_REDRAW },
1303 { 'o', REQ_OPTIONS },
1304 { '.', REQ_TOGGLE_LINENO },
1305 { 'D', REQ_TOGGLE_DATE },
1306 { 'A', REQ_TOGGLE_AUTHOR },
1307 { 'g', REQ_TOGGLE_REV_GRAPH },
1308 { 'F', REQ_TOGGLE_REFS },
1309 { 'I', REQ_TOGGLE_SORT_ORDER },
1310 { 'i', REQ_TOGGLE_SORT_FIELD },
1311 { ':', REQ_PROMPT },
1312 { 'u', REQ_STATUS_UPDATE },
1313 { '!', REQ_STATUS_REVERT },
1314 { 'M', REQ_STATUS_MERGE },
1315 { '@', REQ_STAGE_NEXT },
1316 { ',', REQ_PARENT },
1317 { 'e', REQ_EDIT },
1320 #define KEYMAP_INFO \
1321 KEYMAP_(GENERIC), \
1322 KEYMAP_(MAIN), \
1323 KEYMAP_(DIFF), \
1324 KEYMAP_(LOG), \
1325 KEYMAP_(TREE), \
1326 KEYMAP_(BLOB), \
1327 KEYMAP_(BLAME), \
1328 KEYMAP_(BRANCH), \
1329 KEYMAP_(PAGER), \
1330 KEYMAP_(HELP), \
1331 KEYMAP_(STATUS), \
1332 KEYMAP_(STAGE)
1334 enum keymap {
1335 #define KEYMAP_(name) KEYMAP_##name
1336 KEYMAP_INFO
1337 #undef KEYMAP_
1340 static const struct enum_map keymap_table[] = {
1341 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1342 KEYMAP_INFO
1343 #undef KEYMAP_
1346 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1348 struct keybinding_table {
1349 struct keybinding *data;
1350 size_t size;
1353 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1355 static void
1356 add_keybinding(enum keymap keymap, enum request request, int key)
1358 struct keybinding_table *table = &keybindings[keymap];
1360 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1361 if (!table->data)
1362 die("Failed to allocate keybinding");
1363 table->data[table->size].alias = key;
1364 table->data[table->size++].request = request;
1367 /* Looks for a key binding first in the given map, then in the generic map, and
1368 * lastly in the default keybindings. */
1369 static enum request
1370 get_keybinding(enum keymap keymap, int key)
1372 size_t i;
1374 for (i = 0; i < keybindings[keymap].size; i++)
1375 if (keybindings[keymap].data[i].alias == key)
1376 return keybindings[keymap].data[i].request;
1378 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1379 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1380 return keybindings[KEYMAP_GENERIC].data[i].request;
1382 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1383 if (default_keybindings[i].alias == key)
1384 return default_keybindings[i].request;
1386 return (enum request) key;
1390 struct key {
1391 const char *name;
1392 int value;
1395 static const struct key key_table[] = {
1396 { "Enter", KEY_RETURN },
1397 { "Space", ' ' },
1398 { "Backspace", KEY_BACKSPACE },
1399 { "Tab", KEY_TAB },
1400 { "Escape", KEY_ESC },
1401 { "Left", KEY_LEFT },
1402 { "Right", KEY_RIGHT },
1403 { "Up", KEY_UP },
1404 { "Down", KEY_DOWN },
1405 { "Insert", KEY_IC },
1406 { "Delete", KEY_DC },
1407 { "Hash", '#' },
1408 { "Home", KEY_HOME },
1409 { "End", KEY_END },
1410 { "PageUp", KEY_PPAGE },
1411 { "PageDown", KEY_NPAGE },
1412 { "F1", KEY_F(1) },
1413 { "F2", KEY_F(2) },
1414 { "F3", KEY_F(3) },
1415 { "F4", KEY_F(4) },
1416 { "F5", KEY_F(5) },
1417 { "F6", KEY_F(6) },
1418 { "F7", KEY_F(7) },
1419 { "F8", KEY_F(8) },
1420 { "F9", KEY_F(9) },
1421 { "F10", KEY_F(10) },
1422 { "F11", KEY_F(11) },
1423 { "F12", KEY_F(12) },
1426 static int
1427 get_key_value(const char *name)
1429 int i;
1431 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1432 if (!strcasecmp(key_table[i].name, name))
1433 return key_table[i].value;
1435 if (strlen(name) == 1 && isprint(*name))
1436 return (int) *name;
1438 return ERR;
1441 static const char *
1442 get_key_name(int key_value)
1444 static char key_char[] = "'X'";
1445 const char *seq = NULL;
1446 int key;
1448 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1449 if (key_table[key].value == key_value)
1450 seq = key_table[key].name;
1452 if (seq == NULL &&
1453 key_value < 127 &&
1454 isprint(key_value)) {
1455 key_char[1] = (char) key_value;
1456 seq = key_char;
1459 return seq ? seq : "(no key)";
1462 static bool
1463 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1465 const char *sep = *pos > 0 ? ", " : "";
1466 const char *keyname = get_key_name(keybinding->alias);
1468 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1471 static bool
1472 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1473 enum keymap keymap, bool all)
1475 int i;
1477 for (i = 0; i < keybindings[keymap].size; i++) {
1478 if (keybindings[keymap].data[i].request == request) {
1479 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1480 return FALSE;
1481 if (!all)
1482 break;
1486 return TRUE;
1489 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1491 static const char *
1492 get_keys(enum keymap keymap, enum request request, bool all)
1494 static char buf[BUFSIZ];
1495 size_t pos = 0;
1496 int i;
1498 buf[pos] = 0;
1500 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1501 return "Too many keybindings!";
1502 if (pos > 0 && !all)
1503 return buf;
1505 if (keymap != KEYMAP_GENERIC) {
1506 /* Only the generic keymap includes the default keybindings when
1507 * listing all keys. */
1508 if (all)
1509 return buf;
1511 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1512 return "Too many keybindings!";
1513 if (pos)
1514 return buf;
1517 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1518 if (default_keybindings[i].request == request) {
1519 if (!append_key(buf, &pos, &default_keybindings[i]))
1520 return "Too many keybindings!";
1521 if (!all)
1522 return buf;
1526 return buf;
1529 struct run_request {
1530 enum keymap keymap;
1531 int key;
1532 const char *argv[SIZEOF_ARG];
1535 static struct run_request *run_request;
1536 static size_t run_requests;
1538 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1540 static enum request
1541 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1543 struct run_request *req;
1545 if (argc >= ARRAY_SIZE(req->argv) - 1)
1546 return REQ_NONE;
1548 if (!realloc_run_requests(&run_request, run_requests, 1))
1549 return REQ_NONE;
1551 req = &run_request[run_requests];
1552 req->keymap = keymap;
1553 req->key = key;
1554 req->argv[0] = NULL;
1556 if (!format_argv(req->argv, argv, FORMAT_NONE))
1557 return REQ_NONE;
1559 return REQ_NONE + ++run_requests;
1562 static struct run_request *
1563 get_run_request(enum request request)
1565 if (request <= REQ_NONE)
1566 return NULL;
1567 return &run_request[request - REQ_NONE - 1];
1570 static void
1571 add_builtin_run_requests(void)
1573 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1574 const char *commit[] = { "git", "commit", NULL };
1575 const char *gc[] = { "git", "gc", NULL };
1576 struct {
1577 enum keymap keymap;
1578 int key;
1579 int argc;
1580 const char **argv;
1581 } reqs[] = {
1582 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1583 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1584 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1586 int i;
1588 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1589 enum request req;
1591 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1592 if (req != REQ_NONE)
1593 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1598 * User config file handling.
1601 static int config_lineno;
1602 static bool config_errors;
1603 static const char *config_msg;
1605 static const struct enum_map color_map[] = {
1606 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1607 COLOR_MAP(DEFAULT),
1608 COLOR_MAP(BLACK),
1609 COLOR_MAP(BLUE),
1610 COLOR_MAP(CYAN),
1611 COLOR_MAP(GREEN),
1612 COLOR_MAP(MAGENTA),
1613 COLOR_MAP(RED),
1614 COLOR_MAP(WHITE),
1615 COLOR_MAP(YELLOW),
1618 static const struct enum_map attr_map[] = {
1619 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1620 ATTR_MAP(NORMAL),
1621 ATTR_MAP(BLINK),
1622 ATTR_MAP(BOLD),
1623 ATTR_MAP(DIM),
1624 ATTR_MAP(REVERSE),
1625 ATTR_MAP(STANDOUT),
1626 ATTR_MAP(UNDERLINE),
1629 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1631 static int parse_step(double *opt, const char *arg)
1633 *opt = atoi(arg);
1634 if (!strchr(arg, '%'))
1635 return OK;
1637 /* "Shift down" so 100% and 1 does not conflict. */
1638 *opt = (*opt - 1) / 100;
1639 if (*opt >= 1.0) {
1640 *opt = 0.99;
1641 config_msg = "Step value larger than 100%";
1642 return ERR;
1644 if (*opt < 0.0) {
1645 *opt = 1;
1646 config_msg = "Invalid step value";
1647 return ERR;
1649 return OK;
1652 static int
1653 parse_int(int *opt, const char *arg, int min, int max)
1655 int value = atoi(arg);
1657 if (min <= value && value <= max) {
1658 *opt = value;
1659 return OK;
1662 config_msg = "Integer value out of bound";
1663 return ERR;
1666 static bool
1667 set_color(int *color, const char *name)
1669 if (map_enum(color, color_map, name))
1670 return TRUE;
1671 if (!prefixcmp(name, "color"))
1672 return parse_int(color, name + 5, 0, 255) == OK;
1673 return FALSE;
1676 /* Wants: object fgcolor bgcolor [attribute] */
1677 static int
1678 option_color_command(int argc, const char *argv[])
1680 struct line_info *info;
1682 if (argc < 3) {
1683 config_msg = "Wrong number of arguments given to color command";
1684 return ERR;
1687 info = get_line_info(argv[0]);
1688 if (!info) {
1689 static const struct enum_map obsolete[] = {
1690 ENUM_MAP("main-delim", LINE_DELIMITER),
1691 ENUM_MAP("main-date", LINE_DATE),
1692 ENUM_MAP("main-author", LINE_AUTHOR),
1694 int index;
1696 if (!map_enum(&index, obsolete, argv[0])) {
1697 config_msg = "Unknown color name";
1698 return ERR;
1700 info = &line_info[index];
1703 if (!set_color(&info->fg, argv[1]) ||
1704 !set_color(&info->bg, argv[2])) {
1705 config_msg = "Unknown color";
1706 return ERR;
1709 info->attr = 0;
1710 while (argc-- > 3) {
1711 int attr;
1713 if (!set_attribute(&attr, argv[argc])) {
1714 config_msg = "Unknown attribute";
1715 return ERR;
1717 info->attr |= attr;
1720 return OK;
1723 static int parse_bool(bool *opt, const char *arg)
1725 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1726 ? TRUE : FALSE;
1727 return OK;
1730 static int parse_enum_do(unsigned int *opt, const char *arg,
1731 const struct enum_map *map, size_t map_size)
1733 bool is_true;
1735 assert(map_size > 1);
1737 if (map_enum_do(map, map_size, (int *) opt, arg))
1738 return OK;
1740 if (parse_bool(&is_true, arg) != OK)
1741 return ERR;
1743 *opt = is_true ? map[1].value : map[0].value;
1744 return OK;
1747 #define parse_enum(opt, arg, map) \
1748 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1750 static int
1751 parse_string(char *opt, const char *arg, size_t optsize)
1753 int arglen = strlen(arg);
1755 switch (arg[0]) {
1756 case '\"':
1757 case '\'':
1758 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1759 config_msg = "Unmatched quotation";
1760 return ERR;
1762 arg += 1; arglen -= 2;
1763 default:
1764 string_ncopy_do(opt, optsize, arg, arglen);
1765 return OK;
1769 /* Wants: name = value */
1770 static int
1771 option_set_command(int argc, const char *argv[])
1773 if (argc != 3) {
1774 config_msg = "Wrong number of arguments given to set command";
1775 return ERR;
1778 if (strcmp(argv[1], "=")) {
1779 config_msg = "No value assigned";
1780 return ERR;
1783 if (!strcmp(argv[0], "show-author"))
1784 return parse_enum(&opt_author, argv[2], author_map);
1786 if (!strcmp(argv[0], "show-date"))
1787 return parse_enum(&opt_date, argv[2], date_map);
1789 if (!strcmp(argv[0], "show-rev-graph"))
1790 return parse_bool(&opt_rev_graph, argv[2]);
1792 if (!strcmp(argv[0], "show-refs"))
1793 return parse_bool(&opt_show_refs, argv[2]);
1795 if (!strcmp(argv[0], "show-line-numbers"))
1796 return parse_bool(&opt_line_number, argv[2]);
1798 if (!strcmp(argv[0], "line-graphics"))
1799 return parse_bool(&opt_line_graphics, argv[2]);
1801 if (!strcmp(argv[0], "line-number-interval"))
1802 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1804 if (!strcmp(argv[0], "author-width"))
1805 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1807 if (!strcmp(argv[0], "horizontal-scroll"))
1808 return parse_step(&opt_hscroll, argv[2]);
1810 if (!strcmp(argv[0], "split-view-height"))
1811 return parse_step(&opt_scale_split_view, argv[2]);
1813 if (!strcmp(argv[0], "tab-size"))
1814 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1816 if (!strcmp(argv[0], "commit-encoding"))
1817 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1819 config_msg = "Unknown variable name";
1820 return ERR;
1823 /* Wants: mode request key */
1824 static int
1825 option_bind_command(int argc, const char *argv[])
1827 enum request request;
1828 int keymap = -1;
1829 int key;
1831 if (argc < 3) {
1832 config_msg = "Wrong number of arguments given to bind command";
1833 return ERR;
1836 if (set_keymap(&keymap, argv[0]) == ERR) {
1837 config_msg = "Unknown key map";
1838 return ERR;
1841 key = get_key_value(argv[1]);
1842 if (key == ERR) {
1843 config_msg = "Unknown key";
1844 return ERR;
1847 request = get_request(argv[2]);
1848 if (request == REQ_NONE) {
1849 static const struct enum_map obsolete[] = {
1850 ENUM_MAP("cherry-pick", REQ_NONE),
1851 ENUM_MAP("screen-resize", REQ_NONE),
1852 ENUM_MAP("tree-parent", REQ_PARENT),
1854 int alias;
1856 if (map_enum(&alias, obsolete, argv[2])) {
1857 if (alias != REQ_NONE)
1858 add_keybinding(keymap, alias, key);
1859 config_msg = "Obsolete request name";
1860 return ERR;
1863 if (request == REQ_NONE && *argv[2]++ == '!')
1864 request = add_run_request(keymap, key, argc - 2, argv + 2);
1865 if (request == REQ_NONE) {
1866 config_msg = "Unknown request name";
1867 return ERR;
1870 add_keybinding(keymap, request, key);
1872 return OK;
1875 static int
1876 set_option(const char *opt, char *value)
1878 const char *argv[SIZEOF_ARG];
1879 int argc = 0;
1881 if (!argv_from_string(argv, &argc, value)) {
1882 config_msg = "Too many option arguments";
1883 return ERR;
1886 if (!strcmp(opt, "color"))
1887 return option_color_command(argc, argv);
1889 if (!strcmp(opt, "set"))
1890 return option_set_command(argc, argv);
1892 if (!strcmp(opt, "bind"))
1893 return option_bind_command(argc, argv);
1895 config_msg = "Unknown option command";
1896 return ERR;
1899 static int
1900 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1902 int status = OK;
1904 config_lineno++;
1905 config_msg = "Internal error";
1907 /* Check for comment markers, since read_properties() will
1908 * only ensure opt and value are split at first " \t". */
1909 optlen = strcspn(opt, "#");
1910 if (optlen == 0)
1911 return OK;
1913 if (opt[optlen] != 0) {
1914 config_msg = "No option value";
1915 status = ERR;
1917 } else {
1918 /* Look for comment endings in the value. */
1919 size_t len = strcspn(value, "#");
1921 if (len < valuelen) {
1922 valuelen = len;
1923 value[valuelen] = 0;
1926 status = set_option(opt, value);
1929 if (status == ERR) {
1930 warn("Error on line %d, near '%.*s': %s",
1931 config_lineno, (int) optlen, opt, config_msg);
1932 config_errors = TRUE;
1935 /* Always keep going if errors are encountered. */
1936 return OK;
1939 static void
1940 load_option_file(const char *path)
1942 struct io io = {};
1944 /* It's OK that the file doesn't exist. */
1945 if (!io_open(&io, "%s", path))
1946 return;
1948 config_lineno = 0;
1949 config_errors = FALSE;
1951 if (io_load(&io, " \t", read_option) == ERR ||
1952 config_errors == TRUE)
1953 warn("Errors while loading %s.", path);
1956 static int
1957 load_options(void)
1959 const char *home = getenv("HOME");
1960 const char *tigrc_user = getenv("TIGRC_USER");
1961 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1962 char buf[SIZEOF_STR];
1964 add_builtin_run_requests();
1966 if (!tigrc_system)
1967 tigrc_system = SYSCONFDIR "/tigrc";
1968 load_option_file(tigrc_system);
1970 if (!tigrc_user) {
1971 if (!home || !string_format(buf, "%s/.tigrc", home))
1972 return ERR;
1973 tigrc_user = buf;
1975 load_option_file(tigrc_user);
1977 return OK;
1982 * The viewer
1985 struct view;
1986 struct view_ops;
1988 /* The display array of active views and the index of the current view. */
1989 static struct view *display[2];
1990 static unsigned int current_view;
1992 #define foreach_displayed_view(view, i) \
1993 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1995 #define displayed_views() (display[1] != NULL ? 2 : 1)
1997 /* Current head and commit ID */
1998 static char ref_blob[SIZEOF_REF] = "";
1999 static char ref_commit[SIZEOF_REF] = "HEAD";
2000 static char ref_head[SIZEOF_REF] = "HEAD";
2002 struct view {
2003 const char *name; /* View name */
2004 const char *cmd_env; /* Command line set via environment */
2005 const char *id; /* Points to either of ref_{head,commit,blob} */
2007 struct view_ops *ops; /* View operations */
2009 enum keymap keymap; /* What keymap does this view have */
2010 bool git_dir; /* Whether the view requires a git directory. */
2012 char ref[SIZEOF_REF]; /* Hovered commit reference */
2013 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2015 int height, width; /* The width and height of the main window */
2016 WINDOW *win; /* The main window */
2017 WINDOW *title; /* The title window living below the main window */
2019 /* Navigation */
2020 unsigned long offset; /* Offset of the window top */
2021 unsigned long yoffset; /* Offset from the window side. */
2022 unsigned long lineno; /* Current line number */
2023 unsigned long p_offset; /* Previous offset of the window top */
2024 unsigned long p_yoffset;/* Previous offset from the window side */
2025 unsigned long p_lineno; /* Previous current line number */
2026 bool p_restore; /* Should the previous position be restored. */
2028 /* Searching */
2029 char grep[SIZEOF_STR]; /* Search string */
2030 regex_t *regex; /* Pre-compiled regexp */
2032 /* If non-NULL, points to the view that opened this view. If this view
2033 * is closed tig will switch back to the parent view. */
2034 struct view *parent;
2036 /* Buffering */
2037 size_t lines; /* Total number of lines */
2038 struct line *line; /* Line index */
2039 unsigned int digits; /* Number of digits in the lines member. */
2041 /* Drawing */
2042 struct line *curline; /* Line currently being drawn. */
2043 enum line_type curtype; /* Attribute currently used for drawing. */
2044 unsigned long col; /* Column when drawing. */
2045 bool has_scrolled; /* View was scrolled. */
2047 /* Loading */
2048 struct io io;
2049 struct io *pipe;
2050 time_t start_time;
2051 time_t update_secs;
2054 struct view_ops {
2055 /* What type of content being displayed. Used in the title bar. */
2056 const char *type;
2057 /* Default command arguments. */
2058 const char **argv;
2059 /* Open and reads in all view content. */
2060 bool (*open)(struct view *view);
2061 /* Read one line; updates view->line. */
2062 bool (*read)(struct view *view, char *data);
2063 /* Draw one line; @lineno must be < view->height. */
2064 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2065 /* Depending on view handle a special requests. */
2066 enum request (*request)(struct view *view, enum request request, struct line *line);
2067 /* Search for regexp in a line. */
2068 bool (*grep)(struct view *view, struct line *line);
2069 /* Select line */
2070 void (*select)(struct view *view, struct line *line);
2071 /* Prepare view for loading */
2072 bool (*prepare)(struct view *view);
2075 static struct view_ops blame_ops;
2076 static struct view_ops blob_ops;
2077 static struct view_ops diff_ops;
2078 static struct view_ops help_ops;
2079 static struct view_ops log_ops;
2080 static struct view_ops main_ops;
2081 static struct view_ops pager_ops;
2082 static struct view_ops stage_ops;
2083 static struct view_ops status_ops;
2084 static struct view_ops tree_ops;
2085 static struct view_ops branch_ops;
2087 #define VIEW_STR(name, env, ref, ops, map, git) \
2088 { name, #env, ref, ops, map, git }
2090 #define VIEW_(id, name, ops, git, ref) \
2091 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2094 static struct view views[] = {
2095 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2096 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2097 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2098 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2099 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2100 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2101 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2102 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2103 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2104 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2105 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2108 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2109 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2111 #define foreach_view(view, i) \
2112 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2114 #define view_is_displayed(view) \
2115 (view == display[0] || view == display[1])
2118 static inline void
2119 set_view_attr(struct view *view, enum line_type type)
2121 if (!view->curline->selected && view->curtype != type) {
2122 (void) wattrset(view->win, get_line_attr(type));
2123 wchgat(view->win, -1, 0, type, NULL);
2124 view->curtype = type;
2128 static int
2129 draw_chars(struct view *view, enum line_type type, const char *string,
2130 int max_len, bool use_tilde)
2132 static char out_buffer[BUFSIZ * 2];
2133 int len = 0;
2134 int col = 0;
2135 int trimmed = FALSE;
2136 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2138 if (max_len <= 0)
2139 return 0;
2141 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2143 set_view_attr(view, type);
2144 if (len > 0) {
2145 if (opt_iconv_out != ICONV_NONE) {
2146 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2147 size_t inlen = len + 1;
2149 char *outbuf = out_buffer;
2150 size_t outlen = sizeof(out_buffer);
2152 size_t ret;
2154 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2155 if (ret != (size_t) -1) {
2156 string = out_buffer;
2157 len = sizeof(out_buffer) - outlen;
2161 waddnstr(view->win, string, len);
2163 if (trimmed && use_tilde) {
2164 set_view_attr(view, LINE_DELIMITER);
2165 waddch(view->win, '~');
2166 col++;
2169 return col;
2172 static int
2173 draw_space(struct view *view, enum line_type type, int max, int spaces)
2175 static char space[] = " ";
2176 int col = 0;
2178 spaces = MIN(max, spaces);
2180 while (spaces > 0) {
2181 int len = MIN(spaces, sizeof(space) - 1);
2183 col += draw_chars(view, type, space, len, FALSE);
2184 spaces -= len;
2187 return col;
2190 static bool
2191 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2193 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2194 return view->width + view->yoffset <= view->col;
2197 static bool
2198 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2200 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2201 int max = view->width + view->yoffset - view->col;
2202 int i;
2204 if (max < size)
2205 size = max;
2207 set_view_attr(view, type);
2208 /* Using waddch() instead of waddnstr() ensures that
2209 * they'll be rendered correctly for the cursor line. */
2210 for (i = skip; i < size; i++)
2211 waddch(view->win, graphic[i]);
2213 view->col += size;
2214 if (size < max && skip <= size)
2215 waddch(view->win, ' ');
2216 view->col++;
2218 return view->width + view->yoffset <= view->col;
2221 static bool
2222 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2224 int max = MIN(view->width + view->yoffset - view->col, len);
2225 int col;
2227 if (text)
2228 col = draw_chars(view, type, text, max - 1, trim);
2229 else
2230 col = draw_space(view, type, max - 1, max - 1);
2232 view->col += col;
2233 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2234 return view->width + view->yoffset <= view->col;
2237 static bool
2238 draw_date(struct view *view, struct time *time)
2240 const char *date = mkdate(time, opt_date);
2241 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2243 return draw_field(view, LINE_DATE, date, cols, FALSE);
2246 static bool
2247 draw_author(struct view *view, const char *author)
2249 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2250 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2252 if (abbreviate && author)
2253 author = get_author_initials(author);
2255 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2258 static bool
2259 draw_mode(struct view *view, mode_t mode)
2261 const char *str;
2263 if (S_ISDIR(mode))
2264 str = "drwxr-xr-x";
2265 else if (S_ISLNK(mode))
2266 str = "lrwxrwxrwx";
2267 else if (S_ISGITLINK(mode))
2268 str = "m---------";
2269 else if (S_ISREG(mode) && mode & S_IXUSR)
2270 str = "-rwxr-xr-x";
2271 else if (S_ISREG(mode))
2272 str = "-rw-r--r--";
2273 else
2274 str = "----------";
2276 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2279 static bool
2280 draw_lineno(struct view *view, unsigned int lineno)
2282 char number[10];
2283 int digits3 = view->digits < 3 ? 3 : view->digits;
2284 int max = MIN(view->width + view->yoffset - view->col, digits3);
2285 char *text = NULL;
2286 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2288 lineno += view->offset + 1;
2289 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2290 static char fmt[] = "%1ld";
2292 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2293 if (string_format(number, fmt, lineno))
2294 text = number;
2296 if (text)
2297 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2298 else
2299 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2300 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2303 static bool
2304 draw_view_line(struct view *view, unsigned int lineno)
2306 struct line *line;
2307 bool selected = (view->offset + lineno == view->lineno);
2309 assert(view_is_displayed(view));
2311 if (view->offset + lineno >= view->lines)
2312 return FALSE;
2314 line = &view->line[view->offset + lineno];
2316 wmove(view->win, lineno, 0);
2317 if (line->cleareol)
2318 wclrtoeol(view->win);
2319 view->col = 0;
2320 view->curline = line;
2321 view->curtype = LINE_NONE;
2322 line->selected = FALSE;
2323 line->dirty = line->cleareol = 0;
2325 if (selected) {
2326 set_view_attr(view, LINE_CURSOR);
2327 line->selected = TRUE;
2328 view->ops->select(view, line);
2331 return view->ops->draw(view, line, lineno);
2334 static void
2335 redraw_view_dirty(struct view *view)
2337 bool dirty = FALSE;
2338 int lineno;
2340 for (lineno = 0; lineno < view->height; lineno++) {
2341 if (view->offset + lineno >= view->lines)
2342 break;
2343 if (!view->line[view->offset + lineno].dirty)
2344 continue;
2345 dirty = TRUE;
2346 if (!draw_view_line(view, lineno))
2347 break;
2350 if (!dirty)
2351 return;
2352 wnoutrefresh(view->win);
2355 static void
2356 redraw_view_from(struct view *view, int lineno)
2358 assert(0 <= lineno && lineno < view->height);
2360 for (; lineno < view->height; lineno++) {
2361 if (!draw_view_line(view, lineno))
2362 break;
2365 wnoutrefresh(view->win);
2368 static void
2369 redraw_view(struct view *view)
2371 werase(view->win);
2372 redraw_view_from(view, 0);
2376 static void
2377 update_view_title(struct view *view)
2379 char buf[SIZEOF_STR];
2380 char state[SIZEOF_STR];
2381 size_t bufpos = 0, statelen = 0;
2383 assert(view_is_displayed(view));
2385 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2386 unsigned int view_lines = view->offset + view->height;
2387 unsigned int lines = view->lines
2388 ? MIN(view_lines, view->lines) * 100 / view->lines
2389 : 0;
2391 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2392 view->ops->type,
2393 view->lineno + 1,
2394 view->lines,
2395 lines);
2399 if (view->pipe) {
2400 time_t secs = time(NULL) - view->start_time;
2402 /* Three git seconds are a long time ... */
2403 if (secs > 2)
2404 string_format_from(state, &statelen, " loading %lds", secs);
2407 string_format_from(buf, &bufpos, "[%s]", view->name);
2408 if (*view->ref && bufpos < view->width) {
2409 size_t refsize = strlen(view->ref);
2410 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2412 if (minsize < view->width)
2413 refsize = view->width - minsize + 7;
2414 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2417 if (statelen && bufpos < view->width) {
2418 string_format_from(buf, &bufpos, "%s", state);
2421 if (view == display[current_view])
2422 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2423 else
2424 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2426 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2427 wclrtoeol(view->title);
2428 wnoutrefresh(view->title);
2431 static int
2432 apply_step(double step, int value)
2434 if (step >= 1)
2435 return (int) step;
2436 value *= step + 0.01;
2437 return value ? value : 1;
2440 static void
2441 resize_display(void)
2443 int offset, i;
2444 struct view *base = display[0];
2445 struct view *view = display[1] ? display[1] : display[0];
2447 /* Setup window dimensions */
2449 getmaxyx(stdscr, base->height, base->width);
2451 /* Make room for the status window. */
2452 base->height -= 1;
2454 if (view != base) {
2455 /* Horizontal split. */
2456 view->width = base->width;
2457 view->height = apply_step(opt_scale_split_view, base->height);
2458 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2459 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2460 base->height -= view->height;
2462 /* Make room for the title bar. */
2463 view->height -= 1;
2466 /* Make room for the title bar. */
2467 base->height -= 1;
2469 offset = 0;
2471 foreach_displayed_view (view, i) {
2472 if (!view->win) {
2473 view->win = newwin(view->height, 0, offset, 0);
2474 if (!view->win)
2475 die("Failed to create %s view", view->name);
2477 scrollok(view->win, FALSE);
2479 view->title = newwin(1, 0, offset + view->height, 0);
2480 if (!view->title)
2481 die("Failed to create title window");
2483 } else {
2484 wresize(view->win, view->height, view->width);
2485 mvwin(view->win, offset, 0);
2486 mvwin(view->title, offset + view->height, 0);
2489 offset += view->height + 1;
2493 static void
2494 redraw_display(bool clear)
2496 struct view *view;
2497 int i;
2499 foreach_displayed_view (view, i) {
2500 if (clear)
2501 wclear(view->win);
2502 redraw_view(view);
2503 update_view_title(view);
2507 static void
2508 toggle_enum_option_do(unsigned int *opt, const char *help,
2509 const struct enum_map *map, size_t size)
2511 *opt = (*opt + 1) % size;
2512 redraw_display(FALSE);
2513 report("Displaying %s %s", enum_name(map[*opt]), help);
2516 #define toggle_enum_option(opt, help, map) \
2517 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2519 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2520 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2522 static void
2523 toggle_view_option(bool *option, const char *help)
2525 *option = !*option;
2526 redraw_display(FALSE);
2527 report("%sabling %s", *option ? "En" : "Dis", help);
2530 static void
2531 open_option_menu(void)
2533 const struct menu_item menu[] = {
2534 { '.', "line numbers", &opt_line_number },
2535 { 'D', "date display", &opt_date },
2536 { 'A', "author display", &opt_author },
2537 { 'g', "revision graph display", &opt_rev_graph },
2538 { 'F', "reference display", &opt_show_refs },
2539 { 0 }
2541 int selected = 0;
2543 if (prompt_menu("Toggle option", menu, &selected)) {
2544 if (menu[selected].data == &opt_date)
2545 toggle_date();
2546 else if (menu[selected].data == &opt_author)
2547 toggle_author();
2548 else
2549 toggle_view_option(menu[selected].data, menu[selected].text);
2553 static void
2554 maximize_view(struct view *view)
2556 memset(display, 0, sizeof(display));
2557 current_view = 0;
2558 display[current_view] = view;
2559 resize_display();
2560 redraw_display(FALSE);
2561 report("");
2566 * Navigation
2569 static bool
2570 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2572 if (lineno >= view->lines)
2573 lineno = view->lines > 0 ? view->lines - 1 : 0;
2575 if (offset > lineno || offset + view->height <= lineno) {
2576 unsigned long half = view->height / 2;
2578 if (lineno > half)
2579 offset = lineno - half;
2580 else
2581 offset = 0;
2584 if (offset != view->offset || lineno != view->lineno) {
2585 view->offset = offset;
2586 view->lineno = lineno;
2587 return TRUE;
2590 return FALSE;
2593 /* Scrolling backend */
2594 static void
2595 do_scroll_view(struct view *view, int lines)
2597 bool redraw_current_line = FALSE;
2599 /* The rendering expects the new offset. */
2600 view->offset += lines;
2602 assert(0 <= view->offset && view->offset < view->lines);
2603 assert(lines);
2605 /* Move current line into the view. */
2606 if (view->lineno < view->offset) {
2607 view->lineno = view->offset;
2608 redraw_current_line = TRUE;
2609 } else if (view->lineno >= view->offset + view->height) {
2610 view->lineno = view->offset + view->height - 1;
2611 redraw_current_line = TRUE;
2614 assert(view->offset <= view->lineno && view->lineno < view->lines);
2616 /* Redraw the whole screen if scrolling is pointless. */
2617 if (view->height < ABS(lines)) {
2618 redraw_view(view);
2620 } else {
2621 int line = lines > 0 ? view->height - lines : 0;
2622 int end = line + ABS(lines);
2624 scrollok(view->win, TRUE);
2625 wscrl(view->win, lines);
2626 scrollok(view->win, FALSE);
2628 while (line < end && draw_view_line(view, line))
2629 line++;
2631 if (redraw_current_line)
2632 draw_view_line(view, view->lineno - view->offset);
2633 wnoutrefresh(view->win);
2636 view->has_scrolled = TRUE;
2637 report("");
2640 /* Scroll frontend */
2641 static void
2642 scroll_view(struct view *view, enum request request)
2644 int lines = 1;
2646 assert(view_is_displayed(view));
2648 switch (request) {
2649 case REQ_SCROLL_LEFT:
2650 if (view->yoffset == 0) {
2651 report("Cannot scroll beyond the first column");
2652 return;
2654 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2655 view->yoffset = 0;
2656 else
2657 view->yoffset -= apply_step(opt_hscroll, view->width);
2658 redraw_view_from(view, 0);
2659 report("");
2660 return;
2661 case REQ_SCROLL_RIGHT:
2662 view->yoffset += apply_step(opt_hscroll, view->width);
2663 redraw_view(view);
2664 report("");
2665 return;
2666 case REQ_SCROLL_PAGE_DOWN:
2667 lines = view->height;
2668 case REQ_SCROLL_LINE_DOWN:
2669 if (view->offset + lines > view->lines)
2670 lines = view->lines - view->offset;
2672 if (lines == 0 || view->offset + view->height >= view->lines) {
2673 report("Cannot scroll beyond the last line");
2674 return;
2676 break;
2678 case REQ_SCROLL_PAGE_UP:
2679 lines = view->height;
2680 case REQ_SCROLL_LINE_UP:
2681 if (lines > view->offset)
2682 lines = view->offset;
2684 if (lines == 0) {
2685 report("Cannot scroll beyond the first line");
2686 return;
2689 lines = -lines;
2690 break;
2692 default:
2693 die("request %d not handled in switch", request);
2696 do_scroll_view(view, lines);
2699 /* Cursor moving */
2700 static void
2701 move_view(struct view *view, enum request request)
2703 int scroll_steps = 0;
2704 int steps;
2706 switch (request) {
2707 case REQ_MOVE_FIRST_LINE:
2708 steps = -view->lineno;
2709 break;
2711 case REQ_MOVE_LAST_LINE:
2712 steps = view->lines - view->lineno - 1;
2713 break;
2715 case REQ_MOVE_PAGE_UP:
2716 steps = view->height > view->lineno
2717 ? -view->lineno : -view->height;
2718 break;
2720 case REQ_MOVE_PAGE_DOWN:
2721 steps = view->lineno + view->height >= view->lines
2722 ? view->lines - view->lineno - 1 : view->height;
2723 break;
2725 case REQ_MOVE_UP:
2726 steps = -1;
2727 break;
2729 case REQ_MOVE_DOWN:
2730 steps = 1;
2731 break;
2733 default:
2734 die("request %d not handled in switch", request);
2737 if (steps <= 0 && view->lineno == 0) {
2738 report("Cannot move beyond the first line");
2739 return;
2741 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2742 report("Cannot move beyond the last line");
2743 return;
2746 /* Move the current line */
2747 view->lineno += steps;
2748 assert(0 <= view->lineno && view->lineno < view->lines);
2750 /* Check whether the view needs to be scrolled */
2751 if (view->lineno < view->offset ||
2752 view->lineno >= view->offset + view->height) {
2753 scroll_steps = steps;
2754 if (steps < 0 && -steps > view->offset) {
2755 scroll_steps = -view->offset;
2757 } else if (steps > 0) {
2758 if (view->lineno == view->lines - 1 &&
2759 view->lines > view->height) {
2760 scroll_steps = view->lines - view->offset - 1;
2761 if (scroll_steps >= view->height)
2762 scroll_steps -= view->height - 1;
2767 if (!view_is_displayed(view)) {
2768 view->offset += scroll_steps;
2769 assert(0 <= view->offset && view->offset < view->lines);
2770 view->ops->select(view, &view->line[view->lineno]);
2771 return;
2774 /* Repaint the old "current" line if we be scrolling */
2775 if (ABS(steps) < view->height)
2776 draw_view_line(view, view->lineno - steps - view->offset);
2778 if (scroll_steps) {
2779 do_scroll_view(view, scroll_steps);
2780 return;
2783 /* Draw the current line */
2784 draw_view_line(view, view->lineno - view->offset);
2786 wnoutrefresh(view->win);
2787 report("");
2792 * Searching
2795 static void search_view(struct view *view, enum request request);
2797 static bool
2798 grep_text(struct view *view, const char *text[])
2800 regmatch_t pmatch;
2801 size_t i;
2803 for (i = 0; text[i]; i++)
2804 if (*text[i] &&
2805 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2806 return TRUE;
2807 return FALSE;
2810 static void
2811 select_view_line(struct view *view, unsigned long lineno)
2813 unsigned long old_lineno = view->lineno;
2814 unsigned long old_offset = view->offset;
2816 if (goto_view_line(view, view->offset, lineno)) {
2817 if (view_is_displayed(view)) {
2818 if (old_offset != view->offset) {
2819 redraw_view(view);
2820 } else {
2821 draw_view_line(view, old_lineno - view->offset);
2822 draw_view_line(view, view->lineno - view->offset);
2823 wnoutrefresh(view->win);
2825 } else {
2826 view->ops->select(view, &view->line[view->lineno]);
2831 static void
2832 find_next(struct view *view, enum request request)
2834 unsigned long lineno = view->lineno;
2835 int direction;
2837 if (!*view->grep) {
2838 if (!*opt_search)
2839 report("No previous search");
2840 else
2841 search_view(view, request);
2842 return;
2845 switch (request) {
2846 case REQ_SEARCH:
2847 case REQ_FIND_NEXT:
2848 direction = 1;
2849 break;
2851 case REQ_SEARCH_BACK:
2852 case REQ_FIND_PREV:
2853 direction = -1;
2854 break;
2856 default:
2857 return;
2860 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2861 lineno += direction;
2863 /* Note, lineno is unsigned long so will wrap around in which case it
2864 * will become bigger than view->lines. */
2865 for (; lineno < view->lines; lineno += direction) {
2866 if (view->ops->grep(view, &view->line[lineno])) {
2867 select_view_line(view, lineno);
2868 report("Line %ld matches '%s'", lineno + 1, view->grep);
2869 return;
2873 report("No match found for '%s'", view->grep);
2876 static void
2877 search_view(struct view *view, enum request request)
2879 int regex_err;
2881 if (view->regex) {
2882 regfree(view->regex);
2883 *view->grep = 0;
2884 } else {
2885 view->regex = calloc(1, sizeof(*view->regex));
2886 if (!view->regex)
2887 return;
2890 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2891 if (regex_err != 0) {
2892 char buf[SIZEOF_STR] = "unknown error";
2894 regerror(regex_err, view->regex, buf, sizeof(buf));
2895 report("Search failed: %s", buf);
2896 return;
2899 string_copy(view->grep, opt_search);
2901 find_next(view, request);
2905 * Incremental updating
2908 static void
2909 reset_view(struct view *view)
2911 int i;
2913 for (i = 0; i < view->lines; i++)
2914 free(view->line[i].data);
2915 free(view->line);
2917 view->p_offset = view->offset;
2918 view->p_yoffset = view->yoffset;
2919 view->p_lineno = view->lineno;
2921 view->line = NULL;
2922 view->offset = 0;
2923 view->yoffset = 0;
2924 view->lines = 0;
2925 view->lineno = 0;
2926 view->vid[0] = 0;
2927 view->update_secs = 0;
2930 static void
2931 free_argv(const char *argv[])
2933 int argc;
2935 for (argc = 0; argv[argc]; argc++)
2936 free((void *) argv[argc]);
2939 static const char *
2940 format_arg(const char *name)
2942 static struct {
2943 const char *name;
2944 size_t namelen;
2945 const char *value;
2946 const char *value_if_empty;
2947 } vars[] = {
2948 #define FORMAT_VAR(name, value, value_if_empty) \
2949 { name, STRING_SIZE(name), value, value_if_empty }
2950 FORMAT_VAR("%(directory)", opt_path, ""),
2951 FORMAT_VAR("%(file)", opt_file, ""),
2952 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2953 FORMAT_VAR("%(head)", ref_head, ""),
2954 FORMAT_VAR("%(commit)", ref_commit, ""),
2955 FORMAT_VAR("%(blob)", ref_blob, ""),
2957 int i;
2959 for (i = 0; i < ARRAY_SIZE(vars); i++)
2960 if (!strncmp(name, vars[i].name, vars[i].namelen))
2961 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2963 return NULL;
2965 static bool
2966 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2968 char buf[SIZEOF_STR];
2969 int argc;
2970 bool noreplace = flags == FORMAT_NONE;
2972 free_argv(dst_argv);
2974 for (argc = 0; src_argv[argc]; argc++) {
2975 const char *arg = src_argv[argc];
2976 size_t bufpos = 0;
2978 while (arg) {
2979 char *next = strstr(arg, "%(");
2980 int len = next - arg;
2981 const char *value;
2983 if (!next || noreplace) {
2984 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2985 noreplace = TRUE;
2986 len = strlen(arg);
2987 value = "";
2989 } else {
2990 value = format_arg(next);
2992 if (!value) {
2993 report("Unknown replacement: `%s`", next);
2994 return FALSE;
2998 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2999 return FALSE;
3001 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3004 dst_argv[argc] = strdup(buf);
3005 if (!dst_argv[argc])
3006 break;
3009 dst_argv[argc] = NULL;
3011 return src_argv[argc] == NULL;
3014 static bool
3015 restore_view_position(struct view *view)
3017 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3018 return FALSE;
3020 /* Changing the view position cancels the restoring. */
3021 /* FIXME: Changing back to the first line is not detected. */
3022 if (view->offset != 0 || view->lineno != 0) {
3023 view->p_restore = FALSE;
3024 return FALSE;
3027 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3028 view_is_displayed(view))
3029 werase(view->win);
3031 view->yoffset = view->p_yoffset;
3032 view->p_restore = FALSE;
3034 return TRUE;
3037 static void
3038 end_update(struct view *view, bool force)
3040 if (!view->pipe)
3041 return;
3042 while (!view->ops->read(view, NULL))
3043 if (!force)
3044 return;
3045 if (force)
3046 kill_io(view->pipe);
3047 done_io(view->pipe);
3048 view->pipe = NULL;
3051 static void
3052 setup_update(struct view *view, const char *vid)
3054 reset_view(view);
3055 string_copy_rev(view->vid, vid);
3056 view->pipe = &view->io;
3057 view->start_time = time(NULL);
3060 static bool
3061 prepare_update(struct view *view, const char *argv[], const char *dir,
3062 enum format_flags flags)
3064 if (view->pipe)
3065 end_update(view, TRUE);
3066 return init_io_rd(&view->io, argv, dir, flags);
3069 static bool
3070 prepare_update_file(struct view *view, const char *name)
3072 if (view->pipe)
3073 end_update(view, TRUE);
3074 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3077 static bool
3078 begin_update(struct view *view, bool refresh)
3080 if (view->pipe)
3081 end_update(view, TRUE);
3083 if (!refresh) {
3084 if (view->ops->prepare) {
3085 if (!view->ops->prepare(view))
3086 return FALSE;
3087 } else if (!init_io_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3088 return FALSE;
3091 /* Put the current ref_* value to the view title ref
3092 * member. This is needed by the blob view. Most other
3093 * views sets it automatically after loading because the
3094 * first line is a commit line. */
3095 string_copy_rev(view->ref, view->id);
3098 if (!start_io(&view->io))
3099 return FALSE;
3101 setup_update(view, view->id);
3103 return TRUE;
3106 static bool
3107 update_view(struct view *view)
3109 char out_buffer[BUFSIZ * 2];
3110 char *line;
3111 /* Clear the view and redraw everything since the tree sorting
3112 * might have rearranged things. */
3113 bool redraw = view->lines == 0;
3114 bool can_read = TRUE;
3116 if (!view->pipe)
3117 return TRUE;
3119 if (!io_can_read(view->pipe)) {
3120 if (view->lines == 0 && view_is_displayed(view)) {
3121 time_t secs = time(NULL) - view->start_time;
3123 if (secs > 1 && secs > view->update_secs) {
3124 if (view->update_secs == 0)
3125 redraw_view(view);
3126 update_view_title(view);
3127 view->update_secs = secs;
3130 return TRUE;
3133 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3134 if (opt_iconv_in != ICONV_NONE) {
3135 ICONV_CONST char *inbuf = line;
3136 size_t inlen = strlen(line) + 1;
3138 char *outbuf = out_buffer;
3139 size_t outlen = sizeof(out_buffer);
3141 size_t ret;
3143 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3144 if (ret != (size_t) -1)
3145 line = out_buffer;
3148 if (!view->ops->read(view, line)) {
3149 report("Allocation failure");
3150 end_update(view, TRUE);
3151 return FALSE;
3156 unsigned long lines = view->lines;
3157 int digits;
3159 for (digits = 0; lines; digits++)
3160 lines /= 10;
3162 /* Keep the displayed view in sync with line number scaling. */
3163 if (digits != view->digits) {
3164 view->digits = digits;
3165 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3166 redraw = TRUE;
3170 if (io_error(view->pipe)) {
3171 report("Failed to read: %s", io_strerror(view->pipe));
3172 end_update(view, TRUE);
3174 } else if (io_eof(view->pipe)) {
3175 report("");
3176 end_update(view, FALSE);
3179 if (restore_view_position(view))
3180 redraw = TRUE;
3182 if (!view_is_displayed(view))
3183 return TRUE;
3185 if (redraw)
3186 redraw_view_from(view, 0);
3187 else
3188 redraw_view_dirty(view);
3190 /* Update the title _after_ the redraw so that if the redraw picks up a
3191 * commit reference in view->ref it'll be available here. */
3192 update_view_title(view);
3193 return TRUE;
3196 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3198 static struct line *
3199 add_line_data(struct view *view, void *data, enum line_type type)
3201 struct line *line;
3203 if (!realloc_lines(&view->line, view->lines, 1))
3204 return NULL;
3206 line = &view->line[view->lines++];
3207 memset(line, 0, sizeof(*line));
3208 line->type = type;
3209 line->data = data;
3210 line->dirty = 1;
3212 return line;
3215 static struct line *
3216 add_line_text(struct view *view, const char *text, enum line_type type)
3218 char *data = text ? strdup(text) : NULL;
3220 return data ? add_line_data(view, data, type) : NULL;
3223 static struct line *
3224 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3226 char buf[SIZEOF_STR];
3227 va_list args;
3229 va_start(args, fmt);
3230 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3231 buf[0] = 0;
3232 va_end(args);
3234 return buf[0] ? add_line_text(view, buf, type) : NULL;
3238 * View opening
3241 enum open_flags {
3242 OPEN_DEFAULT = 0, /* Use default view switching. */
3243 OPEN_SPLIT = 1, /* Split current view. */
3244 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3245 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3246 OPEN_PREPARED = 32, /* Open already prepared command. */
3249 static void
3250 open_view(struct view *prev, enum request request, enum open_flags flags)
3252 bool split = !!(flags & OPEN_SPLIT);
3253 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3254 bool nomaximize = !!(flags & OPEN_REFRESH);
3255 struct view *view = VIEW(request);
3256 int nviews = displayed_views();
3257 struct view *base_view = display[0];
3259 if (view == prev && nviews == 1 && !reload) {
3260 report("Already in %s view", view->name);
3261 return;
3264 if (view->git_dir && !opt_git_dir[0]) {
3265 report("The %s view is disabled in pager view", view->name);
3266 return;
3269 if (split) {
3270 display[1] = view;
3271 current_view = 1;
3272 } else if (!nomaximize) {
3273 /* Maximize the current view. */
3274 memset(display, 0, sizeof(display));
3275 current_view = 0;
3276 display[current_view] = view;
3279 /* No parent signals that this is the first loaded view. */
3280 if (prev && view != prev) {
3281 view->parent = prev;
3284 /* Resize the view when switching between split- and full-screen,
3285 * or when switching between two different full-screen views. */
3286 if (nviews != displayed_views() ||
3287 (nviews == 1 && base_view != display[0]))
3288 resize_display();
3290 if (view->ops->open) {
3291 if (view->pipe)
3292 end_update(view, TRUE);
3293 if (!view->ops->open(view)) {
3294 report("Failed to load %s view", view->name);
3295 return;
3297 restore_view_position(view);
3299 } else if ((reload || strcmp(view->vid, view->id)) &&
3300 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3301 report("Failed to load %s view", view->name);
3302 return;
3305 if (split && prev->lineno - prev->offset >= prev->height) {
3306 /* Take the title line into account. */
3307 int lines = prev->lineno - prev->offset - prev->height + 1;
3309 /* Scroll the view that was split if the current line is
3310 * outside the new limited view. */
3311 do_scroll_view(prev, lines);
3314 if (prev && view != prev && split && view_is_displayed(prev)) {
3315 /* "Blur" the previous view. */
3316 update_view_title(prev);
3319 if (view->pipe && view->lines == 0) {
3320 /* Clear the old view and let the incremental updating refill
3321 * the screen. */
3322 werase(view->win);
3323 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3324 report("");
3325 } else if (view_is_displayed(view)) {
3326 redraw_view(view);
3327 report("");
3331 static void
3332 open_external_viewer(const char *argv[], const char *dir)
3334 def_prog_mode(); /* save current tty modes */
3335 endwin(); /* restore original tty modes */
3336 run_io_fg(argv, dir);
3337 fprintf(stderr, "Press Enter to continue");
3338 getc(opt_tty);
3339 reset_prog_mode();
3340 redraw_display(TRUE);
3343 static void
3344 open_mergetool(const char *file)
3346 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3348 open_external_viewer(mergetool_argv, opt_cdup);
3351 static void
3352 open_editor(const char *file)
3354 const char *editor_argv[] = { "vi", file, NULL };
3355 const char *editor;
3357 editor = getenv("GIT_EDITOR");
3358 if (!editor && *opt_editor)
3359 editor = opt_editor;
3360 if (!editor)
3361 editor = getenv("VISUAL");
3362 if (!editor)
3363 editor = getenv("EDITOR");
3364 if (!editor)
3365 editor = "vi";
3367 editor_argv[0] = editor;
3368 open_external_viewer(editor_argv, opt_cdup);
3371 static void
3372 open_run_request(enum request request)
3374 struct run_request *req = get_run_request(request);
3375 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3377 if (!req) {
3378 report("Unknown run request");
3379 return;
3382 if (format_argv(argv, req->argv, FORMAT_ALL))
3383 open_external_viewer(argv, NULL);
3384 free_argv(argv);
3388 * User request switch noodle
3391 static int
3392 view_driver(struct view *view, enum request request)
3394 int i;
3396 if (request == REQ_NONE)
3397 return TRUE;
3399 if (request > REQ_NONE) {
3400 open_run_request(request);
3401 /* FIXME: When all views can refresh always do this. */
3402 if (view == VIEW(REQ_VIEW_STATUS) ||
3403 view == VIEW(REQ_VIEW_MAIN) ||
3404 view == VIEW(REQ_VIEW_LOG) ||
3405 view == VIEW(REQ_VIEW_BRANCH) ||
3406 view == VIEW(REQ_VIEW_STAGE))
3407 request = REQ_REFRESH;
3408 else
3409 return TRUE;
3412 if (view && view->lines) {
3413 request = view->ops->request(view, request, &view->line[view->lineno]);
3414 if (request == REQ_NONE)
3415 return TRUE;
3418 switch (request) {
3419 case REQ_MOVE_UP:
3420 case REQ_MOVE_DOWN:
3421 case REQ_MOVE_PAGE_UP:
3422 case REQ_MOVE_PAGE_DOWN:
3423 case REQ_MOVE_FIRST_LINE:
3424 case REQ_MOVE_LAST_LINE:
3425 move_view(view, request);
3426 break;
3428 case REQ_SCROLL_LEFT:
3429 case REQ_SCROLL_RIGHT:
3430 case REQ_SCROLL_LINE_DOWN:
3431 case REQ_SCROLL_LINE_UP:
3432 case REQ_SCROLL_PAGE_DOWN:
3433 case REQ_SCROLL_PAGE_UP:
3434 scroll_view(view, request);
3435 break;
3437 case REQ_VIEW_BLAME:
3438 if (!opt_file[0]) {
3439 report("No file chosen, press %s to open tree view",
3440 get_key(view->keymap, REQ_VIEW_TREE));
3441 break;
3443 open_view(view, request, OPEN_DEFAULT);
3444 break;
3446 case REQ_VIEW_BLOB:
3447 if (!ref_blob[0]) {
3448 report("No file chosen, press %s to open tree view",
3449 get_key(view->keymap, REQ_VIEW_TREE));
3450 break;
3452 open_view(view, request, OPEN_DEFAULT);
3453 break;
3455 case REQ_VIEW_PAGER:
3456 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3457 report("No pager content, press %s to run command from prompt",
3458 get_key(view->keymap, REQ_PROMPT));
3459 break;
3461 open_view(view, request, OPEN_DEFAULT);
3462 break;
3464 case REQ_VIEW_STAGE:
3465 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3466 report("No stage content, press %s to open the status view and choose file",
3467 get_key(view->keymap, REQ_VIEW_STATUS));
3468 break;
3470 open_view(view, request, OPEN_DEFAULT);
3471 break;
3473 case REQ_VIEW_STATUS:
3474 if (opt_is_inside_work_tree == FALSE) {
3475 report("The status view requires a working tree");
3476 break;
3478 open_view(view, request, OPEN_DEFAULT);
3479 break;
3481 case REQ_VIEW_MAIN:
3482 case REQ_VIEW_DIFF:
3483 case REQ_VIEW_LOG:
3484 case REQ_VIEW_TREE:
3485 case REQ_VIEW_HELP:
3486 case REQ_VIEW_BRANCH:
3487 open_view(view, request, OPEN_DEFAULT);
3488 break;
3490 case REQ_NEXT:
3491 case REQ_PREVIOUS:
3492 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3494 if ((view == VIEW(REQ_VIEW_DIFF) &&
3495 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3496 (view == VIEW(REQ_VIEW_DIFF) &&
3497 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3498 (view == VIEW(REQ_VIEW_STAGE) &&
3499 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3500 (view == VIEW(REQ_VIEW_BLOB) &&
3501 view->parent == VIEW(REQ_VIEW_TREE)) ||
3502 (view == VIEW(REQ_VIEW_MAIN) &&
3503 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3504 int line;
3506 view = view->parent;
3507 line = view->lineno;
3508 move_view(view, request);
3509 if (view_is_displayed(view))
3510 update_view_title(view);
3511 if (line != view->lineno)
3512 view->ops->request(view, REQ_ENTER,
3513 &view->line[view->lineno]);
3515 } else {
3516 move_view(view, request);
3518 break;
3520 case REQ_VIEW_NEXT:
3522 int nviews = displayed_views();
3523 int next_view = (current_view + 1) % nviews;
3525 if (next_view == current_view) {
3526 report("Only one view is displayed");
3527 break;
3530 current_view = next_view;
3531 /* Blur out the title of the previous view. */
3532 update_view_title(view);
3533 report("");
3534 break;
3536 case REQ_REFRESH:
3537 report("Refreshing is not yet supported for the %s view", view->name);
3538 break;
3540 case REQ_MAXIMIZE:
3541 if (displayed_views() == 2)
3542 maximize_view(view);
3543 break;
3545 case REQ_OPTIONS:
3546 open_option_menu();
3547 break;
3549 case REQ_TOGGLE_LINENO:
3550 toggle_view_option(&opt_line_number, "line numbers");
3551 break;
3553 case REQ_TOGGLE_DATE:
3554 toggle_date();
3555 break;
3557 case REQ_TOGGLE_AUTHOR:
3558 toggle_author();
3559 break;
3561 case REQ_TOGGLE_REV_GRAPH:
3562 toggle_view_option(&opt_rev_graph, "revision graph display");
3563 break;
3565 case REQ_TOGGLE_REFS:
3566 toggle_view_option(&opt_show_refs, "reference display");
3567 break;
3569 case REQ_TOGGLE_SORT_FIELD:
3570 case REQ_TOGGLE_SORT_ORDER:
3571 report("Sorting is not yet supported for the %s view", view->name);
3572 break;
3574 case REQ_SEARCH:
3575 case REQ_SEARCH_BACK:
3576 search_view(view, request);
3577 break;
3579 case REQ_FIND_NEXT:
3580 case REQ_FIND_PREV:
3581 find_next(view, request);
3582 break;
3584 case REQ_STOP_LOADING:
3585 for (i = 0; i < ARRAY_SIZE(views); i++) {
3586 view = &views[i];
3587 if (view->pipe)
3588 report("Stopped loading the %s view", view->name),
3589 end_update(view, TRUE);
3591 break;
3593 case REQ_SHOW_VERSION:
3594 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3595 return TRUE;
3597 case REQ_SCREEN_REDRAW:
3598 redraw_display(TRUE);
3599 break;
3601 case REQ_EDIT:
3602 report("Nothing to edit");
3603 break;
3605 case REQ_ENTER:
3606 report("Nothing to enter");
3607 break;
3609 case REQ_VIEW_CLOSE:
3610 /* XXX: Mark closed views by letting view->parent point to the
3611 * view itself. Parents to closed view should never be
3612 * followed. */
3613 if (view->parent &&
3614 view->parent->parent != view->parent) {
3615 maximize_view(view->parent);
3616 view->parent = view;
3617 break;
3619 /* Fall-through */
3620 case REQ_QUIT:
3621 return FALSE;
3623 default:
3624 report("Unknown key, press %s for help",
3625 get_key(view->keymap, REQ_VIEW_HELP));
3626 return TRUE;
3629 return TRUE;
3634 * View backend utilities
3637 enum sort_field {
3638 ORDERBY_NAME,
3639 ORDERBY_DATE,
3640 ORDERBY_AUTHOR,
3643 struct sort_state {
3644 const enum sort_field *fields;
3645 size_t size, current;
3646 bool reverse;
3649 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3650 #define get_sort_field(state) ((state).fields[(state).current])
3651 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3653 static void
3654 sort_view(struct view *view, enum request request, struct sort_state *state,
3655 int (*compare)(const void *, const void *))
3657 switch (request) {
3658 case REQ_TOGGLE_SORT_FIELD:
3659 state->current = (state->current + 1) % state->size;
3660 break;
3662 case REQ_TOGGLE_SORT_ORDER:
3663 state->reverse = !state->reverse;
3664 break;
3665 default:
3666 die("Not a sort request");
3669 qsort(view->line, view->lines, sizeof(*view->line), compare);
3670 redraw_view(view);
3673 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3675 /* Small author cache to reduce memory consumption. It uses binary
3676 * search to lookup or find place to position new entries. No entries
3677 * are ever freed. */
3678 static const char *
3679 get_author(const char *name)
3681 static const char **authors;
3682 static size_t authors_size;
3683 int from = 0, to = authors_size - 1;
3685 while (from <= to) {
3686 size_t pos = (to + from) / 2;
3687 int cmp = strcmp(name, authors[pos]);
3689 if (!cmp)
3690 return authors[pos];
3692 if (cmp < 0)
3693 to = pos - 1;
3694 else
3695 from = pos + 1;
3698 if (!realloc_authors(&authors, authors_size, 1))
3699 return NULL;
3700 name = strdup(name);
3701 if (!name)
3702 return NULL;
3704 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3705 authors[from] = name;
3706 authors_size++;
3708 return name;
3711 static void
3712 parse_timesec(struct time *time, const char *sec)
3714 time->sec = (time_t) atol(sec);
3717 static void
3718 parse_timezone(struct time *time, const char *zone)
3720 long tz;
3722 tz = ('0' - zone[1]) * 60 * 60 * 10;
3723 tz += ('0' - zone[2]) * 60 * 60;
3724 tz += ('0' - zone[3]) * 60;
3725 tz += ('0' - zone[4]);
3727 if (zone[0] == '-')
3728 tz = -tz;
3730 time->tz = tz;
3731 time->sec -= tz;
3734 /* Parse author lines where the name may be empty:
3735 * author <email@address.tld> 1138474660 +0100
3737 static void
3738 parse_author_line(char *ident, const char **author, struct time *time)
3740 char *nameend = strchr(ident, '<');
3741 char *emailend = strchr(ident, '>');
3743 if (nameend && emailend)
3744 *nameend = *emailend = 0;
3745 ident = chomp_string(ident);
3746 if (!*ident) {
3747 if (nameend)
3748 ident = chomp_string(nameend + 1);
3749 if (!*ident)
3750 ident = "Unknown";
3753 *author = get_author(ident);
3755 /* Parse epoch and timezone */
3756 if (emailend && emailend[1] == ' ') {
3757 char *secs = emailend + 2;
3758 char *zone = strchr(secs, ' ');
3760 parse_timesec(time, secs);
3762 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3763 parse_timezone(time, zone + 1);
3767 static bool
3768 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3770 char rev[SIZEOF_REV];
3771 const char *revlist_argv[] = {
3772 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3774 struct menu_item *items;
3775 char text[SIZEOF_STR];
3776 bool ok = TRUE;
3777 int i;
3779 items = calloc(*parents + 1, sizeof(*items));
3780 if (!items)
3781 return FALSE;
3783 for (i = 0; i < *parents; i++) {
3784 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3785 if (!run_io_buf(revlist_argv, text, sizeof(text)) ||
3786 !(items[i].text = strdup(text))) {
3787 ok = FALSE;
3788 break;
3792 if (ok) {
3793 *parents = 0;
3794 ok = prompt_menu("Select parent", items, parents);
3796 for (i = 0; items[i].text; i++)
3797 free((char *) items[i].text);
3798 free(items);
3799 return ok;
3802 static bool
3803 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3805 char buf[SIZEOF_STR * 4];
3806 const char *revlist_argv[] = {
3807 "git", "log", "--no-color", "-1",
3808 "--pretty=format:%P", id, "--", path, NULL
3810 int parents;
3812 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3813 (parents = strlen(buf) / 40) < 0) {
3814 report("Failed to get parent information");
3815 return FALSE;
3817 } else if (parents == 0) {
3818 if (path)
3819 report("Path '%s' does not exist in the parent", path);
3820 else
3821 report("The selected commit has no parents");
3822 return FALSE;
3825 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3826 return FALSE;
3828 string_copy_rev(rev, &buf[41 * parents]);
3829 return TRUE;
3833 * Pager backend
3836 static bool
3837 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3839 char text[SIZEOF_STR];
3841 if (opt_line_number && draw_lineno(view, lineno))
3842 return TRUE;
3844 string_expand(text, sizeof(text), line->data, opt_tab_size);
3845 draw_text(view, line->type, text, TRUE);
3846 return TRUE;
3849 static bool
3850 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3852 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3853 char ref[SIZEOF_STR];
3855 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3856 return TRUE;
3858 /* This is the only fatal call, since it can "corrupt" the buffer. */
3859 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3860 return FALSE;
3862 return TRUE;
3865 static void
3866 add_pager_refs(struct view *view, struct line *line)
3868 char buf[SIZEOF_STR];
3869 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3870 struct ref_list *list;
3871 size_t bufpos = 0, i;
3872 const char *sep = "Refs: ";
3873 bool is_tag = FALSE;
3875 assert(line->type == LINE_COMMIT);
3877 list = get_ref_list(commit_id);
3878 if (!list) {
3879 if (view == VIEW(REQ_VIEW_DIFF))
3880 goto try_add_describe_ref;
3881 return;
3884 for (i = 0; i < list->size; i++) {
3885 struct ref *ref = list->refs[i];
3886 const char *fmt = ref->tag ? "%s[%s]" :
3887 ref->remote ? "%s<%s>" : "%s%s";
3889 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3890 return;
3891 sep = ", ";
3892 if (ref->tag)
3893 is_tag = TRUE;
3896 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3897 try_add_describe_ref:
3898 /* Add <tag>-g<commit_id> "fake" reference. */
3899 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3900 return;
3903 if (bufpos == 0)
3904 return;
3906 add_line_text(view, buf, LINE_PP_REFS);
3909 static bool
3910 pager_read(struct view *view, char *data)
3912 struct line *line;
3914 if (!data)
3915 return TRUE;
3917 line = add_line_text(view, data, get_line_type(data));
3918 if (!line)
3919 return FALSE;
3921 if (line->type == LINE_COMMIT &&
3922 (view == VIEW(REQ_VIEW_DIFF) ||
3923 view == VIEW(REQ_VIEW_LOG)))
3924 add_pager_refs(view, line);
3926 return TRUE;
3929 static enum request
3930 pager_request(struct view *view, enum request request, struct line *line)
3932 int split = 0;
3934 if (request != REQ_ENTER)
3935 return request;
3937 if (line->type == LINE_COMMIT &&
3938 (view == VIEW(REQ_VIEW_LOG) ||
3939 view == VIEW(REQ_VIEW_PAGER))) {
3940 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3941 split = 1;
3944 /* Always scroll the view even if it was split. That way
3945 * you can use Enter to scroll through the log view and
3946 * split open each commit diff. */
3947 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3949 /* FIXME: A minor workaround. Scrolling the view will call report("")
3950 * but if we are scrolling a non-current view this won't properly
3951 * update the view title. */
3952 if (split)
3953 update_view_title(view);
3955 return REQ_NONE;
3958 static bool
3959 pager_grep(struct view *view, struct line *line)
3961 const char *text[] = { line->data, NULL };
3963 return grep_text(view, text);
3966 static void
3967 pager_select(struct view *view, struct line *line)
3969 if (line->type == LINE_COMMIT) {
3970 char *text = (char *)line->data + STRING_SIZE("commit ");
3972 if (view != VIEW(REQ_VIEW_PAGER))
3973 string_copy_rev(view->ref, text);
3974 string_copy_rev(ref_commit, text);
3978 static struct view_ops pager_ops = {
3979 "line",
3980 NULL,
3981 NULL,
3982 pager_read,
3983 pager_draw,
3984 pager_request,
3985 pager_grep,
3986 pager_select,
3989 static const char *log_argv[SIZEOF_ARG] = {
3990 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3993 static enum request
3994 log_request(struct view *view, enum request request, struct line *line)
3996 switch (request) {
3997 case REQ_REFRESH:
3998 load_refs();
3999 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4000 return REQ_NONE;
4001 default:
4002 return pager_request(view, request, line);
4006 static struct view_ops log_ops = {
4007 "line",
4008 log_argv,
4009 NULL,
4010 pager_read,
4011 pager_draw,
4012 log_request,
4013 pager_grep,
4014 pager_select,
4017 static const char *diff_argv[SIZEOF_ARG] = {
4018 "git", "show", "--pretty=fuller", "--no-color", "--root",
4019 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4022 static struct view_ops diff_ops = {
4023 "line",
4024 diff_argv,
4025 NULL,
4026 pager_read,
4027 pager_draw,
4028 pager_request,
4029 pager_grep,
4030 pager_select,
4034 * Help backend
4037 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4039 static bool
4040 help_open_keymap_title(struct view *view, enum keymap keymap)
4042 struct line *line;
4044 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4045 help_keymap_hidden[keymap] ? '+' : '-',
4046 enum_name(keymap_table[keymap]));
4047 if (line)
4048 line->other = keymap;
4050 return help_keymap_hidden[keymap];
4053 static void
4054 help_open_keymap(struct view *view, enum keymap keymap)
4056 const char *group = NULL;
4057 char buf[SIZEOF_STR];
4058 size_t bufpos;
4059 bool add_title = TRUE;
4060 int i;
4062 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4063 const char *key = NULL;
4065 if (req_info[i].request == REQ_NONE)
4066 continue;
4068 if (!req_info[i].request) {
4069 group = req_info[i].help;
4070 continue;
4073 key = get_keys(keymap, req_info[i].request, TRUE);
4074 if (!key || !*key)
4075 continue;
4077 if (add_title && help_open_keymap_title(view, keymap))
4078 return;
4079 add_title = FALSE;
4081 if (group) {
4082 add_line_text(view, group, LINE_HELP_GROUP);
4083 group = NULL;
4086 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4087 enum_name(req_info[i]), req_info[i].help);
4090 group = "External commands:";
4092 for (i = 0; i < run_requests; i++) {
4093 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4094 const char *key;
4095 int argc;
4097 if (!req || req->keymap != keymap)
4098 continue;
4100 key = get_key_name(req->key);
4101 if (!*key)
4102 key = "(no key defined)";
4104 if (add_title && help_open_keymap_title(view, keymap))
4105 return;
4106 if (group) {
4107 add_line_text(view, group, LINE_HELP_GROUP);
4108 group = NULL;
4111 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4112 if (!string_format_from(buf, &bufpos, "%s%s",
4113 argc ? " " : "", req->argv[argc]))
4114 return;
4116 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4120 static bool
4121 help_open(struct view *view)
4123 enum keymap keymap;
4125 reset_view(view);
4126 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4127 add_line_text(view, "", LINE_DEFAULT);
4129 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4130 help_open_keymap(view, keymap);
4132 return TRUE;
4135 static enum request
4136 help_request(struct view *view, enum request request, struct line *line)
4138 switch (request) {
4139 case REQ_ENTER:
4140 if (line->type == LINE_HELP_KEYMAP) {
4141 help_keymap_hidden[line->other] =
4142 !help_keymap_hidden[line->other];
4143 view->p_restore = TRUE;
4144 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4147 return REQ_NONE;
4148 default:
4149 return pager_request(view, request, line);
4153 static struct view_ops help_ops = {
4154 "line",
4155 NULL,
4156 help_open,
4157 NULL,
4158 pager_draw,
4159 help_request,
4160 pager_grep,
4161 pager_select,
4166 * Tree backend
4169 struct tree_stack_entry {
4170 struct tree_stack_entry *prev; /* Entry below this in the stack */
4171 unsigned long lineno; /* Line number to restore */
4172 char *name; /* Position of name in opt_path */
4175 /* The top of the path stack. */
4176 static struct tree_stack_entry *tree_stack = NULL;
4177 unsigned long tree_lineno = 0;
4179 static void
4180 pop_tree_stack_entry(void)
4182 struct tree_stack_entry *entry = tree_stack;
4184 tree_lineno = entry->lineno;
4185 entry->name[0] = 0;
4186 tree_stack = entry->prev;
4187 free(entry);
4190 static void
4191 push_tree_stack_entry(const char *name, unsigned long lineno)
4193 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4194 size_t pathlen = strlen(opt_path);
4196 if (!entry)
4197 return;
4199 entry->prev = tree_stack;
4200 entry->name = opt_path + pathlen;
4201 tree_stack = entry;
4203 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4204 pop_tree_stack_entry();
4205 return;
4208 /* Move the current line to the first tree entry. */
4209 tree_lineno = 1;
4210 entry->lineno = lineno;
4213 /* Parse output from git-ls-tree(1):
4215 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4218 #define SIZEOF_TREE_ATTR \
4219 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4221 #define SIZEOF_TREE_MODE \
4222 STRING_SIZE("100644 ")
4224 #define TREE_ID_OFFSET \
4225 STRING_SIZE("100644 blob ")
4227 struct tree_entry {
4228 char id[SIZEOF_REV];
4229 mode_t mode;
4230 struct time time; /* Date from the author ident. */
4231 const char *author; /* Author of the commit. */
4232 char name[1];
4235 static const char *
4236 tree_path(const struct line *line)
4238 return ((struct tree_entry *) line->data)->name;
4241 static int
4242 tree_compare_entry(const struct line *line1, const struct line *line2)
4244 if (line1->type != line2->type)
4245 return line1->type == LINE_TREE_DIR ? -1 : 1;
4246 return strcmp(tree_path(line1), tree_path(line2));
4249 static const enum sort_field tree_sort_fields[] = {
4250 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4252 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4254 static int
4255 tree_compare(const void *l1, const void *l2)
4257 const struct line *line1 = (const struct line *) l1;
4258 const struct line *line2 = (const struct line *) l2;
4259 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4260 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4262 if (line1->type == LINE_TREE_HEAD)
4263 return -1;
4264 if (line2->type == LINE_TREE_HEAD)
4265 return 1;
4267 switch (get_sort_field(tree_sort_state)) {
4268 case ORDERBY_DATE:
4269 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4271 case ORDERBY_AUTHOR:
4272 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4274 case ORDERBY_NAME:
4275 default:
4276 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4281 static struct line *
4282 tree_entry(struct view *view, enum line_type type, const char *path,
4283 const char *mode, const char *id)
4285 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4286 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4288 if (!entry || !line) {
4289 free(entry);
4290 return NULL;
4293 strncpy(entry->name, path, strlen(path));
4294 if (mode)
4295 entry->mode = strtoul(mode, NULL, 8);
4296 if (id)
4297 string_copy_rev(entry->id, id);
4299 return line;
4302 static bool
4303 tree_read_date(struct view *view, char *text, bool *read_date)
4305 static const char *author_name;
4306 static struct time author_time;
4308 if (!text && *read_date) {
4309 *read_date = FALSE;
4310 return TRUE;
4312 } else if (!text) {
4313 char *path = *opt_path ? opt_path : ".";
4314 /* Find next entry to process */
4315 const char *log_file[] = {
4316 "git", "log", "--no-color", "--pretty=raw",
4317 "--cc", "--raw", view->id, "--", path, NULL
4319 struct io io = {};
4321 if (!view->lines) {
4322 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4323 report("Tree is empty");
4324 return TRUE;
4327 if (!run_io_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4328 report("Failed to load tree data");
4329 return TRUE;
4332 done_io(view->pipe);
4333 view->io = io;
4334 *read_date = TRUE;
4335 return FALSE;
4337 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4338 parse_author_line(text + STRING_SIZE("author "),
4339 &author_name, &author_time);
4341 } else if (*text == ':') {
4342 char *pos;
4343 size_t annotated = 1;
4344 size_t i;
4346 pos = strchr(text, '\t');
4347 if (!pos)
4348 return TRUE;
4349 text = pos + 1;
4350 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4351 text += strlen(opt_path);
4352 pos = strchr(text, '/');
4353 if (pos)
4354 *pos = 0;
4356 for (i = 1; i < view->lines; i++) {
4357 struct line *line = &view->line[i];
4358 struct tree_entry *entry = line->data;
4360 annotated += !!entry->author;
4361 if (entry->author || strcmp(entry->name, text))
4362 continue;
4364 entry->author = author_name;
4365 entry->time = author_time;
4366 line->dirty = 1;
4367 break;
4370 if (annotated == view->lines)
4371 kill_io(view->pipe);
4373 return TRUE;
4376 static bool
4377 tree_read(struct view *view, char *text)
4379 static bool read_date = FALSE;
4380 struct tree_entry *data;
4381 struct line *entry, *line;
4382 enum line_type type;
4383 size_t textlen = text ? strlen(text) : 0;
4384 char *path = text + SIZEOF_TREE_ATTR;
4386 if (read_date || !text)
4387 return tree_read_date(view, text, &read_date);
4389 if (textlen <= SIZEOF_TREE_ATTR)
4390 return FALSE;
4391 if (view->lines == 0 &&
4392 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4393 return FALSE;
4395 /* Strip the path part ... */
4396 if (*opt_path) {
4397 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4398 size_t striplen = strlen(opt_path);
4400 if (pathlen > striplen)
4401 memmove(path, path + striplen,
4402 pathlen - striplen + 1);
4404 /* Insert "link" to parent directory. */
4405 if (view->lines == 1 &&
4406 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4407 return FALSE;
4410 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4411 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4412 if (!entry)
4413 return FALSE;
4414 data = entry->data;
4416 /* Skip "Directory ..." and ".." line. */
4417 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4418 if (tree_compare_entry(line, entry) <= 0)
4419 continue;
4421 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4423 line->data = data;
4424 line->type = type;
4425 for (; line <= entry; line++)
4426 line->dirty = line->cleareol = 1;
4427 return TRUE;
4430 if (tree_lineno > view->lineno) {
4431 view->lineno = tree_lineno;
4432 tree_lineno = 0;
4435 return TRUE;
4438 static bool
4439 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4441 struct tree_entry *entry = line->data;
4443 if (line->type == LINE_TREE_HEAD) {
4444 if (draw_text(view, line->type, "Directory path /", TRUE))
4445 return TRUE;
4446 } else {
4447 if (draw_mode(view, entry->mode))
4448 return TRUE;
4450 if (opt_author && draw_author(view, entry->author))
4451 return TRUE;
4453 if (opt_date && draw_date(view, &entry->time))
4454 return TRUE;
4456 if (draw_text(view, line->type, entry->name, TRUE))
4457 return TRUE;
4458 return TRUE;
4461 static void
4462 open_blob_editor()
4464 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4465 int fd = mkstemp(file);
4467 if (fd == -1)
4468 report("Failed to create temporary file");
4469 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4470 report("Failed to save blob data to file");
4471 else
4472 open_editor(file);
4473 if (fd != -1)
4474 unlink(file);
4477 static enum request
4478 tree_request(struct view *view, enum request request, struct line *line)
4480 enum open_flags flags;
4482 switch (request) {
4483 case REQ_VIEW_BLAME:
4484 if (line->type != LINE_TREE_FILE) {
4485 report("Blame only supported for files");
4486 return REQ_NONE;
4489 string_copy(opt_ref, view->vid);
4490 return request;
4492 case REQ_EDIT:
4493 if (line->type != LINE_TREE_FILE) {
4494 report("Edit only supported for files");
4495 } else if (!is_head_commit(view->vid)) {
4496 open_blob_editor();
4497 } else {
4498 open_editor(opt_file);
4500 return REQ_NONE;
4502 case REQ_TOGGLE_SORT_FIELD:
4503 case REQ_TOGGLE_SORT_ORDER:
4504 sort_view(view, request, &tree_sort_state, tree_compare);
4505 return REQ_NONE;
4507 case REQ_PARENT:
4508 if (!*opt_path) {
4509 /* quit view if at top of tree */
4510 return REQ_VIEW_CLOSE;
4512 /* fake 'cd ..' */
4513 line = &view->line[1];
4514 break;
4516 case REQ_ENTER:
4517 break;
4519 default:
4520 return request;
4523 /* Cleanup the stack if the tree view is at a different tree. */
4524 while (!*opt_path && tree_stack)
4525 pop_tree_stack_entry();
4527 switch (line->type) {
4528 case LINE_TREE_DIR:
4529 /* Depending on whether it is a subdirectory or parent link
4530 * mangle the path buffer. */
4531 if (line == &view->line[1] && *opt_path) {
4532 pop_tree_stack_entry();
4534 } else {
4535 const char *basename = tree_path(line);
4537 push_tree_stack_entry(basename, view->lineno);
4540 /* Trees and subtrees share the same ID, so they are not not
4541 * unique like blobs. */
4542 flags = OPEN_RELOAD;
4543 request = REQ_VIEW_TREE;
4544 break;
4546 case LINE_TREE_FILE:
4547 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4548 request = REQ_VIEW_BLOB;
4549 break;
4551 default:
4552 return REQ_NONE;
4555 open_view(view, request, flags);
4556 if (request == REQ_VIEW_TREE)
4557 view->lineno = tree_lineno;
4559 return REQ_NONE;
4562 static bool
4563 tree_grep(struct view *view, struct line *line)
4565 struct tree_entry *entry = line->data;
4566 const char *text[] = {
4567 entry->name,
4568 opt_author ? entry->author : "",
4569 mkdate(&entry->time, opt_date),
4570 NULL
4573 return grep_text(view, text);
4576 static void
4577 tree_select(struct view *view, struct line *line)
4579 struct tree_entry *entry = line->data;
4581 if (line->type == LINE_TREE_FILE) {
4582 string_copy_rev(ref_blob, entry->id);
4583 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4585 } else if (line->type != LINE_TREE_DIR) {
4586 return;
4589 string_copy_rev(view->ref, entry->id);
4592 static bool
4593 tree_prepare(struct view *view)
4595 if (view->lines == 0 && opt_prefix[0]) {
4596 char *pos = opt_prefix;
4598 while (pos && *pos) {
4599 char *end = strchr(pos, '/');
4601 if (end)
4602 *end = 0;
4603 push_tree_stack_entry(pos, 0);
4604 pos = end;
4605 if (end) {
4606 *end = '/';
4607 pos++;
4611 } else if (strcmp(view->vid, view->id)) {
4612 opt_path[0] = 0;
4615 return init_io_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4618 static const char *tree_argv[SIZEOF_ARG] = {
4619 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4622 static struct view_ops tree_ops = {
4623 "file",
4624 tree_argv,
4625 NULL,
4626 tree_read,
4627 tree_draw,
4628 tree_request,
4629 tree_grep,
4630 tree_select,
4631 tree_prepare,
4634 static bool
4635 blob_read(struct view *view, char *line)
4637 if (!line)
4638 return TRUE;
4639 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4642 static enum request
4643 blob_request(struct view *view, enum request request, struct line *line)
4645 switch (request) {
4646 case REQ_EDIT:
4647 open_blob_editor();
4648 return REQ_NONE;
4649 default:
4650 return pager_request(view, request, line);
4654 static const char *blob_argv[SIZEOF_ARG] = {
4655 "git", "cat-file", "blob", "%(blob)", NULL
4658 static struct view_ops blob_ops = {
4659 "line",
4660 blob_argv,
4661 NULL,
4662 blob_read,
4663 pager_draw,
4664 blob_request,
4665 pager_grep,
4666 pager_select,
4670 * Blame backend
4672 * Loading the blame view is a two phase job:
4674 * 1. File content is read either using opt_file from the
4675 * filesystem or using git-cat-file.
4676 * 2. Then blame information is incrementally added by
4677 * reading output from git-blame.
4680 static const char *blame_head_argv[] = {
4681 "git", "blame", "--incremental", "--", "%(file)", NULL
4684 static const char *blame_ref_argv[] = {
4685 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4688 static const char *blame_cat_file_argv[] = {
4689 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4692 struct blame_commit {
4693 char id[SIZEOF_REV]; /* SHA1 ID. */
4694 char title[128]; /* First line of the commit message. */
4695 const char *author; /* Author of the commit. */
4696 struct time time; /* Date from the author ident. */
4697 char filename[128]; /* Name of file. */
4698 bool has_previous; /* Was a "previous" line detected. */
4701 struct blame {
4702 struct blame_commit *commit;
4703 unsigned long lineno;
4704 char text[1];
4707 static bool
4708 blame_open(struct view *view)
4710 char path[SIZEOF_STR];
4712 if (!view->parent && *opt_prefix) {
4713 string_copy(path, opt_file);
4714 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4715 return FALSE;
4718 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4719 if (!run_io_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4720 return FALSE;
4723 setup_update(view, opt_file);
4724 string_format(view->ref, "%s ...", opt_file);
4726 return TRUE;
4729 static struct blame_commit *
4730 get_blame_commit(struct view *view, const char *id)
4732 size_t i;
4734 for (i = 0; i < view->lines; i++) {
4735 struct blame *blame = view->line[i].data;
4737 if (!blame->commit)
4738 continue;
4740 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4741 return blame->commit;
4745 struct blame_commit *commit = calloc(1, sizeof(*commit));
4747 if (commit)
4748 string_ncopy(commit->id, id, SIZEOF_REV);
4749 return commit;
4753 static bool
4754 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4756 const char *pos = *posref;
4758 *posref = NULL;
4759 pos = strchr(pos + 1, ' ');
4760 if (!pos || !isdigit(pos[1]))
4761 return FALSE;
4762 *number = atoi(pos + 1);
4763 if (*number < min || *number > max)
4764 return FALSE;
4766 *posref = pos;
4767 return TRUE;
4770 static struct blame_commit *
4771 parse_blame_commit(struct view *view, const char *text, int *blamed)
4773 struct blame_commit *commit;
4774 struct blame *blame;
4775 const char *pos = text + SIZEOF_REV - 2;
4776 size_t orig_lineno = 0;
4777 size_t lineno;
4778 size_t group;
4780 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4781 return NULL;
4783 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4784 !parse_number(&pos, &lineno, 1, view->lines) ||
4785 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4786 return NULL;
4788 commit = get_blame_commit(view, text);
4789 if (!commit)
4790 return NULL;
4792 *blamed += group;
4793 while (group--) {
4794 struct line *line = &view->line[lineno + group - 1];
4796 blame = line->data;
4797 blame->commit = commit;
4798 blame->lineno = orig_lineno + group - 1;
4799 line->dirty = 1;
4802 return commit;
4805 static bool
4806 blame_read_file(struct view *view, const char *line, bool *read_file)
4808 if (!line) {
4809 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4810 struct io io = {};
4812 if (view->lines == 0 && !view->parent)
4813 die("No blame exist for %s", view->vid);
4815 if (view->lines == 0 || !run_io_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4816 report("Failed to load blame data");
4817 return TRUE;
4820 done_io(view->pipe);
4821 view->io = io;
4822 *read_file = FALSE;
4823 return FALSE;
4825 } else {
4826 size_t linelen = strlen(line);
4827 struct blame *blame = malloc(sizeof(*blame) + linelen);
4829 if (!blame)
4830 return FALSE;
4832 blame->commit = NULL;
4833 strncpy(blame->text, line, linelen);
4834 blame->text[linelen] = 0;
4835 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4839 static bool
4840 match_blame_header(const char *name, char **line)
4842 size_t namelen = strlen(name);
4843 bool matched = !strncmp(name, *line, namelen);
4845 if (matched)
4846 *line += namelen;
4848 return matched;
4851 static bool
4852 blame_read(struct view *view, char *line)
4854 static struct blame_commit *commit = NULL;
4855 static int blamed = 0;
4856 static bool read_file = TRUE;
4858 if (read_file)
4859 return blame_read_file(view, line, &read_file);
4861 if (!line) {
4862 /* Reset all! */
4863 commit = NULL;
4864 blamed = 0;
4865 read_file = TRUE;
4866 string_format(view->ref, "%s", view->vid);
4867 if (view_is_displayed(view)) {
4868 update_view_title(view);
4869 redraw_view_from(view, 0);
4871 return TRUE;
4874 if (!commit) {
4875 commit = parse_blame_commit(view, line, &blamed);
4876 string_format(view->ref, "%s %2d%%", view->vid,
4877 view->lines ? blamed * 100 / view->lines : 0);
4879 } else if (match_blame_header("author ", &line)) {
4880 commit->author = get_author(line);
4882 } else if (match_blame_header("author-time ", &line)) {
4883 parse_timesec(&commit->time, line);
4885 } else if (match_blame_header("author-tz ", &line)) {
4886 parse_timezone(&commit->time, line);
4888 } else if (match_blame_header("summary ", &line)) {
4889 string_ncopy(commit->title, line, strlen(line));
4891 } else if (match_blame_header("previous ", &line)) {
4892 commit->has_previous = TRUE;
4894 } else if (match_blame_header("filename ", &line)) {
4895 string_ncopy(commit->filename, line, strlen(line));
4896 commit = NULL;
4899 return TRUE;
4902 static bool
4903 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4905 struct blame *blame = line->data;
4906 struct time *time = NULL;
4907 const char *id = NULL, *author = NULL;
4908 char text[SIZEOF_STR];
4910 if (blame->commit && *blame->commit->filename) {
4911 id = blame->commit->id;
4912 author = blame->commit->author;
4913 time = &blame->commit->time;
4916 if (opt_date && draw_date(view, time))
4917 return TRUE;
4919 if (opt_author && draw_author(view, author))
4920 return TRUE;
4922 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4923 return TRUE;
4925 if (draw_lineno(view, lineno))
4926 return TRUE;
4928 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4929 draw_text(view, LINE_DEFAULT, text, TRUE);
4930 return TRUE;
4933 static bool
4934 check_blame_commit(struct blame *blame, bool check_null_id)
4936 if (!blame->commit)
4937 report("Commit data not loaded yet");
4938 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4939 report("No commit exist for the selected line");
4940 else
4941 return TRUE;
4942 return FALSE;
4945 static void
4946 setup_blame_parent_line(struct view *view, struct blame *blame)
4948 const char *diff_tree_argv[] = {
4949 "git", "diff-tree", "-U0", blame->commit->id,
4950 "--", blame->commit->filename, NULL
4952 struct io io = {};
4953 int parent_lineno = -1;
4954 int blamed_lineno = -1;
4955 char *line;
4957 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4958 return;
4960 while ((line = io_get(&io, '\n', TRUE))) {
4961 if (*line == '@') {
4962 char *pos = strchr(line, '+');
4964 parent_lineno = atoi(line + 4);
4965 if (pos)
4966 blamed_lineno = atoi(pos + 1);
4968 } else if (*line == '+' && parent_lineno != -1) {
4969 if (blame->lineno == blamed_lineno - 1 &&
4970 !strcmp(blame->text, line + 1)) {
4971 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4972 break;
4974 blamed_lineno++;
4978 done_io(&io);
4981 static enum request
4982 blame_request(struct view *view, enum request request, struct line *line)
4984 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4985 struct blame *blame = line->data;
4987 switch (request) {
4988 case REQ_VIEW_BLAME:
4989 if (check_blame_commit(blame, TRUE)) {
4990 string_copy(opt_ref, blame->commit->id);
4991 string_copy(opt_file, blame->commit->filename);
4992 if (blame->lineno)
4993 view->lineno = blame->lineno;
4994 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4996 break;
4998 case REQ_PARENT:
4999 if (check_blame_commit(blame, TRUE) &&
5000 select_commit_parent(blame->commit->id, opt_ref,
5001 blame->commit->filename)) {
5002 string_copy(opt_file, blame->commit->filename);
5003 setup_blame_parent_line(view, blame);
5004 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5006 break;
5008 case REQ_ENTER:
5009 if (!check_blame_commit(blame, FALSE))
5010 break;
5012 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5013 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5014 break;
5016 if (!strcmp(blame->commit->id, NULL_ID)) {
5017 struct view *diff = VIEW(REQ_VIEW_DIFF);
5018 const char *diff_index_argv[] = {
5019 "git", "diff-index", "--root", "--patch-with-stat",
5020 "-C", "-M", "HEAD", "--", view->vid, NULL
5023 if (!blame->commit->has_previous) {
5024 diff_index_argv[1] = "diff";
5025 diff_index_argv[2] = "--no-color";
5026 diff_index_argv[6] = "--";
5027 diff_index_argv[7] = "/dev/null";
5030 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5031 report("Failed to allocate diff command");
5032 break;
5034 flags |= OPEN_PREPARED;
5037 open_view(view, REQ_VIEW_DIFF, flags);
5038 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5039 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5040 break;
5042 default:
5043 return request;
5046 return REQ_NONE;
5049 static bool
5050 blame_grep(struct view *view, struct line *line)
5052 struct blame *blame = line->data;
5053 struct blame_commit *commit = blame->commit;
5054 const char *text[] = {
5055 blame->text,
5056 commit ? commit->title : "",
5057 commit ? commit->id : "",
5058 commit && opt_author ? commit->author : "",
5059 commit ? mkdate(&commit->time, opt_date) : "",
5060 NULL
5063 return grep_text(view, text);
5066 static void
5067 blame_select(struct view *view, struct line *line)
5069 struct blame *blame = line->data;
5070 struct blame_commit *commit = blame->commit;
5072 if (!commit)
5073 return;
5075 if (!strcmp(commit->id, NULL_ID))
5076 string_ncopy(ref_commit, "HEAD", 4);
5077 else
5078 string_copy_rev(ref_commit, commit->id);
5081 static struct view_ops blame_ops = {
5082 "line",
5083 NULL,
5084 blame_open,
5085 blame_read,
5086 blame_draw,
5087 blame_request,
5088 blame_grep,
5089 blame_select,
5093 * Branch backend
5096 struct branch {
5097 const char *author; /* Author of the last commit. */
5098 struct time time; /* Date of the last activity. */
5099 const struct ref *ref; /* Name and commit ID information. */
5102 static const struct ref branch_all;
5104 static const enum sort_field branch_sort_fields[] = {
5105 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5107 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5109 static int
5110 branch_compare(const void *l1, const void *l2)
5112 const struct branch *branch1 = ((const struct line *) l1)->data;
5113 const struct branch *branch2 = ((const struct line *) l2)->data;
5115 switch (get_sort_field(branch_sort_state)) {
5116 case ORDERBY_DATE:
5117 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5119 case ORDERBY_AUTHOR:
5120 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5122 case ORDERBY_NAME:
5123 default:
5124 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5128 static bool
5129 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5131 struct branch *branch = line->data;
5132 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5134 if (opt_date && draw_date(view, &branch->time))
5135 return TRUE;
5137 if (opt_author && draw_author(view, branch->author))
5138 return TRUE;
5140 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5141 return TRUE;
5144 static enum request
5145 branch_request(struct view *view, enum request request, struct line *line)
5147 struct branch *branch = line->data;
5149 switch (request) {
5150 case REQ_REFRESH:
5151 load_refs();
5152 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5153 return REQ_NONE;
5155 case REQ_TOGGLE_SORT_FIELD:
5156 case REQ_TOGGLE_SORT_ORDER:
5157 sort_view(view, request, &branch_sort_state, branch_compare);
5158 return REQ_NONE;
5160 case REQ_ENTER:
5161 if (branch->ref == &branch_all) {
5162 const char *all_branches_argv[] = {
5163 "git", "log", "--no-color", "--pretty=raw", "--parents",
5164 "--topo-order", "--all", NULL
5166 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5168 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5169 report("Failed to load view of all branches");
5170 return REQ_NONE;
5172 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5173 } else {
5174 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5176 return REQ_NONE;
5178 default:
5179 return request;
5183 static bool
5184 branch_read(struct view *view, char *line)
5186 static char id[SIZEOF_REV];
5187 struct branch *reference;
5188 size_t i;
5190 if (!line)
5191 return TRUE;
5193 switch (get_line_type(line)) {
5194 case LINE_COMMIT:
5195 string_copy_rev(id, line + STRING_SIZE("commit "));
5196 return TRUE;
5198 case LINE_AUTHOR:
5199 for (i = 0, reference = NULL; i < view->lines; i++) {
5200 struct branch *branch = view->line[i].data;
5202 if (strcmp(branch->ref->id, id))
5203 continue;
5205 view->line[i].dirty = TRUE;
5206 if (reference) {
5207 branch->author = reference->author;
5208 branch->time = reference->time;
5209 continue;
5212 parse_author_line(line + STRING_SIZE("author "),
5213 &branch->author, &branch->time);
5214 reference = branch;
5216 return TRUE;
5218 default:
5219 return TRUE;
5224 static bool
5225 branch_open_visitor(void *data, const struct ref *ref)
5227 struct view *view = data;
5228 struct branch *branch;
5230 if (ref->tag || ref->ltag || ref->remote)
5231 return TRUE;
5233 branch = calloc(1, sizeof(*branch));
5234 if (!branch)
5235 return FALSE;
5237 branch->ref = ref;
5238 return !!add_line_data(view, branch, LINE_DEFAULT);
5241 static bool
5242 branch_open(struct view *view)
5244 const char *branch_log[] = {
5245 "git", "log", "--no-color", "--pretty=raw",
5246 "--simplify-by-decoration", "--all", NULL
5249 if (!run_io_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5250 report("Failed to load branch data");
5251 return TRUE;
5254 setup_update(view, view->id);
5255 branch_open_visitor(view, &branch_all);
5256 foreach_ref(branch_open_visitor, view);
5257 view->p_restore = TRUE;
5259 return TRUE;
5262 static bool
5263 branch_grep(struct view *view, struct line *line)
5265 struct branch *branch = line->data;
5266 const char *text[] = {
5267 branch->ref->name,
5268 branch->author,
5269 NULL
5272 return grep_text(view, text);
5275 static void
5276 branch_select(struct view *view, struct line *line)
5278 struct branch *branch = line->data;
5280 string_copy_rev(view->ref, branch->ref->id);
5281 string_copy_rev(ref_commit, branch->ref->id);
5282 string_copy_rev(ref_head, branch->ref->id);
5285 static struct view_ops branch_ops = {
5286 "branch",
5287 NULL,
5288 branch_open,
5289 branch_read,
5290 branch_draw,
5291 branch_request,
5292 branch_grep,
5293 branch_select,
5297 * Status backend
5300 struct status {
5301 char status;
5302 struct {
5303 mode_t mode;
5304 char rev[SIZEOF_REV];
5305 char name[SIZEOF_STR];
5306 } old;
5307 struct {
5308 mode_t mode;
5309 char rev[SIZEOF_REV];
5310 char name[SIZEOF_STR];
5311 } new;
5314 static char status_onbranch[SIZEOF_STR];
5315 static struct status stage_status;
5316 static enum line_type stage_line_type;
5317 static size_t stage_chunks;
5318 static int *stage_chunk;
5320 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5322 /* This should work even for the "On branch" line. */
5323 static inline bool
5324 status_has_none(struct view *view, struct line *line)
5326 return line < view->line + view->lines && !line[1].data;
5329 /* Get fields from the diff line:
5330 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5332 static inline bool
5333 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5335 const char *old_mode = buf + 1;
5336 const char *new_mode = buf + 8;
5337 const char *old_rev = buf + 15;
5338 const char *new_rev = buf + 56;
5339 const char *status = buf + 97;
5341 if (bufsize < 98 ||
5342 old_mode[-1] != ':' ||
5343 new_mode[-1] != ' ' ||
5344 old_rev[-1] != ' ' ||
5345 new_rev[-1] != ' ' ||
5346 status[-1] != ' ')
5347 return FALSE;
5349 file->status = *status;
5351 string_copy_rev(file->old.rev, old_rev);
5352 string_copy_rev(file->new.rev, new_rev);
5354 file->old.mode = strtoul(old_mode, NULL, 8);
5355 file->new.mode = strtoul(new_mode, NULL, 8);
5357 file->old.name[0] = file->new.name[0] = 0;
5359 return TRUE;
5362 static bool
5363 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5365 struct status *unmerged = NULL;
5366 char *buf;
5367 struct io io = {};
5369 if (!run_io(&io, argv, opt_cdup, IO_RD))
5370 return FALSE;
5372 add_line_data(view, NULL, type);
5374 while ((buf = io_get(&io, 0, TRUE))) {
5375 struct status *file = unmerged;
5377 if (!file) {
5378 file = calloc(1, sizeof(*file));
5379 if (!file || !add_line_data(view, file, type))
5380 goto error_out;
5383 /* Parse diff info part. */
5384 if (status) {
5385 file->status = status;
5386 if (status == 'A')
5387 string_copy(file->old.rev, NULL_ID);
5389 } else if (!file->status || file == unmerged) {
5390 if (!status_get_diff(file, buf, strlen(buf)))
5391 goto error_out;
5393 buf = io_get(&io, 0, TRUE);
5394 if (!buf)
5395 break;
5397 /* Collapse all modified entries that follow an
5398 * associated unmerged entry. */
5399 if (unmerged == file) {
5400 unmerged->status = 'U';
5401 unmerged = NULL;
5402 } else if (file->status == 'U') {
5403 unmerged = file;
5407 /* Grab the old name for rename/copy. */
5408 if (!*file->old.name &&
5409 (file->status == 'R' || file->status == 'C')) {
5410 string_ncopy(file->old.name, buf, strlen(buf));
5412 buf = io_get(&io, 0, TRUE);
5413 if (!buf)
5414 break;
5417 /* git-ls-files just delivers a NUL separated list of
5418 * file names similar to the second half of the
5419 * git-diff-* output. */
5420 string_ncopy(file->new.name, buf, strlen(buf));
5421 if (!*file->old.name)
5422 string_copy(file->old.name, file->new.name);
5423 file = NULL;
5426 if (io_error(&io)) {
5427 error_out:
5428 done_io(&io);
5429 return FALSE;
5432 if (!view->line[view->lines - 1].data)
5433 add_line_data(view, NULL, LINE_STAT_NONE);
5435 done_io(&io);
5436 return TRUE;
5439 /* Don't show unmerged entries in the staged section. */
5440 static const char *status_diff_index_argv[] = {
5441 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5442 "--cached", "-M", "HEAD", NULL
5445 static const char *status_diff_files_argv[] = {
5446 "git", "diff-files", "-z", NULL
5449 static const char *status_list_other_argv[] = {
5450 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5453 static const char *status_list_no_head_argv[] = {
5454 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5457 static const char *update_index_argv[] = {
5458 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5461 /* Restore the previous line number to stay in the context or select a
5462 * line with something that can be updated. */
5463 static void
5464 status_restore(struct view *view)
5466 if (view->p_lineno >= view->lines)
5467 view->p_lineno = view->lines - 1;
5468 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5469 view->p_lineno++;
5470 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5471 view->p_lineno--;
5473 /* If the above fails, always skip the "On branch" line. */
5474 if (view->p_lineno < view->lines)
5475 view->lineno = view->p_lineno;
5476 else
5477 view->lineno = 1;
5479 if (view->lineno < view->offset)
5480 view->offset = view->lineno;
5481 else if (view->offset + view->height <= view->lineno)
5482 view->offset = view->lineno - view->height + 1;
5484 view->p_restore = FALSE;
5487 static void
5488 status_update_onbranch(void)
5490 static const char *paths[][2] = {
5491 { "rebase-apply/rebasing", "Rebasing" },
5492 { "rebase-apply/applying", "Applying mailbox" },
5493 { "rebase-apply/", "Rebasing mailbox" },
5494 { "rebase-merge/interactive", "Interactive rebase" },
5495 { "rebase-merge/", "Rebase merge" },
5496 { "MERGE_HEAD", "Merging" },
5497 { "BISECT_LOG", "Bisecting" },
5498 { "HEAD", "On branch" },
5500 char buf[SIZEOF_STR];
5501 struct stat stat;
5502 int i;
5504 if (is_initial_commit()) {
5505 string_copy(status_onbranch, "Initial commit");
5506 return;
5509 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5510 char *head = opt_head;
5512 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5513 lstat(buf, &stat) < 0)
5514 continue;
5516 if (!*opt_head) {
5517 struct io io = {};
5519 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5520 io_read_buf(&io, buf, sizeof(buf))) {
5521 head = buf;
5522 if (!prefixcmp(head, "refs/heads/"))
5523 head += STRING_SIZE("refs/heads/");
5527 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5528 string_copy(status_onbranch, opt_head);
5529 return;
5532 string_copy(status_onbranch, "Not currently on any branch");
5535 /* First parse staged info using git-diff-index(1), then parse unstaged
5536 * info using git-diff-files(1), and finally untracked files using
5537 * git-ls-files(1). */
5538 static bool
5539 status_open(struct view *view)
5541 reset_view(view);
5543 add_line_data(view, NULL, LINE_STAT_HEAD);
5544 status_update_onbranch();
5546 run_io_bg(update_index_argv);
5548 if (is_initial_commit()) {
5549 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5550 return FALSE;
5551 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5552 return FALSE;
5555 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5556 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5557 return FALSE;
5559 /* Restore the exact position or use the specialized restore
5560 * mode? */
5561 if (!view->p_restore)
5562 status_restore(view);
5563 return TRUE;
5566 static bool
5567 status_draw(struct view *view, struct line *line, unsigned int lineno)
5569 struct status *status = line->data;
5570 enum line_type type;
5571 const char *text;
5573 if (!status) {
5574 switch (line->type) {
5575 case LINE_STAT_STAGED:
5576 type = LINE_STAT_SECTION;
5577 text = "Changes to be committed:";
5578 break;
5580 case LINE_STAT_UNSTAGED:
5581 type = LINE_STAT_SECTION;
5582 text = "Changed but not updated:";
5583 break;
5585 case LINE_STAT_UNTRACKED:
5586 type = LINE_STAT_SECTION;
5587 text = "Untracked files:";
5588 break;
5590 case LINE_STAT_NONE:
5591 type = LINE_DEFAULT;
5592 text = " (no files)";
5593 break;
5595 case LINE_STAT_HEAD:
5596 type = LINE_STAT_HEAD;
5597 text = status_onbranch;
5598 break;
5600 default:
5601 return FALSE;
5603 } else {
5604 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5606 buf[0] = status->status;
5607 if (draw_text(view, line->type, buf, TRUE))
5608 return TRUE;
5609 type = LINE_DEFAULT;
5610 text = status->new.name;
5613 draw_text(view, type, text, TRUE);
5614 return TRUE;
5617 static enum request
5618 status_load_error(struct view *view, struct view *stage, const char *path)
5620 if (displayed_views() == 2 || display[current_view] != view)
5621 maximize_view(view);
5622 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5623 return REQ_NONE;
5626 static enum request
5627 status_enter(struct view *view, struct line *line)
5629 struct status *status = line->data;
5630 const char *oldpath = status ? status->old.name : NULL;
5631 /* Diffs for unmerged entries are empty when passing the new
5632 * path, so leave it empty. */
5633 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5634 const char *info;
5635 enum open_flags split;
5636 struct view *stage = VIEW(REQ_VIEW_STAGE);
5638 if (line->type == LINE_STAT_NONE ||
5639 (!status && line[1].type == LINE_STAT_NONE)) {
5640 report("No file to diff");
5641 return REQ_NONE;
5644 switch (line->type) {
5645 case LINE_STAT_STAGED:
5646 if (is_initial_commit()) {
5647 const char *no_head_diff_argv[] = {
5648 "git", "diff", "--no-color", "--patch-with-stat",
5649 "--", "/dev/null", newpath, NULL
5652 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5653 return status_load_error(view, stage, newpath);
5654 } else {
5655 const char *index_show_argv[] = {
5656 "git", "diff-index", "--root", "--patch-with-stat",
5657 "-C", "-M", "--cached", "HEAD", "--",
5658 oldpath, newpath, NULL
5661 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5662 return status_load_error(view, stage, newpath);
5665 if (status)
5666 info = "Staged changes to %s";
5667 else
5668 info = "Staged changes";
5669 break;
5671 case LINE_STAT_UNSTAGED:
5673 const char *files_show_argv[] = {
5674 "git", "diff-files", "--root", "--patch-with-stat",
5675 "-C", "-M", "--", oldpath, newpath, NULL
5678 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5679 return status_load_error(view, stage, newpath);
5680 if (status)
5681 info = "Unstaged changes to %s";
5682 else
5683 info = "Unstaged changes";
5684 break;
5686 case LINE_STAT_UNTRACKED:
5687 if (!newpath) {
5688 report("No file to show");
5689 return REQ_NONE;
5692 if (!suffixcmp(status->new.name, -1, "/")) {
5693 report("Cannot display a directory");
5694 return REQ_NONE;
5697 if (!prepare_update_file(stage, newpath))
5698 return status_load_error(view, stage, newpath);
5699 info = "Untracked file %s";
5700 break;
5702 case LINE_STAT_HEAD:
5703 return REQ_NONE;
5705 default:
5706 die("line type %d not handled in switch", line->type);
5709 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5710 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5711 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5712 if (status) {
5713 stage_status = *status;
5714 } else {
5715 memset(&stage_status, 0, sizeof(stage_status));
5718 stage_line_type = line->type;
5719 stage_chunks = 0;
5720 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5723 return REQ_NONE;
5726 static bool
5727 status_exists(struct status *status, enum line_type type)
5729 struct view *view = VIEW(REQ_VIEW_STATUS);
5730 unsigned long lineno;
5732 for (lineno = 0; lineno < view->lines; lineno++) {
5733 struct line *line = &view->line[lineno];
5734 struct status *pos = line->data;
5736 if (line->type != type)
5737 continue;
5738 if (!pos && (!status || !status->status) && line[1].data) {
5739 select_view_line(view, lineno);
5740 return TRUE;
5742 if (pos && !strcmp(status->new.name, pos->new.name)) {
5743 select_view_line(view, lineno);
5744 return TRUE;
5748 return FALSE;
5752 static bool
5753 status_update_prepare(struct io *io, enum line_type type)
5755 const char *staged_argv[] = {
5756 "git", "update-index", "-z", "--index-info", NULL
5758 const char *others_argv[] = {
5759 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5762 switch (type) {
5763 case LINE_STAT_STAGED:
5764 return run_io(io, staged_argv, opt_cdup, IO_WR);
5766 case LINE_STAT_UNSTAGED:
5767 case LINE_STAT_UNTRACKED:
5768 return run_io(io, others_argv, opt_cdup, IO_WR);
5770 default:
5771 die("line type %d not handled in switch", type);
5772 return FALSE;
5776 static bool
5777 status_update_write(struct io *io, struct status *status, enum line_type type)
5779 char buf[SIZEOF_STR];
5780 size_t bufsize = 0;
5782 switch (type) {
5783 case LINE_STAT_STAGED:
5784 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5785 status->old.mode,
5786 status->old.rev,
5787 status->old.name, 0))
5788 return FALSE;
5789 break;
5791 case LINE_STAT_UNSTAGED:
5792 case LINE_STAT_UNTRACKED:
5793 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5794 return FALSE;
5795 break;
5797 default:
5798 die("line type %d not handled in switch", type);
5801 return io_write(io, buf, bufsize);
5804 static bool
5805 status_update_file(struct status *status, enum line_type type)
5807 struct io io = {};
5808 bool result;
5810 if (!status_update_prepare(&io, type))
5811 return FALSE;
5813 result = status_update_write(&io, status, type);
5814 return done_io(&io) && result;
5817 static bool
5818 status_update_files(struct view *view, struct line *line)
5820 char buf[sizeof(view->ref)];
5821 struct io io = {};
5822 bool result = TRUE;
5823 struct line *pos = view->line + view->lines;
5824 int files = 0;
5825 int file, done;
5826 int cursor_y = -1, cursor_x = -1;
5828 if (!status_update_prepare(&io, line->type))
5829 return FALSE;
5831 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5832 files++;
5834 string_copy(buf, view->ref);
5835 getsyx(cursor_y, cursor_x);
5836 for (file = 0, done = 5; result && file < files; line++, file++) {
5837 int almost_done = file * 100 / files;
5839 if (almost_done > done) {
5840 done = almost_done;
5841 string_format(view->ref, "updating file %u of %u (%d%% done)",
5842 file, files, done);
5843 update_view_title(view);
5844 setsyx(cursor_y, cursor_x);
5845 doupdate();
5847 result = status_update_write(&io, line->data, line->type);
5849 string_copy(view->ref, buf);
5851 return done_io(&io) && result;
5854 static bool
5855 status_update(struct view *view)
5857 struct line *line = &view->line[view->lineno];
5859 assert(view->lines);
5861 if (!line->data) {
5862 /* This should work even for the "On branch" line. */
5863 if (line < view->line + view->lines && !line[1].data) {
5864 report("Nothing to update");
5865 return FALSE;
5868 if (!status_update_files(view, line + 1)) {
5869 report("Failed to update file status");
5870 return FALSE;
5873 } else if (!status_update_file(line->data, line->type)) {
5874 report("Failed to update file status");
5875 return FALSE;
5878 return TRUE;
5881 static bool
5882 status_revert(struct status *status, enum line_type type, bool has_none)
5884 if (!status || type != LINE_STAT_UNSTAGED) {
5885 if (type == LINE_STAT_STAGED) {
5886 report("Cannot revert changes to staged files");
5887 } else if (type == LINE_STAT_UNTRACKED) {
5888 report("Cannot revert changes to untracked files");
5889 } else if (has_none) {
5890 report("Nothing to revert");
5891 } else {
5892 report("Cannot revert changes to multiple files");
5895 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5896 char mode[10] = "100644";
5897 const char *reset_argv[] = {
5898 "git", "update-index", "--cacheinfo", mode,
5899 status->old.rev, status->old.name, NULL
5901 const char *checkout_argv[] = {
5902 "git", "checkout", "--", status->old.name, NULL
5905 if (status->status == 'U') {
5906 string_format(mode, "%5o", status->old.mode);
5908 if (status->old.mode == 0 && status->new.mode == 0) {
5909 reset_argv[2] = "--force-remove";
5910 reset_argv[3] = status->old.name;
5911 reset_argv[4] = NULL;
5914 if (!run_io_fg(reset_argv, opt_cdup))
5915 return FALSE;
5916 if (status->old.mode == 0 && status->new.mode == 0)
5917 return TRUE;
5920 return run_io_fg(checkout_argv, opt_cdup);
5923 return FALSE;
5926 static enum request
5927 status_request(struct view *view, enum request request, struct line *line)
5929 struct status *status = line->data;
5931 switch (request) {
5932 case REQ_STATUS_UPDATE:
5933 if (!status_update(view))
5934 return REQ_NONE;
5935 break;
5937 case REQ_STATUS_REVERT:
5938 if (!status_revert(status, line->type, status_has_none(view, line)))
5939 return REQ_NONE;
5940 break;
5942 case REQ_STATUS_MERGE:
5943 if (!status || status->status != 'U') {
5944 report("Merging only possible for files with unmerged status ('U').");
5945 return REQ_NONE;
5947 open_mergetool(status->new.name);
5948 break;
5950 case REQ_EDIT:
5951 if (!status)
5952 return request;
5953 if (status->status == 'D') {
5954 report("File has been deleted.");
5955 return REQ_NONE;
5958 open_editor(status->new.name);
5959 break;
5961 case REQ_VIEW_BLAME:
5962 if (status)
5963 opt_ref[0] = 0;
5964 return request;
5966 case REQ_ENTER:
5967 /* After returning the status view has been split to
5968 * show the stage view. No further reloading is
5969 * necessary. */
5970 return status_enter(view, line);
5972 case REQ_REFRESH:
5973 /* Simply reload the view. */
5974 break;
5976 default:
5977 return request;
5980 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5982 return REQ_NONE;
5985 static void
5986 status_select(struct view *view, struct line *line)
5988 struct status *status = line->data;
5989 char file[SIZEOF_STR] = "all files";
5990 const char *text;
5991 const char *key;
5993 if (status && !string_format(file, "'%s'", status->new.name))
5994 return;
5996 if (!status && line[1].type == LINE_STAT_NONE)
5997 line++;
5999 switch (line->type) {
6000 case LINE_STAT_STAGED:
6001 text = "Press %s to unstage %s for commit";
6002 break;
6004 case LINE_STAT_UNSTAGED:
6005 text = "Press %s to stage %s for commit";
6006 break;
6008 case LINE_STAT_UNTRACKED:
6009 text = "Press %s to stage %s for addition";
6010 break;
6012 case LINE_STAT_HEAD:
6013 case LINE_STAT_NONE:
6014 text = "Nothing to update";
6015 break;
6017 default:
6018 die("line type %d not handled in switch", line->type);
6021 if (status && status->status == 'U') {
6022 text = "Press %s to resolve conflict in %s";
6023 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6025 } else {
6026 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6029 string_format(view->ref, text, key, file);
6030 if (status)
6031 string_copy(opt_file, status->new.name);
6034 static bool
6035 status_grep(struct view *view, struct line *line)
6037 struct status *status = line->data;
6039 if (status) {
6040 const char buf[2] = { status->status, 0 };
6041 const char *text[] = { status->new.name, buf, NULL };
6043 return grep_text(view, text);
6046 return FALSE;
6049 static struct view_ops status_ops = {
6050 "file",
6051 NULL,
6052 status_open,
6053 NULL,
6054 status_draw,
6055 status_request,
6056 status_grep,
6057 status_select,
6061 static bool
6062 stage_diff_write(struct io *io, struct line *line, struct line *end)
6064 while (line < end) {
6065 if (!io_write(io, line->data, strlen(line->data)) ||
6066 !io_write(io, "\n", 1))
6067 return FALSE;
6068 line++;
6069 if (line->type == LINE_DIFF_CHUNK ||
6070 line->type == LINE_DIFF_HEADER)
6071 break;
6074 return TRUE;
6077 static struct line *
6078 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6080 for (; view->line < line; line--)
6081 if (line->type == type)
6082 return line;
6084 return NULL;
6087 static bool
6088 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6090 const char *apply_argv[SIZEOF_ARG] = {
6091 "git", "apply", "--whitespace=nowarn", NULL
6093 struct line *diff_hdr;
6094 struct io io = {};
6095 int argc = 3;
6097 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6098 if (!diff_hdr)
6099 return FALSE;
6101 if (!revert)
6102 apply_argv[argc++] = "--cached";
6103 if (revert || stage_line_type == LINE_STAT_STAGED)
6104 apply_argv[argc++] = "-R";
6105 apply_argv[argc++] = "-";
6106 apply_argv[argc++] = NULL;
6107 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
6108 return FALSE;
6110 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6111 !stage_diff_write(&io, chunk, view->line + view->lines))
6112 chunk = NULL;
6114 done_io(&io);
6115 run_io_bg(update_index_argv);
6117 return chunk ? TRUE : FALSE;
6120 static bool
6121 stage_update(struct view *view, struct line *line)
6123 struct line *chunk = NULL;
6125 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6126 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6128 if (chunk) {
6129 if (!stage_apply_chunk(view, chunk, FALSE)) {
6130 report("Failed to apply chunk");
6131 return FALSE;
6134 } else if (!stage_status.status) {
6135 view = VIEW(REQ_VIEW_STATUS);
6137 for (line = view->line; line < view->line + view->lines; line++)
6138 if (line->type == stage_line_type)
6139 break;
6141 if (!status_update_files(view, line + 1)) {
6142 report("Failed to update files");
6143 return FALSE;
6146 } else if (!status_update_file(&stage_status, stage_line_type)) {
6147 report("Failed to update file");
6148 return FALSE;
6151 return TRUE;
6154 static bool
6155 stage_revert(struct view *view, struct line *line)
6157 struct line *chunk = NULL;
6159 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6160 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6162 if (chunk) {
6163 if (!prompt_yesno("Are you sure you want to revert changes?"))
6164 return FALSE;
6166 if (!stage_apply_chunk(view, chunk, TRUE)) {
6167 report("Failed to revert chunk");
6168 return FALSE;
6170 return TRUE;
6172 } else {
6173 return status_revert(stage_status.status ? &stage_status : NULL,
6174 stage_line_type, FALSE);
6179 static void
6180 stage_next(struct view *view, struct line *line)
6182 int i;
6184 if (!stage_chunks) {
6185 for (line = view->line; line < view->line + view->lines; line++) {
6186 if (line->type != LINE_DIFF_CHUNK)
6187 continue;
6189 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6190 report("Allocation failure");
6191 return;
6194 stage_chunk[stage_chunks++] = line - view->line;
6198 for (i = 0; i < stage_chunks; i++) {
6199 if (stage_chunk[i] > view->lineno) {
6200 do_scroll_view(view, stage_chunk[i] - view->lineno);
6201 report("Chunk %d of %d", i + 1, stage_chunks);
6202 return;
6206 report("No next chunk found");
6209 static enum request
6210 stage_request(struct view *view, enum request request, struct line *line)
6212 switch (request) {
6213 case REQ_STATUS_UPDATE:
6214 if (!stage_update(view, line))
6215 return REQ_NONE;
6216 break;
6218 case REQ_STATUS_REVERT:
6219 if (!stage_revert(view, line))
6220 return REQ_NONE;
6221 break;
6223 case REQ_STAGE_NEXT:
6224 if (stage_line_type == LINE_STAT_UNTRACKED) {
6225 report("File is untracked; press %s to add",
6226 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6227 return REQ_NONE;
6229 stage_next(view, line);
6230 return REQ_NONE;
6232 case REQ_EDIT:
6233 if (!stage_status.new.name[0])
6234 return request;
6235 if (stage_status.status == 'D') {
6236 report("File has been deleted.");
6237 return REQ_NONE;
6240 open_editor(stage_status.new.name);
6241 break;
6243 case REQ_REFRESH:
6244 /* Reload everything ... */
6245 break;
6247 case REQ_VIEW_BLAME:
6248 if (stage_status.new.name[0]) {
6249 string_copy(opt_file, stage_status.new.name);
6250 opt_ref[0] = 0;
6252 return request;
6254 case REQ_ENTER:
6255 return pager_request(view, request, line);
6257 default:
6258 return request;
6261 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6262 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6264 /* Check whether the staged entry still exists, and close the
6265 * stage view if it doesn't. */
6266 if (!status_exists(&stage_status, stage_line_type)) {
6267 status_restore(VIEW(REQ_VIEW_STATUS));
6268 return REQ_VIEW_CLOSE;
6271 if (stage_line_type == LINE_STAT_UNTRACKED) {
6272 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6273 report("Cannot display a directory");
6274 return REQ_NONE;
6277 if (!prepare_update_file(view, stage_status.new.name)) {
6278 report("Failed to open file: %s", strerror(errno));
6279 return REQ_NONE;
6282 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6284 return REQ_NONE;
6287 static struct view_ops stage_ops = {
6288 "line",
6289 NULL,
6290 NULL,
6291 pager_read,
6292 pager_draw,
6293 stage_request,
6294 pager_grep,
6295 pager_select,
6300 * Revision graph
6303 struct commit {
6304 char id[SIZEOF_REV]; /* SHA1 ID. */
6305 char title[128]; /* First line of the commit message. */
6306 const char *author; /* Author of the commit. */
6307 struct time time; /* Date from the author ident. */
6308 struct ref_list *refs; /* Repository references. */
6309 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6310 size_t graph_size; /* The width of the graph array. */
6311 bool has_parents; /* Rewritten --parents seen. */
6314 /* Size of rev graph with no "padding" columns */
6315 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6317 struct rev_graph {
6318 struct rev_graph *prev, *next, *parents;
6319 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6320 size_t size;
6321 struct commit *commit;
6322 size_t pos;
6323 unsigned int boundary:1;
6326 /* Parents of the commit being visualized. */
6327 static struct rev_graph graph_parents[4];
6329 /* The current stack of revisions on the graph. */
6330 static struct rev_graph graph_stacks[4] = {
6331 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6332 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6333 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6334 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6337 static inline bool
6338 graph_parent_is_merge(struct rev_graph *graph)
6340 return graph->parents->size > 1;
6343 static inline void
6344 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6346 struct commit *commit = graph->commit;
6348 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6349 commit->graph[commit->graph_size++] = symbol;
6352 static void
6353 clear_rev_graph(struct rev_graph *graph)
6355 graph->boundary = 0;
6356 graph->size = graph->pos = 0;
6357 graph->commit = NULL;
6358 memset(graph->parents, 0, sizeof(*graph->parents));
6361 static void
6362 done_rev_graph(struct rev_graph *graph)
6364 if (graph_parent_is_merge(graph) &&
6365 graph->pos < graph->size - 1 &&
6366 graph->next->size == graph->size + graph->parents->size - 1) {
6367 size_t i = graph->pos + graph->parents->size - 1;
6369 graph->commit->graph_size = i * 2;
6370 while (i < graph->next->size - 1) {
6371 append_to_rev_graph(graph, ' ');
6372 append_to_rev_graph(graph, '\\');
6373 i++;
6377 clear_rev_graph(graph);
6380 static void
6381 push_rev_graph(struct rev_graph *graph, const char *parent)
6383 int i;
6385 /* "Collapse" duplicate parents lines.
6387 * FIXME: This needs to also update update the drawn graph but
6388 * for now it just serves as a method for pruning graph lines. */
6389 for (i = 0; i < graph->size; i++)
6390 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6391 return;
6393 if (graph->size < SIZEOF_REVITEMS) {
6394 string_copy_rev(graph->rev[graph->size++], parent);
6398 static chtype
6399 get_rev_graph_symbol(struct rev_graph *graph)
6401 chtype symbol;
6403 if (graph->boundary)
6404 symbol = REVGRAPH_BOUND;
6405 else if (graph->parents->size == 0)
6406 symbol = REVGRAPH_INIT;
6407 else if (graph_parent_is_merge(graph))
6408 symbol = REVGRAPH_MERGE;
6409 else if (graph->pos >= graph->size)
6410 symbol = REVGRAPH_BRANCH;
6411 else
6412 symbol = REVGRAPH_COMMIT;
6414 return symbol;
6417 static void
6418 draw_rev_graph(struct rev_graph *graph)
6420 struct rev_filler {
6421 chtype separator, line;
6423 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6424 static struct rev_filler fillers[] = {
6425 { ' ', '|' },
6426 { '`', '.' },
6427 { '\'', ' ' },
6428 { '/', ' ' },
6430 chtype symbol = get_rev_graph_symbol(graph);
6431 struct rev_filler *filler;
6432 size_t i;
6434 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6435 filler = &fillers[DEFAULT];
6437 for (i = 0; i < graph->pos; i++) {
6438 append_to_rev_graph(graph, filler->line);
6439 if (graph_parent_is_merge(graph->prev) &&
6440 graph->prev->pos == i)
6441 filler = &fillers[RSHARP];
6443 append_to_rev_graph(graph, filler->separator);
6446 /* Place the symbol for this revision. */
6447 append_to_rev_graph(graph, symbol);
6449 if (graph->prev->size > graph->size)
6450 filler = &fillers[RDIAG];
6451 else
6452 filler = &fillers[DEFAULT];
6454 i++;
6456 for (; i < graph->size; i++) {
6457 append_to_rev_graph(graph, filler->separator);
6458 append_to_rev_graph(graph, filler->line);
6459 if (graph_parent_is_merge(graph->prev) &&
6460 i < graph->prev->pos + graph->parents->size)
6461 filler = &fillers[RSHARP];
6462 if (graph->prev->size > graph->size)
6463 filler = &fillers[LDIAG];
6466 if (graph->prev->size > graph->size) {
6467 append_to_rev_graph(graph, filler->separator);
6468 if (filler->line != ' ')
6469 append_to_rev_graph(graph, filler->line);
6473 /* Prepare the next rev graph */
6474 static void
6475 prepare_rev_graph(struct rev_graph *graph)
6477 size_t i;
6479 /* First, traverse all lines of revisions up to the active one. */
6480 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6481 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6482 break;
6484 push_rev_graph(graph->next, graph->rev[graph->pos]);
6487 /* Interleave the new revision parent(s). */
6488 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6489 push_rev_graph(graph->next, graph->parents->rev[i]);
6491 /* Lastly, put any remaining revisions. */
6492 for (i = graph->pos + 1; i < graph->size; i++)
6493 push_rev_graph(graph->next, graph->rev[i]);
6496 static void
6497 update_rev_graph(struct view *view, struct rev_graph *graph)
6499 /* If this is the finalizing update ... */
6500 if (graph->commit)
6501 prepare_rev_graph(graph);
6503 /* Graph visualization needs a one rev look-ahead,
6504 * so the first update doesn't visualize anything. */
6505 if (!graph->prev->commit)
6506 return;
6508 if (view->lines > 2)
6509 view->line[view->lines - 3].dirty = 1;
6510 if (view->lines > 1)
6511 view->line[view->lines - 2].dirty = 1;
6512 draw_rev_graph(graph->prev);
6513 done_rev_graph(graph->prev->prev);
6518 * Main view backend
6521 static const char *main_argv[SIZEOF_ARG] = {
6522 "git", "log", "--no-color", "--pretty=raw", "--parents",
6523 "--topo-order", "%(head)", NULL
6526 static bool
6527 main_draw(struct view *view, struct line *line, unsigned int lineno)
6529 struct commit *commit = line->data;
6531 if (!commit->author)
6532 return FALSE;
6534 if (opt_date && draw_date(view, &commit->time))
6535 return TRUE;
6537 if (opt_author && draw_author(view, commit->author))
6538 return TRUE;
6540 if (opt_rev_graph && commit->graph_size &&
6541 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6542 return TRUE;
6544 if (opt_show_refs && commit->refs) {
6545 size_t i;
6547 for (i = 0; i < commit->refs->size; i++) {
6548 struct ref *ref = commit->refs->refs[i];
6549 enum line_type type;
6551 if (ref->head)
6552 type = LINE_MAIN_HEAD;
6553 else if (ref->ltag)
6554 type = LINE_MAIN_LOCAL_TAG;
6555 else if (ref->tag)
6556 type = LINE_MAIN_TAG;
6557 else if (ref->tracked)
6558 type = LINE_MAIN_TRACKED;
6559 else if (ref->remote)
6560 type = LINE_MAIN_REMOTE;
6561 else
6562 type = LINE_MAIN_REF;
6564 if (draw_text(view, type, "[", TRUE) ||
6565 draw_text(view, type, ref->name, TRUE) ||
6566 draw_text(view, type, "]", TRUE))
6567 return TRUE;
6569 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6570 return TRUE;
6574 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6575 return TRUE;
6578 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6579 static bool
6580 main_read(struct view *view, char *line)
6582 static struct rev_graph *graph = graph_stacks;
6583 enum line_type type;
6584 struct commit *commit;
6586 if (!line) {
6587 int i;
6589 if (!view->lines && !view->parent)
6590 die("No revisions match the given arguments.");
6591 if (view->lines > 0) {
6592 commit = view->line[view->lines - 1].data;
6593 view->line[view->lines - 1].dirty = 1;
6594 if (!commit->author) {
6595 view->lines--;
6596 free(commit);
6597 graph->commit = NULL;
6600 update_rev_graph(view, graph);
6602 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6603 clear_rev_graph(&graph_stacks[i]);
6604 return TRUE;
6607 type = get_line_type(line);
6608 if (type == LINE_COMMIT) {
6609 commit = calloc(1, sizeof(struct commit));
6610 if (!commit)
6611 return FALSE;
6613 line += STRING_SIZE("commit ");
6614 if (*line == '-') {
6615 graph->boundary = 1;
6616 line++;
6619 string_copy_rev(commit->id, line);
6620 commit->refs = get_ref_list(commit->id);
6621 graph->commit = commit;
6622 add_line_data(view, commit, LINE_MAIN_COMMIT);
6624 while ((line = strchr(line, ' '))) {
6625 line++;
6626 push_rev_graph(graph->parents, line);
6627 commit->has_parents = TRUE;
6629 return TRUE;
6632 if (!view->lines)
6633 return TRUE;
6634 commit = view->line[view->lines - 1].data;
6636 switch (type) {
6637 case LINE_PARENT:
6638 if (commit->has_parents)
6639 break;
6640 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6641 break;
6643 case LINE_AUTHOR:
6644 parse_author_line(line + STRING_SIZE("author "),
6645 &commit->author, &commit->time);
6646 update_rev_graph(view, graph);
6647 graph = graph->next;
6648 break;
6650 default:
6651 /* Fill in the commit title if it has not already been set. */
6652 if (commit->title[0])
6653 break;
6655 /* Require titles to start with a non-space character at the
6656 * offset used by git log. */
6657 if (strncmp(line, " ", 4))
6658 break;
6659 line += 4;
6660 /* Well, if the title starts with a whitespace character,
6661 * try to be forgiving. Otherwise we end up with no title. */
6662 while (isspace(*line))
6663 line++;
6664 if (*line == '\0')
6665 break;
6666 /* FIXME: More graceful handling of titles; append "..." to
6667 * shortened titles, etc. */
6669 string_expand(commit->title, sizeof(commit->title), line, 1);
6670 view->line[view->lines - 1].dirty = 1;
6673 return TRUE;
6676 static enum request
6677 main_request(struct view *view, enum request request, struct line *line)
6679 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6681 switch (request) {
6682 case REQ_ENTER:
6683 open_view(view, REQ_VIEW_DIFF, flags);
6684 break;
6685 case REQ_REFRESH:
6686 load_refs();
6687 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6688 break;
6689 default:
6690 return request;
6693 return REQ_NONE;
6696 static bool
6697 grep_refs(struct ref_list *list, regex_t *regex)
6699 regmatch_t pmatch;
6700 size_t i;
6702 if (!opt_show_refs || !list)
6703 return FALSE;
6705 for (i = 0; i < list->size; i++) {
6706 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6707 return TRUE;
6710 return FALSE;
6713 static bool
6714 main_grep(struct view *view, struct line *line)
6716 struct commit *commit = line->data;
6717 const char *text[] = {
6718 commit->title,
6719 opt_author ? commit->author : "",
6720 mkdate(&commit->time, opt_date),
6721 NULL
6724 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6727 static void
6728 main_select(struct view *view, struct line *line)
6730 struct commit *commit = line->data;
6732 string_copy_rev(view->ref, commit->id);
6733 string_copy_rev(ref_commit, view->ref);
6736 static struct view_ops main_ops = {
6737 "commit",
6738 main_argv,
6739 NULL,
6740 main_read,
6741 main_draw,
6742 main_request,
6743 main_grep,
6744 main_select,
6749 * Unicode / UTF-8 handling
6751 * NOTE: Much of the following code for dealing with Unicode is derived from
6752 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6753 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6756 static inline int
6757 unicode_width(unsigned long c, int tab_size)
6759 if (c >= 0x1100 &&
6760 (c <= 0x115f /* Hangul Jamo */
6761 || c == 0x2329
6762 || c == 0x232a
6763 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6764 /* CJK ... Yi */
6765 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6766 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6767 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6768 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6769 || (c >= 0xffe0 && c <= 0xffe6)
6770 || (c >= 0x20000 && c <= 0x2fffd)
6771 || (c >= 0x30000 && c <= 0x3fffd)))
6772 return 2;
6774 if (c == '\t')
6775 return tab_size;
6777 return 1;
6780 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6781 * Illegal bytes are set one. */
6782 static const unsigned char utf8_bytes[256] = {
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 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,
6788 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6789 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,
6790 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,
6793 static inline unsigned char
6794 utf8_char_length(const char *string, const char *end)
6796 int c = *(unsigned char *) string;
6798 return utf8_bytes[c];
6801 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6802 static inline unsigned long
6803 utf8_to_unicode(const char *string, size_t length)
6805 unsigned long unicode;
6807 switch (length) {
6808 case 1:
6809 unicode = string[0];
6810 break;
6811 case 2:
6812 unicode = (string[0] & 0x1f) << 6;
6813 unicode += (string[1] & 0x3f);
6814 break;
6815 case 3:
6816 unicode = (string[0] & 0x0f) << 12;
6817 unicode += ((string[1] & 0x3f) << 6);
6818 unicode += (string[2] & 0x3f);
6819 break;
6820 case 4:
6821 unicode = (string[0] & 0x0f) << 18;
6822 unicode += ((string[1] & 0x3f) << 12);
6823 unicode += ((string[2] & 0x3f) << 6);
6824 unicode += (string[3] & 0x3f);
6825 break;
6826 case 5:
6827 unicode = (string[0] & 0x0f) << 24;
6828 unicode += ((string[1] & 0x3f) << 18);
6829 unicode += ((string[2] & 0x3f) << 12);
6830 unicode += ((string[3] & 0x3f) << 6);
6831 unicode += (string[4] & 0x3f);
6832 break;
6833 case 6:
6834 unicode = (string[0] & 0x01) << 30;
6835 unicode += ((string[1] & 0x3f) << 24);
6836 unicode += ((string[2] & 0x3f) << 18);
6837 unicode += ((string[3] & 0x3f) << 12);
6838 unicode += ((string[4] & 0x3f) << 6);
6839 unicode += (string[5] & 0x3f);
6840 break;
6841 default:
6842 die("Invalid Unicode length");
6845 /* Invalid characters could return the special 0xfffd value but NUL
6846 * should be just as good. */
6847 return unicode > 0xffff ? 0 : unicode;
6850 /* Calculates how much of string can be shown within the given maximum width
6851 * and sets trimmed parameter to non-zero value if all of string could not be
6852 * shown. If the reserve flag is TRUE, it will reserve at least one
6853 * trailing character, which can be useful when drawing a delimiter.
6855 * Returns the number of bytes to output from string to satisfy max_width. */
6856 static size_t
6857 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6859 const char *string = *start;
6860 const char *end = strchr(string, '\0');
6861 unsigned char last_bytes = 0;
6862 size_t last_ucwidth = 0;
6864 *width = 0;
6865 *trimmed = 0;
6867 while (string < end) {
6868 unsigned char bytes = utf8_char_length(string, end);
6869 size_t ucwidth;
6870 unsigned long unicode;
6872 if (string + bytes > end)
6873 break;
6875 /* Change representation to figure out whether
6876 * it is a single- or double-width character. */
6878 unicode = utf8_to_unicode(string, bytes);
6879 /* FIXME: Graceful handling of invalid Unicode character. */
6880 if (!unicode)
6881 break;
6883 ucwidth = unicode_width(unicode, tab_size);
6884 if (skip > 0) {
6885 skip -= ucwidth <= skip ? ucwidth : skip;
6886 *start += bytes;
6888 *width += ucwidth;
6889 if (*width > max_width) {
6890 *trimmed = 1;
6891 *width -= ucwidth;
6892 if (reserve && *width == max_width) {
6893 string -= last_bytes;
6894 *width -= last_ucwidth;
6896 break;
6899 string += bytes;
6900 last_bytes = ucwidth ? bytes : 0;
6901 last_ucwidth = ucwidth;
6904 return string - *start;
6909 * Status management
6912 /* Whether or not the curses interface has been initialized. */
6913 static bool cursed = FALSE;
6915 /* Terminal hacks and workarounds. */
6916 static bool use_scroll_redrawwin;
6917 static bool use_scroll_status_wclear;
6919 /* The status window is used for polling keystrokes. */
6920 static WINDOW *status_win;
6922 /* Reading from the prompt? */
6923 static bool input_mode = FALSE;
6925 static bool status_empty = FALSE;
6927 /* Update status and title window. */
6928 static void
6929 report(const char *msg, ...)
6931 struct view *view = display[current_view];
6933 if (input_mode)
6934 return;
6936 if (!view) {
6937 char buf[SIZEOF_STR];
6938 va_list args;
6940 va_start(args, msg);
6941 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6942 buf[sizeof(buf) - 1] = 0;
6943 buf[sizeof(buf) - 2] = '.';
6944 buf[sizeof(buf) - 3] = '.';
6945 buf[sizeof(buf) - 4] = '.';
6947 va_end(args);
6948 die("%s", buf);
6951 if (!status_empty || *msg) {
6952 va_list args;
6954 va_start(args, msg);
6956 wmove(status_win, 0, 0);
6957 if (view->has_scrolled && use_scroll_status_wclear)
6958 wclear(status_win);
6959 if (*msg) {
6960 vwprintw(status_win, msg, args);
6961 status_empty = FALSE;
6962 } else {
6963 status_empty = TRUE;
6965 wclrtoeol(status_win);
6966 wnoutrefresh(status_win);
6968 va_end(args);
6971 update_view_title(view);
6974 static void
6975 init_display(void)
6977 const char *term;
6978 int x, y;
6980 /* Initialize the curses library */
6981 if (isatty(STDIN_FILENO)) {
6982 cursed = !!initscr();
6983 opt_tty = stdin;
6984 } else {
6985 /* Leave stdin and stdout alone when acting as a pager. */
6986 opt_tty = fopen("/dev/tty", "r+");
6987 if (!opt_tty)
6988 die("Failed to open /dev/tty");
6989 cursed = !!newterm(NULL, opt_tty, opt_tty);
6992 if (!cursed)
6993 die("Failed to initialize curses");
6995 nonl(); /* Disable conversion and detect newlines from input. */
6996 cbreak(); /* Take input chars one at a time, no wait for \n */
6997 noecho(); /* Don't echo input */
6998 leaveok(stdscr, FALSE);
7000 if (has_colors())
7001 init_colors();
7003 getmaxyx(stdscr, y, x);
7004 status_win = newwin(1, 0, y - 1, 0);
7005 if (!status_win)
7006 die("Failed to create status window");
7008 /* Enable keyboard mapping */
7009 keypad(status_win, TRUE);
7010 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7012 TABSIZE = opt_tab_size;
7014 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7015 if (term && !strcmp(term, "gnome-terminal")) {
7016 /* In the gnome-terminal-emulator, the message from
7017 * scrolling up one line when impossible followed by
7018 * scrolling down one line causes corruption of the
7019 * status line. This is fixed by calling wclear. */
7020 use_scroll_status_wclear = TRUE;
7021 use_scroll_redrawwin = FALSE;
7023 } else if (term && !strcmp(term, "xrvt-xpm")) {
7024 /* No problems with full optimizations in xrvt-(unicode)
7025 * and aterm. */
7026 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7028 } else {
7029 /* When scrolling in (u)xterm the last line in the
7030 * scrolling direction will update slowly. */
7031 use_scroll_redrawwin = TRUE;
7032 use_scroll_status_wclear = FALSE;
7036 static int
7037 get_input(int prompt_position)
7039 struct view *view;
7040 int i, key, cursor_y, cursor_x;
7041 bool loading = FALSE;
7043 if (prompt_position)
7044 input_mode = TRUE;
7046 while (TRUE) {
7047 foreach_view (view, i) {
7048 update_view(view);
7049 if (view_is_displayed(view) && view->has_scrolled &&
7050 use_scroll_redrawwin)
7051 redrawwin(view->win);
7052 view->has_scrolled = FALSE;
7053 if (view->pipe)
7054 loading = TRUE;
7057 /* Update the cursor position. */
7058 if (prompt_position) {
7059 getbegyx(status_win, cursor_y, cursor_x);
7060 cursor_x = prompt_position;
7061 } else {
7062 view = display[current_view];
7063 getbegyx(view->win, cursor_y, cursor_x);
7064 cursor_x = view->width - 1;
7065 cursor_y += view->lineno - view->offset;
7067 setsyx(cursor_y, cursor_x);
7069 /* Refresh, accept single keystroke of input */
7070 doupdate();
7071 nodelay(status_win, loading);
7072 key = wgetch(status_win);
7074 /* wgetch() with nodelay() enabled returns ERR when
7075 * there's no input. */
7076 if (key == ERR) {
7078 } else if (key == KEY_RESIZE) {
7079 int height, width;
7081 getmaxyx(stdscr, height, width);
7083 wresize(status_win, 1, width);
7084 mvwin(status_win, height - 1, 0);
7085 wnoutrefresh(status_win);
7086 resize_display();
7087 redraw_display(TRUE);
7089 } else {
7090 input_mode = FALSE;
7091 return key;
7096 static char *
7097 prompt_input(const char *prompt, input_handler handler, void *data)
7099 enum input_status status = INPUT_OK;
7100 static char buf[SIZEOF_STR];
7101 size_t pos = 0;
7103 buf[pos] = 0;
7105 while (status == INPUT_OK || status == INPUT_SKIP) {
7106 int key;
7108 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7109 wclrtoeol(status_win);
7111 key = get_input(pos + 1);
7112 switch (key) {
7113 case KEY_RETURN:
7114 case KEY_ENTER:
7115 case '\n':
7116 status = pos ? INPUT_STOP : INPUT_CANCEL;
7117 break;
7119 case KEY_BACKSPACE:
7120 if (pos > 0)
7121 buf[--pos] = 0;
7122 else
7123 status = INPUT_CANCEL;
7124 break;
7126 case KEY_ESC:
7127 status = INPUT_CANCEL;
7128 break;
7130 default:
7131 if (pos >= sizeof(buf)) {
7132 report("Input string too long");
7133 return NULL;
7136 status = handler(data, buf, key);
7137 if (status == INPUT_OK)
7138 buf[pos++] = (char) key;
7142 /* Clear the status window */
7143 status_empty = FALSE;
7144 report("");
7146 if (status == INPUT_CANCEL)
7147 return NULL;
7149 buf[pos++] = 0;
7151 return buf;
7154 static enum input_status
7155 prompt_yesno_handler(void *data, char *buf, int c)
7157 if (c == 'y' || c == 'Y')
7158 return INPUT_STOP;
7159 if (c == 'n' || c == 'N')
7160 return INPUT_CANCEL;
7161 return INPUT_SKIP;
7164 static bool
7165 prompt_yesno(const char *prompt)
7167 char prompt2[SIZEOF_STR];
7169 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7170 return FALSE;
7172 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7175 static enum input_status
7176 read_prompt_handler(void *data, char *buf, int c)
7178 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7181 static char *
7182 read_prompt(const char *prompt)
7184 return prompt_input(prompt, read_prompt_handler, NULL);
7187 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7189 enum input_status status = INPUT_OK;
7190 int size = 0;
7192 while (items[size].text)
7193 size++;
7195 while (status == INPUT_OK) {
7196 const struct menu_item *item = &items[*selected];
7197 int key;
7198 int i;
7200 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7201 prompt, *selected + 1, size);
7202 if (item->hotkey)
7203 wprintw(status_win, "[%c] ", (char) item->hotkey);
7204 wprintw(status_win, "%s", item->text);
7205 wclrtoeol(status_win);
7207 key = get_input(COLS - 1);
7208 switch (key) {
7209 case KEY_RETURN:
7210 case KEY_ENTER:
7211 case '\n':
7212 status = INPUT_STOP;
7213 break;
7215 case KEY_LEFT:
7216 case KEY_UP:
7217 *selected = *selected - 1;
7218 if (*selected < 0)
7219 *selected = size - 1;
7220 break;
7222 case KEY_RIGHT:
7223 case KEY_DOWN:
7224 *selected = (*selected + 1) % size;
7225 break;
7227 case KEY_ESC:
7228 status = INPUT_CANCEL;
7229 break;
7231 default:
7232 for (i = 0; items[i].text; i++)
7233 if (items[i].hotkey == key) {
7234 *selected = i;
7235 status = INPUT_STOP;
7236 break;
7241 /* Clear the status window */
7242 status_empty = FALSE;
7243 report("");
7245 return status != INPUT_CANCEL;
7249 * Repository properties
7252 static struct ref **refs = NULL;
7253 static size_t refs_size = 0;
7254 static struct ref *refs_head = NULL;
7256 static struct ref_list **ref_lists = NULL;
7257 static size_t ref_lists_size = 0;
7259 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7260 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7261 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7263 static int
7264 compare_refs(const void *ref1_, const void *ref2_)
7266 const struct ref *ref1 = *(const struct ref **)ref1_;
7267 const struct ref *ref2 = *(const struct ref **)ref2_;
7269 if (ref1->tag != ref2->tag)
7270 return ref2->tag - ref1->tag;
7271 if (ref1->ltag != ref2->ltag)
7272 return ref2->ltag - ref2->ltag;
7273 if (ref1->head != ref2->head)
7274 return ref2->head - ref1->head;
7275 if (ref1->tracked != ref2->tracked)
7276 return ref2->tracked - ref1->tracked;
7277 if (ref1->remote != ref2->remote)
7278 return ref2->remote - ref1->remote;
7279 return strcmp(ref1->name, ref2->name);
7282 static void
7283 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7285 size_t i;
7287 for (i = 0; i < refs_size; i++)
7288 if (!visitor(data, refs[i]))
7289 break;
7292 static struct ref *
7293 get_ref_head()
7295 return refs_head;
7298 static struct ref_list *
7299 get_ref_list(const char *id)
7301 struct ref_list *list;
7302 size_t i;
7304 for (i = 0; i < ref_lists_size; i++)
7305 if (!strcmp(id, ref_lists[i]->id))
7306 return ref_lists[i];
7308 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7309 return NULL;
7310 list = calloc(1, sizeof(*list));
7311 if (!list)
7312 return NULL;
7314 for (i = 0; i < refs_size; i++) {
7315 if (!strcmp(id, refs[i]->id) &&
7316 realloc_refs_list(&list->refs, list->size, 1))
7317 list->refs[list->size++] = refs[i];
7320 if (!list->refs) {
7321 free(list);
7322 return NULL;
7325 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7326 ref_lists[ref_lists_size++] = list;
7327 return list;
7330 static int
7331 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7333 struct ref *ref = NULL;
7334 bool tag = FALSE;
7335 bool ltag = FALSE;
7336 bool remote = FALSE;
7337 bool tracked = FALSE;
7338 bool head = FALSE;
7339 int from = 0, to = refs_size - 1;
7341 if (!prefixcmp(name, "refs/tags/")) {
7342 if (!suffixcmp(name, namelen, "^{}")) {
7343 namelen -= 3;
7344 name[namelen] = 0;
7345 } else {
7346 ltag = TRUE;
7349 tag = TRUE;
7350 namelen -= STRING_SIZE("refs/tags/");
7351 name += STRING_SIZE("refs/tags/");
7353 } else if (!prefixcmp(name, "refs/remotes/")) {
7354 remote = TRUE;
7355 namelen -= STRING_SIZE("refs/remotes/");
7356 name += STRING_SIZE("refs/remotes/");
7357 tracked = !strcmp(opt_remote, name);
7359 } else if (!prefixcmp(name, "refs/heads/")) {
7360 namelen -= STRING_SIZE("refs/heads/");
7361 name += STRING_SIZE("refs/heads/");
7362 if (!strncmp(opt_head, name, namelen))
7363 return OK;
7365 } else if (!strcmp(name, "HEAD")) {
7366 head = TRUE;
7367 if (*opt_head) {
7368 namelen = strlen(opt_head);
7369 name = opt_head;
7373 /* If we are reloading or it's an annotated tag, replace the
7374 * previous SHA1 with the resolved commit id; relies on the fact
7375 * git-ls-remote lists the commit id of an annotated tag right
7376 * before the commit id it points to. */
7377 while (from <= to) {
7378 size_t pos = (to + from) / 2;
7379 int cmp = strcmp(name, refs[pos]->name);
7381 if (!cmp) {
7382 ref = refs[pos];
7383 break;
7386 if (cmp < 0)
7387 to = pos - 1;
7388 else
7389 from = pos + 1;
7392 if (!ref) {
7393 if (!realloc_refs(&refs, refs_size, 1))
7394 return ERR;
7395 ref = calloc(1, sizeof(*ref) + namelen);
7396 if (!ref)
7397 return ERR;
7398 memmove(refs + from + 1, refs + from,
7399 (refs_size - from) * sizeof(*refs));
7400 refs[from] = ref;
7401 strncpy(ref->name, name, namelen);
7402 refs_size++;
7405 ref->head = head;
7406 ref->tag = tag;
7407 ref->ltag = ltag;
7408 ref->remote = remote;
7409 ref->tracked = tracked;
7410 string_copy_rev(ref->id, id);
7412 if (head)
7413 refs_head = ref;
7414 return OK;
7417 static int
7418 load_refs(void)
7420 const char *head_argv[] = {
7421 "git", "symbolic-ref", "HEAD", NULL
7423 static const char *ls_remote_argv[SIZEOF_ARG] = {
7424 "git", "ls-remote", opt_git_dir, NULL
7426 static bool init = FALSE;
7427 size_t i;
7429 if (!init) {
7430 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7431 init = TRUE;
7434 if (!*opt_git_dir)
7435 return OK;
7437 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
7438 !prefixcmp(opt_head, "refs/heads/")) {
7439 char *offset = opt_head + STRING_SIZE("refs/heads/");
7441 memmove(opt_head, offset, strlen(offset) + 1);
7444 refs_head = NULL;
7445 for (i = 0; i < refs_size; i++)
7446 refs[i]->id[0] = 0;
7448 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
7449 return ERR;
7451 /* Update the ref lists to reflect changes. */
7452 for (i = 0; i < ref_lists_size; i++) {
7453 struct ref_list *list = ref_lists[i];
7454 size_t old, new;
7456 for (old = new = 0; old < list->size; old++)
7457 if (!strcmp(list->id, list->refs[old]->id))
7458 list->refs[new++] = list->refs[old];
7459 list->size = new;
7462 return OK;
7465 static void
7466 set_remote_branch(const char *name, const char *value, size_t valuelen)
7468 if (!strcmp(name, ".remote")) {
7469 string_ncopy(opt_remote, value, valuelen);
7471 } else if (*opt_remote && !strcmp(name, ".merge")) {
7472 size_t from = strlen(opt_remote);
7474 if (!prefixcmp(value, "refs/heads/"))
7475 value += STRING_SIZE("refs/heads/");
7477 if (!string_format_from(opt_remote, &from, "/%s", value))
7478 opt_remote[0] = 0;
7482 static void
7483 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7485 const char *argv[SIZEOF_ARG] = { name, "=" };
7486 int argc = 1 + (cmd == option_set_command);
7487 int error = ERR;
7489 if (!argv_from_string(argv, &argc, value))
7490 config_msg = "Too many option arguments";
7491 else
7492 error = cmd(argc, argv);
7494 if (error == ERR)
7495 warn("Option 'tig.%s': %s", name, config_msg);
7498 static bool
7499 set_environment_variable(const char *name, const char *value)
7501 size_t len = strlen(name) + 1 + strlen(value) + 1;
7502 char *env = malloc(len);
7504 if (env &&
7505 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7506 putenv(env) == 0)
7507 return TRUE;
7508 free(env);
7509 return FALSE;
7512 static void
7513 set_work_tree(const char *value)
7515 char cwd[SIZEOF_STR];
7517 if (!getcwd(cwd, sizeof(cwd)))
7518 die("Failed to get cwd path: %s", strerror(errno));
7519 if (chdir(opt_git_dir) < 0)
7520 die("Failed to chdir(%s): %s", strerror(errno));
7521 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7522 die("Failed to get git path: %s", strerror(errno));
7523 if (chdir(cwd) < 0)
7524 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7525 if (chdir(value) < 0)
7526 die("Failed to chdir(%s): %s", value, strerror(errno));
7527 if (!getcwd(cwd, sizeof(cwd)))
7528 die("Failed to get cwd path: %s", strerror(errno));
7529 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7530 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7531 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7532 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7533 opt_is_inside_work_tree = TRUE;
7536 static int
7537 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7539 if (!strcmp(name, "i18n.commitencoding"))
7540 string_ncopy(opt_encoding, value, valuelen);
7542 else if (!strcmp(name, "core.editor"))
7543 string_ncopy(opt_editor, value, valuelen);
7545 else if (!strcmp(name, "core.worktree"))
7546 set_work_tree(value);
7548 else if (!prefixcmp(name, "tig.color."))
7549 set_repo_config_option(name + 10, value, option_color_command);
7551 else if (!prefixcmp(name, "tig.bind."))
7552 set_repo_config_option(name + 9, value, option_bind_command);
7554 else if (!prefixcmp(name, "tig."))
7555 set_repo_config_option(name + 4, value, option_set_command);
7557 else if (*opt_head && !prefixcmp(name, "branch.") &&
7558 !strncmp(name + 7, opt_head, strlen(opt_head)))
7559 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7561 return OK;
7564 static int
7565 load_git_config(void)
7567 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7569 return run_io_load(config_list_argv, "=", read_repo_config_option);
7572 static int
7573 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7575 if (!opt_git_dir[0]) {
7576 string_ncopy(opt_git_dir, name, namelen);
7578 } else if (opt_is_inside_work_tree == -1) {
7579 /* This can be 3 different values depending on the
7580 * version of git being used. If git-rev-parse does not
7581 * understand --is-inside-work-tree it will simply echo
7582 * the option else either "true" or "false" is printed.
7583 * Default to true for the unknown case. */
7584 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7586 } else if (*name == '.') {
7587 string_ncopy(opt_cdup, name, namelen);
7589 } else {
7590 string_ncopy(opt_prefix, name, namelen);
7593 return OK;
7596 static int
7597 load_repo_info(void)
7599 const char *rev_parse_argv[] = {
7600 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7601 "--show-cdup", "--show-prefix", NULL
7604 return run_io_load(rev_parse_argv, "=", read_repo_info);
7609 * Main
7612 static const char usage[] =
7613 "tig " TIG_VERSION " (" __DATE__ ")\n"
7614 "\n"
7615 "Usage: tig [options] [revs] [--] [paths]\n"
7616 " or: tig show [options] [revs] [--] [paths]\n"
7617 " or: tig blame [rev] path\n"
7618 " or: tig status\n"
7619 " or: tig < [git command output]\n"
7620 "\n"
7621 "Options:\n"
7622 " -v, --version Show version and exit\n"
7623 " -h, --help Show help message and exit";
7625 static void __NORETURN
7626 quit(int sig)
7628 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7629 if (cursed)
7630 endwin();
7631 exit(0);
7634 static void __NORETURN
7635 die(const char *err, ...)
7637 va_list args;
7639 endwin();
7641 va_start(args, err);
7642 fputs("tig: ", stderr);
7643 vfprintf(stderr, err, args);
7644 fputs("\n", stderr);
7645 va_end(args);
7647 exit(1);
7650 static void
7651 warn(const char *msg, ...)
7653 va_list args;
7655 va_start(args, msg);
7656 fputs("tig warning: ", stderr);
7657 vfprintf(stderr, msg, args);
7658 fputs("\n", stderr);
7659 va_end(args);
7662 static enum request
7663 parse_options(int argc, const char *argv[])
7665 enum request request = REQ_VIEW_MAIN;
7666 const char *subcommand;
7667 bool seen_dashdash = FALSE;
7668 /* XXX: This is vulnerable to the user overriding options
7669 * required for the main view parser. */
7670 const char *custom_argv[SIZEOF_ARG] = {
7671 "git", "log", "--no-color", "--pretty=raw", "--parents",
7672 "--topo-order", NULL
7674 int i, j = 6;
7676 if (!isatty(STDIN_FILENO)) {
7677 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7678 return REQ_VIEW_PAGER;
7681 if (argc <= 1)
7682 return REQ_NONE;
7684 subcommand = argv[1];
7685 if (!strcmp(subcommand, "status")) {
7686 if (argc > 2)
7687 warn("ignoring arguments after `%s'", subcommand);
7688 return REQ_VIEW_STATUS;
7690 } else if (!strcmp(subcommand, "blame")) {
7691 if (argc <= 2 || argc > 4)
7692 die("invalid number of options to blame\n\n%s", usage);
7694 i = 2;
7695 if (argc == 4) {
7696 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7697 i++;
7700 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7701 return REQ_VIEW_BLAME;
7703 } else if (!strcmp(subcommand, "show")) {
7704 request = REQ_VIEW_DIFF;
7706 } else {
7707 subcommand = NULL;
7710 if (subcommand) {
7711 custom_argv[1] = subcommand;
7712 j = 2;
7715 for (i = 1 + !!subcommand; i < argc; i++) {
7716 const char *opt = argv[i];
7718 if (seen_dashdash || !strcmp(opt, "--")) {
7719 seen_dashdash = TRUE;
7721 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7722 printf("tig version %s\n", TIG_VERSION);
7723 quit(0);
7725 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7726 printf("%s\n", usage);
7727 quit(0);
7730 custom_argv[j++] = opt;
7731 if (j >= ARRAY_SIZE(custom_argv))
7732 die("command too long");
7735 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7736 die("Failed to format arguments");
7738 return request;
7742 main(int argc, const char *argv[])
7744 const char *codeset = "UTF-8";
7745 enum request request = parse_options(argc, argv);
7746 struct view *view;
7747 size_t i;
7749 signal(SIGINT, quit);
7750 signal(SIGPIPE, SIG_IGN);
7752 if (setlocale(LC_ALL, "")) {
7753 codeset = nl_langinfo(CODESET);
7756 if (load_repo_info() == ERR)
7757 die("Failed to load repo info.");
7759 if (load_options() == ERR)
7760 die("Failed to load user config.");
7762 if (load_git_config() == ERR)
7763 die("Failed to load repo config.");
7765 /* Require a git repository unless when running in pager mode. */
7766 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7767 die("Not a git repository");
7769 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7770 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7771 if (opt_iconv_in == ICONV_NONE)
7772 die("Failed to initialize character set conversion");
7775 if (codeset && strcmp(codeset, "UTF-8")) {
7776 opt_iconv_out = iconv_open(codeset, "UTF-8");
7777 if (opt_iconv_out == ICONV_NONE)
7778 die("Failed to initialize character set conversion");
7781 if (load_refs() == ERR)
7782 die("Failed to load refs.");
7784 foreach_view (view, i)
7785 argv_from_env(view->ops->argv, view->cmd_env);
7787 init_display();
7789 if (request != REQ_NONE)
7790 open_view(NULL, request, OPEN_PREPARED);
7791 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7793 while (view_driver(display[current_view], request)) {
7794 int key = get_input(0);
7796 view = display[current_view];
7797 request = get_keybinding(view->keymap, key);
7799 /* Some low-level request handling. This keeps access to
7800 * status_win restricted. */
7801 switch (request) {
7802 case REQ_PROMPT:
7804 char *cmd = read_prompt(":");
7806 if (cmd && isdigit(*cmd)) {
7807 int lineno = view->lineno + 1;
7809 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7810 select_view_line(view, lineno - 1);
7811 report("");
7812 } else {
7813 report("Unable to parse '%s' as a line number", cmd);
7816 } else if (cmd) {
7817 struct view *next = VIEW(REQ_VIEW_PAGER);
7818 const char *argv[SIZEOF_ARG] = { "git" };
7819 int argc = 1;
7821 /* When running random commands, initially show the
7822 * command in the title. However, it maybe later be
7823 * overwritten if a commit line is selected. */
7824 string_ncopy(next->ref, cmd, strlen(cmd));
7826 if (!argv_from_string(argv, &argc, cmd)) {
7827 report("Too many arguments");
7828 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7829 report("Failed to format command");
7830 } else {
7831 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7835 request = REQ_NONE;
7836 break;
7838 case REQ_SEARCH:
7839 case REQ_SEARCH_BACK:
7841 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7842 char *search = read_prompt(prompt);
7844 if (search)
7845 string_ncopy(opt_search, search, strlen(search));
7846 else if (*opt_search)
7847 request = request == REQ_SEARCH ?
7848 REQ_FIND_NEXT :
7849 REQ_FIND_PREV;
7850 else
7851 request = REQ_NONE;
7852 break;
7854 default:
7855 break;
7859 quit(0);
7861 return 0;