1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
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.
7 *
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.
12 */
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. */
130 };
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. */
136 };
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. */
147 };
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
156 };
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;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172 * Allocation helpers ... Entering macro hell to never be seen again.
173 */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 static type * \
177 name(type **mem, size_t size, size_t increase) \
178 { \
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; \
182 \
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; \
187 } \
188 \
189 return tmp; \
190 }
192 /*
193 * String helpers
194 */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198 {
199 if (srclen > dstlen - 1)
200 srclen = dstlen - 1;
202 strncpy(dst, src, srclen);
203 dst[srclen] = 0;
204 }
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)
222 {
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];
235 }
236 }
238 dst[size] = 0;
239 }
241 static char *
242 chomp_string(char *name)
243 {
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;
254 }
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
258 {
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;
270 }
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)
280 {
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];
295 }
297 return 0;
298 }
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;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
313 {
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] = '-';
321 }
323 buf[bufpos] = 0;
324 return buf;
325 }
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)
331 {
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;
339 }
341 return FALSE;
342 }
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)
352 {
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;
357 }
360 /*
361 * Unicode / UTF-8 handling
362 *
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.
366 */
368 static inline int
369 unicode_width(unsigned long c, int tab_size)
370 {
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;
390 }
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,
403 };
405 static inline unsigned char
406 utf8_char_length(const char *string, const char *end)
407 {
408 int c = *(unsigned char *) string;
410 return utf8_bytes[c];
411 }
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)
416 {
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;
455 }
457 /* Invalid characters could return the special 0xfffd value but NUL
458 * should be just as good. */
459 return unicode > 0xffff ? 0 : unicode;
460 }
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.
466 *
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)
470 {
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;
499 }
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;
507 }
508 break;
509 }
511 string += bytes;
512 last_bytes = ucwidth ? bytes : 0;
513 last_ucwidth = ucwidth;
514 }
516 return string - *start;
517 }
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_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534 DATE_INFO
535 #undef DATE_
536 };
538 struct time {
539 time_t sec;
540 int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
544 {
545 return t1->sec - t2->sec;
546 }
548 static const char *
549 mkdate(const struct time *time, enum date date)
550 {
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 },
559 };
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;
584 }
585 }
587 gmtime_r(&time->sec, &tm);
588 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
589 }
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
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606 AUTHOR_VALUES
607 #undef AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
612 {
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++;
631 }
632 }
634 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635 if (i < sizeof(initials) - 1)
636 initials[i++] = *author;
637 }
639 initials[i++] = 0;
640 }
642 return initials;
643 }
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
648 {
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);
657 }
659 if (*argc < SIZEOF_ARG)
660 argv[*argc] = NULL;
661 return *argc < SIZEOF_ARG;
662 }
664 static bool
665 argv_from_env(const char **argv, const char *name)
666 {
667 char *env = argv ? getenv(name) : NULL;
668 int argc = 0;
670 if (env && *env)
671 env = strdup(env);
672 return !env || argv_from_string(argv, &argc, env);
673 }
676 /*
677 * Executing external commands.
678 */
680 enum io_type {
681 IO_FD, /* File descriptor based IO. */
682 IO_BG, /* Execute command in the background. */
683 IO_FG, /* Execute command with same std{in,out,err}. */
684 IO_RD, /* Read only fork+exec IO. */
685 IO_WR, /* Write only fork+exec IO. */
686 IO_AP, /* Append fork+exec output to file. */
687 };
689 struct io {
690 enum io_type type; /* The requested type of pipe. */
691 const char *dir; /* Directory from which to execute. */
692 pid_t pid; /* PID of spawned process. */
693 int pipe; /* Pipe end for reading or writing. */
694 int error; /* Error status. */
695 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
696 char *buf; /* Read buffer. */
697 size_t bufalloc; /* Allocated buffer size. */
698 size_t bufsize; /* Buffer content size. */
699 char *bufpos; /* Current buffer position. */
700 unsigned int eof:1; /* Has end of file been reached. */
701 };
703 static void
704 io_reset(struct io *io)
705 {
706 io->pipe = -1;
707 io->pid = 0;
708 io->buf = io->bufpos = NULL;
709 io->bufalloc = io->bufsize = 0;
710 io->error = 0;
711 io->eof = 0;
712 }
714 static void
715 io_init(struct io *io, const char *dir, enum io_type type)
716 {
717 io_reset(io);
718 io->type = type;
719 io->dir = dir;
720 }
722 static bool
723 io_format(struct io *io, const char *dir, enum io_type type,
724 const char *argv[], enum format_flags flags)
725 {
726 io_init(io, dir, type);
727 return format_argv(io->argv, argv, flags);
728 }
730 static bool
731 io_open(struct io *io, const char *fmt, ...)
732 {
733 char name[SIZEOF_STR] = "";
734 bool fits;
735 va_list args;
737 io_init(io, NULL, IO_FD);
739 va_start(args, fmt);
740 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
741 va_end(args);
743 if (!fits) {
744 io->error = ENAMETOOLONG;
745 return FALSE;
746 }
747 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
748 if (io->pipe == -1)
749 io->error = errno;
750 return io->pipe != -1;
751 }
753 static bool
754 io_kill(struct io *io)
755 {
756 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
757 }
759 static bool
760 io_done(struct io *io)
761 {
762 pid_t pid = io->pid;
764 if (io->pipe != -1)
765 close(io->pipe);
766 free(io->buf);
767 io_reset(io);
769 while (pid > 0) {
770 int status;
771 pid_t waiting = waitpid(pid, &status, 0);
773 if (waiting < 0) {
774 if (errno == EINTR)
775 continue;
776 io->error = errno;
777 return FALSE;
778 }
780 return waiting == pid &&
781 !WIFSIGNALED(status) &&
782 WIFEXITED(status) &&
783 !WEXITSTATUS(status);
784 }
786 return TRUE;
787 }
789 static bool
790 io_start(struct io *io)
791 {
792 int pipefds[2] = { -1, -1 };
794 if (io->type == IO_FD)
795 return TRUE;
797 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
798 io->error = errno;
799 return FALSE;
800 } else if (io->type == IO_AP) {
801 pipefds[1] = io->pipe;
802 }
804 if ((io->pid = fork())) {
805 if (io->pid == -1)
806 io->error = errno;
807 if (pipefds[!(io->type == IO_WR)] != -1)
808 close(pipefds[!(io->type == IO_WR)]);
809 if (io->pid != -1) {
810 io->pipe = pipefds[!!(io->type == IO_WR)];
811 return TRUE;
812 }
814 } else {
815 if (io->type != IO_FG) {
816 int devnull = open("/dev/null", O_RDWR);
817 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
818 int writefd = (io->type == IO_RD || io->type == IO_AP)
819 ? pipefds[1] : devnull;
821 dup2(readfd, STDIN_FILENO);
822 dup2(writefd, STDOUT_FILENO);
823 dup2(devnull, STDERR_FILENO);
825 close(devnull);
826 if (pipefds[0] != -1)
827 close(pipefds[0]);
828 if (pipefds[1] != -1)
829 close(pipefds[1]);
830 }
832 if (io->dir && *io->dir && chdir(io->dir) == -1)
833 exit(errno);
835 execvp(io->argv[0], (char *const*) io->argv);
836 exit(errno);
837 }
839 if (pipefds[!!(io->type == IO_WR)] != -1)
840 close(pipefds[!!(io->type == IO_WR)]);
841 return FALSE;
842 }
844 static bool
845 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
846 {
847 io_init(io, dir, type);
848 if (!format_argv(io->argv, argv, FORMAT_NONE))
849 return FALSE;
850 return io_start(io);
851 }
853 static int
854 io_complete(struct io *io)
855 {
856 return io_start(io) && io_done(io);
857 }
859 static int
860 io_run_bg(const char **argv)
861 {
862 struct io io = {};
864 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
865 return FALSE;
866 return io_complete(&io);
867 }
869 static bool
870 io_run_fg(const char **argv, const char *dir)
871 {
872 struct io io = {};
874 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
875 return FALSE;
876 return io_complete(&io);
877 }
879 static bool
880 io_run_append(const char **argv, enum format_flags flags, int fd)
881 {
882 struct io io = {};
884 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
885 close(fd);
886 return FALSE;
887 }
889 io.pipe = fd;
890 return io_complete(&io);
891 }
893 static bool
894 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
895 {
896 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
897 }
899 static bool
900 io_eof(struct io *io)
901 {
902 return io->eof;
903 }
905 static int
906 io_error(struct io *io)
907 {
908 return io->error;
909 }
911 static char *
912 io_strerror(struct io *io)
913 {
914 return strerror(io->error);
915 }
917 static bool
918 io_can_read(struct io *io)
919 {
920 struct timeval tv = { 0, 500 };
921 fd_set fds;
923 FD_ZERO(&fds);
924 FD_SET(io->pipe, &fds);
926 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
927 }
929 static ssize_t
930 io_read(struct io *io, void *buf, size_t bufsize)
931 {
932 do {
933 ssize_t readsize = read(io->pipe, buf, bufsize);
935 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
936 continue;
937 else if (readsize == -1)
938 io->error = errno;
939 else if (readsize == 0)
940 io->eof = 1;
941 return readsize;
942 } while (1);
943 }
945 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
947 static char *
948 io_get(struct io *io, int c, bool can_read)
949 {
950 char *eol;
951 ssize_t readsize;
953 while (TRUE) {
954 if (io->bufsize > 0) {
955 eol = memchr(io->bufpos, c, io->bufsize);
956 if (eol) {
957 char *line = io->bufpos;
959 *eol = 0;
960 io->bufpos = eol + 1;
961 io->bufsize -= io->bufpos - line;
962 return line;
963 }
964 }
966 if (io_eof(io)) {
967 if (io->bufsize) {
968 io->bufpos[io->bufsize] = 0;
969 io->bufsize = 0;
970 return io->bufpos;
971 }
972 return NULL;
973 }
975 if (!can_read)
976 return NULL;
978 if (io->bufsize > 0 && io->bufpos > io->buf)
979 memmove(io->buf, io->bufpos, io->bufsize);
981 if (io->bufalloc == io->bufsize) {
982 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
983 return NULL;
984 io->bufalloc += BUFSIZ;
985 }
987 io->bufpos = io->buf;
988 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
989 if (io_error(io))
990 return NULL;
991 io->bufsize += readsize;
992 }
993 }
995 static bool
996 io_write(struct io *io, const void *buf, size_t bufsize)
997 {
998 size_t written = 0;
1000 while (!io_error(io) && written < bufsize) {
1001 ssize_t size;
1003 size = write(io->pipe, buf + written, bufsize - written);
1004 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1005 continue;
1006 else if (size == -1)
1007 io->error = errno;
1008 else
1009 written += size;
1010 }
1012 return written == bufsize;
1013 }
1015 static bool
1016 io_read_buf(struct io *io, char buf[], size_t bufsize)
1017 {
1018 char *result = io_get(io, '\n', TRUE);
1020 if (result) {
1021 result = chomp_string(result);
1022 string_ncopy_do(buf, bufsize, result, strlen(result));
1023 }
1025 return io_done(io) && result;
1026 }
1028 static bool
1029 io_run_buf(const char **argv, char buf[], size_t bufsize)
1030 {
1031 struct io io = {};
1033 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1034 && io_read_buf(&io, buf, bufsize);
1035 }
1037 static int
1038 io_load(struct io *io, const char *separators,
1039 int (*read_property)(char *, size_t, char *, size_t))
1040 {
1041 char *name;
1042 int state = OK;
1044 if (!io_start(io))
1045 return ERR;
1047 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1048 char *value;
1049 size_t namelen;
1050 size_t valuelen;
1052 name = chomp_string(name);
1053 namelen = strcspn(name, separators);
1055 if (name[namelen]) {
1056 name[namelen] = 0;
1057 value = chomp_string(name + namelen + 1);
1058 valuelen = strlen(value);
1060 } else {
1061 value = "";
1062 valuelen = 0;
1063 }
1065 state = read_property(name, namelen, value, valuelen);
1066 }
1068 if (state != ERR && io_error(io))
1069 state = ERR;
1070 io_done(io);
1072 return state;
1073 }
1075 static int
1076 io_run_load(const char **argv, const char *separators,
1077 int (*read_property)(char *, size_t, char *, size_t))
1078 {
1079 struct io io = {};
1081 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1082 ? io_load(&io, separators, read_property) : ERR;
1083 }
1086 /*
1087 * User requests
1088 */
1090 #define REQ_INFO \
1091 /* XXX: Keep the view request first and in sync with views[]. */ \
1092 REQ_GROUP("View switching") \
1093 REQ_(VIEW_MAIN, "Show main view"), \
1094 REQ_(VIEW_DIFF, "Show diff view"), \
1095 REQ_(VIEW_LOG, "Show log view"), \
1096 REQ_(VIEW_TREE, "Show tree view"), \
1097 REQ_(VIEW_BLOB, "Show blob view"), \
1098 REQ_(VIEW_BLAME, "Show blame view"), \
1099 REQ_(VIEW_BRANCH, "Show branch view"), \
1100 REQ_(VIEW_HELP, "Show help page"), \
1101 REQ_(VIEW_PAGER, "Show pager view"), \
1102 REQ_(VIEW_STATUS, "Show status view"), \
1103 REQ_(VIEW_STAGE, "Show stage view"), \
1104 \
1105 REQ_GROUP("View manipulation") \
1106 REQ_(ENTER, "Enter current line and scroll"), \
1107 REQ_(NEXT, "Move to next"), \
1108 REQ_(PREVIOUS, "Move to previous"), \
1109 REQ_(PARENT, "Move to parent"), \
1110 REQ_(VIEW_NEXT, "Move focus to next view"), \
1111 REQ_(REFRESH, "Reload and refresh"), \
1112 REQ_(MAXIMIZE, "Maximize the current view"), \
1113 REQ_(VIEW_CLOSE, "Close the current view"), \
1114 REQ_(QUIT, "Close all views and quit"), \
1115 \
1116 REQ_GROUP("View specific requests") \
1117 REQ_(STATUS_UPDATE, "Update file status"), \
1118 REQ_(STATUS_REVERT, "Revert file changes"), \
1119 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1120 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1121 \
1122 REQ_GROUP("Cursor navigation") \
1123 REQ_(MOVE_UP, "Move cursor one line up"), \
1124 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1125 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1126 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1127 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1128 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1129 \
1130 REQ_GROUP("Scrolling") \
1131 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1132 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1133 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1134 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1135 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1136 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1137 \
1138 REQ_GROUP("Searching") \
1139 REQ_(SEARCH, "Search the view"), \
1140 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1141 REQ_(FIND_NEXT, "Find next search match"), \
1142 REQ_(FIND_PREV, "Find previous search match"), \
1143 \
1144 REQ_GROUP("Option manipulation") \
1145 REQ_(OPTIONS, "Open option menu"), \
1146 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1147 REQ_(TOGGLE_DATE, "Toggle date display"), \
1148 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1149 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1150 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1151 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1152 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1153 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1154 \
1155 REQ_GROUP("Misc") \
1156 REQ_(PROMPT, "Bring up the prompt"), \
1157 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1158 REQ_(SHOW_VERSION, "Show version information"), \
1159 REQ_(STOP_LOADING, "Stop all loading views"), \
1160 REQ_(EDIT, "Open in editor"), \
1161 REQ_(NONE, "Do nothing")
1164 /* User action requests. */
1165 enum request {
1166 #define REQ_GROUP(help)
1167 #define REQ_(req, help) REQ_##req
1169 /* Offset all requests to avoid conflicts with ncurses getch values. */
1170 REQ_OFFSET = KEY_MAX + 1,
1171 REQ_INFO
1173 #undef REQ_GROUP
1174 #undef REQ_
1175 };
1177 struct request_info {
1178 enum request request;
1179 const char *name;
1180 int namelen;
1181 const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1194 {
1195 int namelen = strlen(name);
1196 int i;
1198 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199 if (enum_equals(req_info[i], name, namelen))
1200 return req_info[i].request;
1202 return REQ_NONE;
1203 }
1206 /*
1207 * Options
1208 */
1210 /* Option and state variables. */
1211 static enum date opt_date = DATE_DEFAULT;
1212 static enum author opt_author = AUTHOR_DEFAULT;
1213 static bool opt_line_number = FALSE;
1214 static bool opt_line_graphics = TRUE;
1215 static bool opt_rev_graph = FALSE;
1216 static bool opt_show_refs = TRUE;
1217 static int opt_num_interval = 5;
1218 static double opt_hscroll = 0.50;
1219 static double opt_scale_split_view = 2.0 / 3.0;
1220 static int opt_tab_size = 8;
1221 static int opt_author_cols = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR] = "";
1223 static char opt_file[SIZEOF_STR] = "";
1224 static char opt_ref[SIZEOF_REF] = "";
1225 static char opt_head[SIZEOF_REF] = "";
1226 static char opt_remote[SIZEOF_REF] = "";
1227 static char opt_encoding[20] = "UTF-8";
1228 static iconv_t opt_iconv_in = ICONV_NONE;
1229 static iconv_t opt_iconv_out = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR] = "";
1231 static char opt_cdup[SIZEOF_STR] = "";
1232 static char opt_prefix[SIZEOF_STR] = "";
1233 static char opt_git_dir[SIZEOF_STR] = "";
1234 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR] = "";
1236 static FILE *opt_tty = NULL;
1238 #define is_initial_commit() (!get_ref_head())
1239 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243 * Line-oriented content detection.
1244 */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1261 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1262 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1263 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1268 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1269 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1276 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1277 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1278 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1279 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1280 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1281 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1282 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1283 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1284 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1285 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1288 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1289 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1292 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1293 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1294 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1295 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1296 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1297 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1298 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1302 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1303 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307 LINE_##type
1308 LINE_INFO,
1309 LINE_NONE
1310 #undef LINE
1311 };
1313 struct line_info {
1314 const char *name; /* Option name. */
1315 int namelen; /* Size of option name. */
1316 const char *line; /* The start of line to match. */
1317 int linelen; /* Size of string to match. */
1318 int fg, bg, attr; /* Color and text attributes for the lines. */
1319 };
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324 LINE_INFO
1325 #undef LINE
1326 };
1328 static enum line_type
1329 get_line_type(const char *line)
1330 {
1331 int linelen = strlen(line);
1332 enum line_type type;
1334 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335 /* Case insensitive search matches Signed-off-by lines better. */
1336 if (linelen >= line_info[type].linelen &&
1337 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338 return type;
1340 return LINE_DEFAULT;
1341 }
1343 static inline int
1344 get_line_attr(enum line_type type)
1345 {
1346 assert(type < ARRAY_SIZE(line_info));
1347 return COLOR_PAIR(type) | line_info[type].attr;
1348 }
1350 static struct line_info *
1351 get_line_info(const char *name)
1352 {
1353 size_t namelen = strlen(name);
1354 enum line_type type;
1356 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357 if (enum_equals(line_info[type], name, namelen))
1358 return &line_info[type];
1360 return NULL;
1361 }
1363 static void
1364 init_colors(void)
1365 {
1366 int default_bg = line_info[LINE_DEFAULT].bg;
1367 int default_fg = line_info[LINE_DEFAULT].fg;
1368 enum line_type type;
1370 start_color();
1372 if (assume_default_colors(default_fg, default_bg) == ERR) {
1373 default_bg = COLOR_BLACK;
1374 default_fg = COLOR_WHITE;
1375 }
1377 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378 struct line_info *info = &line_info[type];
1379 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382 init_pair(type, fg, bg);
1383 }
1384 }
1386 struct line {
1387 enum line_type type;
1389 /* State flags */
1390 unsigned int selected:1;
1391 unsigned int dirty:1;
1392 unsigned int cleareol:1;
1393 unsigned int other:16;
1395 void *data; /* User data */
1396 };
1399 /*
1400 * Keys
1401 */
1403 struct keybinding {
1404 int alias;
1405 enum request request;
1406 };
1408 static const struct keybinding default_keybindings[] = {
1409 /* View switching */
1410 { 'm', REQ_VIEW_MAIN },
1411 { 'd', REQ_VIEW_DIFF },
1412 { 'l', REQ_VIEW_LOG },
1413 { 't', REQ_VIEW_TREE },
1414 { 'f', REQ_VIEW_BLOB },
1415 { 'B', REQ_VIEW_BLAME },
1416 { 'H', REQ_VIEW_BRANCH },
1417 { 'p', REQ_VIEW_PAGER },
1418 { 'h', REQ_VIEW_HELP },
1419 { 'S', REQ_VIEW_STATUS },
1420 { 'c', REQ_VIEW_STAGE },
1422 /* View manipulation */
1423 { 'q', REQ_VIEW_CLOSE },
1424 { KEY_TAB, REQ_VIEW_NEXT },
1425 { KEY_RETURN, REQ_ENTER },
1426 { KEY_UP, REQ_PREVIOUS },
1427 { KEY_DOWN, REQ_NEXT },
1428 { 'R', REQ_REFRESH },
1429 { KEY_F(5), REQ_REFRESH },
1430 { 'O', REQ_MAXIMIZE },
1432 /* Cursor navigation */
1433 { 'k', REQ_MOVE_UP },
1434 { 'j', REQ_MOVE_DOWN },
1435 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1436 { KEY_END, REQ_MOVE_LAST_LINE },
1437 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1438 { ' ', REQ_MOVE_PAGE_DOWN },
1439 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1440 { 'b', REQ_MOVE_PAGE_UP },
1441 { '-', REQ_MOVE_PAGE_UP },
1443 /* Scrolling */
1444 { KEY_LEFT, REQ_SCROLL_LEFT },
1445 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1446 { KEY_IC, REQ_SCROLL_LINE_UP },
1447 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1448 { 'w', REQ_SCROLL_PAGE_UP },
1449 { 's', REQ_SCROLL_PAGE_DOWN },
1451 /* Searching */
1452 { '/', REQ_SEARCH },
1453 { '?', REQ_SEARCH_BACK },
1454 { 'n', REQ_FIND_NEXT },
1455 { 'N', REQ_FIND_PREV },
1457 /* Misc */
1458 { 'Q', REQ_QUIT },
1459 { 'z', REQ_STOP_LOADING },
1460 { 'v', REQ_SHOW_VERSION },
1461 { 'r', REQ_SCREEN_REDRAW },
1462 { 'o', REQ_OPTIONS },
1463 { '.', REQ_TOGGLE_LINENO },
1464 { 'D', REQ_TOGGLE_DATE },
1465 { 'A', REQ_TOGGLE_AUTHOR },
1466 { 'g', REQ_TOGGLE_REV_GRAPH },
1467 { 'F', REQ_TOGGLE_REFS },
1468 { 'I', REQ_TOGGLE_SORT_ORDER },
1469 { 'i', REQ_TOGGLE_SORT_FIELD },
1470 { ':', REQ_PROMPT },
1471 { 'u', REQ_STATUS_UPDATE },
1472 { '!', REQ_STATUS_REVERT },
1473 { 'M', REQ_STATUS_MERGE },
1474 { '@', REQ_STAGE_NEXT },
1475 { ',', REQ_PARENT },
1476 { 'e', REQ_EDIT },
1477 };
1479 #define KEYMAP_INFO \
1480 KEYMAP_(GENERIC), \
1481 KEYMAP_(MAIN), \
1482 KEYMAP_(DIFF), \
1483 KEYMAP_(LOG), \
1484 KEYMAP_(TREE), \
1485 KEYMAP_(BLOB), \
1486 KEYMAP_(BLAME), \
1487 KEYMAP_(BRANCH), \
1488 KEYMAP_(PAGER), \
1489 KEYMAP_(HELP), \
1490 KEYMAP_(STATUS), \
1491 KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495 KEYMAP_INFO
1496 #undef KEYMAP_
1497 };
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501 KEYMAP_INFO
1502 #undef KEYMAP_
1503 };
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508 struct keybinding *data;
1509 size_t size;
1510 };
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1516 {
1517 struct keybinding_table *table = &keybindings[keymap];
1519 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520 if (!table->data)
1521 die("Failed to allocate keybinding");
1522 table->data[table->size].alias = key;
1523 table->data[table->size++].request = request;
1524 }
1526 /* Looks for a key binding first in the given map, then in the generic map, and
1527 * lastly in the default keybindings. */
1528 static enum request
1529 get_keybinding(enum keymap keymap, int key)
1530 {
1531 size_t i;
1533 for (i = 0; i < keybindings[keymap].size; i++)
1534 if (keybindings[keymap].data[i].alias == key)
1535 return keybindings[keymap].data[i].request;
1537 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1538 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1539 return keybindings[KEYMAP_GENERIC].data[i].request;
1541 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542 if (default_keybindings[i].alias == key)
1543 return default_keybindings[i].request;
1545 return (enum request) key;
1546 }
1549 struct key {
1550 const char *name;
1551 int value;
1552 };
1554 static const struct key key_table[] = {
1555 { "Enter", KEY_RETURN },
1556 { "Space", ' ' },
1557 { "Backspace", KEY_BACKSPACE },
1558 { "Tab", KEY_TAB },
1559 { "Escape", KEY_ESC },
1560 { "Left", KEY_LEFT },
1561 { "Right", KEY_RIGHT },
1562 { "Up", KEY_UP },
1563 { "Down", KEY_DOWN },
1564 { "Insert", KEY_IC },
1565 { "Delete", KEY_DC },
1566 { "Hash", '#' },
1567 { "Home", KEY_HOME },
1568 { "End", KEY_END },
1569 { "PageUp", KEY_PPAGE },
1570 { "PageDown", KEY_NPAGE },
1571 { "F1", KEY_F(1) },
1572 { "F2", KEY_F(2) },
1573 { "F3", KEY_F(3) },
1574 { "F4", KEY_F(4) },
1575 { "F5", KEY_F(5) },
1576 { "F6", KEY_F(6) },
1577 { "F7", KEY_F(7) },
1578 { "F8", KEY_F(8) },
1579 { "F9", KEY_F(9) },
1580 { "F10", KEY_F(10) },
1581 { "F11", KEY_F(11) },
1582 { "F12", KEY_F(12) },
1583 };
1585 static int
1586 get_key_value(const char *name)
1587 {
1588 int i;
1590 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1591 if (!strcasecmp(key_table[i].name, name))
1592 return key_table[i].value;
1594 if (strlen(name) == 1 && isprint(*name))
1595 return (int) *name;
1597 return ERR;
1598 }
1600 static const char *
1601 get_key_name(int key_value)
1602 {
1603 static char key_char[] = "'X'";
1604 const char *seq = NULL;
1605 int key;
1607 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1608 if (key_table[key].value == key_value)
1609 seq = key_table[key].name;
1611 if (seq == NULL &&
1612 key_value < 127 &&
1613 isprint(key_value)) {
1614 key_char[1] = (char) key_value;
1615 seq = key_char;
1616 }
1618 return seq ? seq : "(no key)";
1619 }
1621 static bool
1622 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1623 {
1624 const char *sep = *pos > 0 ? ", " : "";
1625 const char *keyname = get_key_name(keybinding->alias);
1627 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1628 }
1630 static bool
1631 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1632 enum keymap keymap, bool all)
1633 {
1634 int i;
1636 for (i = 0; i < keybindings[keymap].size; i++) {
1637 if (keybindings[keymap].data[i].request == request) {
1638 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1639 return FALSE;
1640 if (!all)
1641 break;
1642 }
1643 }
1645 return TRUE;
1646 }
1648 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1650 static const char *
1651 get_keys(enum keymap keymap, enum request request, bool all)
1652 {
1653 static char buf[BUFSIZ];
1654 size_t pos = 0;
1655 int i;
1657 buf[pos] = 0;
1659 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1660 return "Too many keybindings!";
1661 if (pos > 0 && !all)
1662 return buf;
1664 if (keymap != KEYMAP_GENERIC) {
1665 /* Only the generic keymap includes the default keybindings when
1666 * listing all keys. */
1667 if (all)
1668 return buf;
1670 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1671 return "Too many keybindings!";
1672 if (pos)
1673 return buf;
1674 }
1676 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1677 if (default_keybindings[i].request == request) {
1678 if (!append_key(buf, &pos, &default_keybindings[i]))
1679 return "Too many keybindings!";
1680 if (!all)
1681 return buf;
1682 }
1683 }
1685 return buf;
1686 }
1688 struct run_request {
1689 enum keymap keymap;
1690 int key;
1691 const char *argv[SIZEOF_ARG];
1692 };
1694 static struct run_request *run_request;
1695 static size_t run_requests;
1697 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1699 static enum request
1700 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1701 {
1702 struct run_request *req;
1704 if (argc >= ARRAY_SIZE(req->argv) - 1)
1705 return REQ_NONE;
1707 if (!realloc_run_requests(&run_request, run_requests, 1))
1708 return REQ_NONE;
1710 req = &run_request[run_requests];
1711 req->keymap = keymap;
1712 req->key = key;
1713 req->argv[0] = NULL;
1715 if (!format_argv(req->argv, argv, FORMAT_NONE))
1716 return REQ_NONE;
1718 return REQ_NONE + ++run_requests;
1719 }
1721 static struct run_request *
1722 get_run_request(enum request request)
1723 {
1724 if (request <= REQ_NONE)
1725 return NULL;
1726 return &run_request[request - REQ_NONE - 1];
1727 }
1729 static void
1730 add_builtin_run_requests(void)
1731 {
1732 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1733 const char *checkout[] = { "git", "checkout", "%(branch)", 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_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1745 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1746 };
1747 int i;
1749 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1750 enum request req;
1752 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1753 if (req != REQ_NONE)
1754 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1755 }
1756 }
1758 /*
1759 * User config file handling.
1760 */
1762 static int config_lineno;
1763 static bool config_errors;
1764 static const char *config_msg;
1766 static const struct enum_map color_map[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1768 COLOR_MAP(DEFAULT),
1769 COLOR_MAP(BLACK),
1770 COLOR_MAP(BLUE),
1771 COLOR_MAP(CYAN),
1772 COLOR_MAP(GREEN),
1773 COLOR_MAP(MAGENTA),
1774 COLOR_MAP(RED),
1775 COLOR_MAP(WHITE),
1776 COLOR_MAP(YELLOW),
1777 };
1779 static const struct enum_map attr_map[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1781 ATTR_MAP(NORMAL),
1782 ATTR_MAP(BLINK),
1783 ATTR_MAP(BOLD),
1784 ATTR_MAP(DIM),
1785 ATTR_MAP(REVERSE),
1786 ATTR_MAP(STANDOUT),
1787 ATTR_MAP(UNDERLINE),
1788 };
1790 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt, const char *arg)
1793 {
1794 *opt = atoi(arg);
1795 if (!strchr(arg, '%'))
1796 return OK;
1798 /* "Shift down" so 100% and 1 does not conflict. */
1799 *opt = (*opt - 1) / 100;
1800 if (*opt >= 1.0) {
1801 *opt = 0.99;
1802 config_msg = "Step value larger than 100%";
1803 return ERR;
1804 }
1805 if (*opt < 0.0) {
1806 *opt = 1;
1807 config_msg = "Invalid step value";
1808 return ERR;
1809 }
1810 return OK;
1811 }
1813 static int
1814 parse_int(int *opt, const char *arg, int min, int max)
1815 {
1816 int value = atoi(arg);
1818 if (min <= value && value <= max) {
1819 *opt = value;
1820 return OK;
1821 }
1823 config_msg = "Integer value out of bound";
1824 return ERR;
1825 }
1827 static bool
1828 set_color(int *color, const char *name)
1829 {
1830 if (map_enum(color, color_map, name))
1831 return TRUE;
1832 if (!prefixcmp(name, "color"))
1833 return parse_int(color, name + 5, 0, 255) == OK;
1834 return FALSE;
1835 }
1837 /* Wants: object fgcolor bgcolor [attribute] */
1838 static int
1839 option_color_command(int argc, const char *argv[])
1840 {
1841 struct line_info *info;
1843 if (argc < 3) {
1844 config_msg = "Wrong number of arguments given to color command";
1845 return ERR;
1846 }
1848 info = get_line_info(argv[0]);
1849 if (!info) {
1850 static const struct enum_map obsolete[] = {
1851 ENUM_MAP("main-delim", LINE_DELIMITER),
1852 ENUM_MAP("main-date", LINE_DATE),
1853 ENUM_MAP("main-author", LINE_AUTHOR),
1854 };
1855 int index;
1857 if (!map_enum(&index, obsolete, argv[0])) {
1858 config_msg = "Unknown color name";
1859 return ERR;
1860 }
1861 info = &line_info[index];
1862 }
1864 if (!set_color(&info->fg, argv[1]) ||
1865 !set_color(&info->bg, argv[2])) {
1866 config_msg = "Unknown color";
1867 return ERR;
1868 }
1870 info->attr = 0;
1871 while (argc-- > 3) {
1872 int attr;
1874 if (!set_attribute(&attr, argv[argc])) {
1875 config_msg = "Unknown attribute";
1876 return ERR;
1877 }
1878 info->attr |= attr;
1879 }
1881 return OK;
1882 }
1884 static int parse_bool(bool *opt, const char *arg)
1885 {
1886 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1887 ? TRUE : FALSE;
1888 return OK;
1889 }
1891 static int parse_enum_do(unsigned int *opt, const char *arg,
1892 const struct enum_map *map, size_t map_size)
1893 {
1894 bool is_true;
1896 assert(map_size > 1);
1898 if (map_enum_do(map, map_size, (int *) opt, arg))
1899 return OK;
1901 if (parse_bool(&is_true, arg) != OK)
1902 return ERR;
1904 *opt = is_true ? map[1].value : map[0].value;
1905 return OK;
1906 }
1908 #define parse_enum(opt, arg, map) \
1909 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1911 static int
1912 parse_string(char *opt, const char *arg, size_t optsize)
1913 {
1914 int arglen = strlen(arg);
1916 switch (arg[0]) {
1917 case '\"':
1918 case '\'':
1919 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1920 config_msg = "Unmatched quotation";
1921 return ERR;
1922 }
1923 arg += 1; arglen -= 2;
1924 default:
1925 string_ncopy_do(opt, optsize, arg, arglen);
1926 return OK;
1927 }
1928 }
1930 /* Wants: name = value */
1931 static int
1932 option_set_command(int argc, const char *argv[])
1933 {
1934 if (argc != 3) {
1935 config_msg = "Wrong number of arguments given to set command";
1936 return ERR;
1937 }
1939 if (strcmp(argv[1], "=")) {
1940 config_msg = "No value assigned";
1941 return ERR;
1942 }
1944 if (!strcmp(argv[0], "show-author"))
1945 return parse_enum(&opt_author, argv[2], author_map);
1947 if (!strcmp(argv[0], "show-date"))
1948 return parse_enum(&opt_date, argv[2], date_map);
1950 if (!strcmp(argv[0], "show-rev-graph"))
1951 return parse_bool(&opt_rev_graph, argv[2]);
1953 if (!strcmp(argv[0], "show-refs"))
1954 return parse_bool(&opt_show_refs, argv[2]);
1956 if (!strcmp(argv[0], "show-line-numbers"))
1957 return parse_bool(&opt_line_number, argv[2]);
1959 if (!strcmp(argv[0], "line-graphics"))
1960 return parse_bool(&opt_line_graphics, argv[2]);
1962 if (!strcmp(argv[0], "line-number-interval"))
1963 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1965 if (!strcmp(argv[0], "author-width"))
1966 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1968 if (!strcmp(argv[0], "horizontal-scroll"))
1969 return parse_step(&opt_hscroll, argv[2]);
1971 if (!strcmp(argv[0], "split-view-height"))
1972 return parse_step(&opt_scale_split_view, argv[2]);
1974 if (!strcmp(argv[0], "tab-size"))
1975 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1977 if (!strcmp(argv[0], "commit-encoding"))
1978 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1980 config_msg = "Unknown variable name";
1981 return ERR;
1982 }
1984 /* Wants: mode request key */
1985 static int
1986 option_bind_command(int argc, const char *argv[])
1987 {
1988 enum request request;
1989 int keymap = -1;
1990 int key;
1992 if (argc < 3) {
1993 config_msg = "Wrong number of arguments given to bind command";
1994 return ERR;
1995 }
1997 if (set_keymap(&keymap, argv[0]) == ERR) {
1998 config_msg = "Unknown key map";
1999 return ERR;
2000 }
2002 key = get_key_value(argv[1]);
2003 if (key == ERR) {
2004 config_msg = "Unknown key";
2005 return ERR;
2006 }
2008 request = get_request(argv[2]);
2009 if (request == REQ_NONE) {
2010 static const struct enum_map obsolete[] = {
2011 ENUM_MAP("cherry-pick", REQ_NONE),
2012 ENUM_MAP("screen-resize", REQ_NONE),
2013 ENUM_MAP("tree-parent", REQ_PARENT),
2014 };
2015 int alias;
2017 if (map_enum(&alias, obsolete, argv[2])) {
2018 if (alias != REQ_NONE)
2019 add_keybinding(keymap, alias, key);
2020 config_msg = "Obsolete request name";
2021 return ERR;
2022 }
2023 }
2024 if (request == REQ_NONE && *argv[2]++ == '!')
2025 request = add_run_request(keymap, key, argc - 2, argv + 2);
2026 if (request == REQ_NONE) {
2027 config_msg = "Unknown request name";
2028 return ERR;
2029 }
2031 add_keybinding(keymap, request, key);
2033 return OK;
2034 }
2036 static int
2037 set_option(const char *opt, char *value)
2038 {
2039 const char *argv[SIZEOF_ARG];
2040 int argc = 0;
2042 if (!argv_from_string(argv, &argc, value)) {
2043 config_msg = "Too many option arguments";
2044 return ERR;
2045 }
2047 if (!strcmp(opt, "color"))
2048 return option_color_command(argc, argv);
2050 if (!strcmp(opt, "set"))
2051 return option_set_command(argc, argv);
2053 if (!strcmp(opt, "bind"))
2054 return option_bind_command(argc, argv);
2056 config_msg = "Unknown option command";
2057 return ERR;
2058 }
2060 static int
2061 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062 {
2063 int status = OK;
2065 config_lineno++;
2066 config_msg = "Internal error";
2068 /* Check for comment markers, since read_properties() will
2069 * only ensure opt and value are split at first " \t". */
2070 optlen = strcspn(opt, "#");
2071 if (optlen == 0)
2072 return OK;
2074 if (opt[optlen] != 0) {
2075 config_msg = "No option value";
2076 status = ERR;
2078 } else {
2079 /* Look for comment endings in the value. */
2080 size_t len = strcspn(value, "#");
2082 if (len < valuelen) {
2083 valuelen = len;
2084 value[valuelen] = 0;
2085 }
2087 status = set_option(opt, value);
2088 }
2090 if (status == ERR) {
2091 warn("Error on line %d, near '%.*s': %s",
2092 config_lineno, (int) optlen, opt, config_msg);
2093 config_errors = TRUE;
2094 }
2096 /* Always keep going if errors are encountered. */
2097 return OK;
2098 }
2100 static void
2101 load_option_file(const char *path)
2102 {
2103 struct io io = {};
2105 /* It's OK that the file doesn't exist. */
2106 if (!io_open(&io, "%s", path))
2107 return;
2109 config_lineno = 0;
2110 config_errors = FALSE;
2112 if (io_load(&io, " \t", read_option) == ERR ||
2113 config_errors == TRUE)
2114 warn("Errors while loading %s.", path);
2115 }
2117 static int
2118 load_options(void)
2119 {
2120 const char *home = getenv("HOME");
2121 const char *tigrc_user = getenv("TIGRC_USER");
2122 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2123 char buf[SIZEOF_STR];
2125 add_builtin_run_requests();
2127 if (!tigrc_system)
2128 tigrc_system = SYSCONFDIR "/tigrc";
2129 load_option_file(tigrc_system);
2131 if (!tigrc_user) {
2132 if (!home || !string_format(buf, "%s/.tigrc", home))
2133 return ERR;
2134 tigrc_user = buf;
2135 }
2136 load_option_file(tigrc_user);
2138 return OK;
2139 }
2142 /*
2143 * The viewer
2144 */
2146 struct view;
2147 struct view_ops;
2149 /* The display array of active views and the index of the current view. */
2150 static struct view *display[2];
2151 static unsigned int current_view;
2153 #define foreach_displayed_view(view, i) \
2154 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2156 #define displayed_views() (display[1] != NULL ? 2 : 1)
2158 /* Current head and commit ID */
2159 static char ref_blob[SIZEOF_REF] = "";
2160 static char ref_commit[SIZEOF_REF] = "HEAD";
2161 static char ref_head[SIZEOF_REF] = "HEAD";
2162 static char ref_branch[SIZEOF_REF] = "";
2164 struct view {
2165 const char *name; /* View name */
2166 const char *cmd_env; /* Command line set via environment */
2167 const char *id; /* Points to either of ref_{head,commit,blob} */
2169 struct view_ops *ops; /* View operations */
2171 enum keymap keymap; /* What keymap does this view have */
2172 bool git_dir; /* Whether the view requires a git directory. */
2174 char ref[SIZEOF_REF]; /* Hovered commit reference */
2175 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2177 int height, width; /* The width and height of the main window */
2178 WINDOW *win; /* The main window */
2179 WINDOW *title; /* The title window living below the main window */
2181 /* Navigation */
2182 unsigned long offset; /* Offset of the window top */
2183 unsigned long yoffset; /* Offset from the window side. */
2184 unsigned long lineno; /* Current line number */
2185 unsigned long p_offset; /* Previous offset of the window top */
2186 unsigned long p_yoffset;/* Previous offset from the window side */
2187 unsigned long p_lineno; /* Previous current line number */
2188 bool p_restore; /* Should the previous position be restored. */
2190 /* Searching */
2191 char grep[SIZEOF_STR]; /* Search string */
2192 regex_t *regex; /* Pre-compiled regexp */
2194 /* If non-NULL, points to the view that opened this view. If this view
2195 * is closed tig will switch back to the parent view. */
2196 struct view *parent;
2198 /* Buffering */
2199 size_t lines; /* Total number of lines */
2200 struct line *line; /* Line index */
2201 unsigned int digits; /* Number of digits in the lines member. */
2203 /* Drawing */
2204 struct line *curline; /* Line currently being drawn. */
2205 enum line_type curtype; /* Attribute currently used for drawing. */
2206 unsigned long col; /* Column when drawing. */
2207 bool has_scrolled; /* View was scrolled. */
2209 /* Loading */
2210 struct io io;
2211 struct io *pipe;
2212 time_t start_time;
2213 time_t update_secs;
2214 };
2216 struct view_ops {
2217 /* What type of content being displayed. Used in the title bar. */
2218 const char *type;
2219 /* Default command arguments. */
2220 const char **argv;
2221 /* Open and reads in all view content. */
2222 bool (*open)(struct view *view);
2223 /* Read one line; updates view->line. */
2224 bool (*read)(struct view *view, char *data);
2225 /* Draw one line; @lineno must be < view->height. */
2226 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2227 /* Depending on view handle a special requests. */
2228 enum request (*request)(struct view *view, enum request request, struct line *line);
2229 /* Search for regexp in a line. */
2230 bool (*grep)(struct view *view, struct line *line);
2231 /* Select line */
2232 void (*select)(struct view *view, struct line *line);
2233 /* Prepare view for loading */
2234 bool (*prepare)(struct view *view);
2235 };
2237 static struct view_ops blame_ops;
2238 static struct view_ops blob_ops;
2239 static struct view_ops diff_ops;
2240 static struct view_ops help_ops;
2241 static struct view_ops log_ops;
2242 static struct view_ops main_ops;
2243 static struct view_ops pager_ops;
2244 static struct view_ops stage_ops;
2245 static struct view_ops status_ops;
2246 static struct view_ops tree_ops;
2247 static struct view_ops branch_ops;
2249 #define VIEW_STR(name, env, ref, ops, map, git) \
2250 { name, #env, ref, ops, map, git }
2252 #define VIEW_(id, name, ops, git, ref) \
2253 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2256 static struct view views[] = {
2257 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2258 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2259 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2260 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2261 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2262 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2263 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2264 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2265 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2266 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2267 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2268 };
2270 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2271 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2273 #define foreach_view(view, i) \
2274 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2276 #define view_is_displayed(view) \
2277 (view == display[0] || view == display[1])
2280 static inline void
2281 set_view_attr(struct view *view, enum line_type type)
2282 {
2283 if (!view->curline->selected && view->curtype != type) {
2284 (void) wattrset(view->win, get_line_attr(type));
2285 wchgat(view->win, -1, 0, type, NULL);
2286 view->curtype = type;
2287 }
2288 }
2290 static int
2291 draw_chars(struct view *view, enum line_type type, const char *string,
2292 int max_len, bool use_tilde)
2293 {
2294 static char out_buffer[BUFSIZ * 2];
2295 int len = 0;
2296 int col = 0;
2297 int trimmed = FALSE;
2298 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2300 if (max_len <= 0)
2301 return 0;
2303 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2305 set_view_attr(view, type);
2306 if (len > 0) {
2307 if (opt_iconv_out != ICONV_NONE) {
2308 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2309 size_t inlen = len + 1;
2311 char *outbuf = out_buffer;
2312 size_t outlen = sizeof(out_buffer);
2314 size_t ret;
2316 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2317 if (ret != (size_t) -1) {
2318 string = out_buffer;
2319 len = sizeof(out_buffer) - outlen;
2320 }
2321 }
2323 waddnstr(view->win, string, len);
2324 }
2325 if (trimmed && use_tilde) {
2326 set_view_attr(view, LINE_DELIMITER);
2327 waddch(view->win, '~');
2328 col++;
2329 }
2331 return col;
2332 }
2334 static int
2335 draw_space(struct view *view, enum line_type type, int max, int spaces)
2336 {
2337 static char space[] = " ";
2338 int col = 0;
2340 spaces = MIN(max, spaces);
2342 while (spaces > 0) {
2343 int len = MIN(spaces, sizeof(space) - 1);
2345 col += draw_chars(view, type, space, len, FALSE);
2346 spaces -= len;
2347 }
2349 return col;
2350 }
2352 static bool
2353 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2354 {
2355 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2356 return view->width + view->yoffset <= view->col;
2357 }
2359 static bool
2360 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2361 {
2362 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363 int max = view->width + view->yoffset - view->col;
2364 int i;
2366 if (max < size)
2367 size = max;
2369 set_view_attr(view, type);
2370 /* Using waddch() instead of waddnstr() ensures that
2371 * they'll be rendered correctly for the cursor line. */
2372 for (i = skip; i < size; i++)
2373 waddch(view->win, graphic[i]);
2375 view->col += size;
2376 if (size < max && skip <= size)
2377 waddch(view->win, ' ');
2378 view->col++;
2380 return view->width + view->yoffset <= view->col;
2381 }
2383 static bool
2384 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2385 {
2386 int max = MIN(view->width + view->yoffset - view->col, len);
2387 int col;
2389 if (text)
2390 col = draw_chars(view, type, text, max - 1, trim);
2391 else
2392 col = draw_space(view, type, max - 1, max - 1);
2394 view->col += col;
2395 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2396 return view->width + view->yoffset <= view->col;
2397 }
2399 static bool
2400 draw_date(struct view *view, struct time *time)
2401 {
2402 const char *date = mkdate(time, opt_date);
2403 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2405 return draw_field(view, LINE_DATE, date, cols, FALSE);
2406 }
2408 static bool
2409 draw_author(struct view *view, const char *author)
2410 {
2411 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2412 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2414 if (abbreviate && author)
2415 author = get_author_initials(author);
2417 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2418 }
2420 static bool
2421 draw_mode(struct view *view, mode_t mode)
2422 {
2423 const char *str;
2425 if (S_ISDIR(mode))
2426 str = "drwxr-xr-x";
2427 else if (S_ISLNK(mode))
2428 str = "lrwxrwxrwx";
2429 else if (S_ISGITLINK(mode))
2430 str = "m---------";
2431 else if (S_ISREG(mode) && mode & S_IXUSR)
2432 str = "-rwxr-xr-x";
2433 else if (S_ISREG(mode))
2434 str = "-rw-r--r--";
2435 else
2436 str = "----------";
2438 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2439 }
2441 static bool
2442 draw_lineno(struct view *view, unsigned int lineno)
2443 {
2444 char number[10];
2445 int digits3 = view->digits < 3 ? 3 : view->digits;
2446 int max = MIN(view->width + view->yoffset - view->col, digits3);
2447 char *text = NULL;
2448 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2450 lineno += view->offset + 1;
2451 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2452 static char fmt[] = "%1ld";
2454 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2455 if (string_format(number, fmt, lineno))
2456 text = number;
2457 }
2458 if (text)
2459 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2460 else
2461 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2462 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2463 }
2465 static bool
2466 draw_view_line(struct view *view, unsigned int lineno)
2467 {
2468 struct line *line;
2469 bool selected = (view->offset + lineno == view->lineno);
2471 assert(view_is_displayed(view));
2473 if (view->offset + lineno >= view->lines)
2474 return FALSE;
2476 line = &view->line[view->offset + lineno];
2478 wmove(view->win, lineno, 0);
2479 if (line->cleareol)
2480 wclrtoeol(view->win);
2481 view->col = 0;
2482 view->curline = line;
2483 view->curtype = LINE_NONE;
2484 line->selected = FALSE;
2485 line->dirty = line->cleareol = 0;
2487 if (selected) {
2488 set_view_attr(view, LINE_CURSOR);
2489 line->selected = TRUE;
2490 view->ops->select(view, line);
2491 }
2493 return view->ops->draw(view, line, lineno);
2494 }
2496 static void
2497 redraw_view_dirty(struct view *view)
2498 {
2499 bool dirty = FALSE;
2500 int lineno;
2502 for (lineno = 0; lineno < view->height; lineno++) {
2503 if (view->offset + lineno >= view->lines)
2504 break;
2505 if (!view->line[view->offset + lineno].dirty)
2506 continue;
2507 dirty = TRUE;
2508 if (!draw_view_line(view, lineno))
2509 break;
2510 }
2512 if (!dirty)
2513 return;
2514 wnoutrefresh(view->win);
2515 }
2517 static void
2518 redraw_view_from(struct view *view, int lineno)
2519 {
2520 assert(0 <= lineno && lineno < view->height);
2522 for (; lineno < view->height; lineno++) {
2523 if (!draw_view_line(view, lineno))
2524 break;
2525 }
2527 wnoutrefresh(view->win);
2528 }
2530 static void
2531 redraw_view(struct view *view)
2532 {
2533 werase(view->win);
2534 redraw_view_from(view, 0);
2535 }
2538 static void
2539 update_view_title(struct view *view)
2540 {
2541 char buf[SIZEOF_STR];
2542 char state[SIZEOF_STR];
2543 size_t bufpos = 0, statelen = 0;
2545 assert(view_is_displayed(view));
2547 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2548 unsigned int view_lines = view->offset + view->height;
2549 unsigned int lines = view->lines
2550 ? MIN(view_lines, view->lines) * 100 / view->lines
2551 : 0;
2553 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2554 view->ops->type,
2555 view->lineno + 1,
2556 view->lines,
2557 lines);
2559 }
2561 if (view->pipe) {
2562 time_t secs = time(NULL) - view->start_time;
2564 /* Three git seconds are a long time ... */
2565 if (secs > 2)
2566 string_format_from(state, &statelen, " loading %lds", secs);
2567 }
2569 string_format_from(buf, &bufpos, "[%s]", view->name);
2570 if (*view->ref && bufpos < view->width) {
2571 size_t refsize = strlen(view->ref);
2572 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2574 if (minsize < view->width)
2575 refsize = view->width - minsize + 7;
2576 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2577 }
2579 if (statelen && bufpos < view->width) {
2580 string_format_from(buf, &bufpos, "%s", state);
2581 }
2583 if (view == display[current_view])
2584 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2585 else
2586 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2588 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2589 wclrtoeol(view->title);
2590 wnoutrefresh(view->title);
2591 }
2593 static int
2594 apply_step(double step, int value)
2595 {
2596 if (step >= 1)
2597 return (int) step;
2598 value *= step + 0.01;
2599 return value ? value : 1;
2600 }
2602 static void
2603 resize_display(void)
2604 {
2605 int offset, i;
2606 struct view *base = display[0];
2607 struct view *view = display[1] ? display[1] : display[0];
2609 /* Setup window dimensions */
2611 getmaxyx(stdscr, base->height, base->width);
2613 /* Make room for the status window. */
2614 base->height -= 1;
2616 if (view != base) {
2617 /* Horizontal split. */
2618 view->width = base->width;
2619 view->height = apply_step(opt_scale_split_view, base->height);
2620 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2621 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2622 base->height -= view->height;
2624 /* Make room for the title bar. */
2625 view->height -= 1;
2626 }
2628 /* Make room for the title bar. */
2629 base->height -= 1;
2631 offset = 0;
2633 foreach_displayed_view (view, i) {
2634 if (!view->win) {
2635 view->win = newwin(view->height, 0, offset, 0);
2636 if (!view->win)
2637 die("Failed to create %s view", view->name);
2639 scrollok(view->win, FALSE);
2641 view->title = newwin(1, 0, offset + view->height, 0);
2642 if (!view->title)
2643 die("Failed to create title window");
2645 } else {
2646 wresize(view->win, view->height, view->width);
2647 mvwin(view->win, offset, 0);
2648 mvwin(view->title, offset + view->height, 0);
2649 }
2651 offset += view->height + 1;
2652 }
2653 }
2655 static void
2656 redraw_display(bool clear)
2657 {
2658 struct view *view;
2659 int i;
2661 foreach_displayed_view (view, i) {
2662 if (clear)
2663 wclear(view->win);
2664 redraw_view(view);
2665 update_view_title(view);
2666 }
2667 }
2669 static void
2670 toggle_enum_option_do(unsigned int *opt, const char *help,
2671 const struct enum_map *map, size_t size)
2672 {
2673 *opt = (*opt + 1) % size;
2674 redraw_display(FALSE);
2675 report("Displaying %s %s", enum_name(map[*opt]), help);
2676 }
2678 #define toggle_enum_option(opt, help, map) \
2679 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2681 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2682 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2684 static void
2685 toggle_view_option(bool *option, const char *help)
2686 {
2687 *option = !*option;
2688 redraw_display(FALSE);
2689 report("%sabling %s", *option ? "En" : "Dis", help);
2690 }
2692 static void
2693 open_option_menu(void)
2694 {
2695 const struct menu_item menu[] = {
2696 { '.', "line numbers", &opt_line_number },
2697 { 'D', "date display", &opt_date },
2698 { 'A', "author display", &opt_author },
2699 { 'g', "revision graph display", &opt_rev_graph },
2700 { 'F', "reference display", &opt_show_refs },
2701 { 0 }
2702 };
2703 int selected = 0;
2705 if (prompt_menu("Toggle option", menu, &selected)) {
2706 if (menu[selected].data == &opt_date)
2707 toggle_date();
2708 else if (menu[selected].data == &opt_author)
2709 toggle_author();
2710 else
2711 toggle_view_option(menu[selected].data, menu[selected].text);
2712 }
2713 }
2715 static void
2716 maximize_view(struct view *view)
2717 {
2718 memset(display, 0, sizeof(display));
2719 current_view = 0;
2720 display[current_view] = view;
2721 resize_display();
2722 redraw_display(FALSE);
2723 report("");
2724 }
2727 /*
2728 * Navigation
2729 */
2731 static bool
2732 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2733 {
2734 if (lineno >= view->lines)
2735 lineno = view->lines > 0 ? view->lines - 1 : 0;
2737 if (offset > lineno || offset + view->height <= lineno) {
2738 unsigned long half = view->height / 2;
2740 if (lineno > half)
2741 offset = lineno - half;
2742 else
2743 offset = 0;
2744 }
2746 if (offset != view->offset || lineno != view->lineno) {
2747 view->offset = offset;
2748 view->lineno = lineno;
2749 return TRUE;
2750 }
2752 return FALSE;
2753 }
2755 /* Scrolling backend */
2756 static void
2757 do_scroll_view(struct view *view, int lines)
2758 {
2759 bool redraw_current_line = FALSE;
2761 /* The rendering expects the new offset. */
2762 view->offset += lines;
2764 assert(0 <= view->offset && view->offset < view->lines);
2765 assert(lines);
2767 /* Move current line into the view. */
2768 if (view->lineno < view->offset) {
2769 view->lineno = view->offset;
2770 redraw_current_line = TRUE;
2771 } else if (view->lineno >= view->offset + view->height) {
2772 view->lineno = view->offset + view->height - 1;
2773 redraw_current_line = TRUE;
2774 }
2776 assert(view->offset <= view->lineno && view->lineno < view->lines);
2778 /* Redraw the whole screen if scrolling is pointless. */
2779 if (view->height < ABS(lines)) {
2780 redraw_view(view);
2782 } else {
2783 int line = lines > 0 ? view->height - lines : 0;
2784 int end = line + ABS(lines);
2786 scrollok(view->win, TRUE);
2787 wscrl(view->win, lines);
2788 scrollok(view->win, FALSE);
2790 while (line < end && draw_view_line(view, line))
2791 line++;
2793 if (redraw_current_line)
2794 draw_view_line(view, view->lineno - view->offset);
2795 wnoutrefresh(view->win);
2796 }
2798 view->has_scrolled = TRUE;
2799 report("");
2800 }
2802 /* Scroll frontend */
2803 static void
2804 scroll_view(struct view *view, enum request request)
2805 {
2806 int lines = 1;
2808 assert(view_is_displayed(view));
2810 switch (request) {
2811 case REQ_SCROLL_LEFT:
2812 if (view->yoffset == 0) {
2813 report("Cannot scroll beyond the first column");
2814 return;
2815 }
2816 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2817 view->yoffset = 0;
2818 else
2819 view->yoffset -= apply_step(opt_hscroll, view->width);
2820 redraw_view_from(view, 0);
2821 report("");
2822 return;
2823 case REQ_SCROLL_RIGHT:
2824 view->yoffset += apply_step(opt_hscroll, view->width);
2825 redraw_view(view);
2826 report("");
2827 return;
2828 case REQ_SCROLL_PAGE_DOWN:
2829 lines = view->height;
2830 case REQ_SCROLL_LINE_DOWN:
2831 if (view->offset + lines > view->lines)
2832 lines = view->lines - view->offset;
2834 if (lines == 0 || view->offset + view->height >= view->lines) {
2835 report("Cannot scroll beyond the last line");
2836 return;
2837 }
2838 break;
2840 case REQ_SCROLL_PAGE_UP:
2841 lines = view->height;
2842 case REQ_SCROLL_LINE_UP:
2843 if (lines > view->offset)
2844 lines = view->offset;
2846 if (lines == 0) {
2847 report("Cannot scroll beyond the first line");
2848 return;
2849 }
2851 lines = -lines;
2852 break;
2854 default:
2855 die("request %d not handled in switch", request);
2856 }
2858 do_scroll_view(view, lines);
2859 }
2861 /* Cursor moving */
2862 static void
2863 move_view(struct view *view, enum request request)
2864 {
2865 int scroll_steps = 0;
2866 int steps;
2868 switch (request) {
2869 case REQ_MOVE_FIRST_LINE:
2870 steps = -view->lineno;
2871 break;
2873 case REQ_MOVE_LAST_LINE:
2874 steps = view->lines - view->lineno - 1;
2875 break;
2877 case REQ_MOVE_PAGE_UP:
2878 steps = view->height > view->lineno
2879 ? -view->lineno : -view->height;
2880 break;
2882 case REQ_MOVE_PAGE_DOWN:
2883 steps = view->lineno + view->height >= view->lines
2884 ? view->lines - view->lineno - 1 : view->height;
2885 break;
2887 case REQ_MOVE_UP:
2888 steps = -1;
2889 break;
2891 case REQ_MOVE_DOWN:
2892 steps = 1;
2893 break;
2895 default:
2896 die("request %d not handled in switch", request);
2897 }
2899 if (steps <= 0 && view->lineno == 0) {
2900 report("Cannot move beyond the first line");
2901 return;
2903 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2904 report("Cannot move beyond the last line");
2905 return;
2906 }
2908 /* Move the current line */
2909 view->lineno += steps;
2910 assert(0 <= view->lineno && view->lineno < view->lines);
2912 /* Check whether the view needs to be scrolled */
2913 if (view->lineno < view->offset ||
2914 view->lineno >= view->offset + view->height) {
2915 scroll_steps = steps;
2916 if (steps < 0 && -steps > view->offset) {
2917 scroll_steps = -view->offset;
2919 } else if (steps > 0) {
2920 if (view->lineno == view->lines - 1 &&
2921 view->lines > view->height) {
2922 scroll_steps = view->lines - view->offset - 1;
2923 if (scroll_steps >= view->height)
2924 scroll_steps -= view->height - 1;
2925 }
2926 }
2927 }
2929 if (!view_is_displayed(view)) {
2930 view->offset += scroll_steps;
2931 assert(0 <= view->offset && view->offset < view->lines);
2932 view->ops->select(view, &view->line[view->lineno]);
2933 return;
2934 }
2936 /* Repaint the old "current" line if we be scrolling */
2937 if (ABS(steps) < view->height)
2938 draw_view_line(view, view->lineno - steps - view->offset);
2940 if (scroll_steps) {
2941 do_scroll_view(view, scroll_steps);
2942 return;
2943 }
2945 /* Draw the current line */
2946 draw_view_line(view, view->lineno - view->offset);
2948 wnoutrefresh(view->win);
2949 report("");
2950 }
2953 /*
2954 * Searching
2955 */
2957 static void search_view(struct view *view, enum request request);
2959 static bool
2960 grep_text(struct view *view, const char *text[])
2961 {
2962 regmatch_t pmatch;
2963 size_t i;
2965 for (i = 0; text[i]; i++)
2966 if (*text[i] &&
2967 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2968 return TRUE;
2969 return FALSE;
2970 }
2972 static void
2973 select_view_line(struct view *view, unsigned long lineno)
2974 {
2975 unsigned long old_lineno = view->lineno;
2976 unsigned long old_offset = view->offset;
2978 if (goto_view_line(view, view->offset, lineno)) {
2979 if (view_is_displayed(view)) {
2980 if (old_offset != view->offset) {
2981 redraw_view(view);
2982 } else {
2983 draw_view_line(view, old_lineno - view->offset);
2984 draw_view_line(view, view->lineno - view->offset);
2985 wnoutrefresh(view->win);
2986 }
2987 } else {
2988 view->ops->select(view, &view->line[view->lineno]);
2989 }
2990 }
2991 }
2993 static void
2994 find_next(struct view *view, enum request request)
2995 {
2996 unsigned long lineno = view->lineno;
2997 int direction;
2999 if (!*view->grep) {
3000 if (!*opt_search)
3001 report("No previous search");
3002 else
3003 search_view(view, request);
3004 return;
3005 }
3007 switch (request) {
3008 case REQ_SEARCH:
3009 case REQ_FIND_NEXT:
3010 direction = 1;
3011 break;
3013 case REQ_SEARCH_BACK:
3014 case REQ_FIND_PREV:
3015 direction = -1;
3016 break;
3018 default:
3019 return;
3020 }
3022 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3023 lineno += direction;
3025 /* Note, lineno is unsigned long so will wrap around in which case it
3026 * will become bigger than view->lines. */
3027 for (; lineno < view->lines; lineno += direction) {
3028 if (view->ops->grep(view, &view->line[lineno])) {
3029 select_view_line(view, lineno);
3030 report("Line %ld matches '%s'", lineno + 1, view->grep);
3031 return;
3032 }
3033 }
3035 report("No match found for '%s'", view->grep);
3036 }
3038 static void
3039 search_view(struct view *view, enum request request)
3040 {
3041 int regex_err;
3043 if (view->regex) {
3044 regfree(view->regex);
3045 *view->grep = 0;
3046 } else {
3047 view->regex = calloc(1, sizeof(*view->regex));
3048 if (!view->regex)
3049 return;
3050 }
3052 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3053 if (regex_err != 0) {
3054 char buf[SIZEOF_STR] = "unknown error";
3056 regerror(regex_err, view->regex, buf, sizeof(buf));
3057 report("Search failed: %s", buf);
3058 return;
3059 }
3061 string_copy(view->grep, opt_search);
3063 find_next(view, request);
3064 }
3066 /*
3067 * Incremental updating
3068 */
3070 static void
3071 reset_view(struct view *view)
3072 {
3073 int i;
3075 for (i = 0; i < view->lines; i++)
3076 free(view->line[i].data);
3077 free(view->line);
3079 view->p_offset = view->offset;
3080 view->p_yoffset = view->yoffset;
3081 view->p_lineno = view->lineno;
3083 view->line = NULL;
3084 view->offset = 0;
3085 view->yoffset = 0;
3086 view->lines = 0;
3087 view->lineno = 0;
3088 view->vid[0] = 0;
3089 view->update_secs = 0;
3090 }
3092 static void
3093 free_argv(const char *argv[])
3094 {
3095 int argc;
3097 for (argc = 0; argv[argc]; argc++)
3098 free((void *) argv[argc]);
3099 }
3101 static const char *
3102 format_arg(const char *name)
3103 {
3104 static struct {
3105 const char *name;
3106 size_t namelen;
3107 const char *value;
3108 const char *value_if_empty;
3109 } vars[] = {
3110 #define FORMAT_VAR(name, value, value_if_empty) \
3111 { name, STRING_SIZE(name), value, value_if_empty }
3112 FORMAT_VAR("%(directory)", opt_path, ""),
3113 FORMAT_VAR("%(file)", opt_file, ""),
3114 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3115 FORMAT_VAR("%(head)", ref_head, ""),
3116 FORMAT_VAR("%(commit)", ref_commit, ""),
3117 FORMAT_VAR("%(blob)", ref_blob, ""),
3118 FORMAT_VAR("%(branch)", ref_branch, ""),
3119 };
3120 int i;
3122 for (i = 0; i < ARRAY_SIZE(vars); i++)
3123 if (!strncmp(name, vars[i].name, vars[i].namelen))
3124 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3126 report("Unknown replacement: `%s`", name);
3127 return NULL;
3128 }
3130 static bool
3131 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3132 {
3133 char buf[SIZEOF_STR];
3134 int argc;
3135 bool noreplace = flags == FORMAT_NONE;
3137 free_argv(dst_argv);
3139 for (argc = 0; src_argv[argc]; argc++) {
3140 const char *arg = src_argv[argc];
3141 size_t bufpos = 0;
3143 while (arg) {
3144 char *next = strstr(arg, "%(");
3145 int len = next - arg;
3146 const char *value;
3148 if (!next || noreplace) {
3149 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3150 noreplace = TRUE;
3151 len = strlen(arg);
3152 value = "";
3154 } else {
3155 value = format_arg(next);
3157 if (!value) {
3158 return FALSE;
3159 }
3160 }
3162 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3163 return FALSE;
3165 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3166 }
3168 dst_argv[argc] = strdup(buf);
3169 if (!dst_argv[argc])
3170 break;
3171 }
3173 dst_argv[argc] = NULL;
3175 return src_argv[argc] == NULL;
3176 }
3178 static bool
3179 restore_view_position(struct view *view)
3180 {
3181 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3182 return FALSE;
3184 /* Changing the view position cancels the restoring. */
3185 /* FIXME: Changing back to the first line is not detected. */
3186 if (view->offset != 0 || view->lineno != 0) {
3187 view->p_restore = FALSE;
3188 return FALSE;
3189 }
3191 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3192 view_is_displayed(view))
3193 werase(view->win);
3195 view->yoffset = view->p_yoffset;
3196 view->p_restore = FALSE;
3198 return TRUE;
3199 }
3201 static void
3202 end_update(struct view *view, bool force)
3203 {
3204 if (!view->pipe)
3205 return;
3206 while (!view->ops->read(view, NULL))
3207 if (!force)
3208 return;
3209 if (force)
3210 io_kill(view->pipe);
3211 io_done(view->pipe);
3212 view->pipe = NULL;
3213 }
3215 static void
3216 setup_update(struct view *view, const char *vid)
3217 {
3218 reset_view(view);
3219 string_copy_rev(view->vid, vid);
3220 view->pipe = &view->io;
3221 view->start_time = time(NULL);
3222 }
3224 static bool
3225 prepare_update(struct view *view, const char *argv[], const char *dir,
3226 enum format_flags flags)
3227 {
3228 if (view->pipe)
3229 end_update(view, TRUE);
3230 return io_format(&view->io, dir, IO_RD, argv, flags);
3231 }
3233 static bool
3234 prepare_update_file(struct view *view, const char *name)
3235 {
3236 if (view->pipe)
3237 end_update(view, TRUE);
3238 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3239 }
3241 static bool
3242 begin_update(struct view *view, bool refresh)
3243 {
3244 if (view->pipe)
3245 end_update(view, TRUE);
3247 if (!refresh) {
3248 if (view->ops->prepare) {
3249 if (!view->ops->prepare(view))
3250 return FALSE;
3251 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3252 return FALSE;
3253 }
3255 /* Put the current ref_* value to the view title ref
3256 * member. This is needed by the blob view. Most other
3257 * views sets it automatically after loading because the
3258 * first line is a commit line. */
3259 string_copy_rev(view->ref, view->id);
3260 }
3262 if (!io_start(&view->io))
3263 return FALSE;
3265 setup_update(view, view->id);
3267 return TRUE;
3268 }
3270 static bool
3271 update_view(struct view *view)
3272 {
3273 char out_buffer[BUFSIZ * 2];
3274 char *line;
3275 /* Clear the view and redraw everything since the tree sorting
3276 * might have rearranged things. */
3277 bool redraw = view->lines == 0;
3278 bool can_read = TRUE;
3280 if (!view->pipe)
3281 return TRUE;
3283 if (!io_can_read(view->pipe)) {
3284 if (view->lines == 0 && view_is_displayed(view)) {
3285 time_t secs = time(NULL) - view->start_time;
3287 if (secs > 1 && secs > view->update_secs) {
3288 if (view->update_secs == 0)
3289 redraw_view(view);
3290 update_view_title(view);
3291 view->update_secs = secs;
3292 }
3293 }
3294 return TRUE;
3295 }
3297 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3298 if (opt_iconv_in != ICONV_NONE) {
3299 ICONV_CONST char *inbuf = line;
3300 size_t inlen = strlen(line) + 1;
3302 char *outbuf = out_buffer;
3303 size_t outlen = sizeof(out_buffer);
3305 size_t ret;
3307 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3308 if (ret != (size_t) -1)
3309 line = out_buffer;
3310 }
3312 if (!view->ops->read(view, line)) {
3313 report("Allocation failure");
3314 end_update(view, TRUE);
3315 return FALSE;
3316 }
3317 }
3319 {
3320 unsigned long lines = view->lines;
3321 int digits;
3323 for (digits = 0; lines; digits++)
3324 lines /= 10;
3326 /* Keep the displayed view in sync with line number scaling. */
3327 if (digits != view->digits) {
3328 view->digits = digits;
3329 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3330 redraw = TRUE;
3331 }
3332 }
3334 if (io_error(view->pipe)) {
3335 report("Failed to read: %s", io_strerror(view->pipe));
3336 end_update(view, TRUE);
3338 } else if (io_eof(view->pipe)) {
3339 report("");
3340 end_update(view, FALSE);
3341 }
3343 if (restore_view_position(view))
3344 redraw = TRUE;
3346 if (!view_is_displayed(view))
3347 return TRUE;
3349 if (redraw)
3350 redraw_view_from(view, 0);
3351 else
3352 redraw_view_dirty(view);
3354 /* Update the title _after_ the redraw so that if the redraw picks up a
3355 * commit reference in view->ref it'll be available here. */
3356 update_view_title(view);
3357 return TRUE;
3358 }
3360 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3362 static struct line *
3363 add_line_data(struct view *view, void *data, enum line_type type)
3364 {
3365 struct line *line;
3367 if (!realloc_lines(&view->line, view->lines, 1))
3368 return NULL;
3370 line = &view->line[view->lines++];
3371 memset(line, 0, sizeof(*line));
3372 line->type = type;
3373 line->data = data;
3374 line->dirty = 1;
3376 return line;
3377 }
3379 static struct line *
3380 add_line_text(struct view *view, const char *text, enum line_type type)
3381 {
3382 char *data = text ? strdup(text) : NULL;
3384 return data ? add_line_data(view, data, type) : NULL;
3385 }
3387 static struct line *
3388 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3389 {
3390 char buf[SIZEOF_STR];
3391 va_list args;
3393 va_start(args, fmt);
3394 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3395 buf[0] = 0;
3396 va_end(args);
3398 return buf[0] ? add_line_text(view, buf, type) : NULL;
3399 }
3401 /*
3402 * View opening
3403 */
3405 enum open_flags {
3406 OPEN_DEFAULT = 0, /* Use default view switching. */
3407 OPEN_SPLIT = 1, /* Split current view. */
3408 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3409 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3410 OPEN_PREPARED = 32, /* Open already prepared command. */
3411 };
3413 static void
3414 open_view(struct view *prev, enum request request, enum open_flags flags)
3415 {
3416 bool split = !!(flags & OPEN_SPLIT);
3417 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3418 bool nomaximize = !!(flags & OPEN_REFRESH);
3419 struct view *view = VIEW(request);
3420 int nviews = displayed_views();
3421 struct view *base_view = display[0];
3423 if (view == prev && nviews == 1 && !reload) {
3424 report("Already in %s view", view->name);
3425 return;
3426 }
3428 if (view->git_dir && !opt_git_dir[0]) {
3429 report("The %s view is disabled in pager view", view->name);
3430 return;
3431 }
3433 if (split) {
3434 display[1] = view;
3435 current_view = 1;
3436 } else if (!nomaximize) {
3437 /* Maximize the current view. */
3438 memset(display, 0, sizeof(display));
3439 current_view = 0;
3440 display[current_view] = view;
3441 }
3443 /* No parent signals that this is the first loaded view. */
3444 if (prev && view != prev) {
3445 view->parent = prev;
3446 }
3448 /* Resize the view when switching between split- and full-screen,
3449 * or when switching between two different full-screen views. */
3450 if (nviews != displayed_views() ||
3451 (nviews == 1 && base_view != display[0]))
3452 resize_display();
3454 if (view->ops->open) {
3455 if (view->pipe)
3456 end_update(view, TRUE);
3457 if (!view->ops->open(view)) {
3458 report("Failed to load %s view", view->name);
3459 return;
3460 }
3461 restore_view_position(view);
3463 } else if ((reload || strcmp(view->vid, view->id)) &&
3464 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3465 report("Failed to load %s view", view->name);
3466 return;
3467 }
3469 if (split && prev->lineno - prev->offset >= prev->height) {
3470 /* Take the title line into account. */
3471 int lines = prev->lineno - prev->offset - prev->height + 1;
3473 /* Scroll the view that was split if the current line is
3474 * outside the new limited view. */
3475 do_scroll_view(prev, lines);
3476 }
3478 if (prev && view != prev && split && view_is_displayed(prev)) {
3479 /* "Blur" the previous view. */
3480 update_view_title(prev);
3481 }
3483 if (view->pipe && view->lines == 0) {
3484 /* Clear the old view and let the incremental updating refill
3485 * the screen. */
3486 werase(view->win);
3487 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3488 report("");
3489 } else if (view_is_displayed(view)) {
3490 redraw_view(view);
3491 report("");
3492 }
3493 }
3495 static void
3496 open_external_viewer(const char *argv[], const char *dir)
3497 {
3498 def_prog_mode(); /* save current tty modes */
3499 endwin(); /* restore original tty modes */
3500 io_run_fg(argv, dir);
3501 fprintf(stderr, "Press Enter to continue");
3502 getc(opt_tty);
3503 reset_prog_mode();
3504 redraw_display(TRUE);
3505 }
3507 static void
3508 open_mergetool(const char *file)
3509 {
3510 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3512 open_external_viewer(mergetool_argv, opt_cdup);
3513 }
3515 static void
3516 open_editor(const char *file)
3517 {
3518 const char *editor_argv[] = { "vi", file, NULL };
3519 const char *editor;
3521 editor = getenv("GIT_EDITOR");
3522 if (!editor && *opt_editor)
3523 editor = opt_editor;
3524 if (!editor)
3525 editor = getenv("VISUAL");
3526 if (!editor)
3527 editor = getenv("EDITOR");
3528 if (!editor)
3529 editor = "vi";
3531 editor_argv[0] = editor;
3532 open_external_viewer(editor_argv, opt_cdup);
3533 }
3535 static void
3536 open_run_request(enum request request)
3537 {
3538 struct run_request *req = get_run_request(request);
3539 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3541 if (!req) {
3542 report("Unknown run request");
3543 return;
3544 }
3546 if (format_argv(argv, req->argv, FORMAT_ALL))
3547 open_external_viewer(argv, NULL);
3548 free_argv(argv);
3549 }
3551 /*
3552 * User request switch noodle
3553 */
3555 static int
3556 view_driver(struct view *view, enum request request)
3557 {
3558 int i;
3560 if (request == REQ_NONE)
3561 return TRUE;
3563 if (request > REQ_NONE) {
3564 open_run_request(request);
3565 /* FIXME: When all views can refresh always do this. */
3566 if (view == VIEW(REQ_VIEW_STATUS) ||
3567 view == VIEW(REQ_VIEW_MAIN) ||
3568 view == VIEW(REQ_VIEW_LOG) ||
3569 view == VIEW(REQ_VIEW_BRANCH) ||
3570 view == VIEW(REQ_VIEW_STAGE))
3571 request = REQ_REFRESH;
3572 else
3573 return TRUE;
3574 }
3576 if (view && view->lines) {
3577 request = view->ops->request(view, request, &view->line[view->lineno]);
3578 if (request == REQ_NONE)
3579 return TRUE;
3580 }
3582 switch (request) {
3583 case REQ_MOVE_UP:
3584 case REQ_MOVE_DOWN:
3585 case REQ_MOVE_PAGE_UP:
3586 case REQ_MOVE_PAGE_DOWN:
3587 case REQ_MOVE_FIRST_LINE:
3588 case REQ_MOVE_LAST_LINE:
3589 move_view(view, request);
3590 break;
3592 case REQ_SCROLL_LEFT:
3593 case REQ_SCROLL_RIGHT:
3594 case REQ_SCROLL_LINE_DOWN:
3595 case REQ_SCROLL_LINE_UP:
3596 case REQ_SCROLL_PAGE_DOWN:
3597 case REQ_SCROLL_PAGE_UP:
3598 scroll_view(view, request);
3599 break;
3601 case REQ_VIEW_BLAME:
3602 if (!opt_file[0]) {
3603 report("No file chosen, press %s to open tree view",
3604 get_key(view->keymap, REQ_VIEW_TREE));
3605 break;
3606 }
3607 open_view(view, request, OPEN_DEFAULT);
3608 break;
3610 case REQ_VIEW_BLOB:
3611 if (!ref_blob[0]) {
3612 report("No file chosen, press %s to open tree view",
3613 get_key(view->keymap, REQ_VIEW_TREE));
3614 break;
3615 }
3616 open_view(view, request, OPEN_DEFAULT);
3617 break;
3619 case REQ_VIEW_PAGER:
3620 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3621 report("No pager content, press %s to run command from prompt",
3622 get_key(view->keymap, REQ_PROMPT));
3623 break;
3624 }
3625 open_view(view, request, OPEN_DEFAULT);
3626 break;
3628 case REQ_VIEW_STAGE:
3629 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3630 report("No stage content, press %s to open the status view and choose file",
3631 get_key(view->keymap, REQ_VIEW_STATUS));
3632 break;
3633 }
3634 open_view(view, request, OPEN_DEFAULT);
3635 break;
3637 case REQ_VIEW_STATUS:
3638 if (opt_is_inside_work_tree == FALSE) {
3639 report("The status view requires a working tree");
3640 break;
3641 }
3642 open_view(view, request, OPEN_DEFAULT);
3643 break;
3645 case REQ_VIEW_MAIN:
3646 case REQ_VIEW_DIFF:
3647 case REQ_VIEW_LOG:
3648 case REQ_VIEW_TREE:
3649 case REQ_VIEW_HELP:
3650 case REQ_VIEW_BRANCH:
3651 open_view(view, request, OPEN_DEFAULT);
3652 break;
3654 case REQ_NEXT:
3655 case REQ_PREVIOUS:
3656 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3658 if ((view == VIEW(REQ_VIEW_DIFF) &&
3659 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3660 (view == VIEW(REQ_VIEW_DIFF) &&
3661 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3662 (view == VIEW(REQ_VIEW_STAGE) &&
3663 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3664 (view == VIEW(REQ_VIEW_BLOB) &&
3665 view->parent == VIEW(REQ_VIEW_TREE)) ||
3666 (view == VIEW(REQ_VIEW_MAIN) &&
3667 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3668 int line;
3670 view = view->parent;
3671 line = view->lineno;
3672 move_view(view, request);
3673 if (view_is_displayed(view))
3674 update_view_title(view);
3675 if (line != view->lineno)
3676 view->ops->request(view, REQ_ENTER,
3677 &view->line[view->lineno]);
3679 } else {
3680 move_view(view, request);
3681 }
3682 break;
3684 case REQ_VIEW_NEXT:
3685 {
3686 int nviews = displayed_views();
3687 int next_view = (current_view + 1) % nviews;
3689 if (next_view == current_view) {
3690 report("Only one view is displayed");
3691 break;
3692 }
3694 current_view = next_view;
3695 /* Blur out the title of the previous view. */
3696 update_view_title(view);
3697 report("");
3698 break;
3699 }
3700 case REQ_REFRESH:
3701 report("Refreshing is not yet supported for the %s view", view->name);
3702 break;
3704 case REQ_MAXIMIZE:
3705 if (displayed_views() == 2)
3706 maximize_view(view);
3707 break;
3709 case REQ_OPTIONS:
3710 open_option_menu();
3711 break;
3713 case REQ_TOGGLE_LINENO:
3714 toggle_view_option(&opt_line_number, "line numbers");
3715 break;
3717 case REQ_TOGGLE_DATE:
3718 toggle_date();
3719 break;
3721 case REQ_TOGGLE_AUTHOR:
3722 toggle_author();
3723 break;
3725 case REQ_TOGGLE_REV_GRAPH:
3726 toggle_view_option(&opt_rev_graph, "revision graph display");
3727 break;
3729 case REQ_TOGGLE_REFS:
3730 toggle_view_option(&opt_show_refs, "reference display");
3731 break;
3733 case REQ_TOGGLE_SORT_FIELD:
3734 case REQ_TOGGLE_SORT_ORDER:
3735 report("Sorting is not yet supported for the %s view", view->name);
3736 break;
3738 case REQ_SEARCH:
3739 case REQ_SEARCH_BACK:
3740 search_view(view, request);
3741 break;
3743 case REQ_FIND_NEXT:
3744 case REQ_FIND_PREV:
3745 find_next(view, request);
3746 break;
3748 case REQ_STOP_LOADING:
3749 for (i = 0; i < ARRAY_SIZE(views); i++) {
3750 view = &views[i];
3751 if (view->pipe)
3752 report("Stopped loading the %s view", view->name),
3753 end_update(view, TRUE);
3754 }
3755 break;
3757 case REQ_SHOW_VERSION:
3758 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3759 return TRUE;
3761 case REQ_SCREEN_REDRAW:
3762 redraw_display(TRUE);
3763 break;
3765 case REQ_EDIT:
3766 report("Nothing to edit");
3767 break;
3769 case REQ_ENTER:
3770 report("Nothing to enter");
3771 break;
3773 case REQ_VIEW_CLOSE:
3774 /* XXX: Mark closed views by letting view->parent point to the
3775 * view itself. Parents to closed view should never be
3776 * followed. */
3777 if (view->parent &&
3778 view->parent->parent != view->parent) {
3779 maximize_view(view->parent);
3780 view->parent = view;
3781 break;
3782 }
3783 /* Fall-through */
3784 case REQ_QUIT:
3785 return FALSE;
3787 default:
3788 report("Unknown key, press %s for help",
3789 get_key(view->keymap, REQ_VIEW_HELP));
3790 return TRUE;
3791 }
3793 return TRUE;
3794 }
3797 /*
3798 * View backend utilities
3799 */
3801 enum sort_field {
3802 ORDERBY_NAME,
3803 ORDERBY_DATE,
3804 ORDERBY_AUTHOR,
3805 };
3807 struct sort_state {
3808 const enum sort_field *fields;
3809 size_t size, current;
3810 bool reverse;
3811 };
3813 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3814 #define get_sort_field(state) ((state).fields[(state).current])
3815 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3817 static void
3818 sort_view(struct view *view, enum request request, struct sort_state *state,
3819 int (*compare)(const void *, const void *))
3820 {
3821 switch (request) {
3822 case REQ_TOGGLE_SORT_FIELD:
3823 state->current = (state->current + 1) % state->size;
3824 break;
3826 case REQ_TOGGLE_SORT_ORDER:
3827 state->reverse = !state->reverse;
3828 break;
3829 default:
3830 die("Not a sort request");
3831 }
3833 qsort(view->line, view->lines, sizeof(*view->line), compare);
3834 redraw_view(view);
3835 }
3837 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3839 /* Small author cache to reduce memory consumption. It uses binary
3840 * search to lookup or find place to position new entries. No entries
3841 * are ever freed. */
3842 static const char *
3843 get_author(const char *name)
3844 {
3845 static const char **authors;
3846 static size_t authors_size;
3847 int from = 0, to = authors_size - 1;
3849 while (from <= to) {
3850 size_t pos = (to + from) / 2;
3851 int cmp = strcmp(name, authors[pos]);
3853 if (!cmp)
3854 return authors[pos];
3856 if (cmp < 0)
3857 to = pos - 1;
3858 else
3859 from = pos + 1;
3860 }
3862 if (!realloc_authors(&authors, authors_size, 1))
3863 return NULL;
3864 name = strdup(name);
3865 if (!name)
3866 return NULL;
3868 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3869 authors[from] = name;
3870 authors_size++;
3872 return name;
3873 }
3875 static void
3876 parse_timesec(struct time *time, const char *sec)
3877 {
3878 time->sec = (time_t) atol(sec);
3879 }
3881 static void
3882 parse_timezone(struct time *time, const char *zone)
3883 {
3884 long tz;
3886 tz = ('0' - zone[1]) * 60 * 60 * 10;
3887 tz += ('0' - zone[2]) * 60 * 60;
3888 tz += ('0' - zone[3]) * 60;
3889 tz += ('0' - zone[4]);
3891 if (zone[0] == '-')
3892 tz = -tz;
3894 time->tz = tz;
3895 time->sec -= tz;
3896 }
3898 /* Parse author lines where the name may be empty:
3899 * author <email@address.tld> 1138474660 +0100
3900 */
3901 static void
3902 parse_author_line(char *ident, const char **author, struct time *time)
3903 {
3904 char *nameend = strchr(ident, '<');
3905 char *emailend = strchr(ident, '>');
3907 if (nameend && emailend)
3908 *nameend = *emailend = 0;
3909 ident = chomp_string(ident);
3910 if (!*ident) {
3911 if (nameend)
3912 ident = chomp_string(nameend + 1);
3913 if (!*ident)
3914 ident = "Unknown";
3915 }
3917 *author = get_author(ident);
3919 /* Parse epoch and timezone */
3920 if (emailend && emailend[1] == ' ') {
3921 char *secs = emailend + 2;
3922 char *zone = strchr(secs, ' ');
3924 parse_timesec(time, secs);
3926 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3927 parse_timezone(time, zone + 1);
3928 }
3929 }
3931 static bool
3932 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3933 {
3934 char rev[SIZEOF_REV];
3935 const char *revlist_argv[] = {
3936 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3937 };
3938 struct menu_item *items;
3939 char text[SIZEOF_STR];
3940 bool ok = TRUE;
3941 int i;
3943 items = calloc(*parents + 1, sizeof(*items));
3944 if (!items)
3945 return FALSE;
3947 for (i = 0; i < *parents; i++) {
3948 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3949 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3950 !(items[i].text = strdup(text))) {
3951 ok = FALSE;
3952 break;
3953 }
3954 }
3956 if (ok) {
3957 *parents = 0;
3958 ok = prompt_menu("Select parent", items, parents);
3959 }
3960 for (i = 0; items[i].text; i++)
3961 free((char *) items[i].text);
3962 free(items);
3963 return ok;
3964 }
3966 static bool
3967 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3968 {
3969 char buf[SIZEOF_STR * 4];
3970 const char *revlist_argv[] = {
3971 "git", "log", "--no-color", "-1",
3972 "--pretty=format:%P", id, "--", path, NULL
3973 };
3974 int parents;
3976 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3977 (parents = strlen(buf) / 40) < 0) {
3978 report("Failed to get parent information");
3979 return FALSE;
3981 } else if (parents == 0) {
3982 if (path)
3983 report("Path '%s' does not exist in the parent", path);
3984 else
3985 report("The selected commit has no parents");
3986 return FALSE;
3987 }
3989 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3990 return FALSE;
3992 string_copy_rev(rev, &buf[41 * parents]);
3993 return TRUE;
3994 }
3996 /*
3997 * Pager backend
3998 */
4000 static bool
4001 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4002 {
4003 char text[SIZEOF_STR];
4005 if (opt_line_number && draw_lineno(view, lineno))
4006 return TRUE;
4008 string_expand(text, sizeof(text), line->data, opt_tab_size);
4009 draw_text(view, line->type, text, TRUE);
4010 return TRUE;
4011 }
4013 static bool
4014 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4015 {
4016 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4017 char ref[SIZEOF_STR];
4019 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4020 return TRUE;
4022 /* This is the only fatal call, since it can "corrupt" the buffer. */
4023 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4024 return FALSE;
4026 return TRUE;
4027 }
4029 static void
4030 add_pager_refs(struct view *view, struct line *line)
4031 {
4032 char buf[SIZEOF_STR];
4033 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4034 struct ref_list *list;
4035 size_t bufpos = 0, i;
4036 const char *sep = "Refs: ";
4037 bool is_tag = FALSE;
4039 assert(line->type == LINE_COMMIT);
4041 list = get_ref_list(commit_id);
4042 if (!list) {
4043 if (view == VIEW(REQ_VIEW_DIFF))
4044 goto try_add_describe_ref;
4045 return;
4046 }
4048 for (i = 0; i < list->size; i++) {
4049 struct ref *ref = list->refs[i];
4050 const char *fmt = ref->tag ? "%s[%s]" :
4051 ref->remote ? "%s<%s>" : "%s%s";
4053 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4054 return;
4055 sep = ", ";
4056 if (ref->tag)
4057 is_tag = TRUE;
4058 }
4060 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4061 try_add_describe_ref:
4062 /* Add <tag>-g<commit_id> "fake" reference. */
4063 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4064 return;
4065 }
4067 if (bufpos == 0)
4068 return;
4070 add_line_text(view, buf, LINE_PP_REFS);
4071 }
4073 static bool
4074 pager_read(struct view *view, char *data)
4075 {
4076 struct line *line;
4078 if (!data)
4079 return TRUE;
4081 line = add_line_text(view, data, get_line_type(data));
4082 if (!line)
4083 return FALSE;
4085 if (line->type == LINE_COMMIT &&
4086 (view == VIEW(REQ_VIEW_DIFF) ||
4087 view == VIEW(REQ_VIEW_LOG)))
4088 add_pager_refs(view, line);
4090 return TRUE;
4091 }
4093 static enum request
4094 pager_request(struct view *view, enum request request, struct line *line)
4095 {
4096 int split = 0;
4098 if (request != REQ_ENTER)
4099 return request;
4101 if (line->type == LINE_COMMIT &&
4102 (view == VIEW(REQ_VIEW_LOG) ||
4103 view == VIEW(REQ_VIEW_PAGER))) {
4104 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4105 split = 1;
4106 }
4108 /* Always scroll the view even if it was split. That way
4109 * you can use Enter to scroll through the log view and
4110 * split open each commit diff. */
4111 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4113 /* FIXME: A minor workaround. Scrolling the view will call report("")
4114 * but if we are scrolling a non-current view this won't properly
4115 * update the view title. */
4116 if (split)
4117 update_view_title(view);
4119 return REQ_NONE;
4120 }
4122 static bool
4123 pager_grep(struct view *view, struct line *line)
4124 {
4125 const char *text[] = { line->data, NULL };
4127 return grep_text(view, text);
4128 }
4130 static void
4131 pager_select(struct view *view, struct line *line)
4132 {
4133 if (line->type == LINE_COMMIT) {
4134 char *text = (char *)line->data + STRING_SIZE("commit ");
4136 if (view != VIEW(REQ_VIEW_PAGER))
4137 string_copy_rev(view->ref, text);
4138 string_copy_rev(ref_commit, text);
4139 }
4140 }
4142 static struct view_ops pager_ops = {
4143 "line",
4144 NULL,
4145 NULL,
4146 pager_read,
4147 pager_draw,
4148 pager_request,
4149 pager_grep,
4150 pager_select,
4151 };
4153 static const char *log_argv[SIZEOF_ARG] = {
4154 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4155 };
4157 static enum request
4158 log_request(struct view *view, enum request request, struct line *line)
4159 {
4160 switch (request) {
4161 case REQ_REFRESH:
4162 load_refs();
4163 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4164 return REQ_NONE;
4165 default:
4166 return pager_request(view, request, line);
4167 }
4168 }
4170 static struct view_ops log_ops = {
4171 "line",
4172 log_argv,
4173 NULL,
4174 pager_read,
4175 pager_draw,
4176 log_request,
4177 pager_grep,
4178 pager_select,
4179 };
4181 static const char *diff_argv[SIZEOF_ARG] = {
4182 "git", "show", "--pretty=fuller", "--no-color", "--root",
4183 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4184 };
4186 static struct view_ops diff_ops = {
4187 "line",
4188 diff_argv,
4189 NULL,
4190 pager_read,
4191 pager_draw,
4192 pager_request,
4193 pager_grep,
4194 pager_select,
4195 };
4197 /*
4198 * Help backend
4199 */
4201 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4203 static bool
4204 help_open_keymap_title(struct view *view, enum keymap keymap)
4205 {
4206 struct line *line;
4208 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4209 help_keymap_hidden[keymap] ? '+' : '-',
4210 enum_name(keymap_table[keymap]));
4211 if (line)
4212 line->other = keymap;
4214 return help_keymap_hidden[keymap];
4215 }
4217 static void
4218 help_open_keymap(struct view *view, enum keymap keymap)
4219 {
4220 const char *group = NULL;
4221 char buf[SIZEOF_STR];
4222 size_t bufpos;
4223 bool add_title = TRUE;
4224 int i;
4226 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4227 const char *key = NULL;
4229 if (req_info[i].request == REQ_NONE)
4230 continue;
4232 if (!req_info[i].request) {
4233 group = req_info[i].help;
4234 continue;
4235 }
4237 key = get_keys(keymap, req_info[i].request, TRUE);
4238 if (!key || !*key)
4239 continue;
4241 if (add_title && help_open_keymap_title(view, keymap))
4242 return;
4243 add_title = FALSE;
4245 if (group) {
4246 add_line_text(view, group, LINE_HELP_GROUP);
4247 group = NULL;
4248 }
4250 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4251 enum_name(req_info[i]), req_info[i].help);
4252 }
4254 group = "External commands:";
4256 for (i = 0; i < run_requests; i++) {
4257 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4258 const char *key;
4259 int argc;
4261 if (!req || req->keymap != keymap)
4262 continue;
4264 key = get_key_name(req->key);
4265 if (!*key)
4266 key = "(no key defined)";
4268 if (add_title && help_open_keymap_title(view, keymap))
4269 return;
4270 if (group) {
4271 add_line_text(view, group, LINE_HELP_GROUP);
4272 group = NULL;
4273 }
4275 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4276 if (!string_format_from(buf, &bufpos, "%s%s",
4277 argc ? " " : "", req->argv[argc]))
4278 return;
4280 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4281 }
4282 }
4284 static bool
4285 help_open(struct view *view)
4286 {
4287 enum keymap keymap;
4289 reset_view(view);
4290 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4291 add_line_text(view, "", LINE_DEFAULT);
4293 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4294 help_open_keymap(view, keymap);
4296 return TRUE;
4297 }
4299 static enum request
4300 help_request(struct view *view, enum request request, struct line *line)
4301 {
4302 switch (request) {
4303 case REQ_ENTER:
4304 if (line->type == LINE_HELP_KEYMAP) {
4305 help_keymap_hidden[line->other] =
4306 !help_keymap_hidden[line->other];
4307 view->p_restore = TRUE;
4308 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4309 }
4311 return REQ_NONE;
4312 default:
4313 return pager_request(view, request, line);
4314 }
4315 }
4317 static struct view_ops help_ops = {
4318 "line",
4319 NULL,
4320 help_open,
4321 NULL,
4322 pager_draw,
4323 help_request,
4324 pager_grep,
4325 pager_select,
4326 };
4329 /*
4330 * Tree backend
4331 */
4333 struct tree_stack_entry {
4334 struct tree_stack_entry *prev; /* Entry below this in the stack */
4335 unsigned long lineno; /* Line number to restore */
4336 char *name; /* Position of name in opt_path */
4337 };
4339 /* The top of the path stack. */
4340 static struct tree_stack_entry *tree_stack = NULL;
4341 unsigned long tree_lineno = 0;
4343 static void
4344 pop_tree_stack_entry(void)
4345 {
4346 struct tree_stack_entry *entry = tree_stack;
4348 tree_lineno = entry->lineno;
4349 entry->name[0] = 0;
4350 tree_stack = entry->prev;
4351 free(entry);
4352 }
4354 static void
4355 push_tree_stack_entry(const char *name, unsigned long lineno)
4356 {
4357 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4358 size_t pathlen = strlen(opt_path);
4360 if (!entry)
4361 return;
4363 entry->prev = tree_stack;
4364 entry->name = opt_path + pathlen;
4365 tree_stack = entry;
4367 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4368 pop_tree_stack_entry();
4369 return;
4370 }
4372 /* Move the current line to the first tree entry. */
4373 tree_lineno = 1;
4374 entry->lineno = lineno;
4375 }
4377 /* Parse output from git-ls-tree(1):
4378 *
4379 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4380 */
4382 #define SIZEOF_TREE_ATTR \
4383 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4385 #define SIZEOF_TREE_MODE \
4386 STRING_SIZE("100644 ")
4388 #define TREE_ID_OFFSET \
4389 STRING_SIZE("100644 blob ")
4391 struct tree_entry {
4392 char id[SIZEOF_REV];
4393 mode_t mode;
4394 struct time time; /* Date from the author ident. */
4395 const char *author; /* Author of the commit. */
4396 char name[1];
4397 };
4399 static const char *
4400 tree_path(const struct line *line)
4401 {
4402 return ((struct tree_entry *) line->data)->name;
4403 }
4405 static int
4406 tree_compare_entry(const struct line *line1, const struct line *line2)
4407 {
4408 if (line1->type != line2->type)
4409 return line1->type == LINE_TREE_DIR ? -1 : 1;
4410 return strcmp(tree_path(line1), tree_path(line2));
4411 }
4413 static const enum sort_field tree_sort_fields[] = {
4414 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4415 };
4416 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4418 static int
4419 tree_compare(const void *l1, const void *l2)
4420 {
4421 const struct line *line1 = (const struct line *) l1;
4422 const struct line *line2 = (const struct line *) l2;
4423 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4424 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4426 if (line1->type == LINE_TREE_HEAD)
4427 return -1;
4428 if (line2->type == LINE_TREE_HEAD)
4429 return 1;
4431 switch (get_sort_field(tree_sort_state)) {
4432 case ORDERBY_DATE:
4433 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4435 case ORDERBY_AUTHOR:
4436 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4438 case ORDERBY_NAME:
4439 default:
4440 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4441 }
4442 }
4445 static struct line *
4446 tree_entry(struct view *view, enum line_type type, const char *path,
4447 const char *mode, const char *id)
4448 {
4449 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4450 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4452 if (!entry || !line) {
4453 free(entry);
4454 return NULL;
4455 }
4457 strncpy(entry->name, path, strlen(path));
4458 if (mode)
4459 entry->mode = strtoul(mode, NULL, 8);
4460 if (id)
4461 string_copy_rev(entry->id, id);
4463 return line;
4464 }
4466 static bool
4467 tree_read_date(struct view *view, char *text, bool *read_date)
4468 {
4469 static const char *author_name;
4470 static struct time author_time;
4472 if (!text && *read_date) {
4473 *read_date = FALSE;
4474 return TRUE;
4476 } else if (!text) {
4477 char *path = *opt_path ? opt_path : ".";
4478 /* Find next entry to process */
4479 const char *log_file[] = {
4480 "git", "log", "--no-color", "--pretty=raw",
4481 "--cc", "--raw", view->id, "--", path, NULL
4482 };
4483 struct io io = {};
4485 if (!view->lines) {
4486 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4487 report("Tree is empty");
4488 return TRUE;
4489 }
4491 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4492 report("Failed to load tree data");
4493 return TRUE;
4494 }
4496 io_done(view->pipe);
4497 view->io = io;
4498 *read_date = TRUE;
4499 return FALSE;
4501 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4502 parse_author_line(text + STRING_SIZE("author "),
4503 &author_name, &author_time);
4505 } else if (*text == ':') {
4506 char *pos;
4507 size_t annotated = 1;
4508 size_t i;
4510 pos = strchr(text, '\t');
4511 if (!pos)
4512 return TRUE;
4513 text = pos + 1;
4514 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4515 text += strlen(opt_path);
4516 pos = strchr(text, '/');
4517 if (pos)
4518 *pos = 0;
4520 for (i = 1; i < view->lines; i++) {
4521 struct line *line = &view->line[i];
4522 struct tree_entry *entry = line->data;
4524 annotated += !!entry->author;
4525 if (entry->author || strcmp(entry->name, text))
4526 continue;
4528 entry->author = author_name;
4529 entry->time = author_time;
4530 line->dirty = 1;
4531 break;
4532 }
4534 if (annotated == view->lines)
4535 io_kill(view->pipe);
4536 }
4537 return TRUE;
4538 }
4540 static bool
4541 tree_read(struct view *view, char *text)
4542 {
4543 static bool read_date = FALSE;
4544 struct tree_entry *data;
4545 struct line *entry, *line;
4546 enum line_type type;
4547 size_t textlen = text ? strlen(text) : 0;
4548 char *path = text + SIZEOF_TREE_ATTR;
4550 if (read_date || !text)
4551 return tree_read_date(view, text, &read_date);
4553 if (textlen <= SIZEOF_TREE_ATTR)
4554 return FALSE;
4555 if (view->lines == 0 &&
4556 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4557 return FALSE;
4559 /* Strip the path part ... */
4560 if (*opt_path) {
4561 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4562 size_t striplen = strlen(opt_path);
4564 if (pathlen > striplen)
4565 memmove(path, path + striplen,
4566 pathlen - striplen + 1);
4568 /* Insert "link" to parent directory. */
4569 if (view->lines == 1 &&
4570 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4571 return FALSE;
4572 }
4574 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4575 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4576 if (!entry)
4577 return FALSE;
4578 data = entry->data;
4580 /* Skip "Directory ..." and ".." line. */
4581 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4582 if (tree_compare_entry(line, entry) <= 0)
4583 continue;
4585 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4587 line->data = data;
4588 line->type = type;
4589 for (; line <= entry; line++)
4590 line->dirty = line->cleareol = 1;
4591 return TRUE;
4592 }
4594 if (tree_lineno > view->lineno) {
4595 view->lineno = tree_lineno;
4596 tree_lineno = 0;
4597 }
4599 return TRUE;
4600 }
4602 static bool
4603 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4604 {
4605 struct tree_entry *entry = line->data;
4607 if (line->type == LINE_TREE_HEAD) {
4608 if (draw_text(view, line->type, "Directory path /", TRUE))
4609 return TRUE;
4610 } else {
4611 if (draw_mode(view, entry->mode))
4612 return TRUE;
4614 if (opt_author && draw_author(view, entry->author))
4615 return TRUE;
4617 if (opt_date && draw_date(view, &entry->time))
4618 return TRUE;
4619 }
4620 if (draw_text(view, line->type, entry->name, TRUE))
4621 return TRUE;
4622 return TRUE;
4623 }
4625 static void
4626 open_blob_editor()
4627 {
4628 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4629 int fd = mkstemp(file);
4631 if (fd == -1)
4632 report("Failed to create temporary file");
4633 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4634 report("Failed to save blob data to file");
4635 else
4636 open_editor(file);
4637 if (fd != -1)
4638 unlink(file);
4639 }
4641 static enum request
4642 tree_request(struct view *view, enum request request, struct line *line)
4643 {
4644 enum open_flags flags;
4646 switch (request) {
4647 case REQ_VIEW_BLAME:
4648 if (line->type != LINE_TREE_FILE) {
4649 report("Blame only supported for files");
4650 return REQ_NONE;
4651 }
4653 string_copy(opt_ref, view->vid);
4654 return request;
4656 case REQ_EDIT:
4657 if (line->type != LINE_TREE_FILE) {
4658 report("Edit only supported for files");
4659 } else if (!is_head_commit(view->vid)) {
4660 open_blob_editor();
4661 } else {
4662 open_editor(opt_file);
4663 }
4664 return REQ_NONE;
4666 case REQ_TOGGLE_SORT_FIELD:
4667 case REQ_TOGGLE_SORT_ORDER:
4668 sort_view(view, request, &tree_sort_state, tree_compare);
4669 return REQ_NONE;
4671 case REQ_PARENT:
4672 if (!*opt_path) {
4673 /* quit view if at top of tree */
4674 return REQ_VIEW_CLOSE;
4675 }
4676 /* fake 'cd ..' */
4677 line = &view->line[1];
4678 break;
4680 case REQ_ENTER:
4681 break;
4683 default:
4684 return request;
4685 }
4687 /* Cleanup the stack if the tree view is at a different tree. */
4688 while (!*opt_path && tree_stack)
4689 pop_tree_stack_entry();
4691 switch (line->type) {
4692 case LINE_TREE_DIR:
4693 /* Depending on whether it is a subdirectory or parent link
4694 * mangle the path buffer. */
4695 if (line == &view->line[1] && *opt_path) {
4696 pop_tree_stack_entry();
4698 } else {
4699 const char *basename = tree_path(line);
4701 push_tree_stack_entry(basename, view->lineno);
4702 }
4704 /* Trees and subtrees share the same ID, so they are not not
4705 * unique like blobs. */
4706 flags = OPEN_RELOAD;
4707 request = REQ_VIEW_TREE;
4708 break;
4710 case LINE_TREE_FILE:
4711 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4712 request = REQ_VIEW_BLOB;
4713 break;
4715 default:
4716 return REQ_NONE;
4717 }
4719 open_view(view, request, flags);
4720 if (request == REQ_VIEW_TREE)
4721 view->lineno = tree_lineno;
4723 return REQ_NONE;
4724 }
4726 static bool
4727 tree_grep(struct view *view, struct line *line)
4728 {
4729 struct tree_entry *entry = line->data;
4730 const char *text[] = {
4731 entry->name,
4732 opt_author ? entry->author : "",
4733 mkdate(&entry->time, opt_date),
4734 NULL
4735 };
4737 return grep_text(view, text);
4738 }
4740 static void
4741 tree_select(struct view *view, struct line *line)
4742 {
4743 struct tree_entry *entry = line->data;
4745 if (line->type == LINE_TREE_FILE) {
4746 string_copy_rev(ref_blob, entry->id);
4747 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4749 } else if (line->type != LINE_TREE_DIR) {
4750 return;
4751 }
4753 string_copy_rev(view->ref, entry->id);
4754 }
4756 static bool
4757 tree_prepare(struct view *view)
4758 {
4759 if (view->lines == 0 && opt_prefix[0]) {
4760 char *pos = opt_prefix;
4762 while (pos && *pos) {
4763 char *end = strchr(pos, '/');
4765 if (end)
4766 *end = 0;
4767 push_tree_stack_entry(pos, 0);
4768 pos = end;
4769 if (end) {
4770 *end = '/';
4771 pos++;
4772 }
4773 }
4775 } else if (strcmp(view->vid, view->id)) {
4776 opt_path[0] = 0;
4777 }
4779 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4780 }
4782 static const char *tree_argv[SIZEOF_ARG] = {
4783 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4784 };
4786 static struct view_ops tree_ops = {
4787 "file",
4788 tree_argv,
4789 NULL,
4790 tree_read,
4791 tree_draw,
4792 tree_request,
4793 tree_grep,
4794 tree_select,
4795 tree_prepare,
4796 };
4798 static bool
4799 blob_read(struct view *view, char *line)
4800 {
4801 if (!line)
4802 return TRUE;
4803 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4804 }
4806 static enum request
4807 blob_request(struct view *view, enum request request, struct line *line)
4808 {
4809 switch (request) {
4810 case REQ_EDIT:
4811 open_blob_editor();
4812 return REQ_NONE;
4813 default:
4814 return pager_request(view, request, line);
4815 }
4816 }
4818 static const char *blob_argv[SIZEOF_ARG] = {
4819 "git", "cat-file", "blob", "%(blob)", NULL
4820 };
4822 static struct view_ops blob_ops = {
4823 "line",
4824 blob_argv,
4825 NULL,
4826 blob_read,
4827 pager_draw,
4828 blob_request,
4829 pager_grep,
4830 pager_select,
4831 };
4833 /*
4834 * Blame backend
4835 *
4836 * Loading the blame view is a two phase job:
4837 *
4838 * 1. File content is read either using opt_file from the
4839 * filesystem or using git-cat-file.
4840 * 2. Then blame information is incrementally added by
4841 * reading output from git-blame.
4842 */
4844 static const char *blame_head_argv[] = {
4845 "git", "blame", "--incremental", "--", "%(file)", NULL
4846 };
4848 static const char *blame_ref_argv[] = {
4849 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4850 };
4852 static const char *blame_cat_file_argv[] = {
4853 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4854 };
4856 struct blame_commit {
4857 char id[SIZEOF_REV]; /* SHA1 ID. */
4858 char title[128]; /* First line of the commit message. */
4859 const char *author; /* Author of the commit. */
4860 struct time time; /* Date from the author ident. */
4861 char filename[128]; /* Name of file. */
4862 bool has_previous; /* Was a "previous" line detected. */
4863 };
4865 struct blame {
4866 struct blame_commit *commit;
4867 unsigned long lineno;
4868 char text[1];
4869 };
4871 static bool
4872 blame_open(struct view *view)
4873 {
4874 char path[SIZEOF_STR];
4876 if (!view->parent && *opt_prefix) {
4877 string_copy(path, opt_file);
4878 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4879 return FALSE;
4880 }
4882 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4883 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4884 return FALSE;
4885 }
4887 setup_update(view, opt_file);
4888 string_format(view->ref, "%s ...", opt_file);
4890 return TRUE;
4891 }
4893 static struct blame_commit *
4894 get_blame_commit(struct view *view, const char *id)
4895 {
4896 size_t i;
4898 for (i = 0; i < view->lines; i++) {
4899 struct blame *blame = view->line[i].data;
4901 if (!blame->commit)
4902 continue;
4904 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4905 return blame->commit;
4906 }
4908 {
4909 struct blame_commit *commit = calloc(1, sizeof(*commit));
4911 if (commit)
4912 string_ncopy(commit->id, id, SIZEOF_REV);
4913 return commit;
4914 }
4915 }
4917 static bool
4918 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4919 {
4920 const char *pos = *posref;
4922 *posref = NULL;
4923 pos = strchr(pos + 1, ' ');
4924 if (!pos || !isdigit(pos[1]))
4925 return FALSE;
4926 *number = atoi(pos + 1);
4927 if (*number < min || *number > max)
4928 return FALSE;
4930 *posref = pos;
4931 return TRUE;
4932 }
4934 static struct blame_commit *
4935 parse_blame_commit(struct view *view, const char *text, int *blamed)
4936 {
4937 struct blame_commit *commit;
4938 struct blame *blame;
4939 const char *pos = text + SIZEOF_REV - 2;
4940 size_t orig_lineno = 0;
4941 size_t lineno;
4942 size_t group;
4944 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4945 return NULL;
4947 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4948 !parse_number(&pos, &lineno, 1, view->lines) ||
4949 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4950 return NULL;
4952 commit = get_blame_commit(view, text);
4953 if (!commit)
4954 return NULL;
4956 *blamed += group;
4957 while (group--) {
4958 struct line *line = &view->line[lineno + group - 1];
4960 blame = line->data;
4961 blame->commit = commit;
4962 blame->lineno = orig_lineno + group - 1;
4963 line->dirty = 1;
4964 }
4966 return commit;
4967 }
4969 static bool
4970 blame_read_file(struct view *view, const char *line, bool *read_file)
4971 {
4972 if (!line) {
4973 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4974 struct io io = {};
4976 if (view->lines == 0 && !view->parent)
4977 die("No blame exist for %s", view->vid);
4979 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4980 report("Failed to load blame data");
4981 return TRUE;
4982 }
4984 io_done(view->pipe);
4985 view->io = io;
4986 *read_file = FALSE;
4987 return FALSE;
4989 } else {
4990 size_t linelen = strlen(line);
4991 struct blame *blame = malloc(sizeof(*blame) + linelen);
4993 if (!blame)
4994 return FALSE;
4996 blame->commit = NULL;
4997 strncpy(blame->text, line, linelen);
4998 blame->text[linelen] = 0;
4999 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5000 }
5001 }
5003 static bool
5004 match_blame_header(const char *name, char **line)
5005 {
5006 size_t namelen = strlen(name);
5007 bool matched = !strncmp(name, *line, namelen);
5009 if (matched)
5010 *line += namelen;
5012 return matched;
5013 }
5015 static bool
5016 blame_read(struct view *view, char *line)
5017 {
5018 static struct blame_commit *commit = NULL;
5019 static int blamed = 0;
5020 static bool read_file = TRUE;
5022 if (read_file)
5023 return blame_read_file(view, line, &read_file);
5025 if (!line) {
5026 /* Reset all! */
5027 commit = NULL;
5028 blamed = 0;
5029 read_file = TRUE;
5030 string_format(view->ref, "%s", view->vid);
5031 if (view_is_displayed(view)) {
5032 update_view_title(view);
5033 redraw_view_from(view, 0);
5034 }
5035 return TRUE;
5036 }
5038 if (!commit) {
5039 commit = parse_blame_commit(view, line, &blamed);
5040 string_format(view->ref, "%s %2d%%", view->vid,
5041 view->lines ? blamed * 100 / view->lines : 0);
5043 } else if (match_blame_header("author ", &line)) {
5044 commit->author = get_author(line);
5046 } else if (match_blame_header("author-time ", &line)) {
5047 parse_timesec(&commit->time, line);
5049 } else if (match_blame_header("author-tz ", &line)) {
5050 parse_timezone(&commit->time, line);
5052 } else if (match_blame_header("summary ", &line)) {
5053 string_ncopy(commit->title, line, strlen(line));
5055 } else if (match_blame_header("previous ", &line)) {
5056 commit->has_previous = TRUE;
5058 } else if (match_blame_header("filename ", &line)) {
5059 string_ncopy(commit->filename, line, strlen(line));
5060 commit = NULL;
5061 }
5063 return TRUE;
5064 }
5066 static bool
5067 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5068 {
5069 struct blame *blame = line->data;
5070 struct time *time = NULL;
5071 const char *id = NULL, *author = NULL;
5072 char text[SIZEOF_STR];
5074 if (blame->commit && *blame->commit->filename) {
5075 id = blame->commit->id;
5076 author = blame->commit->author;
5077 time = &blame->commit->time;
5078 }
5080 if (opt_date && draw_date(view, time))
5081 return TRUE;
5083 if (opt_author && draw_author(view, author))
5084 return TRUE;
5086 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5087 return TRUE;
5089 if (draw_lineno(view, lineno))
5090 return TRUE;
5092 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5093 draw_text(view, LINE_DEFAULT, text, TRUE);
5094 return TRUE;
5095 }
5097 static bool
5098 check_blame_commit(struct blame *blame, bool check_null_id)
5099 {
5100 if (!blame->commit)
5101 report("Commit data not loaded yet");
5102 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5103 report("No commit exist for the selected line");
5104 else
5105 return TRUE;
5106 return FALSE;
5107 }
5109 static void
5110 setup_blame_parent_line(struct view *view, struct blame *blame)
5111 {
5112 const char *diff_tree_argv[] = {
5113 "git", "diff-tree", "-U0", blame->commit->id,
5114 "--", blame->commit->filename, NULL
5115 };
5116 struct io io = {};
5117 int parent_lineno = -1;
5118 int blamed_lineno = -1;
5119 char *line;
5121 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5122 return;
5124 while ((line = io_get(&io, '\n', TRUE))) {
5125 if (*line == '@') {
5126 char *pos = strchr(line, '+');
5128 parent_lineno = atoi(line + 4);
5129 if (pos)
5130 blamed_lineno = atoi(pos + 1);
5132 } else if (*line == '+' && parent_lineno != -1) {
5133 if (blame->lineno == blamed_lineno - 1 &&
5134 !strcmp(blame->text, line + 1)) {
5135 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5136 break;
5137 }
5138 blamed_lineno++;
5139 }
5140 }
5142 io_done(&io);
5143 }
5145 static enum request
5146 blame_request(struct view *view, enum request request, struct line *line)
5147 {
5148 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5149 struct blame *blame = line->data;
5151 switch (request) {
5152 case REQ_VIEW_BLAME:
5153 if (check_blame_commit(blame, TRUE)) {
5154 string_copy(opt_ref, blame->commit->id);
5155 string_copy(opt_file, blame->commit->filename);
5156 if (blame->lineno)
5157 view->lineno = blame->lineno;
5158 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5159 }
5160 break;
5162 case REQ_PARENT:
5163 if (check_blame_commit(blame, TRUE) &&
5164 select_commit_parent(blame->commit->id, opt_ref,
5165 blame->commit->filename)) {
5166 string_copy(opt_file, blame->commit->filename);
5167 setup_blame_parent_line(view, blame);
5168 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5169 }
5170 break;
5172 case REQ_ENTER:
5173 if (!check_blame_commit(blame, FALSE))
5174 break;
5176 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5177 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5178 break;
5180 if (!strcmp(blame->commit->id, NULL_ID)) {
5181 struct view *diff = VIEW(REQ_VIEW_DIFF);
5182 const char *diff_index_argv[] = {
5183 "git", "diff-index", "--root", "--patch-with-stat",
5184 "-C", "-M", "HEAD", "--", view->vid, NULL
5185 };
5187 if (!blame->commit->has_previous) {
5188 diff_index_argv[1] = "diff";
5189 diff_index_argv[2] = "--no-color";
5190 diff_index_argv[6] = "--";
5191 diff_index_argv[7] = "/dev/null";
5192 }
5194 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5195 report("Failed to allocate diff command");
5196 break;
5197 }
5198 flags |= OPEN_PREPARED;
5199 }
5201 open_view(view, REQ_VIEW_DIFF, flags);
5202 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5203 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5204 break;
5206 default:
5207 return request;
5208 }
5210 return REQ_NONE;
5211 }
5213 static bool
5214 blame_grep(struct view *view, struct line *line)
5215 {
5216 struct blame *blame = line->data;
5217 struct blame_commit *commit = blame->commit;
5218 const char *text[] = {
5219 blame->text,
5220 commit ? commit->title : "",
5221 commit ? commit->id : "",
5222 commit && opt_author ? commit->author : "",
5223 commit ? mkdate(&commit->time, opt_date) : "",
5224 NULL
5225 };
5227 return grep_text(view, text);
5228 }
5230 static void
5231 blame_select(struct view *view, struct line *line)
5232 {
5233 struct blame *blame = line->data;
5234 struct blame_commit *commit = blame->commit;
5236 if (!commit)
5237 return;
5239 if (!strcmp(commit->id, NULL_ID))
5240 string_ncopy(ref_commit, "HEAD", 4);
5241 else
5242 string_copy_rev(ref_commit, commit->id);
5243 }
5245 static struct view_ops blame_ops = {
5246 "line",
5247 NULL,
5248 blame_open,
5249 blame_read,
5250 blame_draw,
5251 blame_request,
5252 blame_grep,
5253 blame_select,
5254 };
5256 /*
5257 * Branch backend
5258 */
5260 struct branch {
5261 const char *author; /* Author of the last commit. */
5262 struct time time; /* Date of the last activity. */
5263 const struct ref *ref; /* Name and commit ID information. */
5264 };
5266 static const struct ref branch_all;
5268 static const enum sort_field branch_sort_fields[] = {
5269 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5270 };
5271 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5273 static int
5274 branch_compare(const void *l1, const void *l2)
5275 {
5276 const struct branch *branch1 = ((const struct line *) l1)->data;
5277 const struct branch *branch2 = ((const struct line *) l2)->data;
5279 switch (get_sort_field(branch_sort_state)) {
5280 case ORDERBY_DATE:
5281 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5283 case ORDERBY_AUTHOR:
5284 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5286 case ORDERBY_NAME:
5287 default:
5288 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5289 }
5290 }
5292 static bool
5293 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5294 {
5295 struct branch *branch = line->data;
5296 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5298 if (opt_date && draw_date(view, &branch->time))
5299 return TRUE;
5301 if (opt_author && draw_author(view, branch->author))
5302 return TRUE;
5304 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5305 return TRUE;
5306 }
5308 static enum request
5309 branch_request(struct view *view, enum request request, struct line *line)
5310 {
5311 struct branch *branch = line->data;
5313 switch (request) {
5314 case REQ_REFRESH:
5315 load_refs();
5316 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5317 return REQ_NONE;
5319 case REQ_TOGGLE_SORT_FIELD:
5320 case REQ_TOGGLE_SORT_ORDER:
5321 sort_view(view, request, &branch_sort_state, branch_compare);
5322 return REQ_NONE;
5324 case REQ_ENTER:
5325 if (branch->ref == &branch_all) {
5326 const char *all_branches_argv[] = {
5327 "git", "log", "--no-color", "--pretty=raw", "--parents",
5328 "--topo-order", "--all", NULL
5329 };
5330 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5332 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5333 report("Failed to load view of all branches");
5334 return REQ_NONE;
5335 }
5336 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5337 } else {
5338 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5339 }
5340 return REQ_NONE;
5342 default:
5343 return request;
5344 }
5345 }
5347 static bool
5348 branch_read(struct view *view, char *line)
5349 {
5350 static char id[SIZEOF_REV];
5351 struct branch *reference;
5352 size_t i;
5354 if (!line)
5355 return TRUE;
5357 switch (get_line_type(line)) {
5358 case LINE_COMMIT:
5359 string_copy_rev(id, line + STRING_SIZE("commit "));
5360 return TRUE;
5362 case LINE_AUTHOR:
5363 for (i = 0, reference = NULL; i < view->lines; i++) {
5364 struct branch *branch = view->line[i].data;
5366 if (strcmp(branch->ref->id, id))
5367 continue;
5369 view->line[i].dirty = TRUE;
5370 if (reference) {
5371 branch->author = reference->author;
5372 branch->time = reference->time;
5373 continue;
5374 }
5376 parse_author_line(line + STRING_SIZE("author "),
5377 &branch->author, &branch->time);
5378 reference = branch;
5379 }
5380 return TRUE;
5382 default:
5383 return TRUE;
5384 }
5386 }
5388 static bool
5389 branch_open_visitor(void *data, const struct ref *ref)
5390 {
5391 struct view *view = data;
5392 struct branch *branch;
5394 if (ref->tag || ref->ltag || ref->remote)
5395 return TRUE;
5397 branch = calloc(1, sizeof(*branch));
5398 if (!branch)
5399 return FALSE;
5401 branch->ref = ref;
5402 return !!add_line_data(view, branch, LINE_DEFAULT);
5403 }
5405 static bool
5406 branch_open(struct view *view)
5407 {
5408 const char *branch_log[] = {
5409 "git", "log", "--no-color", "--pretty=raw",
5410 "--simplify-by-decoration", "--all", NULL
5411 };
5413 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5414 report("Failed to load branch data");
5415 return TRUE;
5416 }
5418 setup_update(view, view->id);
5419 branch_open_visitor(view, &branch_all);
5420 foreach_ref(branch_open_visitor, view);
5421 view->p_restore = TRUE;
5423 return TRUE;
5424 }
5426 static bool
5427 branch_grep(struct view *view, struct line *line)
5428 {
5429 struct branch *branch = line->data;
5430 const char *text[] = {
5431 branch->ref->name,
5432 branch->author,
5433 NULL
5434 };
5436 return grep_text(view, text);
5437 }
5439 static void
5440 branch_select(struct view *view, struct line *line)
5441 {
5442 struct branch *branch = line->data;
5444 string_copy_rev(view->ref, branch->ref->id);
5445 string_copy_rev(ref_commit, branch->ref->id);
5446 string_copy_rev(ref_head, branch->ref->id);
5447 string_copy_rev(ref_branch, branch->ref->name);
5448 }
5450 static struct view_ops branch_ops = {
5451 "branch",
5452 NULL,
5453 branch_open,
5454 branch_read,
5455 branch_draw,
5456 branch_request,
5457 branch_grep,
5458 branch_select,
5459 };
5461 /*
5462 * Status backend
5463 */
5465 struct status {
5466 char status;
5467 struct {
5468 mode_t mode;
5469 char rev[SIZEOF_REV];
5470 char name[SIZEOF_STR];
5471 } old;
5472 struct {
5473 mode_t mode;
5474 char rev[SIZEOF_REV];
5475 char name[SIZEOF_STR];
5476 } new;
5477 };
5479 static char status_onbranch[SIZEOF_STR];
5480 static struct status stage_status;
5481 static enum line_type stage_line_type;
5482 static size_t stage_chunks;
5483 static int *stage_chunk;
5485 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5487 /* This should work even for the "On branch" line. */
5488 static inline bool
5489 status_has_none(struct view *view, struct line *line)
5490 {
5491 return line < view->line + view->lines && !line[1].data;
5492 }
5494 /* Get fields from the diff line:
5495 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5496 */
5497 static inline bool
5498 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5499 {
5500 const char *old_mode = buf + 1;
5501 const char *new_mode = buf + 8;
5502 const char *old_rev = buf + 15;
5503 const char *new_rev = buf + 56;
5504 const char *status = buf + 97;
5506 if (bufsize < 98 ||
5507 old_mode[-1] != ':' ||
5508 new_mode[-1] != ' ' ||
5509 old_rev[-1] != ' ' ||
5510 new_rev[-1] != ' ' ||
5511 status[-1] != ' ')
5512 return FALSE;
5514 file->status = *status;
5516 string_copy_rev(file->old.rev, old_rev);
5517 string_copy_rev(file->new.rev, new_rev);
5519 file->old.mode = strtoul(old_mode, NULL, 8);
5520 file->new.mode = strtoul(new_mode, NULL, 8);
5522 file->old.name[0] = file->new.name[0] = 0;
5524 return TRUE;
5525 }
5527 static bool
5528 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5529 {
5530 struct status *unmerged = NULL;
5531 char *buf;
5532 struct io io = {};
5534 if (!io_run(&io, argv, opt_cdup, IO_RD))
5535 return FALSE;
5537 add_line_data(view, NULL, type);
5539 while ((buf = io_get(&io, 0, TRUE))) {
5540 struct status *file = unmerged;
5542 if (!file) {
5543 file = calloc(1, sizeof(*file));
5544 if (!file || !add_line_data(view, file, type))
5545 goto error_out;
5546 }
5548 /* Parse diff info part. */
5549 if (status) {
5550 file->status = status;
5551 if (status == 'A')
5552 string_copy(file->old.rev, NULL_ID);
5554 } else if (!file->status || file == unmerged) {
5555 if (!status_get_diff(file, buf, strlen(buf)))
5556 goto error_out;
5558 buf = io_get(&io, 0, TRUE);
5559 if (!buf)
5560 break;
5562 /* Collapse all modified entries that follow an
5563 * associated unmerged entry. */
5564 if (unmerged == file) {
5565 unmerged->status = 'U';
5566 unmerged = NULL;
5567 } else if (file->status == 'U') {
5568 unmerged = file;
5569 }
5570 }
5572 /* Grab the old name for rename/copy. */
5573 if (!*file->old.name &&
5574 (file->status == 'R' || file->status == 'C')) {
5575 string_ncopy(file->old.name, buf, strlen(buf));
5577 buf = io_get(&io, 0, TRUE);
5578 if (!buf)
5579 break;
5580 }
5582 /* git-ls-files just delivers a NUL separated list of
5583 * file names similar to the second half of the
5584 * git-diff-* output. */
5585 string_ncopy(file->new.name, buf, strlen(buf));
5586 if (!*file->old.name)
5587 string_copy(file->old.name, file->new.name);
5588 file = NULL;
5589 }
5591 if (io_error(&io)) {
5592 error_out:
5593 io_done(&io);
5594 return FALSE;
5595 }
5597 if (!view->line[view->lines - 1].data)
5598 add_line_data(view, NULL, LINE_STAT_NONE);
5600 io_done(&io);
5601 return TRUE;
5602 }
5604 /* Don't show unmerged entries in the staged section. */
5605 static const char *status_diff_index_argv[] = {
5606 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5607 "--cached", "-M", "HEAD", NULL
5608 };
5610 static const char *status_diff_files_argv[] = {
5611 "git", "diff-files", "-z", NULL
5612 };
5614 static const char *status_list_other_argv[] = {
5615 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5616 };
5618 static const char *status_list_no_head_argv[] = {
5619 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5620 };
5622 static const char *update_index_argv[] = {
5623 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5624 };
5626 /* Restore the previous line number to stay in the context or select a
5627 * line with something that can be updated. */
5628 static void
5629 status_restore(struct view *view)
5630 {
5631 if (view->p_lineno >= view->lines)
5632 view->p_lineno = view->lines - 1;
5633 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5634 view->p_lineno++;
5635 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5636 view->p_lineno--;
5638 /* If the above fails, always skip the "On branch" line. */
5639 if (view->p_lineno < view->lines)
5640 view->lineno = view->p_lineno;
5641 else
5642 view->lineno = 1;
5644 if (view->lineno < view->offset)
5645 view->offset = view->lineno;
5646 else if (view->offset + view->height <= view->lineno)
5647 view->offset = view->lineno - view->height + 1;
5649 view->p_restore = FALSE;
5650 }
5652 static void
5653 status_update_onbranch(void)
5654 {
5655 static const char *paths[][2] = {
5656 { "rebase-apply/rebasing", "Rebasing" },
5657 { "rebase-apply/applying", "Applying mailbox" },
5658 { "rebase-apply/", "Rebasing mailbox" },
5659 { "rebase-merge/interactive", "Interactive rebase" },
5660 { "rebase-merge/", "Rebase merge" },
5661 { "MERGE_HEAD", "Merging" },
5662 { "BISECT_LOG", "Bisecting" },
5663 { "HEAD", "On branch" },
5664 };
5665 char buf[SIZEOF_STR];
5666 struct stat stat;
5667 int i;
5669 if (is_initial_commit()) {
5670 string_copy(status_onbranch, "Initial commit");
5671 return;
5672 }
5674 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5675 char *head = opt_head;
5677 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5678 lstat(buf, &stat) < 0)
5679 continue;
5681 if (!*opt_head) {
5682 struct io io = {};
5684 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5685 io_read_buf(&io, buf, sizeof(buf))) {
5686 head = buf;
5687 if (!prefixcmp(head, "refs/heads/"))
5688 head += STRING_SIZE("refs/heads/");
5689 }
5690 }
5692 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5693 string_copy(status_onbranch, opt_head);
5694 return;
5695 }
5697 string_copy(status_onbranch, "Not currently on any branch");
5698 }
5700 /* First parse staged info using git-diff-index(1), then parse unstaged
5701 * info using git-diff-files(1), and finally untracked files using
5702 * git-ls-files(1). */
5703 static bool
5704 status_open(struct view *view)
5705 {
5706 reset_view(view);
5708 add_line_data(view, NULL, LINE_STAT_HEAD);
5709 status_update_onbranch();
5711 io_run_bg(update_index_argv);
5713 if (is_initial_commit()) {
5714 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5715 return FALSE;
5716 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5717 return FALSE;
5718 }
5720 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5721 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5722 return FALSE;
5724 /* Restore the exact position or use the specialized restore
5725 * mode? */
5726 if (!view->p_restore)
5727 status_restore(view);
5728 return TRUE;
5729 }
5731 static bool
5732 status_draw(struct view *view, struct line *line, unsigned int lineno)
5733 {
5734 struct status *status = line->data;
5735 enum line_type type;
5736 const char *text;
5738 if (!status) {
5739 switch (line->type) {
5740 case LINE_STAT_STAGED:
5741 type = LINE_STAT_SECTION;
5742 text = "Changes to be committed:";
5743 break;
5745 case LINE_STAT_UNSTAGED:
5746 type = LINE_STAT_SECTION;
5747 text = "Changed but not updated:";
5748 break;
5750 case LINE_STAT_UNTRACKED:
5751 type = LINE_STAT_SECTION;
5752 text = "Untracked files:";
5753 break;
5755 case LINE_STAT_NONE:
5756 type = LINE_DEFAULT;
5757 text = " (no files)";
5758 break;
5760 case LINE_STAT_HEAD:
5761 type = LINE_STAT_HEAD;
5762 text = status_onbranch;
5763 break;
5765 default:
5766 return FALSE;
5767 }
5768 } else {
5769 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5771 buf[0] = status->status;
5772 if (draw_text(view, line->type, buf, TRUE))
5773 return TRUE;
5774 type = LINE_DEFAULT;
5775 text = status->new.name;
5776 }
5778 draw_text(view, type, text, TRUE);
5779 return TRUE;
5780 }
5782 static enum request
5783 status_load_error(struct view *view, struct view *stage, const char *path)
5784 {
5785 if (displayed_views() == 2 || display[current_view] != view)
5786 maximize_view(view);
5787 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5788 return REQ_NONE;
5789 }
5791 static enum request
5792 status_enter(struct view *view, struct line *line)
5793 {
5794 struct status *status = line->data;
5795 const char *oldpath = status ? status->old.name : NULL;
5796 /* Diffs for unmerged entries are empty when passing the new
5797 * path, so leave it empty. */
5798 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5799 const char *info;
5800 enum open_flags split;
5801 struct view *stage = VIEW(REQ_VIEW_STAGE);
5803 if (line->type == LINE_STAT_NONE ||
5804 (!status && line[1].type == LINE_STAT_NONE)) {
5805 report("No file to diff");
5806 return REQ_NONE;
5807 }
5809 switch (line->type) {
5810 case LINE_STAT_STAGED:
5811 if (is_initial_commit()) {
5812 const char *no_head_diff_argv[] = {
5813 "git", "diff", "--no-color", "--patch-with-stat",
5814 "--", "/dev/null", newpath, NULL
5815 };
5817 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5818 return status_load_error(view, stage, newpath);
5819 } else {
5820 const char *index_show_argv[] = {
5821 "git", "diff-index", "--root", "--patch-with-stat",
5822 "-C", "-M", "--cached", "HEAD", "--",
5823 oldpath, newpath, NULL
5824 };
5826 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5827 return status_load_error(view, stage, newpath);
5828 }
5830 if (status)
5831 info = "Staged changes to %s";
5832 else
5833 info = "Staged changes";
5834 break;
5836 case LINE_STAT_UNSTAGED:
5837 {
5838 const char *files_show_argv[] = {
5839 "git", "diff-files", "--root", "--patch-with-stat",
5840 "-C", "-M", "--", oldpath, newpath, NULL
5841 };
5843 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5844 return status_load_error(view, stage, newpath);
5845 if (status)
5846 info = "Unstaged changes to %s";
5847 else
5848 info = "Unstaged changes";
5849 break;
5850 }
5851 case LINE_STAT_UNTRACKED:
5852 if (!newpath) {
5853 report("No file to show");
5854 return REQ_NONE;
5855 }
5857 if (!suffixcmp(status->new.name, -1, "/")) {
5858 report("Cannot display a directory");
5859 return REQ_NONE;
5860 }
5862 if (!prepare_update_file(stage, newpath))
5863 return status_load_error(view, stage, newpath);
5864 info = "Untracked file %s";
5865 break;
5867 case LINE_STAT_HEAD:
5868 return REQ_NONE;
5870 default:
5871 die("line type %d not handled in switch", line->type);
5872 }
5874 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5875 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5876 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5877 if (status) {
5878 stage_status = *status;
5879 } else {
5880 memset(&stage_status, 0, sizeof(stage_status));
5881 }
5883 stage_line_type = line->type;
5884 stage_chunks = 0;
5885 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5886 }
5888 return REQ_NONE;
5889 }
5891 static bool
5892 status_exists(struct status *status, enum line_type type)
5893 {
5894 struct view *view = VIEW(REQ_VIEW_STATUS);
5895 unsigned long lineno;
5897 for (lineno = 0; lineno < view->lines; lineno++) {
5898 struct line *line = &view->line[lineno];
5899 struct status *pos = line->data;
5901 if (line->type != type)
5902 continue;
5903 if (!pos && (!status || !status->status) && line[1].data) {
5904 select_view_line(view, lineno);
5905 return TRUE;
5906 }
5907 if (pos && !strcmp(status->new.name, pos->new.name)) {
5908 select_view_line(view, lineno);
5909 return TRUE;
5910 }
5911 }
5913 return FALSE;
5914 }
5917 static bool
5918 status_update_prepare(struct io *io, enum line_type type)
5919 {
5920 const char *staged_argv[] = {
5921 "git", "update-index", "-z", "--index-info", NULL
5922 };
5923 const char *others_argv[] = {
5924 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5925 };
5927 switch (type) {
5928 case LINE_STAT_STAGED:
5929 return io_run(io, staged_argv, opt_cdup, IO_WR);
5931 case LINE_STAT_UNSTAGED:
5932 case LINE_STAT_UNTRACKED:
5933 return io_run(io, others_argv, opt_cdup, IO_WR);
5935 default:
5936 die("line type %d not handled in switch", type);
5937 return FALSE;
5938 }
5939 }
5941 static bool
5942 status_update_write(struct io *io, struct status *status, enum line_type type)
5943 {
5944 char buf[SIZEOF_STR];
5945 size_t bufsize = 0;
5947 switch (type) {
5948 case LINE_STAT_STAGED:
5949 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5950 status->old.mode,
5951 status->old.rev,
5952 status->old.name, 0))
5953 return FALSE;
5954 break;
5956 case LINE_STAT_UNSTAGED:
5957 case LINE_STAT_UNTRACKED:
5958 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5959 return FALSE;
5960 break;
5962 default:
5963 die("line type %d not handled in switch", type);
5964 }
5966 return io_write(io, buf, bufsize);
5967 }
5969 static bool
5970 status_update_file(struct status *status, enum line_type type)
5971 {
5972 struct io io = {};
5973 bool result;
5975 if (!status_update_prepare(&io, type))
5976 return FALSE;
5978 result = status_update_write(&io, status, type);
5979 return io_done(&io) && result;
5980 }
5982 static bool
5983 status_update_files(struct view *view, struct line *line)
5984 {
5985 char buf[sizeof(view->ref)];
5986 struct io io = {};
5987 bool result = TRUE;
5988 struct line *pos = view->line + view->lines;
5989 int files = 0;
5990 int file, done;
5991 int cursor_y = -1, cursor_x = -1;
5993 if (!status_update_prepare(&io, line->type))
5994 return FALSE;
5996 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5997 files++;
5999 string_copy(buf, view->ref);
6000 getsyx(cursor_y, cursor_x);
6001 for (file = 0, done = 5; result && file < files; line++, file++) {
6002 int almost_done = file * 100 / files;
6004 if (almost_done > done) {
6005 done = almost_done;
6006 string_format(view->ref, "updating file %u of %u (%d%% done)",
6007 file, files, done);
6008 update_view_title(view);
6009 setsyx(cursor_y, cursor_x);
6010 doupdate();
6011 }
6012 result = status_update_write(&io, line->data, line->type);
6013 }
6014 string_copy(view->ref, buf);
6016 return io_done(&io) && result;
6017 }
6019 static bool
6020 status_update(struct view *view)
6021 {
6022 struct line *line = &view->line[view->lineno];
6024 assert(view->lines);
6026 if (!line->data) {
6027 /* This should work even for the "On branch" line. */
6028 if (line < view->line + view->lines && !line[1].data) {
6029 report("Nothing to update");
6030 return FALSE;
6031 }
6033 if (!status_update_files(view, line + 1)) {
6034 report("Failed to update file status");
6035 return FALSE;
6036 }
6038 } else if (!status_update_file(line->data, line->type)) {
6039 report("Failed to update file status");
6040 return FALSE;
6041 }
6043 return TRUE;
6044 }
6046 static bool
6047 status_revert(struct status *status, enum line_type type, bool has_none)
6048 {
6049 if (!status || type != LINE_STAT_UNSTAGED) {
6050 if (type == LINE_STAT_STAGED) {
6051 report("Cannot revert changes to staged files");
6052 } else if (type == LINE_STAT_UNTRACKED) {
6053 report("Cannot revert changes to untracked files");
6054 } else if (has_none) {
6055 report("Nothing to revert");
6056 } else {
6057 report("Cannot revert changes to multiple files");
6058 }
6060 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6061 char mode[10] = "100644";
6062 const char *reset_argv[] = {
6063 "git", "update-index", "--cacheinfo", mode,
6064 status->old.rev, status->old.name, NULL
6065 };
6066 const char *checkout_argv[] = {
6067 "git", "checkout", "--", status->old.name, NULL
6068 };
6070 if (status->status == 'U') {
6071 string_format(mode, "%5o", status->old.mode);
6073 if (status->old.mode == 0 && status->new.mode == 0) {
6074 reset_argv[2] = "--force-remove";
6075 reset_argv[3] = status->old.name;
6076 reset_argv[4] = NULL;
6077 }
6079 if (!io_run_fg(reset_argv, opt_cdup))
6080 return FALSE;
6081 if (status->old.mode == 0 && status->new.mode == 0)
6082 return TRUE;
6083 }
6085 return io_run_fg(checkout_argv, opt_cdup);
6086 }
6088 return FALSE;
6089 }
6091 static enum request
6092 status_request(struct view *view, enum request request, struct line *line)
6093 {
6094 struct status *status = line->data;
6096 switch (request) {
6097 case REQ_STATUS_UPDATE:
6098 if (!status_update(view))
6099 return REQ_NONE;
6100 break;
6102 case REQ_STATUS_REVERT:
6103 if (!status_revert(status, line->type, status_has_none(view, line)))
6104 return REQ_NONE;
6105 break;
6107 case REQ_STATUS_MERGE:
6108 if (!status || status->status != 'U') {
6109 report("Merging only possible for files with unmerged status ('U').");
6110 return REQ_NONE;
6111 }
6112 open_mergetool(status->new.name);
6113 break;
6115 case REQ_EDIT:
6116 if (!status)
6117 return request;
6118 if (status->status == 'D') {
6119 report("File has been deleted.");
6120 return REQ_NONE;
6121 }
6123 open_editor(status->new.name);
6124 break;
6126 case REQ_VIEW_BLAME:
6127 if (status)
6128 opt_ref[0] = 0;
6129 return request;
6131 case REQ_ENTER:
6132 /* After returning the status view has been split to
6133 * show the stage view. No further reloading is
6134 * necessary. */
6135 return status_enter(view, line);
6137 case REQ_REFRESH:
6138 /* Simply reload the view. */
6139 break;
6141 default:
6142 return request;
6143 }
6145 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6147 return REQ_NONE;
6148 }
6150 static void
6151 status_select(struct view *view, struct line *line)
6152 {
6153 struct status *status = line->data;
6154 char file[SIZEOF_STR] = "all files";
6155 const char *text;
6156 const char *key;
6158 if (status && !string_format(file, "'%s'", status->new.name))
6159 return;
6161 if (!status && line[1].type == LINE_STAT_NONE)
6162 line++;
6164 switch (line->type) {
6165 case LINE_STAT_STAGED:
6166 text = "Press %s to unstage %s for commit";
6167 break;
6169 case LINE_STAT_UNSTAGED:
6170 text = "Press %s to stage %s for commit";
6171 break;
6173 case LINE_STAT_UNTRACKED:
6174 text = "Press %s to stage %s for addition";
6175 break;
6177 case LINE_STAT_HEAD:
6178 case LINE_STAT_NONE:
6179 text = "Nothing to update";
6180 break;
6182 default:
6183 die("line type %d not handled in switch", line->type);
6184 }
6186 if (status && status->status == 'U') {
6187 text = "Press %s to resolve conflict in %s";
6188 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6190 } else {
6191 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6192 }
6194 string_format(view->ref, text, key, file);
6195 if (status)
6196 string_copy(opt_file, status->new.name);
6197 }
6199 static bool
6200 status_grep(struct view *view, struct line *line)
6201 {
6202 struct status *status = line->data;
6204 if (status) {
6205 const char buf[2] = { status->status, 0 };
6206 const char *text[] = { status->new.name, buf, NULL };
6208 return grep_text(view, text);
6209 }
6211 return FALSE;
6212 }
6214 static struct view_ops status_ops = {
6215 "file",
6216 NULL,
6217 status_open,
6218 NULL,
6219 status_draw,
6220 status_request,
6221 status_grep,
6222 status_select,
6223 };
6226 static bool
6227 stage_diff_write(struct io *io, struct line *line, struct line *end)
6228 {
6229 while (line < end) {
6230 if (!io_write(io, line->data, strlen(line->data)) ||
6231 !io_write(io, "\n", 1))
6232 return FALSE;
6233 line++;
6234 if (line->type == LINE_DIFF_CHUNK ||
6235 line->type == LINE_DIFF_HEADER)
6236 break;
6237 }
6239 return TRUE;
6240 }
6242 static struct line *
6243 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6244 {
6245 for (; view->line < line; line--)
6246 if (line->type == type)
6247 return line;
6249 return NULL;
6250 }
6252 static bool
6253 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6254 {
6255 const char *apply_argv[SIZEOF_ARG] = {
6256 "git", "apply", "--whitespace=nowarn", NULL
6257 };
6258 struct line *diff_hdr;
6259 struct io io = {};
6260 int argc = 3;
6262 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6263 if (!diff_hdr)
6264 return FALSE;
6266 if (!revert)
6267 apply_argv[argc++] = "--cached";
6268 if (revert || stage_line_type == LINE_STAT_STAGED)
6269 apply_argv[argc++] = "-R";
6270 apply_argv[argc++] = "-";
6271 apply_argv[argc++] = NULL;
6272 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6273 return FALSE;
6275 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6276 !stage_diff_write(&io, chunk, view->line + view->lines))
6277 chunk = NULL;
6279 io_done(&io);
6280 io_run_bg(update_index_argv);
6282 return chunk ? TRUE : FALSE;
6283 }
6285 static bool
6286 stage_update(struct view *view, struct line *line)
6287 {
6288 struct line *chunk = NULL;
6290 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6291 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6293 if (chunk) {
6294 if (!stage_apply_chunk(view, chunk, FALSE)) {
6295 report("Failed to apply chunk");
6296 return FALSE;
6297 }
6299 } else if (!stage_status.status) {
6300 view = VIEW(REQ_VIEW_STATUS);
6302 for (line = view->line; line < view->line + view->lines; line++)
6303 if (line->type == stage_line_type)
6304 break;
6306 if (!status_update_files(view, line + 1)) {
6307 report("Failed to update files");
6308 return FALSE;
6309 }
6311 } else if (!status_update_file(&stage_status, stage_line_type)) {
6312 report("Failed to update file");
6313 return FALSE;
6314 }
6316 return TRUE;
6317 }
6319 static bool
6320 stage_revert(struct view *view, struct line *line)
6321 {
6322 struct line *chunk = NULL;
6324 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6325 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6327 if (chunk) {
6328 if (!prompt_yesno("Are you sure you want to revert changes?"))
6329 return FALSE;
6331 if (!stage_apply_chunk(view, chunk, TRUE)) {
6332 report("Failed to revert chunk");
6333 return FALSE;
6334 }
6335 return TRUE;
6337 } else {
6338 return status_revert(stage_status.status ? &stage_status : NULL,
6339 stage_line_type, FALSE);
6340 }
6341 }
6344 static void
6345 stage_next(struct view *view, struct line *line)
6346 {
6347 int i;
6349 if (!stage_chunks) {
6350 for (line = view->line; line < view->line + view->lines; line++) {
6351 if (line->type != LINE_DIFF_CHUNK)
6352 continue;
6354 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6355 report("Allocation failure");
6356 return;
6357 }
6359 stage_chunk[stage_chunks++] = line - view->line;
6360 }
6361 }
6363 for (i = 0; i < stage_chunks; i++) {
6364 if (stage_chunk[i] > view->lineno) {
6365 do_scroll_view(view, stage_chunk[i] - view->lineno);
6366 report("Chunk %d of %d", i + 1, stage_chunks);
6367 return;
6368 }
6369 }
6371 report("No next chunk found");
6372 }
6374 static enum request
6375 stage_request(struct view *view, enum request request, struct line *line)
6376 {
6377 switch (request) {
6378 case REQ_STATUS_UPDATE:
6379 if (!stage_update(view, line))
6380 return REQ_NONE;
6381 break;
6383 case REQ_STATUS_REVERT:
6384 if (!stage_revert(view, line))
6385 return REQ_NONE;
6386 break;
6388 case REQ_STAGE_NEXT:
6389 if (stage_line_type == LINE_STAT_UNTRACKED) {
6390 report("File is untracked; press %s to add",
6391 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6392 return REQ_NONE;
6393 }
6394 stage_next(view, line);
6395 return REQ_NONE;
6397 case REQ_EDIT:
6398 if (!stage_status.new.name[0])
6399 return request;
6400 if (stage_status.status == 'D') {
6401 report("File has been deleted.");
6402 return REQ_NONE;
6403 }
6405 open_editor(stage_status.new.name);
6406 break;
6408 case REQ_REFRESH:
6409 /* Reload everything ... */
6410 break;
6412 case REQ_VIEW_BLAME:
6413 if (stage_status.new.name[0]) {
6414 string_copy(opt_file, stage_status.new.name);
6415 opt_ref[0] = 0;
6416 }
6417 return request;
6419 case REQ_ENTER:
6420 return pager_request(view, request, line);
6422 default:
6423 return request;
6424 }
6426 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6427 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6429 /* Check whether the staged entry still exists, and close the
6430 * stage view if it doesn't. */
6431 if (!status_exists(&stage_status, stage_line_type)) {
6432 status_restore(VIEW(REQ_VIEW_STATUS));
6433 return REQ_VIEW_CLOSE;
6434 }
6436 if (stage_line_type == LINE_STAT_UNTRACKED) {
6437 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6438 report("Cannot display a directory");
6439 return REQ_NONE;
6440 }
6442 if (!prepare_update_file(view, stage_status.new.name)) {
6443 report("Failed to open file: %s", strerror(errno));
6444 return REQ_NONE;
6445 }
6446 }
6447 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6449 return REQ_NONE;
6450 }
6452 static struct view_ops stage_ops = {
6453 "line",
6454 NULL,
6455 NULL,
6456 pager_read,
6457 pager_draw,
6458 stage_request,
6459 pager_grep,
6460 pager_select,
6461 };
6464 /*
6465 * Revision graph
6466 */
6468 struct commit {
6469 char id[SIZEOF_REV]; /* SHA1 ID. */
6470 char title[128]; /* First line of the commit message. */
6471 const char *author; /* Author of the commit. */
6472 struct time time; /* Date from the author ident. */
6473 struct ref_list *refs; /* Repository references. */
6474 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6475 size_t graph_size; /* The width of the graph array. */
6476 bool has_parents; /* Rewritten --parents seen. */
6477 };
6479 /* Size of rev graph with no "padding" columns */
6480 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6482 struct rev_graph {
6483 struct rev_graph *prev, *next, *parents;
6484 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6485 size_t size;
6486 struct commit *commit;
6487 size_t pos;
6488 unsigned int boundary:1;
6489 };
6491 /* Parents of the commit being visualized. */
6492 static struct rev_graph graph_parents[4];
6494 /* The current stack of revisions on the graph. */
6495 static struct rev_graph graph_stacks[4] = {
6496 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6497 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6498 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6499 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6500 };
6502 static inline bool
6503 graph_parent_is_merge(struct rev_graph *graph)
6504 {
6505 return graph->parents->size > 1;
6506 }
6508 static inline void
6509 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6510 {
6511 struct commit *commit = graph->commit;
6513 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6514 commit->graph[commit->graph_size++] = symbol;
6515 }
6517 static void
6518 clear_rev_graph(struct rev_graph *graph)
6519 {
6520 graph->boundary = 0;
6521 graph->size = graph->pos = 0;
6522 graph->commit = NULL;
6523 memset(graph->parents, 0, sizeof(*graph->parents));
6524 }
6526 static void
6527 done_rev_graph(struct rev_graph *graph)
6528 {
6529 if (graph_parent_is_merge(graph) &&
6530 graph->pos < graph->size - 1 &&
6531 graph->next->size == graph->size + graph->parents->size - 1) {
6532 size_t i = graph->pos + graph->parents->size - 1;
6534 graph->commit->graph_size = i * 2;
6535 while (i < graph->next->size - 1) {
6536 append_to_rev_graph(graph, ' ');
6537 append_to_rev_graph(graph, '\\');
6538 i++;
6539 }
6540 }
6542 clear_rev_graph(graph);
6543 }
6545 static void
6546 push_rev_graph(struct rev_graph *graph, const char *parent)
6547 {
6548 int i;
6550 /* "Collapse" duplicate parents lines.
6551 *
6552 * FIXME: This needs to also update update the drawn graph but
6553 * for now it just serves as a method for pruning graph lines. */
6554 for (i = 0; i < graph->size; i++)
6555 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6556 return;
6558 if (graph->size < SIZEOF_REVITEMS) {
6559 string_copy_rev(graph->rev[graph->size++], parent);
6560 }
6561 }
6563 static chtype
6564 get_rev_graph_symbol(struct rev_graph *graph)
6565 {
6566 chtype symbol;
6568 if (graph->boundary)
6569 symbol = REVGRAPH_BOUND;
6570 else if (graph->parents->size == 0)
6571 symbol = REVGRAPH_INIT;
6572 else if (graph_parent_is_merge(graph))
6573 symbol = REVGRAPH_MERGE;
6574 else if (graph->pos >= graph->size)
6575 symbol = REVGRAPH_BRANCH;
6576 else
6577 symbol = REVGRAPH_COMMIT;
6579 return symbol;
6580 }
6582 static void
6583 draw_rev_graph(struct rev_graph *graph)
6584 {
6585 struct rev_filler {
6586 chtype separator, line;
6587 };
6588 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6589 static struct rev_filler fillers[] = {
6590 { ' ', '|' },
6591 { '`', '.' },
6592 { '\'', ' ' },
6593 { '/', ' ' },
6594 };
6595 chtype symbol = get_rev_graph_symbol(graph);
6596 struct rev_filler *filler;
6597 size_t i;
6599 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6600 filler = &fillers[DEFAULT];
6602 for (i = 0; i < graph->pos; i++) {
6603 append_to_rev_graph(graph, filler->line);
6604 if (graph_parent_is_merge(graph->prev) &&
6605 graph->prev->pos == i)
6606 filler = &fillers[RSHARP];
6608 append_to_rev_graph(graph, filler->separator);
6609 }
6611 /* Place the symbol for this revision. */
6612 append_to_rev_graph(graph, symbol);
6614 if (graph->prev->size > graph->size)
6615 filler = &fillers[RDIAG];
6616 else
6617 filler = &fillers[DEFAULT];
6619 i++;
6621 for (; i < graph->size; i++) {
6622 append_to_rev_graph(graph, filler->separator);
6623 append_to_rev_graph(graph, filler->line);
6624 if (graph_parent_is_merge(graph->prev) &&
6625 i < graph->prev->pos + graph->parents->size)
6626 filler = &fillers[RSHARP];
6627 if (graph->prev->size > graph->size)
6628 filler = &fillers[LDIAG];
6629 }
6631 if (graph->prev->size > graph->size) {
6632 append_to_rev_graph(graph, filler->separator);
6633 if (filler->line != ' ')
6634 append_to_rev_graph(graph, filler->line);
6635 }
6636 }
6638 /* Prepare the next rev graph */
6639 static void
6640 prepare_rev_graph(struct rev_graph *graph)
6641 {
6642 size_t i;
6644 /* First, traverse all lines of revisions up to the active one. */
6645 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6646 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6647 break;
6649 push_rev_graph(graph->next, graph->rev[graph->pos]);
6650 }
6652 /* Interleave the new revision parent(s). */
6653 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6654 push_rev_graph(graph->next, graph->parents->rev[i]);
6656 /* Lastly, put any remaining revisions. */
6657 for (i = graph->pos + 1; i < graph->size; i++)
6658 push_rev_graph(graph->next, graph->rev[i]);
6659 }
6661 static void
6662 update_rev_graph(struct view *view, struct rev_graph *graph)
6663 {
6664 /* If this is the finalizing update ... */
6665 if (graph->commit)
6666 prepare_rev_graph(graph);
6668 /* Graph visualization needs a one rev look-ahead,
6669 * so the first update doesn't visualize anything. */
6670 if (!graph->prev->commit)
6671 return;
6673 if (view->lines > 2)
6674 view->line[view->lines - 3].dirty = 1;
6675 if (view->lines > 1)
6676 view->line[view->lines - 2].dirty = 1;
6677 draw_rev_graph(graph->prev);
6678 done_rev_graph(graph->prev->prev);
6679 }
6682 /*
6683 * Main view backend
6684 */
6686 static const char *main_argv[SIZEOF_ARG] = {
6687 "git", "log", "--no-color", "--pretty=raw", "--parents",
6688 "--topo-order", "%(head)", NULL
6689 };
6691 static bool
6692 main_draw(struct view *view, struct line *line, unsigned int lineno)
6693 {
6694 struct commit *commit = line->data;
6696 if (!commit->author)
6697 return FALSE;
6699 if (opt_date && draw_date(view, &commit->time))
6700 return TRUE;
6702 if (opt_author && draw_author(view, commit->author))
6703 return TRUE;
6705 if (opt_rev_graph && commit->graph_size &&
6706 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6707 return TRUE;
6709 if (opt_show_refs && commit->refs) {
6710 size_t i;
6712 for (i = 0; i < commit->refs->size; i++) {
6713 struct ref *ref = commit->refs->refs[i];
6714 enum line_type type;
6716 if (ref->head)
6717 type = LINE_MAIN_HEAD;
6718 else if (ref->ltag)
6719 type = LINE_MAIN_LOCAL_TAG;
6720 else if (ref->tag)
6721 type = LINE_MAIN_TAG;
6722 else if (ref->tracked)
6723 type = LINE_MAIN_TRACKED;
6724 else if (ref->remote)
6725 type = LINE_MAIN_REMOTE;
6726 else
6727 type = LINE_MAIN_REF;
6729 if (draw_text(view, type, "[", TRUE) ||
6730 draw_text(view, type, ref->name, TRUE) ||
6731 draw_text(view, type, "]", TRUE))
6732 return TRUE;
6734 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6735 return TRUE;
6736 }
6737 }
6739 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6740 return TRUE;
6741 }
6743 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6744 static bool
6745 main_read(struct view *view, char *line)
6746 {
6747 static struct rev_graph *graph = graph_stacks;
6748 enum line_type type;
6749 struct commit *commit;
6751 if (!line) {
6752 int i;
6754 if (!view->lines && !view->parent)
6755 die("No revisions match the given arguments.");
6756 if (view->lines > 0) {
6757 commit = view->line[view->lines - 1].data;
6758 view->line[view->lines - 1].dirty = 1;
6759 if (!commit->author) {
6760 view->lines--;
6761 free(commit);
6762 graph->commit = NULL;
6763 }
6764 }
6765 update_rev_graph(view, graph);
6767 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6768 clear_rev_graph(&graph_stacks[i]);
6769 return TRUE;
6770 }
6772 type = get_line_type(line);
6773 if (type == LINE_COMMIT) {
6774 commit = calloc(1, sizeof(struct commit));
6775 if (!commit)
6776 return FALSE;
6778 line += STRING_SIZE("commit ");
6779 if (*line == '-') {
6780 graph->boundary = 1;
6781 line++;
6782 }
6784 string_copy_rev(commit->id, line);
6785 commit->refs = get_ref_list(commit->id);
6786 graph->commit = commit;
6787 add_line_data(view, commit, LINE_MAIN_COMMIT);
6789 while ((line = strchr(line, ' '))) {
6790 line++;
6791 push_rev_graph(graph->parents, line);
6792 commit->has_parents = TRUE;
6793 }
6794 return TRUE;
6795 }
6797 if (!view->lines)
6798 return TRUE;
6799 commit = view->line[view->lines - 1].data;
6801 switch (type) {
6802 case LINE_PARENT:
6803 if (commit->has_parents)
6804 break;
6805 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6806 break;
6808 case LINE_AUTHOR:
6809 parse_author_line(line + STRING_SIZE("author "),
6810 &commit->author, &commit->time);
6811 update_rev_graph(view, graph);
6812 graph = graph->next;
6813 break;
6815 default:
6816 /* Fill in the commit title if it has not already been set. */
6817 if (commit->title[0])
6818 break;
6820 /* Require titles to start with a non-space character at the
6821 * offset used by git log. */
6822 if (strncmp(line, " ", 4))
6823 break;
6824 line += 4;
6825 /* Well, if the title starts with a whitespace character,
6826 * try to be forgiving. Otherwise we end up with no title. */
6827 while (isspace(*line))
6828 line++;
6829 if (*line == '\0')
6830 break;
6831 /* FIXME: More graceful handling of titles; append "..." to
6832 * shortened titles, etc. */
6834 string_expand(commit->title, sizeof(commit->title), line, 1);
6835 view->line[view->lines - 1].dirty = 1;
6836 }
6838 return TRUE;
6839 }
6841 static enum request
6842 main_request(struct view *view, enum request request, struct line *line)
6843 {
6844 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6846 switch (request) {
6847 case REQ_ENTER:
6848 open_view(view, REQ_VIEW_DIFF, flags);
6849 break;
6850 case REQ_REFRESH:
6851 load_refs();
6852 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6853 break;
6854 default:
6855 return request;
6856 }
6858 return REQ_NONE;
6859 }
6861 static bool
6862 grep_refs(struct ref_list *list, regex_t *regex)
6863 {
6864 regmatch_t pmatch;
6865 size_t i;
6867 if (!opt_show_refs || !list)
6868 return FALSE;
6870 for (i = 0; i < list->size; i++) {
6871 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6872 return TRUE;
6873 }
6875 return FALSE;
6876 }
6878 static bool
6879 main_grep(struct view *view, struct line *line)
6880 {
6881 struct commit *commit = line->data;
6882 const char *text[] = {
6883 commit->title,
6884 opt_author ? commit->author : "",
6885 mkdate(&commit->time, opt_date),
6886 NULL
6887 };
6889 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6890 }
6892 static void
6893 main_select(struct view *view, struct line *line)
6894 {
6895 struct commit *commit = line->data;
6897 string_copy_rev(view->ref, commit->id);
6898 string_copy_rev(ref_commit, view->ref);
6899 }
6901 static struct view_ops main_ops = {
6902 "commit",
6903 main_argv,
6904 NULL,
6905 main_read,
6906 main_draw,
6907 main_request,
6908 main_grep,
6909 main_select,
6910 };
6913 /*
6914 * Status management
6915 */
6917 /* Whether or not the curses interface has been initialized. */
6918 static bool cursed = FALSE;
6920 /* Terminal hacks and workarounds. */
6921 static bool use_scroll_redrawwin;
6922 static bool use_scroll_status_wclear;
6924 /* The status window is used for polling keystrokes. */
6925 static WINDOW *status_win;
6927 /* Reading from the prompt? */
6928 static bool input_mode = FALSE;
6930 static bool status_empty = FALSE;
6932 /* Update status and title window. */
6933 static void
6934 report(const char *msg, ...)
6935 {
6936 struct view *view = display[current_view];
6938 if (input_mode)
6939 return;
6941 if (!view) {
6942 char buf[SIZEOF_STR];
6943 va_list args;
6945 va_start(args, msg);
6946 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6947 buf[sizeof(buf) - 1] = 0;
6948 buf[sizeof(buf) - 2] = '.';
6949 buf[sizeof(buf) - 3] = '.';
6950 buf[sizeof(buf) - 4] = '.';
6951 }
6952 va_end(args);
6953 die("%s", buf);
6954 }
6956 if (!status_empty || *msg) {
6957 va_list args;
6959 va_start(args, msg);
6961 wmove(status_win, 0, 0);
6962 if (view->has_scrolled && use_scroll_status_wclear)
6963 wclear(status_win);
6964 if (*msg) {
6965 vwprintw(status_win, msg, args);
6966 status_empty = FALSE;
6967 } else {
6968 status_empty = TRUE;
6969 }
6970 wclrtoeol(status_win);
6971 wnoutrefresh(status_win);
6973 va_end(args);
6974 }
6976 update_view_title(view);
6977 }
6979 static void
6980 init_display(void)
6981 {
6982 const char *term;
6983 int x, y;
6985 /* Initialize the curses library */
6986 if (isatty(STDIN_FILENO)) {
6987 cursed = !!initscr();
6988 opt_tty = stdin;
6989 } else {
6990 /* Leave stdin and stdout alone when acting as a pager. */
6991 opt_tty = fopen("/dev/tty", "r+");
6992 if (!opt_tty)
6993 die("Failed to open /dev/tty");
6994 cursed = !!newterm(NULL, opt_tty, opt_tty);
6995 }
6997 if (!cursed)
6998 die("Failed to initialize curses");
7000 nonl(); /* Disable conversion and detect newlines from input. */
7001 cbreak(); /* Take input chars one at a time, no wait for \n */
7002 noecho(); /* Don't echo input */
7003 leaveok(stdscr, FALSE);
7005 if (has_colors())
7006 init_colors();
7008 getmaxyx(stdscr, y, x);
7009 status_win = newwin(1, 0, y - 1, 0);
7010 if (!status_win)
7011 die("Failed to create status window");
7013 /* Enable keyboard mapping */
7014 keypad(status_win, TRUE);
7015 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7017 TABSIZE = opt_tab_size;
7019 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7020 if (term && !strcmp(term, "gnome-terminal")) {
7021 /* In the gnome-terminal-emulator, the message from
7022 * scrolling up one line when impossible followed by
7023 * scrolling down one line causes corruption of the
7024 * status line. This is fixed by calling wclear. */
7025 use_scroll_status_wclear = TRUE;
7026 use_scroll_redrawwin = FALSE;
7028 } else if (term && !strcmp(term, "xrvt-xpm")) {
7029 /* No problems with full optimizations in xrvt-(unicode)
7030 * and aterm. */
7031 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7033 } else {
7034 /* When scrolling in (u)xterm the last line in the
7035 * scrolling direction will update slowly. */
7036 use_scroll_redrawwin = TRUE;
7037 use_scroll_status_wclear = FALSE;
7038 }
7039 }
7041 static int
7042 get_input(int prompt_position)
7043 {
7044 struct view *view;
7045 int i, key, cursor_y, cursor_x;
7046 bool loading = FALSE;
7048 if (prompt_position)
7049 input_mode = TRUE;
7051 while (TRUE) {
7052 foreach_view (view, i) {
7053 update_view(view);
7054 if (view_is_displayed(view) && view->has_scrolled &&
7055 use_scroll_redrawwin)
7056 redrawwin(view->win);
7057 view->has_scrolled = FALSE;
7058 if (view->pipe)
7059 loading = TRUE;
7060 }
7062 /* Update the cursor position. */
7063 if (prompt_position) {
7064 getbegyx(status_win, cursor_y, cursor_x);
7065 cursor_x = prompt_position;
7066 } else {
7067 view = display[current_view];
7068 getbegyx(view->win, cursor_y, cursor_x);
7069 cursor_x = view->width - 1;
7070 cursor_y += view->lineno - view->offset;
7071 }
7072 setsyx(cursor_y, cursor_x);
7074 /* Refresh, accept single keystroke of input */
7075 doupdate();
7076 nodelay(status_win, loading);
7077 key = wgetch(status_win);
7079 /* wgetch() with nodelay() enabled returns ERR when
7080 * there's no input. */
7081 if (key == ERR) {
7083 } else if (key == KEY_RESIZE) {
7084 int height, width;
7086 getmaxyx(stdscr, height, width);
7088 wresize(status_win, 1, width);
7089 mvwin(status_win, height - 1, 0);
7090 wnoutrefresh(status_win);
7091 resize_display();
7092 redraw_display(TRUE);
7094 } else {
7095 input_mode = FALSE;
7096 return key;
7097 }
7098 }
7099 }
7101 static char *
7102 prompt_input(const char *prompt, input_handler handler, void *data)
7103 {
7104 enum input_status status = INPUT_OK;
7105 static char buf[SIZEOF_STR];
7106 size_t pos = 0;
7108 buf[pos] = 0;
7110 while (status == INPUT_OK || status == INPUT_SKIP) {
7111 int key;
7113 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7114 wclrtoeol(status_win);
7116 key = get_input(pos + 1);
7117 switch (key) {
7118 case KEY_RETURN:
7119 case KEY_ENTER:
7120 case '\n':
7121 status = pos ? INPUT_STOP : INPUT_CANCEL;
7122 break;
7124 case KEY_BACKSPACE:
7125 if (pos > 0)
7126 buf[--pos] = 0;
7127 else
7128 status = INPUT_CANCEL;
7129 break;
7131 case KEY_ESC:
7132 status = INPUT_CANCEL;
7133 break;
7135 default:
7136 if (pos >= sizeof(buf)) {
7137 report("Input string too long");
7138 return NULL;
7139 }
7141 status = handler(data, buf, key);
7142 if (status == INPUT_OK)
7143 buf[pos++] = (char) key;
7144 }
7145 }
7147 /* Clear the status window */
7148 status_empty = FALSE;
7149 report("");
7151 if (status == INPUT_CANCEL)
7152 return NULL;
7154 buf[pos++] = 0;
7156 return buf;
7157 }
7159 static enum input_status
7160 prompt_yesno_handler(void *data, char *buf, int c)
7161 {
7162 if (c == 'y' || c == 'Y')
7163 return INPUT_STOP;
7164 if (c == 'n' || c == 'N')
7165 return INPUT_CANCEL;
7166 return INPUT_SKIP;
7167 }
7169 static bool
7170 prompt_yesno(const char *prompt)
7171 {
7172 char prompt2[SIZEOF_STR];
7174 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7175 return FALSE;
7177 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7178 }
7180 static enum input_status
7181 read_prompt_handler(void *data, char *buf, int c)
7182 {
7183 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7184 }
7186 static char *
7187 read_prompt(const char *prompt)
7188 {
7189 return prompt_input(prompt, read_prompt_handler, NULL);
7190 }
7192 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7193 {
7194 enum input_status status = INPUT_OK;
7195 int size = 0;
7197 while (items[size].text)
7198 size++;
7200 while (status == INPUT_OK) {
7201 const struct menu_item *item = &items[*selected];
7202 int key;
7203 int i;
7205 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7206 prompt, *selected + 1, size);
7207 if (item->hotkey)
7208 wprintw(status_win, "[%c] ", (char) item->hotkey);
7209 wprintw(status_win, "%s", item->text);
7210 wclrtoeol(status_win);
7212 key = get_input(COLS - 1);
7213 switch (key) {
7214 case KEY_RETURN:
7215 case KEY_ENTER:
7216 case '\n':
7217 status = INPUT_STOP;
7218 break;
7220 case KEY_LEFT:
7221 case KEY_UP:
7222 *selected = *selected - 1;
7223 if (*selected < 0)
7224 *selected = size - 1;
7225 break;
7227 case KEY_RIGHT:
7228 case KEY_DOWN:
7229 *selected = (*selected + 1) % size;
7230 break;
7232 case KEY_ESC:
7233 status = INPUT_CANCEL;
7234 break;
7236 default:
7237 for (i = 0; items[i].text; i++)
7238 if (items[i].hotkey == key) {
7239 *selected = i;
7240 status = INPUT_STOP;
7241 break;
7242 }
7243 }
7244 }
7246 /* Clear the status window */
7247 status_empty = FALSE;
7248 report("");
7250 return status != INPUT_CANCEL;
7251 }
7253 /*
7254 * Repository properties
7255 */
7257 static struct ref **refs = NULL;
7258 static size_t refs_size = 0;
7259 static struct ref *refs_head = NULL;
7261 static struct ref_list **ref_lists = NULL;
7262 static size_t ref_lists_size = 0;
7264 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7265 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7266 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7268 static int
7269 compare_refs(const void *ref1_, const void *ref2_)
7270 {
7271 const struct ref *ref1 = *(const struct ref **)ref1_;
7272 const struct ref *ref2 = *(const struct ref **)ref2_;
7274 if (ref1->tag != ref2->tag)
7275 return ref2->tag - ref1->tag;
7276 if (ref1->ltag != ref2->ltag)
7277 return ref2->ltag - ref2->ltag;
7278 if (ref1->head != ref2->head)
7279 return ref2->head - ref1->head;
7280 if (ref1->tracked != ref2->tracked)
7281 return ref2->tracked - ref1->tracked;
7282 if (ref1->remote != ref2->remote)
7283 return ref2->remote - ref1->remote;
7284 return strcmp(ref1->name, ref2->name);
7285 }
7287 static void
7288 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7289 {
7290 size_t i;
7292 for (i = 0; i < refs_size; i++)
7293 if (!visitor(data, refs[i]))
7294 break;
7295 }
7297 static struct ref *
7298 get_ref_head()
7299 {
7300 return refs_head;
7301 }
7303 static struct ref_list *
7304 get_ref_list(const char *id)
7305 {
7306 struct ref_list *list;
7307 size_t i;
7309 for (i = 0; i < ref_lists_size; i++)
7310 if (!strcmp(id, ref_lists[i]->id))
7311 return ref_lists[i];
7313 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7314 return NULL;
7315 list = calloc(1, sizeof(*list));
7316 if (!list)
7317 return NULL;
7319 for (i = 0; i < refs_size; i++) {
7320 if (!strcmp(id, refs[i]->id) &&
7321 realloc_refs_list(&list->refs, list->size, 1))
7322 list->refs[list->size++] = refs[i];
7323 }
7325 if (!list->refs) {
7326 free(list);
7327 return NULL;
7328 }
7330 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7331 ref_lists[ref_lists_size++] = list;
7332 return list;
7333 }
7335 static int
7336 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7337 {
7338 struct ref *ref = NULL;
7339 bool tag = FALSE;
7340 bool ltag = FALSE;
7341 bool remote = FALSE;
7342 bool tracked = FALSE;
7343 bool head = FALSE;
7344 int from = 0, to = refs_size - 1;
7346 if (!prefixcmp(name, "refs/tags/")) {
7347 if (!suffixcmp(name, namelen, "^{}")) {
7348 namelen -= 3;
7349 name[namelen] = 0;
7350 } else {
7351 ltag = TRUE;
7352 }
7354 tag = TRUE;
7355 namelen -= STRING_SIZE("refs/tags/");
7356 name += STRING_SIZE("refs/tags/");
7358 } else if (!prefixcmp(name, "refs/remotes/")) {
7359 remote = TRUE;
7360 namelen -= STRING_SIZE("refs/remotes/");
7361 name += STRING_SIZE("refs/remotes/");
7362 tracked = !strcmp(opt_remote, name);
7364 } else if (!prefixcmp(name, "refs/heads/")) {
7365 namelen -= STRING_SIZE("refs/heads/");
7366 name += STRING_SIZE("refs/heads/");
7367 if (!strncmp(opt_head, name, namelen))
7368 return OK;
7370 } else if (!strcmp(name, "HEAD")) {
7371 head = TRUE;
7372 if (*opt_head) {
7373 namelen = strlen(opt_head);
7374 name = opt_head;
7375 }
7376 }
7378 /* If we are reloading or it's an annotated tag, replace the
7379 * previous SHA1 with the resolved commit id; relies on the fact
7380 * git-ls-remote lists the commit id of an annotated tag right
7381 * before the commit id it points to. */
7382 while (from <= to) {
7383 size_t pos = (to + from) / 2;
7384 int cmp = strcmp(name, refs[pos]->name);
7386 if (!cmp) {
7387 ref = refs[pos];
7388 break;
7389 }
7391 if (cmp < 0)
7392 to = pos - 1;
7393 else
7394 from = pos + 1;
7395 }
7397 if (!ref) {
7398 if (!realloc_refs(&refs, refs_size, 1))
7399 return ERR;
7400 ref = calloc(1, sizeof(*ref) + namelen);
7401 if (!ref)
7402 return ERR;
7403 memmove(refs + from + 1, refs + from,
7404 (refs_size - from) * sizeof(*refs));
7405 refs[from] = ref;
7406 strncpy(ref->name, name, namelen);
7407 refs_size++;
7408 }
7410 ref->head = head;
7411 ref->tag = tag;
7412 ref->ltag = ltag;
7413 ref->remote = remote;
7414 ref->tracked = tracked;
7415 string_copy_rev(ref->id, id);
7417 if (head)
7418 refs_head = ref;
7419 return OK;
7420 }
7422 static int
7423 load_refs(void)
7424 {
7425 const char *head_argv[] = {
7426 "git", "symbolic-ref", "HEAD", NULL
7427 };
7428 static const char *ls_remote_argv[SIZEOF_ARG] = {
7429 "git", "ls-remote", opt_git_dir, NULL
7430 };
7431 static bool init = FALSE;
7432 size_t i;
7434 if (!init) {
7435 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7436 die("TIG_LS_REMOTE contains too many arguments");
7437 init = TRUE;
7438 }
7440 if (!*opt_git_dir)
7441 return OK;
7443 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7444 !prefixcmp(opt_head, "refs/heads/")) {
7445 char *offset = opt_head + STRING_SIZE("refs/heads/");
7447 memmove(opt_head, offset, strlen(offset) + 1);
7448 }
7450 refs_head = NULL;
7451 for (i = 0; i < refs_size; i++)
7452 refs[i]->id[0] = 0;
7454 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7455 return ERR;
7457 /* Update the ref lists to reflect changes. */
7458 for (i = 0; i < ref_lists_size; i++) {
7459 struct ref_list *list = ref_lists[i];
7460 size_t old, new;
7462 for (old = new = 0; old < list->size; old++)
7463 if (!strcmp(list->id, list->refs[old]->id))
7464 list->refs[new++] = list->refs[old];
7465 list->size = new;
7466 }
7468 return OK;
7469 }
7471 static void
7472 set_remote_branch(const char *name, const char *value, size_t valuelen)
7473 {
7474 if (!strcmp(name, ".remote")) {
7475 string_ncopy(opt_remote, value, valuelen);
7477 } else if (*opt_remote && !strcmp(name, ".merge")) {
7478 size_t from = strlen(opt_remote);
7480 if (!prefixcmp(value, "refs/heads/"))
7481 value += STRING_SIZE("refs/heads/");
7483 if (!string_format_from(opt_remote, &from, "/%s", value))
7484 opt_remote[0] = 0;
7485 }
7486 }
7488 static void
7489 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7490 {
7491 const char *argv[SIZEOF_ARG] = { name, "=" };
7492 int argc = 1 + (cmd == option_set_command);
7493 int error = ERR;
7495 if (!argv_from_string(argv, &argc, value))
7496 config_msg = "Too many option arguments";
7497 else
7498 error = cmd(argc, argv);
7500 if (error == ERR)
7501 warn("Option 'tig.%s': %s", name, config_msg);
7502 }
7504 static bool
7505 set_environment_variable(const char *name, const char *value)
7506 {
7507 size_t len = strlen(name) + 1 + strlen(value) + 1;
7508 char *env = malloc(len);
7510 if (env &&
7511 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7512 putenv(env) == 0)
7513 return TRUE;
7514 free(env);
7515 return FALSE;
7516 }
7518 static void
7519 set_work_tree(const char *value)
7520 {
7521 char cwd[SIZEOF_STR];
7523 if (!getcwd(cwd, sizeof(cwd)))
7524 die("Failed to get cwd path: %s", strerror(errno));
7525 if (chdir(opt_git_dir) < 0)
7526 die("Failed to chdir(%s): %s", strerror(errno));
7527 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7528 die("Failed to get git path: %s", strerror(errno));
7529 if (chdir(cwd) < 0)
7530 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7531 if (chdir(value) < 0)
7532 die("Failed to chdir(%s): %s", value, strerror(errno));
7533 if (!getcwd(cwd, sizeof(cwd)))
7534 die("Failed to get cwd path: %s", strerror(errno));
7535 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7536 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7537 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7538 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7539 opt_is_inside_work_tree = TRUE;
7540 }
7542 static int
7543 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7544 {
7545 if (!strcmp(name, "i18n.commitencoding"))
7546 string_ncopy(opt_encoding, value, valuelen);
7548 else if (!strcmp(name, "core.editor"))
7549 string_ncopy(opt_editor, value, valuelen);
7551 else if (!strcmp(name, "core.worktree"))
7552 set_work_tree(value);
7554 else if (!prefixcmp(name, "tig.color."))
7555 set_repo_config_option(name + 10, value, option_color_command);
7557 else if (!prefixcmp(name, "tig.bind."))
7558 set_repo_config_option(name + 9, value, option_bind_command);
7560 else if (!prefixcmp(name, "tig."))
7561 set_repo_config_option(name + 4, value, option_set_command);
7563 else if (*opt_head && !prefixcmp(name, "branch.") &&
7564 !strncmp(name + 7, opt_head, strlen(opt_head)))
7565 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7567 return OK;
7568 }
7570 static int
7571 load_git_config(void)
7572 {
7573 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7575 return io_run_load(config_list_argv, "=", read_repo_config_option);
7576 }
7578 static int
7579 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7580 {
7581 if (!opt_git_dir[0]) {
7582 string_ncopy(opt_git_dir, name, namelen);
7584 } else if (opt_is_inside_work_tree == -1) {
7585 /* This can be 3 different values depending on the
7586 * version of git being used. If git-rev-parse does not
7587 * understand --is-inside-work-tree it will simply echo
7588 * the option else either "true" or "false" is printed.
7589 * Default to true for the unknown case. */
7590 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7592 } else if (*name == '.') {
7593 string_ncopy(opt_cdup, name, namelen);
7595 } else {
7596 string_ncopy(opt_prefix, name, namelen);
7597 }
7599 return OK;
7600 }
7602 static int
7603 load_repo_info(void)
7604 {
7605 const char *rev_parse_argv[] = {
7606 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7607 "--show-cdup", "--show-prefix", NULL
7608 };
7610 return io_run_load(rev_parse_argv, "=", read_repo_info);
7611 }
7614 /*
7615 * Main
7616 */
7618 static const char usage[] =
7619 "tig " TIG_VERSION " (" __DATE__ ")\n"
7620 "\n"
7621 "Usage: tig [options] [revs] [--] [paths]\n"
7622 " or: tig show [options] [revs] [--] [paths]\n"
7623 " or: tig blame [rev] path\n"
7624 " or: tig status\n"
7625 " or: tig < [git command output]\n"
7626 "\n"
7627 "Options:\n"
7628 " -v, --version Show version and exit\n"
7629 " -h, --help Show help message and exit";
7631 static void __NORETURN
7632 quit(int sig)
7633 {
7634 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7635 if (cursed)
7636 endwin();
7637 exit(0);
7638 }
7640 static void __NORETURN
7641 die(const char *err, ...)
7642 {
7643 va_list args;
7645 endwin();
7647 va_start(args, err);
7648 fputs("tig: ", stderr);
7649 vfprintf(stderr, err, args);
7650 fputs("\n", stderr);
7651 va_end(args);
7653 exit(1);
7654 }
7656 static void
7657 warn(const char *msg, ...)
7658 {
7659 va_list args;
7661 va_start(args, msg);
7662 fputs("tig warning: ", stderr);
7663 vfprintf(stderr, msg, args);
7664 fputs("\n", stderr);
7665 va_end(args);
7666 }
7668 static enum request
7669 parse_options(int argc, const char *argv[])
7670 {
7671 enum request request = REQ_VIEW_MAIN;
7672 const char *subcommand;
7673 bool seen_dashdash = FALSE;
7674 /* XXX: This is vulnerable to the user overriding options
7675 * required for the main view parser. */
7676 const char *custom_argv[SIZEOF_ARG] = {
7677 "git", "log", "--no-color", "--pretty=raw", "--parents",
7678 "--topo-order", NULL
7679 };
7680 int i, j = 6;
7682 if (!isatty(STDIN_FILENO)) {
7683 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7684 return REQ_VIEW_PAGER;
7685 }
7687 if (argc <= 1)
7688 return REQ_NONE;
7690 subcommand = argv[1];
7691 if (!strcmp(subcommand, "status")) {
7692 if (argc > 2)
7693 warn("ignoring arguments after `%s'", subcommand);
7694 return REQ_VIEW_STATUS;
7696 } else if (!strcmp(subcommand, "blame")) {
7697 if (argc <= 2 || argc > 4)
7698 die("invalid number of options to blame\n\n%s", usage);
7700 i = 2;
7701 if (argc == 4) {
7702 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7703 i++;
7704 }
7706 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7707 return REQ_VIEW_BLAME;
7709 } else if (!strcmp(subcommand, "show")) {
7710 request = REQ_VIEW_DIFF;
7712 } else {
7713 subcommand = NULL;
7714 }
7716 if (subcommand) {
7717 custom_argv[1] = subcommand;
7718 j = 2;
7719 }
7721 for (i = 1 + !!subcommand; i < argc; i++) {
7722 const char *opt = argv[i];
7724 if (seen_dashdash || !strcmp(opt, "--")) {
7725 seen_dashdash = TRUE;
7727 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7728 printf("tig version %s\n", TIG_VERSION);
7729 quit(0);
7731 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7732 printf("%s\n", usage);
7733 quit(0);
7734 }
7736 custom_argv[j++] = opt;
7737 if (j >= ARRAY_SIZE(custom_argv))
7738 die("command too long");
7739 }
7741 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7742 die("Failed to format arguments");
7744 return request;
7745 }
7747 int
7748 main(int argc, const char *argv[])
7749 {
7750 const char *codeset = "UTF-8";
7751 enum request request = parse_options(argc, argv);
7752 struct view *view;
7753 size_t i;
7755 signal(SIGINT, quit);
7756 signal(SIGPIPE, SIG_IGN);
7758 if (setlocale(LC_ALL, "")) {
7759 codeset = nl_langinfo(CODESET);
7760 }
7762 if (load_repo_info() == ERR)
7763 die("Failed to load repo info.");
7765 if (load_options() == ERR)
7766 die("Failed to load user config.");
7768 if (load_git_config() == ERR)
7769 die("Failed to load repo config.");
7771 /* Require a git repository unless when running in pager mode. */
7772 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7773 die("Not a git repository");
7775 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7776 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7777 if (opt_iconv_in == ICONV_NONE)
7778 die("Failed to initialize character set conversion");
7779 }
7781 if (codeset && strcmp(codeset, "UTF-8")) {
7782 opt_iconv_out = iconv_open(codeset, "UTF-8");
7783 if (opt_iconv_out == ICONV_NONE)
7784 die("Failed to initialize character set conversion");
7785 }
7787 if (load_refs() == ERR)
7788 die("Failed to load refs.");
7790 foreach_view (view, i)
7791 if (!argv_from_env(view->ops->argv, view->cmd_env))
7792 die("Too many arguments in the `%s` environment variable",
7793 view->cmd_env);
7795 init_display();
7797 if (request != REQ_NONE)
7798 open_view(NULL, request, OPEN_PREPARED);
7799 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7801 while (view_driver(display[current_view], request)) {
7802 int key = get_input(0);
7804 view = display[current_view];
7805 request = get_keybinding(view->keymap, key);
7807 /* Some low-level request handling. This keeps access to
7808 * status_win restricted. */
7809 switch (request) {
7810 case REQ_PROMPT:
7811 {
7812 char *cmd = read_prompt(":");
7814 if (cmd && isdigit(*cmd)) {
7815 int lineno = view->lineno + 1;
7817 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7818 select_view_line(view, lineno - 1);
7819 report("");
7820 } else {
7821 report("Unable to parse '%s' as a line number", cmd);
7822 }
7824 } else if (cmd) {
7825 struct view *next = VIEW(REQ_VIEW_PAGER);
7826 const char *argv[SIZEOF_ARG] = { "git" };
7827 int argc = 1;
7829 /* When running random commands, initially show the
7830 * command in the title. However, it maybe later be
7831 * overwritten if a commit line is selected. */
7832 string_ncopy(next->ref, cmd, strlen(cmd));
7834 if (!argv_from_string(argv, &argc, cmd)) {
7835 report("Too many arguments");
7836 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7837 report("Failed to format command");
7838 } else {
7839 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7840 }
7841 }
7843 request = REQ_NONE;
7844 break;
7845 }
7846 case REQ_SEARCH:
7847 case REQ_SEARCH_BACK:
7848 {
7849 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7850 char *search = read_prompt(prompt);
7852 if (search)
7853 string_ncopy(opt_search, search, strlen(search));
7854 else if (*opt_search)
7855 request = request == REQ_SEARCH ?
7856 REQ_FIND_NEXT :
7857 REQ_FIND_PREV;
7858 else
7859 request = REQ_NONE;
7860 break;
7861 }
7862 default:
7863 break;
7864 }
7865 }
7867 quit(0);
7869 return 0;
7870 }