io: consolidate formatting into io_format
[tig.git] / tig.c
blobcad66c8e6e20d85fe8f84eb262e007c7e2112dc7
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 io_reset(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 io_init(struct io *io, const char *dir, enum io_type type)
560 io_reset(io);
561 io->type = type;
562 io->dir = dir;
565 static bool
566 io_format(struct io *io, const char *dir, enum io_type type,
567 const char *argv[], enum format_flags flags)
569 io_init(io, dir, type);
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 io_init(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 io_kill(struct io *io)
599 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
602 static bool
603 io_done(struct io *io)
605 pid_t pid = io->pid;
607 if (io->pipe != -1)
608 close(io->pipe);
609 free(io->buf);
610 io_reset(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 io->error = errno;
620 return FALSE;
623 return waiting == pid &&
624 !WIFSIGNALED(status) &&
625 WIFEXITED(status) &&
626 !WEXITSTATUS(status);
629 return TRUE;
632 static bool
633 io_start(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) && pipe(pipefds) < 0) {
641 io->error = errno;
642 return FALSE;
643 } else if (io->type == IO_AP) {
644 pipefds[1] = io->pipe;
647 if ((io->pid = fork())) {
648 if (io->pid == -1)
649 io->error = errno;
650 if (pipefds[!(io->type == IO_WR)] != -1)
651 close(pipefds[!(io->type == IO_WR)]);
652 if (io->pid != -1) {
653 io->pipe = pipefds[!!(io->type == IO_WR)];
654 return TRUE;
657 } else {
658 if (io->type != IO_FG) {
659 int devnull = open("/dev/null", O_RDWR);
660 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
661 int writefd = (io->type == IO_RD || io->type == IO_AP)
662 ? pipefds[1] : devnull;
664 dup2(readfd, STDIN_FILENO);
665 dup2(writefd, STDOUT_FILENO);
666 dup2(devnull, STDERR_FILENO);
668 close(devnull);
669 if (pipefds[0] != -1)
670 close(pipefds[0]);
671 if (pipefds[1] != -1)
672 close(pipefds[1]);
675 if (io->dir && *io->dir && chdir(io->dir) == -1)
676 exit(errno);
678 execvp(io->argv[0], (char *const*) io->argv);
679 exit(errno);
682 if (pipefds[!!(io->type == IO_WR)] != -1)
683 close(pipefds[!!(io->type == IO_WR)]);
684 return FALSE;
687 static bool
688 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
690 io_init(io, dir, type);
691 if (!format_argv(io->argv, argv, FORMAT_NONE))
692 return FALSE;
693 return io_start(io);
696 static int
697 io_complete(struct io *io)
699 return io_start(io) && io_done(io);
702 static int
703 io_run_bg(const char **argv)
705 struct io io = {};
707 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
708 return FALSE;
709 return io_complete(&io);
712 static bool
713 io_run_fg(const char **argv, const char *dir)
715 struct io io = {};
717 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
718 return FALSE;
719 return io_complete(&io);
722 static bool
723 io_run_append(const char **argv, enum format_flags flags, int fd)
725 struct io io = {};
727 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
728 close(fd);
729 return FALSE;
732 io.pipe = fd;
733 return io_complete(&io);
736 static bool
737 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
739 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
742 static bool
743 io_eof(struct io *io)
745 return io->eof;
748 static int
749 io_error(struct io *io)
751 return io->error;
754 static char *
755 io_strerror(struct io *io)
757 return strerror(io->error);
760 static bool
761 io_can_read(struct io *io)
763 struct timeval tv = { 0, 500 };
764 fd_set fds;
766 FD_ZERO(&fds);
767 FD_SET(io->pipe, &fds);
769 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
772 static ssize_t
773 io_read(struct io *io, void *buf, size_t bufsize)
775 do {
776 ssize_t readsize = read(io->pipe, buf, bufsize);
778 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
779 continue;
780 else if (readsize == -1)
781 io->error = errno;
782 else if (readsize == 0)
783 io->eof = 1;
784 return readsize;
785 } while (1);
788 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
790 static char *
791 io_get(struct io *io, int c, bool can_read)
793 char *eol;
794 ssize_t readsize;
796 while (TRUE) {
797 if (io->bufsize > 0) {
798 eol = memchr(io->bufpos, c, io->bufsize);
799 if (eol) {
800 char *line = io->bufpos;
802 *eol = 0;
803 io->bufpos = eol + 1;
804 io->bufsize -= io->bufpos - line;
805 return line;
809 if (io_eof(io)) {
810 if (io->bufsize) {
811 io->bufpos[io->bufsize] = 0;
812 io->bufsize = 0;
813 return io->bufpos;
815 return NULL;
818 if (!can_read)
819 return NULL;
821 if (io->bufsize > 0 && io->bufpos > io->buf)
822 memmove(io->buf, io->bufpos, io->bufsize);
824 if (io->bufalloc == io->bufsize) {
825 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
826 return NULL;
827 io->bufalloc += BUFSIZ;
830 io->bufpos = io->buf;
831 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
832 if (io_error(io))
833 return NULL;
834 io->bufsize += readsize;
838 static bool
839 io_write(struct io *io, const void *buf, size_t bufsize)
841 size_t written = 0;
843 while (!io_error(io) && written < bufsize) {
844 ssize_t size;
846 size = write(io->pipe, buf + written, bufsize - written);
847 if (size < 0 && (errno == EAGAIN || errno == EINTR))
848 continue;
849 else if (size == -1)
850 io->error = errno;
851 else
852 written += size;
855 return written == bufsize;
858 static bool
859 io_read_buf(struct io *io, char buf[], size_t bufsize)
861 char *result = io_get(io, '\n', TRUE);
863 if (result) {
864 result = chomp_string(result);
865 string_ncopy_do(buf, bufsize, result, strlen(result));
868 return io_done(io) && result;
871 static bool
872 io_run_buf(const char **argv, char buf[], size_t bufsize)
874 struct io io = {};
876 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
877 && io_read_buf(&io, buf, bufsize);
880 static int
881 io_load(struct io *io, const char *separators,
882 int (*read_property)(char *, size_t, char *, size_t))
884 char *name;
885 int state = OK;
887 if (!io_start(io))
888 return ERR;
890 while (state == OK && (name = io_get(io, '\n', TRUE))) {
891 char *value;
892 size_t namelen;
893 size_t valuelen;
895 name = chomp_string(name);
896 namelen = strcspn(name, separators);
898 if (name[namelen]) {
899 name[namelen] = 0;
900 value = chomp_string(name + namelen + 1);
901 valuelen = strlen(value);
903 } else {
904 value = "";
905 valuelen = 0;
908 state = read_property(name, namelen, value, valuelen);
911 if (state != ERR && io_error(io))
912 state = ERR;
913 io_done(io);
915 return state;
918 static int
919 io_run_load(const char **argv, const char *separators,
920 int (*read_property)(char *, size_t, char *, size_t))
922 struct io io = {};
924 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
925 ? io_load(&io, separators, read_property) : ERR;
930 * User requests
933 #define REQ_INFO \
934 /* XXX: Keep the view request first and in sync with views[]. */ \
935 REQ_GROUP("View switching") \
936 REQ_(VIEW_MAIN, "Show main view"), \
937 REQ_(VIEW_DIFF, "Show diff view"), \
938 REQ_(VIEW_LOG, "Show log view"), \
939 REQ_(VIEW_TREE, "Show tree view"), \
940 REQ_(VIEW_BLOB, "Show blob view"), \
941 REQ_(VIEW_BLAME, "Show blame view"), \
942 REQ_(VIEW_BRANCH, "Show branch view"), \
943 REQ_(VIEW_HELP, "Show help page"), \
944 REQ_(VIEW_PAGER, "Show pager view"), \
945 REQ_(VIEW_STATUS, "Show status view"), \
946 REQ_(VIEW_STAGE, "Show stage view"), \
948 REQ_GROUP("View manipulation") \
949 REQ_(ENTER, "Enter current line and scroll"), \
950 REQ_(NEXT, "Move to next"), \
951 REQ_(PREVIOUS, "Move to previous"), \
952 REQ_(PARENT, "Move to parent"), \
953 REQ_(VIEW_NEXT, "Move focus to next view"), \
954 REQ_(REFRESH, "Reload and refresh"), \
955 REQ_(MAXIMIZE, "Maximize the current view"), \
956 REQ_(VIEW_CLOSE, "Close the current view"), \
957 REQ_(QUIT, "Close all views and quit"), \
959 REQ_GROUP("View specific requests") \
960 REQ_(STATUS_UPDATE, "Update file status"), \
961 REQ_(STATUS_REVERT, "Revert file changes"), \
962 REQ_(STATUS_MERGE, "Merge file using external tool"), \
963 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
965 REQ_GROUP("Cursor navigation") \
966 REQ_(MOVE_UP, "Move cursor one line up"), \
967 REQ_(MOVE_DOWN, "Move cursor one line down"), \
968 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
969 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
970 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
971 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
973 REQ_GROUP("Scrolling") \
974 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
975 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
976 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
977 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
978 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
979 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
981 REQ_GROUP("Searching") \
982 REQ_(SEARCH, "Search the view"), \
983 REQ_(SEARCH_BACK, "Search backwards in the view"), \
984 REQ_(FIND_NEXT, "Find next search match"), \
985 REQ_(FIND_PREV, "Find previous search match"), \
987 REQ_GROUP("Option manipulation") \
988 REQ_(OPTIONS, "Open option menu"), \
989 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
990 REQ_(TOGGLE_DATE, "Toggle date display"), \
991 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
992 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
993 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
994 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
995 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
996 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
998 REQ_GROUP("Misc") \
999 REQ_(PROMPT, "Bring up the prompt"), \
1000 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1001 REQ_(SHOW_VERSION, "Show version information"), \
1002 REQ_(STOP_LOADING, "Stop all loading views"), \
1003 REQ_(EDIT, "Open in editor"), \
1004 REQ_(NONE, "Do nothing")
1007 /* User action requests. */
1008 enum request {
1009 #define REQ_GROUP(help)
1010 #define REQ_(req, help) REQ_##req
1012 /* Offset all requests to avoid conflicts with ncurses getch values. */
1013 REQ_OFFSET = KEY_MAX + 1,
1014 REQ_INFO
1016 #undef REQ_GROUP
1017 #undef REQ_
1020 struct request_info {
1021 enum request request;
1022 const char *name;
1023 int namelen;
1024 const char *help;
1027 static const struct request_info req_info[] = {
1028 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1029 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1030 REQ_INFO
1031 #undef REQ_GROUP
1032 #undef REQ_
1035 static enum request
1036 get_request(const char *name)
1038 int namelen = strlen(name);
1039 int i;
1041 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1042 if (enum_equals(req_info[i], name, namelen))
1043 return req_info[i].request;
1045 return REQ_NONE;
1050 * Options
1053 /* Option and state variables. */
1054 static enum date opt_date = DATE_DEFAULT;
1055 static enum author opt_author = AUTHOR_DEFAULT;
1056 static bool opt_line_number = FALSE;
1057 static bool opt_line_graphics = TRUE;
1058 static bool opt_rev_graph = FALSE;
1059 static bool opt_show_refs = TRUE;
1060 static int opt_num_interval = 5;
1061 static double opt_hscroll = 0.50;
1062 static double opt_scale_split_view = 2.0 / 3.0;
1063 static int opt_tab_size = 8;
1064 static int opt_author_cols = AUTHOR_COLS;
1065 static char opt_path[SIZEOF_STR] = "";
1066 static char opt_file[SIZEOF_STR] = "";
1067 static char opt_ref[SIZEOF_REF] = "";
1068 static char opt_head[SIZEOF_REF] = "";
1069 static char opt_remote[SIZEOF_REF] = "";
1070 static char opt_encoding[20] = "UTF-8";
1071 static iconv_t opt_iconv_in = ICONV_NONE;
1072 static iconv_t opt_iconv_out = ICONV_NONE;
1073 static char opt_search[SIZEOF_STR] = "";
1074 static char opt_cdup[SIZEOF_STR] = "";
1075 static char opt_prefix[SIZEOF_STR] = "";
1076 static char opt_git_dir[SIZEOF_STR] = "";
1077 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1078 static char opt_editor[SIZEOF_STR] = "";
1079 static FILE *opt_tty = NULL;
1081 #define is_initial_commit() (!get_ref_head())
1082 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1086 * Line-oriented content detection.
1089 #define LINE_INFO \
1090 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1091 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1102 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1103 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1104 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1105 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1106 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1107 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1108 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1109 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1110 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1111 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1112 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1113 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1114 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1115 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1116 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1117 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1118 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1119 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1120 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1121 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1122 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1123 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1124 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1125 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1126 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1127 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1128 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1129 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1130 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1132 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1133 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1134 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1135 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1136 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1137 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1138 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1139 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1140 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1141 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1142 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1143 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1144 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1145 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1146 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1148 enum line_type {
1149 #define LINE(type, line, fg, bg, attr) \
1150 LINE_##type
1151 LINE_INFO,
1152 LINE_NONE
1153 #undef LINE
1156 struct line_info {
1157 const char *name; /* Option name. */
1158 int namelen; /* Size of option name. */
1159 const char *line; /* The start of line to match. */
1160 int linelen; /* Size of string to match. */
1161 int fg, bg, attr; /* Color and text attributes for the lines. */
1164 static struct line_info line_info[] = {
1165 #define LINE(type, line, fg, bg, attr) \
1166 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1167 LINE_INFO
1168 #undef LINE
1171 static enum line_type
1172 get_line_type(const char *line)
1174 int linelen = strlen(line);
1175 enum line_type type;
1177 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1178 /* Case insensitive search matches Signed-off-by lines better. */
1179 if (linelen >= line_info[type].linelen &&
1180 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1181 return type;
1183 return LINE_DEFAULT;
1186 static inline int
1187 get_line_attr(enum line_type type)
1189 assert(type < ARRAY_SIZE(line_info));
1190 return COLOR_PAIR(type) | line_info[type].attr;
1193 static struct line_info *
1194 get_line_info(const char *name)
1196 size_t namelen = strlen(name);
1197 enum line_type type;
1199 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1200 if (enum_equals(line_info[type], name, namelen))
1201 return &line_info[type];
1203 return NULL;
1206 static void
1207 init_colors(void)
1209 int default_bg = line_info[LINE_DEFAULT].bg;
1210 int default_fg = line_info[LINE_DEFAULT].fg;
1211 enum line_type type;
1213 start_color();
1215 if (assume_default_colors(default_fg, default_bg) == ERR) {
1216 default_bg = COLOR_BLACK;
1217 default_fg = COLOR_WHITE;
1220 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1221 struct line_info *info = &line_info[type];
1222 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1223 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1225 init_pair(type, fg, bg);
1229 struct line {
1230 enum line_type type;
1232 /* State flags */
1233 unsigned int selected:1;
1234 unsigned int dirty:1;
1235 unsigned int cleareol:1;
1236 unsigned int other:16;
1238 void *data; /* User data */
1243 * Keys
1246 struct keybinding {
1247 int alias;
1248 enum request request;
1251 static const struct keybinding default_keybindings[] = {
1252 /* View switching */
1253 { 'm', REQ_VIEW_MAIN },
1254 { 'd', REQ_VIEW_DIFF },
1255 { 'l', REQ_VIEW_LOG },
1256 { 't', REQ_VIEW_TREE },
1257 { 'f', REQ_VIEW_BLOB },
1258 { 'B', REQ_VIEW_BLAME },
1259 { 'H', REQ_VIEW_BRANCH },
1260 { 'p', REQ_VIEW_PAGER },
1261 { 'h', REQ_VIEW_HELP },
1262 { 'S', REQ_VIEW_STATUS },
1263 { 'c', REQ_VIEW_STAGE },
1265 /* View manipulation */
1266 { 'q', REQ_VIEW_CLOSE },
1267 { KEY_TAB, REQ_VIEW_NEXT },
1268 { KEY_RETURN, REQ_ENTER },
1269 { KEY_UP, REQ_PREVIOUS },
1270 { KEY_DOWN, REQ_NEXT },
1271 { 'R', REQ_REFRESH },
1272 { KEY_F(5), REQ_REFRESH },
1273 { 'O', REQ_MAXIMIZE },
1275 /* Cursor navigation */
1276 { 'k', REQ_MOVE_UP },
1277 { 'j', REQ_MOVE_DOWN },
1278 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1279 { KEY_END, REQ_MOVE_LAST_LINE },
1280 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1281 { ' ', REQ_MOVE_PAGE_DOWN },
1282 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1283 { 'b', REQ_MOVE_PAGE_UP },
1284 { '-', REQ_MOVE_PAGE_UP },
1286 /* Scrolling */
1287 { KEY_LEFT, REQ_SCROLL_LEFT },
1288 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1289 { KEY_IC, REQ_SCROLL_LINE_UP },
1290 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1291 { 'w', REQ_SCROLL_PAGE_UP },
1292 { 's', REQ_SCROLL_PAGE_DOWN },
1294 /* Searching */
1295 { '/', REQ_SEARCH },
1296 { '?', REQ_SEARCH_BACK },
1297 { 'n', REQ_FIND_NEXT },
1298 { 'N', REQ_FIND_PREV },
1300 /* Misc */
1301 { 'Q', REQ_QUIT },
1302 { 'z', REQ_STOP_LOADING },
1303 { 'v', REQ_SHOW_VERSION },
1304 { 'r', REQ_SCREEN_REDRAW },
1305 { 'o', REQ_OPTIONS },
1306 { '.', REQ_TOGGLE_LINENO },
1307 { 'D', REQ_TOGGLE_DATE },
1308 { 'A', REQ_TOGGLE_AUTHOR },
1309 { 'g', REQ_TOGGLE_REV_GRAPH },
1310 { 'F', REQ_TOGGLE_REFS },
1311 { 'I', REQ_TOGGLE_SORT_ORDER },
1312 { 'i', REQ_TOGGLE_SORT_FIELD },
1313 { ':', REQ_PROMPT },
1314 { 'u', REQ_STATUS_UPDATE },
1315 { '!', REQ_STATUS_REVERT },
1316 { 'M', REQ_STATUS_MERGE },
1317 { '@', REQ_STAGE_NEXT },
1318 { ',', REQ_PARENT },
1319 { 'e', REQ_EDIT },
1322 #define KEYMAP_INFO \
1323 KEYMAP_(GENERIC), \
1324 KEYMAP_(MAIN), \
1325 KEYMAP_(DIFF), \
1326 KEYMAP_(LOG), \
1327 KEYMAP_(TREE), \
1328 KEYMAP_(BLOB), \
1329 KEYMAP_(BLAME), \
1330 KEYMAP_(BRANCH), \
1331 KEYMAP_(PAGER), \
1332 KEYMAP_(HELP), \
1333 KEYMAP_(STATUS), \
1334 KEYMAP_(STAGE)
1336 enum keymap {
1337 #define KEYMAP_(name) KEYMAP_##name
1338 KEYMAP_INFO
1339 #undef KEYMAP_
1342 static const struct enum_map keymap_table[] = {
1343 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1344 KEYMAP_INFO
1345 #undef KEYMAP_
1348 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1350 struct keybinding_table {
1351 struct keybinding *data;
1352 size_t size;
1355 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1357 static void
1358 add_keybinding(enum keymap keymap, enum request request, int key)
1360 struct keybinding_table *table = &keybindings[keymap];
1362 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1363 if (!table->data)
1364 die("Failed to allocate keybinding");
1365 table->data[table->size].alias = key;
1366 table->data[table->size++].request = request;
1369 /* Looks for a key binding first in the given map, then in the generic map, and
1370 * lastly in the default keybindings. */
1371 static enum request
1372 get_keybinding(enum keymap keymap, int key)
1374 size_t i;
1376 for (i = 0; i < keybindings[keymap].size; i++)
1377 if (keybindings[keymap].data[i].alias == key)
1378 return keybindings[keymap].data[i].request;
1380 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1381 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1382 return keybindings[KEYMAP_GENERIC].data[i].request;
1384 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1385 if (default_keybindings[i].alias == key)
1386 return default_keybindings[i].request;
1388 return (enum request) key;
1392 struct key {
1393 const char *name;
1394 int value;
1397 static const struct key key_table[] = {
1398 { "Enter", KEY_RETURN },
1399 { "Space", ' ' },
1400 { "Backspace", KEY_BACKSPACE },
1401 { "Tab", KEY_TAB },
1402 { "Escape", KEY_ESC },
1403 { "Left", KEY_LEFT },
1404 { "Right", KEY_RIGHT },
1405 { "Up", KEY_UP },
1406 { "Down", KEY_DOWN },
1407 { "Insert", KEY_IC },
1408 { "Delete", KEY_DC },
1409 { "Hash", '#' },
1410 { "Home", KEY_HOME },
1411 { "End", KEY_END },
1412 { "PageUp", KEY_PPAGE },
1413 { "PageDown", KEY_NPAGE },
1414 { "F1", KEY_F(1) },
1415 { "F2", KEY_F(2) },
1416 { "F3", KEY_F(3) },
1417 { "F4", KEY_F(4) },
1418 { "F5", KEY_F(5) },
1419 { "F6", KEY_F(6) },
1420 { "F7", KEY_F(7) },
1421 { "F8", KEY_F(8) },
1422 { "F9", KEY_F(9) },
1423 { "F10", KEY_F(10) },
1424 { "F11", KEY_F(11) },
1425 { "F12", KEY_F(12) },
1428 static int
1429 get_key_value(const char *name)
1431 int i;
1433 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1434 if (!strcasecmp(key_table[i].name, name))
1435 return key_table[i].value;
1437 if (strlen(name) == 1 && isprint(*name))
1438 return (int) *name;
1440 return ERR;
1443 static const char *
1444 get_key_name(int key_value)
1446 static char key_char[] = "'X'";
1447 const char *seq = NULL;
1448 int key;
1450 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1451 if (key_table[key].value == key_value)
1452 seq = key_table[key].name;
1454 if (seq == NULL &&
1455 key_value < 127 &&
1456 isprint(key_value)) {
1457 key_char[1] = (char) key_value;
1458 seq = key_char;
1461 return seq ? seq : "(no key)";
1464 static bool
1465 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1467 const char *sep = *pos > 0 ? ", " : "";
1468 const char *keyname = get_key_name(keybinding->alias);
1470 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1473 static bool
1474 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1475 enum keymap keymap, bool all)
1477 int i;
1479 for (i = 0; i < keybindings[keymap].size; i++) {
1480 if (keybindings[keymap].data[i].request == request) {
1481 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1482 return FALSE;
1483 if (!all)
1484 break;
1488 return TRUE;
1491 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1493 static const char *
1494 get_keys(enum keymap keymap, enum request request, bool all)
1496 static char buf[BUFSIZ];
1497 size_t pos = 0;
1498 int i;
1500 buf[pos] = 0;
1502 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1503 return "Too many keybindings!";
1504 if (pos > 0 && !all)
1505 return buf;
1507 if (keymap != KEYMAP_GENERIC) {
1508 /* Only the generic keymap includes the default keybindings when
1509 * listing all keys. */
1510 if (all)
1511 return buf;
1513 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1514 return "Too many keybindings!";
1515 if (pos)
1516 return buf;
1519 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1520 if (default_keybindings[i].request == request) {
1521 if (!append_key(buf, &pos, &default_keybindings[i]))
1522 return "Too many keybindings!";
1523 if (!all)
1524 return buf;
1528 return buf;
1531 struct run_request {
1532 enum keymap keymap;
1533 int key;
1534 const char *argv[SIZEOF_ARG];
1537 static struct run_request *run_request;
1538 static size_t run_requests;
1540 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1542 static enum request
1543 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1545 struct run_request *req;
1547 if (argc >= ARRAY_SIZE(req->argv) - 1)
1548 return REQ_NONE;
1550 if (!realloc_run_requests(&run_request, run_requests, 1))
1551 return REQ_NONE;
1553 req = &run_request[run_requests];
1554 req->keymap = keymap;
1555 req->key = key;
1556 req->argv[0] = NULL;
1558 if (!format_argv(req->argv, argv, FORMAT_NONE))
1559 return REQ_NONE;
1561 return REQ_NONE + ++run_requests;
1564 static struct run_request *
1565 get_run_request(enum request request)
1567 if (request <= REQ_NONE)
1568 return NULL;
1569 return &run_request[request - REQ_NONE - 1];
1572 static void
1573 add_builtin_run_requests(void)
1575 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1576 const char *commit[] = { "git", "commit", NULL };
1577 const char *gc[] = { "git", "gc", NULL };
1578 struct {
1579 enum keymap keymap;
1580 int key;
1581 int argc;
1582 const char **argv;
1583 } reqs[] = {
1584 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1585 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1586 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1588 int i;
1590 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1591 enum request req;
1593 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1594 if (req != REQ_NONE)
1595 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1600 * User config file handling.
1603 static int config_lineno;
1604 static bool config_errors;
1605 static const char *config_msg;
1607 static const struct enum_map color_map[] = {
1608 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1609 COLOR_MAP(DEFAULT),
1610 COLOR_MAP(BLACK),
1611 COLOR_MAP(BLUE),
1612 COLOR_MAP(CYAN),
1613 COLOR_MAP(GREEN),
1614 COLOR_MAP(MAGENTA),
1615 COLOR_MAP(RED),
1616 COLOR_MAP(WHITE),
1617 COLOR_MAP(YELLOW),
1620 static const struct enum_map attr_map[] = {
1621 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1622 ATTR_MAP(NORMAL),
1623 ATTR_MAP(BLINK),
1624 ATTR_MAP(BOLD),
1625 ATTR_MAP(DIM),
1626 ATTR_MAP(REVERSE),
1627 ATTR_MAP(STANDOUT),
1628 ATTR_MAP(UNDERLINE),
1631 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1633 static int parse_step(double *opt, const char *arg)
1635 *opt = atoi(arg);
1636 if (!strchr(arg, '%'))
1637 return OK;
1639 /* "Shift down" so 100% and 1 does not conflict. */
1640 *opt = (*opt - 1) / 100;
1641 if (*opt >= 1.0) {
1642 *opt = 0.99;
1643 config_msg = "Step value larger than 100%";
1644 return ERR;
1646 if (*opt < 0.0) {
1647 *opt = 1;
1648 config_msg = "Invalid step value";
1649 return ERR;
1651 return OK;
1654 static int
1655 parse_int(int *opt, const char *arg, int min, int max)
1657 int value = atoi(arg);
1659 if (min <= value && value <= max) {
1660 *opt = value;
1661 return OK;
1664 config_msg = "Integer value out of bound";
1665 return ERR;
1668 static bool
1669 set_color(int *color, const char *name)
1671 if (map_enum(color, color_map, name))
1672 return TRUE;
1673 if (!prefixcmp(name, "color"))
1674 return parse_int(color, name + 5, 0, 255) == OK;
1675 return FALSE;
1678 /* Wants: object fgcolor bgcolor [attribute] */
1679 static int
1680 option_color_command(int argc, const char *argv[])
1682 struct line_info *info;
1684 if (argc < 3) {
1685 config_msg = "Wrong number of arguments given to color command";
1686 return ERR;
1689 info = get_line_info(argv[0]);
1690 if (!info) {
1691 static const struct enum_map obsolete[] = {
1692 ENUM_MAP("main-delim", LINE_DELIMITER),
1693 ENUM_MAP("main-date", LINE_DATE),
1694 ENUM_MAP("main-author", LINE_AUTHOR),
1696 int index;
1698 if (!map_enum(&index, obsolete, argv[0])) {
1699 config_msg = "Unknown color name";
1700 return ERR;
1702 info = &line_info[index];
1705 if (!set_color(&info->fg, argv[1]) ||
1706 !set_color(&info->bg, argv[2])) {
1707 config_msg = "Unknown color";
1708 return ERR;
1711 info->attr = 0;
1712 while (argc-- > 3) {
1713 int attr;
1715 if (!set_attribute(&attr, argv[argc])) {
1716 config_msg = "Unknown attribute";
1717 return ERR;
1719 info->attr |= attr;
1722 return OK;
1725 static int parse_bool(bool *opt, const char *arg)
1727 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1728 ? TRUE : FALSE;
1729 return OK;
1732 static int parse_enum_do(unsigned int *opt, const char *arg,
1733 const struct enum_map *map, size_t map_size)
1735 bool is_true;
1737 assert(map_size > 1);
1739 if (map_enum_do(map, map_size, (int *) opt, arg))
1740 return OK;
1742 if (parse_bool(&is_true, arg) != OK)
1743 return ERR;
1745 *opt = is_true ? map[1].value : map[0].value;
1746 return OK;
1749 #define parse_enum(opt, arg, map) \
1750 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1752 static int
1753 parse_string(char *opt, const char *arg, size_t optsize)
1755 int arglen = strlen(arg);
1757 switch (arg[0]) {
1758 case '\"':
1759 case '\'':
1760 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1761 config_msg = "Unmatched quotation";
1762 return ERR;
1764 arg += 1; arglen -= 2;
1765 default:
1766 string_ncopy_do(opt, optsize, arg, arglen);
1767 return OK;
1771 /* Wants: name = value */
1772 static int
1773 option_set_command(int argc, const char *argv[])
1775 if (argc != 3) {
1776 config_msg = "Wrong number of arguments given to set command";
1777 return ERR;
1780 if (strcmp(argv[1], "=")) {
1781 config_msg = "No value assigned";
1782 return ERR;
1785 if (!strcmp(argv[0], "show-author"))
1786 return parse_enum(&opt_author, argv[2], author_map);
1788 if (!strcmp(argv[0], "show-date"))
1789 return parse_enum(&opt_date, argv[2], date_map);
1791 if (!strcmp(argv[0], "show-rev-graph"))
1792 return parse_bool(&opt_rev_graph, argv[2]);
1794 if (!strcmp(argv[0], "show-refs"))
1795 return parse_bool(&opt_show_refs, argv[2]);
1797 if (!strcmp(argv[0], "show-line-numbers"))
1798 return parse_bool(&opt_line_number, argv[2]);
1800 if (!strcmp(argv[0], "line-graphics"))
1801 return parse_bool(&opt_line_graphics, argv[2]);
1803 if (!strcmp(argv[0], "line-number-interval"))
1804 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1806 if (!strcmp(argv[0], "author-width"))
1807 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1809 if (!strcmp(argv[0], "horizontal-scroll"))
1810 return parse_step(&opt_hscroll, argv[2]);
1812 if (!strcmp(argv[0], "split-view-height"))
1813 return parse_step(&opt_scale_split_view, argv[2]);
1815 if (!strcmp(argv[0], "tab-size"))
1816 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1818 if (!strcmp(argv[0], "commit-encoding"))
1819 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1821 config_msg = "Unknown variable name";
1822 return ERR;
1825 /* Wants: mode request key */
1826 static int
1827 option_bind_command(int argc, const char *argv[])
1829 enum request request;
1830 int keymap = -1;
1831 int key;
1833 if (argc < 3) {
1834 config_msg = "Wrong number of arguments given to bind command";
1835 return ERR;
1838 if (set_keymap(&keymap, argv[0]) == ERR) {
1839 config_msg = "Unknown key map";
1840 return ERR;
1843 key = get_key_value(argv[1]);
1844 if (key == ERR) {
1845 config_msg = "Unknown key";
1846 return ERR;
1849 request = get_request(argv[2]);
1850 if (request == REQ_NONE) {
1851 static const struct enum_map obsolete[] = {
1852 ENUM_MAP("cherry-pick", REQ_NONE),
1853 ENUM_MAP("screen-resize", REQ_NONE),
1854 ENUM_MAP("tree-parent", REQ_PARENT),
1856 int alias;
1858 if (map_enum(&alias, obsolete, argv[2])) {
1859 if (alias != REQ_NONE)
1860 add_keybinding(keymap, alias, key);
1861 config_msg = "Obsolete request name";
1862 return ERR;
1865 if (request == REQ_NONE && *argv[2]++ == '!')
1866 request = add_run_request(keymap, key, argc - 2, argv + 2);
1867 if (request == REQ_NONE) {
1868 config_msg = "Unknown request name";
1869 return ERR;
1872 add_keybinding(keymap, request, key);
1874 return OK;
1877 static int
1878 set_option(const char *opt, char *value)
1880 const char *argv[SIZEOF_ARG];
1881 int argc = 0;
1883 if (!argv_from_string(argv, &argc, value)) {
1884 config_msg = "Too many option arguments";
1885 return ERR;
1888 if (!strcmp(opt, "color"))
1889 return option_color_command(argc, argv);
1891 if (!strcmp(opt, "set"))
1892 return option_set_command(argc, argv);
1894 if (!strcmp(opt, "bind"))
1895 return option_bind_command(argc, argv);
1897 config_msg = "Unknown option command";
1898 return ERR;
1901 static int
1902 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1904 int status = OK;
1906 config_lineno++;
1907 config_msg = "Internal error";
1909 /* Check for comment markers, since read_properties() will
1910 * only ensure opt and value are split at first " \t". */
1911 optlen = strcspn(opt, "#");
1912 if (optlen == 0)
1913 return OK;
1915 if (opt[optlen] != 0) {
1916 config_msg = "No option value";
1917 status = ERR;
1919 } else {
1920 /* Look for comment endings in the value. */
1921 size_t len = strcspn(value, "#");
1923 if (len < valuelen) {
1924 valuelen = len;
1925 value[valuelen] = 0;
1928 status = set_option(opt, value);
1931 if (status == ERR) {
1932 warn("Error on line %d, near '%.*s': %s",
1933 config_lineno, (int) optlen, opt, config_msg);
1934 config_errors = TRUE;
1937 /* Always keep going if errors are encountered. */
1938 return OK;
1941 static void
1942 load_option_file(const char *path)
1944 struct io io = {};
1946 /* It's OK that the file doesn't exist. */
1947 if (!io_open(&io, "%s", path))
1948 return;
1950 config_lineno = 0;
1951 config_errors = FALSE;
1953 if (io_load(&io, " \t", read_option) == ERR ||
1954 config_errors == TRUE)
1955 warn("Errors while loading %s.", path);
1958 static int
1959 load_options(void)
1961 const char *home = getenv("HOME");
1962 const char *tigrc_user = getenv("TIGRC_USER");
1963 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1964 char buf[SIZEOF_STR];
1966 add_builtin_run_requests();
1968 if (!tigrc_system)
1969 tigrc_system = SYSCONFDIR "/tigrc";
1970 load_option_file(tigrc_system);
1972 if (!tigrc_user) {
1973 if (!home || !string_format(buf, "%s/.tigrc", home))
1974 return ERR;
1975 tigrc_user = buf;
1977 load_option_file(tigrc_user);
1979 return OK;
1984 * The viewer
1987 struct view;
1988 struct view_ops;
1990 /* The display array of active views and the index of the current view. */
1991 static struct view *display[2];
1992 static unsigned int current_view;
1994 #define foreach_displayed_view(view, i) \
1995 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1997 #define displayed_views() (display[1] != NULL ? 2 : 1)
1999 /* Current head and commit ID */
2000 static char ref_blob[SIZEOF_REF] = "";
2001 static char ref_commit[SIZEOF_REF] = "HEAD";
2002 static char ref_head[SIZEOF_REF] = "HEAD";
2004 struct view {
2005 const char *name; /* View name */
2006 const char *cmd_env; /* Command line set via environment */
2007 const char *id; /* Points to either of ref_{head,commit,blob} */
2009 struct view_ops *ops; /* View operations */
2011 enum keymap keymap; /* What keymap does this view have */
2012 bool git_dir; /* Whether the view requires a git directory. */
2014 char ref[SIZEOF_REF]; /* Hovered commit reference */
2015 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2017 int height, width; /* The width and height of the main window */
2018 WINDOW *win; /* The main window */
2019 WINDOW *title; /* The title window living below the main window */
2021 /* Navigation */
2022 unsigned long offset; /* Offset of the window top */
2023 unsigned long yoffset; /* Offset from the window side. */
2024 unsigned long lineno; /* Current line number */
2025 unsigned long p_offset; /* Previous offset of the window top */
2026 unsigned long p_yoffset;/* Previous offset from the window side */
2027 unsigned long p_lineno; /* Previous current line number */
2028 bool p_restore; /* Should the previous position be restored. */
2030 /* Searching */
2031 char grep[SIZEOF_STR]; /* Search string */
2032 regex_t *regex; /* Pre-compiled regexp */
2034 /* If non-NULL, points to the view that opened this view. If this view
2035 * is closed tig will switch back to the parent view. */
2036 struct view *parent;
2038 /* Buffering */
2039 size_t lines; /* Total number of lines */
2040 struct line *line; /* Line index */
2041 unsigned int digits; /* Number of digits in the lines member. */
2043 /* Drawing */
2044 struct line *curline; /* Line currently being drawn. */
2045 enum line_type curtype; /* Attribute currently used for drawing. */
2046 unsigned long col; /* Column when drawing. */
2047 bool has_scrolled; /* View was scrolled. */
2049 /* Loading */
2050 struct io io;
2051 struct io *pipe;
2052 time_t start_time;
2053 time_t update_secs;
2056 struct view_ops {
2057 /* What type of content being displayed. Used in the title bar. */
2058 const char *type;
2059 /* Default command arguments. */
2060 const char **argv;
2061 /* Open and reads in all view content. */
2062 bool (*open)(struct view *view);
2063 /* Read one line; updates view->line. */
2064 bool (*read)(struct view *view, char *data);
2065 /* Draw one line; @lineno must be < view->height. */
2066 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2067 /* Depending on view handle a special requests. */
2068 enum request (*request)(struct view *view, enum request request, struct line *line);
2069 /* Search for regexp in a line. */
2070 bool (*grep)(struct view *view, struct line *line);
2071 /* Select line */
2072 void (*select)(struct view *view, struct line *line);
2073 /* Prepare view for loading */
2074 bool (*prepare)(struct view *view);
2077 static struct view_ops blame_ops;
2078 static struct view_ops blob_ops;
2079 static struct view_ops diff_ops;
2080 static struct view_ops help_ops;
2081 static struct view_ops log_ops;
2082 static struct view_ops main_ops;
2083 static struct view_ops pager_ops;
2084 static struct view_ops stage_ops;
2085 static struct view_ops status_ops;
2086 static struct view_ops tree_ops;
2087 static struct view_ops branch_ops;
2089 #define VIEW_STR(name, env, ref, ops, map, git) \
2090 { name, #env, ref, ops, map, git }
2092 #define VIEW_(id, name, ops, git, ref) \
2093 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2096 static struct view views[] = {
2097 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2098 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2099 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2100 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2101 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2102 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2103 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2104 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2105 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2106 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2107 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2110 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2111 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2113 #define foreach_view(view, i) \
2114 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2116 #define view_is_displayed(view) \
2117 (view == display[0] || view == display[1])
2120 static inline void
2121 set_view_attr(struct view *view, enum line_type type)
2123 if (!view->curline->selected && view->curtype != type) {
2124 (void) wattrset(view->win, get_line_attr(type));
2125 wchgat(view->win, -1, 0, type, NULL);
2126 view->curtype = type;
2130 static int
2131 draw_chars(struct view *view, enum line_type type, const char *string,
2132 int max_len, bool use_tilde)
2134 static char out_buffer[BUFSIZ * 2];
2135 int len = 0;
2136 int col = 0;
2137 int trimmed = FALSE;
2138 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2140 if (max_len <= 0)
2141 return 0;
2143 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2145 set_view_attr(view, type);
2146 if (len > 0) {
2147 if (opt_iconv_out != ICONV_NONE) {
2148 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2149 size_t inlen = len + 1;
2151 char *outbuf = out_buffer;
2152 size_t outlen = sizeof(out_buffer);
2154 size_t ret;
2156 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2157 if (ret != (size_t) -1) {
2158 string = out_buffer;
2159 len = sizeof(out_buffer) - outlen;
2163 waddnstr(view->win, string, len);
2165 if (trimmed && use_tilde) {
2166 set_view_attr(view, LINE_DELIMITER);
2167 waddch(view->win, '~');
2168 col++;
2171 return col;
2174 static int
2175 draw_space(struct view *view, enum line_type type, int max, int spaces)
2177 static char space[] = " ";
2178 int col = 0;
2180 spaces = MIN(max, spaces);
2182 while (spaces > 0) {
2183 int len = MIN(spaces, sizeof(space) - 1);
2185 col += draw_chars(view, type, space, len, FALSE);
2186 spaces -= len;
2189 return col;
2192 static bool
2193 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2195 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2196 return view->width + view->yoffset <= view->col;
2199 static bool
2200 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2202 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2203 int max = view->width + view->yoffset - view->col;
2204 int i;
2206 if (max < size)
2207 size = max;
2209 set_view_attr(view, type);
2210 /* Using waddch() instead of waddnstr() ensures that
2211 * they'll be rendered correctly for the cursor line. */
2212 for (i = skip; i < size; i++)
2213 waddch(view->win, graphic[i]);
2215 view->col += size;
2216 if (size < max && skip <= size)
2217 waddch(view->win, ' ');
2218 view->col++;
2220 return view->width + view->yoffset <= view->col;
2223 static bool
2224 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2226 int max = MIN(view->width + view->yoffset - view->col, len);
2227 int col;
2229 if (text)
2230 col = draw_chars(view, type, text, max - 1, trim);
2231 else
2232 col = draw_space(view, type, max - 1, max - 1);
2234 view->col += col;
2235 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2236 return view->width + view->yoffset <= view->col;
2239 static bool
2240 draw_date(struct view *view, struct time *time)
2242 const char *date = mkdate(time, opt_date);
2243 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2245 return draw_field(view, LINE_DATE, date, cols, FALSE);
2248 static bool
2249 draw_author(struct view *view, const char *author)
2251 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2252 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2254 if (abbreviate && author)
2255 author = get_author_initials(author);
2257 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2260 static bool
2261 draw_mode(struct view *view, mode_t mode)
2263 const char *str;
2265 if (S_ISDIR(mode))
2266 str = "drwxr-xr-x";
2267 else if (S_ISLNK(mode))
2268 str = "lrwxrwxrwx";
2269 else if (S_ISGITLINK(mode))
2270 str = "m---------";
2271 else if (S_ISREG(mode) && mode & S_IXUSR)
2272 str = "-rwxr-xr-x";
2273 else if (S_ISREG(mode))
2274 str = "-rw-r--r--";
2275 else
2276 str = "----------";
2278 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2281 static bool
2282 draw_lineno(struct view *view, unsigned int lineno)
2284 char number[10];
2285 int digits3 = view->digits < 3 ? 3 : view->digits;
2286 int max = MIN(view->width + view->yoffset - view->col, digits3);
2287 char *text = NULL;
2288 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2290 lineno += view->offset + 1;
2291 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2292 static char fmt[] = "%1ld";
2294 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2295 if (string_format(number, fmt, lineno))
2296 text = number;
2298 if (text)
2299 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2300 else
2301 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2302 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2305 static bool
2306 draw_view_line(struct view *view, unsigned int lineno)
2308 struct line *line;
2309 bool selected = (view->offset + lineno == view->lineno);
2311 assert(view_is_displayed(view));
2313 if (view->offset + lineno >= view->lines)
2314 return FALSE;
2316 line = &view->line[view->offset + lineno];
2318 wmove(view->win, lineno, 0);
2319 if (line->cleareol)
2320 wclrtoeol(view->win);
2321 view->col = 0;
2322 view->curline = line;
2323 view->curtype = LINE_NONE;
2324 line->selected = FALSE;
2325 line->dirty = line->cleareol = 0;
2327 if (selected) {
2328 set_view_attr(view, LINE_CURSOR);
2329 line->selected = TRUE;
2330 view->ops->select(view, line);
2333 return view->ops->draw(view, line, lineno);
2336 static void
2337 redraw_view_dirty(struct view *view)
2339 bool dirty = FALSE;
2340 int lineno;
2342 for (lineno = 0; lineno < view->height; lineno++) {
2343 if (view->offset + lineno >= view->lines)
2344 break;
2345 if (!view->line[view->offset + lineno].dirty)
2346 continue;
2347 dirty = TRUE;
2348 if (!draw_view_line(view, lineno))
2349 break;
2352 if (!dirty)
2353 return;
2354 wnoutrefresh(view->win);
2357 static void
2358 redraw_view_from(struct view *view, int lineno)
2360 assert(0 <= lineno && lineno < view->height);
2362 for (; lineno < view->height; lineno++) {
2363 if (!draw_view_line(view, lineno))
2364 break;
2367 wnoutrefresh(view->win);
2370 static void
2371 redraw_view(struct view *view)
2373 werase(view->win);
2374 redraw_view_from(view, 0);
2378 static void
2379 update_view_title(struct view *view)
2381 char buf[SIZEOF_STR];
2382 char state[SIZEOF_STR];
2383 size_t bufpos = 0, statelen = 0;
2385 assert(view_is_displayed(view));
2387 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2388 unsigned int view_lines = view->offset + view->height;
2389 unsigned int lines = view->lines
2390 ? MIN(view_lines, view->lines) * 100 / view->lines
2391 : 0;
2393 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2394 view->ops->type,
2395 view->lineno + 1,
2396 view->lines,
2397 lines);
2401 if (view->pipe) {
2402 time_t secs = time(NULL) - view->start_time;
2404 /* Three git seconds are a long time ... */
2405 if (secs > 2)
2406 string_format_from(state, &statelen, " loading %lds", secs);
2409 string_format_from(buf, &bufpos, "[%s]", view->name);
2410 if (*view->ref && bufpos < view->width) {
2411 size_t refsize = strlen(view->ref);
2412 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2414 if (minsize < view->width)
2415 refsize = view->width - minsize + 7;
2416 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2419 if (statelen && bufpos < view->width) {
2420 string_format_from(buf, &bufpos, "%s", state);
2423 if (view == display[current_view])
2424 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2425 else
2426 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2428 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2429 wclrtoeol(view->title);
2430 wnoutrefresh(view->title);
2433 static int
2434 apply_step(double step, int value)
2436 if (step >= 1)
2437 return (int) step;
2438 value *= step + 0.01;
2439 return value ? value : 1;
2442 static void
2443 resize_display(void)
2445 int offset, i;
2446 struct view *base = display[0];
2447 struct view *view = display[1] ? display[1] : display[0];
2449 /* Setup window dimensions */
2451 getmaxyx(stdscr, base->height, base->width);
2453 /* Make room for the status window. */
2454 base->height -= 1;
2456 if (view != base) {
2457 /* Horizontal split. */
2458 view->width = base->width;
2459 view->height = apply_step(opt_scale_split_view, base->height);
2460 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2461 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2462 base->height -= view->height;
2464 /* Make room for the title bar. */
2465 view->height -= 1;
2468 /* Make room for the title bar. */
2469 base->height -= 1;
2471 offset = 0;
2473 foreach_displayed_view (view, i) {
2474 if (!view->win) {
2475 view->win = newwin(view->height, 0, offset, 0);
2476 if (!view->win)
2477 die("Failed to create %s view", view->name);
2479 scrollok(view->win, FALSE);
2481 view->title = newwin(1, 0, offset + view->height, 0);
2482 if (!view->title)
2483 die("Failed to create title window");
2485 } else {
2486 wresize(view->win, view->height, view->width);
2487 mvwin(view->win, offset, 0);
2488 mvwin(view->title, offset + view->height, 0);
2491 offset += view->height + 1;
2495 static void
2496 redraw_display(bool clear)
2498 struct view *view;
2499 int i;
2501 foreach_displayed_view (view, i) {
2502 if (clear)
2503 wclear(view->win);
2504 redraw_view(view);
2505 update_view_title(view);
2509 static void
2510 toggle_enum_option_do(unsigned int *opt, const char *help,
2511 const struct enum_map *map, size_t size)
2513 *opt = (*opt + 1) % size;
2514 redraw_display(FALSE);
2515 report("Displaying %s %s", enum_name(map[*opt]), help);
2518 #define toggle_enum_option(opt, help, map) \
2519 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2521 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2522 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2524 static void
2525 toggle_view_option(bool *option, const char *help)
2527 *option = !*option;
2528 redraw_display(FALSE);
2529 report("%sabling %s", *option ? "En" : "Dis", help);
2532 static void
2533 open_option_menu(void)
2535 const struct menu_item menu[] = {
2536 { '.', "line numbers", &opt_line_number },
2537 { 'D', "date display", &opt_date },
2538 { 'A', "author display", &opt_author },
2539 { 'g', "revision graph display", &opt_rev_graph },
2540 { 'F', "reference display", &opt_show_refs },
2541 { 0 }
2543 int selected = 0;
2545 if (prompt_menu("Toggle option", menu, &selected)) {
2546 if (menu[selected].data == &opt_date)
2547 toggle_date();
2548 else if (menu[selected].data == &opt_author)
2549 toggle_author();
2550 else
2551 toggle_view_option(menu[selected].data, menu[selected].text);
2555 static void
2556 maximize_view(struct view *view)
2558 memset(display, 0, sizeof(display));
2559 current_view = 0;
2560 display[current_view] = view;
2561 resize_display();
2562 redraw_display(FALSE);
2563 report("");
2568 * Navigation
2571 static bool
2572 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2574 if (lineno >= view->lines)
2575 lineno = view->lines > 0 ? view->lines - 1 : 0;
2577 if (offset > lineno || offset + view->height <= lineno) {
2578 unsigned long half = view->height / 2;
2580 if (lineno > half)
2581 offset = lineno - half;
2582 else
2583 offset = 0;
2586 if (offset != view->offset || lineno != view->lineno) {
2587 view->offset = offset;
2588 view->lineno = lineno;
2589 return TRUE;
2592 return FALSE;
2595 /* Scrolling backend */
2596 static void
2597 do_scroll_view(struct view *view, int lines)
2599 bool redraw_current_line = FALSE;
2601 /* The rendering expects the new offset. */
2602 view->offset += lines;
2604 assert(0 <= view->offset && view->offset < view->lines);
2605 assert(lines);
2607 /* Move current line into the view. */
2608 if (view->lineno < view->offset) {
2609 view->lineno = view->offset;
2610 redraw_current_line = TRUE;
2611 } else if (view->lineno >= view->offset + view->height) {
2612 view->lineno = view->offset + view->height - 1;
2613 redraw_current_line = TRUE;
2616 assert(view->offset <= view->lineno && view->lineno < view->lines);
2618 /* Redraw the whole screen if scrolling is pointless. */
2619 if (view->height < ABS(lines)) {
2620 redraw_view(view);
2622 } else {
2623 int line = lines > 0 ? view->height - lines : 0;
2624 int end = line + ABS(lines);
2626 scrollok(view->win, TRUE);
2627 wscrl(view->win, lines);
2628 scrollok(view->win, FALSE);
2630 while (line < end && draw_view_line(view, line))
2631 line++;
2633 if (redraw_current_line)
2634 draw_view_line(view, view->lineno - view->offset);
2635 wnoutrefresh(view->win);
2638 view->has_scrolled = TRUE;
2639 report("");
2642 /* Scroll frontend */
2643 static void
2644 scroll_view(struct view *view, enum request request)
2646 int lines = 1;
2648 assert(view_is_displayed(view));
2650 switch (request) {
2651 case REQ_SCROLL_LEFT:
2652 if (view->yoffset == 0) {
2653 report("Cannot scroll beyond the first column");
2654 return;
2656 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2657 view->yoffset = 0;
2658 else
2659 view->yoffset -= apply_step(opt_hscroll, view->width);
2660 redraw_view_from(view, 0);
2661 report("");
2662 return;
2663 case REQ_SCROLL_RIGHT:
2664 view->yoffset += apply_step(opt_hscroll, view->width);
2665 redraw_view(view);
2666 report("");
2667 return;
2668 case REQ_SCROLL_PAGE_DOWN:
2669 lines = view->height;
2670 case REQ_SCROLL_LINE_DOWN:
2671 if (view->offset + lines > view->lines)
2672 lines = view->lines - view->offset;
2674 if (lines == 0 || view->offset + view->height >= view->lines) {
2675 report("Cannot scroll beyond the last line");
2676 return;
2678 break;
2680 case REQ_SCROLL_PAGE_UP:
2681 lines = view->height;
2682 case REQ_SCROLL_LINE_UP:
2683 if (lines > view->offset)
2684 lines = view->offset;
2686 if (lines == 0) {
2687 report("Cannot scroll beyond the first line");
2688 return;
2691 lines = -lines;
2692 break;
2694 default:
2695 die("request %d not handled in switch", request);
2698 do_scroll_view(view, lines);
2701 /* Cursor moving */
2702 static void
2703 move_view(struct view *view, enum request request)
2705 int scroll_steps = 0;
2706 int steps;
2708 switch (request) {
2709 case REQ_MOVE_FIRST_LINE:
2710 steps = -view->lineno;
2711 break;
2713 case REQ_MOVE_LAST_LINE:
2714 steps = view->lines - view->lineno - 1;
2715 break;
2717 case REQ_MOVE_PAGE_UP:
2718 steps = view->height > view->lineno
2719 ? -view->lineno : -view->height;
2720 break;
2722 case REQ_MOVE_PAGE_DOWN:
2723 steps = view->lineno + view->height >= view->lines
2724 ? view->lines - view->lineno - 1 : view->height;
2725 break;
2727 case REQ_MOVE_UP:
2728 steps = -1;
2729 break;
2731 case REQ_MOVE_DOWN:
2732 steps = 1;
2733 break;
2735 default:
2736 die("request %d not handled in switch", request);
2739 if (steps <= 0 && view->lineno == 0) {
2740 report("Cannot move beyond the first line");
2741 return;
2743 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2744 report("Cannot move beyond the last line");
2745 return;
2748 /* Move the current line */
2749 view->lineno += steps;
2750 assert(0 <= view->lineno && view->lineno < view->lines);
2752 /* Check whether the view needs to be scrolled */
2753 if (view->lineno < view->offset ||
2754 view->lineno >= view->offset + view->height) {
2755 scroll_steps = steps;
2756 if (steps < 0 && -steps > view->offset) {
2757 scroll_steps = -view->offset;
2759 } else if (steps > 0) {
2760 if (view->lineno == view->lines - 1 &&
2761 view->lines > view->height) {
2762 scroll_steps = view->lines - view->offset - 1;
2763 if (scroll_steps >= view->height)
2764 scroll_steps -= view->height - 1;
2769 if (!view_is_displayed(view)) {
2770 view->offset += scroll_steps;
2771 assert(0 <= view->offset && view->offset < view->lines);
2772 view->ops->select(view, &view->line[view->lineno]);
2773 return;
2776 /* Repaint the old "current" line if we be scrolling */
2777 if (ABS(steps) < view->height)
2778 draw_view_line(view, view->lineno - steps - view->offset);
2780 if (scroll_steps) {
2781 do_scroll_view(view, scroll_steps);
2782 return;
2785 /* Draw the current line */
2786 draw_view_line(view, view->lineno - view->offset);
2788 wnoutrefresh(view->win);
2789 report("");
2794 * Searching
2797 static void search_view(struct view *view, enum request request);
2799 static bool
2800 grep_text(struct view *view, const char *text[])
2802 regmatch_t pmatch;
2803 size_t i;
2805 for (i = 0; text[i]; i++)
2806 if (*text[i] &&
2807 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2808 return TRUE;
2809 return FALSE;
2812 static void
2813 select_view_line(struct view *view, unsigned long lineno)
2815 unsigned long old_lineno = view->lineno;
2816 unsigned long old_offset = view->offset;
2818 if (goto_view_line(view, view->offset, lineno)) {
2819 if (view_is_displayed(view)) {
2820 if (old_offset != view->offset) {
2821 redraw_view(view);
2822 } else {
2823 draw_view_line(view, old_lineno - view->offset);
2824 draw_view_line(view, view->lineno - view->offset);
2825 wnoutrefresh(view->win);
2827 } else {
2828 view->ops->select(view, &view->line[view->lineno]);
2833 static void
2834 find_next(struct view *view, enum request request)
2836 unsigned long lineno = view->lineno;
2837 int direction;
2839 if (!*view->grep) {
2840 if (!*opt_search)
2841 report("No previous search");
2842 else
2843 search_view(view, request);
2844 return;
2847 switch (request) {
2848 case REQ_SEARCH:
2849 case REQ_FIND_NEXT:
2850 direction = 1;
2851 break;
2853 case REQ_SEARCH_BACK:
2854 case REQ_FIND_PREV:
2855 direction = -1;
2856 break;
2858 default:
2859 return;
2862 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2863 lineno += direction;
2865 /* Note, lineno is unsigned long so will wrap around in which case it
2866 * will become bigger than view->lines. */
2867 for (; lineno < view->lines; lineno += direction) {
2868 if (view->ops->grep(view, &view->line[lineno])) {
2869 select_view_line(view, lineno);
2870 report("Line %ld matches '%s'", lineno + 1, view->grep);
2871 return;
2875 report("No match found for '%s'", view->grep);
2878 static void
2879 search_view(struct view *view, enum request request)
2881 int regex_err;
2883 if (view->regex) {
2884 regfree(view->regex);
2885 *view->grep = 0;
2886 } else {
2887 view->regex = calloc(1, sizeof(*view->regex));
2888 if (!view->regex)
2889 return;
2892 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2893 if (regex_err != 0) {
2894 char buf[SIZEOF_STR] = "unknown error";
2896 regerror(regex_err, view->regex, buf, sizeof(buf));
2897 report("Search failed: %s", buf);
2898 return;
2901 string_copy(view->grep, opt_search);
2903 find_next(view, request);
2907 * Incremental updating
2910 static void
2911 reset_view(struct view *view)
2913 int i;
2915 for (i = 0; i < view->lines; i++)
2916 free(view->line[i].data);
2917 free(view->line);
2919 view->p_offset = view->offset;
2920 view->p_yoffset = view->yoffset;
2921 view->p_lineno = view->lineno;
2923 view->line = NULL;
2924 view->offset = 0;
2925 view->yoffset = 0;
2926 view->lines = 0;
2927 view->lineno = 0;
2928 view->vid[0] = 0;
2929 view->update_secs = 0;
2932 static void
2933 free_argv(const char *argv[])
2935 int argc;
2937 for (argc = 0; argv[argc]; argc++)
2938 free((void *) argv[argc]);
2941 static const char *
2942 format_arg(const char *name)
2944 static struct {
2945 const char *name;
2946 size_t namelen;
2947 const char *value;
2948 const char *value_if_empty;
2949 } vars[] = {
2950 #define FORMAT_VAR(name, value, value_if_empty) \
2951 { name, STRING_SIZE(name), value, value_if_empty }
2952 FORMAT_VAR("%(directory)", opt_path, ""),
2953 FORMAT_VAR("%(file)", opt_file, ""),
2954 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2955 FORMAT_VAR("%(head)", ref_head, ""),
2956 FORMAT_VAR("%(commit)", ref_commit, ""),
2957 FORMAT_VAR("%(blob)", ref_blob, ""),
2959 int i;
2961 for (i = 0; i < ARRAY_SIZE(vars); i++)
2962 if (!strncmp(name, vars[i].name, vars[i].namelen))
2963 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2965 return NULL;
2968 static bool
2969 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2971 char buf[SIZEOF_STR];
2972 int argc;
2973 bool noreplace = flags == FORMAT_NONE;
2975 free_argv(dst_argv);
2977 for (argc = 0; src_argv[argc]; argc++) {
2978 const char *arg = src_argv[argc];
2979 size_t bufpos = 0;
2981 while (arg) {
2982 char *next = strstr(arg, "%(");
2983 int len = next - arg;
2984 const char *value;
2986 if (!next || noreplace) {
2987 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2988 noreplace = TRUE;
2989 len = strlen(arg);
2990 value = "";
2992 } else {
2993 value = format_arg(next);
2995 if (!value) {
2996 report("Unknown replacement: `%s`", next);
2997 return FALSE;
3001 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3002 return FALSE;
3004 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3007 dst_argv[argc] = strdup(buf);
3008 if (!dst_argv[argc])
3009 break;
3012 dst_argv[argc] = NULL;
3014 return src_argv[argc] == NULL;
3017 static bool
3018 restore_view_position(struct view *view)
3020 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3021 return FALSE;
3023 /* Changing the view position cancels the restoring. */
3024 /* FIXME: Changing back to the first line is not detected. */
3025 if (view->offset != 0 || view->lineno != 0) {
3026 view->p_restore = FALSE;
3027 return FALSE;
3030 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3031 view_is_displayed(view))
3032 werase(view->win);
3034 view->yoffset = view->p_yoffset;
3035 view->p_restore = FALSE;
3037 return TRUE;
3040 static void
3041 end_update(struct view *view, bool force)
3043 if (!view->pipe)
3044 return;
3045 while (!view->ops->read(view, NULL))
3046 if (!force)
3047 return;
3048 if (force)
3049 io_kill(view->pipe);
3050 io_done(view->pipe);
3051 view->pipe = NULL;
3054 static void
3055 setup_update(struct view *view, const char *vid)
3057 reset_view(view);
3058 string_copy_rev(view->vid, vid);
3059 view->pipe = &view->io;
3060 view->start_time = time(NULL);
3063 static bool
3064 prepare_update(struct view *view, const char *argv[], const char *dir,
3065 enum format_flags flags)
3067 if (view->pipe)
3068 end_update(view, TRUE);
3069 return io_format(&view->io, dir, IO_RD, argv, flags);
3072 static bool
3073 prepare_update_file(struct view *view, const char *name)
3075 if (view->pipe)
3076 end_update(view, TRUE);
3077 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3080 static bool
3081 begin_update(struct view *view, bool refresh)
3083 if (view->pipe)
3084 end_update(view, TRUE);
3086 if (!refresh) {
3087 if (view->ops->prepare) {
3088 if (!view->ops->prepare(view))
3089 return FALSE;
3090 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3091 return FALSE;
3094 /* Put the current ref_* value to the view title ref
3095 * member. This is needed by the blob view. Most other
3096 * views sets it automatically after loading because the
3097 * first line is a commit line. */
3098 string_copy_rev(view->ref, view->id);
3101 if (!io_start(&view->io))
3102 return FALSE;
3104 setup_update(view, view->id);
3106 return TRUE;
3109 static bool
3110 update_view(struct view *view)
3112 char out_buffer[BUFSIZ * 2];
3113 char *line;
3114 /* Clear the view and redraw everything since the tree sorting
3115 * might have rearranged things. */
3116 bool redraw = view->lines == 0;
3117 bool can_read = TRUE;
3119 if (!view->pipe)
3120 return TRUE;
3122 if (!io_can_read(view->pipe)) {
3123 if (view->lines == 0 && view_is_displayed(view)) {
3124 time_t secs = time(NULL) - view->start_time;
3126 if (secs > 1 && secs > view->update_secs) {
3127 if (view->update_secs == 0)
3128 redraw_view(view);
3129 update_view_title(view);
3130 view->update_secs = secs;
3133 return TRUE;
3136 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3137 if (opt_iconv_in != ICONV_NONE) {
3138 ICONV_CONST char *inbuf = line;
3139 size_t inlen = strlen(line) + 1;
3141 char *outbuf = out_buffer;
3142 size_t outlen = sizeof(out_buffer);
3144 size_t ret;
3146 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3147 if (ret != (size_t) -1)
3148 line = out_buffer;
3151 if (!view->ops->read(view, line)) {
3152 report("Allocation failure");
3153 end_update(view, TRUE);
3154 return FALSE;
3159 unsigned long lines = view->lines;
3160 int digits;
3162 for (digits = 0; lines; digits++)
3163 lines /= 10;
3165 /* Keep the displayed view in sync with line number scaling. */
3166 if (digits != view->digits) {
3167 view->digits = digits;
3168 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3169 redraw = TRUE;
3173 if (io_error(view->pipe)) {
3174 report("Failed to read: %s", io_strerror(view->pipe));
3175 end_update(view, TRUE);
3177 } else if (io_eof(view->pipe)) {
3178 report("");
3179 end_update(view, FALSE);
3182 if (restore_view_position(view))
3183 redraw = TRUE;
3185 if (!view_is_displayed(view))
3186 return TRUE;
3188 if (redraw)
3189 redraw_view_from(view, 0);
3190 else
3191 redraw_view_dirty(view);
3193 /* Update the title _after_ the redraw so that if the redraw picks up a
3194 * commit reference in view->ref it'll be available here. */
3195 update_view_title(view);
3196 return TRUE;
3199 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3201 static struct line *
3202 add_line_data(struct view *view, void *data, enum line_type type)
3204 struct line *line;
3206 if (!realloc_lines(&view->line, view->lines, 1))
3207 return NULL;
3209 line = &view->line[view->lines++];
3210 memset(line, 0, sizeof(*line));
3211 line->type = type;
3212 line->data = data;
3213 line->dirty = 1;
3215 return line;
3218 static struct line *
3219 add_line_text(struct view *view, const char *text, enum line_type type)
3221 char *data = text ? strdup(text) : NULL;
3223 return data ? add_line_data(view, data, type) : NULL;
3226 static struct line *
3227 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3229 char buf[SIZEOF_STR];
3230 va_list args;
3232 va_start(args, fmt);
3233 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3234 buf[0] = 0;
3235 va_end(args);
3237 return buf[0] ? add_line_text(view, buf, type) : NULL;
3241 * View opening
3244 enum open_flags {
3245 OPEN_DEFAULT = 0, /* Use default view switching. */
3246 OPEN_SPLIT = 1, /* Split current view. */
3247 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3248 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3249 OPEN_PREPARED = 32, /* Open already prepared command. */
3252 static void
3253 open_view(struct view *prev, enum request request, enum open_flags flags)
3255 bool split = !!(flags & OPEN_SPLIT);
3256 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3257 bool nomaximize = !!(flags & OPEN_REFRESH);
3258 struct view *view = VIEW(request);
3259 int nviews = displayed_views();
3260 struct view *base_view = display[0];
3262 if (view == prev && nviews == 1 && !reload) {
3263 report("Already in %s view", view->name);
3264 return;
3267 if (view->git_dir && !opt_git_dir[0]) {
3268 report("The %s view is disabled in pager view", view->name);
3269 return;
3272 if (split) {
3273 display[1] = view;
3274 current_view = 1;
3275 } else if (!nomaximize) {
3276 /* Maximize the current view. */
3277 memset(display, 0, sizeof(display));
3278 current_view = 0;
3279 display[current_view] = view;
3282 /* No parent signals that this is the first loaded view. */
3283 if (prev && view != prev) {
3284 view->parent = prev;
3287 /* Resize the view when switching between split- and full-screen,
3288 * or when switching between two different full-screen views. */
3289 if (nviews != displayed_views() ||
3290 (nviews == 1 && base_view != display[0]))
3291 resize_display();
3293 if (view->ops->open) {
3294 if (view->pipe)
3295 end_update(view, TRUE);
3296 if (!view->ops->open(view)) {
3297 report("Failed to load %s view", view->name);
3298 return;
3300 restore_view_position(view);
3302 } else if ((reload || strcmp(view->vid, view->id)) &&
3303 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3304 report("Failed to load %s view", view->name);
3305 return;
3308 if (split && prev->lineno - prev->offset >= prev->height) {
3309 /* Take the title line into account. */
3310 int lines = prev->lineno - prev->offset - prev->height + 1;
3312 /* Scroll the view that was split if the current line is
3313 * outside the new limited view. */
3314 do_scroll_view(prev, lines);
3317 if (prev && view != prev && split && view_is_displayed(prev)) {
3318 /* "Blur" the previous view. */
3319 update_view_title(prev);
3322 if (view->pipe && view->lines == 0) {
3323 /* Clear the old view and let the incremental updating refill
3324 * the screen. */
3325 werase(view->win);
3326 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3327 report("");
3328 } else if (view_is_displayed(view)) {
3329 redraw_view(view);
3330 report("");
3334 static void
3335 open_external_viewer(const char *argv[], const char *dir)
3337 def_prog_mode(); /* save current tty modes */
3338 endwin(); /* restore original tty modes */
3339 io_run_fg(argv, dir);
3340 fprintf(stderr, "Press Enter to continue");
3341 getc(opt_tty);
3342 reset_prog_mode();
3343 redraw_display(TRUE);
3346 static void
3347 open_mergetool(const char *file)
3349 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3351 open_external_viewer(mergetool_argv, opt_cdup);
3354 static void
3355 open_editor(const char *file)
3357 const char *editor_argv[] = { "vi", file, NULL };
3358 const char *editor;
3360 editor = getenv("GIT_EDITOR");
3361 if (!editor && *opt_editor)
3362 editor = opt_editor;
3363 if (!editor)
3364 editor = getenv("VISUAL");
3365 if (!editor)
3366 editor = getenv("EDITOR");
3367 if (!editor)
3368 editor = "vi";
3370 editor_argv[0] = editor;
3371 open_external_viewer(editor_argv, opt_cdup);
3374 static void
3375 open_run_request(enum request request)
3377 struct run_request *req = get_run_request(request);
3378 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3380 if (!req) {
3381 report("Unknown run request");
3382 return;
3385 if (format_argv(argv, req->argv, FORMAT_ALL))
3386 open_external_viewer(argv, NULL);
3387 free_argv(argv);
3391 * User request switch noodle
3394 static int
3395 view_driver(struct view *view, enum request request)
3397 int i;
3399 if (request == REQ_NONE)
3400 return TRUE;
3402 if (request > REQ_NONE) {
3403 open_run_request(request);
3404 /* FIXME: When all views can refresh always do this. */
3405 if (view == VIEW(REQ_VIEW_STATUS) ||
3406 view == VIEW(REQ_VIEW_MAIN) ||
3407 view == VIEW(REQ_VIEW_LOG) ||
3408 view == VIEW(REQ_VIEW_BRANCH) ||
3409 view == VIEW(REQ_VIEW_STAGE))
3410 request = REQ_REFRESH;
3411 else
3412 return TRUE;
3415 if (view && view->lines) {
3416 request = view->ops->request(view, request, &view->line[view->lineno]);
3417 if (request == REQ_NONE)
3418 return TRUE;
3421 switch (request) {
3422 case REQ_MOVE_UP:
3423 case REQ_MOVE_DOWN:
3424 case REQ_MOVE_PAGE_UP:
3425 case REQ_MOVE_PAGE_DOWN:
3426 case REQ_MOVE_FIRST_LINE:
3427 case REQ_MOVE_LAST_LINE:
3428 move_view(view, request);
3429 break;
3431 case REQ_SCROLL_LEFT:
3432 case REQ_SCROLL_RIGHT:
3433 case REQ_SCROLL_LINE_DOWN:
3434 case REQ_SCROLL_LINE_UP:
3435 case REQ_SCROLL_PAGE_DOWN:
3436 case REQ_SCROLL_PAGE_UP:
3437 scroll_view(view, request);
3438 break;
3440 case REQ_VIEW_BLAME:
3441 if (!opt_file[0]) {
3442 report("No file chosen, press %s to open tree view",
3443 get_key(view->keymap, REQ_VIEW_TREE));
3444 break;
3446 open_view(view, request, OPEN_DEFAULT);
3447 break;
3449 case REQ_VIEW_BLOB:
3450 if (!ref_blob[0]) {
3451 report("No file chosen, press %s to open tree view",
3452 get_key(view->keymap, REQ_VIEW_TREE));
3453 break;
3455 open_view(view, request, OPEN_DEFAULT);
3456 break;
3458 case REQ_VIEW_PAGER:
3459 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3460 report("No pager content, press %s to run command from prompt",
3461 get_key(view->keymap, REQ_PROMPT));
3462 break;
3464 open_view(view, request, OPEN_DEFAULT);
3465 break;
3467 case REQ_VIEW_STAGE:
3468 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3469 report("No stage content, press %s to open the status view and choose file",
3470 get_key(view->keymap, REQ_VIEW_STATUS));
3471 break;
3473 open_view(view, request, OPEN_DEFAULT);
3474 break;
3476 case REQ_VIEW_STATUS:
3477 if (opt_is_inside_work_tree == FALSE) {
3478 report("The status view requires a working tree");
3479 break;
3481 open_view(view, request, OPEN_DEFAULT);
3482 break;
3484 case REQ_VIEW_MAIN:
3485 case REQ_VIEW_DIFF:
3486 case REQ_VIEW_LOG:
3487 case REQ_VIEW_TREE:
3488 case REQ_VIEW_HELP:
3489 case REQ_VIEW_BRANCH:
3490 open_view(view, request, OPEN_DEFAULT);
3491 break;
3493 case REQ_NEXT:
3494 case REQ_PREVIOUS:
3495 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3497 if ((view == VIEW(REQ_VIEW_DIFF) &&
3498 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3499 (view == VIEW(REQ_VIEW_DIFF) &&
3500 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3501 (view == VIEW(REQ_VIEW_STAGE) &&
3502 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3503 (view == VIEW(REQ_VIEW_BLOB) &&
3504 view->parent == VIEW(REQ_VIEW_TREE)) ||
3505 (view == VIEW(REQ_VIEW_MAIN) &&
3506 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3507 int line;
3509 view = view->parent;
3510 line = view->lineno;
3511 move_view(view, request);
3512 if (view_is_displayed(view))
3513 update_view_title(view);
3514 if (line != view->lineno)
3515 view->ops->request(view, REQ_ENTER,
3516 &view->line[view->lineno]);
3518 } else {
3519 move_view(view, request);
3521 break;
3523 case REQ_VIEW_NEXT:
3525 int nviews = displayed_views();
3526 int next_view = (current_view + 1) % nviews;
3528 if (next_view == current_view) {
3529 report("Only one view is displayed");
3530 break;
3533 current_view = next_view;
3534 /* Blur out the title of the previous view. */
3535 update_view_title(view);
3536 report("");
3537 break;
3539 case REQ_REFRESH:
3540 report("Refreshing is not yet supported for the %s view", view->name);
3541 break;
3543 case REQ_MAXIMIZE:
3544 if (displayed_views() == 2)
3545 maximize_view(view);
3546 break;
3548 case REQ_OPTIONS:
3549 open_option_menu();
3550 break;
3552 case REQ_TOGGLE_LINENO:
3553 toggle_view_option(&opt_line_number, "line numbers");
3554 break;
3556 case REQ_TOGGLE_DATE:
3557 toggle_date();
3558 break;
3560 case REQ_TOGGLE_AUTHOR:
3561 toggle_author();
3562 break;
3564 case REQ_TOGGLE_REV_GRAPH:
3565 toggle_view_option(&opt_rev_graph, "revision graph display");
3566 break;
3568 case REQ_TOGGLE_REFS:
3569 toggle_view_option(&opt_show_refs, "reference display");
3570 break;
3572 case REQ_TOGGLE_SORT_FIELD:
3573 case REQ_TOGGLE_SORT_ORDER:
3574 report("Sorting is not yet supported for the %s view", view->name);
3575 break;
3577 case REQ_SEARCH:
3578 case REQ_SEARCH_BACK:
3579 search_view(view, request);
3580 break;
3582 case REQ_FIND_NEXT:
3583 case REQ_FIND_PREV:
3584 find_next(view, request);
3585 break;
3587 case REQ_STOP_LOADING:
3588 for (i = 0; i < ARRAY_SIZE(views); i++) {
3589 view = &views[i];
3590 if (view->pipe)
3591 report("Stopped loading the %s view", view->name),
3592 end_update(view, TRUE);
3594 break;
3596 case REQ_SHOW_VERSION:
3597 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3598 return TRUE;
3600 case REQ_SCREEN_REDRAW:
3601 redraw_display(TRUE);
3602 break;
3604 case REQ_EDIT:
3605 report("Nothing to edit");
3606 break;
3608 case REQ_ENTER:
3609 report("Nothing to enter");
3610 break;
3612 case REQ_VIEW_CLOSE:
3613 /* XXX: Mark closed views by letting view->parent point to the
3614 * view itself. Parents to closed view should never be
3615 * followed. */
3616 if (view->parent &&
3617 view->parent->parent != view->parent) {
3618 maximize_view(view->parent);
3619 view->parent = view;
3620 break;
3622 /* Fall-through */
3623 case REQ_QUIT:
3624 return FALSE;
3626 default:
3627 report("Unknown key, press %s for help",
3628 get_key(view->keymap, REQ_VIEW_HELP));
3629 return TRUE;
3632 return TRUE;
3637 * View backend utilities
3640 enum sort_field {
3641 ORDERBY_NAME,
3642 ORDERBY_DATE,
3643 ORDERBY_AUTHOR,
3646 struct sort_state {
3647 const enum sort_field *fields;
3648 size_t size, current;
3649 bool reverse;
3652 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3653 #define get_sort_field(state) ((state).fields[(state).current])
3654 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3656 static void
3657 sort_view(struct view *view, enum request request, struct sort_state *state,
3658 int (*compare)(const void *, const void *))
3660 switch (request) {
3661 case REQ_TOGGLE_SORT_FIELD:
3662 state->current = (state->current + 1) % state->size;
3663 break;
3665 case REQ_TOGGLE_SORT_ORDER:
3666 state->reverse = !state->reverse;
3667 break;
3668 default:
3669 die("Not a sort request");
3672 qsort(view->line, view->lines, sizeof(*view->line), compare);
3673 redraw_view(view);
3676 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3678 /* Small author cache to reduce memory consumption. It uses binary
3679 * search to lookup or find place to position new entries. No entries
3680 * are ever freed. */
3681 static const char *
3682 get_author(const char *name)
3684 static const char **authors;
3685 static size_t authors_size;
3686 int from = 0, to = authors_size - 1;
3688 while (from <= to) {
3689 size_t pos = (to + from) / 2;
3690 int cmp = strcmp(name, authors[pos]);
3692 if (!cmp)
3693 return authors[pos];
3695 if (cmp < 0)
3696 to = pos - 1;
3697 else
3698 from = pos + 1;
3701 if (!realloc_authors(&authors, authors_size, 1))
3702 return NULL;
3703 name = strdup(name);
3704 if (!name)
3705 return NULL;
3707 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3708 authors[from] = name;
3709 authors_size++;
3711 return name;
3714 static void
3715 parse_timesec(struct time *time, const char *sec)
3717 time->sec = (time_t) atol(sec);
3720 static void
3721 parse_timezone(struct time *time, const char *zone)
3723 long tz;
3725 tz = ('0' - zone[1]) * 60 * 60 * 10;
3726 tz += ('0' - zone[2]) * 60 * 60;
3727 tz += ('0' - zone[3]) * 60;
3728 tz += ('0' - zone[4]);
3730 if (zone[0] == '-')
3731 tz = -tz;
3733 time->tz = tz;
3734 time->sec -= tz;
3737 /* Parse author lines where the name may be empty:
3738 * author <email@address.tld> 1138474660 +0100
3740 static void
3741 parse_author_line(char *ident, const char **author, struct time *time)
3743 char *nameend = strchr(ident, '<');
3744 char *emailend = strchr(ident, '>');
3746 if (nameend && emailend)
3747 *nameend = *emailend = 0;
3748 ident = chomp_string(ident);
3749 if (!*ident) {
3750 if (nameend)
3751 ident = chomp_string(nameend + 1);
3752 if (!*ident)
3753 ident = "Unknown";
3756 *author = get_author(ident);
3758 /* Parse epoch and timezone */
3759 if (emailend && emailend[1] == ' ') {
3760 char *secs = emailend + 2;
3761 char *zone = strchr(secs, ' ');
3763 parse_timesec(time, secs);
3765 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3766 parse_timezone(time, zone + 1);
3770 static bool
3771 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3773 char rev[SIZEOF_REV];
3774 const char *revlist_argv[] = {
3775 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3777 struct menu_item *items;
3778 char text[SIZEOF_STR];
3779 bool ok = TRUE;
3780 int i;
3782 items = calloc(*parents + 1, sizeof(*items));
3783 if (!items)
3784 return FALSE;
3786 for (i = 0; i < *parents; i++) {
3787 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3788 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3789 !(items[i].text = strdup(text))) {
3790 ok = FALSE;
3791 break;
3795 if (ok) {
3796 *parents = 0;
3797 ok = prompt_menu("Select parent", items, parents);
3799 for (i = 0; items[i].text; i++)
3800 free((char *) items[i].text);
3801 free(items);
3802 return ok;
3805 static bool
3806 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3808 char buf[SIZEOF_STR * 4];
3809 const char *revlist_argv[] = {
3810 "git", "log", "--no-color", "-1",
3811 "--pretty=format:%P", id, "--", path, NULL
3813 int parents;
3815 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3816 (parents = strlen(buf) / 40) < 0) {
3817 report("Failed to get parent information");
3818 return FALSE;
3820 } else if (parents == 0) {
3821 if (path)
3822 report("Path '%s' does not exist in the parent", path);
3823 else
3824 report("The selected commit has no parents");
3825 return FALSE;
3828 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3829 return FALSE;
3831 string_copy_rev(rev, &buf[41 * parents]);
3832 return TRUE;
3836 * Pager backend
3839 static bool
3840 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3842 char text[SIZEOF_STR];
3844 if (opt_line_number && draw_lineno(view, lineno))
3845 return TRUE;
3847 string_expand(text, sizeof(text), line->data, opt_tab_size);
3848 draw_text(view, line->type, text, TRUE);
3849 return TRUE;
3852 static bool
3853 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3855 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3856 char ref[SIZEOF_STR];
3858 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3859 return TRUE;
3861 /* This is the only fatal call, since it can "corrupt" the buffer. */
3862 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3863 return FALSE;
3865 return TRUE;
3868 static void
3869 add_pager_refs(struct view *view, struct line *line)
3871 char buf[SIZEOF_STR];
3872 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3873 struct ref_list *list;
3874 size_t bufpos = 0, i;
3875 const char *sep = "Refs: ";
3876 bool is_tag = FALSE;
3878 assert(line->type == LINE_COMMIT);
3880 list = get_ref_list(commit_id);
3881 if (!list) {
3882 if (view == VIEW(REQ_VIEW_DIFF))
3883 goto try_add_describe_ref;
3884 return;
3887 for (i = 0; i < list->size; i++) {
3888 struct ref *ref = list->refs[i];
3889 const char *fmt = ref->tag ? "%s[%s]" :
3890 ref->remote ? "%s<%s>" : "%s%s";
3892 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3893 return;
3894 sep = ", ";
3895 if (ref->tag)
3896 is_tag = TRUE;
3899 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3900 try_add_describe_ref:
3901 /* Add <tag>-g<commit_id> "fake" reference. */
3902 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3903 return;
3906 if (bufpos == 0)
3907 return;
3909 add_line_text(view, buf, LINE_PP_REFS);
3912 static bool
3913 pager_read(struct view *view, char *data)
3915 struct line *line;
3917 if (!data)
3918 return TRUE;
3920 line = add_line_text(view, data, get_line_type(data));
3921 if (!line)
3922 return FALSE;
3924 if (line->type == LINE_COMMIT &&
3925 (view == VIEW(REQ_VIEW_DIFF) ||
3926 view == VIEW(REQ_VIEW_LOG)))
3927 add_pager_refs(view, line);
3929 return TRUE;
3932 static enum request
3933 pager_request(struct view *view, enum request request, struct line *line)
3935 int split = 0;
3937 if (request != REQ_ENTER)
3938 return request;
3940 if (line->type == LINE_COMMIT &&
3941 (view == VIEW(REQ_VIEW_LOG) ||
3942 view == VIEW(REQ_VIEW_PAGER))) {
3943 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3944 split = 1;
3947 /* Always scroll the view even if it was split. That way
3948 * you can use Enter to scroll through the log view and
3949 * split open each commit diff. */
3950 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3952 /* FIXME: A minor workaround. Scrolling the view will call report("")
3953 * but if we are scrolling a non-current view this won't properly
3954 * update the view title. */
3955 if (split)
3956 update_view_title(view);
3958 return REQ_NONE;
3961 static bool
3962 pager_grep(struct view *view, struct line *line)
3964 const char *text[] = { line->data, NULL };
3966 return grep_text(view, text);
3969 static void
3970 pager_select(struct view *view, struct line *line)
3972 if (line->type == LINE_COMMIT) {
3973 char *text = (char *)line->data + STRING_SIZE("commit ");
3975 if (view != VIEW(REQ_VIEW_PAGER))
3976 string_copy_rev(view->ref, text);
3977 string_copy_rev(ref_commit, text);
3981 static struct view_ops pager_ops = {
3982 "line",
3983 NULL,
3984 NULL,
3985 pager_read,
3986 pager_draw,
3987 pager_request,
3988 pager_grep,
3989 pager_select,
3992 static const char *log_argv[SIZEOF_ARG] = {
3993 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3996 static enum request
3997 log_request(struct view *view, enum request request, struct line *line)
3999 switch (request) {
4000 case REQ_REFRESH:
4001 load_refs();
4002 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4003 return REQ_NONE;
4004 default:
4005 return pager_request(view, request, line);
4009 static struct view_ops log_ops = {
4010 "line",
4011 log_argv,
4012 NULL,
4013 pager_read,
4014 pager_draw,
4015 log_request,
4016 pager_grep,
4017 pager_select,
4020 static const char *diff_argv[SIZEOF_ARG] = {
4021 "git", "show", "--pretty=fuller", "--no-color", "--root",
4022 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4025 static struct view_ops diff_ops = {
4026 "line",
4027 diff_argv,
4028 NULL,
4029 pager_read,
4030 pager_draw,
4031 pager_request,
4032 pager_grep,
4033 pager_select,
4037 * Help backend
4040 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4042 static bool
4043 help_open_keymap_title(struct view *view, enum keymap keymap)
4045 struct line *line;
4047 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4048 help_keymap_hidden[keymap] ? '+' : '-',
4049 enum_name(keymap_table[keymap]));
4050 if (line)
4051 line->other = keymap;
4053 return help_keymap_hidden[keymap];
4056 static void
4057 help_open_keymap(struct view *view, enum keymap keymap)
4059 const char *group = NULL;
4060 char buf[SIZEOF_STR];
4061 size_t bufpos;
4062 bool add_title = TRUE;
4063 int i;
4065 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4066 const char *key = NULL;
4068 if (req_info[i].request == REQ_NONE)
4069 continue;
4071 if (!req_info[i].request) {
4072 group = req_info[i].help;
4073 continue;
4076 key = get_keys(keymap, req_info[i].request, TRUE);
4077 if (!key || !*key)
4078 continue;
4080 if (add_title && help_open_keymap_title(view, keymap))
4081 return;
4082 add_title = FALSE;
4084 if (group) {
4085 add_line_text(view, group, LINE_HELP_GROUP);
4086 group = NULL;
4089 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4090 enum_name(req_info[i]), req_info[i].help);
4093 group = "External commands:";
4095 for (i = 0; i < run_requests; i++) {
4096 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4097 const char *key;
4098 int argc;
4100 if (!req || req->keymap != keymap)
4101 continue;
4103 key = get_key_name(req->key);
4104 if (!*key)
4105 key = "(no key defined)";
4107 if (add_title && help_open_keymap_title(view, keymap))
4108 return;
4109 if (group) {
4110 add_line_text(view, group, LINE_HELP_GROUP);
4111 group = NULL;
4114 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4115 if (!string_format_from(buf, &bufpos, "%s%s",
4116 argc ? " " : "", req->argv[argc]))
4117 return;
4119 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4123 static bool
4124 help_open(struct view *view)
4126 enum keymap keymap;
4128 reset_view(view);
4129 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4130 add_line_text(view, "", LINE_DEFAULT);
4132 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4133 help_open_keymap(view, keymap);
4135 return TRUE;
4138 static enum request
4139 help_request(struct view *view, enum request request, struct line *line)
4141 switch (request) {
4142 case REQ_ENTER:
4143 if (line->type == LINE_HELP_KEYMAP) {
4144 help_keymap_hidden[line->other] =
4145 !help_keymap_hidden[line->other];
4146 view->p_restore = TRUE;
4147 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4150 return REQ_NONE;
4151 default:
4152 return pager_request(view, request, line);
4156 static struct view_ops help_ops = {
4157 "line",
4158 NULL,
4159 help_open,
4160 NULL,
4161 pager_draw,
4162 help_request,
4163 pager_grep,
4164 pager_select,
4169 * Tree backend
4172 struct tree_stack_entry {
4173 struct tree_stack_entry *prev; /* Entry below this in the stack */
4174 unsigned long lineno; /* Line number to restore */
4175 char *name; /* Position of name in opt_path */
4178 /* The top of the path stack. */
4179 static struct tree_stack_entry *tree_stack = NULL;
4180 unsigned long tree_lineno = 0;
4182 static void
4183 pop_tree_stack_entry(void)
4185 struct tree_stack_entry *entry = tree_stack;
4187 tree_lineno = entry->lineno;
4188 entry->name[0] = 0;
4189 tree_stack = entry->prev;
4190 free(entry);
4193 static void
4194 push_tree_stack_entry(const char *name, unsigned long lineno)
4196 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4197 size_t pathlen = strlen(opt_path);
4199 if (!entry)
4200 return;
4202 entry->prev = tree_stack;
4203 entry->name = opt_path + pathlen;
4204 tree_stack = entry;
4206 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4207 pop_tree_stack_entry();
4208 return;
4211 /* Move the current line to the first tree entry. */
4212 tree_lineno = 1;
4213 entry->lineno = lineno;
4216 /* Parse output from git-ls-tree(1):
4218 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4221 #define SIZEOF_TREE_ATTR \
4222 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4224 #define SIZEOF_TREE_MODE \
4225 STRING_SIZE("100644 ")
4227 #define TREE_ID_OFFSET \
4228 STRING_SIZE("100644 blob ")
4230 struct tree_entry {
4231 char id[SIZEOF_REV];
4232 mode_t mode;
4233 struct time time; /* Date from the author ident. */
4234 const char *author; /* Author of the commit. */
4235 char name[1];
4238 static const char *
4239 tree_path(const struct line *line)
4241 return ((struct tree_entry *) line->data)->name;
4244 static int
4245 tree_compare_entry(const struct line *line1, const struct line *line2)
4247 if (line1->type != line2->type)
4248 return line1->type == LINE_TREE_DIR ? -1 : 1;
4249 return strcmp(tree_path(line1), tree_path(line2));
4252 static const enum sort_field tree_sort_fields[] = {
4253 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4255 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4257 static int
4258 tree_compare(const void *l1, const void *l2)
4260 const struct line *line1 = (const struct line *) l1;
4261 const struct line *line2 = (const struct line *) l2;
4262 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4263 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4265 if (line1->type == LINE_TREE_HEAD)
4266 return -1;
4267 if (line2->type == LINE_TREE_HEAD)
4268 return 1;
4270 switch (get_sort_field(tree_sort_state)) {
4271 case ORDERBY_DATE:
4272 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4274 case ORDERBY_AUTHOR:
4275 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4277 case ORDERBY_NAME:
4278 default:
4279 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4284 static struct line *
4285 tree_entry(struct view *view, enum line_type type, const char *path,
4286 const char *mode, const char *id)
4288 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4289 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4291 if (!entry || !line) {
4292 free(entry);
4293 return NULL;
4296 strncpy(entry->name, path, strlen(path));
4297 if (mode)
4298 entry->mode = strtoul(mode, NULL, 8);
4299 if (id)
4300 string_copy_rev(entry->id, id);
4302 return line;
4305 static bool
4306 tree_read_date(struct view *view, char *text, bool *read_date)
4308 static const char *author_name;
4309 static struct time author_time;
4311 if (!text && *read_date) {
4312 *read_date = FALSE;
4313 return TRUE;
4315 } else if (!text) {
4316 char *path = *opt_path ? opt_path : ".";
4317 /* Find next entry to process */
4318 const char *log_file[] = {
4319 "git", "log", "--no-color", "--pretty=raw",
4320 "--cc", "--raw", view->id, "--", path, NULL
4322 struct io io = {};
4324 if (!view->lines) {
4325 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4326 report("Tree is empty");
4327 return TRUE;
4330 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4331 report("Failed to load tree data");
4332 return TRUE;
4335 io_done(view->pipe);
4336 view->io = io;
4337 *read_date = TRUE;
4338 return FALSE;
4340 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4341 parse_author_line(text + STRING_SIZE("author "),
4342 &author_name, &author_time);
4344 } else if (*text == ':') {
4345 char *pos;
4346 size_t annotated = 1;
4347 size_t i;
4349 pos = strchr(text, '\t');
4350 if (!pos)
4351 return TRUE;
4352 text = pos + 1;
4353 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4354 text += strlen(opt_path);
4355 pos = strchr(text, '/');
4356 if (pos)
4357 *pos = 0;
4359 for (i = 1; i < view->lines; i++) {
4360 struct line *line = &view->line[i];
4361 struct tree_entry *entry = line->data;
4363 annotated += !!entry->author;
4364 if (entry->author || strcmp(entry->name, text))
4365 continue;
4367 entry->author = author_name;
4368 entry->time = author_time;
4369 line->dirty = 1;
4370 break;
4373 if (annotated == view->lines)
4374 io_kill(view->pipe);
4376 return TRUE;
4379 static bool
4380 tree_read(struct view *view, char *text)
4382 static bool read_date = FALSE;
4383 struct tree_entry *data;
4384 struct line *entry, *line;
4385 enum line_type type;
4386 size_t textlen = text ? strlen(text) : 0;
4387 char *path = text + SIZEOF_TREE_ATTR;
4389 if (read_date || !text)
4390 return tree_read_date(view, text, &read_date);
4392 if (textlen <= SIZEOF_TREE_ATTR)
4393 return FALSE;
4394 if (view->lines == 0 &&
4395 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4396 return FALSE;
4398 /* Strip the path part ... */
4399 if (*opt_path) {
4400 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4401 size_t striplen = strlen(opt_path);
4403 if (pathlen > striplen)
4404 memmove(path, path + striplen,
4405 pathlen - striplen + 1);
4407 /* Insert "link" to parent directory. */
4408 if (view->lines == 1 &&
4409 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4410 return FALSE;
4413 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4414 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4415 if (!entry)
4416 return FALSE;
4417 data = entry->data;
4419 /* Skip "Directory ..." and ".." line. */
4420 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4421 if (tree_compare_entry(line, entry) <= 0)
4422 continue;
4424 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4426 line->data = data;
4427 line->type = type;
4428 for (; line <= entry; line++)
4429 line->dirty = line->cleareol = 1;
4430 return TRUE;
4433 if (tree_lineno > view->lineno) {
4434 view->lineno = tree_lineno;
4435 tree_lineno = 0;
4438 return TRUE;
4441 static bool
4442 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4444 struct tree_entry *entry = line->data;
4446 if (line->type == LINE_TREE_HEAD) {
4447 if (draw_text(view, line->type, "Directory path /", TRUE))
4448 return TRUE;
4449 } else {
4450 if (draw_mode(view, entry->mode))
4451 return TRUE;
4453 if (opt_author && draw_author(view, entry->author))
4454 return TRUE;
4456 if (opt_date && draw_date(view, &entry->time))
4457 return TRUE;
4459 if (draw_text(view, line->type, entry->name, TRUE))
4460 return TRUE;
4461 return TRUE;
4464 static void
4465 open_blob_editor()
4467 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4468 int fd = mkstemp(file);
4470 if (fd == -1)
4471 report("Failed to create temporary file");
4472 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4473 report("Failed to save blob data to file");
4474 else
4475 open_editor(file);
4476 if (fd != -1)
4477 unlink(file);
4480 static enum request
4481 tree_request(struct view *view, enum request request, struct line *line)
4483 enum open_flags flags;
4485 switch (request) {
4486 case REQ_VIEW_BLAME:
4487 if (line->type != LINE_TREE_FILE) {
4488 report("Blame only supported for files");
4489 return REQ_NONE;
4492 string_copy(opt_ref, view->vid);
4493 return request;
4495 case REQ_EDIT:
4496 if (line->type != LINE_TREE_FILE) {
4497 report("Edit only supported for files");
4498 } else if (!is_head_commit(view->vid)) {
4499 open_blob_editor();
4500 } else {
4501 open_editor(opt_file);
4503 return REQ_NONE;
4505 case REQ_TOGGLE_SORT_FIELD:
4506 case REQ_TOGGLE_SORT_ORDER:
4507 sort_view(view, request, &tree_sort_state, tree_compare);
4508 return REQ_NONE;
4510 case REQ_PARENT:
4511 if (!*opt_path) {
4512 /* quit view if at top of tree */
4513 return REQ_VIEW_CLOSE;
4515 /* fake 'cd ..' */
4516 line = &view->line[1];
4517 break;
4519 case REQ_ENTER:
4520 break;
4522 default:
4523 return request;
4526 /* Cleanup the stack if the tree view is at a different tree. */
4527 while (!*opt_path && tree_stack)
4528 pop_tree_stack_entry();
4530 switch (line->type) {
4531 case LINE_TREE_DIR:
4532 /* Depending on whether it is a subdirectory or parent link
4533 * mangle the path buffer. */
4534 if (line == &view->line[1] && *opt_path) {
4535 pop_tree_stack_entry();
4537 } else {
4538 const char *basename = tree_path(line);
4540 push_tree_stack_entry(basename, view->lineno);
4543 /* Trees and subtrees share the same ID, so they are not not
4544 * unique like blobs. */
4545 flags = OPEN_RELOAD;
4546 request = REQ_VIEW_TREE;
4547 break;
4549 case LINE_TREE_FILE:
4550 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4551 request = REQ_VIEW_BLOB;
4552 break;
4554 default:
4555 return REQ_NONE;
4558 open_view(view, request, flags);
4559 if (request == REQ_VIEW_TREE)
4560 view->lineno = tree_lineno;
4562 return REQ_NONE;
4565 static bool
4566 tree_grep(struct view *view, struct line *line)
4568 struct tree_entry *entry = line->data;
4569 const char *text[] = {
4570 entry->name,
4571 opt_author ? entry->author : "",
4572 mkdate(&entry->time, opt_date),
4573 NULL
4576 return grep_text(view, text);
4579 static void
4580 tree_select(struct view *view, struct line *line)
4582 struct tree_entry *entry = line->data;
4584 if (line->type == LINE_TREE_FILE) {
4585 string_copy_rev(ref_blob, entry->id);
4586 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4588 } else if (line->type != LINE_TREE_DIR) {
4589 return;
4592 string_copy_rev(view->ref, entry->id);
4595 static bool
4596 tree_prepare(struct view *view)
4598 if (view->lines == 0 && opt_prefix[0]) {
4599 char *pos = opt_prefix;
4601 while (pos && *pos) {
4602 char *end = strchr(pos, '/');
4604 if (end)
4605 *end = 0;
4606 push_tree_stack_entry(pos, 0);
4607 pos = end;
4608 if (end) {
4609 *end = '/';
4610 pos++;
4614 } else if (strcmp(view->vid, view->id)) {
4615 opt_path[0] = 0;
4618 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4621 static const char *tree_argv[SIZEOF_ARG] = {
4622 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4625 static struct view_ops tree_ops = {
4626 "file",
4627 tree_argv,
4628 NULL,
4629 tree_read,
4630 tree_draw,
4631 tree_request,
4632 tree_grep,
4633 tree_select,
4634 tree_prepare,
4637 static bool
4638 blob_read(struct view *view, char *line)
4640 if (!line)
4641 return TRUE;
4642 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4645 static enum request
4646 blob_request(struct view *view, enum request request, struct line *line)
4648 switch (request) {
4649 case REQ_EDIT:
4650 open_blob_editor();
4651 return REQ_NONE;
4652 default:
4653 return pager_request(view, request, line);
4657 static const char *blob_argv[SIZEOF_ARG] = {
4658 "git", "cat-file", "blob", "%(blob)", NULL
4661 static struct view_ops blob_ops = {
4662 "line",
4663 blob_argv,
4664 NULL,
4665 blob_read,
4666 pager_draw,
4667 blob_request,
4668 pager_grep,
4669 pager_select,
4673 * Blame backend
4675 * Loading the blame view is a two phase job:
4677 * 1. File content is read either using opt_file from the
4678 * filesystem or using git-cat-file.
4679 * 2. Then blame information is incrementally added by
4680 * reading output from git-blame.
4683 static const char *blame_head_argv[] = {
4684 "git", "blame", "--incremental", "--", "%(file)", NULL
4687 static const char *blame_ref_argv[] = {
4688 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4691 static const char *blame_cat_file_argv[] = {
4692 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4695 struct blame_commit {
4696 char id[SIZEOF_REV]; /* SHA1 ID. */
4697 char title[128]; /* First line of the commit message. */
4698 const char *author; /* Author of the commit. */
4699 struct time time; /* Date from the author ident. */
4700 char filename[128]; /* Name of file. */
4701 bool has_previous; /* Was a "previous" line detected. */
4704 struct blame {
4705 struct blame_commit *commit;
4706 unsigned long lineno;
4707 char text[1];
4710 static bool
4711 blame_open(struct view *view)
4713 char path[SIZEOF_STR];
4715 if (!view->parent && *opt_prefix) {
4716 string_copy(path, opt_file);
4717 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4718 return FALSE;
4721 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4722 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4723 return FALSE;
4726 setup_update(view, opt_file);
4727 string_format(view->ref, "%s ...", opt_file);
4729 return TRUE;
4732 static struct blame_commit *
4733 get_blame_commit(struct view *view, const char *id)
4735 size_t i;
4737 for (i = 0; i < view->lines; i++) {
4738 struct blame *blame = view->line[i].data;
4740 if (!blame->commit)
4741 continue;
4743 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4744 return blame->commit;
4748 struct blame_commit *commit = calloc(1, sizeof(*commit));
4750 if (commit)
4751 string_ncopy(commit->id, id, SIZEOF_REV);
4752 return commit;
4756 static bool
4757 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4759 const char *pos = *posref;
4761 *posref = NULL;
4762 pos = strchr(pos + 1, ' ');
4763 if (!pos || !isdigit(pos[1]))
4764 return FALSE;
4765 *number = atoi(pos + 1);
4766 if (*number < min || *number > max)
4767 return FALSE;
4769 *posref = pos;
4770 return TRUE;
4773 static struct blame_commit *
4774 parse_blame_commit(struct view *view, const char *text, int *blamed)
4776 struct blame_commit *commit;
4777 struct blame *blame;
4778 const char *pos = text + SIZEOF_REV - 2;
4779 size_t orig_lineno = 0;
4780 size_t lineno;
4781 size_t group;
4783 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4784 return NULL;
4786 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4787 !parse_number(&pos, &lineno, 1, view->lines) ||
4788 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4789 return NULL;
4791 commit = get_blame_commit(view, text);
4792 if (!commit)
4793 return NULL;
4795 *blamed += group;
4796 while (group--) {
4797 struct line *line = &view->line[lineno + group - 1];
4799 blame = line->data;
4800 blame->commit = commit;
4801 blame->lineno = orig_lineno + group - 1;
4802 line->dirty = 1;
4805 return commit;
4808 static bool
4809 blame_read_file(struct view *view, const char *line, bool *read_file)
4811 if (!line) {
4812 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4813 struct io io = {};
4815 if (view->lines == 0 && !view->parent)
4816 die("No blame exist for %s", view->vid);
4818 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4819 report("Failed to load blame data");
4820 return TRUE;
4823 io_done(view->pipe);
4824 view->io = io;
4825 *read_file = FALSE;
4826 return FALSE;
4828 } else {
4829 size_t linelen = strlen(line);
4830 struct blame *blame = malloc(sizeof(*blame) + linelen);
4832 if (!blame)
4833 return FALSE;
4835 blame->commit = NULL;
4836 strncpy(blame->text, line, linelen);
4837 blame->text[linelen] = 0;
4838 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4842 static bool
4843 match_blame_header(const char *name, char **line)
4845 size_t namelen = strlen(name);
4846 bool matched = !strncmp(name, *line, namelen);
4848 if (matched)
4849 *line += namelen;
4851 return matched;
4854 static bool
4855 blame_read(struct view *view, char *line)
4857 static struct blame_commit *commit = NULL;
4858 static int blamed = 0;
4859 static bool read_file = TRUE;
4861 if (read_file)
4862 return blame_read_file(view, line, &read_file);
4864 if (!line) {
4865 /* Reset all! */
4866 commit = NULL;
4867 blamed = 0;
4868 read_file = TRUE;
4869 string_format(view->ref, "%s", view->vid);
4870 if (view_is_displayed(view)) {
4871 update_view_title(view);
4872 redraw_view_from(view, 0);
4874 return TRUE;
4877 if (!commit) {
4878 commit = parse_blame_commit(view, line, &blamed);
4879 string_format(view->ref, "%s %2d%%", view->vid,
4880 view->lines ? blamed * 100 / view->lines : 0);
4882 } else if (match_blame_header("author ", &line)) {
4883 commit->author = get_author(line);
4885 } else if (match_blame_header("author-time ", &line)) {
4886 parse_timesec(&commit->time, line);
4888 } else if (match_blame_header("author-tz ", &line)) {
4889 parse_timezone(&commit->time, line);
4891 } else if (match_blame_header("summary ", &line)) {
4892 string_ncopy(commit->title, line, strlen(line));
4894 } else if (match_blame_header("previous ", &line)) {
4895 commit->has_previous = TRUE;
4897 } else if (match_blame_header("filename ", &line)) {
4898 string_ncopy(commit->filename, line, strlen(line));
4899 commit = NULL;
4902 return TRUE;
4905 static bool
4906 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4908 struct blame *blame = line->data;
4909 struct time *time = NULL;
4910 const char *id = NULL, *author = NULL;
4911 char text[SIZEOF_STR];
4913 if (blame->commit && *blame->commit->filename) {
4914 id = blame->commit->id;
4915 author = blame->commit->author;
4916 time = &blame->commit->time;
4919 if (opt_date && draw_date(view, time))
4920 return TRUE;
4922 if (opt_author && draw_author(view, author))
4923 return TRUE;
4925 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4926 return TRUE;
4928 if (draw_lineno(view, lineno))
4929 return TRUE;
4931 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4932 draw_text(view, LINE_DEFAULT, text, TRUE);
4933 return TRUE;
4936 static bool
4937 check_blame_commit(struct blame *blame, bool check_null_id)
4939 if (!blame->commit)
4940 report("Commit data not loaded yet");
4941 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4942 report("No commit exist for the selected line");
4943 else
4944 return TRUE;
4945 return FALSE;
4948 static void
4949 setup_blame_parent_line(struct view *view, struct blame *blame)
4951 const char *diff_tree_argv[] = {
4952 "git", "diff-tree", "-U0", blame->commit->id,
4953 "--", blame->commit->filename, NULL
4955 struct io io = {};
4956 int parent_lineno = -1;
4957 int blamed_lineno = -1;
4958 char *line;
4960 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
4961 return;
4963 while ((line = io_get(&io, '\n', TRUE))) {
4964 if (*line == '@') {
4965 char *pos = strchr(line, '+');
4967 parent_lineno = atoi(line + 4);
4968 if (pos)
4969 blamed_lineno = atoi(pos + 1);
4971 } else if (*line == '+' && parent_lineno != -1) {
4972 if (blame->lineno == blamed_lineno - 1 &&
4973 !strcmp(blame->text, line + 1)) {
4974 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4975 break;
4977 blamed_lineno++;
4981 io_done(&io);
4984 static enum request
4985 blame_request(struct view *view, enum request request, struct line *line)
4987 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4988 struct blame *blame = line->data;
4990 switch (request) {
4991 case REQ_VIEW_BLAME:
4992 if (check_blame_commit(blame, TRUE)) {
4993 string_copy(opt_ref, blame->commit->id);
4994 string_copy(opt_file, blame->commit->filename);
4995 if (blame->lineno)
4996 view->lineno = blame->lineno;
4997 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4999 break;
5001 case REQ_PARENT:
5002 if (check_blame_commit(blame, TRUE) &&
5003 select_commit_parent(blame->commit->id, opt_ref,
5004 blame->commit->filename)) {
5005 string_copy(opt_file, blame->commit->filename);
5006 setup_blame_parent_line(view, blame);
5007 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5009 break;
5011 case REQ_ENTER:
5012 if (!check_blame_commit(blame, FALSE))
5013 break;
5015 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5016 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5017 break;
5019 if (!strcmp(blame->commit->id, NULL_ID)) {
5020 struct view *diff = VIEW(REQ_VIEW_DIFF);
5021 const char *diff_index_argv[] = {
5022 "git", "diff-index", "--root", "--patch-with-stat",
5023 "-C", "-M", "HEAD", "--", view->vid, NULL
5026 if (!blame->commit->has_previous) {
5027 diff_index_argv[1] = "diff";
5028 diff_index_argv[2] = "--no-color";
5029 diff_index_argv[6] = "--";
5030 diff_index_argv[7] = "/dev/null";
5033 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5034 report("Failed to allocate diff command");
5035 break;
5037 flags |= OPEN_PREPARED;
5040 open_view(view, REQ_VIEW_DIFF, flags);
5041 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5042 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5043 break;
5045 default:
5046 return request;
5049 return REQ_NONE;
5052 static bool
5053 blame_grep(struct view *view, struct line *line)
5055 struct blame *blame = line->data;
5056 struct blame_commit *commit = blame->commit;
5057 const char *text[] = {
5058 blame->text,
5059 commit ? commit->title : "",
5060 commit ? commit->id : "",
5061 commit && opt_author ? commit->author : "",
5062 commit ? mkdate(&commit->time, opt_date) : "",
5063 NULL
5066 return grep_text(view, text);
5069 static void
5070 blame_select(struct view *view, struct line *line)
5072 struct blame *blame = line->data;
5073 struct blame_commit *commit = blame->commit;
5075 if (!commit)
5076 return;
5078 if (!strcmp(commit->id, NULL_ID))
5079 string_ncopy(ref_commit, "HEAD", 4);
5080 else
5081 string_copy_rev(ref_commit, commit->id);
5084 static struct view_ops blame_ops = {
5085 "line",
5086 NULL,
5087 blame_open,
5088 blame_read,
5089 blame_draw,
5090 blame_request,
5091 blame_grep,
5092 blame_select,
5096 * Branch backend
5099 struct branch {
5100 const char *author; /* Author of the last commit. */
5101 struct time time; /* Date of the last activity. */
5102 const struct ref *ref; /* Name and commit ID information. */
5105 static const struct ref branch_all;
5107 static const enum sort_field branch_sort_fields[] = {
5108 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5110 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5112 static int
5113 branch_compare(const void *l1, const void *l2)
5115 const struct branch *branch1 = ((const struct line *) l1)->data;
5116 const struct branch *branch2 = ((const struct line *) l2)->data;
5118 switch (get_sort_field(branch_sort_state)) {
5119 case ORDERBY_DATE:
5120 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5122 case ORDERBY_AUTHOR:
5123 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5125 case ORDERBY_NAME:
5126 default:
5127 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5131 static bool
5132 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5134 struct branch *branch = line->data;
5135 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5137 if (opt_date && draw_date(view, &branch->time))
5138 return TRUE;
5140 if (opt_author && draw_author(view, branch->author))
5141 return TRUE;
5143 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5144 return TRUE;
5147 static enum request
5148 branch_request(struct view *view, enum request request, struct line *line)
5150 struct branch *branch = line->data;
5152 switch (request) {
5153 case REQ_REFRESH:
5154 load_refs();
5155 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5156 return REQ_NONE;
5158 case REQ_TOGGLE_SORT_FIELD:
5159 case REQ_TOGGLE_SORT_ORDER:
5160 sort_view(view, request, &branch_sort_state, branch_compare);
5161 return REQ_NONE;
5163 case REQ_ENTER:
5164 if (branch->ref == &branch_all) {
5165 const char *all_branches_argv[] = {
5166 "git", "log", "--no-color", "--pretty=raw", "--parents",
5167 "--topo-order", "--all", NULL
5169 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5171 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5172 report("Failed to load view of all branches");
5173 return REQ_NONE;
5175 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5176 } else {
5177 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5179 return REQ_NONE;
5181 default:
5182 return request;
5186 static bool
5187 branch_read(struct view *view, char *line)
5189 static char id[SIZEOF_REV];
5190 struct branch *reference;
5191 size_t i;
5193 if (!line)
5194 return TRUE;
5196 switch (get_line_type(line)) {
5197 case LINE_COMMIT:
5198 string_copy_rev(id, line + STRING_SIZE("commit "));
5199 return TRUE;
5201 case LINE_AUTHOR:
5202 for (i = 0, reference = NULL; i < view->lines; i++) {
5203 struct branch *branch = view->line[i].data;
5205 if (strcmp(branch->ref->id, id))
5206 continue;
5208 view->line[i].dirty = TRUE;
5209 if (reference) {
5210 branch->author = reference->author;
5211 branch->time = reference->time;
5212 continue;
5215 parse_author_line(line + STRING_SIZE("author "),
5216 &branch->author, &branch->time);
5217 reference = branch;
5219 return TRUE;
5221 default:
5222 return TRUE;
5227 static bool
5228 branch_open_visitor(void *data, const struct ref *ref)
5230 struct view *view = data;
5231 struct branch *branch;
5233 if (ref->tag || ref->ltag || ref->remote)
5234 return TRUE;
5236 branch = calloc(1, sizeof(*branch));
5237 if (!branch)
5238 return FALSE;
5240 branch->ref = ref;
5241 return !!add_line_data(view, branch, LINE_DEFAULT);
5244 static bool
5245 branch_open(struct view *view)
5247 const char *branch_log[] = {
5248 "git", "log", "--no-color", "--pretty=raw",
5249 "--simplify-by-decoration", "--all", NULL
5252 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5253 report("Failed to load branch data");
5254 return TRUE;
5257 setup_update(view, view->id);
5258 branch_open_visitor(view, &branch_all);
5259 foreach_ref(branch_open_visitor, view);
5260 view->p_restore = TRUE;
5262 return TRUE;
5265 static bool
5266 branch_grep(struct view *view, struct line *line)
5268 struct branch *branch = line->data;
5269 const char *text[] = {
5270 branch->ref->name,
5271 branch->author,
5272 NULL
5275 return grep_text(view, text);
5278 static void
5279 branch_select(struct view *view, struct line *line)
5281 struct branch *branch = line->data;
5283 string_copy_rev(view->ref, branch->ref->id);
5284 string_copy_rev(ref_commit, branch->ref->id);
5285 string_copy_rev(ref_head, branch->ref->id);
5288 static struct view_ops branch_ops = {
5289 "branch",
5290 NULL,
5291 branch_open,
5292 branch_read,
5293 branch_draw,
5294 branch_request,
5295 branch_grep,
5296 branch_select,
5300 * Status backend
5303 struct status {
5304 char status;
5305 struct {
5306 mode_t mode;
5307 char rev[SIZEOF_REV];
5308 char name[SIZEOF_STR];
5309 } old;
5310 struct {
5311 mode_t mode;
5312 char rev[SIZEOF_REV];
5313 char name[SIZEOF_STR];
5314 } new;
5317 static char status_onbranch[SIZEOF_STR];
5318 static struct status stage_status;
5319 static enum line_type stage_line_type;
5320 static size_t stage_chunks;
5321 static int *stage_chunk;
5323 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5325 /* This should work even for the "On branch" line. */
5326 static inline bool
5327 status_has_none(struct view *view, struct line *line)
5329 return line < view->line + view->lines && !line[1].data;
5332 /* Get fields from the diff line:
5333 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5335 static inline bool
5336 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5338 const char *old_mode = buf + 1;
5339 const char *new_mode = buf + 8;
5340 const char *old_rev = buf + 15;
5341 const char *new_rev = buf + 56;
5342 const char *status = buf + 97;
5344 if (bufsize < 98 ||
5345 old_mode[-1] != ':' ||
5346 new_mode[-1] != ' ' ||
5347 old_rev[-1] != ' ' ||
5348 new_rev[-1] != ' ' ||
5349 status[-1] != ' ')
5350 return FALSE;
5352 file->status = *status;
5354 string_copy_rev(file->old.rev, old_rev);
5355 string_copy_rev(file->new.rev, new_rev);
5357 file->old.mode = strtoul(old_mode, NULL, 8);
5358 file->new.mode = strtoul(new_mode, NULL, 8);
5360 file->old.name[0] = file->new.name[0] = 0;
5362 return TRUE;
5365 static bool
5366 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5368 struct status *unmerged = NULL;
5369 char *buf;
5370 struct io io = {};
5372 if (!io_run(&io, argv, opt_cdup, IO_RD))
5373 return FALSE;
5375 add_line_data(view, NULL, type);
5377 while ((buf = io_get(&io, 0, TRUE))) {
5378 struct status *file = unmerged;
5380 if (!file) {
5381 file = calloc(1, sizeof(*file));
5382 if (!file || !add_line_data(view, file, type))
5383 goto error_out;
5386 /* Parse diff info part. */
5387 if (status) {
5388 file->status = status;
5389 if (status == 'A')
5390 string_copy(file->old.rev, NULL_ID);
5392 } else if (!file->status || file == unmerged) {
5393 if (!status_get_diff(file, buf, strlen(buf)))
5394 goto error_out;
5396 buf = io_get(&io, 0, TRUE);
5397 if (!buf)
5398 break;
5400 /* Collapse all modified entries that follow an
5401 * associated unmerged entry. */
5402 if (unmerged == file) {
5403 unmerged->status = 'U';
5404 unmerged = NULL;
5405 } else if (file->status == 'U') {
5406 unmerged = file;
5410 /* Grab the old name for rename/copy. */
5411 if (!*file->old.name &&
5412 (file->status == 'R' || file->status == 'C')) {
5413 string_ncopy(file->old.name, buf, strlen(buf));
5415 buf = io_get(&io, 0, TRUE);
5416 if (!buf)
5417 break;
5420 /* git-ls-files just delivers a NUL separated list of
5421 * file names similar to the second half of the
5422 * git-diff-* output. */
5423 string_ncopy(file->new.name, buf, strlen(buf));
5424 if (!*file->old.name)
5425 string_copy(file->old.name, file->new.name);
5426 file = NULL;
5429 if (io_error(&io)) {
5430 error_out:
5431 io_done(&io);
5432 return FALSE;
5435 if (!view->line[view->lines - 1].data)
5436 add_line_data(view, NULL, LINE_STAT_NONE);
5438 io_done(&io);
5439 return TRUE;
5442 /* Don't show unmerged entries in the staged section. */
5443 static const char *status_diff_index_argv[] = {
5444 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5445 "--cached", "-M", "HEAD", NULL
5448 static const char *status_diff_files_argv[] = {
5449 "git", "diff-files", "-z", NULL
5452 static const char *status_list_other_argv[] = {
5453 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5456 static const char *status_list_no_head_argv[] = {
5457 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5460 static const char *update_index_argv[] = {
5461 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5464 /* Restore the previous line number to stay in the context or select a
5465 * line with something that can be updated. */
5466 static void
5467 status_restore(struct view *view)
5469 if (view->p_lineno >= view->lines)
5470 view->p_lineno = view->lines - 1;
5471 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5472 view->p_lineno++;
5473 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5474 view->p_lineno--;
5476 /* If the above fails, always skip the "On branch" line. */
5477 if (view->p_lineno < view->lines)
5478 view->lineno = view->p_lineno;
5479 else
5480 view->lineno = 1;
5482 if (view->lineno < view->offset)
5483 view->offset = view->lineno;
5484 else if (view->offset + view->height <= view->lineno)
5485 view->offset = view->lineno - view->height + 1;
5487 view->p_restore = FALSE;
5490 static void
5491 status_update_onbranch(void)
5493 static const char *paths[][2] = {
5494 { "rebase-apply/rebasing", "Rebasing" },
5495 { "rebase-apply/applying", "Applying mailbox" },
5496 { "rebase-apply/", "Rebasing mailbox" },
5497 { "rebase-merge/interactive", "Interactive rebase" },
5498 { "rebase-merge/", "Rebase merge" },
5499 { "MERGE_HEAD", "Merging" },
5500 { "BISECT_LOG", "Bisecting" },
5501 { "HEAD", "On branch" },
5503 char buf[SIZEOF_STR];
5504 struct stat stat;
5505 int i;
5507 if (is_initial_commit()) {
5508 string_copy(status_onbranch, "Initial commit");
5509 return;
5512 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5513 char *head = opt_head;
5515 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5516 lstat(buf, &stat) < 0)
5517 continue;
5519 if (!*opt_head) {
5520 struct io io = {};
5522 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5523 io_read_buf(&io, buf, sizeof(buf))) {
5524 head = buf;
5525 if (!prefixcmp(head, "refs/heads/"))
5526 head += STRING_SIZE("refs/heads/");
5530 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5531 string_copy(status_onbranch, opt_head);
5532 return;
5535 string_copy(status_onbranch, "Not currently on any branch");
5538 /* First parse staged info using git-diff-index(1), then parse unstaged
5539 * info using git-diff-files(1), and finally untracked files using
5540 * git-ls-files(1). */
5541 static bool
5542 status_open(struct view *view)
5544 reset_view(view);
5546 add_line_data(view, NULL, LINE_STAT_HEAD);
5547 status_update_onbranch();
5549 io_run_bg(update_index_argv);
5551 if (is_initial_commit()) {
5552 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5553 return FALSE;
5554 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5555 return FALSE;
5558 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5559 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5560 return FALSE;
5562 /* Restore the exact position or use the specialized restore
5563 * mode? */
5564 if (!view->p_restore)
5565 status_restore(view);
5566 return TRUE;
5569 static bool
5570 status_draw(struct view *view, struct line *line, unsigned int lineno)
5572 struct status *status = line->data;
5573 enum line_type type;
5574 const char *text;
5576 if (!status) {
5577 switch (line->type) {
5578 case LINE_STAT_STAGED:
5579 type = LINE_STAT_SECTION;
5580 text = "Changes to be committed:";
5581 break;
5583 case LINE_STAT_UNSTAGED:
5584 type = LINE_STAT_SECTION;
5585 text = "Changed but not updated:";
5586 break;
5588 case LINE_STAT_UNTRACKED:
5589 type = LINE_STAT_SECTION;
5590 text = "Untracked files:";
5591 break;
5593 case LINE_STAT_NONE:
5594 type = LINE_DEFAULT;
5595 text = " (no files)";
5596 break;
5598 case LINE_STAT_HEAD:
5599 type = LINE_STAT_HEAD;
5600 text = status_onbranch;
5601 break;
5603 default:
5604 return FALSE;
5606 } else {
5607 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5609 buf[0] = status->status;
5610 if (draw_text(view, line->type, buf, TRUE))
5611 return TRUE;
5612 type = LINE_DEFAULT;
5613 text = status->new.name;
5616 draw_text(view, type, text, TRUE);
5617 return TRUE;
5620 static enum request
5621 status_load_error(struct view *view, struct view *stage, const char *path)
5623 if (displayed_views() == 2 || display[current_view] != view)
5624 maximize_view(view);
5625 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5626 return REQ_NONE;
5629 static enum request
5630 status_enter(struct view *view, struct line *line)
5632 struct status *status = line->data;
5633 const char *oldpath = status ? status->old.name : NULL;
5634 /* Diffs for unmerged entries are empty when passing the new
5635 * path, so leave it empty. */
5636 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5637 const char *info;
5638 enum open_flags split;
5639 struct view *stage = VIEW(REQ_VIEW_STAGE);
5641 if (line->type == LINE_STAT_NONE ||
5642 (!status && line[1].type == LINE_STAT_NONE)) {
5643 report("No file to diff");
5644 return REQ_NONE;
5647 switch (line->type) {
5648 case LINE_STAT_STAGED:
5649 if (is_initial_commit()) {
5650 const char *no_head_diff_argv[] = {
5651 "git", "diff", "--no-color", "--patch-with-stat",
5652 "--", "/dev/null", newpath, NULL
5655 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5656 return status_load_error(view, stage, newpath);
5657 } else {
5658 const char *index_show_argv[] = {
5659 "git", "diff-index", "--root", "--patch-with-stat",
5660 "-C", "-M", "--cached", "HEAD", "--",
5661 oldpath, newpath, NULL
5664 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5665 return status_load_error(view, stage, newpath);
5668 if (status)
5669 info = "Staged changes to %s";
5670 else
5671 info = "Staged changes";
5672 break;
5674 case LINE_STAT_UNSTAGED:
5676 const char *files_show_argv[] = {
5677 "git", "diff-files", "--root", "--patch-with-stat",
5678 "-C", "-M", "--", oldpath, newpath, NULL
5681 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5682 return status_load_error(view, stage, newpath);
5683 if (status)
5684 info = "Unstaged changes to %s";
5685 else
5686 info = "Unstaged changes";
5687 break;
5689 case LINE_STAT_UNTRACKED:
5690 if (!newpath) {
5691 report("No file to show");
5692 return REQ_NONE;
5695 if (!suffixcmp(status->new.name, -1, "/")) {
5696 report("Cannot display a directory");
5697 return REQ_NONE;
5700 if (!prepare_update_file(stage, newpath))
5701 return status_load_error(view, stage, newpath);
5702 info = "Untracked file %s";
5703 break;
5705 case LINE_STAT_HEAD:
5706 return REQ_NONE;
5708 default:
5709 die("line type %d not handled in switch", line->type);
5712 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5713 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5714 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5715 if (status) {
5716 stage_status = *status;
5717 } else {
5718 memset(&stage_status, 0, sizeof(stage_status));
5721 stage_line_type = line->type;
5722 stage_chunks = 0;
5723 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5726 return REQ_NONE;
5729 static bool
5730 status_exists(struct status *status, enum line_type type)
5732 struct view *view = VIEW(REQ_VIEW_STATUS);
5733 unsigned long lineno;
5735 for (lineno = 0; lineno < view->lines; lineno++) {
5736 struct line *line = &view->line[lineno];
5737 struct status *pos = line->data;
5739 if (line->type != type)
5740 continue;
5741 if (!pos && (!status || !status->status) && line[1].data) {
5742 select_view_line(view, lineno);
5743 return TRUE;
5745 if (pos && !strcmp(status->new.name, pos->new.name)) {
5746 select_view_line(view, lineno);
5747 return TRUE;
5751 return FALSE;
5755 static bool
5756 status_update_prepare(struct io *io, enum line_type type)
5758 const char *staged_argv[] = {
5759 "git", "update-index", "-z", "--index-info", NULL
5761 const char *others_argv[] = {
5762 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5765 switch (type) {
5766 case LINE_STAT_STAGED:
5767 return io_run(io, staged_argv, opt_cdup, IO_WR);
5769 case LINE_STAT_UNSTAGED:
5770 case LINE_STAT_UNTRACKED:
5771 return io_run(io, others_argv, opt_cdup, IO_WR);
5773 default:
5774 die("line type %d not handled in switch", type);
5775 return FALSE;
5779 static bool
5780 status_update_write(struct io *io, struct status *status, enum line_type type)
5782 char buf[SIZEOF_STR];
5783 size_t bufsize = 0;
5785 switch (type) {
5786 case LINE_STAT_STAGED:
5787 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5788 status->old.mode,
5789 status->old.rev,
5790 status->old.name, 0))
5791 return FALSE;
5792 break;
5794 case LINE_STAT_UNSTAGED:
5795 case LINE_STAT_UNTRACKED:
5796 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5797 return FALSE;
5798 break;
5800 default:
5801 die("line type %d not handled in switch", type);
5804 return io_write(io, buf, bufsize);
5807 static bool
5808 status_update_file(struct status *status, enum line_type type)
5810 struct io io = {};
5811 bool result;
5813 if (!status_update_prepare(&io, type))
5814 return FALSE;
5816 result = status_update_write(&io, status, type);
5817 return io_done(&io) && result;
5820 static bool
5821 status_update_files(struct view *view, struct line *line)
5823 char buf[sizeof(view->ref)];
5824 struct io io = {};
5825 bool result = TRUE;
5826 struct line *pos = view->line + view->lines;
5827 int files = 0;
5828 int file, done;
5829 int cursor_y = -1, cursor_x = -1;
5831 if (!status_update_prepare(&io, line->type))
5832 return FALSE;
5834 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5835 files++;
5837 string_copy(buf, view->ref);
5838 getsyx(cursor_y, cursor_x);
5839 for (file = 0, done = 5; result && file < files; line++, file++) {
5840 int almost_done = file * 100 / files;
5842 if (almost_done > done) {
5843 done = almost_done;
5844 string_format(view->ref, "updating file %u of %u (%d%% done)",
5845 file, files, done);
5846 update_view_title(view);
5847 setsyx(cursor_y, cursor_x);
5848 doupdate();
5850 result = status_update_write(&io, line->data, line->type);
5852 string_copy(view->ref, buf);
5854 return io_done(&io) && result;
5857 static bool
5858 status_update(struct view *view)
5860 struct line *line = &view->line[view->lineno];
5862 assert(view->lines);
5864 if (!line->data) {
5865 /* This should work even for the "On branch" line. */
5866 if (line < view->line + view->lines && !line[1].data) {
5867 report("Nothing to update");
5868 return FALSE;
5871 if (!status_update_files(view, line + 1)) {
5872 report("Failed to update file status");
5873 return FALSE;
5876 } else if (!status_update_file(line->data, line->type)) {
5877 report("Failed to update file status");
5878 return FALSE;
5881 return TRUE;
5884 static bool
5885 status_revert(struct status *status, enum line_type type, bool has_none)
5887 if (!status || type != LINE_STAT_UNSTAGED) {
5888 if (type == LINE_STAT_STAGED) {
5889 report("Cannot revert changes to staged files");
5890 } else if (type == LINE_STAT_UNTRACKED) {
5891 report("Cannot revert changes to untracked files");
5892 } else if (has_none) {
5893 report("Nothing to revert");
5894 } else {
5895 report("Cannot revert changes to multiple files");
5898 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5899 char mode[10] = "100644";
5900 const char *reset_argv[] = {
5901 "git", "update-index", "--cacheinfo", mode,
5902 status->old.rev, status->old.name, NULL
5904 const char *checkout_argv[] = {
5905 "git", "checkout", "--", status->old.name, NULL
5908 if (status->status == 'U') {
5909 string_format(mode, "%5o", status->old.mode);
5911 if (status->old.mode == 0 && status->new.mode == 0) {
5912 reset_argv[2] = "--force-remove";
5913 reset_argv[3] = status->old.name;
5914 reset_argv[4] = NULL;
5917 if (!io_run_fg(reset_argv, opt_cdup))
5918 return FALSE;
5919 if (status->old.mode == 0 && status->new.mode == 0)
5920 return TRUE;
5923 return io_run_fg(checkout_argv, opt_cdup);
5926 return FALSE;
5929 static enum request
5930 status_request(struct view *view, enum request request, struct line *line)
5932 struct status *status = line->data;
5934 switch (request) {
5935 case REQ_STATUS_UPDATE:
5936 if (!status_update(view))
5937 return REQ_NONE;
5938 break;
5940 case REQ_STATUS_REVERT:
5941 if (!status_revert(status, line->type, status_has_none(view, line)))
5942 return REQ_NONE;
5943 break;
5945 case REQ_STATUS_MERGE:
5946 if (!status || status->status != 'U') {
5947 report("Merging only possible for files with unmerged status ('U').");
5948 return REQ_NONE;
5950 open_mergetool(status->new.name);
5951 break;
5953 case REQ_EDIT:
5954 if (!status)
5955 return request;
5956 if (status->status == 'D') {
5957 report("File has been deleted.");
5958 return REQ_NONE;
5961 open_editor(status->new.name);
5962 break;
5964 case REQ_VIEW_BLAME:
5965 if (status)
5966 opt_ref[0] = 0;
5967 return request;
5969 case REQ_ENTER:
5970 /* After returning the status view has been split to
5971 * show the stage view. No further reloading is
5972 * necessary. */
5973 return status_enter(view, line);
5975 case REQ_REFRESH:
5976 /* Simply reload the view. */
5977 break;
5979 default:
5980 return request;
5983 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5985 return REQ_NONE;
5988 static void
5989 status_select(struct view *view, struct line *line)
5991 struct status *status = line->data;
5992 char file[SIZEOF_STR] = "all files";
5993 const char *text;
5994 const char *key;
5996 if (status && !string_format(file, "'%s'", status->new.name))
5997 return;
5999 if (!status && line[1].type == LINE_STAT_NONE)
6000 line++;
6002 switch (line->type) {
6003 case LINE_STAT_STAGED:
6004 text = "Press %s to unstage %s for commit";
6005 break;
6007 case LINE_STAT_UNSTAGED:
6008 text = "Press %s to stage %s for commit";
6009 break;
6011 case LINE_STAT_UNTRACKED:
6012 text = "Press %s to stage %s for addition";
6013 break;
6015 case LINE_STAT_HEAD:
6016 case LINE_STAT_NONE:
6017 text = "Nothing to update";
6018 break;
6020 default:
6021 die("line type %d not handled in switch", line->type);
6024 if (status && status->status == 'U') {
6025 text = "Press %s to resolve conflict in %s";
6026 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6028 } else {
6029 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6032 string_format(view->ref, text, key, file);
6033 if (status)
6034 string_copy(opt_file, status->new.name);
6037 static bool
6038 status_grep(struct view *view, struct line *line)
6040 struct status *status = line->data;
6042 if (status) {
6043 const char buf[2] = { status->status, 0 };
6044 const char *text[] = { status->new.name, buf, NULL };
6046 return grep_text(view, text);
6049 return FALSE;
6052 static struct view_ops status_ops = {
6053 "file",
6054 NULL,
6055 status_open,
6056 NULL,
6057 status_draw,
6058 status_request,
6059 status_grep,
6060 status_select,
6064 static bool
6065 stage_diff_write(struct io *io, struct line *line, struct line *end)
6067 while (line < end) {
6068 if (!io_write(io, line->data, strlen(line->data)) ||
6069 !io_write(io, "\n", 1))
6070 return FALSE;
6071 line++;
6072 if (line->type == LINE_DIFF_CHUNK ||
6073 line->type == LINE_DIFF_HEADER)
6074 break;
6077 return TRUE;
6080 static struct line *
6081 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6083 for (; view->line < line; line--)
6084 if (line->type == type)
6085 return line;
6087 return NULL;
6090 static bool
6091 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6093 const char *apply_argv[SIZEOF_ARG] = {
6094 "git", "apply", "--whitespace=nowarn", NULL
6096 struct line *diff_hdr;
6097 struct io io = {};
6098 int argc = 3;
6100 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6101 if (!diff_hdr)
6102 return FALSE;
6104 if (!revert)
6105 apply_argv[argc++] = "--cached";
6106 if (revert || stage_line_type == LINE_STAT_STAGED)
6107 apply_argv[argc++] = "-R";
6108 apply_argv[argc++] = "-";
6109 apply_argv[argc++] = NULL;
6110 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6111 return FALSE;
6113 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6114 !stage_diff_write(&io, chunk, view->line + view->lines))
6115 chunk = NULL;
6117 io_done(&io);
6118 io_run_bg(update_index_argv);
6120 return chunk ? TRUE : FALSE;
6123 static bool
6124 stage_update(struct view *view, struct line *line)
6126 struct line *chunk = NULL;
6128 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6129 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6131 if (chunk) {
6132 if (!stage_apply_chunk(view, chunk, FALSE)) {
6133 report("Failed to apply chunk");
6134 return FALSE;
6137 } else if (!stage_status.status) {
6138 view = VIEW(REQ_VIEW_STATUS);
6140 for (line = view->line; line < view->line + view->lines; line++)
6141 if (line->type == stage_line_type)
6142 break;
6144 if (!status_update_files(view, line + 1)) {
6145 report("Failed to update files");
6146 return FALSE;
6149 } else if (!status_update_file(&stage_status, stage_line_type)) {
6150 report("Failed to update file");
6151 return FALSE;
6154 return TRUE;
6157 static bool
6158 stage_revert(struct view *view, struct line *line)
6160 struct line *chunk = NULL;
6162 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6163 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6165 if (chunk) {
6166 if (!prompt_yesno("Are you sure you want to revert changes?"))
6167 return FALSE;
6169 if (!stage_apply_chunk(view, chunk, TRUE)) {
6170 report("Failed to revert chunk");
6171 return FALSE;
6173 return TRUE;
6175 } else {
6176 return status_revert(stage_status.status ? &stage_status : NULL,
6177 stage_line_type, FALSE);
6182 static void
6183 stage_next(struct view *view, struct line *line)
6185 int i;
6187 if (!stage_chunks) {
6188 for (line = view->line; line < view->line + view->lines; line++) {
6189 if (line->type != LINE_DIFF_CHUNK)
6190 continue;
6192 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6193 report("Allocation failure");
6194 return;
6197 stage_chunk[stage_chunks++] = line - view->line;
6201 for (i = 0; i < stage_chunks; i++) {
6202 if (stage_chunk[i] > view->lineno) {
6203 do_scroll_view(view, stage_chunk[i] - view->lineno);
6204 report("Chunk %d of %d", i + 1, stage_chunks);
6205 return;
6209 report("No next chunk found");
6212 static enum request
6213 stage_request(struct view *view, enum request request, struct line *line)
6215 switch (request) {
6216 case REQ_STATUS_UPDATE:
6217 if (!stage_update(view, line))
6218 return REQ_NONE;
6219 break;
6221 case REQ_STATUS_REVERT:
6222 if (!stage_revert(view, line))
6223 return REQ_NONE;
6224 break;
6226 case REQ_STAGE_NEXT:
6227 if (stage_line_type == LINE_STAT_UNTRACKED) {
6228 report("File is untracked; press %s to add",
6229 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6230 return REQ_NONE;
6232 stage_next(view, line);
6233 return REQ_NONE;
6235 case REQ_EDIT:
6236 if (!stage_status.new.name[0])
6237 return request;
6238 if (stage_status.status == 'D') {
6239 report("File has been deleted.");
6240 return REQ_NONE;
6243 open_editor(stage_status.new.name);
6244 break;
6246 case REQ_REFRESH:
6247 /* Reload everything ... */
6248 break;
6250 case REQ_VIEW_BLAME:
6251 if (stage_status.new.name[0]) {
6252 string_copy(opt_file, stage_status.new.name);
6253 opt_ref[0] = 0;
6255 return request;
6257 case REQ_ENTER:
6258 return pager_request(view, request, line);
6260 default:
6261 return request;
6264 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6265 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6267 /* Check whether the staged entry still exists, and close the
6268 * stage view if it doesn't. */
6269 if (!status_exists(&stage_status, stage_line_type)) {
6270 status_restore(VIEW(REQ_VIEW_STATUS));
6271 return REQ_VIEW_CLOSE;
6274 if (stage_line_type == LINE_STAT_UNTRACKED) {
6275 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6276 report("Cannot display a directory");
6277 return REQ_NONE;
6280 if (!prepare_update_file(view, stage_status.new.name)) {
6281 report("Failed to open file: %s", strerror(errno));
6282 return REQ_NONE;
6285 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6287 return REQ_NONE;
6290 static struct view_ops stage_ops = {
6291 "line",
6292 NULL,
6293 NULL,
6294 pager_read,
6295 pager_draw,
6296 stage_request,
6297 pager_grep,
6298 pager_select,
6303 * Revision graph
6306 struct commit {
6307 char id[SIZEOF_REV]; /* SHA1 ID. */
6308 char title[128]; /* First line of the commit message. */
6309 const char *author; /* Author of the commit. */
6310 struct time time; /* Date from the author ident. */
6311 struct ref_list *refs; /* Repository references. */
6312 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6313 size_t graph_size; /* The width of the graph array. */
6314 bool has_parents; /* Rewritten --parents seen. */
6317 /* Size of rev graph with no "padding" columns */
6318 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6320 struct rev_graph {
6321 struct rev_graph *prev, *next, *parents;
6322 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6323 size_t size;
6324 struct commit *commit;
6325 size_t pos;
6326 unsigned int boundary:1;
6329 /* Parents of the commit being visualized. */
6330 static struct rev_graph graph_parents[4];
6332 /* The current stack of revisions on the graph. */
6333 static struct rev_graph graph_stacks[4] = {
6334 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6335 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6336 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6337 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6340 static inline bool
6341 graph_parent_is_merge(struct rev_graph *graph)
6343 return graph->parents->size > 1;
6346 static inline void
6347 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6349 struct commit *commit = graph->commit;
6351 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6352 commit->graph[commit->graph_size++] = symbol;
6355 static void
6356 clear_rev_graph(struct rev_graph *graph)
6358 graph->boundary = 0;
6359 graph->size = graph->pos = 0;
6360 graph->commit = NULL;
6361 memset(graph->parents, 0, sizeof(*graph->parents));
6364 static void
6365 done_rev_graph(struct rev_graph *graph)
6367 if (graph_parent_is_merge(graph) &&
6368 graph->pos < graph->size - 1 &&
6369 graph->next->size == graph->size + graph->parents->size - 1) {
6370 size_t i = graph->pos + graph->parents->size - 1;
6372 graph->commit->graph_size = i * 2;
6373 while (i < graph->next->size - 1) {
6374 append_to_rev_graph(graph, ' ');
6375 append_to_rev_graph(graph, '\\');
6376 i++;
6380 clear_rev_graph(graph);
6383 static void
6384 push_rev_graph(struct rev_graph *graph, const char *parent)
6386 int i;
6388 /* "Collapse" duplicate parents lines.
6390 * FIXME: This needs to also update update the drawn graph but
6391 * for now it just serves as a method for pruning graph lines. */
6392 for (i = 0; i < graph->size; i++)
6393 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6394 return;
6396 if (graph->size < SIZEOF_REVITEMS) {
6397 string_copy_rev(graph->rev[graph->size++], parent);
6401 static chtype
6402 get_rev_graph_symbol(struct rev_graph *graph)
6404 chtype symbol;
6406 if (graph->boundary)
6407 symbol = REVGRAPH_BOUND;
6408 else if (graph->parents->size == 0)
6409 symbol = REVGRAPH_INIT;
6410 else if (graph_parent_is_merge(graph))
6411 symbol = REVGRAPH_MERGE;
6412 else if (graph->pos >= graph->size)
6413 symbol = REVGRAPH_BRANCH;
6414 else
6415 symbol = REVGRAPH_COMMIT;
6417 return symbol;
6420 static void
6421 draw_rev_graph(struct rev_graph *graph)
6423 struct rev_filler {
6424 chtype separator, line;
6426 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6427 static struct rev_filler fillers[] = {
6428 { ' ', '|' },
6429 { '`', '.' },
6430 { '\'', ' ' },
6431 { '/', ' ' },
6433 chtype symbol = get_rev_graph_symbol(graph);
6434 struct rev_filler *filler;
6435 size_t i;
6437 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6438 filler = &fillers[DEFAULT];
6440 for (i = 0; i < graph->pos; i++) {
6441 append_to_rev_graph(graph, filler->line);
6442 if (graph_parent_is_merge(graph->prev) &&
6443 graph->prev->pos == i)
6444 filler = &fillers[RSHARP];
6446 append_to_rev_graph(graph, filler->separator);
6449 /* Place the symbol for this revision. */
6450 append_to_rev_graph(graph, symbol);
6452 if (graph->prev->size > graph->size)
6453 filler = &fillers[RDIAG];
6454 else
6455 filler = &fillers[DEFAULT];
6457 i++;
6459 for (; i < graph->size; i++) {
6460 append_to_rev_graph(graph, filler->separator);
6461 append_to_rev_graph(graph, filler->line);
6462 if (graph_parent_is_merge(graph->prev) &&
6463 i < graph->prev->pos + graph->parents->size)
6464 filler = &fillers[RSHARP];
6465 if (graph->prev->size > graph->size)
6466 filler = &fillers[LDIAG];
6469 if (graph->prev->size > graph->size) {
6470 append_to_rev_graph(graph, filler->separator);
6471 if (filler->line != ' ')
6472 append_to_rev_graph(graph, filler->line);
6476 /* Prepare the next rev graph */
6477 static void
6478 prepare_rev_graph(struct rev_graph *graph)
6480 size_t i;
6482 /* First, traverse all lines of revisions up to the active one. */
6483 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6484 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6485 break;
6487 push_rev_graph(graph->next, graph->rev[graph->pos]);
6490 /* Interleave the new revision parent(s). */
6491 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6492 push_rev_graph(graph->next, graph->parents->rev[i]);
6494 /* Lastly, put any remaining revisions. */
6495 for (i = graph->pos + 1; i < graph->size; i++)
6496 push_rev_graph(graph->next, graph->rev[i]);
6499 static void
6500 update_rev_graph(struct view *view, struct rev_graph *graph)
6502 /* If this is the finalizing update ... */
6503 if (graph->commit)
6504 prepare_rev_graph(graph);
6506 /* Graph visualization needs a one rev look-ahead,
6507 * so the first update doesn't visualize anything. */
6508 if (!graph->prev->commit)
6509 return;
6511 if (view->lines > 2)
6512 view->line[view->lines - 3].dirty = 1;
6513 if (view->lines > 1)
6514 view->line[view->lines - 2].dirty = 1;
6515 draw_rev_graph(graph->prev);
6516 done_rev_graph(graph->prev->prev);
6521 * Main view backend
6524 static const char *main_argv[SIZEOF_ARG] = {
6525 "git", "log", "--no-color", "--pretty=raw", "--parents",
6526 "--topo-order", "%(head)", NULL
6529 static bool
6530 main_draw(struct view *view, struct line *line, unsigned int lineno)
6532 struct commit *commit = line->data;
6534 if (!commit->author)
6535 return FALSE;
6537 if (opt_date && draw_date(view, &commit->time))
6538 return TRUE;
6540 if (opt_author && draw_author(view, commit->author))
6541 return TRUE;
6543 if (opt_rev_graph && commit->graph_size &&
6544 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6545 return TRUE;
6547 if (opt_show_refs && commit->refs) {
6548 size_t i;
6550 for (i = 0; i < commit->refs->size; i++) {
6551 struct ref *ref = commit->refs->refs[i];
6552 enum line_type type;
6554 if (ref->head)
6555 type = LINE_MAIN_HEAD;
6556 else if (ref->ltag)
6557 type = LINE_MAIN_LOCAL_TAG;
6558 else if (ref->tag)
6559 type = LINE_MAIN_TAG;
6560 else if (ref->tracked)
6561 type = LINE_MAIN_TRACKED;
6562 else if (ref->remote)
6563 type = LINE_MAIN_REMOTE;
6564 else
6565 type = LINE_MAIN_REF;
6567 if (draw_text(view, type, "[", TRUE) ||
6568 draw_text(view, type, ref->name, TRUE) ||
6569 draw_text(view, type, "]", TRUE))
6570 return TRUE;
6572 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6573 return TRUE;
6577 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6578 return TRUE;
6581 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6582 static bool
6583 main_read(struct view *view, char *line)
6585 static struct rev_graph *graph = graph_stacks;
6586 enum line_type type;
6587 struct commit *commit;
6589 if (!line) {
6590 int i;
6592 if (!view->lines && !view->parent)
6593 die("No revisions match the given arguments.");
6594 if (view->lines > 0) {
6595 commit = view->line[view->lines - 1].data;
6596 view->line[view->lines - 1].dirty = 1;
6597 if (!commit->author) {
6598 view->lines--;
6599 free(commit);
6600 graph->commit = NULL;
6603 update_rev_graph(view, graph);
6605 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6606 clear_rev_graph(&graph_stacks[i]);
6607 return TRUE;
6610 type = get_line_type(line);
6611 if (type == LINE_COMMIT) {
6612 commit = calloc(1, sizeof(struct commit));
6613 if (!commit)
6614 return FALSE;
6616 line += STRING_SIZE("commit ");
6617 if (*line == '-') {
6618 graph->boundary = 1;
6619 line++;
6622 string_copy_rev(commit->id, line);
6623 commit->refs = get_ref_list(commit->id);
6624 graph->commit = commit;
6625 add_line_data(view, commit, LINE_MAIN_COMMIT);
6627 while ((line = strchr(line, ' '))) {
6628 line++;
6629 push_rev_graph(graph->parents, line);
6630 commit->has_parents = TRUE;
6632 return TRUE;
6635 if (!view->lines)
6636 return TRUE;
6637 commit = view->line[view->lines - 1].data;
6639 switch (type) {
6640 case LINE_PARENT:
6641 if (commit->has_parents)
6642 break;
6643 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6644 break;
6646 case LINE_AUTHOR:
6647 parse_author_line(line + STRING_SIZE("author "),
6648 &commit->author, &commit->time);
6649 update_rev_graph(view, graph);
6650 graph = graph->next;
6651 break;
6653 default:
6654 /* Fill in the commit title if it has not already been set. */
6655 if (commit->title[0])
6656 break;
6658 /* Require titles to start with a non-space character at the
6659 * offset used by git log. */
6660 if (strncmp(line, " ", 4))
6661 break;
6662 line += 4;
6663 /* Well, if the title starts with a whitespace character,
6664 * try to be forgiving. Otherwise we end up with no title. */
6665 while (isspace(*line))
6666 line++;
6667 if (*line == '\0')
6668 break;
6669 /* FIXME: More graceful handling of titles; append "..." to
6670 * shortened titles, etc. */
6672 string_expand(commit->title, sizeof(commit->title), line, 1);
6673 view->line[view->lines - 1].dirty = 1;
6676 return TRUE;
6679 static enum request
6680 main_request(struct view *view, enum request request, struct line *line)
6682 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6684 switch (request) {
6685 case REQ_ENTER:
6686 open_view(view, REQ_VIEW_DIFF, flags);
6687 break;
6688 case REQ_REFRESH:
6689 load_refs();
6690 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6691 break;
6692 default:
6693 return request;
6696 return REQ_NONE;
6699 static bool
6700 grep_refs(struct ref_list *list, regex_t *regex)
6702 regmatch_t pmatch;
6703 size_t i;
6705 if (!opt_show_refs || !list)
6706 return FALSE;
6708 for (i = 0; i < list->size; i++) {
6709 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6710 return TRUE;
6713 return FALSE;
6716 static bool
6717 main_grep(struct view *view, struct line *line)
6719 struct commit *commit = line->data;
6720 const char *text[] = {
6721 commit->title,
6722 opt_author ? commit->author : "",
6723 mkdate(&commit->time, opt_date),
6724 NULL
6727 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6730 static void
6731 main_select(struct view *view, struct line *line)
6733 struct commit *commit = line->data;
6735 string_copy_rev(view->ref, commit->id);
6736 string_copy_rev(ref_commit, view->ref);
6739 static struct view_ops main_ops = {
6740 "commit",
6741 main_argv,
6742 NULL,
6743 main_read,
6744 main_draw,
6745 main_request,
6746 main_grep,
6747 main_select,
6752 * Unicode / UTF-8 handling
6754 * NOTE: Much of the following code for dealing with Unicode is derived from
6755 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6756 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6759 static inline int
6760 unicode_width(unsigned long c, int tab_size)
6762 if (c >= 0x1100 &&
6763 (c <= 0x115f /* Hangul Jamo */
6764 || c == 0x2329
6765 || c == 0x232a
6766 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6767 /* CJK ... Yi */
6768 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6769 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6770 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6771 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6772 || (c >= 0xffe0 && c <= 0xffe6)
6773 || (c >= 0x20000 && c <= 0x2fffd)
6774 || (c >= 0x30000 && c <= 0x3fffd)))
6775 return 2;
6777 if (c == '\t')
6778 return tab_size;
6780 return 1;
6783 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6784 * Illegal bytes are set one. */
6785 static const unsigned char utf8_bytes[256] = {
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 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6790 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6791 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6792 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,
6793 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,
6796 static inline unsigned char
6797 utf8_char_length(const char *string, const char *end)
6799 int c = *(unsigned char *) string;
6801 return utf8_bytes[c];
6804 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6805 static inline unsigned long
6806 utf8_to_unicode(const char *string, size_t length)
6808 unsigned long unicode;
6810 switch (length) {
6811 case 1:
6812 unicode = string[0];
6813 break;
6814 case 2:
6815 unicode = (string[0] & 0x1f) << 6;
6816 unicode += (string[1] & 0x3f);
6817 break;
6818 case 3:
6819 unicode = (string[0] & 0x0f) << 12;
6820 unicode += ((string[1] & 0x3f) << 6);
6821 unicode += (string[2] & 0x3f);
6822 break;
6823 case 4:
6824 unicode = (string[0] & 0x0f) << 18;
6825 unicode += ((string[1] & 0x3f) << 12);
6826 unicode += ((string[2] & 0x3f) << 6);
6827 unicode += (string[3] & 0x3f);
6828 break;
6829 case 5:
6830 unicode = (string[0] & 0x0f) << 24;
6831 unicode += ((string[1] & 0x3f) << 18);
6832 unicode += ((string[2] & 0x3f) << 12);
6833 unicode += ((string[3] & 0x3f) << 6);
6834 unicode += (string[4] & 0x3f);
6835 break;
6836 case 6:
6837 unicode = (string[0] & 0x01) << 30;
6838 unicode += ((string[1] & 0x3f) << 24);
6839 unicode += ((string[2] & 0x3f) << 18);
6840 unicode += ((string[3] & 0x3f) << 12);
6841 unicode += ((string[4] & 0x3f) << 6);
6842 unicode += (string[5] & 0x3f);
6843 break;
6844 default:
6845 die("Invalid Unicode length");
6848 /* Invalid characters could return the special 0xfffd value but NUL
6849 * should be just as good. */
6850 return unicode > 0xffff ? 0 : unicode;
6853 /* Calculates how much of string can be shown within the given maximum width
6854 * and sets trimmed parameter to non-zero value if all of string could not be
6855 * shown. If the reserve flag is TRUE, it will reserve at least one
6856 * trailing character, which can be useful when drawing a delimiter.
6858 * Returns the number of bytes to output from string to satisfy max_width. */
6859 static size_t
6860 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6862 const char *string = *start;
6863 const char *end = strchr(string, '\0');
6864 unsigned char last_bytes = 0;
6865 size_t last_ucwidth = 0;
6867 *width = 0;
6868 *trimmed = 0;
6870 while (string < end) {
6871 unsigned char bytes = utf8_char_length(string, end);
6872 size_t ucwidth;
6873 unsigned long unicode;
6875 if (string + bytes > end)
6876 break;
6878 /* Change representation to figure out whether
6879 * it is a single- or double-width character. */
6881 unicode = utf8_to_unicode(string, bytes);
6882 /* FIXME: Graceful handling of invalid Unicode character. */
6883 if (!unicode)
6884 break;
6886 ucwidth = unicode_width(unicode, tab_size);
6887 if (skip > 0) {
6888 skip -= ucwidth <= skip ? ucwidth : skip;
6889 *start += bytes;
6891 *width += ucwidth;
6892 if (*width > max_width) {
6893 *trimmed = 1;
6894 *width -= ucwidth;
6895 if (reserve && *width == max_width) {
6896 string -= last_bytes;
6897 *width -= last_ucwidth;
6899 break;
6902 string += bytes;
6903 last_bytes = ucwidth ? bytes : 0;
6904 last_ucwidth = ucwidth;
6907 return string - *start;
6912 * Status management
6915 /* Whether or not the curses interface has been initialized. */
6916 static bool cursed = FALSE;
6918 /* Terminal hacks and workarounds. */
6919 static bool use_scroll_redrawwin;
6920 static bool use_scroll_status_wclear;
6922 /* The status window is used for polling keystrokes. */
6923 static WINDOW *status_win;
6925 /* Reading from the prompt? */
6926 static bool input_mode = FALSE;
6928 static bool status_empty = FALSE;
6930 /* Update status and title window. */
6931 static void
6932 report(const char *msg, ...)
6934 struct view *view = display[current_view];
6936 if (input_mode)
6937 return;
6939 if (!view) {
6940 char buf[SIZEOF_STR];
6941 va_list args;
6943 va_start(args, msg);
6944 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6945 buf[sizeof(buf) - 1] = 0;
6946 buf[sizeof(buf) - 2] = '.';
6947 buf[sizeof(buf) - 3] = '.';
6948 buf[sizeof(buf) - 4] = '.';
6950 va_end(args);
6951 die("%s", buf);
6954 if (!status_empty || *msg) {
6955 va_list args;
6957 va_start(args, msg);
6959 wmove(status_win, 0, 0);
6960 if (view->has_scrolled && use_scroll_status_wclear)
6961 wclear(status_win);
6962 if (*msg) {
6963 vwprintw(status_win, msg, args);
6964 status_empty = FALSE;
6965 } else {
6966 status_empty = TRUE;
6968 wclrtoeol(status_win);
6969 wnoutrefresh(status_win);
6971 va_end(args);
6974 update_view_title(view);
6977 static void
6978 init_display(void)
6980 const char *term;
6981 int x, y;
6983 /* Initialize the curses library */
6984 if (isatty(STDIN_FILENO)) {
6985 cursed = !!initscr();
6986 opt_tty = stdin;
6987 } else {
6988 /* Leave stdin and stdout alone when acting as a pager. */
6989 opt_tty = fopen("/dev/tty", "r+");
6990 if (!opt_tty)
6991 die("Failed to open /dev/tty");
6992 cursed = !!newterm(NULL, opt_tty, opt_tty);
6995 if (!cursed)
6996 die("Failed to initialize curses");
6998 nonl(); /* Disable conversion and detect newlines from input. */
6999 cbreak(); /* Take input chars one at a time, no wait for \n */
7000 noecho(); /* Don't echo input */
7001 leaveok(stdscr, FALSE);
7003 if (has_colors())
7004 init_colors();
7006 getmaxyx(stdscr, y, x);
7007 status_win = newwin(1, 0, y - 1, 0);
7008 if (!status_win)
7009 die("Failed to create status window");
7011 /* Enable keyboard mapping */
7012 keypad(status_win, TRUE);
7013 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7015 TABSIZE = opt_tab_size;
7017 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7018 if (term && !strcmp(term, "gnome-terminal")) {
7019 /* In the gnome-terminal-emulator, the message from
7020 * scrolling up one line when impossible followed by
7021 * scrolling down one line causes corruption of the
7022 * status line. This is fixed by calling wclear. */
7023 use_scroll_status_wclear = TRUE;
7024 use_scroll_redrawwin = FALSE;
7026 } else if (term && !strcmp(term, "xrvt-xpm")) {
7027 /* No problems with full optimizations in xrvt-(unicode)
7028 * and aterm. */
7029 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7031 } else {
7032 /* When scrolling in (u)xterm the last line in the
7033 * scrolling direction will update slowly. */
7034 use_scroll_redrawwin = TRUE;
7035 use_scroll_status_wclear = FALSE;
7039 static int
7040 get_input(int prompt_position)
7042 struct view *view;
7043 int i, key, cursor_y, cursor_x;
7044 bool loading = FALSE;
7046 if (prompt_position)
7047 input_mode = TRUE;
7049 while (TRUE) {
7050 foreach_view (view, i) {
7051 update_view(view);
7052 if (view_is_displayed(view) && view->has_scrolled &&
7053 use_scroll_redrawwin)
7054 redrawwin(view->win);
7055 view->has_scrolled = FALSE;
7056 if (view->pipe)
7057 loading = TRUE;
7060 /* Update the cursor position. */
7061 if (prompt_position) {
7062 getbegyx(status_win, cursor_y, cursor_x);
7063 cursor_x = prompt_position;
7064 } else {
7065 view = display[current_view];
7066 getbegyx(view->win, cursor_y, cursor_x);
7067 cursor_x = view->width - 1;
7068 cursor_y += view->lineno - view->offset;
7070 setsyx(cursor_y, cursor_x);
7072 /* Refresh, accept single keystroke of input */
7073 doupdate();
7074 nodelay(status_win, loading);
7075 key = wgetch(status_win);
7077 /* wgetch() with nodelay() enabled returns ERR when
7078 * there's no input. */
7079 if (key == ERR) {
7081 } else if (key == KEY_RESIZE) {
7082 int height, width;
7084 getmaxyx(stdscr, height, width);
7086 wresize(status_win, 1, width);
7087 mvwin(status_win, height - 1, 0);
7088 wnoutrefresh(status_win);
7089 resize_display();
7090 redraw_display(TRUE);
7092 } else {
7093 input_mode = FALSE;
7094 return key;
7099 static char *
7100 prompt_input(const char *prompt, input_handler handler, void *data)
7102 enum input_status status = INPUT_OK;
7103 static char buf[SIZEOF_STR];
7104 size_t pos = 0;
7106 buf[pos] = 0;
7108 while (status == INPUT_OK || status == INPUT_SKIP) {
7109 int key;
7111 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7112 wclrtoeol(status_win);
7114 key = get_input(pos + 1);
7115 switch (key) {
7116 case KEY_RETURN:
7117 case KEY_ENTER:
7118 case '\n':
7119 status = pos ? INPUT_STOP : INPUT_CANCEL;
7120 break;
7122 case KEY_BACKSPACE:
7123 if (pos > 0)
7124 buf[--pos] = 0;
7125 else
7126 status = INPUT_CANCEL;
7127 break;
7129 case KEY_ESC:
7130 status = INPUT_CANCEL;
7131 break;
7133 default:
7134 if (pos >= sizeof(buf)) {
7135 report("Input string too long");
7136 return NULL;
7139 status = handler(data, buf, key);
7140 if (status == INPUT_OK)
7141 buf[pos++] = (char) key;
7145 /* Clear the status window */
7146 status_empty = FALSE;
7147 report("");
7149 if (status == INPUT_CANCEL)
7150 return NULL;
7152 buf[pos++] = 0;
7154 return buf;
7157 static enum input_status
7158 prompt_yesno_handler(void *data, char *buf, int c)
7160 if (c == 'y' || c == 'Y')
7161 return INPUT_STOP;
7162 if (c == 'n' || c == 'N')
7163 return INPUT_CANCEL;
7164 return INPUT_SKIP;
7167 static bool
7168 prompt_yesno(const char *prompt)
7170 char prompt2[SIZEOF_STR];
7172 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7173 return FALSE;
7175 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7178 static enum input_status
7179 read_prompt_handler(void *data, char *buf, int c)
7181 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7184 static char *
7185 read_prompt(const char *prompt)
7187 return prompt_input(prompt, read_prompt_handler, NULL);
7190 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7192 enum input_status status = INPUT_OK;
7193 int size = 0;
7195 while (items[size].text)
7196 size++;
7198 while (status == INPUT_OK) {
7199 const struct menu_item *item = &items[*selected];
7200 int key;
7201 int i;
7203 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7204 prompt, *selected + 1, size);
7205 if (item->hotkey)
7206 wprintw(status_win, "[%c] ", (char) item->hotkey);
7207 wprintw(status_win, "%s", item->text);
7208 wclrtoeol(status_win);
7210 key = get_input(COLS - 1);
7211 switch (key) {
7212 case KEY_RETURN:
7213 case KEY_ENTER:
7214 case '\n':
7215 status = INPUT_STOP;
7216 break;
7218 case KEY_LEFT:
7219 case KEY_UP:
7220 *selected = *selected - 1;
7221 if (*selected < 0)
7222 *selected = size - 1;
7223 break;
7225 case KEY_RIGHT:
7226 case KEY_DOWN:
7227 *selected = (*selected + 1) % size;
7228 break;
7230 case KEY_ESC:
7231 status = INPUT_CANCEL;
7232 break;
7234 default:
7235 for (i = 0; items[i].text; i++)
7236 if (items[i].hotkey == key) {
7237 *selected = i;
7238 status = INPUT_STOP;
7239 break;
7244 /* Clear the status window */
7245 status_empty = FALSE;
7246 report("");
7248 return status != INPUT_CANCEL;
7252 * Repository properties
7255 static struct ref **refs = NULL;
7256 static size_t refs_size = 0;
7257 static struct ref *refs_head = NULL;
7259 static struct ref_list **ref_lists = NULL;
7260 static size_t ref_lists_size = 0;
7262 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7263 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7264 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7266 static int
7267 compare_refs(const void *ref1_, const void *ref2_)
7269 const struct ref *ref1 = *(const struct ref **)ref1_;
7270 const struct ref *ref2 = *(const struct ref **)ref2_;
7272 if (ref1->tag != ref2->tag)
7273 return ref2->tag - ref1->tag;
7274 if (ref1->ltag != ref2->ltag)
7275 return ref2->ltag - ref2->ltag;
7276 if (ref1->head != ref2->head)
7277 return ref2->head - ref1->head;
7278 if (ref1->tracked != ref2->tracked)
7279 return ref2->tracked - ref1->tracked;
7280 if (ref1->remote != ref2->remote)
7281 return ref2->remote - ref1->remote;
7282 return strcmp(ref1->name, ref2->name);
7285 static void
7286 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7288 size_t i;
7290 for (i = 0; i < refs_size; i++)
7291 if (!visitor(data, refs[i]))
7292 break;
7295 static struct ref *
7296 get_ref_head()
7298 return refs_head;
7301 static struct ref_list *
7302 get_ref_list(const char *id)
7304 struct ref_list *list;
7305 size_t i;
7307 for (i = 0; i < ref_lists_size; i++)
7308 if (!strcmp(id, ref_lists[i]->id))
7309 return ref_lists[i];
7311 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7312 return NULL;
7313 list = calloc(1, sizeof(*list));
7314 if (!list)
7315 return NULL;
7317 for (i = 0; i < refs_size; i++) {
7318 if (!strcmp(id, refs[i]->id) &&
7319 realloc_refs_list(&list->refs, list->size, 1))
7320 list->refs[list->size++] = refs[i];
7323 if (!list->refs) {
7324 free(list);
7325 return NULL;
7328 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7329 ref_lists[ref_lists_size++] = list;
7330 return list;
7333 static int
7334 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7336 struct ref *ref = NULL;
7337 bool tag = FALSE;
7338 bool ltag = FALSE;
7339 bool remote = FALSE;
7340 bool tracked = FALSE;
7341 bool head = FALSE;
7342 int from = 0, to = refs_size - 1;
7344 if (!prefixcmp(name, "refs/tags/")) {
7345 if (!suffixcmp(name, namelen, "^{}")) {
7346 namelen -= 3;
7347 name[namelen] = 0;
7348 } else {
7349 ltag = TRUE;
7352 tag = TRUE;
7353 namelen -= STRING_SIZE("refs/tags/");
7354 name += STRING_SIZE("refs/tags/");
7356 } else if (!prefixcmp(name, "refs/remotes/")) {
7357 remote = TRUE;
7358 namelen -= STRING_SIZE("refs/remotes/");
7359 name += STRING_SIZE("refs/remotes/");
7360 tracked = !strcmp(opt_remote, name);
7362 } else if (!prefixcmp(name, "refs/heads/")) {
7363 namelen -= STRING_SIZE("refs/heads/");
7364 name += STRING_SIZE("refs/heads/");
7365 if (!strncmp(opt_head, name, namelen))
7366 return OK;
7368 } else if (!strcmp(name, "HEAD")) {
7369 head = TRUE;
7370 if (*opt_head) {
7371 namelen = strlen(opt_head);
7372 name = opt_head;
7376 /* If we are reloading or it's an annotated tag, replace the
7377 * previous SHA1 with the resolved commit id; relies on the fact
7378 * git-ls-remote lists the commit id of an annotated tag right
7379 * before the commit id it points to. */
7380 while (from <= to) {
7381 size_t pos = (to + from) / 2;
7382 int cmp = strcmp(name, refs[pos]->name);
7384 if (!cmp) {
7385 ref = refs[pos];
7386 break;
7389 if (cmp < 0)
7390 to = pos - 1;
7391 else
7392 from = pos + 1;
7395 if (!ref) {
7396 if (!realloc_refs(&refs, refs_size, 1))
7397 return ERR;
7398 ref = calloc(1, sizeof(*ref) + namelen);
7399 if (!ref)
7400 return ERR;
7401 memmove(refs + from + 1, refs + from,
7402 (refs_size - from) * sizeof(*refs));
7403 refs[from] = ref;
7404 strncpy(ref->name, name, namelen);
7405 refs_size++;
7408 ref->head = head;
7409 ref->tag = tag;
7410 ref->ltag = ltag;
7411 ref->remote = remote;
7412 ref->tracked = tracked;
7413 string_copy_rev(ref->id, id);
7415 if (head)
7416 refs_head = ref;
7417 return OK;
7420 static int
7421 load_refs(void)
7423 const char *head_argv[] = {
7424 "git", "symbolic-ref", "HEAD", NULL
7426 static const char *ls_remote_argv[SIZEOF_ARG] = {
7427 "git", "ls-remote", opt_git_dir, NULL
7429 static bool init = FALSE;
7430 size_t i;
7432 if (!init) {
7433 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7434 init = TRUE;
7437 if (!*opt_git_dir)
7438 return OK;
7440 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7441 !prefixcmp(opt_head, "refs/heads/")) {
7442 char *offset = opt_head + STRING_SIZE("refs/heads/");
7444 memmove(opt_head, offset, strlen(offset) + 1);
7447 refs_head = NULL;
7448 for (i = 0; i < refs_size; i++)
7449 refs[i]->id[0] = 0;
7451 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7452 return ERR;
7454 /* Update the ref lists to reflect changes. */
7455 for (i = 0; i < ref_lists_size; i++) {
7456 struct ref_list *list = ref_lists[i];
7457 size_t old, new;
7459 for (old = new = 0; old < list->size; old++)
7460 if (!strcmp(list->id, list->refs[old]->id))
7461 list->refs[new++] = list->refs[old];
7462 list->size = new;
7465 return OK;
7468 static void
7469 set_remote_branch(const char *name, const char *value, size_t valuelen)
7471 if (!strcmp(name, ".remote")) {
7472 string_ncopy(opt_remote, value, valuelen);
7474 } else if (*opt_remote && !strcmp(name, ".merge")) {
7475 size_t from = strlen(opt_remote);
7477 if (!prefixcmp(value, "refs/heads/"))
7478 value += STRING_SIZE("refs/heads/");
7480 if (!string_format_from(opt_remote, &from, "/%s", value))
7481 opt_remote[0] = 0;
7485 static void
7486 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7488 const char *argv[SIZEOF_ARG] = { name, "=" };
7489 int argc = 1 + (cmd == option_set_command);
7490 int error = ERR;
7492 if (!argv_from_string(argv, &argc, value))
7493 config_msg = "Too many option arguments";
7494 else
7495 error = cmd(argc, argv);
7497 if (error == ERR)
7498 warn("Option 'tig.%s': %s", name, config_msg);
7501 static bool
7502 set_environment_variable(const char *name, const char *value)
7504 size_t len = strlen(name) + 1 + strlen(value) + 1;
7505 char *env = malloc(len);
7507 if (env &&
7508 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7509 putenv(env) == 0)
7510 return TRUE;
7511 free(env);
7512 return FALSE;
7515 static void
7516 set_work_tree(const char *value)
7518 char cwd[SIZEOF_STR];
7520 if (!getcwd(cwd, sizeof(cwd)))
7521 die("Failed to get cwd path: %s", strerror(errno));
7522 if (chdir(opt_git_dir) < 0)
7523 die("Failed to chdir(%s): %s", strerror(errno));
7524 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7525 die("Failed to get git path: %s", strerror(errno));
7526 if (chdir(cwd) < 0)
7527 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7528 if (chdir(value) < 0)
7529 die("Failed to chdir(%s): %s", value, strerror(errno));
7530 if (!getcwd(cwd, sizeof(cwd)))
7531 die("Failed to get cwd path: %s", strerror(errno));
7532 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7533 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7534 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7535 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7536 opt_is_inside_work_tree = TRUE;
7539 static int
7540 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7542 if (!strcmp(name, "i18n.commitencoding"))
7543 string_ncopy(opt_encoding, value, valuelen);
7545 else if (!strcmp(name, "core.editor"))
7546 string_ncopy(opt_editor, value, valuelen);
7548 else if (!strcmp(name, "core.worktree"))
7549 set_work_tree(value);
7551 else if (!prefixcmp(name, "tig.color."))
7552 set_repo_config_option(name + 10, value, option_color_command);
7554 else if (!prefixcmp(name, "tig.bind."))
7555 set_repo_config_option(name + 9, value, option_bind_command);
7557 else if (!prefixcmp(name, "tig."))
7558 set_repo_config_option(name + 4, value, option_set_command);
7560 else if (*opt_head && !prefixcmp(name, "branch.") &&
7561 !strncmp(name + 7, opt_head, strlen(opt_head)))
7562 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7564 return OK;
7567 static int
7568 load_git_config(void)
7570 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7572 return io_run_load(config_list_argv, "=", read_repo_config_option);
7575 static int
7576 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7578 if (!opt_git_dir[0]) {
7579 string_ncopy(opt_git_dir, name, namelen);
7581 } else if (opt_is_inside_work_tree == -1) {
7582 /* This can be 3 different values depending on the
7583 * version of git being used. If git-rev-parse does not
7584 * understand --is-inside-work-tree it will simply echo
7585 * the option else either "true" or "false" is printed.
7586 * Default to true for the unknown case. */
7587 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7589 } else if (*name == '.') {
7590 string_ncopy(opt_cdup, name, namelen);
7592 } else {
7593 string_ncopy(opt_prefix, name, namelen);
7596 return OK;
7599 static int
7600 load_repo_info(void)
7602 const char *rev_parse_argv[] = {
7603 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7604 "--show-cdup", "--show-prefix", NULL
7607 return io_run_load(rev_parse_argv, "=", read_repo_info);
7612 * Main
7615 static const char usage[] =
7616 "tig " TIG_VERSION " (" __DATE__ ")\n"
7617 "\n"
7618 "Usage: tig [options] [revs] [--] [paths]\n"
7619 " or: tig show [options] [revs] [--] [paths]\n"
7620 " or: tig blame [rev] path\n"
7621 " or: tig status\n"
7622 " or: tig < [git command output]\n"
7623 "\n"
7624 "Options:\n"
7625 " -v, --version Show version and exit\n"
7626 " -h, --help Show help message and exit";
7628 static void __NORETURN
7629 quit(int sig)
7631 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7632 if (cursed)
7633 endwin();
7634 exit(0);
7637 static void __NORETURN
7638 die(const char *err, ...)
7640 va_list args;
7642 endwin();
7644 va_start(args, err);
7645 fputs("tig: ", stderr);
7646 vfprintf(stderr, err, args);
7647 fputs("\n", stderr);
7648 va_end(args);
7650 exit(1);
7653 static void
7654 warn(const char *msg, ...)
7656 va_list args;
7658 va_start(args, msg);
7659 fputs("tig warning: ", stderr);
7660 vfprintf(stderr, msg, args);
7661 fputs("\n", stderr);
7662 va_end(args);
7665 static enum request
7666 parse_options(int argc, const char *argv[])
7668 enum request request = REQ_VIEW_MAIN;
7669 const char *subcommand;
7670 bool seen_dashdash = FALSE;
7671 /* XXX: This is vulnerable to the user overriding options
7672 * required for the main view parser. */
7673 const char *custom_argv[SIZEOF_ARG] = {
7674 "git", "log", "--no-color", "--pretty=raw", "--parents",
7675 "--topo-order", NULL
7677 int i, j = 6;
7679 if (!isatty(STDIN_FILENO)) {
7680 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7681 return REQ_VIEW_PAGER;
7684 if (argc <= 1)
7685 return REQ_NONE;
7687 subcommand = argv[1];
7688 if (!strcmp(subcommand, "status")) {
7689 if (argc > 2)
7690 warn("ignoring arguments after `%s'", subcommand);
7691 return REQ_VIEW_STATUS;
7693 } else if (!strcmp(subcommand, "blame")) {
7694 if (argc <= 2 || argc > 4)
7695 die("invalid number of options to blame\n\n%s", usage);
7697 i = 2;
7698 if (argc == 4) {
7699 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7700 i++;
7703 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7704 return REQ_VIEW_BLAME;
7706 } else if (!strcmp(subcommand, "show")) {
7707 request = REQ_VIEW_DIFF;
7709 } else {
7710 subcommand = NULL;
7713 if (subcommand) {
7714 custom_argv[1] = subcommand;
7715 j = 2;
7718 for (i = 1 + !!subcommand; i < argc; i++) {
7719 const char *opt = argv[i];
7721 if (seen_dashdash || !strcmp(opt, "--")) {
7722 seen_dashdash = TRUE;
7724 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7725 printf("tig version %s\n", TIG_VERSION);
7726 quit(0);
7728 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7729 printf("%s\n", usage);
7730 quit(0);
7733 custom_argv[j++] = opt;
7734 if (j >= ARRAY_SIZE(custom_argv))
7735 die("command too long");
7738 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7739 die("Failed to format arguments");
7741 return request;
7745 main(int argc, const char *argv[])
7747 const char *codeset = "UTF-8";
7748 enum request request = parse_options(argc, argv);
7749 struct view *view;
7750 size_t i;
7752 signal(SIGINT, quit);
7753 signal(SIGPIPE, SIG_IGN);
7755 if (setlocale(LC_ALL, "")) {
7756 codeset = nl_langinfo(CODESET);
7759 if (load_repo_info() == ERR)
7760 die("Failed to load repo info.");
7762 if (load_options() == ERR)
7763 die("Failed to load user config.");
7765 if (load_git_config() == ERR)
7766 die("Failed to load repo config.");
7768 /* Require a git repository unless when running in pager mode. */
7769 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7770 die("Not a git repository");
7772 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7773 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7774 if (opt_iconv_in == ICONV_NONE)
7775 die("Failed to initialize character set conversion");
7778 if (codeset && strcmp(codeset, "UTF-8")) {
7779 opt_iconv_out = iconv_open(codeset, "UTF-8");
7780 if (opt_iconv_out == ICONV_NONE)
7781 die("Failed to initialize character set conversion");
7784 if (load_refs() == ERR)
7785 die("Failed to load refs.");
7787 foreach_view (view, i)
7788 argv_from_env(view->ops->argv, view->cmd_env);
7790 init_display();
7792 if (request != REQ_NONE)
7793 open_view(NULL, request, OPEN_PREPARED);
7794 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7796 while (view_driver(display[current_view], request)) {
7797 int key = get_input(0);
7799 view = display[current_view];
7800 request = get_keybinding(view->keymap, key);
7802 /* Some low-level request handling. This keeps access to
7803 * status_win restricted. */
7804 switch (request) {
7805 case REQ_PROMPT:
7807 char *cmd = read_prompt(":");
7809 if (cmd && isdigit(*cmd)) {
7810 int lineno = view->lineno + 1;
7812 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7813 select_view_line(view, lineno - 1);
7814 report("");
7815 } else {
7816 report("Unable to parse '%s' as a line number", cmd);
7819 } else if (cmd) {
7820 struct view *next = VIEW(REQ_VIEW_PAGER);
7821 const char *argv[SIZEOF_ARG] = { "git" };
7822 int argc = 1;
7824 /* When running random commands, initially show the
7825 * command in the title. However, it maybe later be
7826 * overwritten if a commit line is selected. */
7827 string_ncopy(next->ref, cmd, strlen(cmd));
7829 if (!argv_from_string(argv, &argc, cmd)) {
7830 report("Too many arguments");
7831 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7832 report("Failed to format command");
7833 } else {
7834 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7838 request = REQ_NONE;
7839 break;
7841 case REQ_SEARCH:
7842 case REQ_SEARCH_BACK:
7844 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7845 char *search = read_prompt(prompt);
7847 if (search)
7848 string_ncopy(opt_search, search, strlen(search));
7849 else if (*opt_search)
7850 request = request == REQ_SEARCH ?
7851 REQ_FIND_NEXT :
7852 REQ_FIND_PREV;
7853 else
7854 request = REQ_NONE;
7855 break;
7857 default:
7858 break;
7862 quit(0);
7864 return 0;