Add possiblity to pass data to io_load property reader
[tig.git] / tig.c
blob8bca63498e8b0417ed1278e4c6d0eb87aeb0c348
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, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB '\t'
119 #define KEY_RETURN '\r'
120 #define KEY_ESC 27
123 struct ref {
124 char id[SIZEOF_REV]; /* Commit SHA1 ID */
125 unsigned int head:1; /* Is it the current HEAD? */
126 unsigned int tag:1; /* Is it a tag? */
127 unsigned int ltag:1; /* If so, is the tag local? */
128 unsigned int remote:1; /* Is it a remote ref? */
129 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130 char name[1]; /* Ref name; tag or head names are shortened. */
133 struct ref_list {
134 char id[SIZEOF_REV]; /* Commit SHA1 ID */
135 size_t size; /* Number of refs. */
136 struct ref **refs; /* References for this ID. */
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145 INPUT_OK,
146 INPUT_SKIP,
147 INPUT_STOP,
148 INPUT_CANCEL
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157 int hotkey;
158 const char *text;
159 void *data;
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
165 * Allocation helpers ... Entering macro hell to never be seen again.
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
169 static type * \
170 name(type **mem, size_t size, size_t increase) \
172 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
173 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174 type *tmp = *mem; \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178 if (tmp) \
179 *mem = tmp; \
182 return tmp; \
186 * String helpers
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192 if (srclen > dstlen - 1)
193 srclen = dstlen - 1;
195 strncpy(dst, src, srclen);
196 dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205 string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216 size_t size, pos;
218 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219 if (src[pos] == '\t') {
220 size_t expanded = tabsize - (size % tabsize);
222 if (expanded + size >= dstlen - 1)
223 expanded = dstlen - size - 1;
224 memcpy(dst + size, " ", expanded);
225 size += expanded;
226 } else {
227 dst[size++] = src[pos];
231 dst[size] = 0;
232 return pos;
235 static char *
236 chomp_string(char *name)
238 int namelen;
240 while (isspace(*name))
241 name++;
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
245 name[namelen--] = 0;
247 return name;
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
253 va_list args;
254 size_t pos = bufpos ? *bufpos : 0;
256 va_start(args, fmt);
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 va_end(args);
260 if (bufpos)
261 *bufpos = pos;
263 return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
275 size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
282 continue;
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
286 continue;
288 return str1[i] - str2[i];
291 return 0;
294 #define enum_equals(entry, str, len) \
295 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298 const char *name;
299 int namelen;
300 int value;
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
308 static char buf[SIZEOF_STR];
309 int bufpos;
311 for (bufpos = 0; bufpos <= namelen; bufpos++) {
312 buf[bufpos] = tolower(name[bufpos]);
313 if (buf[bufpos] == '_')
314 buf[bufpos] = '-';
317 buf[bufpos] = 0;
318 return buf;
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
326 size_t namelen = strlen(name);
327 int i;
329 for (i = 0; i < map_size; i++)
330 if (enum_equals(map[i], name, namelen)) {
331 *value = map[i].value;
332 return TRUE;
335 return FALSE;
338 #define map_enum(attr, map, name) \
339 map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342 strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
347 size_t len = slen >= 0 ? slen : strlen(str);
348 size_t suffixlen = strlen(suffix);
350 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
355 * Unicode / UTF-8 handling
357 * NOTE: Much of the following code for dealing with Unicode is derived from
358 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
365 if (c >= 0x1100 &&
366 (c <= 0x115f /* Hangul Jamo */
367 || c == 0x2329
368 || c == 0x232a
369 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
370 /* CJK ... Yi */
371 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
372 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
373 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
374 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
375 || (c >= 0xffe0 && c <= 0xffe6)
376 || (c >= 0x20000 && c <= 0x2fffd)
377 || (c >= 0x30000 && c <= 0x3fffd)))
378 return 2;
380 if (c == '\t')
381 return tab_size;
383 return 1;
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387 * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
389 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,
390 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,
391 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,
392 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,
393 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,
394 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,
395 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,
396 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,
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
402 int c = *(unsigned char *) string;
404 return utf8_bytes[c];
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
411 unsigned long unicode;
413 switch (length) {
414 case 1:
415 unicode = string[0];
416 break;
417 case 2:
418 unicode = (string[0] & 0x1f) << 6;
419 unicode += (string[1] & 0x3f);
420 break;
421 case 3:
422 unicode = (string[0] & 0x0f) << 12;
423 unicode += ((string[1] & 0x3f) << 6);
424 unicode += (string[2] & 0x3f);
425 break;
426 case 4:
427 unicode = (string[0] & 0x0f) << 18;
428 unicode += ((string[1] & 0x3f) << 12);
429 unicode += ((string[2] & 0x3f) << 6);
430 unicode += (string[3] & 0x3f);
431 break;
432 case 5:
433 unicode = (string[0] & 0x0f) << 24;
434 unicode += ((string[1] & 0x3f) << 18);
435 unicode += ((string[2] & 0x3f) << 12);
436 unicode += ((string[3] & 0x3f) << 6);
437 unicode += (string[4] & 0x3f);
438 break;
439 case 6:
440 unicode = (string[0] & 0x01) << 30;
441 unicode += ((string[1] & 0x3f) << 24);
442 unicode += ((string[2] & 0x3f) << 18);
443 unicode += ((string[3] & 0x3f) << 12);
444 unicode += ((string[4] & 0x3f) << 6);
445 unicode += (string[5] & 0x3f);
446 break;
447 default:
448 return 0;
451 /* Invalid characters could return the special 0xfffd value but NUL
452 * should be just as good. */
453 return unicode > 0xffff ? 0 : unicode;
456 /* Calculates how much of string can be shown within the given maximum width
457 * and sets trimmed parameter to non-zero value if all of string could not be
458 * shown. If the reserve flag is TRUE, it will reserve at least one
459 * trailing character, which can be useful when drawing a delimiter.
461 * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
465 const char *string = *start;
466 const char *end = strchr(string, '\0');
467 unsigned char last_bytes = 0;
468 size_t last_ucwidth = 0;
470 *width = 0;
471 *trimmed = 0;
473 while (string < end) {
474 unsigned char bytes = utf8_char_length(string, end);
475 size_t ucwidth;
476 unsigned long unicode;
478 if (string + bytes > end)
479 break;
481 /* Change representation to figure out whether
482 * it is a single- or double-width character. */
484 unicode = utf8_to_unicode(string, bytes);
485 /* FIXME: Graceful handling of invalid Unicode character. */
486 if (!unicode)
487 break;
489 ucwidth = unicode_width(unicode, tab_size);
490 if (skip > 0) {
491 skip -= ucwidth <= skip ? ucwidth : skip;
492 *start += bytes;
494 *width += ucwidth;
495 if (*width > max_width) {
496 *trimmed = 1;
497 *width -= ucwidth;
498 if (reserve && *width == max_width) {
499 string -= last_bytes;
500 *width -= last_ucwidth;
502 break;
505 string += bytes;
506 last_bytes = ucwidth ? bytes : 0;
507 last_ucwidth = ucwidth;
510 return string - *start;
514 #define DATE_INFO \
515 DATE_(NO), \
516 DATE_(DEFAULT), \
517 DATE_(LOCAL), \
518 DATE_(RELATIVE), \
519 DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523 DATE_INFO
524 #undef DATE_
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529 DATE_INFO
530 #undef DATE_
533 struct time {
534 time_t sec;
535 int tz;
538 static inline int timecmp(const struct time *t1, const struct time *t2)
540 return t1->sec - t2->sec;
543 static const char *
544 mkdate(const struct time *time, enum date date)
546 static char buf[DATE_COLS + 1];
547 static const struct enum_map reldate[] = {
548 { "second", 1, 60 * 2 },
549 { "minute", 60, 60 * 60 * 2 },
550 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
551 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
552 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
553 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
555 struct tm tm;
557 if (!date || !time || !time->sec)
558 return "";
560 if (date == DATE_RELATIVE) {
561 struct timeval now;
562 time_t date = time->sec + time->tz;
563 time_t seconds;
564 int i;
566 gettimeofday(&now, NULL);
567 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569 if (seconds >= reldate[i].value)
570 continue;
572 seconds /= reldate[i].namelen;
573 if (!string_format(buf, "%ld %s%s %s",
574 seconds, reldate[i].name,
575 seconds > 1 ? "s" : "",
576 now.tv_sec >= date ? "ago" : "ahead"))
577 break;
578 return buf;
582 if (date == DATE_LOCAL) {
583 time_t date = time->sec + time->tz;
584 localtime_r(&date, &tm);
586 else {
587 gmtime_r(&time->sec, &tm);
589 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
593 #define AUTHOR_VALUES \
594 AUTHOR_(NO), \
595 AUTHOR_(FULL), \
596 AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600 AUTHOR_VALUES,
601 #undef AUTHOR_
602 AUTHOR_DEFAULT = AUTHOR_FULL
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607 AUTHOR_VALUES
608 #undef AUTHOR_
611 static const char *
612 get_author_initials(const char *author)
614 static char initials[AUTHOR_COLS * 6 + 1];
615 size_t pos = 0;
616 const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620 memset(initials, 0, sizeof(initials));
621 while (author < end) {
622 unsigned char bytes;
623 size_t i;
625 while (is_initial_sep(*author))
626 author++;
628 bytes = utf8_char_length(author, end);
629 if (bytes < sizeof(initials) - 1 - pos) {
630 while (bytes--) {
631 initials[pos++] = *author++;
635 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636 if (i < sizeof(initials) - 1)
637 initials[i++] = *author;
640 initials[i++] = 0;
643 return initials;
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
650 int valuelen;
652 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653 bool advance = cmd[valuelen] != 0;
655 cmd[valuelen] = 0;
656 argv[(*argc)++] = chomp_string(cmd);
657 cmd = chomp_string(cmd + valuelen + advance);
660 if (*argc < SIZEOF_ARG)
661 argv[*argc] = NULL;
662 return *argc < SIZEOF_ARG;
665 static bool
666 argv_from_env(const char **argv, const char *name)
668 char *env = argv ? getenv(name) : NULL;
669 int argc = 0;
671 if (env && *env)
672 env = strdup(env);
673 return !env || argv_from_string(argv, &argc, env);
676 static void
677 argv_free(const char *argv[])
679 int argc;
681 if (!argv)
682 return;
683 for (argc = 0; argv[argc]; argc++)
684 free((void *) argv[argc]);
685 argv[0] = NULL;
688 static size_t
689 argv_size(const char **argv)
691 int argc = 0;
693 while (argv && argv[argc])
694 argc++;
696 return argc;
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
704 size_t argc = argv_size(*argv);
706 if (!argv_realloc(argv, argc, 2))
707 return FALSE;
709 (*argv)[argc++] = strdup(arg);
710 (*argv)[argc] = NULL;
711 return TRUE;
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
717 int i;
719 for (i = 0; src_argv && src_argv[i]; i++)
720 if (!argv_append(dst_argv, src_argv[i]))
721 return FALSE;
722 return TRUE;
725 static bool
726 argv_copy(const char ***dst, const char *src[])
728 int argc;
730 for (argc = 0; src[argc]; argc++)
731 if (!argv_append(dst, src[argc]))
732 return FALSE;
733 return TRUE;
738 * Executing external commands.
741 enum io_type {
742 IO_FD, /* File descriptor based IO. */
743 IO_BG, /* Execute command in the background. */
744 IO_FG, /* Execute command with same std{in,out,err}. */
745 IO_RD, /* Read only fork+exec IO. */
746 IO_WR, /* Write only fork+exec IO. */
747 IO_AP, /* Append fork+exec output to file. */
750 struct io {
751 int pipe; /* Pipe end for reading or writing. */
752 pid_t pid; /* PID of spawned process. */
753 int error; /* Error status. */
754 char *buf; /* Read buffer. */
755 size_t bufalloc; /* Allocated buffer size. */
756 size_t bufsize; /* Buffer content size. */
757 char *bufpos; /* Current buffer position. */
758 unsigned int eof:1; /* Has end of file been reached. */
761 static void
762 io_init(struct io *io)
764 memset(io, 0, sizeof(*io));
765 io->pipe = -1;
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
771 char name[SIZEOF_STR] = "";
772 bool fits;
773 va_list args;
775 io_init(io);
777 va_start(args, fmt);
778 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779 va_end(args);
781 if (!fits) {
782 io->error = ENAMETOOLONG;
783 return FALSE;
785 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786 if (io->pipe == -1)
787 io->error = errno;
788 return io->pipe != -1;
791 static bool
792 io_kill(struct io *io)
794 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
797 static bool
798 io_done(struct io *io)
800 pid_t pid = io->pid;
802 if (io->pipe != -1)
803 close(io->pipe);
804 free(io->buf);
805 io_init(io);
807 while (pid > 0) {
808 int status;
809 pid_t waiting = waitpid(pid, &status, 0);
811 if (waiting < 0) {
812 if (errno == EINTR)
813 continue;
814 io->error = errno;
815 return FALSE;
818 return waiting == pid &&
819 !WIFSIGNALED(status) &&
820 WIFEXITED(status) &&
821 !WEXITSTATUS(status);
824 return TRUE;
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
830 int pipefds[2] = { -1, -1 };
831 va_list args;
833 io_init(io);
835 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836 io->error = errno;
837 return FALSE;
838 } else if (type == IO_AP) {
839 va_start(args, argv);
840 pipefds[1] = va_arg(args, int);
841 va_end(args);
844 if ((io->pid = fork())) {
845 if (io->pid == -1)
846 io->error = errno;
847 if (pipefds[!(type == IO_WR)] != -1)
848 close(pipefds[!(type == IO_WR)]);
849 if (io->pid != -1) {
850 io->pipe = pipefds[!!(type == IO_WR)];
851 return TRUE;
854 } else {
855 if (type != IO_FG) {
856 int devnull = open("/dev/null", O_RDWR);
857 int readfd = type == IO_WR ? pipefds[0] : devnull;
858 int writefd = (type == IO_RD || type == IO_AP)
859 ? pipefds[1] : devnull;
861 dup2(readfd, STDIN_FILENO);
862 dup2(writefd, STDOUT_FILENO);
863 dup2(devnull, STDERR_FILENO);
865 close(devnull);
866 if (pipefds[0] != -1)
867 close(pipefds[0]);
868 if (pipefds[1] != -1)
869 close(pipefds[1]);
872 if (dir && *dir && chdir(dir) == -1)
873 exit(errno);
875 execvp(argv[0], (char *const*) argv);
876 exit(errno);
879 if (pipefds[!!(type == IO_WR)] != -1)
880 close(pipefds[!!(type == IO_WR)]);
881 return FALSE;
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
887 struct io io;
889 return io_run(&io, type, dir, argv, fd) && io_done(&io);
892 static bool
893 io_run_bg(const char **argv)
895 return io_complete(IO_BG, argv, NULL, -1);
898 static bool
899 io_run_fg(const char **argv, const char *dir)
901 return io_complete(IO_FG, argv, dir, -1);
904 static bool
905 io_run_append(const char **argv, int fd)
907 return io_complete(IO_AP, argv, NULL, fd);
910 static bool
911 io_eof(struct io *io)
913 return io->eof;
916 static int
917 io_error(struct io *io)
919 return io->error;
922 static char *
923 io_strerror(struct io *io)
925 return strerror(io->error);
928 static bool
929 io_can_read(struct io *io)
931 struct timeval tv = { 0, 500 };
932 fd_set fds;
934 FD_ZERO(&fds);
935 FD_SET(io->pipe, &fds);
937 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
943 do {
944 ssize_t readsize = read(io->pipe, buf, bufsize);
946 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947 continue;
948 else if (readsize == -1)
949 io->error = errno;
950 else if (readsize == 0)
951 io->eof = 1;
952 return readsize;
953 } while (1);
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
961 char *eol;
962 ssize_t readsize;
964 while (TRUE) {
965 if (io->bufsize > 0) {
966 eol = memchr(io->bufpos, c, io->bufsize);
967 if (eol) {
968 char *line = io->bufpos;
970 *eol = 0;
971 io->bufpos = eol + 1;
972 io->bufsize -= io->bufpos - line;
973 return line;
977 if (io_eof(io)) {
978 if (io->bufsize) {
979 io->bufpos[io->bufsize] = 0;
980 io->bufsize = 0;
981 return io->bufpos;
983 return NULL;
986 if (!can_read)
987 return NULL;
989 if (io->bufsize > 0 && io->bufpos > io->buf)
990 memmove(io->buf, io->bufpos, io->bufsize);
992 if (io->bufalloc == io->bufsize) {
993 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994 return NULL;
995 io->bufalloc += BUFSIZ;
998 io->bufpos = io->buf;
999 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000 if (io_error(io))
1001 return NULL;
1002 io->bufsize += readsize;
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1009 size_t written = 0;
1011 while (!io_error(io) && written < bufsize) {
1012 ssize_t size;
1014 size = write(io->pipe, buf + written, bufsize - written);
1015 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016 continue;
1017 else if (size == -1)
1018 io->error = errno;
1019 else
1020 written += size;
1023 return written == bufsize;
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1029 char *result = io_get(io, '\n', TRUE);
1031 if (result) {
1032 result = chomp_string(result);
1033 string_ncopy_do(buf, bufsize, result, strlen(result));
1036 return io_done(io) && result;
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1042 struct io io;
1044 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1047 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1049 static int
1050 io_load(struct io *io, const char *separators,
1051 io_read_fn read_property, void *data)
1053 char *name;
1054 int state = OK;
1056 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1057 char *value;
1058 size_t namelen;
1059 size_t valuelen;
1061 name = chomp_string(name);
1062 namelen = strcspn(name, separators);
1064 if (name[namelen]) {
1065 name[namelen] = 0;
1066 value = chomp_string(name + namelen + 1);
1067 valuelen = strlen(value);
1069 } else {
1070 value = "";
1071 valuelen = 0;
1074 state = read_property(name, namelen, value, valuelen, data);
1077 if (state != ERR && io_error(io))
1078 state = ERR;
1079 io_done(io);
1081 return state;
1084 static int
1085 io_run_load(const char **argv, const char *separators,
1086 io_read_fn read_property, void *data)
1088 struct io io;
1090 if (!io_run(&io, IO_RD, NULL, argv))
1091 return ERR;
1092 return io_load(&io, separators, read_property, data);
1097 * User requests
1100 #define REQ_INFO \
1101 /* XXX: Keep the view request first and in sync with views[]. */ \
1102 REQ_GROUP("View switching") \
1103 REQ_(VIEW_MAIN, "Show main view"), \
1104 REQ_(VIEW_DIFF, "Show diff view"), \
1105 REQ_(VIEW_LOG, "Show log view"), \
1106 REQ_(VIEW_TREE, "Show tree view"), \
1107 REQ_(VIEW_BLOB, "Show blob view"), \
1108 REQ_(VIEW_BLAME, "Show blame view"), \
1109 REQ_(VIEW_BRANCH, "Show branch view"), \
1110 REQ_(VIEW_HELP, "Show help page"), \
1111 REQ_(VIEW_PAGER, "Show pager view"), \
1112 REQ_(VIEW_STATUS, "Show status view"), \
1113 REQ_(VIEW_STAGE, "Show stage view"), \
1115 REQ_GROUP("View manipulation") \
1116 REQ_(ENTER, "Enter current line and scroll"), \
1117 REQ_(NEXT, "Move to next"), \
1118 REQ_(PREVIOUS, "Move to previous"), \
1119 REQ_(PARENT, "Move to parent"), \
1120 REQ_(VIEW_NEXT, "Move focus to next view"), \
1121 REQ_(REFRESH, "Reload and refresh"), \
1122 REQ_(MAXIMIZE, "Maximize the current view"), \
1123 REQ_(VIEW_CLOSE, "Close the current view"), \
1124 REQ_(QUIT, "Close all views and quit"), \
1126 REQ_GROUP("View specific requests") \
1127 REQ_(STATUS_UPDATE, "Update file status"), \
1128 REQ_(STATUS_REVERT, "Revert file changes"), \
1129 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1130 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1132 REQ_GROUP("Cursor navigation") \
1133 REQ_(MOVE_UP, "Move cursor one line up"), \
1134 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1135 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1136 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1137 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1138 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1140 REQ_GROUP("Scrolling") \
1141 REQ_(SCROLL_FIRST_COL, "Scroll to the first line columns"), \
1142 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1143 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1144 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1145 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1146 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1147 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1149 REQ_GROUP("Searching") \
1150 REQ_(SEARCH, "Search the view"), \
1151 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1152 REQ_(FIND_NEXT, "Find next search match"), \
1153 REQ_(FIND_PREV, "Find previous search match"), \
1155 REQ_GROUP("Option manipulation") \
1156 REQ_(OPTIONS, "Open option menu"), \
1157 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1158 REQ_(TOGGLE_DATE, "Toggle date display"), \
1159 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1160 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1161 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1162 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1163 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1165 REQ_GROUP("Misc") \
1166 REQ_(PROMPT, "Bring up the prompt"), \
1167 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1168 REQ_(SHOW_VERSION, "Show version information"), \
1169 REQ_(STOP_LOADING, "Stop all loading views"), \
1170 REQ_(EDIT, "Open in editor"), \
1171 REQ_(NONE, "Do nothing")
1174 /* User action requests. */
1175 enum request {
1176 #define REQ_GROUP(help)
1177 #define REQ_(req, help) REQ_##req
1179 /* Offset all requests to avoid conflicts with ncurses getch values. */
1180 REQ_UNKNOWN = KEY_MAX + 1,
1181 REQ_OFFSET,
1182 REQ_INFO
1184 #undef REQ_GROUP
1185 #undef REQ_
1188 struct request_info {
1189 enum request request;
1190 const char *name;
1191 int namelen;
1192 const char *help;
1195 static const struct request_info req_info[] = {
1196 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1197 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1198 REQ_INFO
1199 #undef REQ_GROUP
1200 #undef REQ_
1203 static enum request
1204 get_request(const char *name)
1206 int namelen = strlen(name);
1207 int i;
1209 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1210 if (enum_equals(req_info[i], name, namelen))
1211 return req_info[i].request;
1213 return REQ_UNKNOWN;
1218 * Options
1221 /* Option and state variables. */
1222 static enum date opt_date = DATE_DEFAULT;
1223 static enum author opt_author = AUTHOR_DEFAULT;
1224 static bool opt_line_number = FALSE;
1225 static bool opt_line_graphics = TRUE;
1226 static bool opt_rev_graph = FALSE;
1227 static bool opt_show_refs = TRUE;
1228 static bool opt_untracked_dirs_content = TRUE;
1229 static int opt_num_interval = 5;
1230 static double opt_hscroll = 0.50;
1231 static double opt_scale_split_view = 2.0 / 3.0;
1232 static int opt_tab_size = 8;
1233 static int opt_author_cols = AUTHOR_COLS;
1234 static char opt_path[SIZEOF_STR] = "";
1235 static char opt_file[SIZEOF_STR] = "";
1236 static char opt_ref[SIZEOF_REF] = "";
1237 static char opt_head[SIZEOF_REF] = "";
1238 static char opt_remote[SIZEOF_REF] = "";
1239 static char opt_encoding[20] = "UTF-8";
1240 static iconv_t opt_iconv_in = ICONV_NONE;
1241 static iconv_t opt_iconv_out = ICONV_NONE;
1242 static char opt_search[SIZEOF_STR] = "";
1243 static char opt_cdup[SIZEOF_STR] = "";
1244 static char opt_prefix[SIZEOF_STR] = "";
1245 static char opt_git_dir[SIZEOF_STR] = "";
1246 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1247 static char opt_editor[SIZEOF_STR] = "";
1248 static FILE *opt_tty = NULL;
1249 static const char **opt_diff_argv = NULL;
1250 static const char **opt_rev_argv = NULL;
1251 static const char **opt_file_argv = NULL;
1253 #define is_initial_commit() (!get_ref_head())
1254 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1258 * Line-oriented content detection.
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1276 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1277 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1283 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1284 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1286 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1291 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1294 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1295 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1297 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1298 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1300 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1301 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1310 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1311 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1312 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1313 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1314 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1315 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1317 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1318 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1319 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1320 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324 LINE_##type
1325 LINE_INFO,
1326 LINE_NONE
1327 #undef LINE
1330 struct line_info {
1331 const char *name; /* Option name. */
1332 int namelen; /* Size of option name. */
1333 const char *line; /* The start of line to match. */
1334 int linelen; /* Size of string to match. */
1335 int fg, bg, attr; /* Color and text attributes for the lines. */
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341 LINE_INFO
1342 #undef LINE
1345 static enum line_type
1346 get_line_type(const char *line)
1348 int linelen = strlen(line);
1349 enum line_type type;
1351 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352 /* Case insensitive search matches Signed-off-by lines better. */
1353 if (linelen >= line_info[type].linelen &&
1354 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355 return type;
1357 return LINE_DEFAULT;
1360 static inline int
1361 get_line_attr(enum line_type type)
1363 assert(type < ARRAY_SIZE(line_info));
1364 return COLOR_PAIR(type) | line_info[type].attr;
1367 static struct line_info *
1368 get_line_info(const char *name)
1370 size_t namelen = strlen(name);
1371 enum line_type type;
1373 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374 if (enum_equals(line_info[type], name, namelen))
1375 return &line_info[type];
1377 return NULL;
1380 static void
1381 init_colors(void)
1383 int default_bg = line_info[LINE_DEFAULT].bg;
1384 int default_fg = line_info[LINE_DEFAULT].fg;
1385 enum line_type type;
1387 start_color();
1389 if (assume_default_colors(default_fg, default_bg) == ERR) {
1390 default_bg = COLOR_BLACK;
1391 default_fg = COLOR_WHITE;
1394 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395 struct line_info *info = &line_info[type];
1396 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399 init_pair(type, fg, bg);
1403 struct line {
1404 enum line_type type;
1406 /* State flags */
1407 unsigned int selected:1;
1408 unsigned int dirty:1;
1409 unsigned int cleareol:1;
1410 unsigned int other:16;
1412 void *data; /* User data */
1417 * Keys
1420 struct keybinding {
1421 int alias;
1422 enum request request;
1425 static struct keybinding default_keybindings[] = {
1426 /* View switching */
1427 { 'm', REQ_VIEW_MAIN },
1428 { 'd', REQ_VIEW_DIFF },
1429 { 'l', REQ_VIEW_LOG },
1430 { 't', REQ_VIEW_TREE },
1431 { 'f', REQ_VIEW_BLOB },
1432 { 'B', REQ_VIEW_BLAME },
1433 { 'H', REQ_VIEW_BRANCH },
1434 { 'p', REQ_VIEW_PAGER },
1435 { 'h', REQ_VIEW_HELP },
1436 { 'S', REQ_VIEW_STATUS },
1437 { 'c', REQ_VIEW_STAGE },
1439 /* View manipulation */
1440 { 'q', REQ_VIEW_CLOSE },
1441 { KEY_TAB, REQ_VIEW_NEXT },
1442 { KEY_RETURN, REQ_ENTER },
1443 { KEY_UP, REQ_PREVIOUS },
1444 { KEY_CTL('P'), REQ_PREVIOUS },
1445 { KEY_DOWN, REQ_NEXT },
1446 { KEY_CTL('N'), REQ_NEXT },
1447 { 'R', REQ_REFRESH },
1448 { KEY_F(5), REQ_REFRESH },
1449 { 'O', REQ_MAXIMIZE },
1451 /* Cursor navigation */
1452 { 'k', REQ_MOVE_UP },
1453 { 'j', REQ_MOVE_DOWN },
1454 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1455 { KEY_END, REQ_MOVE_LAST_LINE },
1456 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1457 { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1458 { ' ', REQ_MOVE_PAGE_DOWN },
1459 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1460 { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1461 { 'b', REQ_MOVE_PAGE_UP },
1462 { '-', REQ_MOVE_PAGE_UP },
1464 /* Scrolling */
1465 { '|', REQ_SCROLL_FIRST_COL },
1466 { KEY_LEFT, REQ_SCROLL_LEFT },
1467 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1468 { KEY_IC, REQ_SCROLL_LINE_UP },
1469 { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1470 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1471 { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1472 { 'w', REQ_SCROLL_PAGE_UP },
1473 { 's', REQ_SCROLL_PAGE_DOWN },
1475 /* Searching */
1476 { '/', REQ_SEARCH },
1477 { '?', REQ_SEARCH_BACK },
1478 { 'n', REQ_FIND_NEXT },
1479 { 'N', REQ_FIND_PREV },
1481 /* Misc */
1482 { 'Q', REQ_QUIT },
1483 { 'z', REQ_STOP_LOADING },
1484 { 'v', REQ_SHOW_VERSION },
1485 { 'r', REQ_SCREEN_REDRAW },
1486 { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1487 { 'o', REQ_OPTIONS },
1488 { '.', REQ_TOGGLE_LINENO },
1489 { 'D', REQ_TOGGLE_DATE },
1490 { 'A', REQ_TOGGLE_AUTHOR },
1491 { 'g', REQ_TOGGLE_REV_GRAPH },
1492 { 'F', REQ_TOGGLE_REFS },
1493 { 'I', REQ_TOGGLE_SORT_ORDER },
1494 { 'i', REQ_TOGGLE_SORT_FIELD },
1495 { ':', REQ_PROMPT },
1496 { 'u', REQ_STATUS_UPDATE },
1497 { '!', REQ_STATUS_REVERT },
1498 { 'M', REQ_STATUS_MERGE },
1499 { '@', REQ_STAGE_NEXT },
1500 { ',', REQ_PARENT },
1501 { 'e', REQ_EDIT },
1504 #define KEYMAP_INFO \
1505 KEYMAP_(GENERIC), \
1506 KEYMAP_(MAIN), \
1507 KEYMAP_(DIFF), \
1508 KEYMAP_(LOG), \
1509 KEYMAP_(TREE), \
1510 KEYMAP_(BLOB), \
1511 KEYMAP_(BLAME), \
1512 KEYMAP_(BRANCH), \
1513 KEYMAP_(PAGER), \
1514 KEYMAP_(HELP), \
1515 KEYMAP_(STATUS), \
1516 KEYMAP_(STAGE)
1518 enum keymap {
1519 #define KEYMAP_(name) KEYMAP_##name
1520 KEYMAP_INFO
1521 #undef KEYMAP_
1524 static const struct enum_map keymap_table[] = {
1525 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1526 KEYMAP_INFO
1527 #undef KEYMAP_
1530 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1532 struct keybinding_table {
1533 struct keybinding *data;
1534 size_t size;
1537 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1539 static void
1540 add_keybinding(enum keymap keymap, enum request request, int key)
1542 struct keybinding_table *table = &keybindings[keymap];
1543 size_t i;
1545 for (i = 0; i < keybindings[keymap].size; i++) {
1546 if (keybindings[keymap].data[i].alias == key) {
1547 keybindings[keymap].data[i].request = request;
1548 return;
1552 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1553 if (!table->data)
1554 die("Failed to allocate keybinding");
1555 table->data[table->size].alias = key;
1556 table->data[table->size++].request = request;
1558 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1559 int i;
1561 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562 if (default_keybindings[i].alias == key)
1563 default_keybindings[i].request = REQ_NONE;
1567 /* Looks for a key binding first in the given map, then in the generic map, and
1568 * lastly in the default keybindings. */
1569 static enum request
1570 get_keybinding(enum keymap keymap, int key)
1572 size_t i;
1574 for (i = 0; i < keybindings[keymap].size; i++)
1575 if (keybindings[keymap].data[i].alias == key)
1576 return keybindings[keymap].data[i].request;
1578 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1579 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1580 return keybindings[KEYMAP_GENERIC].data[i].request;
1582 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1583 if (default_keybindings[i].alias == key)
1584 return default_keybindings[i].request;
1586 return (enum request) key;
1590 struct key {
1591 const char *name;
1592 int value;
1595 static const struct key key_table[] = {
1596 { "Enter", KEY_RETURN },
1597 { "Space", ' ' },
1598 { "Backspace", KEY_BACKSPACE },
1599 { "Tab", KEY_TAB },
1600 { "Escape", KEY_ESC },
1601 { "Left", KEY_LEFT },
1602 { "Right", KEY_RIGHT },
1603 { "Up", KEY_UP },
1604 { "Down", KEY_DOWN },
1605 { "Insert", KEY_IC },
1606 { "Delete", KEY_DC },
1607 { "Hash", '#' },
1608 { "Home", KEY_HOME },
1609 { "End", KEY_END },
1610 { "PageUp", KEY_PPAGE },
1611 { "PageDown", KEY_NPAGE },
1612 { "F1", KEY_F(1) },
1613 { "F2", KEY_F(2) },
1614 { "F3", KEY_F(3) },
1615 { "F4", KEY_F(4) },
1616 { "F5", KEY_F(5) },
1617 { "F6", KEY_F(6) },
1618 { "F7", KEY_F(7) },
1619 { "F8", KEY_F(8) },
1620 { "F9", KEY_F(9) },
1621 { "F10", KEY_F(10) },
1622 { "F11", KEY_F(11) },
1623 { "F12", KEY_F(12) },
1626 static int
1627 get_key_value(const char *name)
1629 int i;
1631 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1632 if (!strcasecmp(key_table[i].name, name))
1633 return key_table[i].value;
1635 if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1636 return (int)name[1] & 0x1f;
1637 if (strlen(name) == 1 && isprint(*name))
1638 return (int) *name;
1639 return ERR;
1642 static const char *
1643 get_key_name(int key_value)
1645 static char key_char[] = "'X'\0";
1646 const char *seq = NULL;
1647 int key;
1649 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1650 if (key_table[key].value == key_value)
1651 seq = key_table[key].name;
1653 if (seq == NULL && key_value < 0x7f) {
1654 char *s = key_char + 1;
1656 if (key_value >= 0x20) {
1657 *s++ = key_value;
1658 } else {
1659 *s++ = '^';
1660 *s++ = 0x40 | (key_value & 0x1f);
1662 *s++ = '\'';
1663 *s++ = '\0';
1664 seq = key_char;
1667 return seq ? seq : "(no key)";
1670 static bool
1671 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1673 const char *sep = *pos > 0 ? ", " : "";
1674 const char *keyname = get_key_name(keybinding->alias);
1676 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1679 static bool
1680 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1681 enum keymap keymap, bool all)
1683 int i;
1685 for (i = 0; i < keybindings[keymap].size; i++) {
1686 if (keybindings[keymap].data[i].request == request) {
1687 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1688 return FALSE;
1689 if (!all)
1690 break;
1694 return TRUE;
1697 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1699 static const char *
1700 get_keys(enum keymap keymap, enum request request, bool all)
1702 static char buf[BUFSIZ];
1703 size_t pos = 0;
1704 int i;
1706 buf[pos] = 0;
1708 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1709 return "Too many keybindings!";
1710 if (pos > 0 && !all)
1711 return buf;
1713 if (keymap != KEYMAP_GENERIC) {
1714 /* Only the generic keymap includes the default keybindings when
1715 * listing all keys. */
1716 if (all)
1717 return buf;
1719 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1720 return "Too many keybindings!";
1721 if (pos)
1722 return buf;
1725 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1726 if (default_keybindings[i].request == request) {
1727 if (!append_key(buf, &pos, &default_keybindings[i]))
1728 return "Too many keybindings!";
1729 if (!all)
1730 return buf;
1734 return buf;
1737 struct run_request {
1738 enum keymap keymap;
1739 int key;
1740 const char **argv;
1743 static struct run_request *run_request;
1744 static size_t run_requests;
1746 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1748 static enum request
1749 add_run_request(enum keymap keymap, int key, const char **argv)
1751 struct run_request *req;
1753 if (!realloc_run_requests(&run_request, run_requests, 1))
1754 return REQ_NONE;
1756 req = &run_request[run_requests];
1757 req->keymap = keymap;
1758 req->key = key;
1759 req->argv = NULL;
1761 if (!argv_copy(&req->argv, argv))
1762 return REQ_NONE;
1764 return REQ_NONE + ++run_requests;
1767 static struct run_request *
1768 get_run_request(enum request request)
1770 if (request <= REQ_NONE)
1771 return NULL;
1772 return &run_request[request - REQ_NONE - 1];
1775 static void
1776 add_builtin_run_requests(void)
1778 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1779 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1780 const char *commit[] = { "git", "commit", NULL };
1781 const char *gc[] = { "git", "gc", NULL };
1782 struct run_request reqs[] = {
1783 { KEYMAP_MAIN, 'C', cherry_pick },
1784 { KEYMAP_STATUS, 'C', commit },
1785 { KEYMAP_BRANCH, 'C', checkout },
1786 { KEYMAP_GENERIC, 'G', gc },
1788 int i;
1790 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1791 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1793 if (req != reqs[i].key)
1794 continue;
1795 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1796 if (req != REQ_NONE)
1797 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1802 * User config file handling.
1805 static int config_lineno;
1806 static bool config_errors;
1807 static const char *config_msg;
1809 static const struct enum_map color_map[] = {
1810 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1811 COLOR_MAP(DEFAULT),
1812 COLOR_MAP(BLACK),
1813 COLOR_MAP(BLUE),
1814 COLOR_MAP(CYAN),
1815 COLOR_MAP(GREEN),
1816 COLOR_MAP(MAGENTA),
1817 COLOR_MAP(RED),
1818 COLOR_MAP(WHITE),
1819 COLOR_MAP(YELLOW),
1822 static const struct enum_map attr_map[] = {
1823 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1824 ATTR_MAP(NORMAL),
1825 ATTR_MAP(BLINK),
1826 ATTR_MAP(BOLD),
1827 ATTR_MAP(DIM),
1828 ATTR_MAP(REVERSE),
1829 ATTR_MAP(STANDOUT),
1830 ATTR_MAP(UNDERLINE),
1833 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1835 static int parse_step(double *opt, const char *arg)
1837 *opt = atoi(arg);
1838 if (!strchr(arg, '%'))
1839 return OK;
1841 /* "Shift down" so 100% and 1 does not conflict. */
1842 *opt = (*opt - 1) / 100;
1843 if (*opt >= 1.0) {
1844 *opt = 0.99;
1845 config_msg = "Step value larger than 100%";
1846 return ERR;
1848 if (*opt < 0.0) {
1849 *opt = 1;
1850 config_msg = "Invalid step value";
1851 return ERR;
1853 return OK;
1856 static int
1857 parse_int(int *opt, const char *arg, int min, int max)
1859 int value = atoi(arg);
1861 if (min <= value && value <= max) {
1862 *opt = value;
1863 return OK;
1866 config_msg = "Integer value out of bound";
1867 return ERR;
1870 static bool
1871 set_color(int *color, const char *name)
1873 if (map_enum(color, color_map, name))
1874 return TRUE;
1875 if (!prefixcmp(name, "color"))
1876 return parse_int(color, name + 5, 0, 255) == OK;
1877 return FALSE;
1880 /* Wants: object fgcolor bgcolor [attribute] */
1881 static int
1882 option_color_command(int argc, const char *argv[])
1884 struct line_info *info;
1886 if (argc < 3) {
1887 config_msg = "Wrong number of arguments given to color command";
1888 return ERR;
1891 info = get_line_info(argv[0]);
1892 if (!info) {
1893 static const struct enum_map obsolete[] = {
1894 ENUM_MAP("main-delim", LINE_DELIMITER),
1895 ENUM_MAP("main-date", LINE_DATE),
1896 ENUM_MAP("main-author", LINE_AUTHOR),
1898 int index;
1900 if (!map_enum(&index, obsolete, argv[0])) {
1901 config_msg = "Unknown color name";
1902 return ERR;
1904 info = &line_info[index];
1907 if (!set_color(&info->fg, argv[1]) ||
1908 !set_color(&info->bg, argv[2])) {
1909 config_msg = "Unknown color";
1910 return ERR;
1913 info->attr = 0;
1914 while (argc-- > 3) {
1915 int attr;
1917 if (!set_attribute(&attr, argv[argc])) {
1918 config_msg = "Unknown attribute";
1919 return ERR;
1921 info->attr |= attr;
1924 return OK;
1927 static int parse_bool(bool *opt, const char *arg)
1929 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1930 ? TRUE : FALSE;
1931 return OK;
1934 static int parse_enum_do(unsigned int *opt, const char *arg,
1935 const struct enum_map *map, size_t map_size)
1937 bool is_true;
1939 assert(map_size > 1);
1941 if (map_enum_do(map, map_size, (int *) opt, arg))
1942 return OK;
1944 if (parse_bool(&is_true, arg) != OK)
1945 return ERR;
1947 *opt = is_true ? map[1].value : map[0].value;
1948 return OK;
1951 #define parse_enum(opt, arg, map) \
1952 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1954 static int
1955 parse_string(char *opt, const char *arg, size_t optsize)
1957 int arglen = strlen(arg);
1959 switch (arg[0]) {
1960 case '\"':
1961 case '\'':
1962 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1963 config_msg = "Unmatched quotation";
1964 return ERR;
1966 arg += 1; arglen -= 2;
1967 default:
1968 string_ncopy_do(opt, optsize, arg, arglen);
1969 return OK;
1973 /* Wants: name = value */
1974 static int
1975 option_set_command(int argc, const char *argv[])
1977 if (argc != 3) {
1978 config_msg = "Wrong number of arguments given to set command";
1979 return ERR;
1982 if (strcmp(argv[1], "=")) {
1983 config_msg = "No value assigned";
1984 return ERR;
1987 if (!strcmp(argv[0], "show-author"))
1988 return parse_enum(&opt_author, argv[2], author_map);
1990 if (!strcmp(argv[0], "show-date"))
1991 return parse_enum(&opt_date, argv[2], date_map);
1993 if (!strcmp(argv[0], "show-rev-graph"))
1994 return parse_bool(&opt_rev_graph, argv[2]);
1996 if (!strcmp(argv[0], "show-refs"))
1997 return parse_bool(&opt_show_refs, argv[2]);
1999 if (!strcmp(argv[0], "show-line-numbers"))
2000 return parse_bool(&opt_line_number, argv[2]);
2002 if (!strcmp(argv[0], "line-graphics"))
2003 return parse_bool(&opt_line_graphics, argv[2]);
2005 if (!strcmp(argv[0], "line-number-interval"))
2006 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2008 if (!strcmp(argv[0], "author-width"))
2009 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2011 if (!strcmp(argv[0], "horizontal-scroll"))
2012 return parse_step(&opt_hscroll, argv[2]);
2014 if (!strcmp(argv[0], "split-view-height"))
2015 return parse_step(&opt_scale_split_view, argv[2]);
2017 if (!strcmp(argv[0], "tab-size"))
2018 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2020 if (!strcmp(argv[0], "commit-encoding"))
2021 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2023 if (!strcmp(argv[0], "status-untracked-dirs"))
2024 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2026 config_msg = "Unknown variable name";
2027 return ERR;
2030 /* Wants: mode request key */
2031 static int
2032 option_bind_command(int argc, const char *argv[])
2034 enum request request;
2035 int keymap = -1;
2036 int key;
2038 if (argc < 3) {
2039 config_msg = "Wrong number of arguments given to bind command";
2040 return ERR;
2043 if (!set_keymap(&keymap, argv[0])) {
2044 config_msg = "Unknown key map";
2045 return ERR;
2048 key = get_key_value(argv[1]);
2049 if (key == ERR) {
2050 config_msg = "Unknown key";
2051 return ERR;
2054 request = get_request(argv[2]);
2055 if (request == REQ_UNKNOWN) {
2056 static const struct enum_map obsolete[] = {
2057 ENUM_MAP("cherry-pick", REQ_NONE),
2058 ENUM_MAP("screen-resize", REQ_NONE),
2059 ENUM_MAP("tree-parent", REQ_PARENT),
2061 int alias;
2063 if (map_enum(&alias, obsolete, argv[2])) {
2064 if (alias != REQ_NONE)
2065 add_keybinding(keymap, alias, key);
2066 config_msg = "Obsolete request name";
2067 return ERR;
2070 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2071 request = add_run_request(keymap, key, argv + 2);
2072 if (request == REQ_UNKNOWN) {
2073 config_msg = "Unknown request name";
2074 return ERR;
2077 add_keybinding(keymap, request, key);
2079 return OK;
2082 static int
2083 set_option(const char *opt, char *value)
2085 const char *argv[SIZEOF_ARG];
2086 int argc = 0;
2088 if (!argv_from_string(argv, &argc, value)) {
2089 config_msg = "Too many option arguments";
2090 return ERR;
2093 if (!strcmp(opt, "color"))
2094 return option_color_command(argc, argv);
2096 if (!strcmp(opt, "set"))
2097 return option_set_command(argc, argv);
2099 if (!strcmp(opt, "bind"))
2100 return option_bind_command(argc, argv);
2102 config_msg = "Unknown option command";
2103 return ERR;
2106 static int
2107 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2109 int status = OK;
2111 config_lineno++;
2112 config_msg = "Internal error";
2114 /* Check for comment markers, since read_properties() will
2115 * only ensure opt and value are split at first " \t". */
2116 optlen = strcspn(opt, "#");
2117 if (optlen == 0)
2118 return OK;
2120 if (opt[optlen] != 0) {
2121 config_msg = "No option value";
2122 status = ERR;
2124 } else {
2125 /* Look for comment endings in the value. */
2126 size_t len = strcspn(value, "#");
2128 if (len < valuelen) {
2129 valuelen = len;
2130 value[valuelen] = 0;
2133 status = set_option(opt, value);
2136 if (status == ERR) {
2137 warn("Error on line %d, near '%.*s': %s",
2138 config_lineno, (int) optlen, opt, config_msg);
2139 config_errors = TRUE;
2142 /* Always keep going if errors are encountered. */
2143 return OK;
2146 static void
2147 load_option_file(const char *path)
2149 struct io io;
2151 /* It's OK that the file doesn't exist. */
2152 if (!io_open(&io, "%s", path))
2153 return;
2155 config_lineno = 0;
2156 config_errors = FALSE;
2158 if (io_load(&io, " \t", read_option, NULL) == ERR ||
2159 config_errors == TRUE)
2160 warn("Errors while loading %s.", path);
2163 static int
2164 load_options(void)
2166 const char *home = getenv("HOME");
2167 const char *tigrc_user = getenv("TIGRC_USER");
2168 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2169 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2170 char buf[SIZEOF_STR];
2172 if (!tigrc_system)
2173 tigrc_system = SYSCONFDIR "/tigrc";
2174 load_option_file(tigrc_system);
2176 if (!tigrc_user) {
2177 if (!home || !string_format(buf, "%s/.tigrc", home))
2178 return ERR;
2179 tigrc_user = buf;
2181 load_option_file(tigrc_user);
2183 /* Add _after_ loading config files to avoid adding run requests
2184 * that conflict with keybindings. */
2185 add_builtin_run_requests();
2187 if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2188 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2189 int argc = 0;
2191 if (!string_format(buf, "%s", tig_diff_opts) ||
2192 !argv_from_string(diff_opts, &argc, buf))
2193 die("TIG_DIFF_OPTS contains too many arguments");
2194 else if (!argv_copy(&opt_diff_argv, diff_opts))
2195 die("Failed to format TIG_DIFF_OPTS arguments");
2198 return OK;
2203 * The viewer
2206 struct view;
2207 struct view_ops;
2209 /* The display array of active views and the index of the current view. */
2210 static struct view *display[2];
2211 static unsigned int current_view;
2213 #define foreach_displayed_view(view, i) \
2214 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2216 #define displayed_views() (display[1] != NULL ? 2 : 1)
2218 /* Current head and commit ID */
2219 static char ref_blob[SIZEOF_REF] = "";
2220 static char ref_commit[SIZEOF_REF] = "HEAD";
2221 static char ref_head[SIZEOF_REF] = "HEAD";
2222 static char ref_branch[SIZEOF_REF] = "";
2224 enum view_type {
2225 VIEW_MAIN,
2226 VIEW_DIFF,
2227 VIEW_LOG,
2228 VIEW_TREE,
2229 VIEW_BLOB,
2230 VIEW_BLAME,
2231 VIEW_BRANCH,
2232 VIEW_HELP,
2233 VIEW_PAGER,
2234 VIEW_STATUS,
2235 VIEW_STAGE,
2238 struct view {
2239 enum view_type type; /* View type */
2240 const char *name; /* View name */
2241 const char *cmd_env; /* Command line set via environment */
2242 const char *id; /* Points to either of ref_{head,commit,blob} */
2244 struct view_ops *ops; /* View operations */
2246 enum keymap keymap; /* What keymap does this view have */
2247 bool git_dir; /* Whether the view requires a git directory. */
2249 char ref[SIZEOF_REF]; /* Hovered commit reference */
2250 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2252 int height, width; /* The width and height of the main window */
2253 WINDOW *win; /* The main window */
2254 WINDOW *title; /* The title window living below the main window */
2256 /* Navigation */
2257 unsigned long offset; /* Offset of the window top */
2258 unsigned long yoffset; /* Offset from the window side. */
2259 unsigned long lineno; /* Current line number */
2260 unsigned long p_offset; /* Previous offset of the window top */
2261 unsigned long p_yoffset;/* Previous offset from the window side */
2262 unsigned long p_lineno; /* Previous current line number */
2263 bool p_restore; /* Should the previous position be restored. */
2265 /* Searching */
2266 char grep[SIZEOF_STR]; /* Search string */
2267 regex_t *regex; /* Pre-compiled regexp */
2269 /* If non-NULL, points to the view that opened this view. If this view
2270 * is closed tig will switch back to the parent view. */
2271 struct view *parent;
2272 struct view *prev;
2274 /* Buffering */
2275 size_t lines; /* Total number of lines */
2276 struct line *line; /* Line index */
2277 unsigned int digits; /* Number of digits in the lines member. */
2279 /* Drawing */
2280 struct line *curline; /* Line currently being drawn. */
2281 enum line_type curtype; /* Attribute currently used for drawing. */
2282 unsigned long col; /* Column when drawing. */
2283 bool has_scrolled; /* View was scrolled. */
2285 /* Loading */
2286 const char **argv; /* Shell command arguments. */
2287 const char *dir; /* Directory from which to execute. */
2288 struct io io;
2289 struct io *pipe;
2290 time_t start_time;
2291 time_t update_secs;
2294 struct view_ops {
2295 /* What type of content being displayed. Used in the title bar. */
2296 const char *type;
2297 /* Default command arguments. */
2298 const char **argv;
2299 /* Open and reads in all view content. */
2300 bool (*open)(struct view *view);
2301 /* Read one line; updates view->line. */
2302 bool (*read)(struct view *view, char *data);
2303 /* Draw one line; @lineno must be < view->height. */
2304 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2305 /* Depending on view handle a special requests. */
2306 enum request (*request)(struct view *view, enum request request, struct line *line);
2307 /* Search for regexp in a line. */
2308 bool (*grep)(struct view *view, struct line *line);
2309 /* Select line */
2310 void (*select)(struct view *view, struct line *line);
2311 /* Prepare view for loading */
2312 bool (*prepare)(struct view *view);
2315 static struct view_ops blame_ops;
2316 static struct view_ops blob_ops;
2317 static struct view_ops diff_ops;
2318 static struct view_ops help_ops;
2319 static struct view_ops log_ops;
2320 static struct view_ops main_ops;
2321 static struct view_ops pager_ops;
2322 static struct view_ops stage_ops;
2323 static struct view_ops status_ops;
2324 static struct view_ops tree_ops;
2325 static struct view_ops branch_ops;
2327 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2328 { type, name, #env, ref, ops, map, git }
2330 #define VIEW_(id, name, ops, git, ref) \
2331 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2333 static struct view views[] = {
2334 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2335 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2336 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2337 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2338 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2339 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2340 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2341 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2342 VIEW_(PAGER, "pager", &pager_ops, FALSE, ""),
2343 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2344 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2347 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2349 #define foreach_view(view, i) \
2350 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2352 #define view_is_displayed(view) \
2353 (view == display[0] || view == display[1])
2355 static enum request
2356 view_request(struct view *view, enum request request)
2358 if (!view || !view->lines)
2359 return request;
2360 return view->ops->request(view, request, &view->line[view->lineno]);
2365 * View drawing.
2368 static inline void
2369 set_view_attr(struct view *view, enum line_type type)
2371 if (!view->curline->selected && view->curtype != type) {
2372 (void) wattrset(view->win, get_line_attr(type));
2373 wchgat(view->win, -1, 0, type, NULL);
2374 view->curtype = type;
2378 static int
2379 draw_chars(struct view *view, enum line_type type, const char *string,
2380 int max_len, bool use_tilde)
2382 static char out_buffer[BUFSIZ * 2];
2383 int len = 0;
2384 int col = 0;
2385 int trimmed = FALSE;
2386 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2388 if (max_len <= 0)
2389 return 0;
2391 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2393 set_view_attr(view, type);
2394 if (len > 0) {
2395 if (opt_iconv_out != ICONV_NONE) {
2396 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2397 size_t inlen = len + 1;
2399 char *outbuf = out_buffer;
2400 size_t outlen = sizeof(out_buffer);
2402 size_t ret;
2404 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2405 if (ret != (size_t) -1) {
2406 string = out_buffer;
2407 len = sizeof(out_buffer) - outlen;
2411 waddnstr(view->win, string, len);
2413 if (trimmed && use_tilde) {
2414 set_view_attr(view, LINE_DELIMITER);
2415 waddch(view->win, '~');
2416 col++;
2420 return col;
2423 static int
2424 draw_space(struct view *view, enum line_type type, int max, int spaces)
2426 static char space[] = " ";
2427 int col = 0;
2429 spaces = MIN(max, spaces);
2431 while (spaces > 0) {
2432 int len = MIN(spaces, sizeof(space) - 1);
2434 col += draw_chars(view, type, space, len, FALSE);
2435 spaces -= len;
2438 return col;
2441 static bool
2442 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2444 char text[SIZEOF_STR];
2446 do {
2447 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2449 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2450 string += pos;
2451 } while (*string && view->width + view->yoffset > view->col);
2453 return view->width + view->yoffset <= view->col;
2456 static bool
2457 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2459 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2460 int max = view->width + view->yoffset - view->col;
2461 int i;
2463 if (max < size)
2464 size = max;
2466 set_view_attr(view, type);
2467 /* Using waddch() instead of waddnstr() ensures that
2468 * they'll be rendered correctly for the cursor line. */
2469 for (i = skip; i < size; i++)
2470 waddch(view->win, graphic[i]);
2472 view->col += size;
2473 if (size < max && skip <= size)
2474 waddch(view->win, ' ');
2475 view->col++;
2477 return view->width + view->yoffset <= view->col;
2480 static bool
2481 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2483 int max = MIN(view->width + view->yoffset - view->col, len);
2484 int col;
2486 if (text)
2487 col = draw_chars(view, type, text, max - 1, trim);
2488 else
2489 col = draw_space(view, type, max - 1, max - 1);
2491 view->col += col;
2492 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2493 return view->width + view->yoffset <= view->col;
2496 static bool
2497 draw_date(struct view *view, struct time *time)
2499 const char *date = mkdate(time, opt_date);
2500 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2502 return draw_field(view, LINE_DATE, date, cols, FALSE);
2505 static bool
2506 draw_author(struct view *view, const char *author)
2508 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2509 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2511 if (abbreviate && author)
2512 author = get_author_initials(author);
2514 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2517 static bool
2518 draw_mode(struct view *view, mode_t mode)
2520 const char *str;
2522 if (S_ISDIR(mode))
2523 str = "drwxr-xr-x";
2524 else if (S_ISLNK(mode))
2525 str = "lrwxrwxrwx";
2526 else if (S_ISGITLINK(mode))
2527 str = "m---------";
2528 else if (S_ISREG(mode) && mode & S_IXUSR)
2529 str = "-rwxr-xr-x";
2530 else if (S_ISREG(mode))
2531 str = "-rw-r--r--";
2532 else
2533 str = "----------";
2535 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2538 static bool
2539 draw_lineno(struct view *view, unsigned int lineno)
2541 char number[10];
2542 int digits3 = view->digits < 3 ? 3 : view->digits;
2543 int max = MIN(view->width + view->yoffset - view->col, digits3);
2544 char *text = NULL;
2545 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2547 lineno += view->offset + 1;
2548 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2549 static char fmt[] = "%1ld";
2551 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2552 if (string_format(number, fmt, lineno))
2553 text = number;
2555 if (text)
2556 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2557 else
2558 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2559 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2562 static bool
2563 draw_view_line(struct view *view, unsigned int lineno)
2565 struct line *line;
2566 bool selected = (view->offset + lineno == view->lineno);
2568 assert(view_is_displayed(view));
2570 if (view->offset + lineno >= view->lines)
2571 return FALSE;
2573 line = &view->line[view->offset + lineno];
2575 wmove(view->win, lineno, 0);
2576 if (line->cleareol)
2577 wclrtoeol(view->win);
2578 view->col = 0;
2579 view->curline = line;
2580 view->curtype = LINE_NONE;
2581 line->selected = FALSE;
2582 line->dirty = line->cleareol = 0;
2584 if (selected) {
2585 set_view_attr(view, LINE_CURSOR);
2586 line->selected = TRUE;
2587 view->ops->select(view, line);
2590 return view->ops->draw(view, line, lineno);
2593 static void
2594 redraw_view_dirty(struct view *view)
2596 bool dirty = FALSE;
2597 int lineno;
2599 for (lineno = 0; lineno < view->height; lineno++) {
2600 if (view->offset + lineno >= view->lines)
2601 break;
2602 if (!view->line[view->offset + lineno].dirty)
2603 continue;
2604 dirty = TRUE;
2605 if (!draw_view_line(view, lineno))
2606 break;
2609 if (!dirty)
2610 return;
2611 wnoutrefresh(view->win);
2614 static void
2615 redraw_view_from(struct view *view, int lineno)
2617 assert(0 <= lineno && lineno < view->height);
2619 for (; lineno < view->height; lineno++) {
2620 if (!draw_view_line(view, lineno))
2621 break;
2624 wnoutrefresh(view->win);
2627 static void
2628 redraw_view(struct view *view)
2630 werase(view->win);
2631 redraw_view_from(view, 0);
2635 static void
2636 update_view_title(struct view *view)
2638 char buf[SIZEOF_STR];
2639 char state[SIZEOF_STR];
2640 size_t bufpos = 0, statelen = 0;
2642 assert(view_is_displayed(view));
2644 if (view->type != VIEW_STATUS && view->lines) {
2645 unsigned int view_lines = view->offset + view->height;
2646 unsigned int lines = view->lines
2647 ? MIN(view_lines, view->lines) * 100 / view->lines
2648 : 0;
2650 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2651 view->ops->type,
2652 view->lineno + 1,
2653 view->lines,
2654 lines);
2658 if (view->pipe) {
2659 time_t secs = time(NULL) - view->start_time;
2661 /* Three git seconds are a long time ... */
2662 if (secs > 2)
2663 string_format_from(state, &statelen, " loading %lds", secs);
2666 string_format_from(buf, &bufpos, "[%s]", view->name);
2667 if (*view->ref && bufpos < view->width) {
2668 size_t refsize = strlen(view->ref);
2669 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2671 if (minsize < view->width)
2672 refsize = view->width - minsize + 7;
2673 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2676 if (statelen && bufpos < view->width) {
2677 string_format_from(buf, &bufpos, "%s", state);
2680 if (view == display[current_view])
2681 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2682 else
2683 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2685 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2686 wclrtoeol(view->title);
2687 wnoutrefresh(view->title);
2690 static int
2691 apply_step(double step, int value)
2693 if (step >= 1)
2694 return (int) step;
2695 value *= step + 0.01;
2696 return value ? value : 1;
2699 static void
2700 resize_display(void)
2702 int offset, i;
2703 struct view *base = display[0];
2704 struct view *view = display[1] ? display[1] : display[0];
2706 /* Setup window dimensions */
2708 getmaxyx(stdscr, base->height, base->width);
2710 /* Make room for the status window. */
2711 base->height -= 1;
2713 if (view != base) {
2714 /* Horizontal split. */
2715 view->width = base->width;
2716 view->height = apply_step(opt_scale_split_view, base->height);
2717 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2718 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2719 base->height -= view->height;
2721 /* Make room for the title bar. */
2722 view->height -= 1;
2725 /* Make room for the title bar. */
2726 base->height -= 1;
2728 offset = 0;
2730 foreach_displayed_view (view, i) {
2731 if (!view->win) {
2732 view->win = newwin(view->height, 0, offset, 0);
2733 if (!view->win)
2734 die("Failed to create %s view", view->name);
2736 scrollok(view->win, FALSE);
2738 view->title = newwin(1, 0, offset + view->height, 0);
2739 if (!view->title)
2740 die("Failed to create title window");
2742 } else {
2743 wresize(view->win, view->height, view->width);
2744 mvwin(view->win, offset, 0);
2745 mvwin(view->title, offset + view->height, 0);
2748 offset += view->height + 1;
2752 static void
2753 redraw_display(bool clear)
2755 struct view *view;
2756 int i;
2758 foreach_displayed_view (view, i) {
2759 if (clear)
2760 wclear(view->win);
2761 redraw_view(view);
2762 update_view_title(view);
2768 * Option management
2771 static void
2772 toggle_enum_option_do(unsigned int *opt, const char *help,
2773 const struct enum_map *map, size_t size)
2775 *opt = (*opt + 1) % size;
2776 redraw_display(FALSE);
2777 report("Displaying %s %s", enum_name(map[*opt]), help);
2780 #define toggle_enum_option(opt, help, map) \
2781 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2783 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2784 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2786 static void
2787 toggle_view_option(bool *option, const char *help)
2789 *option = !*option;
2790 redraw_display(FALSE);
2791 report("%sabling %s", *option ? "En" : "Dis", help);
2794 static void
2795 open_option_menu(void)
2797 const struct menu_item menu[] = {
2798 { '.', "line numbers", &opt_line_number },
2799 { 'D', "date display", &opt_date },
2800 { 'A', "author display", &opt_author },
2801 { 'g', "revision graph display", &opt_rev_graph },
2802 { 'F', "reference display", &opt_show_refs },
2803 { 0 }
2805 int selected = 0;
2807 if (prompt_menu("Toggle option", menu, &selected)) {
2808 if (menu[selected].data == &opt_date)
2809 toggle_date();
2810 else if (menu[selected].data == &opt_author)
2811 toggle_author();
2812 else
2813 toggle_view_option(menu[selected].data, menu[selected].text);
2817 static void
2818 maximize_view(struct view *view)
2820 memset(display, 0, sizeof(display));
2821 current_view = 0;
2822 display[current_view] = view;
2823 resize_display();
2824 redraw_display(FALSE);
2825 report("");
2830 * Navigation
2833 static bool
2834 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2836 if (lineno >= view->lines)
2837 lineno = view->lines > 0 ? view->lines - 1 : 0;
2839 if (offset > lineno || offset + view->height <= lineno) {
2840 unsigned long half = view->height / 2;
2842 if (lineno > half)
2843 offset = lineno - half;
2844 else
2845 offset = 0;
2848 if (offset != view->offset || lineno != view->lineno) {
2849 view->offset = offset;
2850 view->lineno = lineno;
2851 return TRUE;
2854 return FALSE;
2857 /* Scrolling backend */
2858 static void
2859 do_scroll_view(struct view *view, int lines)
2861 bool redraw_current_line = FALSE;
2863 /* The rendering expects the new offset. */
2864 view->offset += lines;
2866 assert(0 <= view->offset && view->offset < view->lines);
2867 assert(lines);
2869 /* Move current line into the view. */
2870 if (view->lineno < view->offset) {
2871 view->lineno = view->offset;
2872 redraw_current_line = TRUE;
2873 } else if (view->lineno >= view->offset + view->height) {
2874 view->lineno = view->offset + view->height - 1;
2875 redraw_current_line = TRUE;
2878 assert(view->offset <= view->lineno && view->lineno < view->lines);
2880 /* Redraw the whole screen if scrolling is pointless. */
2881 if (view->height < ABS(lines)) {
2882 redraw_view(view);
2884 } else {
2885 int line = lines > 0 ? view->height - lines : 0;
2886 int end = line + ABS(lines);
2888 scrollok(view->win, TRUE);
2889 wscrl(view->win, lines);
2890 scrollok(view->win, FALSE);
2892 while (line < end && draw_view_line(view, line))
2893 line++;
2895 if (redraw_current_line)
2896 draw_view_line(view, view->lineno - view->offset);
2897 wnoutrefresh(view->win);
2900 view->has_scrolled = TRUE;
2901 report("");
2904 /* Scroll frontend */
2905 static void
2906 scroll_view(struct view *view, enum request request)
2908 int lines = 1;
2910 assert(view_is_displayed(view));
2912 switch (request) {
2913 case REQ_SCROLL_FIRST_COL:
2914 view->yoffset = 0;
2915 redraw_view_from(view, 0);
2916 report("");
2917 return;
2918 case REQ_SCROLL_LEFT:
2919 if (view->yoffset == 0) {
2920 report("Cannot scroll beyond the first column");
2921 return;
2923 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2924 view->yoffset = 0;
2925 else
2926 view->yoffset -= apply_step(opt_hscroll, view->width);
2927 redraw_view_from(view, 0);
2928 report("");
2929 return;
2930 case REQ_SCROLL_RIGHT:
2931 view->yoffset += apply_step(opt_hscroll, view->width);
2932 redraw_view(view);
2933 report("");
2934 return;
2935 case REQ_SCROLL_PAGE_DOWN:
2936 lines = view->height;
2937 case REQ_SCROLL_LINE_DOWN:
2938 if (view->offset + lines > view->lines)
2939 lines = view->lines - view->offset;
2941 if (lines == 0 || view->offset + view->height >= view->lines) {
2942 report("Cannot scroll beyond the last line");
2943 return;
2945 break;
2947 case REQ_SCROLL_PAGE_UP:
2948 lines = view->height;
2949 case REQ_SCROLL_LINE_UP:
2950 if (lines > view->offset)
2951 lines = view->offset;
2953 if (lines == 0) {
2954 report("Cannot scroll beyond the first line");
2955 return;
2958 lines = -lines;
2959 break;
2961 default:
2962 die("request %d not handled in switch", request);
2965 do_scroll_view(view, lines);
2968 /* Cursor moving */
2969 static void
2970 move_view(struct view *view, enum request request)
2972 int scroll_steps = 0;
2973 int steps;
2975 switch (request) {
2976 case REQ_MOVE_FIRST_LINE:
2977 steps = -view->lineno;
2978 break;
2980 case REQ_MOVE_LAST_LINE:
2981 steps = view->lines - view->lineno - 1;
2982 break;
2984 case REQ_MOVE_PAGE_UP:
2985 steps = view->height > view->lineno
2986 ? -view->lineno : -view->height;
2987 break;
2989 case REQ_MOVE_PAGE_DOWN:
2990 steps = view->lineno + view->height >= view->lines
2991 ? view->lines - view->lineno - 1 : view->height;
2992 break;
2994 case REQ_MOVE_UP:
2995 steps = -1;
2996 break;
2998 case REQ_MOVE_DOWN:
2999 steps = 1;
3000 break;
3002 default:
3003 die("request %d not handled in switch", request);
3006 if (steps <= 0 && view->lineno == 0) {
3007 report("Cannot move beyond the first line");
3008 return;
3010 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3011 report("Cannot move beyond the last line");
3012 return;
3015 /* Move the current line */
3016 view->lineno += steps;
3017 assert(0 <= view->lineno && view->lineno < view->lines);
3019 /* Check whether the view needs to be scrolled */
3020 if (view->lineno < view->offset ||
3021 view->lineno >= view->offset + view->height) {
3022 scroll_steps = steps;
3023 if (steps < 0 && -steps > view->offset) {
3024 scroll_steps = -view->offset;
3026 } else if (steps > 0) {
3027 if (view->lineno == view->lines - 1 &&
3028 view->lines > view->height) {
3029 scroll_steps = view->lines - view->offset - 1;
3030 if (scroll_steps >= view->height)
3031 scroll_steps -= view->height - 1;
3036 if (!view_is_displayed(view)) {
3037 view->offset += scroll_steps;
3038 assert(0 <= view->offset && view->offset < view->lines);
3039 view->ops->select(view, &view->line[view->lineno]);
3040 return;
3043 /* Repaint the old "current" line if we be scrolling */
3044 if (ABS(steps) < view->height)
3045 draw_view_line(view, view->lineno - steps - view->offset);
3047 if (scroll_steps) {
3048 do_scroll_view(view, scroll_steps);
3049 return;
3052 /* Draw the current line */
3053 draw_view_line(view, view->lineno - view->offset);
3055 wnoutrefresh(view->win);
3056 report("");
3061 * Searching
3064 static void search_view(struct view *view, enum request request);
3066 static bool
3067 grep_text(struct view *view, const char *text[])
3069 regmatch_t pmatch;
3070 size_t i;
3072 for (i = 0; text[i]; i++)
3073 if (*text[i] &&
3074 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3075 return TRUE;
3076 return FALSE;
3079 static void
3080 select_view_line(struct view *view, unsigned long lineno)
3082 unsigned long old_lineno = view->lineno;
3083 unsigned long old_offset = view->offset;
3085 if (goto_view_line(view, view->offset, lineno)) {
3086 if (view_is_displayed(view)) {
3087 if (old_offset != view->offset) {
3088 redraw_view(view);
3089 } else {
3090 draw_view_line(view, old_lineno - view->offset);
3091 draw_view_line(view, view->lineno - view->offset);
3092 wnoutrefresh(view->win);
3094 } else {
3095 view->ops->select(view, &view->line[view->lineno]);
3100 static void
3101 find_next(struct view *view, enum request request)
3103 unsigned long lineno = view->lineno;
3104 int direction;
3106 if (!*view->grep) {
3107 if (!*opt_search)
3108 report("No previous search");
3109 else
3110 search_view(view, request);
3111 return;
3114 switch (request) {
3115 case REQ_SEARCH:
3116 case REQ_FIND_NEXT:
3117 direction = 1;
3118 break;
3120 case REQ_SEARCH_BACK:
3121 case REQ_FIND_PREV:
3122 direction = -1;
3123 break;
3125 default:
3126 return;
3129 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3130 lineno += direction;
3132 /* Note, lineno is unsigned long so will wrap around in which case it
3133 * will become bigger than view->lines. */
3134 for (; lineno < view->lines; lineno += direction) {
3135 if (view->ops->grep(view, &view->line[lineno])) {
3136 select_view_line(view, lineno);
3137 report("Line %ld matches '%s'", lineno + 1, view->grep);
3138 return;
3142 report("No match found for '%s'", view->grep);
3145 static void
3146 search_view(struct view *view, enum request request)
3148 int regex_err;
3150 if (view->regex) {
3151 regfree(view->regex);
3152 *view->grep = 0;
3153 } else {
3154 view->regex = calloc(1, sizeof(*view->regex));
3155 if (!view->regex)
3156 return;
3159 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3160 if (regex_err != 0) {
3161 char buf[SIZEOF_STR] = "unknown error";
3163 regerror(regex_err, view->regex, buf, sizeof(buf));
3164 report("Search failed: %s", buf);
3165 return;
3168 string_copy(view->grep, opt_search);
3170 find_next(view, request);
3174 * Incremental updating
3177 static void
3178 reset_view(struct view *view)
3180 int i;
3182 for (i = 0; i < view->lines; i++)
3183 free(view->line[i].data);
3184 free(view->line);
3186 view->p_offset = view->offset;
3187 view->p_yoffset = view->yoffset;
3188 view->p_lineno = view->lineno;
3190 view->line = NULL;
3191 view->offset = 0;
3192 view->yoffset = 0;
3193 view->lines = 0;
3194 view->lineno = 0;
3195 view->vid[0] = 0;
3196 view->update_secs = 0;
3199 static const char *
3200 format_arg(const char *name)
3202 static struct {
3203 const char *name;
3204 size_t namelen;
3205 const char *value;
3206 const char *value_if_empty;
3207 } vars[] = {
3208 #define FORMAT_VAR(name, value, value_if_empty) \
3209 { name, STRING_SIZE(name), value, value_if_empty }
3210 FORMAT_VAR("%(directory)", opt_path, ""),
3211 FORMAT_VAR("%(file)", opt_file, ""),
3212 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3213 FORMAT_VAR("%(head)", ref_head, ""),
3214 FORMAT_VAR("%(commit)", ref_commit, ""),
3215 FORMAT_VAR("%(blob)", ref_blob, ""),
3216 FORMAT_VAR("%(branch)", ref_branch, ""),
3218 int i;
3220 for (i = 0; i < ARRAY_SIZE(vars); i++)
3221 if (!strncmp(name, vars[i].name, vars[i].namelen))
3222 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3224 report("Unknown replacement: `%s`", name);
3225 return NULL;
3228 static bool
3229 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3231 char buf[SIZEOF_STR];
3232 int argc;
3234 argv_free(*dst_argv);
3236 for (argc = 0; src_argv[argc]; argc++) {
3237 const char *arg = src_argv[argc];
3238 size_t bufpos = 0;
3240 if (!strcmp(arg, "%(fileargs)")) {
3241 if (!argv_append_array(dst_argv, opt_file_argv))
3242 break;
3243 continue;
3245 } else if (!strcmp(arg, "%(diffargs)")) {
3246 if (!argv_append_array(dst_argv, opt_diff_argv))
3247 break;
3248 continue;
3250 } else if (!strcmp(arg, "%(revargs)") ||
3251 (first && !strcmp(arg, "%(commit)"))) {
3252 if (!argv_append_array(dst_argv, opt_rev_argv))
3253 break;
3254 continue;
3257 while (arg) {
3258 char *next = strstr(arg, "%(");
3259 int len = next - arg;
3260 const char *value;
3262 if (!next || !replace) {
3263 len = strlen(arg);
3264 value = "";
3266 } else {
3267 value = format_arg(next);
3269 if (!value) {
3270 return FALSE;
3274 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3275 return FALSE;
3277 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3280 if (!argv_append(dst_argv, buf))
3281 break;
3284 return src_argv[argc] == NULL;
3287 static bool
3288 restore_view_position(struct view *view)
3290 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3291 return FALSE;
3293 /* Changing the view position cancels the restoring. */
3294 /* FIXME: Changing back to the first line is not detected. */
3295 if (view->offset != 0 || view->lineno != 0) {
3296 view->p_restore = FALSE;
3297 return FALSE;
3300 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3301 view_is_displayed(view))
3302 werase(view->win);
3304 view->yoffset = view->p_yoffset;
3305 view->p_restore = FALSE;
3307 return TRUE;
3310 static void
3311 end_update(struct view *view, bool force)
3313 if (!view->pipe)
3314 return;
3315 while (!view->ops->read(view, NULL))
3316 if (!force)
3317 return;
3318 if (force)
3319 io_kill(view->pipe);
3320 io_done(view->pipe);
3321 view->pipe = NULL;
3324 static void
3325 setup_update(struct view *view, const char *vid)
3327 reset_view(view);
3328 string_copy_rev(view->vid, vid);
3329 view->pipe = &view->io;
3330 view->start_time = time(NULL);
3333 static bool
3334 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3336 view->dir = dir;
3337 return format_argv(&view->argv, argv, replace, !view->prev);
3340 static bool
3341 prepare_update(struct view *view, const char *argv[], const char *dir)
3343 if (view->pipe)
3344 end_update(view, TRUE);
3345 return prepare_io(view, dir, argv, FALSE);
3348 static bool
3349 start_update(struct view *view, const char **argv, const char *dir)
3351 if (view->pipe)
3352 io_done(view->pipe);
3353 return prepare_io(view, dir, argv, FALSE) &&
3354 io_run(&view->io, IO_RD, dir, view->argv);
3357 static bool
3358 prepare_update_file(struct view *view, const char *name)
3360 if (view->pipe)
3361 end_update(view, TRUE);
3362 argv_free(view->argv);
3363 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3366 static bool
3367 begin_update(struct view *view, bool refresh)
3369 if (view->pipe)
3370 end_update(view, TRUE);
3372 if (!refresh) {
3373 if (view->ops->prepare) {
3374 if (!view->ops->prepare(view))
3375 return FALSE;
3376 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3377 return FALSE;
3380 /* Put the current ref_* value to the view title ref
3381 * member. This is needed by the blob view. Most other
3382 * views sets it automatically after loading because the
3383 * first line is a commit line. */
3384 string_copy_rev(view->ref, view->id);
3387 if (view->argv && view->argv[0] &&
3388 !io_run(&view->io, IO_RD, view->dir, view->argv))
3389 return FALSE;
3391 setup_update(view, view->id);
3393 return TRUE;
3396 static bool
3397 update_view(struct view *view)
3399 char out_buffer[BUFSIZ * 2];
3400 char *line;
3401 /* Clear the view and redraw everything since the tree sorting
3402 * might have rearranged things. */
3403 bool redraw = view->lines == 0;
3404 bool can_read = TRUE;
3406 if (!view->pipe)
3407 return TRUE;
3409 if (!io_can_read(view->pipe)) {
3410 if (view->lines == 0 && view_is_displayed(view)) {
3411 time_t secs = time(NULL) - view->start_time;
3413 if (secs > 1 && secs > view->update_secs) {
3414 if (view->update_secs == 0)
3415 redraw_view(view);
3416 update_view_title(view);
3417 view->update_secs = secs;
3420 return TRUE;
3423 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3424 if (opt_iconv_in != ICONV_NONE) {
3425 ICONV_CONST char *inbuf = line;
3426 size_t inlen = strlen(line) + 1;
3428 char *outbuf = out_buffer;
3429 size_t outlen = sizeof(out_buffer);
3431 size_t ret;
3433 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3434 if (ret != (size_t) -1)
3435 line = out_buffer;
3438 if (!view->ops->read(view, line)) {
3439 report("Allocation failure");
3440 end_update(view, TRUE);
3441 return FALSE;
3446 unsigned long lines = view->lines;
3447 int digits;
3449 for (digits = 0; lines; digits++)
3450 lines /= 10;
3452 /* Keep the displayed view in sync with line number scaling. */
3453 if (digits != view->digits) {
3454 view->digits = digits;
3455 if (opt_line_number || view->type == VIEW_BLAME)
3456 redraw = TRUE;
3460 if (io_error(view->pipe)) {
3461 report("Failed to read: %s", io_strerror(view->pipe));
3462 end_update(view, TRUE);
3464 } else if (io_eof(view->pipe)) {
3465 if (view_is_displayed(view))
3466 report("");
3467 end_update(view, FALSE);
3470 if (restore_view_position(view))
3471 redraw = TRUE;
3473 if (!view_is_displayed(view))
3474 return TRUE;
3476 if (redraw)
3477 redraw_view_from(view, 0);
3478 else
3479 redraw_view_dirty(view);
3481 /* Update the title _after_ the redraw so that if the redraw picks up a
3482 * commit reference in view->ref it'll be available here. */
3483 update_view_title(view);
3484 return TRUE;
3487 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3489 static struct line *
3490 add_line_data(struct view *view, void *data, enum line_type type)
3492 struct line *line;
3494 if (!realloc_lines(&view->line, view->lines, 1))
3495 return NULL;
3497 line = &view->line[view->lines++];
3498 memset(line, 0, sizeof(*line));
3499 line->type = type;
3500 line->data = data;
3501 line->dirty = 1;
3503 return line;
3506 static struct line *
3507 add_line_text(struct view *view, const char *text, enum line_type type)
3509 char *data = text ? strdup(text) : NULL;
3511 return data ? add_line_data(view, data, type) : NULL;
3514 static struct line *
3515 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3517 char buf[SIZEOF_STR];
3518 va_list args;
3520 va_start(args, fmt);
3521 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3522 buf[0] = 0;
3523 va_end(args);
3525 return buf[0] ? add_line_text(view, buf, type) : NULL;
3529 * View opening
3532 enum open_flags {
3533 OPEN_DEFAULT = 0, /* Use default view switching. */
3534 OPEN_SPLIT = 1, /* Split current view. */
3535 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3536 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3537 OPEN_PREPARED = 32, /* Open already prepared command. */
3540 static void
3541 open_view(struct view *prev, enum request request, enum open_flags flags)
3543 bool split = !!(flags & OPEN_SPLIT);
3544 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3545 bool nomaximize = !!(flags & OPEN_REFRESH);
3546 struct view *view = VIEW(request);
3547 int nviews = displayed_views();
3548 struct view *base_view = display[0];
3550 if (view == prev && nviews == 1 && !reload) {
3551 report("Already in %s view", view->name);
3552 return;
3555 if (view->git_dir && !opt_git_dir[0]) {
3556 report("The %s view is disabled in pager view", view->name);
3557 return;
3560 if (split) {
3561 display[1] = view;
3562 current_view = 1;
3563 view->parent = prev;
3564 } else if (!nomaximize) {
3565 /* Maximize the current view. */
3566 memset(display, 0, sizeof(display));
3567 current_view = 0;
3568 display[current_view] = view;
3571 /* No prev signals that this is the first loaded view. */
3572 if (prev && view != prev) {
3573 view->prev = prev;
3576 /* Resize the view when switching between split- and full-screen,
3577 * or when switching between two different full-screen views. */
3578 if (nviews != displayed_views() ||
3579 (nviews == 1 && base_view != display[0]))
3580 resize_display();
3582 if (view->ops->open) {
3583 if (view->pipe)
3584 end_update(view, TRUE);
3585 if (!view->ops->open(view)) {
3586 report("Failed to load %s view", view->name);
3587 return;
3589 restore_view_position(view);
3591 } else if ((reload || strcmp(view->vid, view->id)) &&
3592 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3593 report("Failed to load %s view", view->name);
3594 return;
3597 if (split && prev->lineno - prev->offset >= prev->height) {
3598 /* Take the title line into account. */
3599 int lines = prev->lineno - prev->offset - prev->height + 1;
3601 /* Scroll the view that was split if the current line is
3602 * outside the new limited view. */
3603 do_scroll_view(prev, lines);
3606 if (prev && view != prev && split && view_is_displayed(prev)) {
3607 /* "Blur" the previous view. */
3608 update_view_title(prev);
3611 if (view->pipe && view->lines == 0) {
3612 /* Clear the old view and let the incremental updating refill
3613 * the screen. */
3614 werase(view->win);
3615 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3616 report("");
3617 } else if (view_is_displayed(view)) {
3618 redraw_view(view);
3619 report("");
3623 static void
3624 open_external_viewer(const char *argv[], const char *dir)
3626 def_prog_mode(); /* save current tty modes */
3627 endwin(); /* restore original tty modes */
3628 io_run_fg(argv, dir);
3629 fprintf(stderr, "Press Enter to continue");
3630 getc(opt_tty);
3631 reset_prog_mode();
3632 redraw_display(TRUE);
3635 static void
3636 open_mergetool(const char *file)
3638 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3640 open_external_viewer(mergetool_argv, opt_cdup);
3643 static void
3644 open_editor(const char *file)
3646 const char *editor_argv[] = { "vi", file, NULL };
3647 const char *editor;
3649 editor = getenv("GIT_EDITOR");
3650 if (!editor && *opt_editor)
3651 editor = opt_editor;
3652 if (!editor)
3653 editor = getenv("VISUAL");
3654 if (!editor)
3655 editor = getenv("EDITOR");
3656 if (!editor)
3657 editor = "vi";
3659 editor_argv[0] = editor;
3660 open_external_viewer(editor_argv, opt_cdup);
3663 static void
3664 open_run_request(enum request request)
3666 struct run_request *req = get_run_request(request);
3667 const char **argv = NULL;
3669 if (!req) {
3670 report("Unknown run request");
3671 return;
3674 if (format_argv(&argv, req->argv, TRUE, FALSE))
3675 open_external_viewer(argv, NULL);
3676 if (argv)
3677 argv_free(argv);
3678 free(argv);
3682 * User request switch noodle
3685 static int
3686 view_driver(struct view *view, enum request request)
3688 int i;
3690 if (request == REQ_NONE)
3691 return TRUE;
3693 if (request > REQ_NONE) {
3694 open_run_request(request);
3695 view_request(view, REQ_REFRESH);
3696 return TRUE;
3699 request = view_request(view, request);
3700 if (request == REQ_NONE)
3701 return TRUE;
3703 switch (request) {
3704 case REQ_MOVE_UP:
3705 case REQ_MOVE_DOWN:
3706 case REQ_MOVE_PAGE_UP:
3707 case REQ_MOVE_PAGE_DOWN:
3708 case REQ_MOVE_FIRST_LINE:
3709 case REQ_MOVE_LAST_LINE:
3710 move_view(view, request);
3711 break;
3713 case REQ_SCROLL_FIRST_COL:
3714 case REQ_SCROLL_LEFT:
3715 case REQ_SCROLL_RIGHT:
3716 case REQ_SCROLL_LINE_DOWN:
3717 case REQ_SCROLL_LINE_UP:
3718 case REQ_SCROLL_PAGE_DOWN:
3719 case REQ_SCROLL_PAGE_UP:
3720 scroll_view(view, request);
3721 break;
3723 case REQ_VIEW_BLAME:
3724 if (!opt_file[0]) {
3725 report("No file chosen, press %s to open tree view",
3726 get_key(view->keymap, REQ_VIEW_TREE));
3727 break;
3729 open_view(view, request, OPEN_DEFAULT);
3730 break;
3732 case REQ_VIEW_BLOB:
3733 if (!ref_blob[0]) {
3734 report("No file chosen, press %s to open tree view",
3735 get_key(view->keymap, REQ_VIEW_TREE));
3736 break;
3738 open_view(view, request, OPEN_DEFAULT);
3739 break;
3741 case REQ_VIEW_PAGER:
3742 if (view == NULL) {
3743 if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3744 die("Failed to open stdin");
3745 open_view(view, request, OPEN_PREPARED);
3746 break;
3749 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3750 report("No pager content, press %s to run command from prompt",
3751 get_key(view->keymap, REQ_PROMPT));
3752 break;
3754 open_view(view, request, OPEN_DEFAULT);
3755 break;
3757 case REQ_VIEW_STAGE:
3758 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3759 report("No stage content, press %s to open the status view and choose file",
3760 get_key(view->keymap, REQ_VIEW_STATUS));
3761 break;
3763 open_view(view, request, OPEN_DEFAULT);
3764 break;
3766 case REQ_VIEW_STATUS:
3767 if (opt_is_inside_work_tree == FALSE) {
3768 report("The status view requires a working tree");
3769 break;
3771 open_view(view, request, OPEN_DEFAULT);
3772 break;
3774 case REQ_VIEW_MAIN:
3775 case REQ_VIEW_DIFF:
3776 case REQ_VIEW_LOG:
3777 case REQ_VIEW_TREE:
3778 case REQ_VIEW_HELP:
3779 case REQ_VIEW_BRANCH:
3780 open_view(view, request, OPEN_DEFAULT);
3781 break;
3783 case REQ_NEXT:
3784 case REQ_PREVIOUS:
3785 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3787 if (view->parent) {
3788 int line;
3790 view = view->parent;
3791 line = view->lineno;
3792 move_view(view, request);
3793 if (view_is_displayed(view))
3794 update_view_title(view);
3795 if (line != view->lineno)
3796 view_request(view, REQ_ENTER);
3797 } else {
3798 move_view(view, request);
3800 break;
3802 case REQ_VIEW_NEXT:
3804 int nviews = displayed_views();
3805 int next_view = (current_view + 1) % nviews;
3807 if (next_view == current_view) {
3808 report("Only one view is displayed");
3809 break;
3812 current_view = next_view;
3813 /* Blur out the title of the previous view. */
3814 update_view_title(view);
3815 report("");
3816 break;
3818 case REQ_REFRESH:
3819 report("Refreshing is not yet supported for the %s view", view->name);
3820 break;
3822 case REQ_MAXIMIZE:
3823 if (displayed_views() == 2)
3824 maximize_view(view);
3825 break;
3827 case REQ_OPTIONS:
3828 open_option_menu();
3829 break;
3831 case REQ_TOGGLE_LINENO:
3832 toggle_view_option(&opt_line_number, "line numbers");
3833 break;
3835 case REQ_TOGGLE_DATE:
3836 toggle_date();
3837 break;
3839 case REQ_TOGGLE_AUTHOR:
3840 toggle_author();
3841 break;
3843 case REQ_TOGGLE_REV_GRAPH:
3844 toggle_view_option(&opt_rev_graph, "revision graph display");
3845 break;
3847 case REQ_TOGGLE_REFS:
3848 toggle_view_option(&opt_show_refs, "reference display");
3849 break;
3851 case REQ_TOGGLE_SORT_FIELD:
3852 case REQ_TOGGLE_SORT_ORDER:
3853 report("Sorting is not yet supported for the %s view", view->name);
3854 break;
3856 case REQ_SEARCH:
3857 case REQ_SEARCH_BACK:
3858 search_view(view, request);
3859 break;
3861 case REQ_FIND_NEXT:
3862 case REQ_FIND_PREV:
3863 find_next(view, request);
3864 break;
3866 case REQ_STOP_LOADING:
3867 foreach_view(view, i) {
3868 if (view->pipe)
3869 report("Stopped loading the %s view", view->name),
3870 end_update(view, TRUE);
3872 break;
3874 case REQ_SHOW_VERSION:
3875 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3876 return TRUE;
3878 case REQ_SCREEN_REDRAW:
3879 redraw_display(TRUE);
3880 break;
3882 case REQ_EDIT:
3883 report("Nothing to edit");
3884 break;
3886 case REQ_ENTER:
3887 report("Nothing to enter");
3888 break;
3890 case REQ_VIEW_CLOSE:
3891 /* XXX: Mark closed views by letting view->prev point to the
3892 * view itself. Parents to closed view should never be
3893 * followed. */
3894 if (view->prev && view->prev != view) {
3895 maximize_view(view->prev);
3896 view->prev = view;
3897 break;
3899 /* Fall-through */
3900 case REQ_QUIT:
3901 return FALSE;
3903 default:
3904 report("Unknown key, press %s for help",
3905 get_key(view->keymap, REQ_VIEW_HELP));
3906 return TRUE;
3909 return TRUE;
3914 * View backend utilities
3917 enum sort_field {
3918 ORDERBY_NAME,
3919 ORDERBY_DATE,
3920 ORDERBY_AUTHOR,
3923 struct sort_state {
3924 const enum sort_field *fields;
3925 size_t size, current;
3926 bool reverse;
3929 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3930 #define get_sort_field(state) ((state).fields[(state).current])
3931 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3933 static void
3934 sort_view(struct view *view, enum request request, struct sort_state *state,
3935 int (*compare)(const void *, const void *))
3937 switch (request) {
3938 case REQ_TOGGLE_SORT_FIELD:
3939 state->current = (state->current + 1) % state->size;
3940 break;
3942 case REQ_TOGGLE_SORT_ORDER:
3943 state->reverse = !state->reverse;
3944 break;
3945 default:
3946 die("Not a sort request");
3949 qsort(view->line, view->lines, sizeof(*view->line), compare);
3950 redraw_view(view);
3953 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3955 /* Small author cache to reduce memory consumption. It uses binary
3956 * search to lookup or find place to position new entries. No entries
3957 * are ever freed. */
3958 static const char *
3959 get_author(const char *name)
3961 static const char **authors;
3962 static size_t authors_size;
3963 int from = 0, to = authors_size - 1;
3965 while (from <= to) {
3966 size_t pos = (to + from) / 2;
3967 int cmp = strcmp(name, authors[pos]);
3969 if (!cmp)
3970 return authors[pos];
3972 if (cmp < 0)
3973 to = pos - 1;
3974 else
3975 from = pos + 1;
3978 if (!realloc_authors(&authors, authors_size, 1))
3979 return NULL;
3980 name = strdup(name);
3981 if (!name)
3982 return NULL;
3984 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3985 authors[from] = name;
3986 authors_size++;
3988 return name;
3991 static void
3992 parse_timesec(struct time *time, const char *sec)
3994 time->sec = (time_t) atol(sec);
3997 static void
3998 parse_timezone(struct time *time, const char *zone)
4000 long tz;
4002 tz = ('0' - zone[1]) * 60 * 60 * 10;
4003 tz += ('0' - zone[2]) * 60 * 60;
4004 tz += ('0' - zone[3]) * 60 * 10;
4005 tz += ('0' - zone[4]) * 60;
4007 if (zone[0] == '-')
4008 tz = -tz;
4010 time->tz = tz;
4011 time->sec -= tz;
4014 /* Parse author lines where the name may be empty:
4015 * author <email@address.tld> 1138474660 +0100
4017 static void
4018 parse_author_line(char *ident, const char **author, struct time *time)
4020 char *nameend = strchr(ident, '<');
4021 char *emailend = strchr(ident, '>');
4023 if (nameend && emailend)
4024 *nameend = *emailend = 0;
4025 ident = chomp_string(ident);
4026 if (!*ident) {
4027 if (nameend)
4028 ident = chomp_string(nameend + 1);
4029 if (!*ident)
4030 ident = "Unknown";
4033 *author = get_author(ident);
4035 /* Parse epoch and timezone */
4036 if (emailend && emailend[1] == ' ') {
4037 char *secs = emailend + 2;
4038 char *zone = strchr(secs, ' ');
4040 parse_timesec(time, secs);
4042 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4043 parse_timezone(time, zone + 1);
4048 * Pager backend
4051 static bool
4052 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4054 if (opt_line_number && draw_lineno(view, lineno))
4055 return TRUE;
4057 draw_text(view, line->type, line->data, TRUE);
4058 return TRUE;
4061 static bool
4062 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4064 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4065 char ref[SIZEOF_STR];
4067 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4068 return TRUE;
4070 /* This is the only fatal call, since it can "corrupt" the buffer. */
4071 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4072 return FALSE;
4074 return TRUE;
4077 static void
4078 add_pager_refs(struct view *view, struct line *line)
4080 char buf[SIZEOF_STR];
4081 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4082 struct ref_list *list;
4083 size_t bufpos = 0, i;
4084 const char *sep = "Refs: ";
4085 bool is_tag = FALSE;
4087 assert(line->type == LINE_COMMIT);
4089 list = get_ref_list(commit_id);
4090 if (!list) {
4091 if (view->type == VIEW_DIFF)
4092 goto try_add_describe_ref;
4093 return;
4096 for (i = 0; i < list->size; i++) {
4097 struct ref *ref = list->refs[i];
4098 const char *fmt = ref->tag ? "%s[%s]" :
4099 ref->remote ? "%s<%s>" : "%s%s";
4101 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4102 return;
4103 sep = ", ";
4104 if (ref->tag)
4105 is_tag = TRUE;
4108 if (!is_tag && view->type == VIEW_DIFF) {
4109 try_add_describe_ref:
4110 /* Add <tag>-g<commit_id> "fake" reference. */
4111 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4112 return;
4115 if (bufpos == 0)
4116 return;
4118 add_line_text(view, buf, LINE_PP_REFS);
4121 static bool
4122 pager_read(struct view *view, char *data)
4124 struct line *line;
4126 if (!data)
4127 return TRUE;
4129 line = add_line_text(view, data, get_line_type(data));
4130 if (!line)
4131 return FALSE;
4133 if (line->type == LINE_COMMIT &&
4134 (view->type == VIEW_DIFF ||
4135 view->type == VIEW_LOG))
4136 add_pager_refs(view, line);
4138 return TRUE;
4141 static enum request
4142 pager_request(struct view *view, enum request request, struct line *line)
4144 int split = 0;
4146 if (request != REQ_ENTER)
4147 return request;
4149 if (line->type == LINE_COMMIT &&
4150 (view->type == VIEW_LOG ||
4151 view->type == VIEW_PAGER)) {
4152 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4153 split = 1;
4156 /* Always scroll the view even if it was split. That way
4157 * you can use Enter to scroll through the log view and
4158 * split open each commit diff. */
4159 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4161 /* FIXME: A minor workaround. Scrolling the view will call report("")
4162 * but if we are scrolling a non-current view this won't properly
4163 * update the view title. */
4164 if (split)
4165 update_view_title(view);
4167 return REQ_NONE;
4170 static bool
4171 pager_grep(struct view *view, struct line *line)
4173 const char *text[] = { line->data, NULL };
4175 return grep_text(view, text);
4178 static void
4179 pager_select(struct view *view, struct line *line)
4181 if (line->type == LINE_COMMIT) {
4182 char *text = (char *)line->data + STRING_SIZE("commit ");
4184 if (view->type != VIEW_PAGER)
4185 string_copy_rev(view->ref, text);
4186 string_copy_rev(ref_commit, text);
4190 static struct view_ops pager_ops = {
4191 "line",
4192 NULL,
4193 NULL,
4194 pager_read,
4195 pager_draw,
4196 pager_request,
4197 pager_grep,
4198 pager_select,
4201 static const char *log_argv[SIZEOF_ARG] = {
4202 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4205 static enum request
4206 log_request(struct view *view, enum request request, struct line *line)
4208 switch (request) {
4209 case REQ_REFRESH:
4210 load_refs();
4211 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4212 return REQ_NONE;
4213 default:
4214 return pager_request(view, request, line);
4218 static struct view_ops log_ops = {
4219 "line",
4220 log_argv,
4221 NULL,
4222 pager_read,
4223 pager_draw,
4224 log_request,
4225 pager_grep,
4226 pager_select,
4229 static const char *diff_argv[SIZEOF_ARG] = {
4230 "git", "show", "--pretty=fuller", "--no-color", "--root",
4231 "--patch-with-stat", "--find-copies-harder", "-C",
4232 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4235 static bool
4236 diff_read(struct view *view, char *data)
4238 if (!data) {
4239 /* Fall back to retry if no diff will be shown. */
4240 if (view->lines == 0 && opt_file_argv) {
4241 int pos = argv_size(view->argv)
4242 - argv_size(opt_file_argv) - 1;
4244 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4245 for (; view->argv[pos]; pos++) {
4246 free((void *) view->argv[pos]);
4247 view->argv[pos] = NULL;
4250 if (view->pipe)
4251 io_done(view->pipe);
4252 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4253 return FALSE;
4256 return TRUE;
4259 return pager_read(view, data);
4262 static struct view_ops diff_ops = {
4263 "line",
4264 diff_argv,
4265 NULL,
4266 diff_read,
4267 pager_draw,
4268 pager_request,
4269 pager_grep,
4270 pager_select,
4274 * Help backend
4277 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4279 static bool
4280 help_open_keymap_title(struct view *view, enum keymap keymap)
4282 struct line *line;
4284 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4285 help_keymap_hidden[keymap] ? '+' : '-',
4286 enum_name(keymap_table[keymap]));
4287 if (line)
4288 line->other = keymap;
4290 return help_keymap_hidden[keymap];
4293 static void
4294 help_open_keymap(struct view *view, enum keymap keymap)
4296 const char *group = NULL;
4297 char buf[SIZEOF_STR];
4298 size_t bufpos;
4299 bool add_title = TRUE;
4300 int i;
4302 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4303 const char *key = NULL;
4305 if (req_info[i].request == REQ_NONE)
4306 continue;
4308 if (!req_info[i].request) {
4309 group = req_info[i].help;
4310 continue;
4313 key = get_keys(keymap, req_info[i].request, TRUE);
4314 if (!key || !*key)
4315 continue;
4317 if (add_title && help_open_keymap_title(view, keymap))
4318 return;
4319 add_title = FALSE;
4321 if (group) {
4322 add_line_text(view, group, LINE_HELP_GROUP);
4323 group = NULL;
4326 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4327 enum_name(req_info[i]), req_info[i].help);
4330 group = "External commands:";
4332 for (i = 0; i < run_requests; i++) {
4333 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4334 const char *key;
4335 int argc;
4337 if (!req || req->keymap != keymap)
4338 continue;
4340 key = get_key_name(req->key);
4341 if (!*key)
4342 key = "(no key defined)";
4344 if (add_title && help_open_keymap_title(view, keymap))
4345 return;
4346 if (group) {
4347 add_line_text(view, group, LINE_HELP_GROUP);
4348 group = NULL;
4351 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4352 if (!string_format_from(buf, &bufpos, "%s%s",
4353 argc ? " " : "", req->argv[argc]))
4354 return;
4356 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4360 static bool
4361 help_open(struct view *view)
4363 enum keymap keymap;
4365 reset_view(view);
4366 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4367 add_line_text(view, "", LINE_DEFAULT);
4369 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4370 help_open_keymap(view, keymap);
4372 return TRUE;
4375 static enum request
4376 help_request(struct view *view, enum request request, struct line *line)
4378 switch (request) {
4379 case REQ_ENTER:
4380 if (line->type == LINE_HELP_KEYMAP) {
4381 help_keymap_hidden[line->other] =
4382 !help_keymap_hidden[line->other];
4383 view->p_restore = TRUE;
4384 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4387 return REQ_NONE;
4388 default:
4389 return pager_request(view, request, line);
4393 static struct view_ops help_ops = {
4394 "line",
4395 NULL,
4396 help_open,
4397 NULL,
4398 pager_draw,
4399 help_request,
4400 pager_grep,
4401 pager_select,
4406 * Tree backend
4409 struct tree_stack_entry {
4410 struct tree_stack_entry *prev; /* Entry below this in the stack */
4411 unsigned long lineno; /* Line number to restore */
4412 char *name; /* Position of name in opt_path */
4415 /* The top of the path stack. */
4416 static struct tree_stack_entry *tree_stack = NULL;
4417 unsigned long tree_lineno = 0;
4419 static void
4420 pop_tree_stack_entry(void)
4422 struct tree_stack_entry *entry = tree_stack;
4424 tree_lineno = entry->lineno;
4425 entry->name[0] = 0;
4426 tree_stack = entry->prev;
4427 free(entry);
4430 static void
4431 push_tree_stack_entry(const char *name, unsigned long lineno)
4433 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4434 size_t pathlen = strlen(opt_path);
4436 if (!entry)
4437 return;
4439 entry->prev = tree_stack;
4440 entry->name = opt_path + pathlen;
4441 tree_stack = entry;
4443 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4444 pop_tree_stack_entry();
4445 return;
4448 /* Move the current line to the first tree entry. */
4449 tree_lineno = 1;
4450 entry->lineno = lineno;
4453 /* Parse output from git-ls-tree(1):
4455 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4458 #define SIZEOF_TREE_ATTR \
4459 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4461 #define SIZEOF_TREE_MODE \
4462 STRING_SIZE("100644 ")
4464 #define TREE_ID_OFFSET \
4465 STRING_SIZE("100644 blob ")
4467 struct tree_entry {
4468 char id[SIZEOF_REV];
4469 mode_t mode;
4470 struct time time; /* Date from the author ident. */
4471 const char *author; /* Author of the commit. */
4472 char name[1];
4475 static const char *
4476 tree_path(const struct line *line)
4478 return ((struct tree_entry *) line->data)->name;
4481 static int
4482 tree_compare_entry(const struct line *line1, const struct line *line2)
4484 if (line1->type != line2->type)
4485 return line1->type == LINE_TREE_DIR ? -1 : 1;
4486 return strcmp(tree_path(line1), tree_path(line2));
4489 static const enum sort_field tree_sort_fields[] = {
4490 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4492 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4494 static int
4495 tree_compare(const void *l1, const void *l2)
4497 const struct line *line1 = (const struct line *) l1;
4498 const struct line *line2 = (const struct line *) l2;
4499 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4500 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4502 if (line1->type == LINE_TREE_HEAD)
4503 return -1;
4504 if (line2->type == LINE_TREE_HEAD)
4505 return 1;
4507 switch (get_sort_field(tree_sort_state)) {
4508 case ORDERBY_DATE:
4509 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4511 case ORDERBY_AUTHOR:
4512 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4514 case ORDERBY_NAME:
4515 default:
4516 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4521 static struct line *
4522 tree_entry(struct view *view, enum line_type type, const char *path,
4523 const char *mode, const char *id)
4525 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4526 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4528 if (!entry || !line) {
4529 free(entry);
4530 return NULL;
4533 strncpy(entry->name, path, strlen(path));
4534 if (mode)
4535 entry->mode = strtoul(mode, NULL, 8);
4536 if (id)
4537 string_copy_rev(entry->id, id);
4539 return line;
4542 static bool
4543 tree_read_date(struct view *view, char *text, bool *read_date)
4545 static const char *author_name;
4546 static struct time author_time;
4548 if (!text && *read_date) {
4549 *read_date = FALSE;
4550 return TRUE;
4552 } else if (!text) {
4553 char *path = *opt_path ? opt_path : ".";
4554 /* Find next entry to process */
4555 const char *log_file[] = {
4556 "git", "log", "--no-color", "--pretty=raw",
4557 "--cc", "--raw", view->id, "--", path, NULL
4560 if (!view->lines) {
4561 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4562 report("Tree is empty");
4563 return TRUE;
4566 if (!start_update(view, log_file, opt_cdup)) {
4567 report("Failed to load tree data");
4568 return TRUE;
4571 *read_date = TRUE;
4572 return FALSE;
4574 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4575 parse_author_line(text + STRING_SIZE("author "),
4576 &author_name, &author_time);
4578 } else if (*text == ':') {
4579 char *pos;
4580 size_t annotated = 1;
4581 size_t i;
4583 pos = strchr(text, '\t');
4584 if (!pos)
4585 return TRUE;
4586 text = pos + 1;
4587 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4588 text += strlen(opt_path);
4589 pos = strchr(text, '/');
4590 if (pos)
4591 *pos = 0;
4593 for (i = 1; i < view->lines; i++) {
4594 struct line *line = &view->line[i];
4595 struct tree_entry *entry = line->data;
4597 annotated += !!entry->author;
4598 if (entry->author || strcmp(entry->name, text))
4599 continue;
4601 entry->author = author_name;
4602 entry->time = author_time;
4603 line->dirty = 1;
4604 break;
4607 if (annotated == view->lines)
4608 io_kill(view->pipe);
4610 return TRUE;
4613 static bool
4614 tree_read(struct view *view, char *text)
4616 static bool read_date = FALSE;
4617 struct tree_entry *data;
4618 struct line *entry, *line;
4619 enum line_type type;
4620 size_t textlen = text ? strlen(text) : 0;
4621 char *path = text + SIZEOF_TREE_ATTR;
4623 if (read_date || !text)
4624 return tree_read_date(view, text, &read_date);
4626 if (textlen <= SIZEOF_TREE_ATTR)
4627 return FALSE;
4628 if (view->lines == 0 &&
4629 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4630 return FALSE;
4632 /* Strip the path part ... */
4633 if (*opt_path) {
4634 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4635 size_t striplen = strlen(opt_path);
4637 if (pathlen > striplen)
4638 memmove(path, path + striplen,
4639 pathlen - striplen + 1);
4641 /* Insert "link" to parent directory. */
4642 if (view->lines == 1 &&
4643 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4644 return FALSE;
4647 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4648 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4649 if (!entry)
4650 return FALSE;
4651 data = entry->data;
4653 /* Skip "Directory ..." and ".." line. */
4654 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4655 if (tree_compare_entry(line, entry) <= 0)
4656 continue;
4658 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4660 line->data = data;
4661 line->type = type;
4662 for (; line <= entry; line++)
4663 line->dirty = line->cleareol = 1;
4664 return TRUE;
4667 if (tree_lineno > view->lineno) {
4668 view->lineno = tree_lineno;
4669 tree_lineno = 0;
4672 return TRUE;
4675 static bool
4676 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4678 struct tree_entry *entry = line->data;
4680 if (line->type == LINE_TREE_HEAD) {
4681 if (draw_text(view, line->type, "Directory path /", TRUE))
4682 return TRUE;
4683 } else {
4684 if (draw_mode(view, entry->mode))
4685 return TRUE;
4687 if (opt_author && draw_author(view, entry->author))
4688 return TRUE;
4690 if (opt_date && draw_date(view, &entry->time))
4691 return TRUE;
4693 if (draw_text(view, line->type, entry->name, TRUE))
4694 return TRUE;
4695 return TRUE;
4698 static void
4699 open_blob_editor(const char *id)
4701 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4702 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4703 int fd = mkstemp(file);
4705 if (fd == -1)
4706 report("Failed to create temporary file");
4707 else if (!io_run_append(blob_argv, fd))
4708 report("Failed to save blob data to file");
4709 else
4710 open_editor(file);
4711 if (fd != -1)
4712 unlink(file);
4715 static enum request
4716 tree_request(struct view *view, enum request request, struct line *line)
4718 enum open_flags flags;
4719 struct tree_entry *entry = line->data;
4721 switch (request) {
4722 case REQ_VIEW_BLAME:
4723 if (line->type != LINE_TREE_FILE) {
4724 report("Blame only supported for files");
4725 return REQ_NONE;
4728 string_copy(opt_ref, view->vid);
4729 return request;
4731 case REQ_EDIT:
4732 if (line->type != LINE_TREE_FILE) {
4733 report("Edit only supported for files");
4734 } else if (!is_head_commit(view->vid)) {
4735 open_blob_editor(entry->id);
4736 } else {
4737 open_editor(opt_file);
4739 return REQ_NONE;
4741 case REQ_TOGGLE_SORT_FIELD:
4742 case REQ_TOGGLE_SORT_ORDER:
4743 sort_view(view, request, &tree_sort_state, tree_compare);
4744 return REQ_NONE;
4746 case REQ_PARENT:
4747 if (!*opt_path) {
4748 /* quit view if at top of tree */
4749 return REQ_VIEW_CLOSE;
4751 /* fake 'cd ..' */
4752 line = &view->line[1];
4753 break;
4755 case REQ_ENTER:
4756 break;
4758 default:
4759 return request;
4762 /* Cleanup the stack if the tree view is at a different tree. */
4763 while (!*opt_path && tree_stack)
4764 pop_tree_stack_entry();
4766 switch (line->type) {
4767 case LINE_TREE_DIR:
4768 /* Depending on whether it is a subdirectory or parent link
4769 * mangle the path buffer. */
4770 if (line == &view->line[1] && *opt_path) {
4771 pop_tree_stack_entry();
4773 } else {
4774 const char *basename = tree_path(line);
4776 push_tree_stack_entry(basename, view->lineno);
4779 /* Trees and subtrees share the same ID, so they are not not
4780 * unique like blobs. */
4781 flags = OPEN_RELOAD;
4782 request = REQ_VIEW_TREE;
4783 break;
4785 case LINE_TREE_FILE:
4786 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4787 request = REQ_VIEW_BLOB;
4788 break;
4790 default:
4791 return REQ_NONE;
4794 open_view(view, request, flags);
4795 if (request == REQ_VIEW_TREE)
4796 view->lineno = tree_lineno;
4798 return REQ_NONE;
4801 static bool
4802 tree_grep(struct view *view, struct line *line)
4804 struct tree_entry *entry = line->data;
4805 const char *text[] = {
4806 entry->name,
4807 opt_author ? entry->author : "",
4808 mkdate(&entry->time, opt_date),
4809 NULL
4812 return grep_text(view, text);
4815 static void
4816 tree_select(struct view *view, struct line *line)
4818 struct tree_entry *entry = line->data;
4820 if (line->type == LINE_TREE_FILE) {
4821 string_copy_rev(ref_blob, entry->id);
4822 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4824 } else if (line->type != LINE_TREE_DIR) {
4825 return;
4828 string_copy_rev(view->ref, entry->id);
4831 static bool
4832 tree_prepare(struct view *view)
4834 if (view->lines == 0 && opt_prefix[0]) {
4835 char *pos = opt_prefix;
4837 while (pos && *pos) {
4838 char *end = strchr(pos, '/');
4840 if (end)
4841 *end = 0;
4842 push_tree_stack_entry(pos, 0);
4843 pos = end;
4844 if (end) {
4845 *end = '/';
4846 pos++;
4850 } else if (strcmp(view->vid, view->id)) {
4851 opt_path[0] = 0;
4854 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4857 static const char *tree_argv[SIZEOF_ARG] = {
4858 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4861 static struct view_ops tree_ops = {
4862 "file",
4863 tree_argv,
4864 NULL,
4865 tree_read,
4866 tree_draw,
4867 tree_request,
4868 tree_grep,
4869 tree_select,
4870 tree_prepare,
4873 static bool
4874 blob_read(struct view *view, char *line)
4876 if (!line)
4877 return TRUE;
4878 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4881 static enum request
4882 blob_request(struct view *view, enum request request, struct line *line)
4884 switch (request) {
4885 case REQ_EDIT:
4886 open_blob_editor(view->vid);
4887 return REQ_NONE;
4888 default:
4889 return pager_request(view, request, line);
4893 static const char *blob_argv[SIZEOF_ARG] = {
4894 "git", "cat-file", "blob", "%(blob)", NULL
4897 static struct view_ops blob_ops = {
4898 "line",
4899 blob_argv,
4900 NULL,
4901 blob_read,
4902 pager_draw,
4903 blob_request,
4904 pager_grep,
4905 pager_select,
4909 * Blame backend
4911 * Loading the blame view is a two phase job:
4913 * 1. File content is read either using opt_file from the
4914 * filesystem or using git-cat-file.
4915 * 2. Then blame information is incrementally added by
4916 * reading output from git-blame.
4919 struct blame_commit {
4920 char id[SIZEOF_REV]; /* SHA1 ID. */
4921 char title[128]; /* First line of the commit message. */
4922 const char *author; /* Author of the commit. */
4923 struct time time; /* Date from the author ident. */
4924 char filename[128]; /* Name of file. */
4925 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4926 char parent_filename[128]; /* Parent/previous name of file. */
4929 struct blame {
4930 struct blame_commit *commit;
4931 unsigned long lineno;
4932 char text[1];
4935 static bool
4936 blame_open(struct view *view)
4938 char path[SIZEOF_STR];
4939 size_t i;
4941 if (!view->prev && *opt_prefix) {
4942 string_copy(path, opt_file);
4943 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4944 return FALSE;
4947 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4948 const char *blame_cat_file_argv[] = {
4949 "git", "cat-file", "blob", path, NULL
4952 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4953 !start_update(view, blame_cat_file_argv, opt_cdup))
4954 return FALSE;
4957 /* First pass: remove multiple references to the same commit. */
4958 for (i = 0; i < view->lines; i++) {
4959 struct blame *blame = view->line[i].data;
4961 if (blame->commit && blame->commit->id[0])
4962 blame->commit->id[0] = 0;
4963 else
4964 blame->commit = NULL;
4967 /* Second pass: free existing references. */
4968 for (i = 0; i < view->lines; i++) {
4969 struct blame *blame = view->line[i].data;
4971 if (blame->commit)
4972 free(blame->commit);
4975 setup_update(view, opt_file);
4976 string_format(view->ref, "%s ...", opt_file);
4978 return TRUE;
4981 static struct blame_commit *
4982 get_blame_commit(struct view *view, const char *id)
4984 size_t i;
4986 for (i = 0; i < view->lines; i++) {
4987 struct blame *blame = view->line[i].data;
4989 if (!blame->commit)
4990 continue;
4992 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4993 return blame->commit;
4997 struct blame_commit *commit = calloc(1, sizeof(*commit));
4999 if (commit)
5000 string_ncopy(commit->id, id, SIZEOF_REV);
5001 return commit;
5005 static bool
5006 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5008 const char *pos = *posref;
5010 *posref = NULL;
5011 pos = strchr(pos + 1, ' ');
5012 if (!pos || !isdigit(pos[1]))
5013 return FALSE;
5014 *number = atoi(pos + 1);
5015 if (*number < min || *number > max)
5016 return FALSE;
5018 *posref = pos;
5019 return TRUE;
5022 static struct blame_commit *
5023 parse_blame_commit(struct view *view, const char *text, int *blamed)
5025 struct blame_commit *commit;
5026 struct blame *blame;
5027 const char *pos = text + SIZEOF_REV - 2;
5028 size_t orig_lineno = 0;
5029 size_t lineno;
5030 size_t group;
5032 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5033 return NULL;
5035 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5036 !parse_number(&pos, &lineno, 1, view->lines) ||
5037 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5038 return NULL;
5040 commit = get_blame_commit(view, text);
5041 if (!commit)
5042 return NULL;
5044 *blamed += group;
5045 while (group--) {
5046 struct line *line = &view->line[lineno + group - 1];
5048 blame = line->data;
5049 blame->commit = commit;
5050 blame->lineno = orig_lineno + group - 1;
5051 line->dirty = 1;
5054 return commit;
5057 static bool
5058 blame_read_file(struct view *view, const char *line, bool *read_file)
5060 if (!line) {
5061 const char *blame_argv[] = {
5062 "git", "blame", "--incremental",
5063 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5066 if (view->lines == 0 && !view->prev)
5067 die("No blame exist for %s", view->vid);
5069 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5070 report("Failed to load blame data");
5071 return TRUE;
5074 *read_file = FALSE;
5075 return FALSE;
5077 } else {
5078 size_t linelen = strlen(line);
5079 struct blame *blame = malloc(sizeof(*blame) + linelen);
5081 if (!blame)
5082 return FALSE;
5084 blame->commit = NULL;
5085 strncpy(blame->text, line, linelen);
5086 blame->text[linelen] = 0;
5087 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5091 static bool
5092 match_blame_header(const char *name, char **line)
5094 size_t namelen = strlen(name);
5095 bool matched = !strncmp(name, *line, namelen);
5097 if (matched)
5098 *line += namelen;
5100 return matched;
5103 static bool
5104 blame_read(struct view *view, char *line)
5106 static struct blame_commit *commit = NULL;
5107 static int blamed = 0;
5108 static bool read_file = TRUE;
5110 if (read_file)
5111 return blame_read_file(view, line, &read_file);
5113 if (!line) {
5114 /* Reset all! */
5115 commit = NULL;
5116 blamed = 0;
5117 read_file = TRUE;
5118 string_format(view->ref, "%s", view->vid);
5119 if (view_is_displayed(view)) {
5120 update_view_title(view);
5121 redraw_view_from(view, 0);
5123 return TRUE;
5126 if (!commit) {
5127 commit = parse_blame_commit(view, line, &blamed);
5128 string_format(view->ref, "%s %2d%%", view->vid,
5129 view->lines ? blamed * 100 / view->lines : 0);
5131 } else if (match_blame_header("author ", &line)) {
5132 commit->author = get_author(line);
5134 } else if (match_blame_header("author-time ", &line)) {
5135 parse_timesec(&commit->time, line);
5137 } else if (match_blame_header("author-tz ", &line)) {
5138 parse_timezone(&commit->time, line);
5140 } else if (match_blame_header("summary ", &line)) {
5141 string_ncopy(commit->title, line, strlen(line));
5143 } else if (match_blame_header("previous ", &line)) {
5144 if (strlen(line) <= SIZEOF_REV)
5145 return FALSE;
5146 string_copy_rev(commit->parent_id, line);
5147 line += SIZEOF_REV;
5148 string_ncopy(commit->parent_filename, line, strlen(line));
5150 } else if (match_blame_header("filename ", &line)) {
5151 string_ncopy(commit->filename, line, strlen(line));
5152 commit = NULL;
5155 return TRUE;
5158 static bool
5159 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5161 struct blame *blame = line->data;
5162 struct time *time = NULL;
5163 const char *id = NULL, *author = NULL;
5165 if (blame->commit && *blame->commit->filename) {
5166 id = blame->commit->id;
5167 author = blame->commit->author;
5168 time = &blame->commit->time;
5171 if (opt_date && draw_date(view, time))
5172 return TRUE;
5174 if (opt_author && draw_author(view, author))
5175 return TRUE;
5177 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5178 return TRUE;
5180 if (draw_lineno(view, lineno))
5181 return TRUE;
5183 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5184 return TRUE;
5187 static bool
5188 check_blame_commit(struct blame *blame, bool check_null_id)
5190 if (!blame->commit)
5191 report("Commit data not loaded yet");
5192 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5193 report("No commit exist for the selected line");
5194 else
5195 return TRUE;
5196 return FALSE;
5199 static void
5200 setup_blame_parent_line(struct view *view, struct blame *blame)
5202 char from[SIZEOF_REF + SIZEOF_STR];
5203 char to[SIZEOF_REF + SIZEOF_STR];
5204 const char *diff_tree_argv[] = {
5205 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5206 "-U0", from, to, "--", NULL
5208 struct io io;
5209 int parent_lineno = -1;
5210 int blamed_lineno = -1;
5211 char *line;
5213 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5214 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5215 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5216 return;
5218 while ((line = io_get(&io, '\n', TRUE))) {
5219 if (*line == '@') {
5220 char *pos = strchr(line, '+');
5222 parent_lineno = atoi(line + 4);
5223 if (pos)
5224 blamed_lineno = atoi(pos + 1);
5226 } else if (*line == '+' && parent_lineno != -1) {
5227 if (blame->lineno == blamed_lineno - 1 &&
5228 !strcmp(blame->text, line + 1)) {
5229 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5230 break;
5232 blamed_lineno++;
5236 io_done(&io);
5239 static enum request
5240 blame_request(struct view *view, enum request request, struct line *line)
5242 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5243 struct blame *blame = line->data;
5245 switch (request) {
5246 case REQ_VIEW_BLAME:
5247 if (check_blame_commit(blame, TRUE)) {
5248 string_copy(opt_ref, blame->commit->id);
5249 string_copy(opt_file, blame->commit->filename);
5250 if (blame->lineno)
5251 view->lineno = blame->lineno;
5252 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5254 break;
5256 case REQ_PARENT:
5257 if (!check_blame_commit(blame, TRUE))
5258 break;
5259 if (!*blame->commit->parent_id) {
5260 report("The selected commit has no parents");
5261 } else {
5262 string_copy_rev(opt_ref, blame->commit->parent_id);
5263 string_copy(opt_file, blame->commit->parent_filename);
5264 setup_blame_parent_line(view, blame);
5265 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5267 break;
5269 case REQ_ENTER:
5270 if (!check_blame_commit(blame, FALSE))
5271 break;
5273 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5274 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5275 break;
5277 if (!strcmp(blame->commit->id, NULL_ID)) {
5278 struct view *diff = VIEW(REQ_VIEW_DIFF);
5279 const char *diff_index_argv[] = {
5280 "git", "diff-index", "--root", "--patch-with-stat",
5281 "-C", "-M", "HEAD", "--", view->vid, NULL
5284 if (!*blame->commit->parent_id) {
5285 diff_index_argv[1] = "diff";
5286 diff_index_argv[2] = "--no-color";
5287 diff_index_argv[6] = "--";
5288 diff_index_argv[7] = "/dev/null";
5291 if (!prepare_update(diff, diff_index_argv, NULL)) {
5292 report("Failed to allocate diff command");
5293 break;
5295 flags |= OPEN_PREPARED;
5298 open_view(view, REQ_VIEW_DIFF, flags);
5299 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5300 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5301 break;
5303 default:
5304 return request;
5307 return REQ_NONE;
5310 static bool
5311 blame_grep(struct view *view, struct line *line)
5313 struct blame *blame = line->data;
5314 struct blame_commit *commit = blame->commit;
5315 const char *text[] = {
5316 blame->text,
5317 commit ? commit->title : "",
5318 commit ? commit->id : "",
5319 commit && opt_author ? commit->author : "",
5320 commit ? mkdate(&commit->time, opt_date) : "",
5321 NULL
5324 return grep_text(view, text);
5327 static void
5328 blame_select(struct view *view, struct line *line)
5330 struct blame *blame = line->data;
5331 struct blame_commit *commit = blame->commit;
5333 if (!commit)
5334 return;
5336 if (!strcmp(commit->id, NULL_ID))
5337 string_ncopy(ref_commit, "HEAD", 4);
5338 else
5339 string_copy_rev(ref_commit, commit->id);
5342 static struct view_ops blame_ops = {
5343 "line",
5344 NULL,
5345 blame_open,
5346 blame_read,
5347 blame_draw,
5348 blame_request,
5349 blame_grep,
5350 blame_select,
5354 * Branch backend
5357 struct branch {
5358 const char *author; /* Author of the last commit. */
5359 struct time time; /* Date of the last activity. */
5360 const struct ref *ref; /* Name and commit ID information. */
5363 static const struct ref branch_all;
5365 static const enum sort_field branch_sort_fields[] = {
5366 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5368 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5370 static int
5371 branch_compare(const void *l1, const void *l2)
5373 const struct branch *branch1 = ((const struct line *) l1)->data;
5374 const struct branch *branch2 = ((const struct line *) l2)->data;
5376 switch (get_sort_field(branch_sort_state)) {
5377 case ORDERBY_DATE:
5378 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5380 case ORDERBY_AUTHOR:
5381 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5383 case ORDERBY_NAME:
5384 default:
5385 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5389 static bool
5390 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5392 struct branch *branch = line->data;
5393 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5395 if (opt_date && draw_date(view, &branch->time))
5396 return TRUE;
5398 if (opt_author && draw_author(view, branch->author))
5399 return TRUE;
5401 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5402 return TRUE;
5405 static enum request
5406 branch_request(struct view *view, enum request request, struct line *line)
5408 struct branch *branch = line->data;
5410 switch (request) {
5411 case REQ_REFRESH:
5412 load_refs();
5413 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5414 return REQ_NONE;
5416 case REQ_TOGGLE_SORT_FIELD:
5417 case REQ_TOGGLE_SORT_ORDER:
5418 sort_view(view, request, &branch_sort_state, branch_compare);
5419 return REQ_NONE;
5421 case REQ_ENTER:
5423 const struct ref *ref = branch->ref;
5424 const char *all_branches_argv[] = {
5425 "git", "log", "--no-color", "--pretty=raw", "--parents",
5426 "--topo-order",
5427 ref == &branch_all ? "--all" : ref->name, NULL
5429 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5431 if (!prepare_update(main_view, all_branches_argv, NULL))
5432 report("Failed to load view of all branches");
5433 else
5434 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5435 return REQ_NONE;
5437 default:
5438 return request;
5442 static bool
5443 branch_read(struct view *view, char *line)
5445 static char id[SIZEOF_REV];
5446 struct branch *reference;
5447 size_t i;
5449 if (!line)
5450 return TRUE;
5452 switch (get_line_type(line)) {
5453 case LINE_COMMIT:
5454 string_copy_rev(id, line + STRING_SIZE("commit "));
5455 return TRUE;
5457 case LINE_AUTHOR:
5458 for (i = 0, reference = NULL; i < view->lines; i++) {
5459 struct branch *branch = view->line[i].data;
5461 if (strcmp(branch->ref->id, id))
5462 continue;
5464 view->line[i].dirty = TRUE;
5465 if (reference) {
5466 branch->author = reference->author;
5467 branch->time = reference->time;
5468 continue;
5471 parse_author_line(line + STRING_SIZE("author "),
5472 &branch->author, &branch->time);
5473 reference = branch;
5475 return TRUE;
5477 default:
5478 return TRUE;
5483 static bool
5484 branch_open_visitor(void *data, const struct ref *ref)
5486 struct view *view = data;
5487 struct branch *branch;
5489 if (ref->tag || ref->ltag || ref->remote)
5490 return TRUE;
5492 branch = calloc(1, sizeof(*branch));
5493 if (!branch)
5494 return FALSE;
5496 branch->ref = ref;
5497 return !!add_line_data(view, branch, LINE_DEFAULT);
5500 static bool
5501 branch_open(struct view *view)
5503 const char *branch_log[] = {
5504 "git", "log", "--no-color", "--pretty=raw",
5505 "--simplify-by-decoration", "--all", NULL
5508 if (!start_update(view, branch_log, NULL)) {
5509 report("Failed to load branch data");
5510 return TRUE;
5513 setup_update(view, view->id);
5514 branch_open_visitor(view, &branch_all);
5515 foreach_ref(branch_open_visitor, view);
5516 view->p_restore = TRUE;
5518 return TRUE;
5521 static bool
5522 branch_grep(struct view *view, struct line *line)
5524 struct branch *branch = line->data;
5525 const char *text[] = {
5526 branch->ref->name,
5527 branch->author,
5528 NULL
5531 return grep_text(view, text);
5534 static void
5535 branch_select(struct view *view, struct line *line)
5537 struct branch *branch = line->data;
5539 string_copy_rev(view->ref, branch->ref->id);
5540 string_copy_rev(ref_commit, branch->ref->id);
5541 string_copy_rev(ref_head, branch->ref->id);
5542 string_copy_rev(ref_branch, branch->ref->name);
5545 static struct view_ops branch_ops = {
5546 "branch",
5547 NULL,
5548 branch_open,
5549 branch_read,
5550 branch_draw,
5551 branch_request,
5552 branch_grep,
5553 branch_select,
5557 * Status backend
5560 struct status {
5561 char status;
5562 struct {
5563 mode_t mode;
5564 char rev[SIZEOF_REV];
5565 char name[SIZEOF_STR];
5566 } old;
5567 struct {
5568 mode_t mode;
5569 char rev[SIZEOF_REV];
5570 char name[SIZEOF_STR];
5571 } new;
5574 static char status_onbranch[SIZEOF_STR];
5575 static struct status stage_status;
5576 static enum line_type stage_line_type;
5577 static size_t stage_chunks;
5578 static int *stage_chunk;
5580 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5582 /* This should work even for the "On branch" line. */
5583 static inline bool
5584 status_has_none(struct view *view, struct line *line)
5586 return line < view->line + view->lines && !line[1].data;
5589 /* Get fields from the diff line:
5590 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5592 static inline bool
5593 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5595 const char *old_mode = buf + 1;
5596 const char *new_mode = buf + 8;
5597 const char *old_rev = buf + 15;
5598 const char *new_rev = buf + 56;
5599 const char *status = buf + 97;
5601 if (bufsize < 98 ||
5602 old_mode[-1] != ':' ||
5603 new_mode[-1] != ' ' ||
5604 old_rev[-1] != ' ' ||
5605 new_rev[-1] != ' ' ||
5606 status[-1] != ' ')
5607 return FALSE;
5609 file->status = *status;
5611 string_copy_rev(file->old.rev, old_rev);
5612 string_copy_rev(file->new.rev, new_rev);
5614 file->old.mode = strtoul(old_mode, NULL, 8);
5615 file->new.mode = strtoul(new_mode, NULL, 8);
5617 file->old.name[0] = file->new.name[0] = 0;
5619 return TRUE;
5622 static bool
5623 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5625 struct status *unmerged = NULL;
5626 char *buf;
5627 struct io io;
5629 if (!io_run(&io, IO_RD, opt_cdup, argv))
5630 return FALSE;
5632 add_line_data(view, NULL, type);
5634 while ((buf = io_get(&io, 0, TRUE))) {
5635 struct status *file = unmerged;
5637 if (!file) {
5638 file = calloc(1, sizeof(*file));
5639 if (!file || !add_line_data(view, file, type))
5640 goto error_out;
5643 /* Parse diff info part. */
5644 if (status) {
5645 file->status = status;
5646 if (status == 'A')
5647 string_copy(file->old.rev, NULL_ID);
5649 } else if (!file->status || file == unmerged) {
5650 if (!status_get_diff(file, buf, strlen(buf)))
5651 goto error_out;
5653 buf = io_get(&io, 0, TRUE);
5654 if (!buf)
5655 break;
5657 /* Collapse all modified entries that follow an
5658 * associated unmerged entry. */
5659 if (unmerged == file) {
5660 unmerged->status = 'U';
5661 unmerged = NULL;
5662 } else if (file->status == 'U') {
5663 unmerged = file;
5667 /* Grab the old name for rename/copy. */
5668 if (!*file->old.name &&
5669 (file->status == 'R' || file->status == 'C')) {
5670 string_ncopy(file->old.name, buf, strlen(buf));
5672 buf = io_get(&io, 0, TRUE);
5673 if (!buf)
5674 break;
5677 /* git-ls-files just delivers a NUL separated list of
5678 * file names similar to the second half of the
5679 * git-diff-* output. */
5680 string_ncopy(file->new.name, buf, strlen(buf));
5681 if (!*file->old.name)
5682 string_copy(file->old.name, file->new.name);
5683 file = NULL;
5686 if (io_error(&io)) {
5687 error_out:
5688 io_done(&io);
5689 return FALSE;
5692 if (!view->line[view->lines - 1].data)
5693 add_line_data(view, NULL, LINE_STAT_NONE);
5695 io_done(&io);
5696 return TRUE;
5699 /* Don't show unmerged entries in the staged section. */
5700 static const char *status_diff_index_argv[] = {
5701 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5702 "--cached", "-M", "HEAD", NULL
5705 static const char *status_diff_files_argv[] = {
5706 "git", "diff-files", "-z", NULL
5709 static const char *status_list_other_argv[] = {
5710 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5713 static const char *status_list_no_head_argv[] = {
5714 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5717 static const char *update_index_argv[] = {
5718 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5721 /* Restore the previous line number to stay in the context or select a
5722 * line with something that can be updated. */
5723 static void
5724 status_restore(struct view *view)
5726 if (view->p_lineno >= view->lines)
5727 view->p_lineno = view->lines - 1;
5728 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5729 view->p_lineno++;
5730 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5731 view->p_lineno--;
5733 /* If the above fails, always skip the "On branch" line. */
5734 if (view->p_lineno < view->lines)
5735 view->lineno = view->p_lineno;
5736 else
5737 view->lineno = 1;
5739 if (view->lineno < view->offset)
5740 view->offset = view->lineno;
5741 else if (view->offset + view->height <= view->lineno)
5742 view->offset = view->lineno - view->height + 1;
5744 view->p_restore = FALSE;
5747 static void
5748 status_update_onbranch(void)
5750 static const char *paths[][2] = {
5751 { "rebase-apply/rebasing", "Rebasing" },
5752 { "rebase-apply/applying", "Applying mailbox" },
5753 { "rebase-apply/", "Rebasing mailbox" },
5754 { "rebase-merge/interactive", "Interactive rebase" },
5755 { "rebase-merge/", "Rebase merge" },
5756 { "MERGE_HEAD", "Merging" },
5757 { "BISECT_LOG", "Bisecting" },
5758 { "HEAD", "On branch" },
5760 char buf[SIZEOF_STR];
5761 struct stat stat;
5762 int i;
5764 if (is_initial_commit()) {
5765 string_copy(status_onbranch, "Initial commit");
5766 return;
5769 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5770 char *head = opt_head;
5772 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5773 lstat(buf, &stat) < 0)
5774 continue;
5776 if (!*opt_head) {
5777 struct io io;
5779 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5780 io_read_buf(&io, buf, sizeof(buf))) {
5781 head = buf;
5782 if (!prefixcmp(head, "refs/heads/"))
5783 head += STRING_SIZE("refs/heads/");
5787 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5788 string_copy(status_onbranch, opt_head);
5789 return;
5792 string_copy(status_onbranch, "Not currently on any branch");
5795 /* First parse staged info using git-diff-index(1), then parse unstaged
5796 * info using git-diff-files(1), and finally untracked files using
5797 * git-ls-files(1). */
5798 static bool
5799 status_open(struct view *view)
5801 reset_view(view);
5803 add_line_data(view, NULL, LINE_STAT_HEAD);
5804 status_update_onbranch();
5806 io_run_bg(update_index_argv);
5808 if (is_initial_commit()) {
5809 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5810 return FALSE;
5811 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5812 return FALSE;
5815 if (!opt_untracked_dirs_content)
5816 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5818 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5819 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5820 return FALSE;
5822 /* Restore the exact position or use the specialized restore
5823 * mode? */
5824 if (!view->p_restore)
5825 status_restore(view);
5826 return TRUE;
5829 static bool
5830 status_draw(struct view *view, struct line *line, unsigned int lineno)
5832 struct status *status = line->data;
5833 enum line_type type;
5834 const char *text;
5836 if (!status) {
5837 switch (line->type) {
5838 case LINE_STAT_STAGED:
5839 type = LINE_STAT_SECTION;
5840 text = "Changes to be committed:";
5841 break;
5843 case LINE_STAT_UNSTAGED:
5844 type = LINE_STAT_SECTION;
5845 text = "Changed but not updated:";
5846 break;
5848 case LINE_STAT_UNTRACKED:
5849 type = LINE_STAT_SECTION;
5850 text = "Untracked files:";
5851 break;
5853 case LINE_STAT_NONE:
5854 type = LINE_DEFAULT;
5855 text = " (no files)";
5856 break;
5858 case LINE_STAT_HEAD:
5859 type = LINE_STAT_HEAD;
5860 text = status_onbranch;
5861 break;
5863 default:
5864 return FALSE;
5866 } else {
5867 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5869 buf[0] = status->status;
5870 if (draw_text(view, line->type, buf, TRUE))
5871 return TRUE;
5872 type = LINE_DEFAULT;
5873 text = status->new.name;
5876 draw_text(view, type, text, TRUE);
5877 return TRUE;
5880 static enum request
5881 status_load_error(struct view *view, struct view *stage, const char *path)
5883 if (displayed_views() == 2 || display[current_view] != view)
5884 maximize_view(view);
5885 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5886 return REQ_NONE;
5889 static enum request
5890 status_enter(struct view *view, struct line *line)
5892 struct status *status = line->data;
5893 const char *oldpath = status ? status->old.name : NULL;
5894 /* Diffs for unmerged entries are empty when passing the new
5895 * path, so leave it empty. */
5896 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5897 const char *info;
5898 enum open_flags split;
5899 struct view *stage = VIEW(REQ_VIEW_STAGE);
5901 if (line->type == LINE_STAT_NONE ||
5902 (!status && line[1].type == LINE_STAT_NONE)) {
5903 report("No file to diff");
5904 return REQ_NONE;
5907 switch (line->type) {
5908 case LINE_STAT_STAGED:
5909 if (is_initial_commit()) {
5910 const char *no_head_diff_argv[] = {
5911 "git", "diff", "--no-color", "--patch-with-stat",
5912 "--", "/dev/null", newpath, NULL
5915 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5916 return status_load_error(view, stage, newpath);
5917 } else {
5918 const char *index_show_argv[] = {
5919 "git", "diff-index", "--root", "--patch-with-stat",
5920 "-C", "-M", "--cached", "HEAD", "--",
5921 oldpath, newpath, NULL
5924 if (!prepare_update(stage, index_show_argv, opt_cdup))
5925 return status_load_error(view, stage, newpath);
5928 if (status)
5929 info = "Staged changes to %s";
5930 else
5931 info = "Staged changes";
5932 break;
5934 case LINE_STAT_UNSTAGED:
5936 const char *files_show_argv[] = {
5937 "git", "diff-files", "--root", "--patch-with-stat",
5938 "-C", "-M", "--", oldpath, newpath, NULL
5941 if (!prepare_update(stage, files_show_argv, opt_cdup))
5942 return status_load_error(view, stage, newpath);
5943 if (status)
5944 info = "Unstaged changes to %s";
5945 else
5946 info = "Unstaged changes";
5947 break;
5949 case LINE_STAT_UNTRACKED:
5950 if (!newpath) {
5951 report("No file to show");
5952 return REQ_NONE;
5955 if (!suffixcmp(status->new.name, -1, "/")) {
5956 report("Cannot display a directory");
5957 return REQ_NONE;
5960 if (!prepare_update_file(stage, newpath))
5961 return status_load_error(view, stage, newpath);
5962 info = "Untracked file %s";
5963 break;
5965 case LINE_STAT_HEAD:
5966 return REQ_NONE;
5968 default:
5969 die("line type %d not handled in switch", line->type);
5972 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5973 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5974 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5975 if (status) {
5976 stage_status = *status;
5977 } else {
5978 memset(&stage_status, 0, sizeof(stage_status));
5981 stage_line_type = line->type;
5982 stage_chunks = 0;
5983 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5986 return REQ_NONE;
5989 static bool
5990 status_exists(struct status *status, enum line_type type)
5992 struct view *view = VIEW(REQ_VIEW_STATUS);
5993 unsigned long lineno;
5995 for (lineno = 0; lineno < view->lines; lineno++) {
5996 struct line *line = &view->line[lineno];
5997 struct status *pos = line->data;
5999 if (line->type != type)
6000 continue;
6001 if (!pos && (!status || !status->status) && line[1].data) {
6002 select_view_line(view, lineno);
6003 return TRUE;
6005 if (pos && !strcmp(status->new.name, pos->new.name)) {
6006 select_view_line(view, lineno);
6007 return TRUE;
6011 return FALSE;
6015 static bool
6016 status_update_prepare(struct io *io, enum line_type type)
6018 const char *staged_argv[] = {
6019 "git", "update-index", "-z", "--index-info", NULL
6021 const char *others_argv[] = {
6022 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6025 switch (type) {
6026 case LINE_STAT_STAGED:
6027 return io_run(io, IO_WR, opt_cdup, staged_argv);
6029 case LINE_STAT_UNSTAGED:
6030 case LINE_STAT_UNTRACKED:
6031 return io_run(io, IO_WR, opt_cdup, others_argv);
6033 default:
6034 die("line type %d not handled in switch", type);
6035 return FALSE;
6039 static bool
6040 status_update_write(struct io *io, struct status *status, enum line_type type)
6042 char buf[SIZEOF_STR];
6043 size_t bufsize = 0;
6045 switch (type) {
6046 case LINE_STAT_STAGED:
6047 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6048 status->old.mode,
6049 status->old.rev,
6050 status->old.name, 0))
6051 return FALSE;
6052 break;
6054 case LINE_STAT_UNSTAGED:
6055 case LINE_STAT_UNTRACKED:
6056 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6057 return FALSE;
6058 break;
6060 default:
6061 die("line type %d not handled in switch", type);
6064 return io_write(io, buf, bufsize);
6067 static bool
6068 status_update_file(struct status *status, enum line_type type)
6070 struct io io;
6071 bool result;
6073 if (!status_update_prepare(&io, type))
6074 return FALSE;
6076 result = status_update_write(&io, status, type);
6077 return io_done(&io) && result;
6080 static bool
6081 status_update_files(struct view *view, struct line *line)
6083 char buf[sizeof(view->ref)];
6084 struct io io;
6085 bool result = TRUE;
6086 struct line *pos = view->line + view->lines;
6087 int files = 0;
6088 int file, done;
6089 int cursor_y = -1, cursor_x = -1;
6091 if (!status_update_prepare(&io, line->type))
6092 return FALSE;
6094 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6095 files++;
6097 string_copy(buf, view->ref);
6098 getsyx(cursor_y, cursor_x);
6099 for (file = 0, done = 5; result && file < files; line++, file++) {
6100 int almost_done = file * 100 / files;
6102 if (almost_done > done) {
6103 done = almost_done;
6104 string_format(view->ref, "updating file %u of %u (%d%% done)",
6105 file, files, done);
6106 update_view_title(view);
6107 setsyx(cursor_y, cursor_x);
6108 doupdate();
6110 result = status_update_write(&io, line->data, line->type);
6112 string_copy(view->ref, buf);
6114 return io_done(&io) && result;
6117 static bool
6118 status_update(struct view *view)
6120 struct line *line = &view->line[view->lineno];
6122 assert(view->lines);
6124 if (!line->data) {
6125 /* This should work even for the "On branch" line. */
6126 if (line < view->line + view->lines && !line[1].data) {
6127 report("Nothing to update");
6128 return FALSE;
6131 if (!status_update_files(view, line + 1)) {
6132 report("Failed to update file status");
6133 return FALSE;
6136 } else if (!status_update_file(line->data, line->type)) {
6137 report("Failed to update file status");
6138 return FALSE;
6141 return TRUE;
6144 static bool
6145 status_revert(struct status *status, enum line_type type, bool has_none)
6147 if (!status || type != LINE_STAT_UNSTAGED) {
6148 if (type == LINE_STAT_STAGED) {
6149 report("Cannot revert changes to staged files");
6150 } else if (type == LINE_STAT_UNTRACKED) {
6151 report("Cannot revert changes to untracked files");
6152 } else if (has_none) {
6153 report("Nothing to revert");
6154 } else {
6155 report("Cannot revert changes to multiple files");
6158 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6159 char mode[10] = "100644";
6160 const char *reset_argv[] = {
6161 "git", "update-index", "--cacheinfo", mode,
6162 status->old.rev, status->old.name, NULL
6164 const char *checkout_argv[] = {
6165 "git", "checkout", "--", status->old.name, NULL
6168 if (status->status == 'U') {
6169 string_format(mode, "%5o", status->old.mode);
6171 if (status->old.mode == 0 && status->new.mode == 0) {
6172 reset_argv[2] = "--force-remove";
6173 reset_argv[3] = status->old.name;
6174 reset_argv[4] = NULL;
6177 if (!io_run_fg(reset_argv, opt_cdup))
6178 return FALSE;
6179 if (status->old.mode == 0 && status->new.mode == 0)
6180 return TRUE;
6183 return io_run_fg(checkout_argv, opt_cdup);
6186 return FALSE;
6189 static enum request
6190 status_request(struct view *view, enum request request, struct line *line)
6192 struct status *status = line->data;
6194 switch (request) {
6195 case REQ_STATUS_UPDATE:
6196 if (!status_update(view))
6197 return REQ_NONE;
6198 break;
6200 case REQ_STATUS_REVERT:
6201 if (!status_revert(status, line->type, status_has_none(view, line)))
6202 return REQ_NONE;
6203 break;
6205 case REQ_STATUS_MERGE:
6206 if (!status || status->status != 'U') {
6207 report("Merging only possible for files with unmerged status ('U').");
6208 return REQ_NONE;
6210 open_mergetool(status->new.name);
6211 break;
6213 case REQ_EDIT:
6214 if (!status)
6215 return request;
6216 if (status->status == 'D') {
6217 report("File has been deleted.");
6218 return REQ_NONE;
6221 open_editor(status->new.name);
6222 break;
6224 case REQ_VIEW_BLAME:
6225 if (status)
6226 opt_ref[0] = 0;
6227 return request;
6229 case REQ_ENTER:
6230 /* After returning the status view has been split to
6231 * show the stage view. No further reloading is
6232 * necessary. */
6233 return status_enter(view, line);
6235 case REQ_REFRESH:
6236 /* Simply reload the view. */
6237 break;
6239 default:
6240 return request;
6243 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6245 return REQ_NONE;
6248 static void
6249 status_select(struct view *view, struct line *line)
6251 struct status *status = line->data;
6252 char file[SIZEOF_STR] = "all files";
6253 const char *text;
6254 const char *key;
6256 if (status && !string_format(file, "'%s'", status->new.name))
6257 return;
6259 if (!status && line[1].type == LINE_STAT_NONE)
6260 line++;
6262 switch (line->type) {
6263 case LINE_STAT_STAGED:
6264 text = "Press %s to unstage %s for commit";
6265 break;
6267 case LINE_STAT_UNSTAGED:
6268 text = "Press %s to stage %s for commit";
6269 break;
6271 case LINE_STAT_UNTRACKED:
6272 text = "Press %s to stage %s for addition";
6273 break;
6275 case LINE_STAT_HEAD:
6276 case LINE_STAT_NONE:
6277 text = "Nothing to update";
6278 break;
6280 default:
6281 die("line type %d not handled in switch", line->type);
6284 if (status && status->status == 'U') {
6285 text = "Press %s to resolve conflict in %s";
6286 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6288 } else {
6289 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6292 string_format(view->ref, text, key, file);
6293 if (status)
6294 string_copy(opt_file, status->new.name);
6297 static bool
6298 status_grep(struct view *view, struct line *line)
6300 struct status *status = line->data;
6302 if (status) {
6303 const char buf[2] = { status->status, 0 };
6304 const char *text[] = { status->new.name, buf, NULL };
6306 return grep_text(view, text);
6309 return FALSE;
6312 static struct view_ops status_ops = {
6313 "file",
6314 NULL,
6315 status_open,
6316 NULL,
6317 status_draw,
6318 status_request,
6319 status_grep,
6320 status_select,
6324 static bool
6325 stage_diff_write(struct io *io, struct line *line, struct line *end)
6327 while (line < end) {
6328 if (!io_write(io, line->data, strlen(line->data)) ||
6329 !io_write(io, "\n", 1))
6330 return FALSE;
6331 line++;
6332 if (line->type == LINE_DIFF_CHUNK ||
6333 line->type == LINE_DIFF_HEADER)
6334 break;
6337 return TRUE;
6340 static struct line *
6341 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6343 for (; view->line < line; line--)
6344 if (line->type == type)
6345 return line;
6347 return NULL;
6350 static bool
6351 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6353 const char *apply_argv[SIZEOF_ARG] = {
6354 "git", "apply", "--whitespace=nowarn", NULL
6356 struct line *diff_hdr;
6357 struct io io;
6358 int argc = 3;
6360 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6361 if (!diff_hdr)
6362 return FALSE;
6364 if (!revert)
6365 apply_argv[argc++] = "--cached";
6366 if (revert || stage_line_type == LINE_STAT_STAGED)
6367 apply_argv[argc++] = "-R";
6368 apply_argv[argc++] = "-";
6369 apply_argv[argc++] = NULL;
6370 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6371 return FALSE;
6373 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6374 !stage_diff_write(&io, chunk, view->line + view->lines))
6375 chunk = NULL;
6377 io_done(&io);
6378 io_run_bg(update_index_argv);
6380 return chunk ? TRUE : FALSE;
6383 static bool
6384 stage_update(struct view *view, struct line *line)
6386 struct line *chunk = NULL;
6388 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6389 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6391 if (chunk) {
6392 if (!stage_apply_chunk(view, chunk, FALSE)) {
6393 report("Failed to apply chunk");
6394 return FALSE;
6397 } else if (!stage_status.status) {
6398 view = VIEW(REQ_VIEW_STATUS);
6400 for (line = view->line; line < view->line + view->lines; line++)
6401 if (line->type == stage_line_type)
6402 break;
6404 if (!status_update_files(view, line + 1)) {
6405 report("Failed to update files");
6406 return FALSE;
6409 } else if (!status_update_file(&stage_status, stage_line_type)) {
6410 report("Failed to update file");
6411 return FALSE;
6414 return TRUE;
6417 static bool
6418 stage_revert(struct view *view, struct line *line)
6420 struct line *chunk = NULL;
6422 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6423 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6425 if (chunk) {
6426 if (!prompt_yesno("Are you sure you want to revert changes?"))
6427 return FALSE;
6429 if (!stage_apply_chunk(view, chunk, TRUE)) {
6430 report("Failed to revert chunk");
6431 return FALSE;
6433 return TRUE;
6435 } else {
6436 return status_revert(stage_status.status ? &stage_status : NULL,
6437 stage_line_type, FALSE);
6442 static void
6443 stage_next(struct view *view, struct line *line)
6445 int i;
6447 if (!stage_chunks) {
6448 for (line = view->line; line < view->line + view->lines; line++) {
6449 if (line->type != LINE_DIFF_CHUNK)
6450 continue;
6452 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6453 report("Allocation failure");
6454 return;
6457 stage_chunk[stage_chunks++] = line - view->line;
6461 for (i = 0; i < stage_chunks; i++) {
6462 if (stage_chunk[i] > view->lineno) {
6463 do_scroll_view(view, stage_chunk[i] - view->lineno);
6464 report("Chunk %d of %d", i + 1, stage_chunks);
6465 return;
6469 report("No next chunk found");
6472 static enum request
6473 stage_request(struct view *view, enum request request, struct line *line)
6475 switch (request) {
6476 case REQ_STATUS_UPDATE:
6477 if (!stage_update(view, line))
6478 return REQ_NONE;
6479 break;
6481 case REQ_STATUS_REVERT:
6482 if (!stage_revert(view, line))
6483 return REQ_NONE;
6484 break;
6486 case REQ_STAGE_NEXT:
6487 if (stage_line_type == LINE_STAT_UNTRACKED) {
6488 report("File is untracked; press %s to add",
6489 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6490 return REQ_NONE;
6492 stage_next(view, line);
6493 return REQ_NONE;
6495 case REQ_EDIT:
6496 if (!stage_status.new.name[0])
6497 return request;
6498 if (stage_status.status == 'D') {
6499 report("File has been deleted.");
6500 return REQ_NONE;
6503 open_editor(stage_status.new.name);
6504 break;
6506 case REQ_REFRESH:
6507 /* Reload everything ... */
6508 break;
6510 case REQ_VIEW_BLAME:
6511 if (stage_status.new.name[0]) {
6512 string_copy(opt_file, stage_status.new.name);
6513 opt_ref[0] = 0;
6515 return request;
6517 case REQ_ENTER:
6518 return pager_request(view, request, line);
6520 default:
6521 return request;
6524 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6525 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6527 /* Check whether the staged entry still exists, and close the
6528 * stage view if it doesn't. */
6529 if (!status_exists(&stage_status, stage_line_type)) {
6530 status_restore(VIEW(REQ_VIEW_STATUS));
6531 return REQ_VIEW_CLOSE;
6534 if (stage_line_type == LINE_STAT_UNTRACKED) {
6535 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6536 report("Cannot display a directory");
6537 return REQ_NONE;
6540 if (!prepare_update_file(view, stage_status.new.name)) {
6541 report("Failed to open file: %s", strerror(errno));
6542 return REQ_NONE;
6545 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6547 return REQ_NONE;
6550 static struct view_ops stage_ops = {
6551 "line",
6552 NULL,
6553 NULL,
6554 pager_read,
6555 pager_draw,
6556 stage_request,
6557 pager_grep,
6558 pager_select,
6563 * Revision graph
6566 struct commit {
6567 char id[SIZEOF_REV]; /* SHA1 ID. */
6568 char title[128]; /* First line of the commit message. */
6569 const char *author; /* Author of the commit. */
6570 struct time time; /* Date from the author ident. */
6571 struct ref_list *refs; /* Repository references. */
6572 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6573 size_t graph_size; /* The width of the graph array. */
6574 bool has_parents; /* Rewritten --parents seen. */
6577 /* Size of rev graph with no "padding" columns */
6578 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6580 struct rev_graph {
6581 struct rev_graph *prev, *next, *parents;
6582 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6583 size_t size;
6584 struct commit *commit;
6585 size_t pos;
6586 unsigned int boundary:1;
6589 /* Parents of the commit being visualized. */
6590 static struct rev_graph graph_parents[4];
6592 /* The current stack of revisions on the graph. */
6593 static struct rev_graph graph_stacks[4] = {
6594 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6595 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6596 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6597 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6600 static inline bool
6601 graph_parent_is_merge(struct rev_graph *graph)
6603 return graph->parents->size > 1;
6606 static inline void
6607 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6609 struct commit *commit = graph->commit;
6611 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6612 commit->graph[commit->graph_size++] = symbol;
6615 static void
6616 clear_rev_graph(struct rev_graph *graph)
6618 graph->boundary = 0;
6619 graph->size = graph->pos = 0;
6620 graph->commit = NULL;
6621 memset(graph->parents, 0, sizeof(*graph->parents));
6624 static void
6625 done_rev_graph(struct rev_graph *graph)
6627 if (graph_parent_is_merge(graph) &&
6628 graph->pos < graph->size - 1 &&
6629 graph->next->size == graph->size + graph->parents->size - 1) {
6630 size_t i = graph->pos + graph->parents->size - 1;
6632 graph->commit->graph_size = i * 2;
6633 while (i < graph->next->size - 1) {
6634 append_to_rev_graph(graph, ' ');
6635 append_to_rev_graph(graph, '\\');
6636 i++;
6640 clear_rev_graph(graph);
6643 static void
6644 push_rev_graph(struct rev_graph *graph, const char *parent)
6646 int i;
6648 /* "Collapse" duplicate parents lines.
6650 * FIXME: This needs to also update update the drawn graph but
6651 * for now it just serves as a method for pruning graph lines. */
6652 for (i = 0; i < graph->size; i++)
6653 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6654 return;
6656 if (graph->size < SIZEOF_REVITEMS) {
6657 string_copy_rev(graph->rev[graph->size++], parent);
6661 static chtype
6662 get_rev_graph_symbol(struct rev_graph *graph)
6664 chtype symbol;
6666 if (graph->boundary)
6667 symbol = REVGRAPH_BOUND;
6668 else if (graph->parents->size == 0)
6669 symbol = REVGRAPH_INIT;
6670 else if (graph_parent_is_merge(graph))
6671 symbol = REVGRAPH_MERGE;
6672 else if (graph->pos >= graph->size)
6673 symbol = REVGRAPH_BRANCH;
6674 else
6675 symbol = REVGRAPH_COMMIT;
6677 return symbol;
6680 static void
6681 draw_rev_graph(struct rev_graph *graph)
6683 struct rev_filler {
6684 chtype separator, line;
6686 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6687 static struct rev_filler fillers[] = {
6688 { ' ', '|' },
6689 { '`', '.' },
6690 { '\'', ' ' },
6691 { '/', ' ' },
6693 chtype symbol = get_rev_graph_symbol(graph);
6694 struct rev_filler *filler;
6695 size_t i;
6697 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6698 filler = &fillers[DEFAULT];
6700 for (i = 0; i < graph->pos; i++) {
6701 append_to_rev_graph(graph, filler->line);
6702 if (graph_parent_is_merge(graph->prev) &&
6703 graph->prev->pos == i)
6704 filler = &fillers[RSHARP];
6706 append_to_rev_graph(graph, filler->separator);
6709 /* Place the symbol for this revision. */
6710 append_to_rev_graph(graph, symbol);
6712 if (graph->prev->size > graph->size)
6713 filler = &fillers[RDIAG];
6714 else
6715 filler = &fillers[DEFAULT];
6717 i++;
6719 for (; i < graph->size; i++) {
6720 append_to_rev_graph(graph, filler->separator);
6721 append_to_rev_graph(graph, filler->line);
6722 if (graph_parent_is_merge(graph->prev) &&
6723 i < graph->prev->pos + graph->parents->size)
6724 filler = &fillers[RSHARP];
6725 if (graph->prev->size > graph->size)
6726 filler = &fillers[LDIAG];
6729 if (graph->prev->size > graph->size) {
6730 append_to_rev_graph(graph, filler->separator);
6731 if (filler->line != ' ')
6732 append_to_rev_graph(graph, filler->line);
6736 /* Prepare the next rev graph */
6737 static void
6738 prepare_rev_graph(struct rev_graph *graph)
6740 size_t i;
6742 /* First, traverse all lines of revisions up to the active one. */
6743 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6744 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6745 break;
6747 push_rev_graph(graph->next, graph->rev[graph->pos]);
6750 /* Interleave the new revision parent(s). */
6751 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6752 push_rev_graph(graph->next, graph->parents->rev[i]);
6754 /* Lastly, put any remaining revisions. */
6755 for (i = graph->pos + 1; i < graph->size; i++)
6756 push_rev_graph(graph->next, graph->rev[i]);
6759 static void
6760 update_rev_graph(struct view *view, struct rev_graph *graph)
6762 /* If this is the finalizing update ... */
6763 if (graph->commit)
6764 prepare_rev_graph(graph);
6766 /* Graph visualization needs a one rev look-ahead,
6767 * so the first update doesn't visualize anything. */
6768 if (!graph->prev->commit)
6769 return;
6771 if (view->lines > 2)
6772 view->line[view->lines - 3].dirty = 1;
6773 if (view->lines > 1)
6774 view->line[view->lines - 2].dirty = 1;
6775 draw_rev_graph(graph->prev);
6776 done_rev_graph(graph->prev->prev);
6781 * Main view backend
6784 static const char *main_argv[SIZEOF_ARG] = {
6785 "git", "log", "--no-color", "--pretty=raw", "--parents",
6786 "--topo-order", "%(diffargs)", "%(revargs)",
6787 "--", "%(fileargs)", NULL
6790 static bool
6791 main_draw(struct view *view, struct line *line, unsigned int lineno)
6793 struct commit *commit = line->data;
6795 if (!commit->author)
6796 return FALSE;
6798 if (opt_date && draw_date(view, &commit->time))
6799 return TRUE;
6801 if (opt_author && draw_author(view, commit->author))
6802 return TRUE;
6804 if (opt_rev_graph && commit->graph_size &&
6805 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6806 return TRUE;
6808 if (opt_show_refs && commit->refs) {
6809 size_t i;
6811 for (i = 0; i < commit->refs->size; i++) {
6812 struct ref *ref = commit->refs->refs[i];
6813 enum line_type type;
6815 if (ref->head)
6816 type = LINE_MAIN_HEAD;
6817 else if (ref->ltag)
6818 type = LINE_MAIN_LOCAL_TAG;
6819 else if (ref->tag)
6820 type = LINE_MAIN_TAG;
6821 else if (ref->tracked)
6822 type = LINE_MAIN_TRACKED;
6823 else if (ref->remote)
6824 type = LINE_MAIN_REMOTE;
6825 else
6826 type = LINE_MAIN_REF;
6828 if (draw_text(view, type, "[", TRUE) ||
6829 draw_text(view, type, ref->name, TRUE) ||
6830 draw_text(view, type, "]", TRUE))
6831 return TRUE;
6833 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6834 return TRUE;
6838 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6839 return TRUE;
6842 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6843 static bool
6844 main_read(struct view *view, char *line)
6846 static struct rev_graph *graph = graph_stacks;
6847 enum line_type type;
6848 struct commit *commit;
6850 if (!line) {
6851 int i;
6853 if (!view->lines && !view->prev)
6854 die("No revisions match the given arguments.");
6855 if (view->lines > 0) {
6856 commit = view->line[view->lines - 1].data;
6857 view->line[view->lines - 1].dirty = 1;
6858 if (!commit->author) {
6859 view->lines--;
6860 free(commit);
6861 graph->commit = NULL;
6864 update_rev_graph(view, graph);
6866 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6867 clear_rev_graph(&graph_stacks[i]);
6868 return TRUE;
6871 type = get_line_type(line);
6872 if (type == LINE_COMMIT) {
6873 commit = calloc(1, sizeof(struct commit));
6874 if (!commit)
6875 return FALSE;
6877 line += STRING_SIZE("commit ");
6878 if (*line == '-') {
6879 graph->boundary = 1;
6880 line++;
6883 string_copy_rev(commit->id, line);
6884 commit->refs = get_ref_list(commit->id);
6885 graph->commit = commit;
6886 add_line_data(view, commit, LINE_MAIN_COMMIT);
6888 while ((line = strchr(line, ' '))) {
6889 line++;
6890 push_rev_graph(graph->parents, line);
6891 commit->has_parents = TRUE;
6893 return TRUE;
6896 if (!view->lines)
6897 return TRUE;
6898 commit = view->line[view->lines - 1].data;
6900 switch (type) {
6901 case LINE_PARENT:
6902 if (commit->has_parents)
6903 break;
6904 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6905 break;
6907 case LINE_AUTHOR:
6908 parse_author_line(line + STRING_SIZE("author "),
6909 &commit->author, &commit->time);
6910 update_rev_graph(view, graph);
6911 graph = graph->next;
6912 break;
6914 default:
6915 /* Fill in the commit title if it has not already been set. */
6916 if (commit->title[0])
6917 break;
6919 /* Require titles to start with a non-space character at the
6920 * offset used by git log. */
6921 if (strncmp(line, " ", 4))
6922 break;
6923 line += 4;
6924 /* Well, if the title starts with a whitespace character,
6925 * try to be forgiving. Otherwise we end up with no title. */
6926 while (isspace(*line))
6927 line++;
6928 if (*line == '\0')
6929 break;
6930 /* FIXME: More graceful handling of titles; append "..." to
6931 * shortened titles, etc. */
6933 string_expand(commit->title, sizeof(commit->title), line, 1);
6934 view->line[view->lines - 1].dirty = 1;
6937 return TRUE;
6940 static enum request
6941 main_request(struct view *view, enum request request, struct line *line)
6943 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6945 switch (request) {
6946 case REQ_ENTER:
6947 if (view_is_displayed(view) && display[0] != view)
6948 maximize_view(view);
6949 open_view(view, REQ_VIEW_DIFF, flags);
6950 break;
6951 case REQ_REFRESH:
6952 load_refs();
6953 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6954 break;
6955 default:
6956 return request;
6959 return REQ_NONE;
6962 static bool
6963 grep_refs(struct ref_list *list, regex_t *regex)
6965 regmatch_t pmatch;
6966 size_t i;
6968 if (!opt_show_refs || !list)
6969 return FALSE;
6971 for (i = 0; i < list->size; i++) {
6972 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6973 return TRUE;
6976 return FALSE;
6979 static bool
6980 main_grep(struct view *view, struct line *line)
6982 struct commit *commit = line->data;
6983 const char *text[] = {
6984 commit->title,
6985 opt_author ? commit->author : "",
6986 mkdate(&commit->time, opt_date),
6987 NULL
6990 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6993 static void
6994 main_select(struct view *view, struct line *line)
6996 struct commit *commit = line->data;
6998 string_copy_rev(view->ref, commit->id);
6999 string_copy_rev(ref_commit, view->ref);
7002 static struct view_ops main_ops = {
7003 "commit",
7004 main_argv,
7005 NULL,
7006 main_read,
7007 main_draw,
7008 main_request,
7009 main_grep,
7010 main_select,
7015 * Status management
7018 /* Whether or not the curses interface has been initialized. */
7019 static bool cursed = FALSE;
7021 /* Terminal hacks and workarounds. */
7022 static bool use_scroll_redrawwin;
7023 static bool use_scroll_status_wclear;
7025 /* The status window is used for polling keystrokes. */
7026 static WINDOW *status_win;
7028 /* Reading from the prompt? */
7029 static bool input_mode = FALSE;
7031 static bool status_empty = FALSE;
7033 /* Update status and title window. */
7034 static void
7035 report(const char *msg, ...)
7037 struct view *view = display[current_view];
7039 if (input_mode)
7040 return;
7042 if (!view) {
7043 char buf[SIZEOF_STR];
7044 va_list args;
7046 va_start(args, msg);
7047 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7048 buf[sizeof(buf) - 1] = 0;
7049 buf[sizeof(buf) - 2] = '.';
7050 buf[sizeof(buf) - 3] = '.';
7051 buf[sizeof(buf) - 4] = '.';
7053 va_end(args);
7054 die("%s", buf);
7057 if (!status_empty || *msg) {
7058 va_list args;
7060 va_start(args, msg);
7062 wmove(status_win, 0, 0);
7063 if (view->has_scrolled && use_scroll_status_wclear)
7064 wclear(status_win);
7065 if (*msg) {
7066 vwprintw(status_win, msg, args);
7067 status_empty = FALSE;
7068 } else {
7069 status_empty = TRUE;
7071 wclrtoeol(status_win);
7072 wnoutrefresh(status_win);
7074 va_end(args);
7077 update_view_title(view);
7080 static void
7081 init_display(void)
7083 const char *term;
7084 int x, y;
7086 /* Initialize the curses library */
7087 if (isatty(STDIN_FILENO)) {
7088 cursed = !!initscr();
7089 opt_tty = stdin;
7090 } else {
7091 /* Leave stdin and stdout alone when acting as a pager. */
7092 opt_tty = fopen("/dev/tty", "r+");
7093 if (!opt_tty)
7094 die("Failed to open /dev/tty");
7095 cursed = !!newterm(NULL, opt_tty, opt_tty);
7098 if (!cursed)
7099 die("Failed to initialize curses");
7101 nonl(); /* Disable conversion and detect newlines from input. */
7102 cbreak(); /* Take input chars one at a time, no wait for \n */
7103 noecho(); /* Don't echo input */
7104 leaveok(stdscr, FALSE);
7106 if (has_colors())
7107 init_colors();
7109 getmaxyx(stdscr, y, x);
7110 status_win = newwin(1, 0, y - 1, 0);
7111 if (!status_win)
7112 die("Failed to create status window");
7114 /* Enable keyboard mapping */
7115 keypad(status_win, TRUE);
7116 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7118 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7119 set_tabsize(opt_tab_size);
7120 #else
7121 TABSIZE = opt_tab_size;
7122 #endif
7124 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7125 if (term && !strcmp(term, "gnome-terminal")) {
7126 /* In the gnome-terminal-emulator, the message from
7127 * scrolling up one line when impossible followed by
7128 * scrolling down one line causes corruption of the
7129 * status line. This is fixed by calling wclear. */
7130 use_scroll_status_wclear = TRUE;
7131 use_scroll_redrawwin = FALSE;
7133 } else if (term && !strcmp(term, "xrvt-xpm")) {
7134 /* No problems with full optimizations in xrvt-(unicode)
7135 * and aterm. */
7136 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7138 } else {
7139 /* When scrolling in (u)xterm the last line in the
7140 * scrolling direction will update slowly. */
7141 use_scroll_redrawwin = TRUE;
7142 use_scroll_status_wclear = FALSE;
7146 static int
7147 get_input(int prompt_position)
7149 struct view *view;
7150 int i, key, cursor_y, cursor_x;
7152 if (prompt_position)
7153 input_mode = TRUE;
7155 while (TRUE) {
7156 bool loading = FALSE;
7158 foreach_view (view, i) {
7159 update_view(view);
7160 if (view_is_displayed(view) && view->has_scrolled &&
7161 use_scroll_redrawwin)
7162 redrawwin(view->win);
7163 view->has_scrolled = FALSE;
7164 if (view->pipe)
7165 loading = TRUE;
7168 /* Update the cursor position. */
7169 if (prompt_position) {
7170 getbegyx(status_win, cursor_y, cursor_x);
7171 cursor_x = prompt_position;
7172 } else {
7173 view = display[current_view];
7174 getbegyx(view->win, cursor_y, cursor_x);
7175 cursor_x = view->width - 1;
7176 cursor_y += view->lineno - view->offset;
7178 setsyx(cursor_y, cursor_x);
7180 /* Refresh, accept single keystroke of input */
7181 doupdate();
7182 nodelay(status_win, loading);
7183 key = wgetch(status_win);
7185 /* wgetch() with nodelay() enabled returns ERR when
7186 * there's no input. */
7187 if (key == ERR) {
7189 } else if (key == KEY_RESIZE) {
7190 int height, width;
7192 getmaxyx(stdscr, height, width);
7194 wresize(status_win, 1, width);
7195 mvwin(status_win, height - 1, 0);
7196 wnoutrefresh(status_win);
7197 resize_display();
7198 redraw_display(TRUE);
7200 } else {
7201 input_mode = FALSE;
7202 return key;
7207 static char *
7208 prompt_input(const char *prompt, input_handler handler, void *data)
7210 enum input_status status = INPUT_OK;
7211 static char buf[SIZEOF_STR];
7212 size_t pos = 0;
7214 buf[pos] = 0;
7216 while (status == INPUT_OK || status == INPUT_SKIP) {
7217 int key;
7219 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7220 wclrtoeol(status_win);
7222 key = get_input(pos + 1);
7223 switch (key) {
7224 case KEY_RETURN:
7225 case KEY_ENTER:
7226 case '\n':
7227 status = pos ? INPUT_STOP : INPUT_CANCEL;
7228 break;
7230 case KEY_BACKSPACE:
7231 if (pos > 0)
7232 buf[--pos] = 0;
7233 else
7234 status = INPUT_CANCEL;
7235 break;
7237 case KEY_ESC:
7238 status = INPUT_CANCEL;
7239 break;
7241 default:
7242 if (pos >= sizeof(buf)) {
7243 report("Input string too long");
7244 return NULL;
7247 status = handler(data, buf, key);
7248 if (status == INPUT_OK)
7249 buf[pos++] = (char) key;
7253 /* Clear the status window */
7254 status_empty = FALSE;
7255 report("");
7257 if (status == INPUT_CANCEL)
7258 return NULL;
7260 buf[pos++] = 0;
7262 return buf;
7265 static enum input_status
7266 prompt_yesno_handler(void *data, char *buf, int c)
7268 if (c == 'y' || c == 'Y')
7269 return INPUT_STOP;
7270 if (c == 'n' || c == 'N')
7271 return INPUT_CANCEL;
7272 return INPUT_SKIP;
7275 static bool
7276 prompt_yesno(const char *prompt)
7278 char prompt2[SIZEOF_STR];
7280 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7281 return FALSE;
7283 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7286 static enum input_status
7287 read_prompt_handler(void *data, char *buf, int c)
7289 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7292 static char *
7293 read_prompt(const char *prompt)
7295 return prompt_input(prompt, read_prompt_handler, NULL);
7298 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7300 enum input_status status = INPUT_OK;
7301 int size = 0;
7303 while (items[size].text)
7304 size++;
7306 while (status == INPUT_OK) {
7307 const struct menu_item *item = &items[*selected];
7308 int key;
7309 int i;
7311 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7312 prompt, *selected + 1, size);
7313 if (item->hotkey)
7314 wprintw(status_win, "[%c] ", (char) item->hotkey);
7315 wprintw(status_win, "%s", item->text);
7316 wclrtoeol(status_win);
7318 key = get_input(COLS - 1);
7319 switch (key) {
7320 case KEY_RETURN:
7321 case KEY_ENTER:
7322 case '\n':
7323 status = INPUT_STOP;
7324 break;
7326 case KEY_LEFT:
7327 case KEY_UP:
7328 *selected = *selected - 1;
7329 if (*selected < 0)
7330 *selected = size - 1;
7331 break;
7333 case KEY_RIGHT:
7334 case KEY_DOWN:
7335 *selected = (*selected + 1) % size;
7336 break;
7338 case KEY_ESC:
7339 status = INPUT_CANCEL;
7340 break;
7342 default:
7343 for (i = 0; items[i].text; i++)
7344 if (items[i].hotkey == key) {
7345 *selected = i;
7346 status = INPUT_STOP;
7347 break;
7352 /* Clear the status window */
7353 status_empty = FALSE;
7354 report("");
7356 return status != INPUT_CANCEL;
7360 * Repository properties
7363 static struct ref **refs = NULL;
7364 static size_t refs_size = 0;
7365 static struct ref *refs_head = NULL;
7367 static struct ref_list **ref_lists = NULL;
7368 static size_t ref_lists_size = 0;
7370 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7371 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7372 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7374 static int
7375 compare_refs(const void *ref1_, const void *ref2_)
7377 const struct ref *ref1 = *(const struct ref **)ref1_;
7378 const struct ref *ref2 = *(const struct ref **)ref2_;
7380 if (ref1->tag != ref2->tag)
7381 return ref2->tag - ref1->tag;
7382 if (ref1->ltag != ref2->ltag)
7383 return ref2->ltag - ref2->ltag;
7384 if (ref1->head != ref2->head)
7385 return ref2->head - ref1->head;
7386 if (ref1->tracked != ref2->tracked)
7387 return ref2->tracked - ref1->tracked;
7388 if (ref1->remote != ref2->remote)
7389 return ref2->remote - ref1->remote;
7390 return strcmp(ref1->name, ref2->name);
7393 static void
7394 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7396 size_t i;
7398 for (i = 0; i < refs_size; i++)
7399 if (!visitor(data, refs[i]))
7400 break;
7403 static struct ref *
7404 get_ref_head()
7406 return refs_head;
7409 static struct ref_list *
7410 get_ref_list(const char *id)
7412 struct ref_list *list;
7413 size_t i;
7415 for (i = 0; i < ref_lists_size; i++)
7416 if (!strcmp(id, ref_lists[i]->id))
7417 return ref_lists[i];
7419 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7420 return NULL;
7421 list = calloc(1, sizeof(*list));
7422 if (!list)
7423 return NULL;
7425 for (i = 0; i < refs_size; i++) {
7426 if (!strcmp(id, refs[i]->id) &&
7427 realloc_refs_list(&list->refs, list->size, 1))
7428 list->refs[list->size++] = refs[i];
7431 if (!list->refs) {
7432 free(list);
7433 return NULL;
7436 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7437 ref_lists[ref_lists_size++] = list;
7438 return list;
7441 static int
7442 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7444 struct ref *ref = NULL;
7445 bool tag = FALSE;
7446 bool ltag = FALSE;
7447 bool remote = FALSE;
7448 bool tracked = FALSE;
7449 bool head = FALSE;
7450 int from = 0, to = refs_size - 1;
7452 if (!prefixcmp(name, "refs/tags/")) {
7453 if (!suffixcmp(name, namelen, "^{}")) {
7454 namelen -= 3;
7455 name[namelen] = 0;
7456 } else {
7457 ltag = TRUE;
7460 tag = TRUE;
7461 namelen -= STRING_SIZE("refs/tags/");
7462 name += STRING_SIZE("refs/tags/");
7464 } else if (!prefixcmp(name, "refs/remotes/")) {
7465 remote = TRUE;
7466 namelen -= STRING_SIZE("refs/remotes/");
7467 name += STRING_SIZE("refs/remotes/");
7468 tracked = !strcmp(opt_remote, name);
7470 } else if (!prefixcmp(name, "refs/heads/")) {
7471 namelen -= STRING_SIZE("refs/heads/");
7472 name += STRING_SIZE("refs/heads/");
7473 if (!strncmp(opt_head, name, namelen))
7474 return OK;
7476 } else if (!strcmp(name, "HEAD")) {
7477 head = TRUE;
7478 if (*opt_head) {
7479 namelen = strlen(opt_head);
7480 name = opt_head;
7484 /* If we are reloading or it's an annotated tag, replace the
7485 * previous SHA1 with the resolved commit id; relies on the fact
7486 * git-ls-remote lists the commit id of an annotated tag right
7487 * before the commit id it points to. */
7488 while (from <= to) {
7489 size_t pos = (to + from) / 2;
7490 int cmp = strcmp(name, refs[pos]->name);
7492 if (!cmp) {
7493 ref = refs[pos];
7494 break;
7497 if (cmp < 0)
7498 to = pos - 1;
7499 else
7500 from = pos + 1;
7503 if (!ref) {
7504 if (!realloc_refs(&refs, refs_size, 1))
7505 return ERR;
7506 ref = calloc(1, sizeof(*ref) + namelen);
7507 if (!ref)
7508 return ERR;
7509 memmove(refs + from + 1, refs + from,
7510 (refs_size - from) * sizeof(*refs));
7511 refs[from] = ref;
7512 strncpy(ref->name, name, namelen);
7513 refs_size++;
7516 ref->head = head;
7517 ref->tag = tag;
7518 ref->ltag = ltag;
7519 ref->remote = remote;
7520 ref->tracked = tracked;
7521 string_copy_rev(ref->id, id);
7523 if (head)
7524 refs_head = ref;
7525 return OK;
7528 static int
7529 load_refs(void)
7531 const char *head_argv[] = {
7532 "git", "symbolic-ref", "HEAD", NULL
7534 static const char *ls_remote_argv[SIZEOF_ARG] = {
7535 "git", "ls-remote", opt_git_dir, NULL
7537 static bool init = FALSE;
7538 size_t i;
7540 if (!init) {
7541 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7542 die("TIG_LS_REMOTE contains too many arguments");
7543 init = TRUE;
7546 if (!*opt_git_dir)
7547 return OK;
7549 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7550 !prefixcmp(opt_head, "refs/heads/")) {
7551 char *offset = opt_head + STRING_SIZE("refs/heads/");
7553 memmove(opt_head, offset, strlen(offset) + 1);
7556 refs_head = NULL;
7557 for (i = 0; i < refs_size; i++)
7558 refs[i]->id[0] = 0;
7560 if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7561 return ERR;
7563 /* Update the ref lists to reflect changes. */
7564 for (i = 0; i < ref_lists_size; i++) {
7565 struct ref_list *list = ref_lists[i];
7566 size_t old, new;
7568 for (old = new = 0; old < list->size; old++)
7569 if (!strcmp(list->id, list->refs[old]->id))
7570 list->refs[new++] = list->refs[old];
7571 list->size = new;
7574 return OK;
7577 static void
7578 set_remote_branch(const char *name, const char *value, size_t valuelen)
7580 if (!strcmp(name, ".remote")) {
7581 string_ncopy(opt_remote, value, valuelen);
7583 } else if (*opt_remote && !strcmp(name, ".merge")) {
7584 size_t from = strlen(opt_remote);
7586 if (!prefixcmp(value, "refs/heads/"))
7587 value += STRING_SIZE("refs/heads/");
7589 if (!string_format_from(opt_remote, &from, "/%s", value))
7590 opt_remote[0] = 0;
7594 static void
7595 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7597 const char *argv[SIZEOF_ARG] = { name, "=" };
7598 int argc = 1 + (cmd == option_set_command);
7599 int error = ERR;
7601 if (!argv_from_string(argv, &argc, value))
7602 config_msg = "Too many option arguments";
7603 else
7604 error = cmd(argc, argv);
7606 if (error == ERR)
7607 warn("Option 'tig.%s': %s", name, config_msg);
7610 static bool
7611 set_environment_variable(const char *name, const char *value)
7613 size_t len = strlen(name) + 1 + strlen(value) + 1;
7614 char *env = malloc(len);
7616 if (env &&
7617 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7618 putenv(env) == 0)
7619 return TRUE;
7620 free(env);
7621 return FALSE;
7624 static void
7625 set_work_tree(const char *value)
7627 char cwd[SIZEOF_STR];
7629 if (!getcwd(cwd, sizeof(cwd)))
7630 die("Failed to get cwd path: %s", strerror(errno));
7631 if (chdir(opt_git_dir) < 0)
7632 die("Failed to chdir(%s): %s", strerror(errno));
7633 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7634 die("Failed to get git path: %s", strerror(errno));
7635 if (chdir(cwd) < 0)
7636 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7637 if (chdir(value) < 0)
7638 die("Failed to chdir(%s): %s", value, strerror(errno));
7639 if (!getcwd(cwd, sizeof(cwd)))
7640 die("Failed to get cwd path: %s", strerror(errno));
7641 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7642 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7643 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7644 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7645 opt_is_inside_work_tree = TRUE;
7648 static int
7649 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7651 if (!strcmp(name, "i18n.commitencoding"))
7652 string_ncopy(opt_encoding, value, valuelen);
7654 else if (!strcmp(name, "core.editor"))
7655 string_ncopy(opt_editor, value, valuelen);
7657 else if (!strcmp(name, "core.worktree"))
7658 set_work_tree(value);
7660 else if (!prefixcmp(name, "tig.color."))
7661 set_repo_config_option(name + 10, value, option_color_command);
7663 else if (!prefixcmp(name, "tig.bind."))
7664 set_repo_config_option(name + 9, value, option_bind_command);
7666 else if (!prefixcmp(name, "tig."))
7667 set_repo_config_option(name + 4, value, option_set_command);
7669 else if (*opt_head && !prefixcmp(name, "branch.") &&
7670 !strncmp(name + 7, opt_head, strlen(opt_head)))
7671 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7673 return OK;
7676 static int
7677 load_git_config(void)
7679 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7681 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7684 static int
7685 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7687 if (!opt_git_dir[0]) {
7688 string_ncopy(opt_git_dir, name, namelen);
7690 } else if (opt_is_inside_work_tree == -1) {
7691 /* This can be 3 different values depending on the
7692 * version of git being used. If git-rev-parse does not
7693 * understand --is-inside-work-tree it will simply echo
7694 * the option else either "true" or "false" is printed.
7695 * Default to true for the unknown case. */
7696 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7698 } else if (*name == '.') {
7699 string_ncopy(opt_cdup, name, namelen);
7701 } else {
7702 string_ncopy(opt_prefix, name, namelen);
7705 return OK;
7708 static int
7709 load_repo_info(void)
7711 const char *rev_parse_argv[] = {
7712 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7713 "--show-cdup", "--show-prefix", NULL
7716 return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7721 * Main
7724 static const char usage[] =
7725 "tig " TIG_VERSION " (" __DATE__ ")\n"
7726 "\n"
7727 "Usage: tig [options] [revs] [--] [paths]\n"
7728 " or: tig show [options] [revs] [--] [paths]\n"
7729 " or: tig blame [rev] path\n"
7730 " or: tig status\n"
7731 " or: tig < [git command output]\n"
7732 "\n"
7733 "Options:\n"
7734 " -v, --version Show version and exit\n"
7735 " -h, --help Show help message and exit";
7737 static void __NORETURN
7738 quit(int sig)
7740 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7741 if (cursed)
7742 endwin();
7743 exit(0);
7746 static void __NORETURN
7747 die(const char *err, ...)
7749 va_list args;
7751 endwin();
7753 va_start(args, err);
7754 fputs("tig: ", stderr);
7755 vfprintf(stderr, err, args);
7756 fputs("\n", stderr);
7757 va_end(args);
7759 exit(1);
7762 static void
7763 warn(const char *msg, ...)
7765 va_list args;
7767 va_start(args, msg);
7768 fputs("tig warning: ", stderr);
7769 vfprintf(stderr, msg, args);
7770 fputs("\n", stderr);
7771 va_end(args);
7774 static int
7775 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7777 const char ***filter_args = data;
7779 return argv_append(filter_args, name) ? OK : ERR;
7782 static void
7783 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7785 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7786 const char **all_argv = NULL;
7788 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7789 !argv_append_array(&all_argv, argv) ||
7790 !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7791 die("Failed to split arguments");
7792 argv_free(all_argv);
7793 free(all_argv);
7796 static void
7797 filter_options(const char *argv[])
7799 filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7800 filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7801 filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7804 static enum request
7805 parse_options(int argc, const char *argv[])
7807 enum request request = REQ_VIEW_MAIN;
7808 const char *subcommand;
7809 bool seen_dashdash = FALSE;
7810 const char **filter_argv = NULL;
7811 int i;
7813 if (!isatty(STDIN_FILENO))
7814 return REQ_VIEW_PAGER;
7816 if (argc <= 1)
7817 return REQ_VIEW_MAIN;
7819 subcommand = argv[1];
7820 if (!strcmp(subcommand, "status")) {
7821 if (argc > 2)
7822 warn("ignoring arguments after `%s'", subcommand);
7823 return REQ_VIEW_STATUS;
7825 } else if (!strcmp(subcommand, "blame")) {
7826 if (argc <= 2 || argc > 4)
7827 die("invalid number of options to blame\n\n%s", usage);
7829 i = 2;
7830 if (argc == 4) {
7831 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7832 i++;
7835 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7836 return REQ_VIEW_BLAME;
7838 } else if (!strcmp(subcommand, "show")) {
7839 request = REQ_VIEW_DIFF;
7841 } else {
7842 subcommand = NULL;
7845 for (i = 1 + !!subcommand; i < argc; i++) {
7846 const char *opt = argv[i];
7848 if (seen_dashdash) {
7849 argv_append(&opt_file_argv, opt);
7850 continue;
7852 } else if (!strcmp(opt, "--")) {
7853 seen_dashdash = TRUE;
7854 continue;
7856 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7857 printf("tig version %s\n", TIG_VERSION);
7858 quit(0);
7860 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7861 printf("%s\n", usage);
7862 quit(0);
7864 } else if (!strcmp(opt, "--all")) {
7865 argv_append(&opt_rev_argv, opt);
7866 continue;
7869 if (!argv_append(&filter_argv, opt))
7870 die("command too long");
7873 if (filter_argv)
7874 filter_options(filter_argv);
7876 return request;
7880 main(int argc, const char *argv[])
7882 const char *codeset = "UTF-8";
7883 enum request request = parse_options(argc, argv);
7884 struct view *view;
7885 size_t i;
7887 signal(SIGINT, quit);
7888 signal(SIGPIPE, SIG_IGN);
7890 if (setlocale(LC_ALL, "")) {
7891 codeset = nl_langinfo(CODESET);
7894 if (load_repo_info() == ERR)
7895 die("Failed to load repo info.");
7897 if (load_options() == ERR)
7898 die("Failed to load user config.");
7900 if (load_git_config() == ERR)
7901 die("Failed to load repo config.");
7903 /* Require a git repository unless when running in pager mode. */
7904 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7905 die("Not a git repository");
7907 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7908 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7909 if (opt_iconv_in == ICONV_NONE)
7910 die("Failed to initialize character set conversion");
7913 if (codeset && strcmp(codeset, "UTF-8")) {
7914 opt_iconv_out = iconv_open(codeset, "UTF-8");
7915 if (opt_iconv_out == ICONV_NONE)
7916 die("Failed to initialize character set conversion");
7919 if (load_refs() == ERR)
7920 die("Failed to load refs.");
7922 foreach_view (view, i) {
7923 if (getenv(view->cmd_env))
7924 warn("Use of the %s environment variable is deprecated,"
7925 " use options or TIG_DIFF_ARGS instead",
7926 view->cmd_env);
7927 if (!argv_from_env(view->ops->argv, view->cmd_env))
7928 die("Too many arguments in the `%s` environment variable",
7929 view->cmd_env);
7932 init_display();
7934 while (view_driver(display[current_view], request)) {
7935 int key = get_input(0);
7937 view = display[current_view];
7938 request = get_keybinding(view->keymap, key);
7940 /* Some low-level request handling. This keeps access to
7941 * status_win restricted. */
7942 switch (request) {
7943 case REQ_NONE:
7944 report("Unknown key, press %s for help",
7945 get_key(view->keymap, REQ_VIEW_HELP));
7946 break;
7947 case REQ_PROMPT:
7949 char *cmd = read_prompt(":");
7951 if (cmd && isdigit(*cmd)) {
7952 int lineno = view->lineno + 1;
7954 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7955 select_view_line(view, lineno - 1);
7956 report("");
7957 } else {
7958 report("Unable to parse '%s' as a line number", cmd);
7961 } else if (cmd) {
7962 struct view *next = VIEW(REQ_VIEW_PAGER);
7963 const char *argv[SIZEOF_ARG] = { "git" };
7964 int argc = 1;
7966 /* When running random commands, initially show the
7967 * command in the title. However, it maybe later be
7968 * overwritten if a commit line is selected. */
7969 string_ncopy(next->ref, cmd, strlen(cmd));
7971 if (!argv_from_string(argv, &argc, cmd)) {
7972 report("Too many arguments");
7973 } else if (!prepare_update(next, argv, NULL)) {
7974 report("Failed to format command");
7975 } else {
7976 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7980 request = REQ_NONE;
7981 break;
7983 case REQ_SEARCH:
7984 case REQ_SEARCH_BACK:
7986 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7987 char *search = read_prompt(prompt);
7989 if (search)
7990 string_ncopy(opt_search, search, strlen(search));
7991 else if (*opt_search)
7992 request = request == REQ_SEARCH ?
7993 REQ_FIND_NEXT :
7994 REQ_FIND_PREV;
7995 else
7996 request = REQ_NONE;
7997 break;
7999 default:
8000 break;
8004 quit(0);
8006 return 0;