utf8: move unicode related functions below the string helpers
[tig.git] / tig.c
blob75f7944bb06b6f42b534356f1397dc9e16fe2c84
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_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152 INPUT_OK,
153 INPUT_SKIP,
154 INPUT_STOP,
155 INPUT_CANCEL
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164 int hotkey;
165 const char *text;
166 void *data;
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
172 * Allocation helpers ... Entering macro hell to never be seen again.
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
179 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
180 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181 type *tmp = *mem; \
183 if (mem == NULL || num_chunks != num_chunks_new) { \
184 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185 if (tmp) \
186 *mem = tmp; \
189 return tmp; \
193 * String helpers
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212 string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223 size_t size, pos;
225 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226 if (src[pos] == '\t') {
227 size_t expanded = tabsize - (size % tabsize);
229 if (expanded + size >= dstlen - 1)
230 expanded = dstlen - size - 1;
231 memcpy(dst + size, " ", expanded);
232 size += expanded;
233 } else {
234 dst[size++] = src[pos];
238 dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244 int namelen;
246 while (isspace(*name))
247 name++;
249 namelen = strlen(name) - 1;
250 while (namelen > 0 && isspace(name[namelen]))
251 name[namelen--] = 0;
253 return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259 va_list args;
260 size_t pos = bufpos ? *bufpos : 0;
262 va_start(args, fmt);
263 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264 va_end(args);
266 if (bufpos)
267 *bufpos = pos;
269 return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273 string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276 string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281 size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285 /* Diff-Header == DIFF_HEADER */
286 for (i = 0; i < len; i++) {
287 if (toupper(str1[i]) == toupper(str2[i]))
288 continue;
290 if (string_enum_sep(str1[i]) &&
291 string_enum_sep(str2[i]))
292 continue;
294 return str1[i] - str2[i];
297 return 0;
300 #define enum_equals(entry, str, len) \
301 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304 const char *name;
305 int namelen;
306 int value;
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314 static char buf[SIZEOF_STR];
315 int bufpos;
317 for (bufpos = 0; bufpos <= namelen; bufpos++) {
318 buf[bufpos] = tolower(name[bufpos]);
319 if (buf[bufpos] == '_')
320 buf[bufpos] = '-';
323 buf[bufpos] = 0;
324 return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332 size_t namelen = strlen(name);
333 int i;
335 for (i = 0; i < map_size; i++)
336 if (enum_equals(map[i], name, namelen)) {
337 *value = map[i].value;
338 return TRUE;
341 return FALSE;
344 #define map_enum(attr, map, name) \
345 map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348 strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353 size_t len = slen >= 0 ? slen : strlen(str);
354 size_t suffixlen = strlen(suffix);
356 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
361 * Unicode / UTF-8 handling
363 * NOTE: Much of the following code for dealing with Unicode is derived from
364 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
365 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
368 static inline int
369 unicode_width(unsigned long c, int tab_size)
371 if (c >= 0x1100 &&
372 (c <= 0x115f /* Hangul Jamo */
373 || c == 0x2329
374 || c == 0x232a
375 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
376 /* CJK ... Yi */
377 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
378 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
379 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
380 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
381 || (c >= 0xffe0 && c <= 0xffe6)
382 || (c >= 0x20000 && c <= 0x2fffd)
383 || (c >= 0x30000 && c <= 0x3fffd)))
384 return 2;
386 if (c == '\t')
387 return tab_size;
389 return 1;
392 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
393 * Illegal bytes are set one. */
394 static const unsigned char utf8_bytes[256] = {
395 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,
396 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,
397 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,
398 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,
399 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,
400 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,
401 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,
402 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,
405 static inline unsigned char
406 utf8_char_length(const char *string, const char *end)
408 int c = *(unsigned char *) string;
410 return utf8_bytes[c];
413 /* Decode UTF-8 multi-byte representation into a Unicode character. */
414 static inline unsigned long
415 utf8_to_unicode(const char *string, size_t length)
417 unsigned long unicode;
419 switch (length) {
420 case 1:
421 unicode = string[0];
422 break;
423 case 2:
424 unicode = (string[0] & 0x1f) << 6;
425 unicode += (string[1] & 0x3f);
426 break;
427 case 3:
428 unicode = (string[0] & 0x0f) << 12;
429 unicode += ((string[1] & 0x3f) << 6);
430 unicode += (string[2] & 0x3f);
431 break;
432 case 4:
433 unicode = (string[0] & 0x0f) << 18;
434 unicode += ((string[1] & 0x3f) << 12);
435 unicode += ((string[2] & 0x3f) << 6);
436 unicode += (string[3] & 0x3f);
437 break;
438 case 5:
439 unicode = (string[0] & 0x0f) << 24;
440 unicode += ((string[1] & 0x3f) << 18);
441 unicode += ((string[2] & 0x3f) << 12);
442 unicode += ((string[3] & 0x3f) << 6);
443 unicode += (string[4] & 0x3f);
444 break;
445 case 6:
446 unicode = (string[0] & 0x01) << 30;
447 unicode += ((string[1] & 0x3f) << 24);
448 unicode += ((string[2] & 0x3f) << 18);
449 unicode += ((string[3] & 0x3f) << 12);
450 unicode += ((string[4] & 0x3f) << 6);
451 unicode += (string[5] & 0x3f);
452 break;
453 default:
454 return 0;
457 /* Invalid characters could return the special 0xfffd value but NUL
458 * should be just as good. */
459 return unicode > 0xffff ? 0 : unicode;
462 /* Calculates how much of string can be shown within the given maximum width
463 * and sets trimmed parameter to non-zero value if all of string could not be
464 * shown. If the reserve flag is TRUE, it will reserve at least one
465 * trailing character, which can be useful when drawing a delimiter.
467 * Returns the number of bytes to output from string to satisfy max_width. */
468 static size_t
469 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
471 const char *string = *start;
472 const char *end = strchr(string, '\0');
473 unsigned char last_bytes = 0;
474 size_t last_ucwidth = 0;
476 *width = 0;
477 *trimmed = 0;
479 while (string < end) {
480 unsigned char bytes = utf8_char_length(string, end);
481 size_t ucwidth;
482 unsigned long unicode;
484 if (string + bytes > end)
485 break;
487 /* Change representation to figure out whether
488 * it is a single- or double-width character. */
490 unicode = utf8_to_unicode(string, bytes);
491 /* FIXME: Graceful handling of invalid Unicode character. */
492 if (!unicode)
493 break;
495 ucwidth = unicode_width(unicode, tab_size);
496 if (skip > 0) {
497 skip -= ucwidth <= skip ? ucwidth : skip;
498 *start += bytes;
500 *width += ucwidth;
501 if (*width > max_width) {
502 *trimmed = 1;
503 *width -= ucwidth;
504 if (reserve && *width == max_width) {
505 string -= last_bytes;
506 *width -= last_ucwidth;
508 break;
511 string += bytes;
512 last_bytes = ucwidth ? bytes : 0;
513 last_ucwidth = ucwidth;
516 return string - *start;
520 #define DATE_INFO \
521 DATE_(NO), \
522 DATE_(DEFAULT), \
523 DATE_(RELATIVE), \
524 DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528 DATE_INFO
529 #undef DATE_
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534 DATE_INFO
535 #undef DATE_
538 struct time {
539 time_t sec;
540 int tz;
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545 return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551 static char buf[DATE_COLS + 1];
552 static const struct enum_map reldate[] = {
553 { "second", 1, 60 * 2 },
554 { "minute", 60, 60 * 60 * 2 },
555 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
556 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
557 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
558 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
560 struct tm tm;
562 if (!date || !time || !time->sec)
563 return "";
565 if (date == DATE_RELATIVE) {
566 struct timeval now;
567 time_t date = time->sec + time->tz;
568 time_t seconds;
569 int i;
571 gettimeofday(&now, NULL);
572 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574 if (seconds >= reldate[i].value)
575 continue;
577 seconds /= reldate[i].namelen;
578 if (!string_format(buf, "%ld %s%s %s",
579 seconds, reldate[i].name,
580 seconds > 1 ? "s" : "",
581 now.tv_sec >= date ? "ago" : "ahead"))
582 break;
583 return buf;
587 gmtime_r(&time->sec, &tm);
588 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593 AUTHOR_(NO), \
594 AUTHOR_(FULL), \
595 AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599 AUTHOR_VALUES,
600 #undef AUTHOR_
601 AUTHOR_DEFAULT = AUTHOR_FULL
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606 AUTHOR_VALUES
607 #undef AUTHOR_
610 static const char *
611 get_author_initials(const char *author)
613 static char initials[AUTHOR_COLS * 6 + 1];
614 size_t pos = 0;
615 const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619 memset(initials, 0, sizeof(initials));
620 while (author < end) {
621 unsigned char bytes;
622 size_t i;
624 while (is_initial_sep(*author))
625 author++;
627 bytes = utf8_char_length(author, end);
628 if (bytes < sizeof(initials) - 1 - pos) {
629 while (bytes--) {
630 initials[pos++] = *author++;
634 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635 if (i < sizeof(initials) - 1)
636 initials[i++] = *author;
639 initials[i++] = 0;
642 return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649 int valuelen;
651 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652 bool advance = cmd[valuelen] != 0;
654 cmd[valuelen] = 0;
655 argv[(*argc)++] = chomp_string(cmd);
656 cmd = chomp_string(cmd + valuelen + advance);
659 if (*argc < SIZEOF_ARG)
660 argv[*argc] = NULL;
661 return *argc < SIZEOF_ARG;
664 static void
665 argv_from_env(const char **argv, const char *name)
667 char *env = argv ? getenv(name) : NULL;
668 int argc = 0;
670 if (env && *env)
671 env = strdup(env);
672 if (env && !argv_from_string(argv, &argc, env))
673 die("Too many arguments in the `%s` environment variable", name);
678 * Executing external commands.
681 enum io_type {
682 IO_FD, /* File descriptor based IO. */
683 IO_BG, /* Execute command in the background. */
684 IO_FG, /* Execute command with same std{in,out,err}. */
685 IO_RD, /* Read only fork+exec IO. */
686 IO_WR, /* Write only fork+exec IO. */
687 IO_AP, /* Append fork+exec output to file. */
690 struct io {
691 enum io_type type; /* The requested type of pipe. */
692 const char *dir; /* Directory from which to execute. */
693 pid_t pid; /* PID of spawned process. */
694 int pipe; /* Pipe end for reading or writing. */
695 int error; /* Error status. */
696 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
697 char *buf; /* Read buffer. */
698 size_t bufalloc; /* Allocated buffer size. */
699 size_t bufsize; /* Buffer content size. */
700 char *bufpos; /* Current buffer position. */
701 unsigned int eof:1; /* Has end of file been reached. */
704 static void
705 io_reset(struct io *io)
707 io->pipe = -1;
708 io->pid = 0;
709 io->buf = io->bufpos = NULL;
710 io->bufalloc = io->bufsize = 0;
711 io->error = 0;
712 io->eof = 0;
715 static void
716 io_init(struct io *io, const char *dir, enum io_type type)
718 io_reset(io);
719 io->type = type;
720 io->dir = dir;
723 static bool
724 io_format(struct io *io, const char *dir, enum io_type type,
725 const char *argv[], enum format_flags flags)
727 io_init(io, dir, type);
728 return format_argv(io->argv, argv, flags);
731 static bool
732 io_open(struct io *io, const char *fmt, ...)
734 char name[SIZEOF_STR] = "";
735 bool fits;
736 va_list args;
738 io_init(io, NULL, IO_FD);
740 va_start(args, fmt);
741 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
742 va_end(args);
744 if (!fits) {
745 io->error = ENAMETOOLONG;
746 return FALSE;
748 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
749 if (io->pipe == -1)
750 io->error = errno;
751 return io->pipe != -1;
754 static bool
755 io_kill(struct io *io)
757 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
760 static bool
761 io_done(struct io *io)
763 pid_t pid = io->pid;
765 if (io->pipe != -1)
766 close(io->pipe);
767 free(io->buf);
768 io_reset(io);
770 while (pid > 0) {
771 int status;
772 pid_t waiting = waitpid(pid, &status, 0);
774 if (waiting < 0) {
775 if (errno == EINTR)
776 continue;
777 io->error = errno;
778 return FALSE;
781 return waiting == pid &&
782 !WIFSIGNALED(status) &&
783 WIFEXITED(status) &&
784 !WEXITSTATUS(status);
787 return TRUE;
790 static bool
791 io_start(struct io *io)
793 int pipefds[2] = { -1, -1 };
795 if (io->type == IO_FD)
796 return TRUE;
798 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
799 io->error = errno;
800 return FALSE;
801 } else if (io->type == IO_AP) {
802 pipefds[1] = io->pipe;
805 if ((io->pid = fork())) {
806 if (io->pid == -1)
807 io->error = errno;
808 if (pipefds[!(io->type == IO_WR)] != -1)
809 close(pipefds[!(io->type == IO_WR)]);
810 if (io->pid != -1) {
811 io->pipe = pipefds[!!(io->type == IO_WR)];
812 return TRUE;
815 } else {
816 if (io->type != IO_FG) {
817 int devnull = open("/dev/null", O_RDWR);
818 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
819 int writefd = (io->type == IO_RD || io->type == IO_AP)
820 ? pipefds[1] : devnull;
822 dup2(readfd, STDIN_FILENO);
823 dup2(writefd, STDOUT_FILENO);
824 dup2(devnull, STDERR_FILENO);
826 close(devnull);
827 if (pipefds[0] != -1)
828 close(pipefds[0]);
829 if (pipefds[1] != -1)
830 close(pipefds[1]);
833 if (io->dir && *io->dir && chdir(io->dir) == -1)
834 exit(errno);
836 execvp(io->argv[0], (char *const*) io->argv);
837 exit(errno);
840 if (pipefds[!!(io->type == IO_WR)] != -1)
841 close(pipefds[!!(io->type == IO_WR)]);
842 return FALSE;
845 static bool
846 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
848 io_init(io, dir, type);
849 if (!format_argv(io->argv, argv, FORMAT_NONE))
850 return FALSE;
851 return io_start(io);
854 static int
855 io_complete(struct io *io)
857 return io_start(io) && io_done(io);
860 static int
861 io_run_bg(const char **argv)
863 struct io io = {};
865 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
866 return FALSE;
867 return io_complete(&io);
870 static bool
871 io_run_fg(const char **argv, const char *dir)
873 struct io io = {};
875 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
876 return FALSE;
877 return io_complete(&io);
880 static bool
881 io_run_append(const char **argv, enum format_flags flags, int fd)
883 struct io io = {};
885 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
886 close(fd);
887 return FALSE;
890 io.pipe = fd;
891 return io_complete(&io);
894 static bool
895 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
897 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
900 static bool
901 io_eof(struct io *io)
903 return io->eof;
906 static int
907 io_error(struct io *io)
909 return io->error;
912 static char *
913 io_strerror(struct io *io)
915 return strerror(io->error);
918 static bool
919 io_can_read(struct io *io)
921 struct timeval tv = { 0, 500 };
922 fd_set fds;
924 FD_ZERO(&fds);
925 FD_SET(io->pipe, &fds);
927 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
933 do {
934 ssize_t readsize = read(io->pipe, buf, bufsize);
936 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937 continue;
938 else if (readsize == -1)
939 io->error = errno;
940 else if (readsize == 0)
941 io->eof = 1;
942 return readsize;
943 } while (1);
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
951 char *eol;
952 ssize_t readsize;
954 while (TRUE) {
955 if (io->bufsize > 0) {
956 eol = memchr(io->bufpos, c, io->bufsize);
957 if (eol) {
958 char *line = io->bufpos;
960 *eol = 0;
961 io->bufpos = eol + 1;
962 io->bufsize -= io->bufpos - line;
963 return line;
967 if (io_eof(io)) {
968 if (io->bufsize) {
969 io->bufpos[io->bufsize] = 0;
970 io->bufsize = 0;
971 return io->bufpos;
973 return NULL;
976 if (!can_read)
977 return NULL;
979 if (io->bufsize > 0 && io->bufpos > io->buf)
980 memmove(io->buf, io->bufpos, io->bufsize);
982 if (io->bufalloc == io->bufsize) {
983 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984 return NULL;
985 io->bufalloc += BUFSIZ;
988 io->bufpos = io->buf;
989 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990 if (io_error(io))
991 return NULL;
992 io->bufsize += readsize;
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
999 size_t written = 0;
1001 while (!io_error(io) && written < bufsize) {
1002 ssize_t size;
1004 size = write(io->pipe, buf + written, bufsize - written);
1005 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006 continue;
1007 else if (size == -1)
1008 io->error = errno;
1009 else
1010 written += size;
1013 return written == bufsize;
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1019 char *result = io_get(io, '\n', TRUE);
1021 if (result) {
1022 result = chomp_string(result);
1023 string_ncopy_do(buf, bufsize, result, strlen(result));
1026 return io_done(io) && result;
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1032 struct io io = {};
1034 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1035 && io_read_buf(&io, buf, bufsize);
1038 static int
1039 io_load(struct io *io, const char *separators,
1040 int (*read_property)(char *, size_t, char *, size_t))
1042 char *name;
1043 int state = OK;
1045 if (!io_start(io))
1046 return ERR;
1048 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1049 char *value;
1050 size_t namelen;
1051 size_t valuelen;
1053 name = chomp_string(name);
1054 namelen = strcspn(name, separators);
1056 if (name[namelen]) {
1057 name[namelen] = 0;
1058 value = chomp_string(name + namelen + 1);
1059 valuelen = strlen(value);
1061 } else {
1062 value = "";
1063 valuelen = 0;
1066 state = read_property(name, namelen, value, valuelen);
1069 if (state != ERR && io_error(io))
1070 state = ERR;
1071 io_done(io);
1073 return state;
1076 static int
1077 io_run_load(const char **argv, const char *separators,
1078 int (*read_property)(char *, size_t, char *, size_t))
1080 struct io io = {};
1082 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1083 ? io_load(&io, separators, read_property) : ERR;
1088 * User requests
1091 #define REQ_INFO \
1092 /* XXX: Keep the view request first and in sync with views[]. */ \
1093 REQ_GROUP("View switching") \
1094 REQ_(VIEW_MAIN, "Show main view"), \
1095 REQ_(VIEW_DIFF, "Show diff view"), \
1096 REQ_(VIEW_LOG, "Show log view"), \
1097 REQ_(VIEW_TREE, "Show tree view"), \
1098 REQ_(VIEW_BLOB, "Show blob view"), \
1099 REQ_(VIEW_BLAME, "Show blame view"), \
1100 REQ_(VIEW_BRANCH, "Show branch view"), \
1101 REQ_(VIEW_HELP, "Show help page"), \
1102 REQ_(VIEW_PAGER, "Show pager view"), \
1103 REQ_(VIEW_STATUS, "Show status view"), \
1104 REQ_(VIEW_STAGE, "Show stage view"), \
1106 REQ_GROUP("View manipulation") \
1107 REQ_(ENTER, "Enter current line and scroll"), \
1108 REQ_(NEXT, "Move to next"), \
1109 REQ_(PREVIOUS, "Move to previous"), \
1110 REQ_(PARENT, "Move to parent"), \
1111 REQ_(VIEW_NEXT, "Move focus to next view"), \
1112 REQ_(REFRESH, "Reload and refresh"), \
1113 REQ_(MAXIMIZE, "Maximize the current view"), \
1114 REQ_(VIEW_CLOSE, "Close the current view"), \
1115 REQ_(QUIT, "Close all views and quit"), \
1117 REQ_GROUP("View specific requests") \
1118 REQ_(STATUS_UPDATE, "Update file status"), \
1119 REQ_(STATUS_REVERT, "Revert file changes"), \
1120 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1121 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1123 REQ_GROUP("Cursor navigation") \
1124 REQ_(MOVE_UP, "Move cursor one line up"), \
1125 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1126 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1127 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1128 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1129 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1131 REQ_GROUP("Scrolling") \
1132 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1133 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1134 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1135 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1136 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1137 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1139 REQ_GROUP("Searching") \
1140 REQ_(SEARCH, "Search the view"), \
1141 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1142 REQ_(FIND_NEXT, "Find next search match"), \
1143 REQ_(FIND_PREV, "Find previous search match"), \
1145 REQ_GROUP("Option manipulation") \
1146 REQ_(OPTIONS, "Open option menu"), \
1147 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1148 REQ_(TOGGLE_DATE, "Toggle date display"), \
1149 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1150 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1151 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1152 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1153 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1154 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1156 REQ_GROUP("Misc") \
1157 REQ_(PROMPT, "Bring up the prompt"), \
1158 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1159 REQ_(SHOW_VERSION, "Show version information"), \
1160 REQ_(STOP_LOADING, "Stop all loading views"), \
1161 REQ_(EDIT, "Open in editor"), \
1162 REQ_(NONE, "Do nothing")
1165 /* User action requests. */
1166 enum request {
1167 #define REQ_GROUP(help)
1168 #define REQ_(req, help) REQ_##req
1170 /* Offset all requests to avoid conflicts with ncurses getch values. */
1171 REQ_OFFSET = KEY_MAX + 1,
1172 REQ_INFO
1174 #undef REQ_GROUP
1175 #undef REQ_
1178 struct request_info {
1179 enum request request;
1180 const char *name;
1181 int namelen;
1182 const char *help;
1185 static const struct request_info req_info[] = {
1186 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1187 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1188 REQ_INFO
1189 #undef REQ_GROUP
1190 #undef REQ_
1193 static enum request
1194 get_request(const char *name)
1196 int namelen = strlen(name);
1197 int i;
1199 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1200 if (enum_equals(req_info[i], name, namelen))
1201 return req_info[i].request;
1203 return REQ_NONE;
1208 * Options
1211 /* Option and state variables. */
1212 static enum date opt_date = DATE_DEFAULT;
1213 static enum author opt_author = AUTHOR_DEFAULT;
1214 static bool opt_line_number = FALSE;
1215 static bool opt_line_graphics = TRUE;
1216 static bool opt_rev_graph = FALSE;
1217 static bool opt_show_refs = TRUE;
1218 static int opt_num_interval = 5;
1219 static double opt_hscroll = 0.50;
1220 static double opt_scale_split_view = 2.0 / 3.0;
1221 static int opt_tab_size = 8;
1222 static int opt_author_cols = AUTHOR_COLS;
1223 static char opt_path[SIZEOF_STR] = "";
1224 static char opt_file[SIZEOF_STR] = "";
1225 static char opt_ref[SIZEOF_REF] = "";
1226 static char opt_head[SIZEOF_REF] = "";
1227 static char opt_remote[SIZEOF_REF] = "";
1228 static char opt_encoding[20] = "UTF-8";
1229 static iconv_t opt_iconv_in = ICONV_NONE;
1230 static iconv_t opt_iconv_out = ICONV_NONE;
1231 static char opt_search[SIZEOF_STR] = "";
1232 static char opt_cdup[SIZEOF_STR] = "";
1233 static char opt_prefix[SIZEOF_STR] = "";
1234 static char opt_git_dir[SIZEOF_STR] = "";
1235 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1236 static char opt_editor[SIZEOF_STR] = "";
1237 static FILE *opt_tty = NULL;
1239 #define is_initial_commit() (!get_ref_head())
1240 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1244 * Line-oriented content detection.
1247 #define LINE_INFO \
1248 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1262 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1263 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1264 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1265 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1269 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1270 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1273 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1277 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1278 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1279 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1280 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1281 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1282 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1283 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1284 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1285 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1286 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1287 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1291 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1292 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1293 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1294 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1295 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1296 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1297 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1298 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1299 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1303 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1304 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1306 enum line_type {
1307 #define LINE(type, line, fg, bg, attr) \
1308 LINE_##type
1309 LINE_INFO,
1310 LINE_NONE
1311 #undef LINE
1314 struct line_info {
1315 const char *name; /* Option name. */
1316 int namelen; /* Size of option name. */
1317 const char *line; /* The start of line to match. */
1318 int linelen; /* Size of string to match. */
1319 int fg, bg, attr; /* Color and text attributes for the lines. */
1322 static struct line_info line_info[] = {
1323 #define LINE(type, line, fg, bg, attr) \
1324 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1325 LINE_INFO
1326 #undef LINE
1329 static enum line_type
1330 get_line_type(const char *line)
1332 int linelen = strlen(line);
1333 enum line_type type;
1335 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1336 /* Case insensitive search matches Signed-off-by lines better. */
1337 if (linelen >= line_info[type].linelen &&
1338 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1339 return type;
1341 return LINE_DEFAULT;
1344 static inline int
1345 get_line_attr(enum line_type type)
1347 assert(type < ARRAY_SIZE(line_info));
1348 return COLOR_PAIR(type) | line_info[type].attr;
1351 static struct line_info *
1352 get_line_info(const char *name)
1354 size_t namelen = strlen(name);
1355 enum line_type type;
1357 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1358 if (enum_equals(line_info[type], name, namelen))
1359 return &line_info[type];
1361 return NULL;
1364 static void
1365 init_colors(void)
1367 int default_bg = line_info[LINE_DEFAULT].bg;
1368 int default_fg = line_info[LINE_DEFAULT].fg;
1369 enum line_type type;
1371 start_color();
1373 if (assume_default_colors(default_fg, default_bg) == ERR) {
1374 default_bg = COLOR_BLACK;
1375 default_fg = COLOR_WHITE;
1378 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1379 struct line_info *info = &line_info[type];
1380 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1381 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1383 init_pair(type, fg, bg);
1387 struct line {
1388 enum line_type type;
1390 /* State flags */
1391 unsigned int selected:1;
1392 unsigned int dirty:1;
1393 unsigned int cleareol:1;
1394 unsigned int other:16;
1396 void *data; /* User data */
1401 * Keys
1404 struct keybinding {
1405 int alias;
1406 enum request request;
1409 static const struct keybinding default_keybindings[] = {
1410 /* View switching */
1411 { 'm', REQ_VIEW_MAIN },
1412 { 'd', REQ_VIEW_DIFF },
1413 { 'l', REQ_VIEW_LOG },
1414 { 't', REQ_VIEW_TREE },
1415 { 'f', REQ_VIEW_BLOB },
1416 { 'B', REQ_VIEW_BLAME },
1417 { 'H', REQ_VIEW_BRANCH },
1418 { 'p', REQ_VIEW_PAGER },
1419 { 'h', REQ_VIEW_HELP },
1420 { 'S', REQ_VIEW_STATUS },
1421 { 'c', REQ_VIEW_STAGE },
1423 /* View manipulation */
1424 { 'q', REQ_VIEW_CLOSE },
1425 { KEY_TAB, REQ_VIEW_NEXT },
1426 { KEY_RETURN, REQ_ENTER },
1427 { KEY_UP, REQ_PREVIOUS },
1428 { KEY_DOWN, REQ_NEXT },
1429 { 'R', REQ_REFRESH },
1430 { KEY_F(5), REQ_REFRESH },
1431 { 'O', REQ_MAXIMIZE },
1433 /* Cursor navigation */
1434 { 'k', REQ_MOVE_UP },
1435 { 'j', REQ_MOVE_DOWN },
1436 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1437 { KEY_END, REQ_MOVE_LAST_LINE },
1438 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1439 { ' ', REQ_MOVE_PAGE_DOWN },
1440 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1441 { 'b', REQ_MOVE_PAGE_UP },
1442 { '-', REQ_MOVE_PAGE_UP },
1444 /* Scrolling */
1445 { KEY_LEFT, REQ_SCROLL_LEFT },
1446 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1447 { KEY_IC, REQ_SCROLL_LINE_UP },
1448 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1449 { 'w', REQ_SCROLL_PAGE_UP },
1450 { 's', REQ_SCROLL_PAGE_DOWN },
1452 /* Searching */
1453 { '/', REQ_SEARCH },
1454 { '?', REQ_SEARCH_BACK },
1455 { 'n', REQ_FIND_NEXT },
1456 { 'N', REQ_FIND_PREV },
1458 /* Misc */
1459 { 'Q', REQ_QUIT },
1460 { 'z', REQ_STOP_LOADING },
1461 { 'v', REQ_SHOW_VERSION },
1462 { 'r', REQ_SCREEN_REDRAW },
1463 { 'o', REQ_OPTIONS },
1464 { '.', REQ_TOGGLE_LINENO },
1465 { 'D', REQ_TOGGLE_DATE },
1466 { 'A', REQ_TOGGLE_AUTHOR },
1467 { 'g', REQ_TOGGLE_REV_GRAPH },
1468 { 'F', REQ_TOGGLE_REFS },
1469 { 'I', REQ_TOGGLE_SORT_ORDER },
1470 { 'i', REQ_TOGGLE_SORT_FIELD },
1471 { ':', REQ_PROMPT },
1472 { 'u', REQ_STATUS_UPDATE },
1473 { '!', REQ_STATUS_REVERT },
1474 { 'M', REQ_STATUS_MERGE },
1475 { '@', REQ_STAGE_NEXT },
1476 { ',', REQ_PARENT },
1477 { 'e', REQ_EDIT },
1480 #define KEYMAP_INFO \
1481 KEYMAP_(GENERIC), \
1482 KEYMAP_(MAIN), \
1483 KEYMAP_(DIFF), \
1484 KEYMAP_(LOG), \
1485 KEYMAP_(TREE), \
1486 KEYMAP_(BLOB), \
1487 KEYMAP_(BLAME), \
1488 KEYMAP_(BRANCH), \
1489 KEYMAP_(PAGER), \
1490 KEYMAP_(HELP), \
1491 KEYMAP_(STATUS), \
1492 KEYMAP_(STAGE)
1494 enum keymap {
1495 #define KEYMAP_(name) KEYMAP_##name
1496 KEYMAP_INFO
1497 #undef KEYMAP_
1500 static const struct enum_map keymap_table[] = {
1501 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1502 KEYMAP_INFO
1503 #undef KEYMAP_
1506 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1508 struct keybinding_table {
1509 struct keybinding *data;
1510 size_t size;
1513 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1515 static void
1516 add_keybinding(enum keymap keymap, enum request request, int key)
1518 struct keybinding_table *table = &keybindings[keymap];
1520 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1521 if (!table->data)
1522 die("Failed to allocate keybinding");
1523 table->data[table->size].alias = key;
1524 table->data[table->size++].request = request;
1527 /* Looks for a key binding first in the given map, then in the generic map, and
1528 * lastly in the default keybindings. */
1529 static enum request
1530 get_keybinding(enum keymap keymap, int key)
1532 size_t i;
1534 for (i = 0; i < keybindings[keymap].size; i++)
1535 if (keybindings[keymap].data[i].alias == key)
1536 return keybindings[keymap].data[i].request;
1538 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1539 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1540 return keybindings[KEYMAP_GENERIC].data[i].request;
1542 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1543 if (default_keybindings[i].alias == key)
1544 return default_keybindings[i].request;
1546 return (enum request) key;
1550 struct key {
1551 const char *name;
1552 int value;
1555 static const struct key key_table[] = {
1556 { "Enter", KEY_RETURN },
1557 { "Space", ' ' },
1558 { "Backspace", KEY_BACKSPACE },
1559 { "Tab", KEY_TAB },
1560 { "Escape", KEY_ESC },
1561 { "Left", KEY_LEFT },
1562 { "Right", KEY_RIGHT },
1563 { "Up", KEY_UP },
1564 { "Down", KEY_DOWN },
1565 { "Insert", KEY_IC },
1566 { "Delete", KEY_DC },
1567 { "Hash", '#' },
1568 { "Home", KEY_HOME },
1569 { "End", KEY_END },
1570 { "PageUp", KEY_PPAGE },
1571 { "PageDown", KEY_NPAGE },
1572 { "F1", KEY_F(1) },
1573 { "F2", KEY_F(2) },
1574 { "F3", KEY_F(3) },
1575 { "F4", KEY_F(4) },
1576 { "F5", KEY_F(5) },
1577 { "F6", KEY_F(6) },
1578 { "F7", KEY_F(7) },
1579 { "F8", KEY_F(8) },
1580 { "F9", KEY_F(9) },
1581 { "F10", KEY_F(10) },
1582 { "F11", KEY_F(11) },
1583 { "F12", KEY_F(12) },
1586 static int
1587 get_key_value(const char *name)
1589 int i;
1591 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1592 if (!strcasecmp(key_table[i].name, name))
1593 return key_table[i].value;
1595 if (strlen(name) == 1 && isprint(*name))
1596 return (int) *name;
1598 return ERR;
1601 static const char *
1602 get_key_name(int key_value)
1604 static char key_char[] = "'X'";
1605 const char *seq = NULL;
1606 int key;
1608 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1609 if (key_table[key].value == key_value)
1610 seq = key_table[key].name;
1612 if (seq == NULL &&
1613 key_value < 127 &&
1614 isprint(key_value)) {
1615 key_char[1] = (char) key_value;
1616 seq = key_char;
1619 return seq ? seq : "(no key)";
1622 static bool
1623 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1625 const char *sep = *pos > 0 ? ", " : "";
1626 const char *keyname = get_key_name(keybinding->alias);
1628 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1631 static bool
1632 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1633 enum keymap keymap, bool all)
1635 int i;
1637 for (i = 0; i < keybindings[keymap].size; i++) {
1638 if (keybindings[keymap].data[i].request == request) {
1639 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1640 return FALSE;
1641 if (!all)
1642 break;
1646 return TRUE;
1649 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1651 static const char *
1652 get_keys(enum keymap keymap, enum request request, bool all)
1654 static char buf[BUFSIZ];
1655 size_t pos = 0;
1656 int i;
1658 buf[pos] = 0;
1660 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1661 return "Too many keybindings!";
1662 if (pos > 0 && !all)
1663 return buf;
1665 if (keymap != KEYMAP_GENERIC) {
1666 /* Only the generic keymap includes the default keybindings when
1667 * listing all keys. */
1668 if (all)
1669 return buf;
1671 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1672 return "Too many keybindings!";
1673 if (pos)
1674 return buf;
1677 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1678 if (default_keybindings[i].request == request) {
1679 if (!append_key(buf, &pos, &default_keybindings[i]))
1680 return "Too many keybindings!";
1681 if (!all)
1682 return buf;
1686 return buf;
1689 struct run_request {
1690 enum keymap keymap;
1691 int key;
1692 const char *argv[SIZEOF_ARG];
1695 static struct run_request *run_request;
1696 static size_t run_requests;
1698 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1700 static enum request
1701 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1703 struct run_request *req;
1705 if (argc >= ARRAY_SIZE(req->argv) - 1)
1706 return REQ_NONE;
1708 if (!realloc_run_requests(&run_request, run_requests, 1))
1709 return REQ_NONE;
1711 req = &run_request[run_requests];
1712 req->keymap = keymap;
1713 req->key = key;
1714 req->argv[0] = NULL;
1716 if (!format_argv(req->argv, argv, FORMAT_NONE))
1717 return REQ_NONE;
1719 return REQ_NONE + ++run_requests;
1722 static struct run_request *
1723 get_run_request(enum request request)
1725 if (request <= REQ_NONE)
1726 return NULL;
1727 return &run_request[request - REQ_NONE - 1];
1730 static void
1731 add_builtin_run_requests(void)
1733 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1734 const char *commit[] = { "git", "commit", NULL };
1735 const char *gc[] = { "git", "gc", NULL };
1736 struct {
1737 enum keymap keymap;
1738 int key;
1739 int argc;
1740 const char **argv;
1741 } reqs[] = {
1742 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1743 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1744 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1746 int i;
1748 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749 enum request req;
1751 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752 if (req != REQ_NONE)
1753 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1758 * User config file handling.
1761 static int config_lineno;
1762 static bool config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767 COLOR_MAP(DEFAULT),
1768 COLOR_MAP(BLACK),
1769 COLOR_MAP(BLUE),
1770 COLOR_MAP(CYAN),
1771 COLOR_MAP(GREEN),
1772 COLOR_MAP(MAGENTA),
1773 COLOR_MAP(RED),
1774 COLOR_MAP(WHITE),
1775 COLOR_MAP(YELLOW),
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780 ATTR_MAP(NORMAL),
1781 ATTR_MAP(BLINK),
1782 ATTR_MAP(BOLD),
1783 ATTR_MAP(DIM),
1784 ATTR_MAP(REVERSE),
1785 ATTR_MAP(STANDOUT),
1786 ATTR_MAP(UNDERLINE),
1789 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1793 *opt = atoi(arg);
1794 if (!strchr(arg, '%'))
1795 return OK;
1797 /* "Shift down" so 100% and 1 does not conflict. */
1798 *opt = (*opt - 1) / 100;
1799 if (*opt >= 1.0) {
1800 *opt = 0.99;
1801 config_msg = "Step value larger than 100%";
1802 return ERR;
1804 if (*opt < 0.0) {
1805 *opt = 1;
1806 config_msg = "Invalid step value";
1807 return ERR;
1809 return OK;
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1815 int value = atoi(arg);
1817 if (min <= value && value <= max) {
1818 *opt = value;
1819 return OK;
1822 config_msg = "Integer value out of bound";
1823 return ERR;
1826 static bool
1827 set_color(int *color, const char *name)
1829 if (map_enum(color, color_map, name))
1830 return TRUE;
1831 if (!prefixcmp(name, "color"))
1832 return parse_int(color, name + 5, 0, 255) == OK;
1833 return FALSE;
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1840 struct line_info *info;
1842 if (argc < 3) {
1843 config_msg = "Wrong number of arguments given to color command";
1844 return ERR;
1847 info = get_line_info(argv[0]);
1848 if (!info) {
1849 static const struct enum_map obsolete[] = {
1850 ENUM_MAP("main-delim", LINE_DELIMITER),
1851 ENUM_MAP("main-date", LINE_DATE),
1852 ENUM_MAP("main-author", LINE_AUTHOR),
1854 int index;
1856 if (!map_enum(&index, obsolete, argv[0])) {
1857 config_msg = "Unknown color name";
1858 return ERR;
1860 info = &line_info[index];
1863 if (!set_color(&info->fg, argv[1]) ||
1864 !set_color(&info->bg, argv[2])) {
1865 config_msg = "Unknown color";
1866 return ERR;
1869 info->attr = 0;
1870 while (argc-- > 3) {
1871 int attr;
1873 if (!set_attribute(&attr, argv[argc])) {
1874 config_msg = "Unknown attribute";
1875 return ERR;
1877 info->attr |= attr;
1880 return OK;
1883 static int parse_bool(bool *opt, const char *arg)
1885 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886 ? TRUE : FALSE;
1887 return OK;
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891 const struct enum_map *map, size_t map_size)
1893 bool is_true;
1895 assert(map_size > 1);
1897 if (map_enum_do(map, map_size, (int *) opt, arg))
1898 return OK;
1900 if (parse_bool(&is_true, arg) != OK)
1901 return ERR;
1903 *opt = is_true ? map[1].value : map[0].value;
1904 return OK;
1907 #define parse_enum(opt, arg, map) \
1908 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1913 int arglen = strlen(arg);
1915 switch (arg[0]) {
1916 case '\"':
1917 case '\'':
1918 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919 config_msg = "Unmatched quotation";
1920 return ERR;
1922 arg += 1; arglen -= 2;
1923 default:
1924 string_ncopy_do(opt, optsize, arg, arglen);
1925 return OK;
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1933 if (argc != 3) {
1934 config_msg = "Wrong number of arguments given to set command";
1935 return ERR;
1938 if (strcmp(argv[1], "=")) {
1939 config_msg = "No value assigned";
1940 return ERR;
1943 if (!strcmp(argv[0], "show-author"))
1944 return parse_enum(&opt_author, argv[2], author_map);
1946 if (!strcmp(argv[0], "show-date"))
1947 return parse_enum(&opt_date, argv[2], date_map);
1949 if (!strcmp(argv[0], "show-rev-graph"))
1950 return parse_bool(&opt_rev_graph, argv[2]);
1952 if (!strcmp(argv[0], "show-refs"))
1953 return parse_bool(&opt_show_refs, argv[2]);
1955 if (!strcmp(argv[0], "show-line-numbers"))
1956 return parse_bool(&opt_line_number, argv[2]);
1958 if (!strcmp(argv[0], "line-graphics"))
1959 return parse_bool(&opt_line_graphics, argv[2]);
1961 if (!strcmp(argv[0], "line-number-interval"))
1962 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964 if (!strcmp(argv[0], "author-width"))
1965 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967 if (!strcmp(argv[0], "horizontal-scroll"))
1968 return parse_step(&opt_hscroll, argv[2]);
1970 if (!strcmp(argv[0], "split-view-height"))
1971 return parse_step(&opt_scale_split_view, argv[2]);
1973 if (!strcmp(argv[0], "tab-size"))
1974 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976 if (!strcmp(argv[0], "commit-encoding"))
1977 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979 config_msg = "Unknown variable name";
1980 return ERR;
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1987 enum request request;
1988 int keymap = -1;
1989 int key;
1991 if (argc < 3) {
1992 config_msg = "Wrong number of arguments given to bind command";
1993 return ERR;
1996 if (set_keymap(&keymap, argv[0]) == ERR) {
1997 config_msg = "Unknown key map";
1998 return ERR;
2001 key = get_key_value(argv[1]);
2002 if (key == ERR) {
2003 config_msg = "Unknown key";
2004 return ERR;
2007 request = get_request(argv[2]);
2008 if (request == REQ_NONE) {
2009 static const struct enum_map obsolete[] = {
2010 ENUM_MAP("cherry-pick", REQ_NONE),
2011 ENUM_MAP("screen-resize", REQ_NONE),
2012 ENUM_MAP("tree-parent", REQ_PARENT),
2014 int alias;
2016 if (map_enum(&alias, obsolete, argv[2])) {
2017 if (alias != REQ_NONE)
2018 add_keybinding(keymap, alias, key);
2019 config_msg = "Obsolete request name";
2020 return ERR;
2023 if (request == REQ_NONE && *argv[2]++ == '!')
2024 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025 if (request == REQ_NONE) {
2026 config_msg = "Unknown request name";
2027 return ERR;
2030 add_keybinding(keymap, request, key);
2032 return OK;
2035 static int
2036 set_option(const char *opt, char *value)
2038 const char *argv[SIZEOF_ARG];
2039 int argc = 0;
2041 if (!argv_from_string(argv, &argc, value)) {
2042 config_msg = "Too many option arguments";
2043 return ERR;
2046 if (!strcmp(opt, "color"))
2047 return option_color_command(argc, argv);
2049 if (!strcmp(opt, "set"))
2050 return option_set_command(argc, argv);
2052 if (!strcmp(opt, "bind"))
2053 return option_bind_command(argc, argv);
2055 config_msg = "Unknown option command";
2056 return ERR;
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062 int status = OK;
2064 config_lineno++;
2065 config_msg = "Internal error";
2067 /* Check for comment markers, since read_properties() will
2068 * only ensure opt and value are split at first " \t". */
2069 optlen = strcspn(opt, "#");
2070 if (optlen == 0)
2071 return OK;
2073 if (opt[optlen] != 0) {
2074 config_msg = "No option value";
2075 status = ERR;
2077 } else {
2078 /* Look for comment endings in the value. */
2079 size_t len = strcspn(value, "#");
2081 if (len < valuelen) {
2082 valuelen = len;
2083 value[valuelen] = 0;
2086 status = set_option(opt, value);
2089 if (status == ERR) {
2090 warn("Error on line %d, near '%.*s': %s",
2091 config_lineno, (int) optlen, opt, config_msg);
2092 config_errors = TRUE;
2095 /* Always keep going if errors are encountered. */
2096 return OK;
2099 static void
2100 load_option_file(const char *path)
2102 struct io io = {};
2104 /* It's OK that the file doesn't exist. */
2105 if (!io_open(&io, "%s", path))
2106 return;
2108 config_lineno = 0;
2109 config_errors = FALSE;
2111 if (io_load(&io, " \t", read_option) == ERR ||
2112 config_errors == TRUE)
2113 warn("Errors while loading %s.", path);
2116 static int
2117 load_options(void)
2119 const char *home = getenv("HOME");
2120 const char *tigrc_user = getenv("TIGRC_USER");
2121 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122 char buf[SIZEOF_STR];
2124 add_builtin_run_requests();
2126 if (!tigrc_system)
2127 tigrc_system = SYSCONFDIR "/tigrc";
2128 load_option_file(tigrc_system);
2130 if (!tigrc_user) {
2131 if (!home || !string_format(buf, "%s/.tigrc", home))
2132 return ERR;
2133 tigrc_user = buf;
2135 load_option_file(tigrc_user);
2137 return OK;
2142 * The viewer
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views() (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF] = "";
2159 static char ref_commit[SIZEOF_REF] = "HEAD";
2160 static char ref_head[SIZEOF_REF] = "HEAD";
2162 struct view {
2163 const char *name; /* View name */
2164 const char *cmd_env; /* Command line set via environment */
2165 const char *id; /* Points to either of ref_{head,commit,blob} */
2167 struct view_ops *ops; /* View operations */
2169 enum keymap keymap; /* What keymap does this view have */
2170 bool git_dir; /* Whether the view requires a git directory. */
2172 char ref[SIZEOF_REF]; /* Hovered commit reference */
2173 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2175 int height, width; /* The width and height of the main window */
2176 WINDOW *win; /* The main window */
2177 WINDOW *title; /* The title window living below the main window */
2179 /* Navigation */
2180 unsigned long offset; /* Offset of the window top */
2181 unsigned long yoffset; /* Offset from the window side. */
2182 unsigned long lineno; /* Current line number */
2183 unsigned long p_offset; /* Previous offset of the window top */
2184 unsigned long p_yoffset;/* Previous offset from the window side */
2185 unsigned long p_lineno; /* Previous current line number */
2186 bool p_restore; /* Should the previous position be restored. */
2188 /* Searching */
2189 char grep[SIZEOF_STR]; /* Search string */
2190 regex_t *regex; /* Pre-compiled regexp */
2192 /* If non-NULL, points to the view that opened this view. If this view
2193 * is closed tig will switch back to the parent view. */
2194 struct view *parent;
2196 /* Buffering */
2197 size_t lines; /* Total number of lines */
2198 struct line *line; /* Line index */
2199 unsigned int digits; /* Number of digits in the lines member. */
2201 /* Drawing */
2202 struct line *curline; /* Line currently being drawn. */
2203 enum line_type curtype; /* Attribute currently used for drawing. */
2204 unsigned long col; /* Column when drawing. */
2205 bool has_scrolled; /* View was scrolled. */
2207 /* Loading */
2208 struct io io;
2209 struct io *pipe;
2210 time_t start_time;
2211 time_t update_secs;
2214 struct view_ops {
2215 /* What type of content being displayed. Used in the title bar. */
2216 const char *type;
2217 /* Default command arguments. */
2218 const char **argv;
2219 /* Open and reads in all view content. */
2220 bool (*open)(struct view *view);
2221 /* Read one line; updates view->line. */
2222 bool (*read)(struct view *view, char *data);
2223 /* Draw one line; @lineno must be < view->height. */
2224 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2225 /* Depending on view handle a special requests. */
2226 enum request (*request)(struct view *view, enum request request, struct line *line);
2227 /* Search for regexp in a line. */
2228 bool (*grep)(struct view *view, struct line *line);
2229 /* Select line */
2230 void (*select)(struct view *view, struct line *line);
2231 /* Prepare view for loading */
2232 bool (*prepare)(struct view *view);
2235 static struct view_ops blame_ops;
2236 static struct view_ops blob_ops;
2237 static struct view_ops diff_ops;
2238 static struct view_ops help_ops;
2239 static struct view_ops log_ops;
2240 static struct view_ops main_ops;
2241 static struct view_ops pager_ops;
2242 static struct view_ops stage_ops;
2243 static struct view_ops status_ops;
2244 static struct view_ops tree_ops;
2245 static struct view_ops branch_ops;
2247 #define VIEW_STR(name, env, ref, ops, map, git) \
2248 { name, #env, ref, ops, map, git }
2250 #define VIEW_(id, name, ops, git, ref) \
2251 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2254 static struct view views[] = {
2255 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2256 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2257 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2258 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2259 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2260 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2261 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2262 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2263 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2264 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2265 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2268 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2269 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2271 #define foreach_view(view, i) \
2272 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2274 #define view_is_displayed(view) \
2275 (view == display[0] || view == display[1])
2278 static inline void
2279 set_view_attr(struct view *view, enum line_type type)
2281 if (!view->curline->selected && view->curtype != type) {
2282 (void) wattrset(view->win, get_line_attr(type));
2283 wchgat(view->win, -1, 0, type, NULL);
2284 view->curtype = type;
2288 static int
2289 draw_chars(struct view *view, enum line_type type, const char *string,
2290 int max_len, bool use_tilde)
2292 static char out_buffer[BUFSIZ * 2];
2293 int len = 0;
2294 int col = 0;
2295 int trimmed = FALSE;
2296 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2298 if (max_len <= 0)
2299 return 0;
2301 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2303 set_view_attr(view, type);
2304 if (len > 0) {
2305 if (opt_iconv_out != ICONV_NONE) {
2306 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2307 size_t inlen = len + 1;
2309 char *outbuf = out_buffer;
2310 size_t outlen = sizeof(out_buffer);
2312 size_t ret;
2314 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2315 if (ret != (size_t) -1) {
2316 string = out_buffer;
2317 len = sizeof(out_buffer) - outlen;
2321 waddnstr(view->win, string, len);
2323 if (trimmed && use_tilde) {
2324 set_view_attr(view, LINE_DELIMITER);
2325 waddch(view->win, '~');
2326 col++;
2329 return col;
2332 static int
2333 draw_space(struct view *view, enum line_type type, int max, int spaces)
2335 static char space[] = " ";
2336 int col = 0;
2338 spaces = MIN(max, spaces);
2340 while (spaces > 0) {
2341 int len = MIN(spaces, sizeof(space) - 1);
2343 col += draw_chars(view, type, space, len, FALSE);
2344 spaces -= len;
2347 return col;
2350 static bool
2351 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2353 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2354 return view->width + view->yoffset <= view->col;
2357 static bool
2358 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2360 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2361 int max = view->width + view->yoffset - view->col;
2362 int i;
2364 if (max < size)
2365 size = max;
2367 set_view_attr(view, type);
2368 /* Using waddch() instead of waddnstr() ensures that
2369 * they'll be rendered correctly for the cursor line. */
2370 for (i = skip; i < size; i++)
2371 waddch(view->win, graphic[i]);
2373 view->col += size;
2374 if (size < max && skip <= size)
2375 waddch(view->win, ' ');
2376 view->col++;
2378 return view->width + view->yoffset <= view->col;
2381 static bool
2382 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2384 int max = MIN(view->width + view->yoffset - view->col, len);
2385 int col;
2387 if (text)
2388 col = draw_chars(view, type, text, max - 1, trim);
2389 else
2390 col = draw_space(view, type, max - 1, max - 1);
2392 view->col += col;
2393 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2394 return view->width + view->yoffset <= view->col;
2397 static bool
2398 draw_date(struct view *view, struct time *time)
2400 const char *date = mkdate(time, opt_date);
2401 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2403 return draw_field(view, LINE_DATE, date, cols, FALSE);
2406 static bool
2407 draw_author(struct view *view, const char *author)
2409 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2410 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2412 if (abbreviate && author)
2413 author = get_author_initials(author);
2415 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2418 static bool
2419 draw_mode(struct view *view, mode_t mode)
2421 const char *str;
2423 if (S_ISDIR(mode))
2424 str = "drwxr-xr-x";
2425 else if (S_ISLNK(mode))
2426 str = "lrwxrwxrwx";
2427 else if (S_ISGITLINK(mode))
2428 str = "m---------";
2429 else if (S_ISREG(mode) && mode & S_IXUSR)
2430 str = "-rwxr-xr-x";
2431 else if (S_ISREG(mode))
2432 str = "-rw-r--r--";
2433 else
2434 str = "----------";
2436 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2439 static bool
2440 draw_lineno(struct view *view, unsigned int lineno)
2442 char number[10];
2443 int digits3 = view->digits < 3 ? 3 : view->digits;
2444 int max = MIN(view->width + view->yoffset - view->col, digits3);
2445 char *text = NULL;
2446 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2448 lineno += view->offset + 1;
2449 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2450 static char fmt[] = "%1ld";
2452 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2453 if (string_format(number, fmt, lineno))
2454 text = number;
2456 if (text)
2457 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2458 else
2459 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2460 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2463 static bool
2464 draw_view_line(struct view *view, unsigned int lineno)
2466 struct line *line;
2467 bool selected = (view->offset + lineno == view->lineno);
2469 assert(view_is_displayed(view));
2471 if (view->offset + lineno >= view->lines)
2472 return FALSE;
2474 line = &view->line[view->offset + lineno];
2476 wmove(view->win, lineno, 0);
2477 if (line->cleareol)
2478 wclrtoeol(view->win);
2479 view->col = 0;
2480 view->curline = line;
2481 view->curtype = LINE_NONE;
2482 line->selected = FALSE;
2483 line->dirty = line->cleareol = 0;
2485 if (selected) {
2486 set_view_attr(view, LINE_CURSOR);
2487 line->selected = TRUE;
2488 view->ops->select(view, line);
2491 return view->ops->draw(view, line, lineno);
2494 static void
2495 redraw_view_dirty(struct view *view)
2497 bool dirty = FALSE;
2498 int lineno;
2500 for (lineno = 0; lineno < view->height; lineno++) {
2501 if (view->offset + lineno >= view->lines)
2502 break;
2503 if (!view->line[view->offset + lineno].dirty)
2504 continue;
2505 dirty = TRUE;
2506 if (!draw_view_line(view, lineno))
2507 break;
2510 if (!dirty)
2511 return;
2512 wnoutrefresh(view->win);
2515 static void
2516 redraw_view_from(struct view *view, int lineno)
2518 assert(0 <= lineno && lineno < view->height);
2520 for (; lineno < view->height; lineno++) {
2521 if (!draw_view_line(view, lineno))
2522 break;
2525 wnoutrefresh(view->win);
2528 static void
2529 redraw_view(struct view *view)
2531 werase(view->win);
2532 redraw_view_from(view, 0);
2536 static void
2537 update_view_title(struct view *view)
2539 char buf[SIZEOF_STR];
2540 char state[SIZEOF_STR];
2541 size_t bufpos = 0, statelen = 0;
2543 assert(view_is_displayed(view));
2545 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2546 unsigned int view_lines = view->offset + view->height;
2547 unsigned int lines = view->lines
2548 ? MIN(view_lines, view->lines) * 100 / view->lines
2549 : 0;
2551 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2552 view->ops->type,
2553 view->lineno + 1,
2554 view->lines,
2555 lines);
2559 if (view->pipe) {
2560 time_t secs = time(NULL) - view->start_time;
2562 /* Three git seconds are a long time ... */
2563 if (secs > 2)
2564 string_format_from(state, &statelen, " loading %lds", secs);
2567 string_format_from(buf, &bufpos, "[%s]", view->name);
2568 if (*view->ref && bufpos < view->width) {
2569 size_t refsize = strlen(view->ref);
2570 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2572 if (minsize < view->width)
2573 refsize = view->width - minsize + 7;
2574 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2577 if (statelen && bufpos < view->width) {
2578 string_format_from(buf, &bufpos, "%s", state);
2581 if (view == display[current_view])
2582 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2583 else
2584 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2586 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2587 wclrtoeol(view->title);
2588 wnoutrefresh(view->title);
2591 static int
2592 apply_step(double step, int value)
2594 if (step >= 1)
2595 return (int) step;
2596 value *= step + 0.01;
2597 return value ? value : 1;
2600 static void
2601 resize_display(void)
2603 int offset, i;
2604 struct view *base = display[0];
2605 struct view *view = display[1] ? display[1] : display[0];
2607 /* Setup window dimensions */
2609 getmaxyx(stdscr, base->height, base->width);
2611 /* Make room for the status window. */
2612 base->height -= 1;
2614 if (view != base) {
2615 /* Horizontal split. */
2616 view->width = base->width;
2617 view->height = apply_step(opt_scale_split_view, base->height);
2618 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2619 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2620 base->height -= view->height;
2622 /* Make room for the title bar. */
2623 view->height -= 1;
2626 /* Make room for the title bar. */
2627 base->height -= 1;
2629 offset = 0;
2631 foreach_displayed_view (view, i) {
2632 if (!view->win) {
2633 view->win = newwin(view->height, 0, offset, 0);
2634 if (!view->win)
2635 die("Failed to create %s view", view->name);
2637 scrollok(view->win, FALSE);
2639 view->title = newwin(1, 0, offset + view->height, 0);
2640 if (!view->title)
2641 die("Failed to create title window");
2643 } else {
2644 wresize(view->win, view->height, view->width);
2645 mvwin(view->win, offset, 0);
2646 mvwin(view->title, offset + view->height, 0);
2649 offset += view->height + 1;
2653 static void
2654 redraw_display(bool clear)
2656 struct view *view;
2657 int i;
2659 foreach_displayed_view (view, i) {
2660 if (clear)
2661 wclear(view->win);
2662 redraw_view(view);
2663 update_view_title(view);
2667 static void
2668 toggle_enum_option_do(unsigned int *opt, const char *help,
2669 const struct enum_map *map, size_t size)
2671 *opt = (*opt + 1) % size;
2672 redraw_display(FALSE);
2673 report("Displaying %s %s", enum_name(map[*opt]), help);
2676 #define toggle_enum_option(opt, help, map) \
2677 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2679 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2680 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2682 static void
2683 toggle_view_option(bool *option, const char *help)
2685 *option = !*option;
2686 redraw_display(FALSE);
2687 report("%sabling %s", *option ? "En" : "Dis", help);
2690 static void
2691 open_option_menu(void)
2693 const struct menu_item menu[] = {
2694 { '.', "line numbers", &opt_line_number },
2695 { 'D', "date display", &opt_date },
2696 { 'A', "author display", &opt_author },
2697 { 'g', "revision graph display", &opt_rev_graph },
2698 { 'F', "reference display", &opt_show_refs },
2699 { 0 }
2701 int selected = 0;
2703 if (prompt_menu("Toggle option", menu, &selected)) {
2704 if (menu[selected].data == &opt_date)
2705 toggle_date();
2706 else if (menu[selected].data == &opt_author)
2707 toggle_author();
2708 else
2709 toggle_view_option(menu[selected].data, menu[selected].text);
2713 static void
2714 maximize_view(struct view *view)
2716 memset(display, 0, sizeof(display));
2717 current_view = 0;
2718 display[current_view] = view;
2719 resize_display();
2720 redraw_display(FALSE);
2721 report("");
2726 * Navigation
2729 static bool
2730 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2732 if (lineno >= view->lines)
2733 lineno = view->lines > 0 ? view->lines - 1 : 0;
2735 if (offset > lineno || offset + view->height <= lineno) {
2736 unsigned long half = view->height / 2;
2738 if (lineno > half)
2739 offset = lineno - half;
2740 else
2741 offset = 0;
2744 if (offset != view->offset || lineno != view->lineno) {
2745 view->offset = offset;
2746 view->lineno = lineno;
2747 return TRUE;
2750 return FALSE;
2753 /* Scrolling backend */
2754 static void
2755 do_scroll_view(struct view *view, int lines)
2757 bool redraw_current_line = FALSE;
2759 /* The rendering expects the new offset. */
2760 view->offset += lines;
2762 assert(0 <= view->offset && view->offset < view->lines);
2763 assert(lines);
2765 /* Move current line into the view. */
2766 if (view->lineno < view->offset) {
2767 view->lineno = view->offset;
2768 redraw_current_line = TRUE;
2769 } else if (view->lineno >= view->offset + view->height) {
2770 view->lineno = view->offset + view->height - 1;
2771 redraw_current_line = TRUE;
2774 assert(view->offset <= view->lineno && view->lineno < view->lines);
2776 /* Redraw the whole screen if scrolling is pointless. */
2777 if (view->height < ABS(lines)) {
2778 redraw_view(view);
2780 } else {
2781 int line = lines > 0 ? view->height - lines : 0;
2782 int end = line + ABS(lines);
2784 scrollok(view->win, TRUE);
2785 wscrl(view->win, lines);
2786 scrollok(view->win, FALSE);
2788 while (line < end && draw_view_line(view, line))
2789 line++;
2791 if (redraw_current_line)
2792 draw_view_line(view, view->lineno - view->offset);
2793 wnoutrefresh(view->win);
2796 view->has_scrolled = TRUE;
2797 report("");
2800 /* Scroll frontend */
2801 static void
2802 scroll_view(struct view *view, enum request request)
2804 int lines = 1;
2806 assert(view_is_displayed(view));
2808 switch (request) {
2809 case REQ_SCROLL_LEFT:
2810 if (view->yoffset == 0) {
2811 report("Cannot scroll beyond the first column");
2812 return;
2814 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2815 view->yoffset = 0;
2816 else
2817 view->yoffset -= apply_step(opt_hscroll, view->width);
2818 redraw_view_from(view, 0);
2819 report("");
2820 return;
2821 case REQ_SCROLL_RIGHT:
2822 view->yoffset += apply_step(opt_hscroll, view->width);
2823 redraw_view(view);
2824 report("");
2825 return;
2826 case REQ_SCROLL_PAGE_DOWN:
2827 lines = view->height;
2828 case REQ_SCROLL_LINE_DOWN:
2829 if (view->offset + lines > view->lines)
2830 lines = view->lines - view->offset;
2832 if (lines == 0 || view->offset + view->height >= view->lines) {
2833 report("Cannot scroll beyond the last line");
2834 return;
2836 break;
2838 case REQ_SCROLL_PAGE_UP:
2839 lines = view->height;
2840 case REQ_SCROLL_LINE_UP:
2841 if (lines > view->offset)
2842 lines = view->offset;
2844 if (lines == 0) {
2845 report("Cannot scroll beyond the first line");
2846 return;
2849 lines = -lines;
2850 break;
2852 default:
2853 die("request %d not handled in switch", request);
2856 do_scroll_view(view, lines);
2859 /* Cursor moving */
2860 static void
2861 move_view(struct view *view, enum request request)
2863 int scroll_steps = 0;
2864 int steps;
2866 switch (request) {
2867 case REQ_MOVE_FIRST_LINE:
2868 steps = -view->lineno;
2869 break;
2871 case REQ_MOVE_LAST_LINE:
2872 steps = view->lines - view->lineno - 1;
2873 break;
2875 case REQ_MOVE_PAGE_UP:
2876 steps = view->height > view->lineno
2877 ? -view->lineno : -view->height;
2878 break;
2880 case REQ_MOVE_PAGE_DOWN:
2881 steps = view->lineno + view->height >= view->lines
2882 ? view->lines - view->lineno - 1 : view->height;
2883 break;
2885 case REQ_MOVE_UP:
2886 steps = -1;
2887 break;
2889 case REQ_MOVE_DOWN:
2890 steps = 1;
2891 break;
2893 default:
2894 die("request %d not handled in switch", request);
2897 if (steps <= 0 && view->lineno == 0) {
2898 report("Cannot move beyond the first line");
2899 return;
2901 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2902 report("Cannot move beyond the last line");
2903 return;
2906 /* Move the current line */
2907 view->lineno += steps;
2908 assert(0 <= view->lineno && view->lineno < view->lines);
2910 /* Check whether the view needs to be scrolled */
2911 if (view->lineno < view->offset ||
2912 view->lineno >= view->offset + view->height) {
2913 scroll_steps = steps;
2914 if (steps < 0 && -steps > view->offset) {
2915 scroll_steps = -view->offset;
2917 } else if (steps > 0) {
2918 if (view->lineno == view->lines - 1 &&
2919 view->lines > view->height) {
2920 scroll_steps = view->lines - view->offset - 1;
2921 if (scroll_steps >= view->height)
2922 scroll_steps -= view->height - 1;
2927 if (!view_is_displayed(view)) {
2928 view->offset += scroll_steps;
2929 assert(0 <= view->offset && view->offset < view->lines);
2930 view->ops->select(view, &view->line[view->lineno]);
2931 return;
2934 /* Repaint the old "current" line if we be scrolling */
2935 if (ABS(steps) < view->height)
2936 draw_view_line(view, view->lineno - steps - view->offset);
2938 if (scroll_steps) {
2939 do_scroll_view(view, scroll_steps);
2940 return;
2943 /* Draw the current line */
2944 draw_view_line(view, view->lineno - view->offset);
2946 wnoutrefresh(view->win);
2947 report("");
2952 * Searching
2955 static void search_view(struct view *view, enum request request);
2957 static bool
2958 grep_text(struct view *view, const char *text[])
2960 regmatch_t pmatch;
2961 size_t i;
2963 for (i = 0; text[i]; i++)
2964 if (*text[i] &&
2965 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2966 return TRUE;
2967 return FALSE;
2970 static void
2971 select_view_line(struct view *view, unsigned long lineno)
2973 unsigned long old_lineno = view->lineno;
2974 unsigned long old_offset = view->offset;
2976 if (goto_view_line(view, view->offset, lineno)) {
2977 if (view_is_displayed(view)) {
2978 if (old_offset != view->offset) {
2979 redraw_view(view);
2980 } else {
2981 draw_view_line(view, old_lineno - view->offset);
2982 draw_view_line(view, view->lineno - view->offset);
2983 wnoutrefresh(view->win);
2985 } else {
2986 view->ops->select(view, &view->line[view->lineno]);
2991 static void
2992 find_next(struct view *view, enum request request)
2994 unsigned long lineno = view->lineno;
2995 int direction;
2997 if (!*view->grep) {
2998 if (!*opt_search)
2999 report("No previous search");
3000 else
3001 search_view(view, request);
3002 return;
3005 switch (request) {
3006 case REQ_SEARCH:
3007 case REQ_FIND_NEXT:
3008 direction = 1;
3009 break;
3011 case REQ_SEARCH_BACK:
3012 case REQ_FIND_PREV:
3013 direction = -1;
3014 break;
3016 default:
3017 return;
3020 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3021 lineno += direction;
3023 /* Note, lineno is unsigned long so will wrap around in which case it
3024 * will become bigger than view->lines. */
3025 for (; lineno < view->lines; lineno += direction) {
3026 if (view->ops->grep(view, &view->line[lineno])) {
3027 select_view_line(view, lineno);
3028 report("Line %ld matches '%s'", lineno + 1, view->grep);
3029 return;
3033 report("No match found for '%s'", view->grep);
3036 static void
3037 search_view(struct view *view, enum request request)
3039 int regex_err;
3041 if (view->regex) {
3042 regfree(view->regex);
3043 *view->grep = 0;
3044 } else {
3045 view->regex = calloc(1, sizeof(*view->regex));
3046 if (!view->regex)
3047 return;
3050 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3051 if (regex_err != 0) {
3052 char buf[SIZEOF_STR] = "unknown error";
3054 regerror(regex_err, view->regex, buf, sizeof(buf));
3055 report("Search failed: %s", buf);
3056 return;
3059 string_copy(view->grep, opt_search);
3061 find_next(view, request);
3065 * Incremental updating
3068 static void
3069 reset_view(struct view *view)
3071 int i;
3073 for (i = 0; i < view->lines; i++)
3074 free(view->line[i].data);
3075 free(view->line);
3077 view->p_offset = view->offset;
3078 view->p_yoffset = view->yoffset;
3079 view->p_lineno = view->lineno;
3081 view->line = NULL;
3082 view->offset = 0;
3083 view->yoffset = 0;
3084 view->lines = 0;
3085 view->lineno = 0;
3086 view->vid[0] = 0;
3087 view->update_secs = 0;
3090 static void
3091 free_argv(const char *argv[])
3093 int argc;
3095 for (argc = 0; argv[argc]; argc++)
3096 free((void *) argv[argc]);
3099 static const char *
3100 format_arg(const char *name)
3102 static struct {
3103 const char *name;
3104 size_t namelen;
3105 const char *value;
3106 const char *value_if_empty;
3107 } vars[] = {
3108 #define FORMAT_VAR(name, value, value_if_empty) \
3109 { name, STRING_SIZE(name), value, value_if_empty }
3110 FORMAT_VAR("%(directory)", opt_path, ""),
3111 FORMAT_VAR("%(file)", opt_file, ""),
3112 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3113 FORMAT_VAR("%(head)", ref_head, ""),
3114 FORMAT_VAR("%(commit)", ref_commit, ""),
3115 FORMAT_VAR("%(blob)", ref_blob, ""),
3117 int i;
3119 for (i = 0; i < ARRAY_SIZE(vars); i++)
3120 if (!strncmp(name, vars[i].name, vars[i].namelen))
3121 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3123 return NULL;
3126 static bool
3127 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3129 char buf[SIZEOF_STR];
3130 int argc;
3131 bool noreplace = flags == FORMAT_NONE;
3133 free_argv(dst_argv);
3135 for (argc = 0; src_argv[argc]; argc++) {
3136 const char *arg = src_argv[argc];
3137 size_t bufpos = 0;
3139 while (arg) {
3140 char *next = strstr(arg, "%(");
3141 int len = next - arg;
3142 const char *value;
3144 if (!next || noreplace) {
3145 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3146 noreplace = TRUE;
3147 len = strlen(arg);
3148 value = "";
3150 } else {
3151 value = format_arg(next);
3153 if (!value) {
3154 report("Unknown replacement: `%s`", next);
3155 return FALSE;
3159 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3160 return FALSE;
3162 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3165 dst_argv[argc] = strdup(buf);
3166 if (!dst_argv[argc])
3167 break;
3170 dst_argv[argc] = NULL;
3172 return src_argv[argc] == NULL;
3175 static bool
3176 restore_view_position(struct view *view)
3178 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3179 return FALSE;
3181 /* Changing the view position cancels the restoring. */
3182 /* FIXME: Changing back to the first line is not detected. */
3183 if (view->offset != 0 || view->lineno != 0) {
3184 view->p_restore = FALSE;
3185 return FALSE;
3188 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3189 view_is_displayed(view))
3190 werase(view->win);
3192 view->yoffset = view->p_yoffset;
3193 view->p_restore = FALSE;
3195 return TRUE;
3198 static void
3199 end_update(struct view *view, bool force)
3201 if (!view->pipe)
3202 return;
3203 while (!view->ops->read(view, NULL))
3204 if (!force)
3205 return;
3206 if (force)
3207 io_kill(view->pipe);
3208 io_done(view->pipe);
3209 view->pipe = NULL;
3212 static void
3213 setup_update(struct view *view, const char *vid)
3215 reset_view(view);
3216 string_copy_rev(view->vid, vid);
3217 view->pipe = &view->io;
3218 view->start_time = time(NULL);
3221 static bool
3222 prepare_update(struct view *view, const char *argv[], const char *dir,
3223 enum format_flags flags)
3225 if (view->pipe)
3226 end_update(view, TRUE);
3227 return io_format(&view->io, dir, IO_RD, argv, flags);
3230 static bool
3231 prepare_update_file(struct view *view, const char *name)
3233 if (view->pipe)
3234 end_update(view, TRUE);
3235 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3238 static bool
3239 begin_update(struct view *view, bool refresh)
3241 if (view->pipe)
3242 end_update(view, TRUE);
3244 if (!refresh) {
3245 if (view->ops->prepare) {
3246 if (!view->ops->prepare(view))
3247 return FALSE;
3248 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3249 return FALSE;
3252 /* Put the current ref_* value to the view title ref
3253 * member. This is needed by the blob view. Most other
3254 * views sets it automatically after loading because the
3255 * first line is a commit line. */
3256 string_copy_rev(view->ref, view->id);
3259 if (!io_start(&view->io))
3260 return FALSE;
3262 setup_update(view, view->id);
3264 return TRUE;
3267 static bool
3268 update_view(struct view *view)
3270 char out_buffer[BUFSIZ * 2];
3271 char *line;
3272 /* Clear the view and redraw everything since the tree sorting
3273 * might have rearranged things. */
3274 bool redraw = view->lines == 0;
3275 bool can_read = TRUE;
3277 if (!view->pipe)
3278 return TRUE;
3280 if (!io_can_read(view->pipe)) {
3281 if (view->lines == 0 && view_is_displayed(view)) {
3282 time_t secs = time(NULL) - view->start_time;
3284 if (secs > 1 && secs > view->update_secs) {
3285 if (view->update_secs == 0)
3286 redraw_view(view);
3287 update_view_title(view);
3288 view->update_secs = secs;
3291 return TRUE;
3294 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3295 if (opt_iconv_in != ICONV_NONE) {
3296 ICONV_CONST char *inbuf = line;
3297 size_t inlen = strlen(line) + 1;
3299 char *outbuf = out_buffer;
3300 size_t outlen = sizeof(out_buffer);
3302 size_t ret;
3304 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3305 if (ret != (size_t) -1)
3306 line = out_buffer;
3309 if (!view->ops->read(view, line)) {
3310 report("Allocation failure");
3311 end_update(view, TRUE);
3312 return FALSE;
3317 unsigned long lines = view->lines;
3318 int digits;
3320 for (digits = 0; lines; digits++)
3321 lines /= 10;
3323 /* Keep the displayed view in sync with line number scaling. */
3324 if (digits != view->digits) {
3325 view->digits = digits;
3326 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3327 redraw = TRUE;
3331 if (io_error(view->pipe)) {
3332 report("Failed to read: %s", io_strerror(view->pipe));
3333 end_update(view, TRUE);
3335 } else if (io_eof(view->pipe)) {
3336 report("");
3337 end_update(view, FALSE);
3340 if (restore_view_position(view))
3341 redraw = TRUE;
3343 if (!view_is_displayed(view))
3344 return TRUE;
3346 if (redraw)
3347 redraw_view_from(view, 0);
3348 else
3349 redraw_view_dirty(view);
3351 /* Update the title _after_ the redraw so that if the redraw picks up a
3352 * commit reference in view->ref it'll be available here. */
3353 update_view_title(view);
3354 return TRUE;
3357 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3359 static struct line *
3360 add_line_data(struct view *view, void *data, enum line_type type)
3362 struct line *line;
3364 if (!realloc_lines(&view->line, view->lines, 1))
3365 return NULL;
3367 line = &view->line[view->lines++];
3368 memset(line, 0, sizeof(*line));
3369 line->type = type;
3370 line->data = data;
3371 line->dirty = 1;
3373 return line;
3376 static struct line *
3377 add_line_text(struct view *view, const char *text, enum line_type type)
3379 char *data = text ? strdup(text) : NULL;
3381 return data ? add_line_data(view, data, type) : NULL;
3384 static struct line *
3385 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3387 char buf[SIZEOF_STR];
3388 va_list args;
3390 va_start(args, fmt);
3391 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3392 buf[0] = 0;
3393 va_end(args);
3395 return buf[0] ? add_line_text(view, buf, type) : NULL;
3399 * View opening
3402 enum open_flags {
3403 OPEN_DEFAULT = 0, /* Use default view switching. */
3404 OPEN_SPLIT = 1, /* Split current view. */
3405 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3406 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3407 OPEN_PREPARED = 32, /* Open already prepared command. */
3410 static void
3411 open_view(struct view *prev, enum request request, enum open_flags flags)
3413 bool split = !!(flags & OPEN_SPLIT);
3414 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3415 bool nomaximize = !!(flags & OPEN_REFRESH);
3416 struct view *view = VIEW(request);
3417 int nviews = displayed_views();
3418 struct view *base_view = display[0];
3420 if (view == prev && nviews == 1 && !reload) {
3421 report("Already in %s view", view->name);
3422 return;
3425 if (view->git_dir && !opt_git_dir[0]) {
3426 report("The %s view is disabled in pager view", view->name);
3427 return;
3430 if (split) {
3431 display[1] = view;
3432 current_view = 1;
3433 } else if (!nomaximize) {
3434 /* Maximize the current view. */
3435 memset(display, 0, sizeof(display));
3436 current_view = 0;
3437 display[current_view] = view;
3440 /* No parent signals that this is the first loaded view. */
3441 if (prev && view != prev) {
3442 view->parent = prev;
3445 /* Resize the view when switching between split- and full-screen,
3446 * or when switching between two different full-screen views. */
3447 if (nviews != displayed_views() ||
3448 (nviews == 1 && base_view != display[0]))
3449 resize_display();
3451 if (view->ops->open) {
3452 if (view->pipe)
3453 end_update(view, TRUE);
3454 if (!view->ops->open(view)) {
3455 report("Failed to load %s view", view->name);
3456 return;
3458 restore_view_position(view);
3460 } else if ((reload || strcmp(view->vid, view->id)) &&
3461 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3462 report("Failed to load %s view", view->name);
3463 return;
3466 if (split && prev->lineno - prev->offset >= prev->height) {
3467 /* Take the title line into account. */
3468 int lines = prev->lineno - prev->offset - prev->height + 1;
3470 /* Scroll the view that was split if the current line is
3471 * outside the new limited view. */
3472 do_scroll_view(prev, lines);
3475 if (prev && view != prev && split && view_is_displayed(prev)) {
3476 /* "Blur" the previous view. */
3477 update_view_title(prev);
3480 if (view->pipe && view->lines == 0) {
3481 /* Clear the old view and let the incremental updating refill
3482 * the screen. */
3483 werase(view->win);
3484 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3485 report("");
3486 } else if (view_is_displayed(view)) {
3487 redraw_view(view);
3488 report("");
3492 static void
3493 open_external_viewer(const char *argv[], const char *dir)
3495 def_prog_mode(); /* save current tty modes */
3496 endwin(); /* restore original tty modes */
3497 io_run_fg(argv, dir);
3498 fprintf(stderr, "Press Enter to continue");
3499 getc(opt_tty);
3500 reset_prog_mode();
3501 redraw_display(TRUE);
3504 static void
3505 open_mergetool(const char *file)
3507 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3509 open_external_viewer(mergetool_argv, opt_cdup);
3512 static void
3513 open_editor(const char *file)
3515 const char *editor_argv[] = { "vi", file, NULL };
3516 const char *editor;
3518 editor = getenv("GIT_EDITOR");
3519 if (!editor && *opt_editor)
3520 editor = opt_editor;
3521 if (!editor)
3522 editor = getenv("VISUAL");
3523 if (!editor)
3524 editor = getenv("EDITOR");
3525 if (!editor)
3526 editor = "vi";
3528 editor_argv[0] = editor;
3529 open_external_viewer(editor_argv, opt_cdup);
3532 static void
3533 open_run_request(enum request request)
3535 struct run_request *req = get_run_request(request);
3536 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3538 if (!req) {
3539 report("Unknown run request");
3540 return;
3543 if (format_argv(argv, req->argv, FORMAT_ALL))
3544 open_external_viewer(argv, NULL);
3545 free_argv(argv);
3549 * User request switch noodle
3552 static int
3553 view_driver(struct view *view, enum request request)
3555 int i;
3557 if (request == REQ_NONE)
3558 return TRUE;
3560 if (request > REQ_NONE) {
3561 open_run_request(request);
3562 /* FIXME: When all views can refresh always do this. */
3563 if (view == VIEW(REQ_VIEW_STATUS) ||
3564 view == VIEW(REQ_VIEW_MAIN) ||
3565 view == VIEW(REQ_VIEW_LOG) ||
3566 view == VIEW(REQ_VIEW_BRANCH) ||
3567 view == VIEW(REQ_VIEW_STAGE))
3568 request = REQ_REFRESH;
3569 else
3570 return TRUE;
3573 if (view && view->lines) {
3574 request = view->ops->request(view, request, &view->line[view->lineno]);
3575 if (request == REQ_NONE)
3576 return TRUE;
3579 switch (request) {
3580 case REQ_MOVE_UP:
3581 case REQ_MOVE_DOWN:
3582 case REQ_MOVE_PAGE_UP:
3583 case REQ_MOVE_PAGE_DOWN:
3584 case REQ_MOVE_FIRST_LINE:
3585 case REQ_MOVE_LAST_LINE:
3586 move_view(view, request);
3587 break;
3589 case REQ_SCROLL_LEFT:
3590 case REQ_SCROLL_RIGHT:
3591 case REQ_SCROLL_LINE_DOWN:
3592 case REQ_SCROLL_LINE_UP:
3593 case REQ_SCROLL_PAGE_DOWN:
3594 case REQ_SCROLL_PAGE_UP:
3595 scroll_view(view, request);
3596 break;
3598 case REQ_VIEW_BLAME:
3599 if (!opt_file[0]) {
3600 report("No file chosen, press %s to open tree view",
3601 get_key(view->keymap, REQ_VIEW_TREE));
3602 break;
3604 open_view(view, request, OPEN_DEFAULT);
3605 break;
3607 case REQ_VIEW_BLOB:
3608 if (!ref_blob[0]) {
3609 report("No file chosen, press %s to open tree view",
3610 get_key(view->keymap, REQ_VIEW_TREE));
3611 break;
3613 open_view(view, request, OPEN_DEFAULT);
3614 break;
3616 case REQ_VIEW_PAGER:
3617 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3618 report("No pager content, press %s to run command from prompt",
3619 get_key(view->keymap, REQ_PROMPT));
3620 break;
3622 open_view(view, request, OPEN_DEFAULT);
3623 break;
3625 case REQ_VIEW_STAGE:
3626 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3627 report("No stage content, press %s to open the status view and choose file",
3628 get_key(view->keymap, REQ_VIEW_STATUS));
3629 break;
3631 open_view(view, request, OPEN_DEFAULT);
3632 break;
3634 case REQ_VIEW_STATUS:
3635 if (opt_is_inside_work_tree == FALSE) {
3636 report("The status view requires a working tree");
3637 break;
3639 open_view(view, request, OPEN_DEFAULT);
3640 break;
3642 case REQ_VIEW_MAIN:
3643 case REQ_VIEW_DIFF:
3644 case REQ_VIEW_LOG:
3645 case REQ_VIEW_TREE:
3646 case REQ_VIEW_HELP:
3647 case REQ_VIEW_BRANCH:
3648 open_view(view, request, OPEN_DEFAULT);
3649 break;
3651 case REQ_NEXT:
3652 case REQ_PREVIOUS:
3653 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3655 if ((view == VIEW(REQ_VIEW_DIFF) &&
3656 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3657 (view == VIEW(REQ_VIEW_DIFF) &&
3658 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3659 (view == VIEW(REQ_VIEW_STAGE) &&
3660 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3661 (view == VIEW(REQ_VIEW_BLOB) &&
3662 view->parent == VIEW(REQ_VIEW_TREE)) ||
3663 (view == VIEW(REQ_VIEW_MAIN) &&
3664 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3665 int line;
3667 view = view->parent;
3668 line = view->lineno;
3669 move_view(view, request);
3670 if (view_is_displayed(view))
3671 update_view_title(view);
3672 if (line != view->lineno)
3673 view->ops->request(view, REQ_ENTER,
3674 &view->line[view->lineno]);
3676 } else {
3677 move_view(view, request);
3679 break;
3681 case REQ_VIEW_NEXT:
3683 int nviews = displayed_views();
3684 int next_view = (current_view + 1) % nviews;
3686 if (next_view == current_view) {
3687 report("Only one view is displayed");
3688 break;
3691 current_view = next_view;
3692 /* Blur out the title of the previous view. */
3693 update_view_title(view);
3694 report("");
3695 break;
3697 case REQ_REFRESH:
3698 report("Refreshing is not yet supported for the %s view", view->name);
3699 break;
3701 case REQ_MAXIMIZE:
3702 if (displayed_views() == 2)
3703 maximize_view(view);
3704 break;
3706 case REQ_OPTIONS:
3707 open_option_menu();
3708 break;
3710 case REQ_TOGGLE_LINENO:
3711 toggle_view_option(&opt_line_number, "line numbers");
3712 break;
3714 case REQ_TOGGLE_DATE:
3715 toggle_date();
3716 break;
3718 case REQ_TOGGLE_AUTHOR:
3719 toggle_author();
3720 break;
3722 case REQ_TOGGLE_REV_GRAPH:
3723 toggle_view_option(&opt_rev_graph, "revision graph display");
3724 break;
3726 case REQ_TOGGLE_REFS:
3727 toggle_view_option(&opt_show_refs, "reference display");
3728 break;
3730 case REQ_TOGGLE_SORT_FIELD:
3731 case REQ_TOGGLE_SORT_ORDER:
3732 report("Sorting is not yet supported for the %s view", view->name);
3733 break;
3735 case REQ_SEARCH:
3736 case REQ_SEARCH_BACK:
3737 search_view(view, request);
3738 break;
3740 case REQ_FIND_NEXT:
3741 case REQ_FIND_PREV:
3742 find_next(view, request);
3743 break;
3745 case REQ_STOP_LOADING:
3746 for (i = 0; i < ARRAY_SIZE(views); i++) {
3747 view = &views[i];
3748 if (view->pipe)
3749 report("Stopped loading the %s view", view->name),
3750 end_update(view, TRUE);
3752 break;
3754 case REQ_SHOW_VERSION:
3755 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3756 return TRUE;
3758 case REQ_SCREEN_REDRAW:
3759 redraw_display(TRUE);
3760 break;
3762 case REQ_EDIT:
3763 report("Nothing to edit");
3764 break;
3766 case REQ_ENTER:
3767 report("Nothing to enter");
3768 break;
3770 case REQ_VIEW_CLOSE:
3771 /* XXX: Mark closed views by letting view->parent point to the
3772 * view itself. Parents to closed view should never be
3773 * followed. */
3774 if (view->parent &&
3775 view->parent->parent != view->parent) {
3776 maximize_view(view->parent);
3777 view->parent = view;
3778 break;
3780 /* Fall-through */
3781 case REQ_QUIT:
3782 return FALSE;
3784 default:
3785 report("Unknown key, press %s for help",
3786 get_key(view->keymap, REQ_VIEW_HELP));
3787 return TRUE;
3790 return TRUE;
3795 * View backend utilities
3798 enum sort_field {
3799 ORDERBY_NAME,
3800 ORDERBY_DATE,
3801 ORDERBY_AUTHOR,
3804 struct sort_state {
3805 const enum sort_field *fields;
3806 size_t size, current;
3807 bool reverse;
3810 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3811 #define get_sort_field(state) ((state).fields[(state).current])
3812 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3814 static void
3815 sort_view(struct view *view, enum request request, struct sort_state *state,
3816 int (*compare)(const void *, const void *))
3818 switch (request) {
3819 case REQ_TOGGLE_SORT_FIELD:
3820 state->current = (state->current + 1) % state->size;
3821 break;
3823 case REQ_TOGGLE_SORT_ORDER:
3824 state->reverse = !state->reverse;
3825 break;
3826 default:
3827 die("Not a sort request");
3830 qsort(view->line, view->lines, sizeof(*view->line), compare);
3831 redraw_view(view);
3834 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3836 /* Small author cache to reduce memory consumption. It uses binary
3837 * search to lookup or find place to position new entries. No entries
3838 * are ever freed. */
3839 static const char *
3840 get_author(const char *name)
3842 static const char **authors;
3843 static size_t authors_size;
3844 int from = 0, to = authors_size - 1;
3846 while (from <= to) {
3847 size_t pos = (to + from) / 2;
3848 int cmp = strcmp(name, authors[pos]);
3850 if (!cmp)
3851 return authors[pos];
3853 if (cmp < 0)
3854 to = pos - 1;
3855 else
3856 from = pos + 1;
3859 if (!realloc_authors(&authors, authors_size, 1))
3860 return NULL;
3861 name = strdup(name);
3862 if (!name)
3863 return NULL;
3865 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3866 authors[from] = name;
3867 authors_size++;
3869 return name;
3872 static void
3873 parse_timesec(struct time *time, const char *sec)
3875 time->sec = (time_t) atol(sec);
3878 static void
3879 parse_timezone(struct time *time, const char *zone)
3881 long tz;
3883 tz = ('0' - zone[1]) * 60 * 60 * 10;
3884 tz += ('0' - zone[2]) * 60 * 60;
3885 tz += ('0' - zone[3]) * 60;
3886 tz += ('0' - zone[4]);
3888 if (zone[0] == '-')
3889 tz = -tz;
3891 time->tz = tz;
3892 time->sec -= tz;
3895 /* Parse author lines where the name may be empty:
3896 * author <email@address.tld> 1138474660 +0100
3898 static void
3899 parse_author_line(char *ident, const char **author, struct time *time)
3901 char *nameend = strchr(ident, '<');
3902 char *emailend = strchr(ident, '>');
3904 if (nameend && emailend)
3905 *nameend = *emailend = 0;
3906 ident = chomp_string(ident);
3907 if (!*ident) {
3908 if (nameend)
3909 ident = chomp_string(nameend + 1);
3910 if (!*ident)
3911 ident = "Unknown";
3914 *author = get_author(ident);
3916 /* Parse epoch and timezone */
3917 if (emailend && emailend[1] == ' ') {
3918 char *secs = emailend + 2;
3919 char *zone = strchr(secs, ' ');
3921 parse_timesec(time, secs);
3923 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3924 parse_timezone(time, zone + 1);
3928 static bool
3929 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3931 char rev[SIZEOF_REV];
3932 const char *revlist_argv[] = {
3933 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3935 struct menu_item *items;
3936 char text[SIZEOF_STR];
3937 bool ok = TRUE;
3938 int i;
3940 items = calloc(*parents + 1, sizeof(*items));
3941 if (!items)
3942 return FALSE;
3944 for (i = 0; i < *parents; i++) {
3945 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3946 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3947 !(items[i].text = strdup(text))) {
3948 ok = FALSE;
3949 break;
3953 if (ok) {
3954 *parents = 0;
3955 ok = prompt_menu("Select parent", items, parents);
3957 for (i = 0; items[i].text; i++)
3958 free((char *) items[i].text);
3959 free(items);
3960 return ok;
3963 static bool
3964 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3966 char buf[SIZEOF_STR * 4];
3967 const char *revlist_argv[] = {
3968 "git", "log", "--no-color", "-1",
3969 "--pretty=format:%P", id, "--", path, NULL
3971 int parents;
3973 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3974 (parents = strlen(buf) / 40) < 0) {
3975 report("Failed to get parent information");
3976 return FALSE;
3978 } else if (parents == 0) {
3979 if (path)
3980 report("Path '%s' does not exist in the parent", path);
3981 else
3982 report("The selected commit has no parents");
3983 return FALSE;
3986 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3987 return FALSE;
3989 string_copy_rev(rev, &buf[41 * parents]);
3990 return TRUE;
3994 * Pager backend
3997 static bool
3998 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4000 char text[SIZEOF_STR];
4002 if (opt_line_number && draw_lineno(view, lineno))
4003 return TRUE;
4005 string_expand(text, sizeof(text), line->data, opt_tab_size);
4006 draw_text(view, line->type, text, TRUE);
4007 return TRUE;
4010 static bool
4011 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4013 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4014 char ref[SIZEOF_STR];
4016 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4017 return TRUE;
4019 /* This is the only fatal call, since it can "corrupt" the buffer. */
4020 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4021 return FALSE;
4023 return TRUE;
4026 static void
4027 add_pager_refs(struct view *view, struct line *line)
4029 char buf[SIZEOF_STR];
4030 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4031 struct ref_list *list;
4032 size_t bufpos = 0, i;
4033 const char *sep = "Refs: ";
4034 bool is_tag = FALSE;
4036 assert(line->type == LINE_COMMIT);
4038 list = get_ref_list(commit_id);
4039 if (!list) {
4040 if (view == VIEW(REQ_VIEW_DIFF))
4041 goto try_add_describe_ref;
4042 return;
4045 for (i = 0; i < list->size; i++) {
4046 struct ref *ref = list->refs[i];
4047 const char *fmt = ref->tag ? "%s[%s]" :
4048 ref->remote ? "%s<%s>" : "%s%s";
4050 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4051 return;
4052 sep = ", ";
4053 if (ref->tag)
4054 is_tag = TRUE;
4057 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4058 try_add_describe_ref:
4059 /* Add <tag>-g<commit_id> "fake" reference. */
4060 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4061 return;
4064 if (bufpos == 0)
4065 return;
4067 add_line_text(view, buf, LINE_PP_REFS);
4070 static bool
4071 pager_read(struct view *view, char *data)
4073 struct line *line;
4075 if (!data)
4076 return TRUE;
4078 line = add_line_text(view, data, get_line_type(data));
4079 if (!line)
4080 return FALSE;
4082 if (line->type == LINE_COMMIT &&
4083 (view == VIEW(REQ_VIEW_DIFF) ||
4084 view == VIEW(REQ_VIEW_LOG)))
4085 add_pager_refs(view, line);
4087 return TRUE;
4090 static enum request
4091 pager_request(struct view *view, enum request request, struct line *line)
4093 int split = 0;
4095 if (request != REQ_ENTER)
4096 return request;
4098 if (line->type == LINE_COMMIT &&
4099 (view == VIEW(REQ_VIEW_LOG) ||
4100 view == VIEW(REQ_VIEW_PAGER))) {
4101 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4102 split = 1;
4105 /* Always scroll the view even if it was split. That way
4106 * you can use Enter to scroll through the log view and
4107 * split open each commit diff. */
4108 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4110 /* FIXME: A minor workaround. Scrolling the view will call report("")
4111 * but if we are scrolling a non-current view this won't properly
4112 * update the view title. */
4113 if (split)
4114 update_view_title(view);
4116 return REQ_NONE;
4119 static bool
4120 pager_grep(struct view *view, struct line *line)
4122 const char *text[] = { line->data, NULL };
4124 return grep_text(view, text);
4127 static void
4128 pager_select(struct view *view, struct line *line)
4130 if (line->type == LINE_COMMIT) {
4131 char *text = (char *)line->data + STRING_SIZE("commit ");
4133 if (view != VIEW(REQ_VIEW_PAGER))
4134 string_copy_rev(view->ref, text);
4135 string_copy_rev(ref_commit, text);
4139 static struct view_ops pager_ops = {
4140 "line",
4141 NULL,
4142 NULL,
4143 pager_read,
4144 pager_draw,
4145 pager_request,
4146 pager_grep,
4147 pager_select,
4150 static const char *log_argv[SIZEOF_ARG] = {
4151 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4154 static enum request
4155 log_request(struct view *view, enum request request, struct line *line)
4157 switch (request) {
4158 case REQ_REFRESH:
4159 load_refs();
4160 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4161 return REQ_NONE;
4162 default:
4163 return pager_request(view, request, line);
4167 static struct view_ops log_ops = {
4168 "line",
4169 log_argv,
4170 NULL,
4171 pager_read,
4172 pager_draw,
4173 log_request,
4174 pager_grep,
4175 pager_select,
4178 static const char *diff_argv[SIZEOF_ARG] = {
4179 "git", "show", "--pretty=fuller", "--no-color", "--root",
4180 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4183 static struct view_ops diff_ops = {
4184 "line",
4185 diff_argv,
4186 NULL,
4187 pager_read,
4188 pager_draw,
4189 pager_request,
4190 pager_grep,
4191 pager_select,
4195 * Help backend
4198 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4200 static bool
4201 help_open_keymap_title(struct view *view, enum keymap keymap)
4203 struct line *line;
4205 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4206 help_keymap_hidden[keymap] ? '+' : '-',
4207 enum_name(keymap_table[keymap]));
4208 if (line)
4209 line->other = keymap;
4211 return help_keymap_hidden[keymap];
4214 static void
4215 help_open_keymap(struct view *view, enum keymap keymap)
4217 const char *group = NULL;
4218 char buf[SIZEOF_STR];
4219 size_t bufpos;
4220 bool add_title = TRUE;
4221 int i;
4223 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4224 const char *key = NULL;
4226 if (req_info[i].request == REQ_NONE)
4227 continue;
4229 if (!req_info[i].request) {
4230 group = req_info[i].help;
4231 continue;
4234 key = get_keys(keymap, req_info[i].request, TRUE);
4235 if (!key || !*key)
4236 continue;
4238 if (add_title && help_open_keymap_title(view, keymap))
4239 return;
4240 add_title = FALSE;
4242 if (group) {
4243 add_line_text(view, group, LINE_HELP_GROUP);
4244 group = NULL;
4247 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4248 enum_name(req_info[i]), req_info[i].help);
4251 group = "External commands:";
4253 for (i = 0; i < run_requests; i++) {
4254 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4255 const char *key;
4256 int argc;
4258 if (!req || req->keymap != keymap)
4259 continue;
4261 key = get_key_name(req->key);
4262 if (!*key)
4263 key = "(no key defined)";
4265 if (add_title && help_open_keymap_title(view, keymap))
4266 return;
4267 if (group) {
4268 add_line_text(view, group, LINE_HELP_GROUP);
4269 group = NULL;
4272 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4273 if (!string_format_from(buf, &bufpos, "%s%s",
4274 argc ? " " : "", req->argv[argc]))
4275 return;
4277 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4281 static bool
4282 help_open(struct view *view)
4284 enum keymap keymap;
4286 reset_view(view);
4287 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4288 add_line_text(view, "", LINE_DEFAULT);
4290 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4291 help_open_keymap(view, keymap);
4293 return TRUE;
4296 static enum request
4297 help_request(struct view *view, enum request request, struct line *line)
4299 switch (request) {
4300 case REQ_ENTER:
4301 if (line->type == LINE_HELP_KEYMAP) {
4302 help_keymap_hidden[line->other] =
4303 !help_keymap_hidden[line->other];
4304 view->p_restore = TRUE;
4305 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4308 return REQ_NONE;
4309 default:
4310 return pager_request(view, request, line);
4314 static struct view_ops help_ops = {
4315 "line",
4316 NULL,
4317 help_open,
4318 NULL,
4319 pager_draw,
4320 help_request,
4321 pager_grep,
4322 pager_select,
4327 * Tree backend
4330 struct tree_stack_entry {
4331 struct tree_stack_entry *prev; /* Entry below this in the stack */
4332 unsigned long lineno; /* Line number to restore */
4333 char *name; /* Position of name in opt_path */
4336 /* The top of the path stack. */
4337 static struct tree_stack_entry *tree_stack = NULL;
4338 unsigned long tree_lineno = 0;
4340 static void
4341 pop_tree_stack_entry(void)
4343 struct tree_stack_entry *entry = tree_stack;
4345 tree_lineno = entry->lineno;
4346 entry->name[0] = 0;
4347 tree_stack = entry->prev;
4348 free(entry);
4351 static void
4352 push_tree_stack_entry(const char *name, unsigned long lineno)
4354 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4355 size_t pathlen = strlen(opt_path);
4357 if (!entry)
4358 return;
4360 entry->prev = tree_stack;
4361 entry->name = opt_path + pathlen;
4362 tree_stack = entry;
4364 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4365 pop_tree_stack_entry();
4366 return;
4369 /* Move the current line to the first tree entry. */
4370 tree_lineno = 1;
4371 entry->lineno = lineno;
4374 /* Parse output from git-ls-tree(1):
4376 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4379 #define SIZEOF_TREE_ATTR \
4380 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4382 #define SIZEOF_TREE_MODE \
4383 STRING_SIZE("100644 ")
4385 #define TREE_ID_OFFSET \
4386 STRING_SIZE("100644 blob ")
4388 struct tree_entry {
4389 char id[SIZEOF_REV];
4390 mode_t mode;
4391 struct time time; /* Date from the author ident. */
4392 const char *author; /* Author of the commit. */
4393 char name[1];
4396 static const char *
4397 tree_path(const struct line *line)
4399 return ((struct tree_entry *) line->data)->name;
4402 static int
4403 tree_compare_entry(const struct line *line1, const struct line *line2)
4405 if (line1->type != line2->type)
4406 return line1->type == LINE_TREE_DIR ? -1 : 1;
4407 return strcmp(tree_path(line1), tree_path(line2));
4410 static const enum sort_field tree_sort_fields[] = {
4411 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4413 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4415 static int
4416 tree_compare(const void *l1, const void *l2)
4418 const struct line *line1 = (const struct line *) l1;
4419 const struct line *line2 = (const struct line *) l2;
4420 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4421 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4423 if (line1->type == LINE_TREE_HEAD)
4424 return -1;
4425 if (line2->type == LINE_TREE_HEAD)
4426 return 1;
4428 switch (get_sort_field(tree_sort_state)) {
4429 case ORDERBY_DATE:
4430 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4432 case ORDERBY_AUTHOR:
4433 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4435 case ORDERBY_NAME:
4436 default:
4437 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4442 static struct line *
4443 tree_entry(struct view *view, enum line_type type, const char *path,
4444 const char *mode, const char *id)
4446 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4447 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4449 if (!entry || !line) {
4450 free(entry);
4451 return NULL;
4454 strncpy(entry->name, path, strlen(path));
4455 if (mode)
4456 entry->mode = strtoul(mode, NULL, 8);
4457 if (id)
4458 string_copy_rev(entry->id, id);
4460 return line;
4463 static bool
4464 tree_read_date(struct view *view, char *text, bool *read_date)
4466 static const char *author_name;
4467 static struct time author_time;
4469 if (!text && *read_date) {
4470 *read_date = FALSE;
4471 return TRUE;
4473 } else if (!text) {
4474 char *path = *opt_path ? opt_path : ".";
4475 /* Find next entry to process */
4476 const char *log_file[] = {
4477 "git", "log", "--no-color", "--pretty=raw",
4478 "--cc", "--raw", view->id, "--", path, NULL
4480 struct io io = {};
4482 if (!view->lines) {
4483 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4484 report("Tree is empty");
4485 return TRUE;
4488 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4489 report("Failed to load tree data");
4490 return TRUE;
4493 io_done(view->pipe);
4494 view->io = io;
4495 *read_date = TRUE;
4496 return FALSE;
4498 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4499 parse_author_line(text + STRING_SIZE("author "),
4500 &author_name, &author_time);
4502 } else if (*text == ':') {
4503 char *pos;
4504 size_t annotated = 1;
4505 size_t i;
4507 pos = strchr(text, '\t');
4508 if (!pos)
4509 return TRUE;
4510 text = pos + 1;
4511 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4512 text += strlen(opt_path);
4513 pos = strchr(text, '/');
4514 if (pos)
4515 *pos = 0;
4517 for (i = 1; i < view->lines; i++) {
4518 struct line *line = &view->line[i];
4519 struct tree_entry *entry = line->data;
4521 annotated += !!entry->author;
4522 if (entry->author || strcmp(entry->name, text))
4523 continue;
4525 entry->author = author_name;
4526 entry->time = author_time;
4527 line->dirty = 1;
4528 break;
4531 if (annotated == view->lines)
4532 io_kill(view->pipe);
4534 return TRUE;
4537 static bool
4538 tree_read(struct view *view, char *text)
4540 static bool read_date = FALSE;
4541 struct tree_entry *data;
4542 struct line *entry, *line;
4543 enum line_type type;
4544 size_t textlen = text ? strlen(text) : 0;
4545 char *path = text + SIZEOF_TREE_ATTR;
4547 if (read_date || !text)
4548 return tree_read_date(view, text, &read_date);
4550 if (textlen <= SIZEOF_TREE_ATTR)
4551 return FALSE;
4552 if (view->lines == 0 &&
4553 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4554 return FALSE;
4556 /* Strip the path part ... */
4557 if (*opt_path) {
4558 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4559 size_t striplen = strlen(opt_path);
4561 if (pathlen > striplen)
4562 memmove(path, path + striplen,
4563 pathlen - striplen + 1);
4565 /* Insert "link" to parent directory. */
4566 if (view->lines == 1 &&
4567 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4568 return FALSE;
4571 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4572 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4573 if (!entry)
4574 return FALSE;
4575 data = entry->data;
4577 /* Skip "Directory ..." and ".." line. */
4578 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4579 if (tree_compare_entry(line, entry) <= 0)
4580 continue;
4582 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4584 line->data = data;
4585 line->type = type;
4586 for (; line <= entry; line++)
4587 line->dirty = line->cleareol = 1;
4588 return TRUE;
4591 if (tree_lineno > view->lineno) {
4592 view->lineno = tree_lineno;
4593 tree_lineno = 0;
4596 return TRUE;
4599 static bool
4600 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4602 struct tree_entry *entry = line->data;
4604 if (line->type == LINE_TREE_HEAD) {
4605 if (draw_text(view, line->type, "Directory path /", TRUE))
4606 return TRUE;
4607 } else {
4608 if (draw_mode(view, entry->mode))
4609 return TRUE;
4611 if (opt_author && draw_author(view, entry->author))
4612 return TRUE;
4614 if (opt_date && draw_date(view, &entry->time))
4615 return TRUE;
4617 if (draw_text(view, line->type, entry->name, TRUE))
4618 return TRUE;
4619 return TRUE;
4622 static void
4623 open_blob_editor()
4625 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4626 int fd = mkstemp(file);
4628 if (fd == -1)
4629 report("Failed to create temporary file");
4630 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4631 report("Failed to save blob data to file");
4632 else
4633 open_editor(file);
4634 if (fd != -1)
4635 unlink(file);
4638 static enum request
4639 tree_request(struct view *view, enum request request, struct line *line)
4641 enum open_flags flags;
4643 switch (request) {
4644 case REQ_VIEW_BLAME:
4645 if (line->type != LINE_TREE_FILE) {
4646 report("Blame only supported for files");
4647 return REQ_NONE;
4650 string_copy(opt_ref, view->vid);
4651 return request;
4653 case REQ_EDIT:
4654 if (line->type != LINE_TREE_FILE) {
4655 report("Edit only supported for files");
4656 } else if (!is_head_commit(view->vid)) {
4657 open_blob_editor();
4658 } else {
4659 open_editor(opt_file);
4661 return REQ_NONE;
4663 case REQ_TOGGLE_SORT_FIELD:
4664 case REQ_TOGGLE_SORT_ORDER:
4665 sort_view(view, request, &tree_sort_state, tree_compare);
4666 return REQ_NONE;
4668 case REQ_PARENT:
4669 if (!*opt_path) {
4670 /* quit view if at top of tree */
4671 return REQ_VIEW_CLOSE;
4673 /* fake 'cd ..' */
4674 line = &view->line[1];
4675 break;
4677 case REQ_ENTER:
4678 break;
4680 default:
4681 return request;
4684 /* Cleanup the stack if the tree view is at a different tree. */
4685 while (!*opt_path && tree_stack)
4686 pop_tree_stack_entry();
4688 switch (line->type) {
4689 case LINE_TREE_DIR:
4690 /* Depending on whether it is a subdirectory or parent link
4691 * mangle the path buffer. */
4692 if (line == &view->line[1] && *opt_path) {
4693 pop_tree_stack_entry();
4695 } else {
4696 const char *basename = tree_path(line);
4698 push_tree_stack_entry(basename, view->lineno);
4701 /* Trees and subtrees share the same ID, so they are not not
4702 * unique like blobs. */
4703 flags = OPEN_RELOAD;
4704 request = REQ_VIEW_TREE;
4705 break;
4707 case LINE_TREE_FILE:
4708 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4709 request = REQ_VIEW_BLOB;
4710 break;
4712 default:
4713 return REQ_NONE;
4716 open_view(view, request, flags);
4717 if (request == REQ_VIEW_TREE)
4718 view->lineno = tree_lineno;
4720 return REQ_NONE;
4723 static bool
4724 tree_grep(struct view *view, struct line *line)
4726 struct tree_entry *entry = line->data;
4727 const char *text[] = {
4728 entry->name,
4729 opt_author ? entry->author : "",
4730 mkdate(&entry->time, opt_date),
4731 NULL
4734 return grep_text(view, text);
4737 static void
4738 tree_select(struct view *view, struct line *line)
4740 struct tree_entry *entry = line->data;
4742 if (line->type == LINE_TREE_FILE) {
4743 string_copy_rev(ref_blob, entry->id);
4744 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4746 } else if (line->type != LINE_TREE_DIR) {
4747 return;
4750 string_copy_rev(view->ref, entry->id);
4753 static bool
4754 tree_prepare(struct view *view)
4756 if (view->lines == 0 && opt_prefix[0]) {
4757 char *pos = opt_prefix;
4759 while (pos && *pos) {
4760 char *end = strchr(pos, '/');
4762 if (end)
4763 *end = 0;
4764 push_tree_stack_entry(pos, 0);
4765 pos = end;
4766 if (end) {
4767 *end = '/';
4768 pos++;
4772 } else if (strcmp(view->vid, view->id)) {
4773 opt_path[0] = 0;
4776 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4779 static const char *tree_argv[SIZEOF_ARG] = {
4780 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4783 static struct view_ops tree_ops = {
4784 "file",
4785 tree_argv,
4786 NULL,
4787 tree_read,
4788 tree_draw,
4789 tree_request,
4790 tree_grep,
4791 tree_select,
4792 tree_prepare,
4795 static bool
4796 blob_read(struct view *view, char *line)
4798 if (!line)
4799 return TRUE;
4800 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4803 static enum request
4804 blob_request(struct view *view, enum request request, struct line *line)
4806 switch (request) {
4807 case REQ_EDIT:
4808 open_blob_editor();
4809 return REQ_NONE;
4810 default:
4811 return pager_request(view, request, line);
4815 static const char *blob_argv[SIZEOF_ARG] = {
4816 "git", "cat-file", "blob", "%(blob)", NULL
4819 static struct view_ops blob_ops = {
4820 "line",
4821 blob_argv,
4822 NULL,
4823 blob_read,
4824 pager_draw,
4825 blob_request,
4826 pager_grep,
4827 pager_select,
4831 * Blame backend
4833 * Loading the blame view is a two phase job:
4835 * 1. File content is read either using opt_file from the
4836 * filesystem or using git-cat-file.
4837 * 2. Then blame information is incrementally added by
4838 * reading output from git-blame.
4841 static const char *blame_head_argv[] = {
4842 "git", "blame", "--incremental", "--", "%(file)", NULL
4845 static const char *blame_ref_argv[] = {
4846 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4849 static const char *blame_cat_file_argv[] = {
4850 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4853 struct blame_commit {
4854 char id[SIZEOF_REV]; /* SHA1 ID. */
4855 char title[128]; /* First line of the commit message. */
4856 const char *author; /* Author of the commit. */
4857 struct time time; /* Date from the author ident. */
4858 char filename[128]; /* Name of file. */
4859 bool has_previous; /* Was a "previous" line detected. */
4862 struct blame {
4863 struct blame_commit *commit;
4864 unsigned long lineno;
4865 char text[1];
4868 static bool
4869 blame_open(struct view *view)
4871 char path[SIZEOF_STR];
4873 if (!view->parent && *opt_prefix) {
4874 string_copy(path, opt_file);
4875 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4876 return FALSE;
4879 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4880 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4881 return FALSE;
4884 setup_update(view, opt_file);
4885 string_format(view->ref, "%s ...", opt_file);
4887 return TRUE;
4890 static struct blame_commit *
4891 get_blame_commit(struct view *view, const char *id)
4893 size_t i;
4895 for (i = 0; i < view->lines; i++) {
4896 struct blame *blame = view->line[i].data;
4898 if (!blame->commit)
4899 continue;
4901 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4902 return blame->commit;
4906 struct blame_commit *commit = calloc(1, sizeof(*commit));
4908 if (commit)
4909 string_ncopy(commit->id, id, SIZEOF_REV);
4910 return commit;
4914 static bool
4915 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4917 const char *pos = *posref;
4919 *posref = NULL;
4920 pos = strchr(pos + 1, ' ');
4921 if (!pos || !isdigit(pos[1]))
4922 return FALSE;
4923 *number = atoi(pos + 1);
4924 if (*number < min || *number > max)
4925 return FALSE;
4927 *posref = pos;
4928 return TRUE;
4931 static struct blame_commit *
4932 parse_blame_commit(struct view *view, const char *text, int *blamed)
4934 struct blame_commit *commit;
4935 struct blame *blame;
4936 const char *pos = text + SIZEOF_REV - 2;
4937 size_t orig_lineno = 0;
4938 size_t lineno;
4939 size_t group;
4941 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4942 return NULL;
4944 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4945 !parse_number(&pos, &lineno, 1, view->lines) ||
4946 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4947 return NULL;
4949 commit = get_blame_commit(view, text);
4950 if (!commit)
4951 return NULL;
4953 *blamed += group;
4954 while (group--) {
4955 struct line *line = &view->line[lineno + group - 1];
4957 blame = line->data;
4958 blame->commit = commit;
4959 blame->lineno = orig_lineno + group - 1;
4960 line->dirty = 1;
4963 return commit;
4966 static bool
4967 blame_read_file(struct view *view, const char *line, bool *read_file)
4969 if (!line) {
4970 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4971 struct io io = {};
4973 if (view->lines == 0 && !view->parent)
4974 die("No blame exist for %s", view->vid);
4976 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4977 report("Failed to load blame data");
4978 return TRUE;
4981 io_done(view->pipe);
4982 view->io = io;
4983 *read_file = FALSE;
4984 return FALSE;
4986 } else {
4987 size_t linelen = strlen(line);
4988 struct blame *blame = malloc(sizeof(*blame) + linelen);
4990 if (!blame)
4991 return FALSE;
4993 blame->commit = NULL;
4994 strncpy(blame->text, line, linelen);
4995 blame->text[linelen] = 0;
4996 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5000 static bool
5001 match_blame_header(const char *name, char **line)
5003 size_t namelen = strlen(name);
5004 bool matched = !strncmp(name, *line, namelen);
5006 if (matched)
5007 *line += namelen;
5009 return matched;
5012 static bool
5013 blame_read(struct view *view, char *line)
5015 static struct blame_commit *commit = NULL;
5016 static int blamed = 0;
5017 static bool read_file = TRUE;
5019 if (read_file)
5020 return blame_read_file(view, line, &read_file);
5022 if (!line) {
5023 /* Reset all! */
5024 commit = NULL;
5025 blamed = 0;
5026 read_file = TRUE;
5027 string_format(view->ref, "%s", view->vid);
5028 if (view_is_displayed(view)) {
5029 update_view_title(view);
5030 redraw_view_from(view, 0);
5032 return TRUE;
5035 if (!commit) {
5036 commit = parse_blame_commit(view, line, &blamed);
5037 string_format(view->ref, "%s %2d%%", view->vid,
5038 view->lines ? blamed * 100 / view->lines : 0);
5040 } else if (match_blame_header("author ", &line)) {
5041 commit->author = get_author(line);
5043 } else if (match_blame_header("author-time ", &line)) {
5044 parse_timesec(&commit->time, line);
5046 } else if (match_blame_header("author-tz ", &line)) {
5047 parse_timezone(&commit->time, line);
5049 } else if (match_blame_header("summary ", &line)) {
5050 string_ncopy(commit->title, line, strlen(line));
5052 } else if (match_blame_header("previous ", &line)) {
5053 commit->has_previous = TRUE;
5055 } else if (match_blame_header("filename ", &line)) {
5056 string_ncopy(commit->filename, line, strlen(line));
5057 commit = NULL;
5060 return TRUE;
5063 static bool
5064 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5066 struct blame *blame = line->data;
5067 struct time *time = NULL;
5068 const char *id = NULL, *author = NULL;
5069 char text[SIZEOF_STR];
5071 if (blame->commit && *blame->commit->filename) {
5072 id = blame->commit->id;
5073 author = blame->commit->author;
5074 time = &blame->commit->time;
5077 if (opt_date && draw_date(view, time))
5078 return TRUE;
5080 if (opt_author && draw_author(view, author))
5081 return TRUE;
5083 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5084 return TRUE;
5086 if (draw_lineno(view, lineno))
5087 return TRUE;
5089 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5090 draw_text(view, LINE_DEFAULT, text, TRUE);
5091 return TRUE;
5094 static bool
5095 check_blame_commit(struct blame *blame, bool check_null_id)
5097 if (!blame->commit)
5098 report("Commit data not loaded yet");
5099 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5100 report("No commit exist for the selected line");
5101 else
5102 return TRUE;
5103 return FALSE;
5106 static void
5107 setup_blame_parent_line(struct view *view, struct blame *blame)
5109 const char *diff_tree_argv[] = {
5110 "git", "diff-tree", "-U0", blame->commit->id,
5111 "--", blame->commit->filename, NULL
5113 struct io io = {};
5114 int parent_lineno = -1;
5115 int blamed_lineno = -1;
5116 char *line;
5118 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5119 return;
5121 while ((line = io_get(&io, '\n', TRUE))) {
5122 if (*line == '@') {
5123 char *pos = strchr(line, '+');
5125 parent_lineno = atoi(line + 4);
5126 if (pos)
5127 blamed_lineno = atoi(pos + 1);
5129 } else if (*line == '+' && parent_lineno != -1) {
5130 if (blame->lineno == blamed_lineno - 1 &&
5131 !strcmp(blame->text, line + 1)) {
5132 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5133 break;
5135 blamed_lineno++;
5139 io_done(&io);
5142 static enum request
5143 blame_request(struct view *view, enum request request, struct line *line)
5145 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5146 struct blame *blame = line->data;
5148 switch (request) {
5149 case REQ_VIEW_BLAME:
5150 if (check_blame_commit(blame, TRUE)) {
5151 string_copy(opt_ref, blame->commit->id);
5152 string_copy(opt_file, blame->commit->filename);
5153 if (blame->lineno)
5154 view->lineno = blame->lineno;
5155 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5157 break;
5159 case REQ_PARENT:
5160 if (check_blame_commit(blame, TRUE) &&
5161 select_commit_parent(blame->commit->id, opt_ref,
5162 blame->commit->filename)) {
5163 string_copy(opt_file, blame->commit->filename);
5164 setup_blame_parent_line(view, blame);
5165 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5167 break;
5169 case REQ_ENTER:
5170 if (!check_blame_commit(blame, FALSE))
5171 break;
5173 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5174 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5175 break;
5177 if (!strcmp(blame->commit->id, NULL_ID)) {
5178 struct view *diff = VIEW(REQ_VIEW_DIFF);
5179 const char *diff_index_argv[] = {
5180 "git", "diff-index", "--root", "--patch-with-stat",
5181 "-C", "-M", "HEAD", "--", view->vid, NULL
5184 if (!blame->commit->has_previous) {
5185 diff_index_argv[1] = "diff";
5186 diff_index_argv[2] = "--no-color";
5187 diff_index_argv[6] = "--";
5188 diff_index_argv[7] = "/dev/null";
5191 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5192 report("Failed to allocate diff command");
5193 break;
5195 flags |= OPEN_PREPARED;
5198 open_view(view, REQ_VIEW_DIFF, flags);
5199 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5200 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5201 break;
5203 default:
5204 return request;
5207 return REQ_NONE;
5210 static bool
5211 blame_grep(struct view *view, struct line *line)
5213 struct blame *blame = line->data;
5214 struct blame_commit *commit = blame->commit;
5215 const char *text[] = {
5216 blame->text,
5217 commit ? commit->title : "",
5218 commit ? commit->id : "",
5219 commit && opt_author ? commit->author : "",
5220 commit ? mkdate(&commit->time, opt_date) : "",
5221 NULL
5224 return grep_text(view, text);
5227 static void
5228 blame_select(struct view *view, struct line *line)
5230 struct blame *blame = line->data;
5231 struct blame_commit *commit = blame->commit;
5233 if (!commit)
5234 return;
5236 if (!strcmp(commit->id, NULL_ID))
5237 string_ncopy(ref_commit, "HEAD", 4);
5238 else
5239 string_copy_rev(ref_commit, commit->id);
5242 static struct view_ops blame_ops = {
5243 "line",
5244 NULL,
5245 blame_open,
5246 blame_read,
5247 blame_draw,
5248 blame_request,
5249 blame_grep,
5250 blame_select,
5254 * Branch backend
5257 struct branch {
5258 const char *author; /* Author of the last commit. */
5259 struct time time; /* Date of the last activity. */
5260 const struct ref *ref; /* Name and commit ID information. */
5263 static const struct ref branch_all;
5265 static const enum sort_field branch_sort_fields[] = {
5266 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5268 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5270 static int
5271 branch_compare(const void *l1, const void *l2)
5273 const struct branch *branch1 = ((const struct line *) l1)->data;
5274 const struct branch *branch2 = ((const struct line *) l2)->data;
5276 switch (get_sort_field(branch_sort_state)) {
5277 case ORDERBY_DATE:
5278 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5280 case ORDERBY_AUTHOR:
5281 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5283 case ORDERBY_NAME:
5284 default:
5285 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5289 static bool
5290 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5292 struct branch *branch = line->data;
5293 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5295 if (opt_date && draw_date(view, &branch->time))
5296 return TRUE;
5298 if (opt_author && draw_author(view, branch->author))
5299 return TRUE;
5301 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5302 return TRUE;
5305 static enum request
5306 branch_request(struct view *view, enum request request, struct line *line)
5308 struct branch *branch = line->data;
5310 switch (request) {
5311 case REQ_REFRESH:
5312 load_refs();
5313 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5314 return REQ_NONE;
5316 case REQ_TOGGLE_SORT_FIELD:
5317 case REQ_TOGGLE_SORT_ORDER:
5318 sort_view(view, request, &branch_sort_state, branch_compare);
5319 return REQ_NONE;
5321 case REQ_ENTER:
5322 if (branch->ref == &branch_all) {
5323 const char *all_branches_argv[] = {
5324 "git", "log", "--no-color", "--pretty=raw", "--parents",
5325 "--topo-order", "--all", NULL
5327 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5329 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5330 report("Failed to load view of all branches");
5331 return REQ_NONE;
5333 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5334 } else {
5335 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5337 return REQ_NONE;
5339 default:
5340 return request;
5344 static bool
5345 branch_read(struct view *view, char *line)
5347 static char id[SIZEOF_REV];
5348 struct branch *reference;
5349 size_t i;
5351 if (!line)
5352 return TRUE;
5354 switch (get_line_type(line)) {
5355 case LINE_COMMIT:
5356 string_copy_rev(id, line + STRING_SIZE("commit "));
5357 return TRUE;
5359 case LINE_AUTHOR:
5360 for (i = 0, reference = NULL; i < view->lines; i++) {
5361 struct branch *branch = view->line[i].data;
5363 if (strcmp(branch->ref->id, id))
5364 continue;
5366 view->line[i].dirty = TRUE;
5367 if (reference) {
5368 branch->author = reference->author;
5369 branch->time = reference->time;
5370 continue;
5373 parse_author_line(line + STRING_SIZE("author "),
5374 &branch->author, &branch->time);
5375 reference = branch;
5377 return TRUE;
5379 default:
5380 return TRUE;
5385 static bool
5386 branch_open_visitor(void *data, const struct ref *ref)
5388 struct view *view = data;
5389 struct branch *branch;
5391 if (ref->tag || ref->ltag || ref->remote)
5392 return TRUE;
5394 branch = calloc(1, sizeof(*branch));
5395 if (!branch)
5396 return FALSE;
5398 branch->ref = ref;
5399 return !!add_line_data(view, branch, LINE_DEFAULT);
5402 static bool
5403 branch_open(struct view *view)
5405 const char *branch_log[] = {
5406 "git", "log", "--no-color", "--pretty=raw",
5407 "--simplify-by-decoration", "--all", NULL
5410 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5411 report("Failed to load branch data");
5412 return TRUE;
5415 setup_update(view, view->id);
5416 branch_open_visitor(view, &branch_all);
5417 foreach_ref(branch_open_visitor, view);
5418 view->p_restore = TRUE;
5420 return TRUE;
5423 static bool
5424 branch_grep(struct view *view, struct line *line)
5426 struct branch *branch = line->data;
5427 const char *text[] = {
5428 branch->ref->name,
5429 branch->author,
5430 NULL
5433 return grep_text(view, text);
5436 static void
5437 branch_select(struct view *view, struct line *line)
5439 struct branch *branch = line->data;
5441 string_copy_rev(view->ref, branch->ref->id);
5442 string_copy_rev(ref_commit, branch->ref->id);
5443 string_copy_rev(ref_head, branch->ref->id);
5446 static struct view_ops branch_ops = {
5447 "branch",
5448 NULL,
5449 branch_open,
5450 branch_read,
5451 branch_draw,
5452 branch_request,
5453 branch_grep,
5454 branch_select,
5458 * Status backend
5461 struct status {
5462 char status;
5463 struct {
5464 mode_t mode;
5465 char rev[SIZEOF_REV];
5466 char name[SIZEOF_STR];
5467 } old;
5468 struct {
5469 mode_t mode;
5470 char rev[SIZEOF_REV];
5471 char name[SIZEOF_STR];
5472 } new;
5475 static char status_onbranch[SIZEOF_STR];
5476 static struct status stage_status;
5477 static enum line_type stage_line_type;
5478 static size_t stage_chunks;
5479 static int *stage_chunk;
5481 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5483 /* This should work even for the "On branch" line. */
5484 static inline bool
5485 status_has_none(struct view *view, struct line *line)
5487 return line < view->line + view->lines && !line[1].data;
5490 /* Get fields from the diff line:
5491 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5493 static inline bool
5494 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5496 const char *old_mode = buf + 1;
5497 const char *new_mode = buf + 8;
5498 const char *old_rev = buf + 15;
5499 const char *new_rev = buf + 56;
5500 const char *status = buf + 97;
5502 if (bufsize < 98 ||
5503 old_mode[-1] != ':' ||
5504 new_mode[-1] != ' ' ||
5505 old_rev[-1] != ' ' ||
5506 new_rev[-1] != ' ' ||
5507 status[-1] != ' ')
5508 return FALSE;
5510 file->status = *status;
5512 string_copy_rev(file->old.rev, old_rev);
5513 string_copy_rev(file->new.rev, new_rev);
5515 file->old.mode = strtoul(old_mode, NULL, 8);
5516 file->new.mode = strtoul(new_mode, NULL, 8);
5518 file->old.name[0] = file->new.name[0] = 0;
5520 return TRUE;
5523 static bool
5524 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5526 struct status *unmerged = NULL;
5527 char *buf;
5528 struct io io = {};
5530 if (!io_run(&io, argv, opt_cdup, IO_RD))
5531 return FALSE;
5533 add_line_data(view, NULL, type);
5535 while ((buf = io_get(&io, 0, TRUE))) {
5536 struct status *file = unmerged;
5538 if (!file) {
5539 file = calloc(1, sizeof(*file));
5540 if (!file || !add_line_data(view, file, type))
5541 goto error_out;
5544 /* Parse diff info part. */
5545 if (status) {
5546 file->status = status;
5547 if (status == 'A')
5548 string_copy(file->old.rev, NULL_ID);
5550 } else if (!file->status || file == unmerged) {
5551 if (!status_get_diff(file, buf, strlen(buf)))
5552 goto error_out;
5554 buf = io_get(&io, 0, TRUE);
5555 if (!buf)
5556 break;
5558 /* Collapse all modified entries that follow an
5559 * associated unmerged entry. */
5560 if (unmerged == file) {
5561 unmerged->status = 'U';
5562 unmerged = NULL;
5563 } else if (file->status == 'U') {
5564 unmerged = file;
5568 /* Grab the old name for rename/copy. */
5569 if (!*file->old.name &&
5570 (file->status == 'R' || file->status == 'C')) {
5571 string_ncopy(file->old.name, buf, strlen(buf));
5573 buf = io_get(&io, 0, TRUE);
5574 if (!buf)
5575 break;
5578 /* git-ls-files just delivers a NUL separated list of
5579 * file names similar to the second half of the
5580 * git-diff-* output. */
5581 string_ncopy(file->new.name, buf, strlen(buf));
5582 if (!*file->old.name)
5583 string_copy(file->old.name, file->new.name);
5584 file = NULL;
5587 if (io_error(&io)) {
5588 error_out:
5589 io_done(&io);
5590 return FALSE;
5593 if (!view->line[view->lines - 1].data)
5594 add_line_data(view, NULL, LINE_STAT_NONE);
5596 io_done(&io);
5597 return TRUE;
5600 /* Don't show unmerged entries in the staged section. */
5601 static const char *status_diff_index_argv[] = {
5602 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5603 "--cached", "-M", "HEAD", NULL
5606 static const char *status_diff_files_argv[] = {
5607 "git", "diff-files", "-z", NULL
5610 static const char *status_list_other_argv[] = {
5611 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5614 static const char *status_list_no_head_argv[] = {
5615 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5618 static const char *update_index_argv[] = {
5619 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5622 /* Restore the previous line number to stay in the context or select a
5623 * line with something that can be updated. */
5624 static void
5625 status_restore(struct view *view)
5627 if (view->p_lineno >= view->lines)
5628 view->p_lineno = view->lines - 1;
5629 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5630 view->p_lineno++;
5631 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5632 view->p_lineno--;
5634 /* If the above fails, always skip the "On branch" line. */
5635 if (view->p_lineno < view->lines)
5636 view->lineno = view->p_lineno;
5637 else
5638 view->lineno = 1;
5640 if (view->lineno < view->offset)
5641 view->offset = view->lineno;
5642 else if (view->offset + view->height <= view->lineno)
5643 view->offset = view->lineno - view->height + 1;
5645 view->p_restore = FALSE;
5648 static void
5649 status_update_onbranch(void)
5651 static const char *paths[][2] = {
5652 { "rebase-apply/rebasing", "Rebasing" },
5653 { "rebase-apply/applying", "Applying mailbox" },
5654 { "rebase-apply/", "Rebasing mailbox" },
5655 { "rebase-merge/interactive", "Interactive rebase" },
5656 { "rebase-merge/", "Rebase merge" },
5657 { "MERGE_HEAD", "Merging" },
5658 { "BISECT_LOG", "Bisecting" },
5659 { "HEAD", "On branch" },
5661 char buf[SIZEOF_STR];
5662 struct stat stat;
5663 int i;
5665 if (is_initial_commit()) {
5666 string_copy(status_onbranch, "Initial commit");
5667 return;
5670 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5671 char *head = opt_head;
5673 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5674 lstat(buf, &stat) < 0)
5675 continue;
5677 if (!*opt_head) {
5678 struct io io = {};
5680 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5681 io_read_buf(&io, buf, sizeof(buf))) {
5682 head = buf;
5683 if (!prefixcmp(head, "refs/heads/"))
5684 head += STRING_SIZE("refs/heads/");
5688 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5689 string_copy(status_onbranch, opt_head);
5690 return;
5693 string_copy(status_onbranch, "Not currently on any branch");
5696 /* First parse staged info using git-diff-index(1), then parse unstaged
5697 * info using git-diff-files(1), and finally untracked files using
5698 * git-ls-files(1). */
5699 static bool
5700 status_open(struct view *view)
5702 reset_view(view);
5704 add_line_data(view, NULL, LINE_STAT_HEAD);
5705 status_update_onbranch();
5707 io_run_bg(update_index_argv);
5709 if (is_initial_commit()) {
5710 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5711 return FALSE;
5712 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5713 return FALSE;
5716 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5717 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5718 return FALSE;
5720 /* Restore the exact position or use the specialized restore
5721 * mode? */
5722 if (!view->p_restore)
5723 status_restore(view);
5724 return TRUE;
5727 static bool
5728 status_draw(struct view *view, struct line *line, unsigned int lineno)
5730 struct status *status = line->data;
5731 enum line_type type;
5732 const char *text;
5734 if (!status) {
5735 switch (line->type) {
5736 case LINE_STAT_STAGED:
5737 type = LINE_STAT_SECTION;
5738 text = "Changes to be committed:";
5739 break;
5741 case LINE_STAT_UNSTAGED:
5742 type = LINE_STAT_SECTION;
5743 text = "Changed but not updated:";
5744 break;
5746 case LINE_STAT_UNTRACKED:
5747 type = LINE_STAT_SECTION;
5748 text = "Untracked files:";
5749 break;
5751 case LINE_STAT_NONE:
5752 type = LINE_DEFAULT;
5753 text = " (no files)";
5754 break;
5756 case LINE_STAT_HEAD:
5757 type = LINE_STAT_HEAD;
5758 text = status_onbranch;
5759 break;
5761 default:
5762 return FALSE;
5764 } else {
5765 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5767 buf[0] = status->status;
5768 if (draw_text(view, line->type, buf, TRUE))
5769 return TRUE;
5770 type = LINE_DEFAULT;
5771 text = status->new.name;
5774 draw_text(view, type, text, TRUE);
5775 return TRUE;
5778 static enum request
5779 status_load_error(struct view *view, struct view *stage, const char *path)
5781 if (displayed_views() == 2 || display[current_view] != view)
5782 maximize_view(view);
5783 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5784 return REQ_NONE;
5787 static enum request
5788 status_enter(struct view *view, struct line *line)
5790 struct status *status = line->data;
5791 const char *oldpath = status ? status->old.name : NULL;
5792 /* Diffs for unmerged entries are empty when passing the new
5793 * path, so leave it empty. */
5794 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5795 const char *info;
5796 enum open_flags split;
5797 struct view *stage = VIEW(REQ_VIEW_STAGE);
5799 if (line->type == LINE_STAT_NONE ||
5800 (!status && line[1].type == LINE_STAT_NONE)) {
5801 report("No file to diff");
5802 return REQ_NONE;
5805 switch (line->type) {
5806 case LINE_STAT_STAGED:
5807 if (is_initial_commit()) {
5808 const char *no_head_diff_argv[] = {
5809 "git", "diff", "--no-color", "--patch-with-stat",
5810 "--", "/dev/null", newpath, NULL
5813 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5814 return status_load_error(view, stage, newpath);
5815 } else {
5816 const char *index_show_argv[] = {
5817 "git", "diff-index", "--root", "--patch-with-stat",
5818 "-C", "-M", "--cached", "HEAD", "--",
5819 oldpath, newpath, NULL
5822 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5823 return status_load_error(view, stage, newpath);
5826 if (status)
5827 info = "Staged changes to %s";
5828 else
5829 info = "Staged changes";
5830 break;
5832 case LINE_STAT_UNSTAGED:
5834 const char *files_show_argv[] = {
5835 "git", "diff-files", "--root", "--patch-with-stat",
5836 "-C", "-M", "--", oldpath, newpath, NULL
5839 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5840 return status_load_error(view, stage, newpath);
5841 if (status)
5842 info = "Unstaged changes to %s";
5843 else
5844 info = "Unstaged changes";
5845 break;
5847 case LINE_STAT_UNTRACKED:
5848 if (!newpath) {
5849 report("No file to show");
5850 return REQ_NONE;
5853 if (!suffixcmp(status->new.name, -1, "/")) {
5854 report("Cannot display a directory");
5855 return REQ_NONE;
5858 if (!prepare_update_file(stage, newpath))
5859 return status_load_error(view, stage, newpath);
5860 info = "Untracked file %s";
5861 break;
5863 case LINE_STAT_HEAD:
5864 return REQ_NONE;
5866 default:
5867 die("line type %d not handled in switch", line->type);
5870 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5871 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5872 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5873 if (status) {
5874 stage_status = *status;
5875 } else {
5876 memset(&stage_status, 0, sizeof(stage_status));
5879 stage_line_type = line->type;
5880 stage_chunks = 0;
5881 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5884 return REQ_NONE;
5887 static bool
5888 status_exists(struct status *status, enum line_type type)
5890 struct view *view = VIEW(REQ_VIEW_STATUS);
5891 unsigned long lineno;
5893 for (lineno = 0; lineno < view->lines; lineno++) {
5894 struct line *line = &view->line[lineno];
5895 struct status *pos = line->data;
5897 if (line->type != type)
5898 continue;
5899 if (!pos && (!status || !status->status) && line[1].data) {
5900 select_view_line(view, lineno);
5901 return TRUE;
5903 if (pos && !strcmp(status->new.name, pos->new.name)) {
5904 select_view_line(view, lineno);
5905 return TRUE;
5909 return FALSE;
5913 static bool
5914 status_update_prepare(struct io *io, enum line_type type)
5916 const char *staged_argv[] = {
5917 "git", "update-index", "-z", "--index-info", NULL
5919 const char *others_argv[] = {
5920 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5923 switch (type) {
5924 case LINE_STAT_STAGED:
5925 return io_run(io, staged_argv, opt_cdup, IO_WR);
5927 case LINE_STAT_UNSTAGED:
5928 case LINE_STAT_UNTRACKED:
5929 return io_run(io, others_argv, opt_cdup, IO_WR);
5931 default:
5932 die("line type %d not handled in switch", type);
5933 return FALSE;
5937 static bool
5938 status_update_write(struct io *io, struct status *status, enum line_type type)
5940 char buf[SIZEOF_STR];
5941 size_t bufsize = 0;
5943 switch (type) {
5944 case LINE_STAT_STAGED:
5945 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5946 status->old.mode,
5947 status->old.rev,
5948 status->old.name, 0))
5949 return FALSE;
5950 break;
5952 case LINE_STAT_UNSTAGED:
5953 case LINE_STAT_UNTRACKED:
5954 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5955 return FALSE;
5956 break;
5958 default:
5959 die("line type %d not handled in switch", type);
5962 return io_write(io, buf, bufsize);
5965 static bool
5966 status_update_file(struct status *status, enum line_type type)
5968 struct io io = {};
5969 bool result;
5971 if (!status_update_prepare(&io, type))
5972 return FALSE;
5974 result = status_update_write(&io, status, type);
5975 return io_done(&io) && result;
5978 static bool
5979 status_update_files(struct view *view, struct line *line)
5981 char buf[sizeof(view->ref)];
5982 struct io io = {};
5983 bool result = TRUE;
5984 struct line *pos = view->line + view->lines;
5985 int files = 0;
5986 int file, done;
5987 int cursor_y = -1, cursor_x = -1;
5989 if (!status_update_prepare(&io, line->type))
5990 return FALSE;
5992 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5993 files++;
5995 string_copy(buf, view->ref);
5996 getsyx(cursor_y, cursor_x);
5997 for (file = 0, done = 5; result && file < files; line++, file++) {
5998 int almost_done = file * 100 / files;
6000 if (almost_done > done) {
6001 done = almost_done;
6002 string_format(view->ref, "updating file %u of %u (%d%% done)",
6003 file, files, done);
6004 update_view_title(view);
6005 setsyx(cursor_y, cursor_x);
6006 doupdate();
6008 result = status_update_write(&io, line->data, line->type);
6010 string_copy(view->ref, buf);
6012 return io_done(&io) && result;
6015 static bool
6016 status_update(struct view *view)
6018 struct line *line = &view->line[view->lineno];
6020 assert(view->lines);
6022 if (!line->data) {
6023 /* This should work even for the "On branch" line. */
6024 if (line < view->line + view->lines && !line[1].data) {
6025 report("Nothing to update");
6026 return FALSE;
6029 if (!status_update_files(view, line + 1)) {
6030 report("Failed to update file status");
6031 return FALSE;
6034 } else if (!status_update_file(line->data, line->type)) {
6035 report("Failed to update file status");
6036 return FALSE;
6039 return TRUE;
6042 static bool
6043 status_revert(struct status *status, enum line_type type, bool has_none)
6045 if (!status || type != LINE_STAT_UNSTAGED) {
6046 if (type == LINE_STAT_STAGED) {
6047 report("Cannot revert changes to staged files");
6048 } else if (type == LINE_STAT_UNTRACKED) {
6049 report("Cannot revert changes to untracked files");
6050 } else if (has_none) {
6051 report("Nothing to revert");
6052 } else {
6053 report("Cannot revert changes to multiple files");
6056 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6057 char mode[10] = "100644";
6058 const char *reset_argv[] = {
6059 "git", "update-index", "--cacheinfo", mode,
6060 status->old.rev, status->old.name, NULL
6062 const char *checkout_argv[] = {
6063 "git", "checkout", "--", status->old.name, NULL
6066 if (status->status == 'U') {
6067 string_format(mode, "%5o", status->old.mode);
6069 if (status->old.mode == 0 && status->new.mode == 0) {
6070 reset_argv[2] = "--force-remove";
6071 reset_argv[3] = status->old.name;
6072 reset_argv[4] = NULL;
6075 if (!io_run_fg(reset_argv, opt_cdup))
6076 return FALSE;
6077 if (status->old.mode == 0 && status->new.mode == 0)
6078 return TRUE;
6081 return io_run_fg(checkout_argv, opt_cdup);
6084 return FALSE;
6087 static enum request
6088 status_request(struct view *view, enum request request, struct line *line)
6090 struct status *status = line->data;
6092 switch (request) {
6093 case REQ_STATUS_UPDATE:
6094 if (!status_update(view))
6095 return REQ_NONE;
6096 break;
6098 case REQ_STATUS_REVERT:
6099 if (!status_revert(status, line->type, status_has_none(view, line)))
6100 return REQ_NONE;
6101 break;
6103 case REQ_STATUS_MERGE:
6104 if (!status || status->status != 'U') {
6105 report("Merging only possible for files with unmerged status ('U').");
6106 return REQ_NONE;
6108 open_mergetool(status->new.name);
6109 break;
6111 case REQ_EDIT:
6112 if (!status)
6113 return request;
6114 if (status->status == 'D') {
6115 report("File has been deleted.");
6116 return REQ_NONE;
6119 open_editor(status->new.name);
6120 break;
6122 case REQ_VIEW_BLAME:
6123 if (status)
6124 opt_ref[0] = 0;
6125 return request;
6127 case REQ_ENTER:
6128 /* After returning the status view has been split to
6129 * show the stage view. No further reloading is
6130 * necessary. */
6131 return status_enter(view, line);
6133 case REQ_REFRESH:
6134 /* Simply reload the view. */
6135 break;
6137 default:
6138 return request;
6141 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6143 return REQ_NONE;
6146 static void
6147 status_select(struct view *view, struct line *line)
6149 struct status *status = line->data;
6150 char file[SIZEOF_STR] = "all files";
6151 const char *text;
6152 const char *key;
6154 if (status && !string_format(file, "'%s'", status->new.name))
6155 return;
6157 if (!status && line[1].type == LINE_STAT_NONE)
6158 line++;
6160 switch (line->type) {
6161 case LINE_STAT_STAGED:
6162 text = "Press %s to unstage %s for commit";
6163 break;
6165 case LINE_STAT_UNSTAGED:
6166 text = "Press %s to stage %s for commit";
6167 break;
6169 case LINE_STAT_UNTRACKED:
6170 text = "Press %s to stage %s for addition";
6171 break;
6173 case LINE_STAT_HEAD:
6174 case LINE_STAT_NONE:
6175 text = "Nothing to update";
6176 break;
6178 default:
6179 die("line type %d not handled in switch", line->type);
6182 if (status && status->status == 'U') {
6183 text = "Press %s to resolve conflict in %s";
6184 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6186 } else {
6187 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6190 string_format(view->ref, text, key, file);
6191 if (status)
6192 string_copy(opt_file, status->new.name);
6195 static bool
6196 status_grep(struct view *view, struct line *line)
6198 struct status *status = line->data;
6200 if (status) {
6201 const char buf[2] = { status->status, 0 };
6202 const char *text[] = { status->new.name, buf, NULL };
6204 return grep_text(view, text);
6207 return FALSE;
6210 static struct view_ops status_ops = {
6211 "file",
6212 NULL,
6213 status_open,
6214 NULL,
6215 status_draw,
6216 status_request,
6217 status_grep,
6218 status_select,
6222 static bool
6223 stage_diff_write(struct io *io, struct line *line, struct line *end)
6225 while (line < end) {
6226 if (!io_write(io, line->data, strlen(line->data)) ||
6227 !io_write(io, "\n", 1))
6228 return FALSE;
6229 line++;
6230 if (line->type == LINE_DIFF_CHUNK ||
6231 line->type == LINE_DIFF_HEADER)
6232 break;
6235 return TRUE;
6238 static struct line *
6239 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6241 for (; view->line < line; line--)
6242 if (line->type == type)
6243 return line;
6245 return NULL;
6248 static bool
6249 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6251 const char *apply_argv[SIZEOF_ARG] = {
6252 "git", "apply", "--whitespace=nowarn", NULL
6254 struct line *diff_hdr;
6255 struct io io = {};
6256 int argc = 3;
6258 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6259 if (!diff_hdr)
6260 return FALSE;
6262 if (!revert)
6263 apply_argv[argc++] = "--cached";
6264 if (revert || stage_line_type == LINE_STAT_STAGED)
6265 apply_argv[argc++] = "-R";
6266 apply_argv[argc++] = "-";
6267 apply_argv[argc++] = NULL;
6268 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6269 return FALSE;
6271 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6272 !stage_diff_write(&io, chunk, view->line + view->lines))
6273 chunk = NULL;
6275 io_done(&io);
6276 io_run_bg(update_index_argv);
6278 return chunk ? TRUE : FALSE;
6281 static bool
6282 stage_update(struct view *view, struct line *line)
6284 struct line *chunk = NULL;
6286 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6287 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6289 if (chunk) {
6290 if (!stage_apply_chunk(view, chunk, FALSE)) {
6291 report("Failed to apply chunk");
6292 return FALSE;
6295 } else if (!stage_status.status) {
6296 view = VIEW(REQ_VIEW_STATUS);
6298 for (line = view->line; line < view->line + view->lines; line++)
6299 if (line->type == stage_line_type)
6300 break;
6302 if (!status_update_files(view, line + 1)) {
6303 report("Failed to update files");
6304 return FALSE;
6307 } else if (!status_update_file(&stage_status, stage_line_type)) {
6308 report("Failed to update file");
6309 return FALSE;
6312 return TRUE;
6315 static bool
6316 stage_revert(struct view *view, struct line *line)
6318 struct line *chunk = NULL;
6320 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6321 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6323 if (chunk) {
6324 if (!prompt_yesno("Are you sure you want to revert changes?"))
6325 return FALSE;
6327 if (!stage_apply_chunk(view, chunk, TRUE)) {
6328 report("Failed to revert chunk");
6329 return FALSE;
6331 return TRUE;
6333 } else {
6334 return status_revert(stage_status.status ? &stage_status : NULL,
6335 stage_line_type, FALSE);
6340 static void
6341 stage_next(struct view *view, struct line *line)
6343 int i;
6345 if (!stage_chunks) {
6346 for (line = view->line; line < view->line + view->lines; line++) {
6347 if (line->type != LINE_DIFF_CHUNK)
6348 continue;
6350 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6351 report("Allocation failure");
6352 return;
6355 stage_chunk[stage_chunks++] = line - view->line;
6359 for (i = 0; i < stage_chunks; i++) {
6360 if (stage_chunk[i] > view->lineno) {
6361 do_scroll_view(view, stage_chunk[i] - view->lineno);
6362 report("Chunk %d of %d", i + 1, stage_chunks);
6363 return;
6367 report("No next chunk found");
6370 static enum request
6371 stage_request(struct view *view, enum request request, struct line *line)
6373 switch (request) {
6374 case REQ_STATUS_UPDATE:
6375 if (!stage_update(view, line))
6376 return REQ_NONE;
6377 break;
6379 case REQ_STATUS_REVERT:
6380 if (!stage_revert(view, line))
6381 return REQ_NONE;
6382 break;
6384 case REQ_STAGE_NEXT:
6385 if (stage_line_type == LINE_STAT_UNTRACKED) {
6386 report("File is untracked; press %s to add",
6387 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6388 return REQ_NONE;
6390 stage_next(view, line);
6391 return REQ_NONE;
6393 case REQ_EDIT:
6394 if (!stage_status.new.name[0])
6395 return request;
6396 if (stage_status.status == 'D') {
6397 report("File has been deleted.");
6398 return REQ_NONE;
6401 open_editor(stage_status.new.name);
6402 break;
6404 case REQ_REFRESH:
6405 /* Reload everything ... */
6406 break;
6408 case REQ_VIEW_BLAME:
6409 if (stage_status.new.name[0]) {
6410 string_copy(opt_file, stage_status.new.name);
6411 opt_ref[0] = 0;
6413 return request;
6415 case REQ_ENTER:
6416 return pager_request(view, request, line);
6418 default:
6419 return request;
6422 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6423 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6425 /* Check whether the staged entry still exists, and close the
6426 * stage view if it doesn't. */
6427 if (!status_exists(&stage_status, stage_line_type)) {
6428 status_restore(VIEW(REQ_VIEW_STATUS));
6429 return REQ_VIEW_CLOSE;
6432 if (stage_line_type == LINE_STAT_UNTRACKED) {
6433 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6434 report("Cannot display a directory");
6435 return REQ_NONE;
6438 if (!prepare_update_file(view, stage_status.new.name)) {
6439 report("Failed to open file: %s", strerror(errno));
6440 return REQ_NONE;
6443 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6445 return REQ_NONE;
6448 static struct view_ops stage_ops = {
6449 "line",
6450 NULL,
6451 NULL,
6452 pager_read,
6453 pager_draw,
6454 stage_request,
6455 pager_grep,
6456 pager_select,
6461 * Revision graph
6464 struct commit {
6465 char id[SIZEOF_REV]; /* SHA1 ID. */
6466 char title[128]; /* First line of the commit message. */
6467 const char *author; /* Author of the commit. */
6468 struct time time; /* Date from the author ident. */
6469 struct ref_list *refs; /* Repository references. */
6470 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6471 size_t graph_size; /* The width of the graph array. */
6472 bool has_parents; /* Rewritten --parents seen. */
6475 /* Size of rev graph with no "padding" columns */
6476 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6478 struct rev_graph {
6479 struct rev_graph *prev, *next, *parents;
6480 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6481 size_t size;
6482 struct commit *commit;
6483 size_t pos;
6484 unsigned int boundary:1;
6487 /* Parents of the commit being visualized. */
6488 static struct rev_graph graph_parents[4];
6490 /* The current stack of revisions on the graph. */
6491 static struct rev_graph graph_stacks[4] = {
6492 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6493 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6494 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6495 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6498 static inline bool
6499 graph_parent_is_merge(struct rev_graph *graph)
6501 return graph->parents->size > 1;
6504 static inline void
6505 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6507 struct commit *commit = graph->commit;
6509 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6510 commit->graph[commit->graph_size++] = symbol;
6513 static void
6514 clear_rev_graph(struct rev_graph *graph)
6516 graph->boundary = 0;
6517 graph->size = graph->pos = 0;
6518 graph->commit = NULL;
6519 memset(graph->parents, 0, sizeof(*graph->parents));
6522 static void
6523 done_rev_graph(struct rev_graph *graph)
6525 if (graph_parent_is_merge(graph) &&
6526 graph->pos < graph->size - 1 &&
6527 graph->next->size == graph->size + graph->parents->size - 1) {
6528 size_t i = graph->pos + graph->parents->size - 1;
6530 graph->commit->graph_size = i * 2;
6531 while (i < graph->next->size - 1) {
6532 append_to_rev_graph(graph, ' ');
6533 append_to_rev_graph(graph, '\\');
6534 i++;
6538 clear_rev_graph(graph);
6541 static void
6542 push_rev_graph(struct rev_graph *graph, const char *parent)
6544 int i;
6546 /* "Collapse" duplicate parents lines.
6548 * FIXME: This needs to also update update the drawn graph but
6549 * for now it just serves as a method for pruning graph lines. */
6550 for (i = 0; i < graph->size; i++)
6551 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6552 return;
6554 if (graph->size < SIZEOF_REVITEMS) {
6555 string_copy_rev(graph->rev[graph->size++], parent);
6559 static chtype
6560 get_rev_graph_symbol(struct rev_graph *graph)
6562 chtype symbol;
6564 if (graph->boundary)
6565 symbol = REVGRAPH_BOUND;
6566 else if (graph->parents->size == 0)
6567 symbol = REVGRAPH_INIT;
6568 else if (graph_parent_is_merge(graph))
6569 symbol = REVGRAPH_MERGE;
6570 else if (graph->pos >= graph->size)
6571 symbol = REVGRAPH_BRANCH;
6572 else
6573 symbol = REVGRAPH_COMMIT;
6575 return symbol;
6578 static void
6579 draw_rev_graph(struct rev_graph *graph)
6581 struct rev_filler {
6582 chtype separator, line;
6584 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6585 static struct rev_filler fillers[] = {
6586 { ' ', '|' },
6587 { '`', '.' },
6588 { '\'', ' ' },
6589 { '/', ' ' },
6591 chtype symbol = get_rev_graph_symbol(graph);
6592 struct rev_filler *filler;
6593 size_t i;
6595 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6596 filler = &fillers[DEFAULT];
6598 for (i = 0; i < graph->pos; i++) {
6599 append_to_rev_graph(graph, filler->line);
6600 if (graph_parent_is_merge(graph->prev) &&
6601 graph->prev->pos == i)
6602 filler = &fillers[RSHARP];
6604 append_to_rev_graph(graph, filler->separator);
6607 /* Place the symbol for this revision. */
6608 append_to_rev_graph(graph, symbol);
6610 if (graph->prev->size > graph->size)
6611 filler = &fillers[RDIAG];
6612 else
6613 filler = &fillers[DEFAULT];
6615 i++;
6617 for (; i < graph->size; i++) {
6618 append_to_rev_graph(graph, filler->separator);
6619 append_to_rev_graph(graph, filler->line);
6620 if (graph_parent_is_merge(graph->prev) &&
6621 i < graph->prev->pos + graph->parents->size)
6622 filler = &fillers[RSHARP];
6623 if (graph->prev->size > graph->size)
6624 filler = &fillers[LDIAG];
6627 if (graph->prev->size > graph->size) {
6628 append_to_rev_graph(graph, filler->separator);
6629 if (filler->line != ' ')
6630 append_to_rev_graph(graph, filler->line);
6634 /* Prepare the next rev graph */
6635 static void
6636 prepare_rev_graph(struct rev_graph *graph)
6638 size_t i;
6640 /* First, traverse all lines of revisions up to the active one. */
6641 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6642 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6643 break;
6645 push_rev_graph(graph->next, graph->rev[graph->pos]);
6648 /* Interleave the new revision parent(s). */
6649 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6650 push_rev_graph(graph->next, graph->parents->rev[i]);
6652 /* Lastly, put any remaining revisions. */
6653 for (i = graph->pos + 1; i < graph->size; i++)
6654 push_rev_graph(graph->next, graph->rev[i]);
6657 static void
6658 update_rev_graph(struct view *view, struct rev_graph *graph)
6660 /* If this is the finalizing update ... */
6661 if (graph->commit)
6662 prepare_rev_graph(graph);
6664 /* Graph visualization needs a one rev look-ahead,
6665 * so the first update doesn't visualize anything. */
6666 if (!graph->prev->commit)
6667 return;
6669 if (view->lines > 2)
6670 view->line[view->lines - 3].dirty = 1;
6671 if (view->lines > 1)
6672 view->line[view->lines - 2].dirty = 1;
6673 draw_rev_graph(graph->prev);
6674 done_rev_graph(graph->prev->prev);
6679 * Main view backend
6682 static const char *main_argv[SIZEOF_ARG] = {
6683 "git", "log", "--no-color", "--pretty=raw", "--parents",
6684 "--topo-order", "%(head)", NULL
6687 static bool
6688 main_draw(struct view *view, struct line *line, unsigned int lineno)
6690 struct commit *commit = line->data;
6692 if (!commit->author)
6693 return FALSE;
6695 if (opt_date && draw_date(view, &commit->time))
6696 return TRUE;
6698 if (opt_author && draw_author(view, commit->author))
6699 return TRUE;
6701 if (opt_rev_graph && commit->graph_size &&
6702 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6703 return TRUE;
6705 if (opt_show_refs && commit->refs) {
6706 size_t i;
6708 for (i = 0; i < commit->refs->size; i++) {
6709 struct ref *ref = commit->refs->refs[i];
6710 enum line_type type;
6712 if (ref->head)
6713 type = LINE_MAIN_HEAD;
6714 else if (ref->ltag)
6715 type = LINE_MAIN_LOCAL_TAG;
6716 else if (ref->tag)
6717 type = LINE_MAIN_TAG;
6718 else if (ref->tracked)
6719 type = LINE_MAIN_TRACKED;
6720 else if (ref->remote)
6721 type = LINE_MAIN_REMOTE;
6722 else
6723 type = LINE_MAIN_REF;
6725 if (draw_text(view, type, "[", TRUE) ||
6726 draw_text(view, type, ref->name, TRUE) ||
6727 draw_text(view, type, "]", TRUE))
6728 return TRUE;
6730 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6731 return TRUE;
6735 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6736 return TRUE;
6739 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6740 static bool
6741 main_read(struct view *view, char *line)
6743 static struct rev_graph *graph = graph_stacks;
6744 enum line_type type;
6745 struct commit *commit;
6747 if (!line) {
6748 int i;
6750 if (!view->lines && !view->parent)
6751 die("No revisions match the given arguments.");
6752 if (view->lines > 0) {
6753 commit = view->line[view->lines - 1].data;
6754 view->line[view->lines - 1].dirty = 1;
6755 if (!commit->author) {
6756 view->lines--;
6757 free(commit);
6758 graph->commit = NULL;
6761 update_rev_graph(view, graph);
6763 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6764 clear_rev_graph(&graph_stacks[i]);
6765 return TRUE;
6768 type = get_line_type(line);
6769 if (type == LINE_COMMIT) {
6770 commit = calloc(1, sizeof(struct commit));
6771 if (!commit)
6772 return FALSE;
6774 line += STRING_SIZE("commit ");
6775 if (*line == '-') {
6776 graph->boundary = 1;
6777 line++;
6780 string_copy_rev(commit->id, line);
6781 commit->refs = get_ref_list(commit->id);
6782 graph->commit = commit;
6783 add_line_data(view, commit, LINE_MAIN_COMMIT);
6785 while ((line = strchr(line, ' '))) {
6786 line++;
6787 push_rev_graph(graph->parents, line);
6788 commit->has_parents = TRUE;
6790 return TRUE;
6793 if (!view->lines)
6794 return TRUE;
6795 commit = view->line[view->lines - 1].data;
6797 switch (type) {
6798 case LINE_PARENT:
6799 if (commit->has_parents)
6800 break;
6801 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6802 break;
6804 case LINE_AUTHOR:
6805 parse_author_line(line + STRING_SIZE("author "),
6806 &commit->author, &commit->time);
6807 update_rev_graph(view, graph);
6808 graph = graph->next;
6809 break;
6811 default:
6812 /* Fill in the commit title if it has not already been set. */
6813 if (commit->title[0])
6814 break;
6816 /* Require titles to start with a non-space character at the
6817 * offset used by git log. */
6818 if (strncmp(line, " ", 4))
6819 break;
6820 line += 4;
6821 /* Well, if the title starts with a whitespace character,
6822 * try to be forgiving. Otherwise we end up with no title. */
6823 while (isspace(*line))
6824 line++;
6825 if (*line == '\0')
6826 break;
6827 /* FIXME: More graceful handling of titles; append "..." to
6828 * shortened titles, etc. */
6830 string_expand(commit->title, sizeof(commit->title), line, 1);
6831 view->line[view->lines - 1].dirty = 1;
6834 return TRUE;
6837 static enum request
6838 main_request(struct view *view, enum request request, struct line *line)
6840 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6842 switch (request) {
6843 case REQ_ENTER:
6844 open_view(view, REQ_VIEW_DIFF, flags);
6845 break;
6846 case REQ_REFRESH:
6847 load_refs();
6848 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6849 break;
6850 default:
6851 return request;
6854 return REQ_NONE;
6857 static bool
6858 grep_refs(struct ref_list *list, regex_t *regex)
6860 regmatch_t pmatch;
6861 size_t i;
6863 if (!opt_show_refs || !list)
6864 return FALSE;
6866 for (i = 0; i < list->size; i++) {
6867 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6868 return TRUE;
6871 return FALSE;
6874 static bool
6875 main_grep(struct view *view, struct line *line)
6877 struct commit *commit = line->data;
6878 const char *text[] = {
6879 commit->title,
6880 opt_author ? commit->author : "",
6881 mkdate(&commit->time, opt_date),
6882 NULL
6885 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6888 static void
6889 main_select(struct view *view, struct line *line)
6891 struct commit *commit = line->data;
6893 string_copy_rev(view->ref, commit->id);
6894 string_copy_rev(ref_commit, view->ref);
6897 static struct view_ops main_ops = {
6898 "commit",
6899 main_argv,
6900 NULL,
6901 main_read,
6902 main_draw,
6903 main_request,
6904 main_grep,
6905 main_select,
6910 * Status management
6913 /* Whether or not the curses interface has been initialized. */
6914 static bool cursed = FALSE;
6916 /* Terminal hacks and workarounds. */
6917 static bool use_scroll_redrawwin;
6918 static bool use_scroll_status_wclear;
6920 /* The status window is used for polling keystrokes. */
6921 static WINDOW *status_win;
6923 /* Reading from the prompt? */
6924 static bool input_mode = FALSE;
6926 static bool status_empty = FALSE;
6928 /* Update status and title window. */
6929 static void
6930 report(const char *msg, ...)
6932 struct view *view = display[current_view];
6934 if (input_mode)
6935 return;
6937 if (!view) {
6938 char buf[SIZEOF_STR];
6939 va_list args;
6941 va_start(args, msg);
6942 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6943 buf[sizeof(buf) - 1] = 0;
6944 buf[sizeof(buf) - 2] = '.';
6945 buf[sizeof(buf) - 3] = '.';
6946 buf[sizeof(buf) - 4] = '.';
6948 va_end(args);
6949 die("%s", buf);
6952 if (!status_empty || *msg) {
6953 va_list args;
6955 va_start(args, msg);
6957 wmove(status_win, 0, 0);
6958 if (view->has_scrolled && use_scroll_status_wclear)
6959 wclear(status_win);
6960 if (*msg) {
6961 vwprintw(status_win, msg, args);
6962 status_empty = FALSE;
6963 } else {
6964 status_empty = TRUE;
6966 wclrtoeol(status_win);
6967 wnoutrefresh(status_win);
6969 va_end(args);
6972 update_view_title(view);
6975 static void
6976 init_display(void)
6978 const char *term;
6979 int x, y;
6981 /* Initialize the curses library */
6982 if (isatty(STDIN_FILENO)) {
6983 cursed = !!initscr();
6984 opt_tty = stdin;
6985 } else {
6986 /* Leave stdin and stdout alone when acting as a pager. */
6987 opt_tty = fopen("/dev/tty", "r+");
6988 if (!opt_tty)
6989 die("Failed to open /dev/tty");
6990 cursed = !!newterm(NULL, opt_tty, opt_tty);
6993 if (!cursed)
6994 die("Failed to initialize curses");
6996 nonl(); /* Disable conversion and detect newlines from input. */
6997 cbreak(); /* Take input chars one at a time, no wait for \n */
6998 noecho(); /* Don't echo input */
6999 leaveok(stdscr, FALSE);
7001 if (has_colors())
7002 init_colors();
7004 getmaxyx(stdscr, y, x);
7005 status_win = newwin(1, 0, y - 1, 0);
7006 if (!status_win)
7007 die("Failed to create status window");
7009 /* Enable keyboard mapping */
7010 keypad(status_win, TRUE);
7011 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7013 TABSIZE = opt_tab_size;
7015 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7016 if (term && !strcmp(term, "gnome-terminal")) {
7017 /* In the gnome-terminal-emulator, the message from
7018 * scrolling up one line when impossible followed by
7019 * scrolling down one line causes corruption of the
7020 * status line. This is fixed by calling wclear. */
7021 use_scroll_status_wclear = TRUE;
7022 use_scroll_redrawwin = FALSE;
7024 } else if (term && !strcmp(term, "xrvt-xpm")) {
7025 /* No problems with full optimizations in xrvt-(unicode)
7026 * and aterm. */
7027 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7029 } else {
7030 /* When scrolling in (u)xterm the last line in the
7031 * scrolling direction will update slowly. */
7032 use_scroll_redrawwin = TRUE;
7033 use_scroll_status_wclear = FALSE;
7037 static int
7038 get_input(int prompt_position)
7040 struct view *view;
7041 int i, key, cursor_y, cursor_x;
7042 bool loading = FALSE;
7044 if (prompt_position)
7045 input_mode = TRUE;
7047 while (TRUE) {
7048 foreach_view (view, i) {
7049 update_view(view);
7050 if (view_is_displayed(view) && view->has_scrolled &&
7051 use_scroll_redrawwin)
7052 redrawwin(view->win);
7053 view->has_scrolled = FALSE;
7054 if (view->pipe)
7055 loading = TRUE;
7058 /* Update the cursor position. */
7059 if (prompt_position) {
7060 getbegyx(status_win, cursor_y, cursor_x);
7061 cursor_x = prompt_position;
7062 } else {
7063 view = display[current_view];
7064 getbegyx(view->win, cursor_y, cursor_x);
7065 cursor_x = view->width - 1;
7066 cursor_y += view->lineno - view->offset;
7068 setsyx(cursor_y, cursor_x);
7070 /* Refresh, accept single keystroke of input */
7071 doupdate();
7072 nodelay(status_win, loading);
7073 key = wgetch(status_win);
7075 /* wgetch() with nodelay() enabled returns ERR when
7076 * there's no input. */
7077 if (key == ERR) {
7079 } else if (key == KEY_RESIZE) {
7080 int height, width;
7082 getmaxyx(stdscr, height, width);
7084 wresize(status_win, 1, width);
7085 mvwin(status_win, height - 1, 0);
7086 wnoutrefresh(status_win);
7087 resize_display();
7088 redraw_display(TRUE);
7090 } else {
7091 input_mode = FALSE;
7092 return key;
7097 static char *
7098 prompt_input(const char *prompt, input_handler handler, void *data)
7100 enum input_status status = INPUT_OK;
7101 static char buf[SIZEOF_STR];
7102 size_t pos = 0;
7104 buf[pos] = 0;
7106 while (status == INPUT_OK || status == INPUT_SKIP) {
7107 int key;
7109 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7110 wclrtoeol(status_win);
7112 key = get_input(pos + 1);
7113 switch (key) {
7114 case KEY_RETURN:
7115 case KEY_ENTER:
7116 case '\n':
7117 status = pos ? INPUT_STOP : INPUT_CANCEL;
7118 break;
7120 case KEY_BACKSPACE:
7121 if (pos > 0)
7122 buf[--pos] = 0;
7123 else
7124 status = INPUT_CANCEL;
7125 break;
7127 case KEY_ESC:
7128 status = INPUT_CANCEL;
7129 break;
7131 default:
7132 if (pos >= sizeof(buf)) {
7133 report("Input string too long");
7134 return NULL;
7137 status = handler(data, buf, key);
7138 if (status == INPUT_OK)
7139 buf[pos++] = (char) key;
7143 /* Clear the status window */
7144 status_empty = FALSE;
7145 report("");
7147 if (status == INPUT_CANCEL)
7148 return NULL;
7150 buf[pos++] = 0;
7152 return buf;
7155 static enum input_status
7156 prompt_yesno_handler(void *data, char *buf, int c)
7158 if (c == 'y' || c == 'Y')
7159 return INPUT_STOP;
7160 if (c == 'n' || c == 'N')
7161 return INPUT_CANCEL;
7162 return INPUT_SKIP;
7165 static bool
7166 prompt_yesno(const char *prompt)
7168 char prompt2[SIZEOF_STR];
7170 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7171 return FALSE;
7173 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7176 static enum input_status
7177 read_prompt_handler(void *data, char *buf, int c)
7179 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7182 static char *
7183 read_prompt(const char *prompt)
7185 return prompt_input(prompt, read_prompt_handler, NULL);
7188 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7190 enum input_status status = INPUT_OK;
7191 int size = 0;
7193 while (items[size].text)
7194 size++;
7196 while (status == INPUT_OK) {
7197 const struct menu_item *item = &items[*selected];
7198 int key;
7199 int i;
7201 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7202 prompt, *selected + 1, size);
7203 if (item->hotkey)
7204 wprintw(status_win, "[%c] ", (char) item->hotkey);
7205 wprintw(status_win, "%s", item->text);
7206 wclrtoeol(status_win);
7208 key = get_input(COLS - 1);
7209 switch (key) {
7210 case KEY_RETURN:
7211 case KEY_ENTER:
7212 case '\n':
7213 status = INPUT_STOP;
7214 break;
7216 case KEY_LEFT:
7217 case KEY_UP:
7218 *selected = *selected - 1;
7219 if (*selected < 0)
7220 *selected = size - 1;
7221 break;
7223 case KEY_RIGHT:
7224 case KEY_DOWN:
7225 *selected = (*selected + 1) % size;
7226 break;
7228 case KEY_ESC:
7229 status = INPUT_CANCEL;
7230 break;
7232 default:
7233 for (i = 0; items[i].text; i++)
7234 if (items[i].hotkey == key) {
7235 *selected = i;
7236 status = INPUT_STOP;
7237 break;
7242 /* Clear the status window */
7243 status_empty = FALSE;
7244 report("");
7246 return status != INPUT_CANCEL;
7250 * Repository properties
7253 static struct ref **refs = NULL;
7254 static size_t refs_size = 0;
7255 static struct ref *refs_head = NULL;
7257 static struct ref_list **ref_lists = NULL;
7258 static size_t ref_lists_size = 0;
7260 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7261 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7262 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7264 static int
7265 compare_refs(const void *ref1_, const void *ref2_)
7267 const struct ref *ref1 = *(const struct ref **)ref1_;
7268 const struct ref *ref2 = *(const struct ref **)ref2_;
7270 if (ref1->tag != ref2->tag)
7271 return ref2->tag - ref1->tag;
7272 if (ref1->ltag != ref2->ltag)
7273 return ref2->ltag - ref2->ltag;
7274 if (ref1->head != ref2->head)
7275 return ref2->head - ref1->head;
7276 if (ref1->tracked != ref2->tracked)
7277 return ref2->tracked - ref1->tracked;
7278 if (ref1->remote != ref2->remote)
7279 return ref2->remote - ref1->remote;
7280 return strcmp(ref1->name, ref2->name);
7283 static void
7284 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7286 size_t i;
7288 for (i = 0; i < refs_size; i++)
7289 if (!visitor(data, refs[i]))
7290 break;
7293 static struct ref *
7294 get_ref_head()
7296 return refs_head;
7299 static struct ref_list *
7300 get_ref_list(const char *id)
7302 struct ref_list *list;
7303 size_t i;
7305 for (i = 0; i < ref_lists_size; i++)
7306 if (!strcmp(id, ref_lists[i]->id))
7307 return ref_lists[i];
7309 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7310 return NULL;
7311 list = calloc(1, sizeof(*list));
7312 if (!list)
7313 return NULL;
7315 for (i = 0; i < refs_size; i++) {
7316 if (!strcmp(id, refs[i]->id) &&
7317 realloc_refs_list(&list->refs, list->size, 1))
7318 list->refs[list->size++] = refs[i];
7321 if (!list->refs) {
7322 free(list);
7323 return NULL;
7326 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7327 ref_lists[ref_lists_size++] = list;
7328 return list;
7331 static int
7332 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7334 struct ref *ref = NULL;
7335 bool tag = FALSE;
7336 bool ltag = FALSE;
7337 bool remote = FALSE;
7338 bool tracked = FALSE;
7339 bool head = FALSE;
7340 int from = 0, to = refs_size - 1;
7342 if (!prefixcmp(name, "refs/tags/")) {
7343 if (!suffixcmp(name, namelen, "^{}")) {
7344 namelen -= 3;
7345 name[namelen] = 0;
7346 } else {
7347 ltag = TRUE;
7350 tag = TRUE;
7351 namelen -= STRING_SIZE("refs/tags/");
7352 name += STRING_SIZE("refs/tags/");
7354 } else if (!prefixcmp(name, "refs/remotes/")) {
7355 remote = TRUE;
7356 namelen -= STRING_SIZE("refs/remotes/");
7357 name += STRING_SIZE("refs/remotes/");
7358 tracked = !strcmp(opt_remote, name);
7360 } else if (!prefixcmp(name, "refs/heads/")) {
7361 namelen -= STRING_SIZE("refs/heads/");
7362 name += STRING_SIZE("refs/heads/");
7363 if (!strncmp(opt_head, name, namelen))
7364 return OK;
7366 } else if (!strcmp(name, "HEAD")) {
7367 head = TRUE;
7368 if (*opt_head) {
7369 namelen = strlen(opt_head);
7370 name = opt_head;
7374 /* If we are reloading or it's an annotated tag, replace the
7375 * previous SHA1 with the resolved commit id; relies on the fact
7376 * git-ls-remote lists the commit id of an annotated tag right
7377 * before the commit id it points to. */
7378 while (from <= to) {
7379 size_t pos = (to + from) / 2;
7380 int cmp = strcmp(name, refs[pos]->name);
7382 if (!cmp) {
7383 ref = refs[pos];
7384 break;
7387 if (cmp < 0)
7388 to = pos - 1;
7389 else
7390 from = pos + 1;
7393 if (!ref) {
7394 if (!realloc_refs(&refs, refs_size, 1))
7395 return ERR;
7396 ref = calloc(1, sizeof(*ref) + namelen);
7397 if (!ref)
7398 return ERR;
7399 memmove(refs + from + 1, refs + from,
7400 (refs_size - from) * sizeof(*refs));
7401 refs[from] = ref;
7402 strncpy(ref->name, name, namelen);
7403 refs_size++;
7406 ref->head = head;
7407 ref->tag = tag;
7408 ref->ltag = ltag;
7409 ref->remote = remote;
7410 ref->tracked = tracked;
7411 string_copy_rev(ref->id, id);
7413 if (head)
7414 refs_head = ref;
7415 return OK;
7418 static int
7419 load_refs(void)
7421 const char *head_argv[] = {
7422 "git", "symbolic-ref", "HEAD", NULL
7424 static const char *ls_remote_argv[SIZEOF_ARG] = {
7425 "git", "ls-remote", opt_git_dir, NULL
7427 static bool init = FALSE;
7428 size_t i;
7430 if (!init) {
7431 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7432 init = TRUE;
7435 if (!*opt_git_dir)
7436 return OK;
7438 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7439 !prefixcmp(opt_head, "refs/heads/")) {
7440 char *offset = opt_head + STRING_SIZE("refs/heads/");
7442 memmove(opt_head, offset, strlen(offset) + 1);
7445 refs_head = NULL;
7446 for (i = 0; i < refs_size; i++)
7447 refs[i]->id[0] = 0;
7449 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7450 return ERR;
7452 /* Update the ref lists to reflect changes. */
7453 for (i = 0; i < ref_lists_size; i++) {
7454 struct ref_list *list = ref_lists[i];
7455 size_t old, new;
7457 for (old = new = 0; old < list->size; old++)
7458 if (!strcmp(list->id, list->refs[old]->id))
7459 list->refs[new++] = list->refs[old];
7460 list->size = new;
7463 return OK;
7466 static void
7467 set_remote_branch(const char *name, const char *value, size_t valuelen)
7469 if (!strcmp(name, ".remote")) {
7470 string_ncopy(opt_remote, value, valuelen);
7472 } else if (*opt_remote && !strcmp(name, ".merge")) {
7473 size_t from = strlen(opt_remote);
7475 if (!prefixcmp(value, "refs/heads/"))
7476 value += STRING_SIZE("refs/heads/");
7478 if (!string_format_from(opt_remote, &from, "/%s", value))
7479 opt_remote[0] = 0;
7483 static void
7484 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7486 const char *argv[SIZEOF_ARG] = { name, "=" };
7487 int argc = 1 + (cmd == option_set_command);
7488 int error = ERR;
7490 if (!argv_from_string(argv, &argc, value))
7491 config_msg = "Too many option arguments";
7492 else
7493 error = cmd(argc, argv);
7495 if (error == ERR)
7496 warn("Option 'tig.%s': %s", name, config_msg);
7499 static bool
7500 set_environment_variable(const char *name, const char *value)
7502 size_t len = strlen(name) + 1 + strlen(value) + 1;
7503 char *env = malloc(len);
7505 if (env &&
7506 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7507 putenv(env) == 0)
7508 return TRUE;
7509 free(env);
7510 return FALSE;
7513 static void
7514 set_work_tree(const char *value)
7516 char cwd[SIZEOF_STR];
7518 if (!getcwd(cwd, sizeof(cwd)))
7519 die("Failed to get cwd path: %s", strerror(errno));
7520 if (chdir(opt_git_dir) < 0)
7521 die("Failed to chdir(%s): %s", strerror(errno));
7522 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7523 die("Failed to get git path: %s", strerror(errno));
7524 if (chdir(cwd) < 0)
7525 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7526 if (chdir(value) < 0)
7527 die("Failed to chdir(%s): %s", value, strerror(errno));
7528 if (!getcwd(cwd, sizeof(cwd)))
7529 die("Failed to get cwd path: %s", strerror(errno));
7530 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7531 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7532 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7533 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7534 opt_is_inside_work_tree = TRUE;
7537 static int
7538 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7540 if (!strcmp(name, "i18n.commitencoding"))
7541 string_ncopy(opt_encoding, value, valuelen);
7543 else if (!strcmp(name, "core.editor"))
7544 string_ncopy(opt_editor, value, valuelen);
7546 else if (!strcmp(name, "core.worktree"))
7547 set_work_tree(value);
7549 else if (!prefixcmp(name, "tig.color."))
7550 set_repo_config_option(name + 10, value, option_color_command);
7552 else if (!prefixcmp(name, "tig.bind."))
7553 set_repo_config_option(name + 9, value, option_bind_command);
7555 else if (!prefixcmp(name, "tig."))
7556 set_repo_config_option(name + 4, value, option_set_command);
7558 else if (*opt_head && !prefixcmp(name, "branch.") &&
7559 !strncmp(name + 7, opt_head, strlen(opt_head)))
7560 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7562 return OK;
7565 static int
7566 load_git_config(void)
7568 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7570 return io_run_load(config_list_argv, "=", read_repo_config_option);
7573 static int
7574 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7576 if (!opt_git_dir[0]) {
7577 string_ncopy(opt_git_dir, name, namelen);
7579 } else if (opt_is_inside_work_tree == -1) {
7580 /* This can be 3 different values depending on the
7581 * version of git being used. If git-rev-parse does not
7582 * understand --is-inside-work-tree it will simply echo
7583 * the option else either "true" or "false" is printed.
7584 * Default to true for the unknown case. */
7585 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7587 } else if (*name == '.') {
7588 string_ncopy(opt_cdup, name, namelen);
7590 } else {
7591 string_ncopy(opt_prefix, name, namelen);
7594 return OK;
7597 static int
7598 load_repo_info(void)
7600 const char *rev_parse_argv[] = {
7601 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7602 "--show-cdup", "--show-prefix", NULL
7605 return io_run_load(rev_parse_argv, "=", read_repo_info);
7610 * Main
7613 static const char usage[] =
7614 "tig " TIG_VERSION " (" __DATE__ ")\n"
7615 "\n"
7616 "Usage: tig [options] [revs] [--] [paths]\n"
7617 " or: tig show [options] [revs] [--] [paths]\n"
7618 " or: tig blame [rev] path\n"
7619 " or: tig status\n"
7620 " or: tig < [git command output]\n"
7621 "\n"
7622 "Options:\n"
7623 " -v, --version Show version and exit\n"
7624 " -h, --help Show help message and exit";
7626 static void __NORETURN
7627 quit(int sig)
7629 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7630 if (cursed)
7631 endwin();
7632 exit(0);
7635 static void __NORETURN
7636 die(const char *err, ...)
7638 va_list args;
7640 endwin();
7642 va_start(args, err);
7643 fputs("tig: ", stderr);
7644 vfprintf(stderr, err, args);
7645 fputs("\n", stderr);
7646 va_end(args);
7648 exit(1);
7651 static void
7652 warn(const char *msg, ...)
7654 va_list args;
7656 va_start(args, msg);
7657 fputs("tig warning: ", stderr);
7658 vfprintf(stderr, msg, args);
7659 fputs("\n", stderr);
7660 va_end(args);
7663 static enum request
7664 parse_options(int argc, const char *argv[])
7666 enum request request = REQ_VIEW_MAIN;
7667 const char *subcommand;
7668 bool seen_dashdash = FALSE;
7669 /* XXX: This is vulnerable to the user overriding options
7670 * required for the main view parser. */
7671 const char *custom_argv[SIZEOF_ARG] = {
7672 "git", "log", "--no-color", "--pretty=raw", "--parents",
7673 "--topo-order", NULL
7675 int i, j = 6;
7677 if (!isatty(STDIN_FILENO)) {
7678 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7679 return REQ_VIEW_PAGER;
7682 if (argc <= 1)
7683 return REQ_NONE;
7685 subcommand = argv[1];
7686 if (!strcmp(subcommand, "status")) {
7687 if (argc > 2)
7688 warn("ignoring arguments after `%s'", subcommand);
7689 return REQ_VIEW_STATUS;
7691 } else if (!strcmp(subcommand, "blame")) {
7692 if (argc <= 2 || argc > 4)
7693 die("invalid number of options to blame\n\n%s", usage);
7695 i = 2;
7696 if (argc == 4) {
7697 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7698 i++;
7701 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7702 return REQ_VIEW_BLAME;
7704 } else if (!strcmp(subcommand, "show")) {
7705 request = REQ_VIEW_DIFF;
7707 } else {
7708 subcommand = NULL;
7711 if (subcommand) {
7712 custom_argv[1] = subcommand;
7713 j = 2;
7716 for (i = 1 + !!subcommand; i < argc; i++) {
7717 const char *opt = argv[i];
7719 if (seen_dashdash || !strcmp(opt, "--")) {
7720 seen_dashdash = TRUE;
7722 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7723 printf("tig version %s\n", TIG_VERSION);
7724 quit(0);
7726 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7727 printf("%s\n", usage);
7728 quit(0);
7731 custom_argv[j++] = opt;
7732 if (j >= ARRAY_SIZE(custom_argv))
7733 die("command too long");
7736 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7737 die("Failed to format arguments");
7739 return request;
7743 main(int argc, const char *argv[])
7745 const char *codeset = "UTF-8";
7746 enum request request = parse_options(argc, argv);
7747 struct view *view;
7748 size_t i;
7750 signal(SIGINT, quit);
7751 signal(SIGPIPE, SIG_IGN);
7753 if (setlocale(LC_ALL, "")) {
7754 codeset = nl_langinfo(CODESET);
7757 if (load_repo_info() == ERR)
7758 die("Failed to load repo info.");
7760 if (load_options() == ERR)
7761 die("Failed to load user config.");
7763 if (load_git_config() == ERR)
7764 die("Failed to load repo config.");
7766 /* Require a git repository unless when running in pager mode. */
7767 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7768 die("Not a git repository");
7770 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7771 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7772 if (opt_iconv_in == ICONV_NONE)
7773 die("Failed to initialize character set conversion");
7776 if (codeset && strcmp(codeset, "UTF-8")) {
7777 opt_iconv_out = iconv_open(codeset, "UTF-8");
7778 if (opt_iconv_out == ICONV_NONE)
7779 die("Failed to initialize character set conversion");
7782 if (load_refs() == ERR)
7783 die("Failed to load refs.");
7785 foreach_view (view, i)
7786 argv_from_env(view->ops->argv, view->cmd_env);
7788 init_display();
7790 if (request != REQ_NONE)
7791 open_view(NULL, request, OPEN_PREPARED);
7792 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7794 while (view_driver(display[current_view], request)) {
7795 int key = get_input(0);
7797 view = display[current_view];
7798 request = get_keybinding(view->keymap, key);
7800 /* Some low-level request handling. This keeps access to
7801 * status_win restricted. */
7802 switch (request) {
7803 case REQ_PROMPT:
7805 char *cmd = read_prompt(":");
7807 if (cmd && isdigit(*cmd)) {
7808 int lineno = view->lineno + 1;
7810 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7811 select_view_line(view, lineno - 1);
7812 report("");
7813 } else {
7814 report("Unable to parse '%s' as a line number", cmd);
7817 } else if (cmd) {
7818 struct view *next = VIEW(REQ_VIEW_PAGER);
7819 const char *argv[SIZEOF_ARG] = { "git" };
7820 int argc = 1;
7822 /* When running random commands, initially show the
7823 * command in the title. However, it maybe later be
7824 * overwritten if a commit line is selected. */
7825 string_ncopy(next->ref, cmd, strlen(cmd));
7827 if (!argv_from_string(argv, &argc, cmd)) {
7828 report("Too many arguments");
7829 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7830 report("Failed to format command");
7831 } else {
7832 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7836 request = REQ_NONE;
7837 break;
7839 case REQ_SEARCH:
7840 case REQ_SEARCH_BACK:
7842 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7843 char *search = read_prompt(prompt);
7845 if (search)
7846 string_ncopy(opt_search, search, strlen(search));
7847 else if (*opt_search)
7848 request = request == REQ_SEARCH ?
7849 REQ_FIND_NEXT :
7850 REQ_FIND_PREV;
7851 else
7852 request = REQ_NONE;
7853 break;
7855 default:
7856 break;
7860 quit(0);
7862 return 0;