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_NONE /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151 INPUT_OK,
152 INPUT_SKIP,
153 INPUT_STOP,
154 INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163 int hotkey;
164 const char *text;
165 void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171 * Allocation helpers ... Entering macro hell to never be seen again.
172 */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
175 static type * \
176 name(type **mem, size_t size, size_t increase) \
177 { \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180 type *tmp = *mem; \
181 \
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184 if (tmp) \
185 *mem = tmp; \
186 } \
187 \
188 return tmp; \
189 }
191 /*
192 * String helpers
193 */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
197 {
198 if (srclen > dstlen - 1)
199 srclen = dstlen - 1;
201 strncpy(dst, src, srclen);
202 dst[srclen] = 0;
203 }
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
221 {
222 size_t size, pos;
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
231 size += expanded;
232 } else {
233 dst[size++] = src[pos];
234 }
235 }
237 dst[size] = 0;
238 }
240 static char *
241 chomp_string(char *name)
242 {
243 int namelen;
245 while (isspace(*name))
246 name++;
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
250 name[namelen--] = 0;
252 return name;
253 }
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
257 {
258 va_list args;
259 size_t pos = bufpos ? *bufpos : 0;
261 va_start(args, fmt);
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 va_end(args);
265 if (bufpos)
266 *bufpos = pos;
268 return pos >= bufsize ? FALSE : TRUE;
269 }
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
279 {
280 size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
287 continue;
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
291 continue;
293 return str1[i] - str2[i];
294 }
296 return 0;
297 }
299 #define enum_equals(entry, str, len) \
300 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
312 {
313 static char buf[SIZEOF_STR];
314 int bufpos;
316 for (bufpos = 0; bufpos <= namelen; bufpos++) {
317 buf[bufpos] = tolower(name[bufpos]);
318 if (buf[bufpos] == '_')
319 buf[bufpos] = '-';
320 }
322 buf[bufpos] = 0;
323 return buf;
324 }
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
330 {
331 size_t namelen = strlen(name);
332 int i;
334 for (i = 0; i < map_size; i++)
335 if (enum_equals(map[i], name, namelen)) {
336 *value = map[i].value;
337 return TRUE;
338 }
340 return FALSE;
341 }
343 #define map_enum(attr, map, name) \
344 map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347 strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
351 {
352 size_t len = slen >= 0 ? slen : strlen(str);
353 size_t suffixlen = strlen(suffix);
355 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
356 }
359 /*
360 * Unicode / UTF-8 handling
361 *
362 * NOTE: Much of the following code for dealing with Unicode is derived from
363 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365 */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
369 {
370 if (c >= 0x1100 &&
371 (c <= 0x115f /* Hangul Jamo */
372 || c == 0x2329
373 || c == 0x232a
374 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
375 /* CJK ... Yi */
376 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
377 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
378 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
379 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
380 || (c >= 0xffe0 && c <= 0xffe6)
381 || (c >= 0x20000 && c <= 0x2fffd)
382 || (c >= 0x30000 && c <= 0x3fffd)))
383 return 2;
385 if (c == '\t')
386 return tab_size;
388 return 1;
389 }
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392 * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 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 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,
401 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,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
406 {
407 int c = *(unsigned char *) string;
409 return utf8_bytes[c];
410 }
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
415 {
416 unsigned long unicode;
418 switch (length) {
419 case 1:
420 unicode = string[0];
421 break;
422 case 2:
423 unicode = (string[0] & 0x1f) << 6;
424 unicode += (string[1] & 0x3f);
425 break;
426 case 3:
427 unicode = (string[0] & 0x0f) << 12;
428 unicode += ((string[1] & 0x3f) << 6);
429 unicode += (string[2] & 0x3f);
430 break;
431 case 4:
432 unicode = (string[0] & 0x0f) << 18;
433 unicode += ((string[1] & 0x3f) << 12);
434 unicode += ((string[2] & 0x3f) << 6);
435 unicode += (string[3] & 0x3f);
436 break;
437 case 5:
438 unicode = (string[0] & 0x0f) << 24;
439 unicode += ((string[1] & 0x3f) << 18);
440 unicode += ((string[2] & 0x3f) << 12);
441 unicode += ((string[3] & 0x3f) << 6);
442 unicode += (string[4] & 0x3f);
443 break;
444 case 6:
445 unicode = (string[0] & 0x01) << 30;
446 unicode += ((string[1] & 0x3f) << 24);
447 unicode += ((string[2] & 0x3f) << 18);
448 unicode += ((string[3] & 0x3f) << 12);
449 unicode += ((string[4] & 0x3f) << 6);
450 unicode += (string[5] & 0x3f);
451 break;
452 default:
453 return 0;
454 }
456 /* Invalid characters could return the special 0xfffd value but NUL
457 * should be just as good. */
458 return unicode > 0xffff ? 0 : unicode;
459 }
461 /* Calculates how much of string can be shown within the given maximum width
462 * and sets trimmed parameter to non-zero value if all of string could not be
463 * shown. If the reserve flag is TRUE, it will reserve at least one
464 * trailing character, which can be useful when drawing a delimiter.
465 *
466 * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
469 {
470 const char *string = *start;
471 const char *end = strchr(string, '\0');
472 unsigned char last_bytes = 0;
473 size_t last_ucwidth = 0;
475 *width = 0;
476 *trimmed = 0;
478 while (string < end) {
479 unsigned char bytes = utf8_char_length(string, end);
480 size_t ucwidth;
481 unsigned long unicode;
483 if (string + bytes > end)
484 break;
486 /* Change representation to figure out whether
487 * it is a single- or double-width character. */
489 unicode = utf8_to_unicode(string, bytes);
490 /* FIXME: Graceful handling of invalid Unicode character. */
491 if (!unicode)
492 break;
494 ucwidth = unicode_width(unicode, tab_size);
495 if (skip > 0) {
496 skip -= ucwidth <= skip ? ucwidth : skip;
497 *start += bytes;
498 }
499 *width += ucwidth;
500 if (*width > max_width) {
501 *trimmed = 1;
502 *width -= ucwidth;
503 if (reserve && *width == max_width) {
504 string -= last_bytes;
505 *width -= last_ucwidth;
506 }
507 break;
508 }
510 string += bytes;
511 last_bytes = ucwidth ? bytes : 0;
512 last_ucwidth = ucwidth;
513 }
515 return string - *start;
516 }
519 #define DATE_INFO \
520 DATE_(NO), \
521 DATE_(DEFAULT), \
522 DATE_(LOCAL), \
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 if (date == DATE_LOCAL) {
588 time_t date = time->sec + time->tz;
589 localtime_r(&date, &tm);
590 }
591 else {
592 gmtime_r(&time->sec, &tm);
593 }
594 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
595 }
598 #define AUTHOR_VALUES \
599 AUTHOR_(NO), \
600 AUTHOR_(FULL), \
601 AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605 AUTHOR_VALUES,
606 #undef AUTHOR_
607 AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612 AUTHOR_VALUES
613 #undef AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
618 {
619 static char initials[AUTHOR_COLS * 6 + 1];
620 size_t pos = 0;
621 const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625 memset(initials, 0, sizeof(initials));
626 while (author < end) {
627 unsigned char bytes;
628 size_t i;
630 while (is_initial_sep(*author))
631 author++;
633 bytes = utf8_char_length(author, end);
634 if (bytes < sizeof(initials) - 1 - pos) {
635 while (bytes--) {
636 initials[pos++] = *author++;
637 }
638 }
640 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641 if (i < sizeof(initials) - 1)
642 initials[i++] = *author;
643 }
645 initials[i++] = 0;
646 }
648 return initials;
649 }
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
654 {
655 int valuelen;
657 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658 bool advance = cmd[valuelen] != 0;
660 cmd[valuelen] = 0;
661 argv[(*argc)++] = chomp_string(cmd);
662 cmd = chomp_string(cmd + valuelen + advance);
663 }
665 if (*argc < SIZEOF_ARG)
666 argv[*argc] = NULL;
667 return *argc < SIZEOF_ARG;
668 }
670 static bool
671 argv_from_env(const char **argv, const char *name)
672 {
673 char *env = argv ? getenv(name) : NULL;
674 int argc = 0;
676 if (env && *env)
677 env = strdup(env);
678 return !env || argv_from_string(argv, &argc, env);
679 }
682 /*
683 * Executing external commands.
684 */
686 enum io_type {
687 IO_FD, /* File descriptor based IO. */
688 IO_BG, /* Execute command in the background. */
689 IO_FG, /* Execute command with same std{in,out,err}. */
690 IO_RD, /* Read only fork+exec IO. */
691 IO_WR, /* Write only fork+exec IO. */
692 IO_AP, /* Append fork+exec output to file. */
693 };
695 struct io {
696 enum io_type type; /* The requested type of pipe. */
697 const char *dir; /* Directory from which to execute. */
698 pid_t pid; /* PID of spawned process. */
699 int pipe; /* Pipe end for reading or writing. */
700 int error; /* Error status. */
701 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
702 char *buf; /* Read buffer. */
703 size_t bufalloc; /* Allocated buffer size. */
704 size_t bufsize; /* Buffer content size. */
705 char *bufpos; /* Current buffer position. */
706 unsigned int eof:1; /* Has end of file been reached. */
707 };
709 static void
710 io_reset(struct io *io)
711 {
712 io->pipe = -1;
713 io->pid = 0;
714 io->buf = io->bufpos = NULL;
715 io->bufalloc = io->bufsize = 0;
716 io->error = 0;
717 io->eof = 0;
718 }
720 static void
721 io_init(struct io *io, const char *dir, enum io_type type)
722 {
723 io_reset(io);
724 io->type = type;
725 io->dir = dir;
726 }
728 static bool
729 io_format(struct io *io, const char *dir, enum io_type type,
730 const char *argv[], enum format_flags flags)
731 {
732 io_init(io, dir, type);
733 return format_argv(io->argv, argv, flags);
734 }
736 static bool
737 io_open(struct io *io, const char *fmt, ...)
738 {
739 char name[SIZEOF_STR] = "";
740 bool fits;
741 va_list args;
743 io_init(io, NULL, IO_FD);
745 va_start(args, fmt);
746 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
747 va_end(args);
749 if (!fits) {
750 io->error = ENAMETOOLONG;
751 return FALSE;
752 }
753 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
754 if (io->pipe == -1)
755 io->error = errno;
756 return io->pipe != -1;
757 }
759 static bool
760 io_kill(struct io *io)
761 {
762 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
763 }
765 static bool
766 io_done(struct io *io)
767 {
768 pid_t pid = io->pid;
770 if (io->pipe != -1)
771 close(io->pipe);
772 free(io->buf);
773 io_reset(io);
775 while (pid > 0) {
776 int status;
777 pid_t waiting = waitpid(pid, &status, 0);
779 if (waiting < 0) {
780 if (errno == EINTR)
781 continue;
782 io->error = errno;
783 return FALSE;
784 }
786 return waiting == pid &&
787 !WIFSIGNALED(status) &&
788 WIFEXITED(status) &&
789 !WEXITSTATUS(status);
790 }
792 return TRUE;
793 }
795 static bool
796 io_start(struct io *io)
797 {
798 int pipefds[2] = { -1, -1 };
800 if (io->type == IO_FD)
801 return TRUE;
803 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
804 io->error = errno;
805 return FALSE;
806 } else if (io->type == IO_AP) {
807 pipefds[1] = io->pipe;
808 }
810 if ((io->pid = fork())) {
811 if (io->pid == -1)
812 io->error = errno;
813 if (pipefds[!(io->type == IO_WR)] != -1)
814 close(pipefds[!(io->type == IO_WR)]);
815 if (io->pid != -1) {
816 io->pipe = pipefds[!!(io->type == IO_WR)];
817 return TRUE;
818 }
820 } else {
821 if (io->type != IO_FG) {
822 int devnull = open("/dev/null", O_RDWR);
823 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
824 int writefd = (io->type == IO_RD || io->type == IO_AP)
825 ? pipefds[1] : devnull;
827 dup2(readfd, STDIN_FILENO);
828 dup2(writefd, STDOUT_FILENO);
829 dup2(devnull, STDERR_FILENO);
831 close(devnull);
832 if (pipefds[0] != -1)
833 close(pipefds[0]);
834 if (pipefds[1] != -1)
835 close(pipefds[1]);
836 }
838 if (io->dir && *io->dir && chdir(io->dir) == -1)
839 exit(errno);
841 execvp(io->argv[0], (char *const*) io->argv);
842 exit(errno);
843 }
845 if (pipefds[!!(io->type == IO_WR)] != -1)
846 close(pipefds[!!(io->type == IO_WR)]);
847 return FALSE;
848 }
850 static bool
851 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
852 {
853 io_init(io, dir, type);
854 if (!format_argv(io->argv, argv, FORMAT_NONE))
855 return FALSE;
856 return io_start(io);
857 }
859 static int
860 io_complete(struct io *io)
861 {
862 return io_start(io) && io_done(io);
863 }
865 static int
866 io_run_bg(const char **argv)
867 {
868 struct io io = {};
870 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
871 return FALSE;
872 return io_complete(&io);
873 }
875 static bool
876 io_run_fg(const char **argv, const char *dir)
877 {
878 struct io io = {};
880 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
881 return FALSE;
882 return io_complete(&io);
883 }
885 static bool
886 io_run_append(const char **argv, enum format_flags flags, int fd)
887 {
888 struct io io = {};
890 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
891 close(fd);
892 return FALSE;
893 }
895 io.pipe = fd;
896 return io_complete(&io);
897 }
899 static bool
900 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
901 {
902 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
903 }
905 static bool
906 io_eof(struct io *io)
907 {
908 return io->eof;
909 }
911 static int
912 io_error(struct io *io)
913 {
914 return io->error;
915 }
917 static char *
918 io_strerror(struct io *io)
919 {
920 return strerror(io->error);
921 }
923 static bool
924 io_can_read(struct io *io)
925 {
926 struct timeval tv = { 0, 500 };
927 fd_set fds;
929 FD_ZERO(&fds);
930 FD_SET(io->pipe, &fds);
932 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
933 }
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
937 {
938 do {
939 ssize_t readsize = read(io->pipe, buf, bufsize);
941 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942 continue;
943 else if (readsize == -1)
944 io->error = errno;
945 else if (readsize == 0)
946 io->eof = 1;
947 return readsize;
948 } while (1);
949 }
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
955 {
956 char *eol;
957 ssize_t readsize;
959 while (TRUE) {
960 if (io->bufsize > 0) {
961 eol = memchr(io->bufpos, c, io->bufsize);
962 if (eol) {
963 char *line = io->bufpos;
965 *eol = 0;
966 io->bufpos = eol + 1;
967 io->bufsize -= io->bufpos - line;
968 return line;
969 }
970 }
972 if (io_eof(io)) {
973 if (io->bufsize) {
974 io->bufpos[io->bufsize] = 0;
975 io->bufsize = 0;
976 return io->bufpos;
977 }
978 return NULL;
979 }
981 if (!can_read)
982 return NULL;
984 if (io->bufsize > 0 && io->bufpos > io->buf)
985 memmove(io->buf, io->bufpos, io->bufsize);
987 if (io->bufalloc == io->bufsize) {
988 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989 return NULL;
990 io->bufalloc += BUFSIZ;
991 }
993 io->bufpos = io->buf;
994 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995 if (io_error(io))
996 return NULL;
997 io->bufsize += readsize;
998 }
999 }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1003 {
1004 size_t written = 0;
1006 while (!io_error(io) && written < bufsize) {
1007 ssize_t size;
1009 size = write(io->pipe, buf + written, bufsize - written);
1010 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011 continue;
1012 else if (size == -1)
1013 io->error = errno;
1014 else
1015 written += size;
1016 }
1018 return written == bufsize;
1019 }
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1023 {
1024 char *result = io_get(io, '\n', TRUE);
1026 if (result) {
1027 result = chomp_string(result);
1028 string_ncopy_do(buf, bufsize, result, strlen(result));
1029 }
1031 return io_done(io) && result;
1032 }
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1036 {
1037 struct io io = {};
1039 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1040 && io_read_buf(&io, buf, bufsize);
1041 }
1043 static int
1044 io_load(struct io *io, const char *separators,
1045 int (*read_property)(char *, size_t, char *, size_t))
1046 {
1047 char *name;
1048 int state = OK;
1050 if (!io_start(io))
1051 return ERR;
1053 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054 char *value;
1055 size_t namelen;
1056 size_t valuelen;
1058 name = chomp_string(name);
1059 namelen = strcspn(name, separators);
1061 if (name[namelen]) {
1062 name[namelen] = 0;
1063 value = chomp_string(name + namelen + 1);
1064 valuelen = strlen(value);
1066 } else {
1067 value = "";
1068 valuelen = 0;
1069 }
1071 state = read_property(name, namelen, value, valuelen);
1072 }
1074 if (state != ERR && io_error(io))
1075 state = ERR;
1076 io_done(io);
1078 return state;
1079 }
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083 int (*read_property)(char *, size_t, char *, size_t))
1084 {
1085 struct io io = {};
1087 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1088 ? io_load(&io, separators, read_property) : ERR;
1089 }
1092 /*
1093 * User requests
1094 */
1096 #define REQ_INFO \
1097 /* XXX: Keep the view request first and in sync with views[]. */ \
1098 REQ_GROUP("View switching") \
1099 REQ_(VIEW_MAIN, "Show main view"), \
1100 REQ_(VIEW_DIFF, "Show diff view"), \
1101 REQ_(VIEW_LOG, "Show log view"), \
1102 REQ_(VIEW_TREE, "Show tree view"), \
1103 REQ_(VIEW_BLOB, "Show blob view"), \
1104 REQ_(VIEW_BLAME, "Show blame view"), \
1105 REQ_(VIEW_BRANCH, "Show branch view"), \
1106 REQ_(VIEW_HELP, "Show help page"), \
1107 REQ_(VIEW_PAGER, "Show pager view"), \
1108 REQ_(VIEW_STATUS, "Show status view"), \
1109 REQ_(VIEW_STAGE, "Show stage view"), \
1110 \
1111 REQ_GROUP("View manipulation") \
1112 REQ_(ENTER, "Enter current line and scroll"), \
1113 REQ_(NEXT, "Move to next"), \
1114 REQ_(PREVIOUS, "Move to previous"), \
1115 REQ_(PARENT, "Move to parent"), \
1116 REQ_(VIEW_NEXT, "Move focus to next view"), \
1117 REQ_(REFRESH, "Reload and refresh"), \
1118 REQ_(MAXIMIZE, "Maximize the current view"), \
1119 REQ_(VIEW_CLOSE, "Close the current view"), \
1120 REQ_(QUIT, "Close all views and quit"), \
1121 \
1122 REQ_GROUP("View specific requests") \
1123 REQ_(STATUS_UPDATE, "Update file status"), \
1124 REQ_(STATUS_REVERT, "Revert file changes"), \
1125 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1126 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1127 \
1128 REQ_GROUP("Cursor navigation") \
1129 REQ_(MOVE_UP, "Move cursor one line up"), \
1130 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1131 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1132 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1133 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1134 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1135 \
1136 REQ_GROUP("Scrolling") \
1137 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1138 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1139 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1140 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1141 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1142 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1143 \
1144 REQ_GROUP("Searching") \
1145 REQ_(SEARCH, "Search the view"), \
1146 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1147 REQ_(FIND_NEXT, "Find next search match"), \
1148 REQ_(FIND_PREV, "Find previous search match"), \
1149 \
1150 REQ_GROUP("Option manipulation") \
1151 REQ_(OPTIONS, "Open option menu"), \
1152 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1153 REQ_(TOGGLE_DATE, "Toggle date display"), \
1154 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1156 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1157 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1158 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160 \
1161 REQ_GROUP("Misc") \
1162 REQ_(PROMPT, "Bring up the prompt"), \
1163 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1164 REQ_(SHOW_VERSION, "Show version information"), \
1165 REQ_(STOP_LOADING, "Stop all loading views"), \
1166 REQ_(EDIT, "Open in editor"), \
1167 REQ_(NONE, "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175 /* Offset all requests to avoid conflicts with ncurses getch values. */
1176 REQ_UNKNOWN = KEY_MAX + 1,
1177 REQ_OFFSET,
1178 REQ_INFO
1180 #undef REQ_GROUP
1181 #undef REQ_
1182 };
1184 struct request_info {
1185 enum request request;
1186 const char *name;
1187 int namelen;
1188 const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194 REQ_INFO
1195 #undef REQ_GROUP
1196 #undef REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1201 {
1202 int namelen = strlen(name);
1203 int i;
1205 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206 if (enum_equals(req_info[i], name, namelen))
1207 return req_info[i].request;
1209 return REQ_UNKNOWN;
1210 }
1213 /*
1214 * Options
1215 */
1217 /* Option and state variables. */
1218 static enum date opt_date = DATE_DEFAULT;
1219 static enum author opt_author = AUTHOR_DEFAULT;
1220 static bool opt_line_number = FALSE;
1221 static bool opt_line_graphics = TRUE;
1222 static bool opt_rev_graph = FALSE;
1223 static bool opt_show_refs = TRUE;
1224 static int opt_num_interval = 5;
1225 static double opt_hscroll = 0.50;
1226 static double opt_scale_split_view = 2.0 / 3.0;
1227 static int opt_tab_size = 8;
1228 static int opt_author_cols = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR] = "";
1230 static char opt_file[SIZEOF_STR] = "";
1231 static char opt_ref[SIZEOF_REF] = "";
1232 static char opt_head[SIZEOF_REF] = "";
1233 static char opt_remote[SIZEOF_REF] = "";
1234 static char opt_encoding[20] = "UTF-8";
1235 static iconv_t opt_iconv_in = ICONV_NONE;
1236 static iconv_t opt_iconv_out = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR] = "";
1238 static char opt_cdup[SIZEOF_STR] = "";
1239 static char opt_prefix[SIZEOF_STR] = "";
1240 static char opt_git_dir[SIZEOF_STR] = "";
1241 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR] = "";
1243 static FILE *opt_tty = NULL;
1245 #define is_initial_commit() (!get_ref_head())
1246 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250 * Line-oriented content detection.
1251 */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1268 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1269 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1270 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1275 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1276 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1278 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1279 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1280 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1283 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1284 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1285 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1286 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1289 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1290 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1291 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1292 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1293 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1297 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1298 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1300 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1302 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1303 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1304 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1307 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1308 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1310 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1311 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1312 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1314 enum line_type {
1315 #define LINE(type, line, fg, bg, attr) \
1316 LINE_##type
1317 LINE_INFO,
1318 LINE_NONE
1319 #undef LINE
1320 };
1322 struct line_info {
1323 const char *name; /* Option name. */
1324 int namelen; /* Size of option name. */
1325 const char *line; /* The start of line to match. */
1326 int linelen; /* Size of string to match. */
1327 int fg, bg, attr; /* Color and text attributes for the lines. */
1328 };
1330 static struct line_info line_info[] = {
1331 #define LINE(type, line, fg, bg, attr) \
1332 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333 LINE_INFO
1334 #undef LINE
1335 };
1337 static enum line_type
1338 get_line_type(const char *line)
1339 {
1340 int linelen = strlen(line);
1341 enum line_type type;
1343 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1344 /* Case insensitive search matches Signed-off-by lines better. */
1345 if (linelen >= line_info[type].linelen &&
1346 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1347 return type;
1349 return LINE_DEFAULT;
1350 }
1352 static inline int
1353 get_line_attr(enum line_type type)
1354 {
1355 assert(type < ARRAY_SIZE(line_info));
1356 return COLOR_PAIR(type) | line_info[type].attr;
1357 }
1359 static struct line_info *
1360 get_line_info(const char *name)
1361 {
1362 size_t namelen = strlen(name);
1363 enum line_type type;
1365 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1366 if (enum_equals(line_info[type], name, namelen))
1367 return &line_info[type];
1369 return NULL;
1370 }
1372 static void
1373 init_colors(void)
1374 {
1375 int default_bg = line_info[LINE_DEFAULT].bg;
1376 int default_fg = line_info[LINE_DEFAULT].fg;
1377 enum line_type type;
1379 start_color();
1381 if (assume_default_colors(default_fg, default_bg) == ERR) {
1382 default_bg = COLOR_BLACK;
1383 default_fg = COLOR_WHITE;
1384 }
1386 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1387 struct line_info *info = &line_info[type];
1388 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1389 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1391 init_pair(type, fg, bg);
1392 }
1393 }
1395 struct line {
1396 enum line_type type;
1398 /* State flags */
1399 unsigned int selected:1;
1400 unsigned int dirty:1;
1401 unsigned int cleareol:1;
1402 unsigned int other:16;
1404 void *data; /* User data */
1405 };
1408 /*
1409 * Keys
1410 */
1412 struct keybinding {
1413 int alias;
1414 enum request request;
1415 };
1417 static struct keybinding default_keybindings[] = {
1418 /* View switching */
1419 { 'm', REQ_VIEW_MAIN },
1420 { 'd', REQ_VIEW_DIFF },
1421 { 'l', REQ_VIEW_LOG },
1422 { 't', REQ_VIEW_TREE },
1423 { 'f', REQ_VIEW_BLOB },
1424 { 'B', REQ_VIEW_BLAME },
1425 { 'H', REQ_VIEW_BRANCH },
1426 { 'p', REQ_VIEW_PAGER },
1427 { 'h', REQ_VIEW_HELP },
1428 { 'S', REQ_VIEW_STATUS },
1429 { 'c', REQ_VIEW_STAGE },
1431 /* View manipulation */
1432 { 'q', REQ_VIEW_CLOSE },
1433 { KEY_TAB, REQ_VIEW_NEXT },
1434 { KEY_RETURN, REQ_ENTER },
1435 { KEY_UP, REQ_PREVIOUS },
1436 { KEY_DOWN, REQ_NEXT },
1437 { 'R', REQ_REFRESH },
1438 { KEY_F(5), REQ_REFRESH },
1439 { 'O', REQ_MAXIMIZE },
1441 /* Cursor navigation */
1442 { 'k', REQ_MOVE_UP },
1443 { 'j', REQ_MOVE_DOWN },
1444 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1445 { KEY_END, REQ_MOVE_LAST_LINE },
1446 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1447 { ' ', REQ_MOVE_PAGE_DOWN },
1448 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1449 { 'b', REQ_MOVE_PAGE_UP },
1450 { '-', REQ_MOVE_PAGE_UP },
1452 /* Scrolling */
1453 { KEY_LEFT, REQ_SCROLL_LEFT },
1454 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1455 { KEY_IC, REQ_SCROLL_LINE_UP },
1456 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1457 { 'w', REQ_SCROLL_PAGE_UP },
1458 { 's', REQ_SCROLL_PAGE_DOWN },
1460 /* Searching */
1461 { '/', REQ_SEARCH },
1462 { '?', REQ_SEARCH_BACK },
1463 { 'n', REQ_FIND_NEXT },
1464 { 'N', REQ_FIND_PREV },
1466 /* Misc */
1467 { 'Q', REQ_QUIT },
1468 { 'z', REQ_STOP_LOADING },
1469 { 'v', REQ_SHOW_VERSION },
1470 { 'r', REQ_SCREEN_REDRAW },
1471 { 'o', REQ_OPTIONS },
1472 { '.', REQ_TOGGLE_LINENO },
1473 { 'D', REQ_TOGGLE_DATE },
1474 { 'A', REQ_TOGGLE_AUTHOR },
1475 { 'g', REQ_TOGGLE_REV_GRAPH },
1476 { 'F', REQ_TOGGLE_REFS },
1477 { 'I', REQ_TOGGLE_SORT_ORDER },
1478 { 'i', REQ_TOGGLE_SORT_FIELD },
1479 { ':', REQ_PROMPT },
1480 { 'u', REQ_STATUS_UPDATE },
1481 { '!', REQ_STATUS_REVERT },
1482 { 'M', REQ_STATUS_MERGE },
1483 { '@', REQ_STAGE_NEXT },
1484 { ',', REQ_PARENT },
1485 { 'e', REQ_EDIT },
1486 };
1488 #define KEYMAP_INFO \
1489 KEYMAP_(GENERIC), \
1490 KEYMAP_(MAIN), \
1491 KEYMAP_(DIFF), \
1492 KEYMAP_(LOG), \
1493 KEYMAP_(TREE), \
1494 KEYMAP_(BLOB), \
1495 KEYMAP_(BLAME), \
1496 KEYMAP_(BRANCH), \
1497 KEYMAP_(PAGER), \
1498 KEYMAP_(HELP), \
1499 KEYMAP_(STATUS), \
1500 KEYMAP_(STAGE)
1502 enum keymap {
1503 #define KEYMAP_(name) KEYMAP_##name
1504 KEYMAP_INFO
1505 #undef KEYMAP_
1506 };
1508 static const struct enum_map keymap_table[] = {
1509 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510 KEYMAP_INFO
1511 #undef KEYMAP_
1512 };
1514 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1516 struct keybinding_table {
1517 struct keybinding *data;
1518 size_t size;
1519 };
1521 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1523 static void
1524 add_keybinding(enum keymap keymap, enum request request, int key)
1525 {
1526 struct keybinding_table *table = &keybindings[keymap];
1527 size_t i;
1529 for (i = 0; i < keybindings[keymap].size; i++) {
1530 if (keybindings[keymap].data[i].alias == key) {
1531 keybindings[keymap].data[i].request = request;
1532 return;
1533 }
1534 }
1536 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1537 if (!table->data)
1538 die("Failed to allocate keybinding");
1539 table->data[table->size].alias = key;
1540 table->data[table->size++].request = request;
1542 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1543 int i;
1545 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1546 if (default_keybindings[i].alias == key)
1547 default_keybindings[i].request = REQ_NONE;
1548 }
1549 }
1551 /* Looks for a key binding first in the given map, then in the generic map, and
1552 * lastly in the default keybindings. */
1553 static enum request
1554 get_keybinding(enum keymap keymap, int key)
1555 {
1556 size_t i;
1558 for (i = 0; i < keybindings[keymap].size; i++)
1559 if (keybindings[keymap].data[i].alias == key)
1560 return keybindings[keymap].data[i].request;
1562 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1563 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1564 return keybindings[KEYMAP_GENERIC].data[i].request;
1566 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1567 if (default_keybindings[i].alias == key)
1568 return default_keybindings[i].request;
1570 return (enum request) key;
1571 }
1574 struct key {
1575 const char *name;
1576 int value;
1577 };
1579 static const struct key key_table[] = {
1580 { "Enter", KEY_RETURN },
1581 { "Space", ' ' },
1582 { "Backspace", KEY_BACKSPACE },
1583 { "Tab", KEY_TAB },
1584 { "Escape", KEY_ESC },
1585 { "Left", KEY_LEFT },
1586 { "Right", KEY_RIGHT },
1587 { "Up", KEY_UP },
1588 { "Down", KEY_DOWN },
1589 { "Insert", KEY_IC },
1590 { "Delete", KEY_DC },
1591 { "Hash", '#' },
1592 { "Home", KEY_HOME },
1593 { "End", KEY_END },
1594 { "PageUp", KEY_PPAGE },
1595 { "PageDown", KEY_NPAGE },
1596 { "F1", KEY_F(1) },
1597 { "F2", KEY_F(2) },
1598 { "F3", KEY_F(3) },
1599 { "F4", KEY_F(4) },
1600 { "F5", KEY_F(5) },
1601 { "F6", KEY_F(6) },
1602 { "F7", KEY_F(7) },
1603 { "F8", KEY_F(8) },
1604 { "F9", KEY_F(9) },
1605 { "F10", KEY_F(10) },
1606 { "F11", KEY_F(11) },
1607 { "F12", KEY_F(12) },
1608 };
1610 static int
1611 get_key_value(const char *name)
1612 {
1613 int i;
1615 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1616 if (!strcasecmp(key_table[i].name, name))
1617 return key_table[i].value;
1619 if (strlen(name) == 1 && isprint(*name))
1620 return (int) *name;
1622 return ERR;
1623 }
1625 static const char *
1626 get_key_name(int key_value)
1627 {
1628 static char key_char[] = "'X'";
1629 const char *seq = NULL;
1630 int key;
1632 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1633 if (key_table[key].value == key_value)
1634 seq = key_table[key].name;
1636 if (seq == NULL &&
1637 key_value < 127 &&
1638 isprint(key_value)) {
1639 key_char[1] = (char) key_value;
1640 seq = key_char;
1641 }
1643 return seq ? seq : "(no key)";
1644 }
1646 static bool
1647 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1648 {
1649 const char *sep = *pos > 0 ? ", " : "";
1650 const char *keyname = get_key_name(keybinding->alias);
1652 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1653 }
1655 static bool
1656 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1657 enum keymap keymap, bool all)
1658 {
1659 int i;
1661 for (i = 0; i < keybindings[keymap].size; i++) {
1662 if (keybindings[keymap].data[i].request == request) {
1663 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1664 return FALSE;
1665 if (!all)
1666 break;
1667 }
1668 }
1670 return TRUE;
1671 }
1673 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1675 static const char *
1676 get_keys(enum keymap keymap, enum request request, bool all)
1677 {
1678 static char buf[BUFSIZ];
1679 size_t pos = 0;
1680 int i;
1682 buf[pos] = 0;
1684 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1685 return "Too many keybindings!";
1686 if (pos > 0 && !all)
1687 return buf;
1689 if (keymap != KEYMAP_GENERIC) {
1690 /* Only the generic keymap includes the default keybindings when
1691 * listing all keys. */
1692 if (all)
1693 return buf;
1695 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1696 return "Too many keybindings!";
1697 if (pos)
1698 return buf;
1699 }
1701 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1702 if (default_keybindings[i].request == request) {
1703 if (!append_key(buf, &pos, &default_keybindings[i]))
1704 return "Too many keybindings!";
1705 if (!all)
1706 return buf;
1707 }
1708 }
1710 return buf;
1711 }
1713 struct run_request {
1714 enum keymap keymap;
1715 int key;
1716 const char *argv[SIZEOF_ARG];
1717 };
1719 static struct run_request *run_request;
1720 static size_t run_requests;
1722 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1724 static enum request
1725 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1726 {
1727 struct run_request *req;
1729 if (argc >= ARRAY_SIZE(req->argv) - 1)
1730 return REQ_NONE;
1732 if (!realloc_run_requests(&run_request, run_requests, 1))
1733 return REQ_NONE;
1735 req = &run_request[run_requests];
1736 req->keymap = keymap;
1737 req->key = key;
1738 req->argv[0] = NULL;
1740 if (!format_argv(req->argv, argv, FORMAT_NONE))
1741 return REQ_NONE;
1743 return REQ_NONE + ++run_requests;
1744 }
1746 static struct run_request *
1747 get_run_request(enum request request)
1748 {
1749 if (request <= REQ_NONE)
1750 return NULL;
1751 return &run_request[request - REQ_NONE - 1];
1752 }
1754 static void
1755 add_builtin_run_requests(void)
1756 {
1757 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1758 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1759 const char *commit[] = { "git", "commit", NULL };
1760 const char *gc[] = { "git", "gc", NULL };
1761 struct {
1762 enum keymap keymap;
1763 int key;
1764 int argc;
1765 const char **argv;
1766 } reqs[] = {
1767 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1768 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1769 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1770 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1771 };
1772 int i;
1774 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1775 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1777 if (req != reqs[i].key)
1778 continue;
1779 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1780 if (req != REQ_NONE)
1781 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782 }
1783 }
1785 /*
1786 * User config file handling.
1787 */
1789 static int config_lineno;
1790 static bool config_errors;
1791 static const char *config_msg;
1793 static const struct enum_map color_map[] = {
1794 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1795 COLOR_MAP(DEFAULT),
1796 COLOR_MAP(BLACK),
1797 COLOR_MAP(BLUE),
1798 COLOR_MAP(CYAN),
1799 COLOR_MAP(GREEN),
1800 COLOR_MAP(MAGENTA),
1801 COLOR_MAP(RED),
1802 COLOR_MAP(WHITE),
1803 COLOR_MAP(YELLOW),
1804 };
1806 static const struct enum_map attr_map[] = {
1807 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1808 ATTR_MAP(NORMAL),
1809 ATTR_MAP(BLINK),
1810 ATTR_MAP(BOLD),
1811 ATTR_MAP(DIM),
1812 ATTR_MAP(REVERSE),
1813 ATTR_MAP(STANDOUT),
1814 ATTR_MAP(UNDERLINE),
1815 };
1817 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1819 static int parse_step(double *opt, const char *arg)
1820 {
1821 *opt = atoi(arg);
1822 if (!strchr(arg, '%'))
1823 return OK;
1825 /* "Shift down" so 100% and 1 does not conflict. */
1826 *opt = (*opt - 1) / 100;
1827 if (*opt >= 1.0) {
1828 *opt = 0.99;
1829 config_msg = "Step value larger than 100%";
1830 return ERR;
1831 }
1832 if (*opt < 0.0) {
1833 *opt = 1;
1834 config_msg = "Invalid step value";
1835 return ERR;
1836 }
1837 return OK;
1838 }
1840 static int
1841 parse_int(int *opt, const char *arg, int min, int max)
1842 {
1843 int value = atoi(arg);
1845 if (min <= value && value <= max) {
1846 *opt = value;
1847 return OK;
1848 }
1850 config_msg = "Integer value out of bound";
1851 return ERR;
1852 }
1854 static bool
1855 set_color(int *color, const char *name)
1856 {
1857 if (map_enum(color, color_map, name))
1858 return TRUE;
1859 if (!prefixcmp(name, "color"))
1860 return parse_int(color, name + 5, 0, 255) == OK;
1861 return FALSE;
1862 }
1864 /* Wants: object fgcolor bgcolor [attribute] */
1865 static int
1866 option_color_command(int argc, const char *argv[])
1867 {
1868 struct line_info *info;
1870 if (argc < 3) {
1871 config_msg = "Wrong number of arguments given to color command";
1872 return ERR;
1873 }
1875 info = get_line_info(argv[0]);
1876 if (!info) {
1877 static const struct enum_map obsolete[] = {
1878 ENUM_MAP("main-delim", LINE_DELIMITER),
1879 ENUM_MAP("main-date", LINE_DATE),
1880 ENUM_MAP("main-author", LINE_AUTHOR),
1881 };
1882 int index;
1884 if (!map_enum(&index, obsolete, argv[0])) {
1885 config_msg = "Unknown color name";
1886 return ERR;
1887 }
1888 info = &line_info[index];
1889 }
1891 if (!set_color(&info->fg, argv[1]) ||
1892 !set_color(&info->bg, argv[2])) {
1893 config_msg = "Unknown color";
1894 return ERR;
1895 }
1897 info->attr = 0;
1898 while (argc-- > 3) {
1899 int attr;
1901 if (!set_attribute(&attr, argv[argc])) {
1902 config_msg = "Unknown attribute";
1903 return ERR;
1904 }
1905 info->attr |= attr;
1906 }
1908 return OK;
1909 }
1911 static int parse_bool(bool *opt, const char *arg)
1912 {
1913 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1914 ? TRUE : FALSE;
1915 return OK;
1916 }
1918 static int parse_enum_do(unsigned int *opt, const char *arg,
1919 const struct enum_map *map, size_t map_size)
1920 {
1921 bool is_true;
1923 assert(map_size > 1);
1925 if (map_enum_do(map, map_size, (int *) opt, arg))
1926 return OK;
1928 if (parse_bool(&is_true, arg) != OK)
1929 return ERR;
1931 *opt = is_true ? map[1].value : map[0].value;
1932 return OK;
1933 }
1935 #define parse_enum(opt, arg, map) \
1936 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1938 static int
1939 parse_string(char *opt, const char *arg, size_t optsize)
1940 {
1941 int arglen = strlen(arg);
1943 switch (arg[0]) {
1944 case '\"':
1945 case '\'':
1946 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1947 config_msg = "Unmatched quotation";
1948 return ERR;
1949 }
1950 arg += 1; arglen -= 2;
1951 default:
1952 string_ncopy_do(opt, optsize, arg, arglen);
1953 return OK;
1954 }
1955 }
1957 /* Wants: name = value */
1958 static int
1959 option_set_command(int argc, const char *argv[])
1960 {
1961 if (argc != 3) {
1962 config_msg = "Wrong number of arguments given to set command";
1963 return ERR;
1964 }
1966 if (strcmp(argv[1], "=")) {
1967 config_msg = "No value assigned";
1968 return ERR;
1969 }
1971 if (!strcmp(argv[0], "show-author"))
1972 return parse_enum(&opt_author, argv[2], author_map);
1974 if (!strcmp(argv[0], "show-date"))
1975 return parse_enum(&opt_date, argv[2], date_map);
1977 if (!strcmp(argv[0], "show-rev-graph"))
1978 return parse_bool(&opt_rev_graph, argv[2]);
1980 if (!strcmp(argv[0], "show-refs"))
1981 return parse_bool(&opt_show_refs, argv[2]);
1983 if (!strcmp(argv[0], "show-line-numbers"))
1984 return parse_bool(&opt_line_number, argv[2]);
1986 if (!strcmp(argv[0], "line-graphics"))
1987 return parse_bool(&opt_line_graphics, argv[2]);
1989 if (!strcmp(argv[0], "line-number-interval"))
1990 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1992 if (!strcmp(argv[0], "author-width"))
1993 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1995 if (!strcmp(argv[0], "horizontal-scroll"))
1996 return parse_step(&opt_hscroll, argv[2]);
1998 if (!strcmp(argv[0], "split-view-height"))
1999 return parse_step(&opt_scale_split_view, argv[2]);
2001 if (!strcmp(argv[0], "tab-size"))
2002 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2004 if (!strcmp(argv[0], "commit-encoding"))
2005 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2007 config_msg = "Unknown variable name";
2008 return ERR;
2009 }
2011 /* Wants: mode request key */
2012 static int
2013 option_bind_command(int argc, const char *argv[])
2014 {
2015 enum request request;
2016 int keymap = -1;
2017 int key;
2019 if (argc < 3) {
2020 config_msg = "Wrong number of arguments given to bind command";
2021 return ERR;
2022 }
2024 if (!set_keymap(&keymap, argv[0])) {
2025 config_msg = "Unknown key map";
2026 return ERR;
2027 }
2029 key = get_key_value(argv[1]);
2030 if (key == ERR) {
2031 config_msg = "Unknown key";
2032 return ERR;
2033 }
2035 request = get_request(argv[2]);
2036 if (request == REQ_UNKNOWN) {
2037 static const struct enum_map obsolete[] = {
2038 ENUM_MAP("cherry-pick", REQ_NONE),
2039 ENUM_MAP("screen-resize", REQ_NONE),
2040 ENUM_MAP("tree-parent", REQ_PARENT),
2041 };
2042 int alias;
2044 if (map_enum(&alias, obsolete, argv[2])) {
2045 if (alias != REQ_NONE)
2046 add_keybinding(keymap, alias, key);
2047 config_msg = "Obsolete request name";
2048 return ERR;
2049 }
2050 }
2051 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2052 request = add_run_request(keymap, key, argc - 2, argv + 2);
2053 if (request == REQ_UNKNOWN) {
2054 config_msg = "Unknown request name";
2055 return ERR;
2056 }
2058 add_keybinding(keymap, request, key);
2060 return OK;
2061 }
2063 static int
2064 set_option(const char *opt, char *value)
2065 {
2066 const char *argv[SIZEOF_ARG];
2067 int argc = 0;
2069 if (!argv_from_string(argv, &argc, value)) {
2070 config_msg = "Too many option arguments";
2071 return ERR;
2072 }
2074 if (!strcmp(opt, "color"))
2075 return option_color_command(argc, argv);
2077 if (!strcmp(opt, "set"))
2078 return option_set_command(argc, argv);
2080 if (!strcmp(opt, "bind"))
2081 return option_bind_command(argc, argv);
2083 config_msg = "Unknown option command";
2084 return ERR;
2085 }
2087 static int
2088 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2089 {
2090 int status = OK;
2092 config_lineno++;
2093 config_msg = "Internal error";
2095 /* Check for comment markers, since read_properties() will
2096 * only ensure opt and value are split at first " \t". */
2097 optlen = strcspn(opt, "#");
2098 if (optlen == 0)
2099 return OK;
2101 if (opt[optlen] != 0) {
2102 config_msg = "No option value";
2103 status = ERR;
2105 } else {
2106 /* Look for comment endings in the value. */
2107 size_t len = strcspn(value, "#");
2109 if (len < valuelen) {
2110 valuelen = len;
2111 value[valuelen] = 0;
2112 }
2114 status = set_option(opt, value);
2115 }
2117 if (status == ERR) {
2118 warn("Error on line %d, near '%.*s': %s",
2119 config_lineno, (int) optlen, opt, config_msg);
2120 config_errors = TRUE;
2121 }
2123 /* Always keep going if errors are encountered. */
2124 return OK;
2125 }
2127 static void
2128 load_option_file(const char *path)
2129 {
2130 struct io io = {};
2132 /* It's OK that the file doesn't exist. */
2133 if (!io_open(&io, "%s", path))
2134 return;
2136 config_lineno = 0;
2137 config_errors = FALSE;
2139 if (io_load(&io, " \t", read_option) == ERR ||
2140 config_errors == TRUE)
2141 warn("Errors while loading %s.", path);
2142 }
2144 static int
2145 load_options(void)
2146 {
2147 const char *home = getenv("HOME");
2148 const char *tigrc_user = getenv("TIGRC_USER");
2149 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2150 char buf[SIZEOF_STR];
2152 if (!tigrc_system)
2153 tigrc_system = SYSCONFDIR "/tigrc";
2154 load_option_file(tigrc_system);
2156 if (!tigrc_user) {
2157 if (!home || !string_format(buf, "%s/.tigrc", home))
2158 return ERR;
2159 tigrc_user = buf;
2160 }
2161 load_option_file(tigrc_user);
2163 /* Add _after_ loading config files to avoid adding run requests
2164 * that conflict with keybindings. */
2165 add_builtin_run_requests();
2167 return OK;
2168 }
2171 /*
2172 * The viewer
2173 */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views() (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF] = "";
2189 static char ref_commit[SIZEOF_REF] = "HEAD";
2190 static char ref_head[SIZEOF_REF] = "HEAD";
2191 static char ref_branch[SIZEOF_REF] = "";
2193 enum view_type {
2194 VIEW_MAIN,
2195 VIEW_DIFF,
2196 VIEW_LOG,
2197 VIEW_TREE,
2198 VIEW_BLOB,
2199 VIEW_BLAME,
2200 VIEW_BRANCH,
2201 VIEW_HELP,
2202 VIEW_PAGER,
2203 VIEW_STATUS,
2204 VIEW_STAGE,
2205 };
2207 struct view {
2208 enum view_type type; /* View type */
2209 const char *name; /* View name */
2210 const char *cmd_env; /* Command line set via environment */
2211 const char *id; /* Points to either of ref_{head,commit,blob} */
2213 struct view_ops *ops; /* View operations */
2215 enum keymap keymap; /* What keymap does this view have */
2216 bool git_dir; /* Whether the view requires a git directory. */
2217 bool refresh; /* Whether the view supports refreshing. */
2219 char ref[SIZEOF_REF]; /* Hovered commit reference */
2220 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2222 int height, width; /* The width and height of the main window */
2223 WINDOW *win; /* The main window */
2224 WINDOW *title; /* The title window living below the main window */
2226 /* Navigation */
2227 unsigned long offset; /* Offset of the window top */
2228 unsigned long yoffset; /* Offset from the window side. */
2229 unsigned long lineno; /* Current line number */
2230 unsigned long p_offset; /* Previous offset of the window top */
2231 unsigned long p_yoffset;/* Previous offset from the window side */
2232 unsigned long p_lineno; /* Previous current line number */
2233 bool p_restore; /* Should the previous position be restored. */
2235 /* Searching */
2236 char grep[SIZEOF_STR]; /* Search string */
2237 regex_t *regex; /* Pre-compiled regexp */
2239 /* If non-NULL, points to the view that opened this view. If this view
2240 * is closed tig will switch back to the parent view. */
2241 struct view *parent;
2242 struct view *prev;
2244 /* Buffering */
2245 size_t lines; /* Total number of lines */
2246 struct line *line; /* Line index */
2247 unsigned int digits; /* Number of digits in the lines member. */
2249 /* Drawing */
2250 struct line *curline; /* Line currently being drawn. */
2251 enum line_type curtype; /* Attribute currently used for drawing. */
2252 unsigned long col; /* Column when drawing. */
2253 bool has_scrolled; /* View was scrolled. */
2255 /* Loading */
2256 struct io io;
2257 struct io *pipe;
2258 time_t start_time;
2259 time_t update_secs;
2260 };
2262 struct view_ops {
2263 /* What type of content being displayed. Used in the title bar. */
2264 const char *type;
2265 /* Default command arguments. */
2266 const char **argv;
2267 /* Open and reads in all view content. */
2268 bool (*open)(struct view *view);
2269 /* Read one line; updates view->line. */
2270 bool (*read)(struct view *view, char *data);
2271 /* Draw one line; @lineno must be < view->height. */
2272 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2273 /* Depending on view handle a special requests. */
2274 enum request (*request)(struct view *view, enum request request, struct line *line);
2275 /* Search for regexp in a line. */
2276 bool (*grep)(struct view *view, struct line *line);
2277 /* Select line */
2278 void (*select)(struct view *view, struct line *line);
2279 /* Prepare view for loading */
2280 bool (*prepare)(struct view *view);
2281 };
2283 static struct view_ops blame_ops;
2284 static struct view_ops blob_ops;
2285 static struct view_ops diff_ops;
2286 static struct view_ops help_ops;
2287 static struct view_ops log_ops;
2288 static struct view_ops main_ops;
2289 static struct view_ops pager_ops;
2290 static struct view_ops stage_ops;
2291 static struct view_ops status_ops;
2292 static struct view_ops tree_ops;
2293 static struct view_ops branch_ops;
2295 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2296 { type, name, #env, ref, ops, map, git, refresh }
2298 #define VIEW_(id, name, ops, git, refresh, ref) \
2299 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2301 static struct view views[] = {
2302 VIEW_(MAIN, "main", &main_ops, TRUE, TRUE, ref_head),
2303 VIEW_(DIFF, "diff", &diff_ops, TRUE, FALSE, ref_commit),
2304 VIEW_(LOG, "log", &log_ops, TRUE, TRUE, ref_head),
2305 VIEW_(TREE, "tree", &tree_ops, TRUE, FALSE, ref_commit),
2306 VIEW_(BLOB, "blob", &blob_ops, TRUE, FALSE, ref_blob),
2307 VIEW_(BLAME, "blame", &blame_ops, TRUE, FALSE, ref_commit),
2308 VIEW_(BRANCH, "branch", &branch_ops, TRUE, TRUE, ref_head),
2309 VIEW_(HELP, "help", &help_ops, FALSE, FALSE, ""),
2310 VIEW_(PAGER, "pager", &pager_ops, FALSE, FALSE, "stdin"),
2311 VIEW_(STATUS, "status", &status_ops, TRUE, TRUE, ""),
2312 VIEW_(STAGE, "stage", &stage_ops, TRUE, TRUE, ""),
2313 };
2315 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2317 #define foreach_view(view, i) \
2318 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2320 #define view_is_displayed(view) \
2321 (view == display[0] || view == display[1])
2323 #define view_has_parent(view, child_type, parent_type) \
2324 (view->type == child_type && view->parent && view->parent->type == parent_type)
2326 static inline void
2327 set_view_attr(struct view *view, enum line_type type)
2328 {
2329 if (!view->curline->selected && view->curtype != type) {
2330 (void) wattrset(view->win, get_line_attr(type));
2331 wchgat(view->win, -1, 0, type, NULL);
2332 view->curtype = type;
2333 }
2334 }
2336 static int
2337 draw_chars(struct view *view, enum line_type type, const char *string,
2338 int max_len, bool use_tilde)
2339 {
2340 static char out_buffer[BUFSIZ * 2];
2341 int len = 0;
2342 int col = 0;
2343 int trimmed = FALSE;
2344 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2346 if (max_len <= 0)
2347 return 0;
2349 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2351 set_view_attr(view, type);
2352 if (len > 0) {
2353 if (opt_iconv_out != ICONV_NONE) {
2354 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2355 size_t inlen = len + 1;
2357 char *outbuf = out_buffer;
2358 size_t outlen = sizeof(out_buffer);
2360 size_t ret;
2362 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2363 if (ret != (size_t) -1) {
2364 string = out_buffer;
2365 len = sizeof(out_buffer) - outlen;
2366 }
2367 }
2369 waddnstr(view->win, string, len);
2370 }
2371 if (trimmed && use_tilde) {
2372 set_view_attr(view, LINE_DELIMITER);
2373 waddch(view->win, '~');
2374 col++;
2375 }
2377 return col;
2378 }
2380 static int
2381 draw_space(struct view *view, enum line_type type, int max, int spaces)
2382 {
2383 static char space[] = " ";
2384 int col = 0;
2386 spaces = MIN(max, spaces);
2388 while (spaces > 0) {
2389 int len = MIN(spaces, sizeof(space) - 1);
2391 col += draw_chars(view, type, space, len, FALSE);
2392 spaces -= len;
2393 }
2395 return col;
2396 }
2398 static bool
2399 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2400 {
2401 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2402 return view->width + view->yoffset <= view->col;
2403 }
2405 static bool
2406 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2407 {
2408 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2409 int max = view->width + view->yoffset - view->col;
2410 int i;
2412 if (max < size)
2413 size = max;
2415 set_view_attr(view, type);
2416 /* Using waddch() instead of waddnstr() ensures that
2417 * they'll be rendered correctly for the cursor line. */
2418 for (i = skip; i < size; i++)
2419 waddch(view->win, graphic[i]);
2421 view->col += size;
2422 if (size < max && skip <= size)
2423 waddch(view->win, ' ');
2424 view->col++;
2426 return view->width + view->yoffset <= view->col;
2427 }
2429 static bool
2430 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2431 {
2432 int max = MIN(view->width + view->yoffset - view->col, len);
2433 int col;
2435 if (text)
2436 col = draw_chars(view, type, text, max - 1, trim);
2437 else
2438 col = draw_space(view, type, max - 1, max - 1);
2440 view->col += col;
2441 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2442 return view->width + view->yoffset <= view->col;
2443 }
2445 static bool
2446 draw_date(struct view *view, struct time *time)
2447 {
2448 const char *date = mkdate(time, opt_date);
2449 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2451 return draw_field(view, LINE_DATE, date, cols, FALSE);
2452 }
2454 static bool
2455 draw_author(struct view *view, const char *author)
2456 {
2457 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2458 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2460 if (abbreviate && author)
2461 author = get_author_initials(author);
2463 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2464 }
2466 static bool
2467 draw_mode(struct view *view, mode_t mode)
2468 {
2469 const char *str;
2471 if (S_ISDIR(mode))
2472 str = "drwxr-xr-x";
2473 else if (S_ISLNK(mode))
2474 str = "lrwxrwxrwx";
2475 else if (S_ISGITLINK(mode))
2476 str = "m---------";
2477 else if (S_ISREG(mode) && mode & S_IXUSR)
2478 str = "-rwxr-xr-x";
2479 else if (S_ISREG(mode))
2480 str = "-rw-r--r--";
2481 else
2482 str = "----------";
2484 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2485 }
2487 static bool
2488 draw_lineno(struct view *view, unsigned int lineno)
2489 {
2490 char number[10];
2491 int digits3 = view->digits < 3 ? 3 : view->digits;
2492 int max = MIN(view->width + view->yoffset - view->col, digits3);
2493 char *text = NULL;
2494 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2496 lineno += view->offset + 1;
2497 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2498 static char fmt[] = "%1ld";
2500 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2501 if (string_format(number, fmt, lineno))
2502 text = number;
2503 }
2504 if (text)
2505 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2506 else
2507 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2508 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2509 }
2511 static bool
2512 draw_view_line(struct view *view, unsigned int lineno)
2513 {
2514 struct line *line;
2515 bool selected = (view->offset + lineno == view->lineno);
2517 assert(view_is_displayed(view));
2519 if (view->offset + lineno >= view->lines)
2520 return FALSE;
2522 line = &view->line[view->offset + lineno];
2524 wmove(view->win, lineno, 0);
2525 if (line->cleareol)
2526 wclrtoeol(view->win);
2527 view->col = 0;
2528 view->curline = line;
2529 view->curtype = LINE_NONE;
2530 line->selected = FALSE;
2531 line->dirty = line->cleareol = 0;
2533 if (selected) {
2534 set_view_attr(view, LINE_CURSOR);
2535 line->selected = TRUE;
2536 view->ops->select(view, line);
2537 }
2539 return view->ops->draw(view, line, lineno);
2540 }
2542 static void
2543 redraw_view_dirty(struct view *view)
2544 {
2545 bool dirty = FALSE;
2546 int lineno;
2548 for (lineno = 0; lineno < view->height; lineno++) {
2549 if (view->offset + lineno >= view->lines)
2550 break;
2551 if (!view->line[view->offset + lineno].dirty)
2552 continue;
2553 dirty = TRUE;
2554 if (!draw_view_line(view, lineno))
2555 break;
2556 }
2558 if (!dirty)
2559 return;
2560 wnoutrefresh(view->win);
2561 }
2563 static void
2564 redraw_view_from(struct view *view, int lineno)
2565 {
2566 assert(0 <= lineno && lineno < view->height);
2568 for (; lineno < view->height; lineno++) {
2569 if (!draw_view_line(view, lineno))
2570 break;
2571 }
2573 wnoutrefresh(view->win);
2574 }
2576 static void
2577 redraw_view(struct view *view)
2578 {
2579 werase(view->win);
2580 redraw_view_from(view, 0);
2581 }
2584 static void
2585 update_view_title(struct view *view)
2586 {
2587 char buf[SIZEOF_STR];
2588 char state[SIZEOF_STR];
2589 size_t bufpos = 0, statelen = 0;
2591 assert(view_is_displayed(view));
2593 if (view->type != VIEW_STATUS && view->lines) {
2594 unsigned int view_lines = view->offset + view->height;
2595 unsigned int lines = view->lines
2596 ? MIN(view_lines, view->lines) * 100 / view->lines
2597 : 0;
2599 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2600 view->ops->type,
2601 view->lineno + 1,
2602 view->lines,
2603 lines);
2605 }
2607 if (view->pipe) {
2608 time_t secs = time(NULL) - view->start_time;
2610 /* Three git seconds are a long time ... */
2611 if (secs > 2)
2612 string_format_from(state, &statelen, " loading %lds", secs);
2613 }
2615 string_format_from(buf, &bufpos, "[%s]", view->name);
2616 if (*view->ref && bufpos < view->width) {
2617 size_t refsize = strlen(view->ref);
2618 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2620 if (minsize < view->width)
2621 refsize = view->width - minsize + 7;
2622 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2623 }
2625 if (statelen && bufpos < view->width) {
2626 string_format_from(buf, &bufpos, "%s", state);
2627 }
2629 if (view == display[current_view])
2630 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2631 else
2632 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2634 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2635 wclrtoeol(view->title);
2636 wnoutrefresh(view->title);
2637 }
2639 static int
2640 apply_step(double step, int value)
2641 {
2642 if (step >= 1)
2643 return (int) step;
2644 value *= step + 0.01;
2645 return value ? value : 1;
2646 }
2648 static void
2649 resize_display(void)
2650 {
2651 int offset, i;
2652 struct view *base = display[0];
2653 struct view *view = display[1] ? display[1] : display[0];
2655 /* Setup window dimensions */
2657 getmaxyx(stdscr, base->height, base->width);
2659 /* Make room for the status window. */
2660 base->height -= 1;
2662 if (view != base) {
2663 /* Horizontal split. */
2664 view->width = base->width;
2665 view->height = apply_step(opt_scale_split_view, base->height);
2666 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2667 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2668 base->height -= view->height;
2670 /* Make room for the title bar. */
2671 view->height -= 1;
2672 }
2674 /* Make room for the title bar. */
2675 base->height -= 1;
2677 offset = 0;
2679 foreach_displayed_view (view, i) {
2680 if (!view->win) {
2681 view->win = newwin(view->height, 0, offset, 0);
2682 if (!view->win)
2683 die("Failed to create %s view", view->name);
2685 scrollok(view->win, FALSE);
2687 view->title = newwin(1, 0, offset + view->height, 0);
2688 if (!view->title)
2689 die("Failed to create title window");
2691 } else {
2692 wresize(view->win, view->height, view->width);
2693 mvwin(view->win, offset, 0);
2694 mvwin(view->title, offset + view->height, 0);
2695 }
2697 offset += view->height + 1;
2698 }
2699 }
2701 static void
2702 redraw_display(bool clear)
2703 {
2704 struct view *view;
2705 int i;
2707 foreach_displayed_view (view, i) {
2708 if (clear)
2709 wclear(view->win);
2710 redraw_view(view);
2711 update_view_title(view);
2712 }
2713 }
2715 static void
2716 toggle_enum_option_do(unsigned int *opt, const char *help,
2717 const struct enum_map *map, size_t size)
2718 {
2719 *opt = (*opt + 1) % size;
2720 redraw_display(FALSE);
2721 report("Displaying %s %s", enum_name(map[*opt]), help);
2722 }
2724 #define toggle_enum_option(opt, help, map) \
2725 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2727 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2728 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2730 static void
2731 toggle_view_option(bool *option, const char *help)
2732 {
2733 *option = !*option;
2734 redraw_display(FALSE);
2735 report("%sabling %s", *option ? "En" : "Dis", help);
2736 }
2738 static void
2739 open_option_menu(void)
2740 {
2741 const struct menu_item menu[] = {
2742 { '.', "line numbers", &opt_line_number },
2743 { 'D', "date display", &opt_date },
2744 { 'A', "author display", &opt_author },
2745 { 'g', "revision graph display", &opt_rev_graph },
2746 { 'F', "reference display", &opt_show_refs },
2747 { 0 }
2748 };
2749 int selected = 0;
2751 if (prompt_menu("Toggle option", menu, &selected)) {
2752 if (menu[selected].data == &opt_date)
2753 toggle_date();
2754 else if (menu[selected].data == &opt_author)
2755 toggle_author();
2756 else
2757 toggle_view_option(menu[selected].data, menu[selected].text);
2758 }
2759 }
2761 static void
2762 maximize_view(struct view *view)
2763 {
2764 memset(display, 0, sizeof(display));
2765 current_view = 0;
2766 display[current_view] = view;
2767 resize_display();
2768 redraw_display(FALSE);
2769 report("");
2770 }
2773 /*
2774 * Navigation
2775 */
2777 static bool
2778 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2779 {
2780 if (lineno >= view->lines)
2781 lineno = view->lines > 0 ? view->lines - 1 : 0;
2783 if (offset > lineno || offset + view->height <= lineno) {
2784 unsigned long half = view->height / 2;
2786 if (lineno > half)
2787 offset = lineno - half;
2788 else
2789 offset = 0;
2790 }
2792 if (offset != view->offset || lineno != view->lineno) {
2793 view->offset = offset;
2794 view->lineno = lineno;
2795 return TRUE;
2796 }
2798 return FALSE;
2799 }
2801 /* Scrolling backend */
2802 static void
2803 do_scroll_view(struct view *view, int lines)
2804 {
2805 bool redraw_current_line = FALSE;
2807 /* The rendering expects the new offset. */
2808 view->offset += lines;
2810 assert(0 <= view->offset && view->offset < view->lines);
2811 assert(lines);
2813 /* Move current line into the view. */
2814 if (view->lineno < view->offset) {
2815 view->lineno = view->offset;
2816 redraw_current_line = TRUE;
2817 } else if (view->lineno >= view->offset + view->height) {
2818 view->lineno = view->offset + view->height - 1;
2819 redraw_current_line = TRUE;
2820 }
2822 assert(view->offset <= view->lineno && view->lineno < view->lines);
2824 /* Redraw the whole screen if scrolling is pointless. */
2825 if (view->height < ABS(lines)) {
2826 redraw_view(view);
2828 } else {
2829 int line = lines > 0 ? view->height - lines : 0;
2830 int end = line + ABS(lines);
2832 scrollok(view->win, TRUE);
2833 wscrl(view->win, lines);
2834 scrollok(view->win, FALSE);
2836 while (line < end && draw_view_line(view, line))
2837 line++;
2839 if (redraw_current_line)
2840 draw_view_line(view, view->lineno - view->offset);
2841 wnoutrefresh(view->win);
2842 }
2844 view->has_scrolled = TRUE;
2845 report("");
2846 }
2848 /* Scroll frontend */
2849 static void
2850 scroll_view(struct view *view, enum request request)
2851 {
2852 int lines = 1;
2854 assert(view_is_displayed(view));
2856 switch (request) {
2857 case REQ_SCROLL_LEFT:
2858 if (view->yoffset == 0) {
2859 report("Cannot scroll beyond the first column");
2860 return;
2861 }
2862 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2863 view->yoffset = 0;
2864 else
2865 view->yoffset -= apply_step(opt_hscroll, view->width);
2866 redraw_view_from(view, 0);
2867 report("");
2868 return;
2869 case REQ_SCROLL_RIGHT:
2870 view->yoffset += apply_step(opt_hscroll, view->width);
2871 redraw_view(view);
2872 report("");
2873 return;
2874 case REQ_SCROLL_PAGE_DOWN:
2875 lines = view->height;
2876 case REQ_SCROLL_LINE_DOWN:
2877 if (view->offset + lines > view->lines)
2878 lines = view->lines - view->offset;
2880 if (lines == 0 || view->offset + view->height >= view->lines) {
2881 report("Cannot scroll beyond the last line");
2882 return;
2883 }
2884 break;
2886 case REQ_SCROLL_PAGE_UP:
2887 lines = view->height;
2888 case REQ_SCROLL_LINE_UP:
2889 if (lines > view->offset)
2890 lines = view->offset;
2892 if (lines == 0) {
2893 report("Cannot scroll beyond the first line");
2894 return;
2895 }
2897 lines = -lines;
2898 break;
2900 default:
2901 die("request %d not handled in switch", request);
2902 }
2904 do_scroll_view(view, lines);
2905 }
2907 /* Cursor moving */
2908 static void
2909 move_view(struct view *view, enum request request)
2910 {
2911 int scroll_steps = 0;
2912 int steps;
2914 switch (request) {
2915 case REQ_MOVE_FIRST_LINE:
2916 steps = -view->lineno;
2917 break;
2919 case REQ_MOVE_LAST_LINE:
2920 steps = view->lines - view->lineno - 1;
2921 break;
2923 case REQ_MOVE_PAGE_UP:
2924 steps = view->height > view->lineno
2925 ? -view->lineno : -view->height;
2926 break;
2928 case REQ_MOVE_PAGE_DOWN:
2929 steps = view->lineno + view->height >= view->lines
2930 ? view->lines - view->lineno - 1 : view->height;
2931 break;
2933 case REQ_MOVE_UP:
2934 steps = -1;
2935 break;
2937 case REQ_MOVE_DOWN:
2938 steps = 1;
2939 break;
2941 default:
2942 die("request %d not handled in switch", request);
2943 }
2945 if (steps <= 0 && view->lineno == 0) {
2946 report("Cannot move beyond the first line");
2947 return;
2949 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2950 report("Cannot move beyond the last line");
2951 return;
2952 }
2954 /* Move the current line */
2955 view->lineno += steps;
2956 assert(0 <= view->lineno && view->lineno < view->lines);
2958 /* Check whether the view needs to be scrolled */
2959 if (view->lineno < view->offset ||
2960 view->lineno >= view->offset + view->height) {
2961 scroll_steps = steps;
2962 if (steps < 0 && -steps > view->offset) {
2963 scroll_steps = -view->offset;
2965 } else if (steps > 0) {
2966 if (view->lineno == view->lines - 1 &&
2967 view->lines > view->height) {
2968 scroll_steps = view->lines - view->offset - 1;
2969 if (scroll_steps >= view->height)
2970 scroll_steps -= view->height - 1;
2971 }
2972 }
2973 }
2975 if (!view_is_displayed(view)) {
2976 view->offset += scroll_steps;
2977 assert(0 <= view->offset && view->offset < view->lines);
2978 view->ops->select(view, &view->line[view->lineno]);
2979 return;
2980 }
2982 /* Repaint the old "current" line if we be scrolling */
2983 if (ABS(steps) < view->height)
2984 draw_view_line(view, view->lineno - steps - view->offset);
2986 if (scroll_steps) {
2987 do_scroll_view(view, scroll_steps);
2988 return;
2989 }
2991 /* Draw the current line */
2992 draw_view_line(view, view->lineno - view->offset);
2994 wnoutrefresh(view->win);
2995 report("");
2996 }
2999 /*
3000 * Searching
3001 */
3003 static void search_view(struct view *view, enum request request);
3005 static bool
3006 grep_text(struct view *view, const char *text[])
3007 {
3008 regmatch_t pmatch;
3009 size_t i;
3011 for (i = 0; text[i]; i++)
3012 if (*text[i] &&
3013 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3014 return TRUE;
3015 return FALSE;
3016 }
3018 static void
3019 select_view_line(struct view *view, unsigned long lineno)
3020 {
3021 unsigned long old_lineno = view->lineno;
3022 unsigned long old_offset = view->offset;
3024 if (goto_view_line(view, view->offset, lineno)) {
3025 if (view_is_displayed(view)) {
3026 if (old_offset != view->offset) {
3027 redraw_view(view);
3028 } else {
3029 draw_view_line(view, old_lineno - view->offset);
3030 draw_view_line(view, view->lineno - view->offset);
3031 wnoutrefresh(view->win);
3032 }
3033 } else {
3034 view->ops->select(view, &view->line[view->lineno]);
3035 }
3036 }
3037 }
3039 static void
3040 find_next(struct view *view, enum request request)
3041 {
3042 unsigned long lineno = view->lineno;
3043 int direction;
3045 if (!*view->grep) {
3046 if (!*opt_search)
3047 report("No previous search");
3048 else
3049 search_view(view, request);
3050 return;
3051 }
3053 switch (request) {
3054 case REQ_SEARCH:
3055 case REQ_FIND_NEXT:
3056 direction = 1;
3057 break;
3059 case REQ_SEARCH_BACK:
3060 case REQ_FIND_PREV:
3061 direction = -1;
3062 break;
3064 default:
3065 return;
3066 }
3068 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3069 lineno += direction;
3071 /* Note, lineno is unsigned long so will wrap around in which case it
3072 * will become bigger than view->lines. */
3073 for (; lineno < view->lines; lineno += direction) {
3074 if (view->ops->grep(view, &view->line[lineno])) {
3075 select_view_line(view, lineno);
3076 report("Line %ld matches '%s'", lineno + 1, view->grep);
3077 return;
3078 }
3079 }
3081 report("No match found for '%s'", view->grep);
3082 }
3084 static void
3085 search_view(struct view *view, enum request request)
3086 {
3087 int regex_err;
3089 if (view->regex) {
3090 regfree(view->regex);
3091 *view->grep = 0;
3092 } else {
3093 view->regex = calloc(1, sizeof(*view->regex));
3094 if (!view->regex)
3095 return;
3096 }
3098 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3099 if (regex_err != 0) {
3100 char buf[SIZEOF_STR] = "unknown error";
3102 regerror(regex_err, view->regex, buf, sizeof(buf));
3103 report("Search failed: %s", buf);
3104 return;
3105 }
3107 string_copy(view->grep, opt_search);
3109 find_next(view, request);
3110 }
3112 /*
3113 * Incremental updating
3114 */
3116 static void
3117 reset_view(struct view *view)
3118 {
3119 int i;
3121 for (i = 0; i < view->lines; i++)
3122 free(view->line[i].data);
3123 free(view->line);
3125 view->p_offset = view->offset;
3126 view->p_yoffset = view->yoffset;
3127 view->p_lineno = view->lineno;
3129 view->line = NULL;
3130 view->offset = 0;
3131 view->yoffset = 0;
3132 view->lines = 0;
3133 view->lineno = 0;
3134 view->vid[0] = 0;
3135 view->update_secs = 0;
3136 }
3138 static void
3139 free_argv(const char *argv[])
3140 {
3141 int argc;
3143 for (argc = 0; argv[argc]; argc++)
3144 free((void *) argv[argc]);
3145 }
3147 static const char *
3148 format_arg(const char *name)
3149 {
3150 static struct {
3151 const char *name;
3152 size_t namelen;
3153 const char *value;
3154 const char *value_if_empty;
3155 } vars[] = {
3156 #define FORMAT_VAR(name, value, value_if_empty) \
3157 { name, STRING_SIZE(name), value, value_if_empty }
3158 FORMAT_VAR("%(directory)", opt_path, ""),
3159 FORMAT_VAR("%(file)", opt_file, ""),
3160 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3161 FORMAT_VAR("%(head)", ref_head, ""),
3162 FORMAT_VAR("%(commit)", ref_commit, ""),
3163 FORMAT_VAR("%(blob)", ref_blob, ""),
3164 FORMAT_VAR("%(branch)", ref_branch, ""),
3165 };
3166 int i;
3168 for (i = 0; i < ARRAY_SIZE(vars); i++)
3169 if (!strncmp(name, vars[i].name, vars[i].namelen))
3170 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3172 report("Unknown replacement: `%s`", name);
3173 return NULL;
3174 }
3176 static bool
3177 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3178 {
3179 char buf[SIZEOF_STR];
3180 int argc;
3181 bool noreplace = flags == FORMAT_NONE;
3183 free_argv(dst_argv);
3185 for (argc = 0; src_argv[argc]; argc++) {
3186 const char *arg = src_argv[argc];
3187 size_t bufpos = 0;
3189 while (arg) {
3190 char *next = strstr(arg, "%(");
3191 int len = next - arg;
3192 const char *value;
3194 if (!next || noreplace) {
3195 len = strlen(arg);
3196 value = "";
3198 } else {
3199 value = format_arg(next);
3201 if (!value) {
3202 return FALSE;
3203 }
3204 }
3206 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3207 return FALSE;
3209 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3210 }
3212 dst_argv[argc] = strdup(buf);
3213 if (!dst_argv[argc])
3214 break;
3215 }
3217 dst_argv[argc] = NULL;
3219 return src_argv[argc] == NULL;
3220 }
3222 static bool
3223 restore_view_position(struct view *view)
3224 {
3225 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3226 return FALSE;
3228 /* Changing the view position cancels the restoring. */
3229 /* FIXME: Changing back to the first line is not detected. */
3230 if (view->offset != 0 || view->lineno != 0) {
3231 view->p_restore = FALSE;
3232 return FALSE;
3233 }
3235 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3236 view_is_displayed(view))
3237 werase(view->win);
3239 view->yoffset = view->p_yoffset;
3240 view->p_restore = FALSE;
3242 return TRUE;
3243 }
3245 static void
3246 end_update(struct view *view, bool force)
3247 {
3248 if (!view->pipe)
3249 return;
3250 while (!view->ops->read(view, NULL))
3251 if (!force)
3252 return;
3253 if (force)
3254 io_kill(view->pipe);
3255 io_done(view->pipe);
3256 view->pipe = NULL;
3257 }
3259 static void
3260 setup_update(struct view *view, const char *vid)
3261 {
3262 reset_view(view);
3263 string_copy_rev(view->vid, vid);
3264 view->pipe = &view->io;
3265 view->start_time = time(NULL);
3266 }
3268 static bool
3269 prepare_update(struct view *view, const char *argv[], const char *dir)
3270 {
3271 if (view->pipe)
3272 end_update(view, TRUE);
3273 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3274 }
3276 static bool
3277 prepare_update_file(struct view *view, const char *name)
3278 {
3279 if (view->pipe)
3280 end_update(view, TRUE);
3281 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3282 }
3284 static bool
3285 begin_update(struct view *view, bool refresh)
3286 {
3287 if (view->pipe)
3288 end_update(view, TRUE);
3290 if (!refresh) {
3291 if (view->ops->prepare) {
3292 if (!view->ops->prepare(view))
3293 return FALSE;
3294 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3295 return FALSE;
3296 }
3298 /* Put the current ref_* value to the view title ref
3299 * member. This is needed by the blob view. Most other
3300 * views sets it automatically after loading because the
3301 * first line is a commit line. */
3302 string_copy_rev(view->ref, view->id);
3303 }
3305 if (!io_start(&view->io))
3306 return FALSE;
3308 setup_update(view, view->id);
3310 return TRUE;
3311 }
3313 static bool
3314 update_view(struct view *view)
3315 {
3316 char out_buffer[BUFSIZ * 2];
3317 char *line;
3318 /* Clear the view and redraw everything since the tree sorting
3319 * might have rearranged things. */
3320 bool redraw = view->lines == 0;
3321 bool can_read = TRUE;
3323 if (!view->pipe)
3324 return TRUE;
3326 if (!io_can_read(view->pipe)) {
3327 if (view->lines == 0 && view_is_displayed(view)) {
3328 time_t secs = time(NULL) - view->start_time;
3330 if (secs > 1 && secs > view->update_secs) {
3331 if (view->update_secs == 0)
3332 redraw_view(view);
3333 update_view_title(view);
3334 view->update_secs = secs;
3335 }
3336 }
3337 return TRUE;
3338 }
3340 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3341 if (opt_iconv_in != ICONV_NONE) {
3342 ICONV_CONST char *inbuf = line;
3343 size_t inlen = strlen(line) + 1;
3345 char *outbuf = out_buffer;
3346 size_t outlen = sizeof(out_buffer);
3348 size_t ret;
3350 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3351 if (ret != (size_t) -1)
3352 line = out_buffer;
3353 }
3355 if (!view->ops->read(view, line)) {
3356 report("Allocation failure");
3357 end_update(view, TRUE);
3358 return FALSE;
3359 }
3360 }
3362 {
3363 unsigned long lines = view->lines;
3364 int digits;
3366 for (digits = 0; lines; digits++)
3367 lines /= 10;
3369 /* Keep the displayed view in sync with line number scaling. */
3370 if (digits != view->digits) {
3371 view->digits = digits;
3372 if (opt_line_number || view->type == VIEW_BLAME)
3373 redraw = TRUE;
3374 }
3375 }
3377 if (io_error(view->pipe)) {
3378 report("Failed to read: %s", io_strerror(view->pipe));
3379 end_update(view, TRUE);
3381 } else if (io_eof(view->pipe)) {
3382 report("");
3383 end_update(view, FALSE);
3384 }
3386 if (restore_view_position(view))
3387 redraw = TRUE;
3389 if (!view_is_displayed(view))
3390 return TRUE;
3392 if (redraw)
3393 redraw_view_from(view, 0);
3394 else
3395 redraw_view_dirty(view);
3397 /* Update the title _after_ the redraw so that if the redraw picks up a
3398 * commit reference in view->ref it'll be available here. */
3399 update_view_title(view);
3400 return TRUE;
3401 }
3403 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3405 static struct line *
3406 add_line_data(struct view *view, void *data, enum line_type type)
3407 {
3408 struct line *line;
3410 if (!realloc_lines(&view->line, view->lines, 1))
3411 return NULL;
3413 line = &view->line[view->lines++];
3414 memset(line, 0, sizeof(*line));
3415 line->type = type;
3416 line->data = data;
3417 line->dirty = 1;
3419 return line;
3420 }
3422 static struct line *
3423 add_line_text(struct view *view, const char *text, enum line_type type)
3424 {
3425 char *data = text ? strdup(text) : NULL;
3427 return data ? add_line_data(view, data, type) : NULL;
3428 }
3430 static struct line *
3431 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3432 {
3433 char buf[SIZEOF_STR];
3434 va_list args;
3436 va_start(args, fmt);
3437 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3438 buf[0] = 0;
3439 va_end(args);
3441 return buf[0] ? add_line_text(view, buf, type) : NULL;
3442 }
3444 /*
3445 * View opening
3446 */
3448 enum open_flags {
3449 OPEN_DEFAULT = 0, /* Use default view switching. */
3450 OPEN_SPLIT = 1, /* Split current view. */
3451 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3452 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3453 OPEN_PREPARED = 32, /* Open already prepared command. */
3454 };
3456 static void
3457 open_view(struct view *prev, enum request request, enum open_flags flags)
3458 {
3459 bool split = !!(flags & OPEN_SPLIT);
3460 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3461 bool nomaximize = !!(flags & OPEN_REFRESH);
3462 struct view *view = VIEW(request);
3463 int nviews = displayed_views();
3464 struct view *base_view = display[0];
3466 if (view == prev && nviews == 1 && !reload) {
3467 report("Already in %s view", view->name);
3468 return;
3469 }
3471 if (view->git_dir && !opt_git_dir[0]) {
3472 report("The %s view is disabled in pager view", view->name);
3473 return;
3474 }
3476 if (split) {
3477 display[1] = view;
3478 current_view = 1;
3479 view->parent = prev;
3480 } else if (!nomaximize) {
3481 /* Maximize the current view. */
3482 memset(display, 0, sizeof(display));
3483 current_view = 0;
3484 display[current_view] = view;
3485 }
3487 /* No prev signals that this is the first loaded view. */
3488 if (prev && view != prev) {
3489 view->prev = prev;
3490 }
3492 /* Resize the view when switching between split- and full-screen,
3493 * or when switching between two different full-screen views. */
3494 if (nviews != displayed_views() ||
3495 (nviews == 1 && base_view != display[0]))
3496 resize_display();
3498 if (view->ops->open) {
3499 if (view->pipe)
3500 end_update(view, TRUE);
3501 if (!view->ops->open(view)) {
3502 report("Failed to load %s view", view->name);
3503 return;
3504 }
3505 restore_view_position(view);
3507 } else if ((reload || strcmp(view->vid, view->id)) &&
3508 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3509 report("Failed to load %s view", view->name);
3510 return;
3511 }
3513 if (split && prev->lineno - prev->offset >= prev->height) {
3514 /* Take the title line into account. */
3515 int lines = prev->lineno - prev->offset - prev->height + 1;
3517 /* Scroll the view that was split if the current line is
3518 * outside the new limited view. */
3519 do_scroll_view(prev, lines);
3520 }
3522 if (prev && view != prev && split && view_is_displayed(prev)) {
3523 /* "Blur" the previous view. */
3524 update_view_title(prev);
3525 }
3527 if (view->pipe && view->lines == 0) {
3528 /* Clear the old view and let the incremental updating refill
3529 * the screen. */
3530 werase(view->win);
3531 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3532 report("");
3533 } else if (view_is_displayed(view)) {
3534 redraw_view(view);
3535 report("");
3536 }
3537 }
3539 static void
3540 open_external_viewer(const char *argv[], const char *dir)
3541 {
3542 def_prog_mode(); /* save current tty modes */
3543 endwin(); /* restore original tty modes */
3544 io_run_fg(argv, dir);
3545 fprintf(stderr, "Press Enter to continue");
3546 getc(opt_tty);
3547 reset_prog_mode();
3548 redraw_display(TRUE);
3549 }
3551 static void
3552 open_mergetool(const char *file)
3553 {
3554 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3556 open_external_viewer(mergetool_argv, opt_cdup);
3557 }
3559 static void
3560 open_editor(const char *file)
3561 {
3562 const char *editor_argv[] = { "vi", file, NULL };
3563 const char *editor;
3565 editor = getenv("GIT_EDITOR");
3566 if (!editor && *opt_editor)
3567 editor = opt_editor;
3568 if (!editor)
3569 editor = getenv("VISUAL");
3570 if (!editor)
3571 editor = getenv("EDITOR");
3572 if (!editor)
3573 editor = "vi";
3575 editor_argv[0] = editor;
3576 open_external_viewer(editor_argv, opt_cdup);
3577 }
3579 static void
3580 open_run_request(enum request request)
3581 {
3582 struct run_request *req = get_run_request(request);
3583 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3585 if (!req) {
3586 report("Unknown run request");
3587 return;
3588 }
3590 if (format_argv(argv, req->argv, FORMAT_ALL))
3591 open_external_viewer(argv, NULL);
3592 free_argv(argv);
3593 }
3595 /*
3596 * User request switch noodle
3597 */
3599 static int
3600 view_driver(struct view *view, enum request request)
3601 {
3602 int i;
3604 if (request == REQ_NONE)
3605 return TRUE;
3607 if (request > REQ_NONE) {
3608 open_run_request(request);
3609 /* FIXME: When all views can refresh always do this. */
3610 if (view->refresh)
3611 request = REQ_REFRESH;
3612 else
3613 return TRUE;
3614 }
3616 if (view && view->lines) {
3617 request = view->ops->request(view, request, &view->line[view->lineno]);
3618 if (request == REQ_NONE)
3619 return TRUE;
3620 }
3622 switch (request) {
3623 case REQ_MOVE_UP:
3624 case REQ_MOVE_DOWN:
3625 case REQ_MOVE_PAGE_UP:
3626 case REQ_MOVE_PAGE_DOWN:
3627 case REQ_MOVE_FIRST_LINE:
3628 case REQ_MOVE_LAST_LINE:
3629 move_view(view, request);
3630 break;
3632 case REQ_SCROLL_LEFT:
3633 case REQ_SCROLL_RIGHT:
3634 case REQ_SCROLL_LINE_DOWN:
3635 case REQ_SCROLL_LINE_UP:
3636 case REQ_SCROLL_PAGE_DOWN:
3637 case REQ_SCROLL_PAGE_UP:
3638 scroll_view(view, request);
3639 break;
3641 case REQ_VIEW_BLAME:
3642 if (!opt_file[0]) {
3643 report("No file chosen, press %s to open tree view",
3644 get_key(view->keymap, REQ_VIEW_TREE));
3645 break;
3646 }
3647 open_view(view, request, OPEN_DEFAULT);
3648 break;
3650 case REQ_VIEW_BLOB:
3651 if (!ref_blob[0]) {
3652 report("No file chosen, press %s to open tree view",
3653 get_key(view->keymap, REQ_VIEW_TREE));
3654 break;
3655 }
3656 open_view(view, request, OPEN_DEFAULT);
3657 break;
3659 case REQ_VIEW_PAGER:
3660 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3661 report("No pager content, press %s to run command from prompt",
3662 get_key(view->keymap, REQ_PROMPT));
3663 break;
3664 }
3665 open_view(view, request, OPEN_DEFAULT);
3666 break;
3668 case REQ_VIEW_STAGE:
3669 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3670 report("No stage content, press %s to open the status view and choose file",
3671 get_key(view->keymap, REQ_VIEW_STATUS));
3672 break;
3673 }
3674 open_view(view, request, OPEN_DEFAULT);
3675 break;
3677 case REQ_VIEW_STATUS:
3678 if (opt_is_inside_work_tree == FALSE) {
3679 report("The status view requires a working tree");
3680 break;
3681 }
3682 open_view(view, request, OPEN_DEFAULT);
3683 break;
3685 case REQ_VIEW_MAIN:
3686 case REQ_VIEW_DIFF:
3687 case REQ_VIEW_LOG:
3688 case REQ_VIEW_TREE:
3689 case REQ_VIEW_HELP:
3690 case REQ_VIEW_BRANCH:
3691 open_view(view, request, OPEN_DEFAULT);
3692 break;
3694 case REQ_NEXT:
3695 case REQ_PREVIOUS:
3696 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3698 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3699 view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3700 view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3701 view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3702 view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3703 int line;
3705 view = view->parent;
3706 line = view->lineno;
3707 move_view(view, request);
3708 if (view_is_displayed(view))
3709 update_view_title(view);
3710 if (line != view->lineno)
3711 view->ops->request(view, REQ_ENTER,
3712 &view->line[view->lineno]);
3714 } else {
3715 move_view(view, request);
3716 }
3717 break;
3719 case REQ_VIEW_NEXT:
3720 {
3721 int nviews = displayed_views();
3722 int next_view = (current_view + 1) % nviews;
3724 if (next_view == current_view) {
3725 report("Only one view is displayed");
3726 break;
3727 }
3729 current_view = next_view;
3730 /* Blur out the title of the previous view. */
3731 update_view_title(view);
3732 report("");
3733 break;
3734 }
3735 case REQ_REFRESH:
3736 report("Refreshing is not yet supported for the %s view", view->name);
3737 break;
3739 case REQ_MAXIMIZE:
3740 if (displayed_views() == 2)
3741 maximize_view(view);
3742 break;
3744 case REQ_OPTIONS:
3745 open_option_menu();
3746 break;
3748 case REQ_TOGGLE_LINENO:
3749 toggle_view_option(&opt_line_number, "line numbers");
3750 break;
3752 case REQ_TOGGLE_DATE:
3753 toggle_date();
3754 break;
3756 case REQ_TOGGLE_AUTHOR:
3757 toggle_author();
3758 break;
3760 case REQ_TOGGLE_REV_GRAPH:
3761 toggle_view_option(&opt_rev_graph, "revision graph display");
3762 break;
3764 case REQ_TOGGLE_REFS:
3765 toggle_view_option(&opt_show_refs, "reference display");
3766 break;
3768 case REQ_TOGGLE_SORT_FIELD:
3769 case REQ_TOGGLE_SORT_ORDER:
3770 report("Sorting is not yet supported for the %s view", view->name);
3771 break;
3773 case REQ_SEARCH:
3774 case REQ_SEARCH_BACK:
3775 search_view(view, request);
3776 break;
3778 case REQ_FIND_NEXT:
3779 case REQ_FIND_PREV:
3780 find_next(view, request);
3781 break;
3783 case REQ_STOP_LOADING:
3784 foreach_view(view, i) {
3785 if (view->pipe)
3786 report("Stopped loading the %s view", view->name),
3787 end_update(view, TRUE);
3788 }
3789 break;
3791 case REQ_SHOW_VERSION:
3792 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3793 return TRUE;
3795 case REQ_SCREEN_REDRAW:
3796 redraw_display(TRUE);
3797 break;
3799 case REQ_EDIT:
3800 report("Nothing to edit");
3801 break;
3803 case REQ_ENTER:
3804 report("Nothing to enter");
3805 break;
3807 case REQ_VIEW_CLOSE:
3808 /* XXX: Mark closed views by letting view->prev point to the
3809 * view itself. Parents to closed view should never be
3810 * followed. */
3811 if (view->prev && view->prev != view) {
3812 maximize_view(view->prev);
3813 view->prev = view;
3814 break;
3815 }
3816 /* Fall-through */
3817 case REQ_QUIT:
3818 return FALSE;
3820 default:
3821 report("Unknown key, press %s for help",
3822 get_key(view->keymap, REQ_VIEW_HELP));
3823 return TRUE;
3824 }
3826 return TRUE;
3827 }
3830 /*
3831 * View backend utilities
3832 */
3834 enum sort_field {
3835 ORDERBY_NAME,
3836 ORDERBY_DATE,
3837 ORDERBY_AUTHOR,
3838 };
3840 struct sort_state {
3841 const enum sort_field *fields;
3842 size_t size, current;
3843 bool reverse;
3844 };
3846 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3847 #define get_sort_field(state) ((state).fields[(state).current])
3848 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3850 static void
3851 sort_view(struct view *view, enum request request, struct sort_state *state,
3852 int (*compare)(const void *, const void *))
3853 {
3854 switch (request) {
3855 case REQ_TOGGLE_SORT_FIELD:
3856 state->current = (state->current + 1) % state->size;
3857 break;
3859 case REQ_TOGGLE_SORT_ORDER:
3860 state->reverse = !state->reverse;
3861 break;
3862 default:
3863 die("Not a sort request");
3864 }
3866 qsort(view->line, view->lines, sizeof(*view->line), compare);
3867 redraw_view(view);
3868 }
3870 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3872 /* Small author cache to reduce memory consumption. It uses binary
3873 * search to lookup or find place to position new entries. No entries
3874 * are ever freed. */
3875 static const char *
3876 get_author(const char *name)
3877 {
3878 static const char **authors;
3879 static size_t authors_size;
3880 int from = 0, to = authors_size - 1;
3882 while (from <= to) {
3883 size_t pos = (to + from) / 2;
3884 int cmp = strcmp(name, authors[pos]);
3886 if (!cmp)
3887 return authors[pos];
3889 if (cmp < 0)
3890 to = pos - 1;
3891 else
3892 from = pos + 1;
3893 }
3895 if (!realloc_authors(&authors, authors_size, 1))
3896 return NULL;
3897 name = strdup(name);
3898 if (!name)
3899 return NULL;
3901 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3902 authors[from] = name;
3903 authors_size++;
3905 return name;
3906 }
3908 static void
3909 parse_timesec(struct time *time, const char *sec)
3910 {
3911 time->sec = (time_t) atol(sec);
3912 }
3914 static void
3915 parse_timezone(struct time *time, const char *zone)
3916 {
3917 long tz;
3919 tz = ('0' - zone[1]) * 60 * 60 * 10;
3920 tz += ('0' - zone[2]) * 60 * 60;
3921 tz += ('0' - zone[3]) * 60 * 10;
3922 tz += ('0' - zone[4]) * 60;
3924 if (zone[0] == '-')
3925 tz = -tz;
3927 time->tz = tz;
3928 time->sec -= tz;
3929 }
3931 /* Parse author lines where the name may be empty:
3932 * author <email@address.tld> 1138474660 +0100
3933 */
3934 static void
3935 parse_author_line(char *ident, const char **author, struct time *time)
3936 {
3937 char *nameend = strchr(ident, '<');
3938 char *emailend = strchr(ident, '>');
3940 if (nameend && emailend)
3941 *nameend = *emailend = 0;
3942 ident = chomp_string(ident);
3943 if (!*ident) {
3944 if (nameend)
3945 ident = chomp_string(nameend + 1);
3946 if (!*ident)
3947 ident = "Unknown";
3948 }
3950 *author = get_author(ident);
3952 /* Parse epoch and timezone */
3953 if (emailend && emailend[1] == ' ') {
3954 char *secs = emailend + 2;
3955 char *zone = strchr(secs, ' ');
3957 parse_timesec(time, secs);
3959 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3960 parse_timezone(time, zone + 1);
3961 }
3962 }
3964 static bool
3965 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3966 {
3967 char rev[SIZEOF_REV];
3968 const char *revlist_argv[] = {
3969 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3970 };
3971 struct menu_item *items;
3972 char text[SIZEOF_STR];
3973 bool ok = TRUE;
3974 int i;
3976 items = calloc(*parents + 1, sizeof(*items));
3977 if (!items)
3978 return FALSE;
3980 for (i = 0; i < *parents; i++) {
3981 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3982 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3983 !(items[i].text = strdup(text))) {
3984 ok = FALSE;
3985 break;
3986 }
3987 }
3989 if (ok) {
3990 *parents = 0;
3991 ok = prompt_menu("Select parent", items, parents);
3992 }
3993 for (i = 0; items[i].text; i++)
3994 free((char *) items[i].text);
3995 free(items);
3996 return ok;
3997 }
3999 static bool
4000 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4001 {
4002 char buf[SIZEOF_STR * 4];
4003 const char *revlist_argv[] = {
4004 "git", "log", "--no-color", "-1",
4005 "--pretty=format:%P", id, "--", path, NULL
4006 };
4007 int parents;
4009 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4010 (parents = strlen(buf) / 40) < 0) {
4011 report("Failed to get parent information");
4012 return FALSE;
4014 } else if (parents == 0) {
4015 if (path)
4016 report("Path '%s' does not exist in the parent", path);
4017 else
4018 report("The selected commit has no parents");
4019 return FALSE;
4020 }
4022 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
4023 return FALSE;
4025 string_copy_rev(rev, &buf[41 * parents]);
4026 return TRUE;
4027 }
4029 /*
4030 * Pager backend
4031 */
4033 static bool
4034 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4035 {
4036 char text[SIZEOF_STR];
4038 if (opt_line_number && draw_lineno(view, lineno))
4039 return TRUE;
4041 string_expand(text, sizeof(text), line->data, opt_tab_size);
4042 draw_text(view, line->type, text, TRUE);
4043 return TRUE;
4044 }
4046 static bool
4047 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4048 {
4049 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4050 char ref[SIZEOF_STR];
4052 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4053 return TRUE;
4055 /* This is the only fatal call, since it can "corrupt" the buffer. */
4056 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4057 return FALSE;
4059 return TRUE;
4060 }
4062 static void
4063 add_pager_refs(struct view *view, struct line *line)
4064 {
4065 char buf[SIZEOF_STR];
4066 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4067 struct ref_list *list;
4068 size_t bufpos = 0, i;
4069 const char *sep = "Refs: ";
4070 bool is_tag = FALSE;
4072 assert(line->type == LINE_COMMIT);
4074 list = get_ref_list(commit_id);
4075 if (!list) {
4076 if (view->type == VIEW_DIFF)
4077 goto try_add_describe_ref;
4078 return;
4079 }
4081 for (i = 0; i < list->size; i++) {
4082 struct ref *ref = list->refs[i];
4083 const char *fmt = ref->tag ? "%s[%s]" :
4084 ref->remote ? "%s<%s>" : "%s%s";
4086 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4087 return;
4088 sep = ", ";
4089 if (ref->tag)
4090 is_tag = TRUE;
4091 }
4093 if (!is_tag && view->type == VIEW_DIFF) {
4094 try_add_describe_ref:
4095 /* Add <tag>-g<commit_id> "fake" reference. */
4096 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4097 return;
4098 }
4100 if (bufpos == 0)
4101 return;
4103 add_line_text(view, buf, LINE_PP_REFS);
4104 }
4106 static bool
4107 pager_read(struct view *view, char *data)
4108 {
4109 struct line *line;
4111 if (!data)
4112 return TRUE;
4114 line = add_line_text(view, data, get_line_type(data));
4115 if (!line)
4116 return FALSE;
4118 if (line->type == LINE_COMMIT &&
4119 (view->type == VIEW_DIFF ||
4120 view->type == VIEW_LOG))
4121 add_pager_refs(view, line);
4123 return TRUE;
4124 }
4126 static enum request
4127 pager_request(struct view *view, enum request request, struct line *line)
4128 {
4129 int split = 0;
4131 if (request != REQ_ENTER)
4132 return request;
4134 if (line->type == LINE_COMMIT &&
4135 (view->type == VIEW_LOG ||
4136 view->type == VIEW_PAGER)) {
4137 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4138 split = 1;
4139 }
4141 /* Always scroll the view even if it was split. That way
4142 * you can use Enter to scroll through the log view and
4143 * split open each commit diff. */
4144 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4146 /* FIXME: A minor workaround. Scrolling the view will call report("")
4147 * but if we are scrolling a non-current view this won't properly
4148 * update the view title. */
4149 if (split)
4150 update_view_title(view);
4152 return REQ_NONE;
4153 }
4155 static bool
4156 pager_grep(struct view *view, struct line *line)
4157 {
4158 const char *text[] = { line->data, NULL };
4160 return grep_text(view, text);
4161 }
4163 static void
4164 pager_select(struct view *view, struct line *line)
4165 {
4166 if (line->type == LINE_COMMIT) {
4167 char *text = (char *)line->data + STRING_SIZE("commit ");
4169 if (view->type != VIEW_PAGER)
4170 string_copy_rev(view->ref, text);
4171 string_copy_rev(ref_commit, text);
4172 }
4173 }
4175 static struct view_ops pager_ops = {
4176 "line",
4177 NULL,
4178 NULL,
4179 pager_read,
4180 pager_draw,
4181 pager_request,
4182 pager_grep,
4183 pager_select,
4184 };
4186 static const char *log_argv[SIZEOF_ARG] = {
4187 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4188 };
4190 static enum request
4191 log_request(struct view *view, enum request request, struct line *line)
4192 {
4193 switch (request) {
4194 case REQ_REFRESH:
4195 load_refs();
4196 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4197 return REQ_NONE;
4198 default:
4199 return pager_request(view, request, line);
4200 }
4201 }
4203 static struct view_ops log_ops = {
4204 "line",
4205 log_argv,
4206 NULL,
4207 pager_read,
4208 pager_draw,
4209 log_request,
4210 pager_grep,
4211 pager_select,
4212 };
4214 static const char *diff_argv[SIZEOF_ARG] = {
4215 "git", "show", "--pretty=fuller", "--no-color", "--root",
4216 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4217 };
4219 static struct view_ops diff_ops = {
4220 "line",
4221 diff_argv,
4222 NULL,
4223 pager_read,
4224 pager_draw,
4225 pager_request,
4226 pager_grep,
4227 pager_select,
4228 };
4230 /*
4231 * Help backend
4232 */
4234 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4236 static bool
4237 help_open_keymap_title(struct view *view, enum keymap keymap)
4238 {
4239 struct line *line;
4241 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4242 help_keymap_hidden[keymap] ? '+' : '-',
4243 enum_name(keymap_table[keymap]));
4244 if (line)
4245 line->other = keymap;
4247 return help_keymap_hidden[keymap];
4248 }
4250 static void
4251 help_open_keymap(struct view *view, enum keymap keymap)
4252 {
4253 const char *group = NULL;
4254 char buf[SIZEOF_STR];
4255 size_t bufpos;
4256 bool add_title = TRUE;
4257 int i;
4259 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4260 const char *key = NULL;
4262 if (req_info[i].request == REQ_NONE)
4263 continue;
4265 if (!req_info[i].request) {
4266 group = req_info[i].help;
4267 continue;
4268 }
4270 key = get_keys(keymap, req_info[i].request, TRUE);
4271 if (!key || !*key)
4272 continue;
4274 if (add_title && help_open_keymap_title(view, keymap))
4275 return;
4276 add_title = FALSE;
4278 if (group) {
4279 add_line_text(view, group, LINE_HELP_GROUP);
4280 group = NULL;
4281 }
4283 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4284 enum_name(req_info[i]), req_info[i].help);
4285 }
4287 group = "External commands:";
4289 for (i = 0; i < run_requests; i++) {
4290 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4291 const char *key;
4292 int argc;
4294 if (!req || req->keymap != keymap)
4295 continue;
4297 key = get_key_name(req->key);
4298 if (!*key)
4299 key = "(no key defined)";
4301 if (add_title && help_open_keymap_title(view, keymap))
4302 return;
4303 if (group) {
4304 add_line_text(view, group, LINE_HELP_GROUP);
4305 group = NULL;
4306 }
4308 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4309 if (!string_format_from(buf, &bufpos, "%s%s",
4310 argc ? " " : "", req->argv[argc]))
4311 return;
4313 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4314 }
4315 }
4317 static bool
4318 help_open(struct view *view)
4319 {
4320 enum keymap keymap;
4322 reset_view(view);
4323 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4324 add_line_text(view, "", LINE_DEFAULT);
4326 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4327 help_open_keymap(view, keymap);
4329 return TRUE;
4330 }
4332 static enum request
4333 help_request(struct view *view, enum request request, struct line *line)
4334 {
4335 switch (request) {
4336 case REQ_ENTER:
4337 if (line->type == LINE_HELP_KEYMAP) {
4338 help_keymap_hidden[line->other] =
4339 !help_keymap_hidden[line->other];
4340 view->p_restore = TRUE;
4341 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4342 }
4344 return REQ_NONE;
4345 default:
4346 return pager_request(view, request, line);
4347 }
4348 }
4350 static struct view_ops help_ops = {
4351 "line",
4352 NULL,
4353 help_open,
4354 NULL,
4355 pager_draw,
4356 help_request,
4357 pager_grep,
4358 pager_select,
4359 };
4362 /*
4363 * Tree backend
4364 */
4366 struct tree_stack_entry {
4367 struct tree_stack_entry *prev; /* Entry below this in the stack */
4368 unsigned long lineno; /* Line number to restore */
4369 char *name; /* Position of name in opt_path */
4370 };
4372 /* The top of the path stack. */
4373 static struct tree_stack_entry *tree_stack = NULL;
4374 unsigned long tree_lineno = 0;
4376 static void
4377 pop_tree_stack_entry(void)
4378 {
4379 struct tree_stack_entry *entry = tree_stack;
4381 tree_lineno = entry->lineno;
4382 entry->name[0] = 0;
4383 tree_stack = entry->prev;
4384 free(entry);
4385 }
4387 static void
4388 push_tree_stack_entry(const char *name, unsigned long lineno)
4389 {
4390 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4391 size_t pathlen = strlen(opt_path);
4393 if (!entry)
4394 return;
4396 entry->prev = tree_stack;
4397 entry->name = opt_path + pathlen;
4398 tree_stack = entry;
4400 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4401 pop_tree_stack_entry();
4402 return;
4403 }
4405 /* Move the current line to the first tree entry. */
4406 tree_lineno = 1;
4407 entry->lineno = lineno;
4408 }
4410 /* Parse output from git-ls-tree(1):
4411 *
4412 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4413 */
4415 #define SIZEOF_TREE_ATTR \
4416 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4418 #define SIZEOF_TREE_MODE \
4419 STRING_SIZE("100644 ")
4421 #define TREE_ID_OFFSET \
4422 STRING_SIZE("100644 blob ")
4424 struct tree_entry {
4425 char id[SIZEOF_REV];
4426 mode_t mode;
4427 struct time time; /* Date from the author ident. */
4428 const char *author; /* Author of the commit. */
4429 char name[1];
4430 };
4432 static const char *
4433 tree_path(const struct line *line)
4434 {
4435 return ((struct tree_entry *) line->data)->name;
4436 }
4438 static int
4439 tree_compare_entry(const struct line *line1, const struct line *line2)
4440 {
4441 if (line1->type != line2->type)
4442 return line1->type == LINE_TREE_DIR ? -1 : 1;
4443 return strcmp(tree_path(line1), tree_path(line2));
4444 }
4446 static const enum sort_field tree_sort_fields[] = {
4447 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4448 };
4449 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4451 static int
4452 tree_compare(const void *l1, const void *l2)
4453 {
4454 const struct line *line1 = (const struct line *) l1;
4455 const struct line *line2 = (const struct line *) l2;
4456 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4457 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4459 if (line1->type == LINE_TREE_HEAD)
4460 return -1;
4461 if (line2->type == LINE_TREE_HEAD)
4462 return 1;
4464 switch (get_sort_field(tree_sort_state)) {
4465 case ORDERBY_DATE:
4466 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4468 case ORDERBY_AUTHOR:
4469 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4471 case ORDERBY_NAME:
4472 default:
4473 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4474 }
4475 }
4478 static struct line *
4479 tree_entry(struct view *view, enum line_type type, const char *path,
4480 const char *mode, const char *id)
4481 {
4482 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4483 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4485 if (!entry || !line) {
4486 free(entry);
4487 return NULL;
4488 }
4490 strncpy(entry->name, path, strlen(path));
4491 if (mode)
4492 entry->mode = strtoul(mode, NULL, 8);
4493 if (id)
4494 string_copy_rev(entry->id, id);
4496 return line;
4497 }
4499 static bool
4500 tree_read_date(struct view *view, char *text, bool *read_date)
4501 {
4502 static const char *author_name;
4503 static struct time author_time;
4505 if (!text && *read_date) {
4506 *read_date = FALSE;
4507 return TRUE;
4509 } else if (!text) {
4510 char *path = *opt_path ? opt_path : ".";
4511 /* Find next entry to process */
4512 const char *log_file[] = {
4513 "git", "log", "--no-color", "--pretty=raw",
4514 "--cc", "--raw", view->id, "--", path, NULL
4515 };
4516 struct io io = {};
4518 if (!view->lines) {
4519 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4520 report("Tree is empty");
4521 return TRUE;
4522 }
4524 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4525 report("Failed to load tree data");
4526 return TRUE;
4527 }
4529 io_done(view->pipe);
4530 view->io = io;
4531 *read_date = TRUE;
4532 return FALSE;
4534 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4535 parse_author_line(text + STRING_SIZE("author "),
4536 &author_name, &author_time);
4538 } else if (*text == ':') {
4539 char *pos;
4540 size_t annotated = 1;
4541 size_t i;
4543 pos = strchr(text, '\t');
4544 if (!pos)
4545 return TRUE;
4546 text = pos + 1;
4547 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4548 text += strlen(opt_path);
4549 pos = strchr(text, '/');
4550 if (pos)
4551 *pos = 0;
4553 for (i = 1; i < view->lines; i++) {
4554 struct line *line = &view->line[i];
4555 struct tree_entry *entry = line->data;
4557 annotated += !!entry->author;
4558 if (entry->author || strcmp(entry->name, text))
4559 continue;
4561 entry->author = author_name;
4562 entry->time = author_time;
4563 line->dirty = 1;
4564 break;
4565 }
4567 if (annotated == view->lines)
4568 io_kill(view->pipe);
4569 }
4570 return TRUE;
4571 }
4573 static bool
4574 tree_read(struct view *view, char *text)
4575 {
4576 static bool read_date = FALSE;
4577 struct tree_entry *data;
4578 struct line *entry, *line;
4579 enum line_type type;
4580 size_t textlen = text ? strlen(text) : 0;
4581 char *path = text + SIZEOF_TREE_ATTR;
4583 if (read_date || !text)
4584 return tree_read_date(view, text, &read_date);
4586 if (textlen <= SIZEOF_TREE_ATTR)
4587 return FALSE;
4588 if (view->lines == 0 &&
4589 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4590 return FALSE;
4592 /* Strip the path part ... */
4593 if (*opt_path) {
4594 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4595 size_t striplen = strlen(opt_path);
4597 if (pathlen > striplen)
4598 memmove(path, path + striplen,
4599 pathlen - striplen + 1);
4601 /* Insert "link" to parent directory. */
4602 if (view->lines == 1 &&
4603 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4604 return FALSE;
4605 }
4607 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4608 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4609 if (!entry)
4610 return FALSE;
4611 data = entry->data;
4613 /* Skip "Directory ..." and ".." line. */
4614 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4615 if (tree_compare_entry(line, entry) <= 0)
4616 continue;
4618 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4620 line->data = data;
4621 line->type = type;
4622 for (; line <= entry; line++)
4623 line->dirty = line->cleareol = 1;
4624 return TRUE;
4625 }
4627 if (tree_lineno > view->lineno) {
4628 view->lineno = tree_lineno;
4629 tree_lineno = 0;
4630 }
4632 return TRUE;
4633 }
4635 static bool
4636 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4637 {
4638 struct tree_entry *entry = line->data;
4640 if (line->type == LINE_TREE_HEAD) {
4641 if (draw_text(view, line->type, "Directory path /", TRUE))
4642 return TRUE;
4643 } else {
4644 if (draw_mode(view, entry->mode))
4645 return TRUE;
4647 if (opt_author && draw_author(view, entry->author))
4648 return TRUE;
4650 if (opt_date && draw_date(view, &entry->time))
4651 return TRUE;
4652 }
4653 if (draw_text(view, line->type, entry->name, TRUE))
4654 return TRUE;
4655 return TRUE;
4656 }
4658 static void
4659 open_blob_editor()
4660 {
4661 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4662 int fd = mkstemp(file);
4664 if (fd == -1)
4665 report("Failed to create temporary file");
4666 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4667 report("Failed to save blob data to file");
4668 else
4669 open_editor(file);
4670 if (fd != -1)
4671 unlink(file);
4672 }
4674 static enum request
4675 tree_request(struct view *view, enum request request, struct line *line)
4676 {
4677 enum open_flags flags;
4679 switch (request) {
4680 case REQ_VIEW_BLAME:
4681 if (line->type != LINE_TREE_FILE) {
4682 report("Blame only supported for files");
4683 return REQ_NONE;
4684 }
4686 string_copy(opt_ref, view->vid);
4687 return request;
4689 case REQ_EDIT:
4690 if (line->type != LINE_TREE_FILE) {
4691 report("Edit only supported for files");
4692 } else if (!is_head_commit(view->vid)) {
4693 open_blob_editor();
4694 } else {
4695 open_editor(opt_file);
4696 }
4697 return REQ_NONE;
4699 case REQ_TOGGLE_SORT_FIELD:
4700 case REQ_TOGGLE_SORT_ORDER:
4701 sort_view(view, request, &tree_sort_state, tree_compare);
4702 return REQ_NONE;
4704 case REQ_PARENT:
4705 if (!*opt_path) {
4706 /* quit view if at top of tree */
4707 return REQ_VIEW_CLOSE;
4708 }
4709 /* fake 'cd ..' */
4710 line = &view->line[1];
4711 break;
4713 case REQ_ENTER:
4714 break;
4716 default:
4717 return request;
4718 }
4720 /* Cleanup the stack if the tree view is at a different tree. */
4721 while (!*opt_path && tree_stack)
4722 pop_tree_stack_entry();
4724 switch (line->type) {
4725 case LINE_TREE_DIR:
4726 /* Depending on whether it is a subdirectory or parent link
4727 * mangle the path buffer. */
4728 if (line == &view->line[1] && *opt_path) {
4729 pop_tree_stack_entry();
4731 } else {
4732 const char *basename = tree_path(line);
4734 push_tree_stack_entry(basename, view->lineno);
4735 }
4737 /* Trees and subtrees share the same ID, so they are not not
4738 * unique like blobs. */
4739 flags = OPEN_RELOAD;
4740 request = REQ_VIEW_TREE;
4741 break;
4743 case LINE_TREE_FILE:
4744 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4745 request = REQ_VIEW_BLOB;
4746 break;
4748 default:
4749 return REQ_NONE;
4750 }
4752 open_view(view, request, flags);
4753 if (request == REQ_VIEW_TREE)
4754 view->lineno = tree_lineno;
4756 return REQ_NONE;
4757 }
4759 static bool
4760 tree_grep(struct view *view, struct line *line)
4761 {
4762 struct tree_entry *entry = line->data;
4763 const char *text[] = {
4764 entry->name,
4765 opt_author ? entry->author : "",
4766 mkdate(&entry->time, opt_date),
4767 NULL
4768 };
4770 return grep_text(view, text);
4771 }
4773 static void
4774 tree_select(struct view *view, struct line *line)
4775 {
4776 struct tree_entry *entry = line->data;
4778 if (line->type == LINE_TREE_FILE) {
4779 string_copy_rev(ref_blob, entry->id);
4780 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4782 } else if (line->type != LINE_TREE_DIR) {
4783 return;
4784 }
4786 string_copy_rev(view->ref, entry->id);
4787 }
4789 static bool
4790 tree_prepare(struct view *view)
4791 {
4792 if (view->lines == 0 && opt_prefix[0]) {
4793 char *pos = opt_prefix;
4795 while (pos && *pos) {
4796 char *end = strchr(pos, '/');
4798 if (end)
4799 *end = 0;
4800 push_tree_stack_entry(pos, 0);
4801 pos = end;
4802 if (end) {
4803 *end = '/';
4804 pos++;
4805 }
4806 }
4808 } else if (strcmp(view->vid, view->id)) {
4809 opt_path[0] = 0;
4810 }
4812 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4813 }
4815 static const char *tree_argv[SIZEOF_ARG] = {
4816 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4817 };
4819 static struct view_ops tree_ops = {
4820 "file",
4821 tree_argv,
4822 NULL,
4823 tree_read,
4824 tree_draw,
4825 tree_request,
4826 tree_grep,
4827 tree_select,
4828 tree_prepare,
4829 };
4831 static bool
4832 blob_read(struct view *view, char *line)
4833 {
4834 if (!line)
4835 return TRUE;
4836 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4837 }
4839 static enum request
4840 blob_request(struct view *view, enum request request, struct line *line)
4841 {
4842 switch (request) {
4843 case REQ_EDIT:
4844 open_blob_editor();
4845 return REQ_NONE;
4846 default:
4847 return pager_request(view, request, line);
4848 }
4849 }
4851 static const char *blob_argv[SIZEOF_ARG] = {
4852 "git", "cat-file", "blob", "%(blob)", NULL
4853 };
4855 static struct view_ops blob_ops = {
4856 "line",
4857 blob_argv,
4858 NULL,
4859 blob_read,
4860 pager_draw,
4861 blob_request,
4862 pager_grep,
4863 pager_select,
4864 };
4866 /*
4867 * Blame backend
4868 *
4869 * Loading the blame view is a two phase job:
4870 *
4871 * 1. File content is read either using opt_file from the
4872 * filesystem or using git-cat-file.
4873 * 2. Then blame information is incrementally added by
4874 * reading output from git-blame.
4875 */
4877 static const char *blame_head_argv[] = {
4878 "git", "blame", "--incremental", "--", "%(file)", NULL
4879 };
4881 static const char *blame_ref_argv[] = {
4882 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4883 };
4885 static const char *blame_cat_file_argv[] = {
4886 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4887 };
4889 struct blame_commit {
4890 char id[SIZEOF_REV]; /* SHA1 ID. */
4891 char title[128]; /* First line of the commit message. */
4892 const char *author; /* Author of the commit. */
4893 struct time time; /* Date from the author ident. */
4894 char filename[128]; /* Name of file. */
4895 bool has_previous; /* Was a "previous" line detected. */
4896 };
4898 struct blame {
4899 struct blame_commit *commit;
4900 unsigned long lineno;
4901 char text[1];
4902 };
4904 static bool
4905 blame_open(struct view *view)
4906 {
4907 char path[SIZEOF_STR];
4909 if (!view->prev && *opt_prefix) {
4910 string_copy(path, opt_file);
4911 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4912 return FALSE;
4913 }
4915 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4916 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4917 return FALSE;
4918 }
4920 setup_update(view, opt_file);
4921 string_format(view->ref, "%s ...", opt_file);
4923 return TRUE;
4924 }
4926 static struct blame_commit *
4927 get_blame_commit(struct view *view, const char *id)
4928 {
4929 size_t i;
4931 for (i = 0; i < view->lines; i++) {
4932 struct blame *blame = view->line[i].data;
4934 if (!blame->commit)
4935 continue;
4937 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4938 return blame->commit;
4939 }
4941 {
4942 struct blame_commit *commit = calloc(1, sizeof(*commit));
4944 if (commit)
4945 string_ncopy(commit->id, id, SIZEOF_REV);
4946 return commit;
4947 }
4948 }
4950 static bool
4951 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4952 {
4953 const char *pos = *posref;
4955 *posref = NULL;
4956 pos = strchr(pos + 1, ' ');
4957 if (!pos || !isdigit(pos[1]))
4958 return FALSE;
4959 *number = atoi(pos + 1);
4960 if (*number < min || *number > max)
4961 return FALSE;
4963 *posref = pos;
4964 return TRUE;
4965 }
4967 static struct blame_commit *
4968 parse_blame_commit(struct view *view, const char *text, int *blamed)
4969 {
4970 struct blame_commit *commit;
4971 struct blame *blame;
4972 const char *pos = text + SIZEOF_REV - 2;
4973 size_t orig_lineno = 0;
4974 size_t lineno;
4975 size_t group;
4977 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4978 return NULL;
4980 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4981 !parse_number(&pos, &lineno, 1, view->lines) ||
4982 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4983 return NULL;
4985 commit = get_blame_commit(view, text);
4986 if (!commit)
4987 return NULL;
4989 *blamed += group;
4990 while (group--) {
4991 struct line *line = &view->line[lineno + group - 1];
4993 blame = line->data;
4994 blame->commit = commit;
4995 blame->lineno = orig_lineno + group - 1;
4996 line->dirty = 1;
4997 }
4999 return commit;
5000 }
5002 static bool
5003 blame_read_file(struct view *view, const char *line, bool *read_file)
5004 {
5005 if (!line) {
5006 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
5007 struct io io = {};
5009 if (view->lines == 0 && !view->prev)
5010 die("No blame exist for %s", view->vid);
5012 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
5013 report("Failed to load blame data");
5014 return TRUE;
5015 }
5017 io_done(view->pipe);
5018 view->io = io;
5019 *read_file = FALSE;
5020 return FALSE;
5022 } else {
5023 size_t linelen = strlen(line);
5024 struct blame *blame = malloc(sizeof(*blame) + linelen);
5026 if (!blame)
5027 return FALSE;
5029 blame->commit = NULL;
5030 strncpy(blame->text, line, linelen);
5031 blame->text[linelen] = 0;
5032 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5033 }
5034 }
5036 static bool
5037 match_blame_header(const char *name, char **line)
5038 {
5039 size_t namelen = strlen(name);
5040 bool matched = !strncmp(name, *line, namelen);
5042 if (matched)
5043 *line += namelen;
5045 return matched;
5046 }
5048 static bool
5049 blame_read(struct view *view, char *line)
5050 {
5051 static struct blame_commit *commit = NULL;
5052 static int blamed = 0;
5053 static bool read_file = TRUE;
5055 if (read_file)
5056 return blame_read_file(view, line, &read_file);
5058 if (!line) {
5059 /* Reset all! */
5060 commit = NULL;
5061 blamed = 0;
5062 read_file = TRUE;
5063 string_format(view->ref, "%s", view->vid);
5064 if (view_is_displayed(view)) {
5065 update_view_title(view);
5066 redraw_view_from(view, 0);
5067 }
5068 return TRUE;
5069 }
5071 if (!commit) {
5072 commit = parse_blame_commit(view, line, &blamed);
5073 string_format(view->ref, "%s %2d%%", view->vid,
5074 view->lines ? blamed * 100 / view->lines : 0);
5076 } else if (match_blame_header("author ", &line)) {
5077 commit->author = get_author(line);
5079 } else if (match_blame_header("author-time ", &line)) {
5080 parse_timesec(&commit->time, line);
5082 } else if (match_blame_header("author-tz ", &line)) {
5083 parse_timezone(&commit->time, line);
5085 } else if (match_blame_header("summary ", &line)) {
5086 string_ncopy(commit->title, line, strlen(line));
5088 } else if (match_blame_header("previous ", &line)) {
5089 commit->has_previous = TRUE;
5091 } else if (match_blame_header("filename ", &line)) {
5092 string_ncopy(commit->filename, line, strlen(line));
5093 commit = NULL;
5094 }
5096 return TRUE;
5097 }
5099 static bool
5100 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5101 {
5102 struct blame *blame = line->data;
5103 struct time *time = NULL;
5104 const char *id = NULL, *author = NULL;
5105 char text[SIZEOF_STR];
5107 if (blame->commit && *blame->commit->filename) {
5108 id = blame->commit->id;
5109 author = blame->commit->author;
5110 time = &blame->commit->time;
5111 }
5113 if (opt_date && draw_date(view, time))
5114 return TRUE;
5116 if (opt_author && draw_author(view, author))
5117 return TRUE;
5119 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5120 return TRUE;
5122 if (draw_lineno(view, lineno))
5123 return TRUE;
5125 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5126 draw_text(view, LINE_DEFAULT, text, TRUE);
5127 return TRUE;
5128 }
5130 static bool
5131 check_blame_commit(struct blame *blame, bool check_null_id)
5132 {
5133 if (!blame->commit)
5134 report("Commit data not loaded yet");
5135 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5136 report("No commit exist for the selected line");
5137 else
5138 return TRUE;
5139 return FALSE;
5140 }
5142 static void
5143 setup_blame_parent_line(struct view *view, struct blame *blame)
5144 {
5145 const char *diff_tree_argv[] = {
5146 "git", "diff-tree", "-U0", blame->commit->id,
5147 "--", blame->commit->filename, NULL
5148 };
5149 struct io io = {};
5150 int parent_lineno = -1;
5151 int blamed_lineno = -1;
5152 char *line;
5154 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5155 return;
5157 while ((line = io_get(&io, '\n', TRUE))) {
5158 if (*line == '@') {
5159 char *pos = strchr(line, '+');
5161 parent_lineno = atoi(line + 4);
5162 if (pos)
5163 blamed_lineno = atoi(pos + 1);
5165 } else if (*line == '+' && parent_lineno != -1) {
5166 if (blame->lineno == blamed_lineno - 1 &&
5167 !strcmp(blame->text, line + 1)) {
5168 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5169 break;
5170 }
5171 blamed_lineno++;
5172 }
5173 }
5175 io_done(&io);
5176 }
5178 static enum request
5179 blame_request(struct view *view, enum request request, struct line *line)
5180 {
5181 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5182 struct blame *blame = line->data;
5184 switch (request) {
5185 case REQ_VIEW_BLAME:
5186 if (check_blame_commit(blame, TRUE)) {
5187 string_copy(opt_ref, blame->commit->id);
5188 string_copy(opt_file, blame->commit->filename);
5189 if (blame->lineno)
5190 view->lineno = blame->lineno;
5191 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5192 }
5193 break;
5195 case REQ_PARENT:
5196 if (check_blame_commit(blame, TRUE) &&
5197 select_commit_parent(blame->commit->id, opt_ref,
5198 blame->commit->filename)) {
5199 string_copy(opt_file, blame->commit->filename);
5200 setup_blame_parent_line(view, blame);
5201 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5202 }
5203 break;
5205 case REQ_ENTER:
5206 if (!check_blame_commit(blame, FALSE))
5207 break;
5209 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5210 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5211 break;
5213 if (!strcmp(blame->commit->id, NULL_ID)) {
5214 struct view *diff = VIEW(REQ_VIEW_DIFF);
5215 const char *diff_index_argv[] = {
5216 "git", "diff-index", "--root", "--patch-with-stat",
5217 "-C", "-M", "HEAD", "--", view->vid, NULL
5218 };
5220 if (!blame->commit->has_previous) {
5221 diff_index_argv[1] = "diff";
5222 diff_index_argv[2] = "--no-color";
5223 diff_index_argv[6] = "--";
5224 diff_index_argv[7] = "/dev/null";
5225 }
5227 if (!prepare_update(diff, diff_index_argv, NULL)) {
5228 report("Failed to allocate diff command");
5229 break;
5230 }
5231 flags |= OPEN_PREPARED;
5232 }
5234 open_view(view, REQ_VIEW_DIFF, flags);
5235 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5236 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5237 break;
5239 default:
5240 return request;
5241 }
5243 return REQ_NONE;
5244 }
5246 static bool
5247 blame_grep(struct view *view, struct line *line)
5248 {
5249 struct blame *blame = line->data;
5250 struct blame_commit *commit = blame->commit;
5251 const char *text[] = {
5252 blame->text,
5253 commit ? commit->title : "",
5254 commit ? commit->id : "",
5255 commit && opt_author ? commit->author : "",
5256 commit ? mkdate(&commit->time, opt_date) : "",
5257 NULL
5258 };
5260 return grep_text(view, text);
5261 }
5263 static void
5264 blame_select(struct view *view, struct line *line)
5265 {
5266 struct blame *blame = line->data;
5267 struct blame_commit *commit = blame->commit;
5269 if (!commit)
5270 return;
5272 if (!strcmp(commit->id, NULL_ID))
5273 string_ncopy(ref_commit, "HEAD", 4);
5274 else
5275 string_copy_rev(ref_commit, commit->id);
5276 }
5278 static struct view_ops blame_ops = {
5279 "line",
5280 NULL,
5281 blame_open,
5282 blame_read,
5283 blame_draw,
5284 blame_request,
5285 blame_grep,
5286 blame_select,
5287 };
5289 /*
5290 * Branch backend
5291 */
5293 struct branch {
5294 const char *author; /* Author of the last commit. */
5295 struct time time; /* Date of the last activity. */
5296 const struct ref *ref; /* Name and commit ID information. */
5297 };
5299 static const struct ref branch_all;
5301 static const enum sort_field branch_sort_fields[] = {
5302 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5303 };
5304 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5306 static int
5307 branch_compare(const void *l1, const void *l2)
5308 {
5309 const struct branch *branch1 = ((const struct line *) l1)->data;
5310 const struct branch *branch2 = ((const struct line *) l2)->data;
5312 switch (get_sort_field(branch_sort_state)) {
5313 case ORDERBY_DATE:
5314 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5316 case ORDERBY_AUTHOR:
5317 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5319 case ORDERBY_NAME:
5320 default:
5321 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5322 }
5323 }
5325 static bool
5326 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5327 {
5328 struct branch *branch = line->data;
5329 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5331 if (opt_date && draw_date(view, &branch->time))
5332 return TRUE;
5334 if (opt_author && draw_author(view, branch->author))
5335 return TRUE;
5337 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5338 return TRUE;
5339 }
5341 static enum request
5342 branch_request(struct view *view, enum request request, struct line *line)
5343 {
5344 struct branch *branch = line->data;
5346 switch (request) {
5347 case REQ_REFRESH:
5348 load_refs();
5349 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5350 return REQ_NONE;
5352 case REQ_TOGGLE_SORT_FIELD:
5353 case REQ_TOGGLE_SORT_ORDER:
5354 sort_view(view, request, &branch_sort_state, branch_compare);
5355 return REQ_NONE;
5357 case REQ_ENTER:
5358 if (branch->ref == &branch_all) {
5359 const char *all_branches_argv[] = {
5360 "git", "log", "--no-color", "--pretty=raw", "--parents",
5361 "--topo-order", "--all", NULL
5362 };
5363 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5365 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5366 report("Failed to load view of all branches");
5367 return REQ_NONE;
5368 }
5369 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5370 } else {
5371 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5372 }
5373 return REQ_NONE;
5375 default:
5376 return request;
5377 }
5378 }
5380 static bool
5381 branch_read(struct view *view, char *line)
5382 {
5383 static char id[SIZEOF_REV];
5384 struct branch *reference;
5385 size_t i;
5387 if (!line)
5388 return TRUE;
5390 switch (get_line_type(line)) {
5391 case LINE_COMMIT:
5392 string_copy_rev(id, line + STRING_SIZE("commit "));
5393 return TRUE;
5395 case LINE_AUTHOR:
5396 for (i = 0, reference = NULL; i < view->lines; i++) {
5397 struct branch *branch = view->line[i].data;
5399 if (strcmp(branch->ref->id, id))
5400 continue;
5402 view->line[i].dirty = TRUE;
5403 if (reference) {
5404 branch->author = reference->author;
5405 branch->time = reference->time;
5406 continue;
5407 }
5409 parse_author_line(line + STRING_SIZE("author "),
5410 &branch->author, &branch->time);
5411 reference = branch;
5412 }
5413 return TRUE;
5415 default:
5416 return TRUE;
5417 }
5419 }
5421 static bool
5422 branch_open_visitor(void *data, const struct ref *ref)
5423 {
5424 struct view *view = data;
5425 struct branch *branch;
5427 if (ref->tag || ref->ltag || ref->remote)
5428 return TRUE;
5430 branch = calloc(1, sizeof(*branch));
5431 if (!branch)
5432 return FALSE;
5434 branch->ref = ref;
5435 return !!add_line_data(view, branch, LINE_DEFAULT);
5436 }
5438 static bool
5439 branch_open(struct view *view)
5440 {
5441 const char *branch_log[] = {
5442 "git", "log", "--no-color", "--pretty=raw",
5443 "--simplify-by-decoration", "--all", NULL
5444 };
5446 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5447 report("Failed to load branch data");
5448 return TRUE;
5449 }
5451 setup_update(view, view->id);
5452 branch_open_visitor(view, &branch_all);
5453 foreach_ref(branch_open_visitor, view);
5454 view->p_restore = TRUE;
5456 return TRUE;
5457 }
5459 static bool
5460 branch_grep(struct view *view, struct line *line)
5461 {
5462 struct branch *branch = line->data;
5463 const char *text[] = {
5464 branch->ref->name,
5465 branch->author,
5466 NULL
5467 };
5469 return grep_text(view, text);
5470 }
5472 static void
5473 branch_select(struct view *view, struct line *line)
5474 {
5475 struct branch *branch = line->data;
5477 string_copy_rev(view->ref, branch->ref->id);
5478 string_copy_rev(ref_commit, branch->ref->id);
5479 string_copy_rev(ref_head, branch->ref->id);
5480 string_copy_rev(ref_branch, branch->ref->name);
5481 }
5483 static struct view_ops branch_ops = {
5484 "branch",
5485 NULL,
5486 branch_open,
5487 branch_read,
5488 branch_draw,
5489 branch_request,
5490 branch_grep,
5491 branch_select,
5492 };
5494 /*
5495 * Status backend
5496 */
5498 struct status {
5499 char status;
5500 struct {
5501 mode_t mode;
5502 char rev[SIZEOF_REV];
5503 char name[SIZEOF_STR];
5504 } old;
5505 struct {
5506 mode_t mode;
5507 char rev[SIZEOF_REV];
5508 char name[SIZEOF_STR];
5509 } new;
5510 };
5512 static char status_onbranch[SIZEOF_STR];
5513 static struct status stage_status;
5514 static enum line_type stage_line_type;
5515 static size_t stage_chunks;
5516 static int *stage_chunk;
5518 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5520 /* This should work even for the "On branch" line. */
5521 static inline bool
5522 status_has_none(struct view *view, struct line *line)
5523 {
5524 return line < view->line + view->lines && !line[1].data;
5525 }
5527 /* Get fields from the diff line:
5528 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5529 */
5530 static inline bool
5531 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5532 {
5533 const char *old_mode = buf + 1;
5534 const char *new_mode = buf + 8;
5535 const char *old_rev = buf + 15;
5536 const char *new_rev = buf + 56;
5537 const char *status = buf + 97;
5539 if (bufsize < 98 ||
5540 old_mode[-1] != ':' ||
5541 new_mode[-1] != ' ' ||
5542 old_rev[-1] != ' ' ||
5543 new_rev[-1] != ' ' ||
5544 status[-1] != ' ')
5545 return FALSE;
5547 file->status = *status;
5549 string_copy_rev(file->old.rev, old_rev);
5550 string_copy_rev(file->new.rev, new_rev);
5552 file->old.mode = strtoul(old_mode, NULL, 8);
5553 file->new.mode = strtoul(new_mode, NULL, 8);
5555 file->old.name[0] = file->new.name[0] = 0;
5557 return TRUE;
5558 }
5560 static bool
5561 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5562 {
5563 struct status *unmerged = NULL;
5564 char *buf;
5565 struct io io = {};
5567 if (!io_run(&io, argv, opt_cdup, IO_RD))
5568 return FALSE;
5570 add_line_data(view, NULL, type);
5572 while ((buf = io_get(&io, 0, TRUE))) {
5573 struct status *file = unmerged;
5575 if (!file) {
5576 file = calloc(1, sizeof(*file));
5577 if (!file || !add_line_data(view, file, type))
5578 goto error_out;
5579 }
5581 /* Parse diff info part. */
5582 if (status) {
5583 file->status = status;
5584 if (status == 'A')
5585 string_copy(file->old.rev, NULL_ID);
5587 } else if (!file->status || file == unmerged) {
5588 if (!status_get_diff(file, buf, strlen(buf)))
5589 goto error_out;
5591 buf = io_get(&io, 0, TRUE);
5592 if (!buf)
5593 break;
5595 /* Collapse all modified entries that follow an
5596 * associated unmerged entry. */
5597 if (unmerged == file) {
5598 unmerged->status = 'U';
5599 unmerged = NULL;
5600 } else if (file->status == 'U') {
5601 unmerged = file;
5602 }
5603 }
5605 /* Grab the old name for rename/copy. */
5606 if (!*file->old.name &&
5607 (file->status == 'R' || file->status == 'C')) {
5608 string_ncopy(file->old.name, buf, strlen(buf));
5610 buf = io_get(&io, 0, TRUE);
5611 if (!buf)
5612 break;
5613 }
5615 /* git-ls-files just delivers a NUL separated list of
5616 * file names similar to the second half of the
5617 * git-diff-* output. */
5618 string_ncopy(file->new.name, buf, strlen(buf));
5619 if (!*file->old.name)
5620 string_copy(file->old.name, file->new.name);
5621 file = NULL;
5622 }
5624 if (io_error(&io)) {
5625 error_out:
5626 io_done(&io);
5627 return FALSE;
5628 }
5630 if (!view->line[view->lines - 1].data)
5631 add_line_data(view, NULL, LINE_STAT_NONE);
5633 io_done(&io);
5634 return TRUE;
5635 }
5637 /* Don't show unmerged entries in the staged section. */
5638 static const char *status_diff_index_argv[] = {
5639 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5640 "--cached", "-M", "HEAD", NULL
5641 };
5643 static const char *status_diff_files_argv[] = {
5644 "git", "diff-files", "-z", NULL
5645 };
5647 static const char *status_list_other_argv[] = {
5648 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5649 };
5651 static const char *status_list_no_head_argv[] = {
5652 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5653 };
5655 static const char *update_index_argv[] = {
5656 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5657 };
5659 /* Restore the previous line number to stay in the context or select a
5660 * line with something that can be updated. */
5661 static void
5662 status_restore(struct view *view)
5663 {
5664 if (view->p_lineno >= view->lines)
5665 view->p_lineno = view->lines - 1;
5666 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5667 view->p_lineno++;
5668 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5669 view->p_lineno--;
5671 /* If the above fails, always skip the "On branch" line. */
5672 if (view->p_lineno < view->lines)
5673 view->lineno = view->p_lineno;
5674 else
5675 view->lineno = 1;
5677 if (view->lineno < view->offset)
5678 view->offset = view->lineno;
5679 else if (view->offset + view->height <= view->lineno)
5680 view->offset = view->lineno - view->height + 1;
5682 view->p_restore = FALSE;
5683 }
5685 static void
5686 status_update_onbranch(void)
5687 {
5688 static const char *paths[][2] = {
5689 { "rebase-apply/rebasing", "Rebasing" },
5690 { "rebase-apply/applying", "Applying mailbox" },
5691 { "rebase-apply/", "Rebasing mailbox" },
5692 { "rebase-merge/interactive", "Interactive rebase" },
5693 { "rebase-merge/", "Rebase merge" },
5694 { "MERGE_HEAD", "Merging" },
5695 { "BISECT_LOG", "Bisecting" },
5696 { "HEAD", "On branch" },
5697 };
5698 char buf[SIZEOF_STR];
5699 struct stat stat;
5700 int i;
5702 if (is_initial_commit()) {
5703 string_copy(status_onbranch, "Initial commit");
5704 return;
5705 }
5707 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5708 char *head = opt_head;
5710 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5711 lstat(buf, &stat) < 0)
5712 continue;
5714 if (!*opt_head) {
5715 struct io io = {};
5717 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5718 io_read_buf(&io, buf, sizeof(buf))) {
5719 head = buf;
5720 if (!prefixcmp(head, "refs/heads/"))
5721 head += STRING_SIZE("refs/heads/");
5722 }
5723 }
5725 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5726 string_copy(status_onbranch, opt_head);
5727 return;
5728 }
5730 string_copy(status_onbranch, "Not currently on any branch");
5731 }
5733 /* First parse staged info using git-diff-index(1), then parse unstaged
5734 * info using git-diff-files(1), and finally untracked files using
5735 * git-ls-files(1). */
5736 static bool
5737 status_open(struct view *view)
5738 {
5739 reset_view(view);
5741 add_line_data(view, NULL, LINE_STAT_HEAD);
5742 status_update_onbranch();
5744 io_run_bg(update_index_argv);
5746 if (is_initial_commit()) {
5747 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5748 return FALSE;
5749 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5750 return FALSE;
5751 }
5753 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5754 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5755 return FALSE;
5757 /* Restore the exact position or use the specialized restore
5758 * mode? */
5759 if (!view->p_restore)
5760 status_restore(view);
5761 return TRUE;
5762 }
5764 static bool
5765 status_draw(struct view *view, struct line *line, unsigned int lineno)
5766 {
5767 struct status *status = line->data;
5768 enum line_type type;
5769 const char *text;
5771 if (!status) {
5772 switch (line->type) {
5773 case LINE_STAT_STAGED:
5774 type = LINE_STAT_SECTION;
5775 text = "Changes to be committed:";
5776 break;
5778 case LINE_STAT_UNSTAGED:
5779 type = LINE_STAT_SECTION;
5780 text = "Changed but not updated:";
5781 break;
5783 case LINE_STAT_UNTRACKED:
5784 type = LINE_STAT_SECTION;
5785 text = "Untracked files:";
5786 break;
5788 case LINE_STAT_NONE:
5789 type = LINE_DEFAULT;
5790 text = " (no files)";
5791 break;
5793 case LINE_STAT_HEAD:
5794 type = LINE_STAT_HEAD;
5795 text = status_onbranch;
5796 break;
5798 default:
5799 return FALSE;
5800 }
5801 } else {
5802 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5804 buf[0] = status->status;
5805 if (draw_text(view, line->type, buf, TRUE))
5806 return TRUE;
5807 type = LINE_DEFAULT;
5808 text = status->new.name;
5809 }
5811 draw_text(view, type, text, TRUE);
5812 return TRUE;
5813 }
5815 static enum request
5816 status_load_error(struct view *view, struct view *stage, const char *path)
5817 {
5818 if (displayed_views() == 2 || display[current_view] != view)
5819 maximize_view(view);
5820 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5821 return REQ_NONE;
5822 }
5824 static enum request
5825 status_enter(struct view *view, struct line *line)
5826 {
5827 struct status *status = line->data;
5828 const char *oldpath = status ? status->old.name : NULL;
5829 /* Diffs for unmerged entries are empty when passing the new
5830 * path, so leave it empty. */
5831 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5832 const char *info;
5833 enum open_flags split;
5834 struct view *stage = VIEW(REQ_VIEW_STAGE);
5836 if (line->type == LINE_STAT_NONE ||
5837 (!status && line[1].type == LINE_STAT_NONE)) {
5838 report("No file to diff");
5839 return REQ_NONE;
5840 }
5842 switch (line->type) {
5843 case LINE_STAT_STAGED:
5844 if (is_initial_commit()) {
5845 const char *no_head_diff_argv[] = {
5846 "git", "diff", "--no-color", "--patch-with-stat",
5847 "--", "/dev/null", newpath, NULL
5848 };
5850 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5851 return status_load_error(view, stage, newpath);
5852 } else {
5853 const char *index_show_argv[] = {
5854 "git", "diff-index", "--root", "--patch-with-stat",
5855 "-C", "-M", "--cached", "HEAD", "--",
5856 oldpath, newpath, NULL
5857 };
5859 if (!prepare_update(stage, index_show_argv, opt_cdup))
5860 return status_load_error(view, stage, newpath);
5861 }
5863 if (status)
5864 info = "Staged changes to %s";
5865 else
5866 info = "Staged changes";
5867 break;
5869 case LINE_STAT_UNSTAGED:
5870 {
5871 const char *files_show_argv[] = {
5872 "git", "diff-files", "--root", "--patch-with-stat",
5873 "-C", "-M", "--", oldpath, newpath, NULL
5874 };
5876 if (!prepare_update(stage, files_show_argv, opt_cdup))
5877 return status_load_error(view, stage, newpath);
5878 if (status)
5879 info = "Unstaged changes to %s";
5880 else
5881 info = "Unstaged changes";
5882 break;
5883 }
5884 case LINE_STAT_UNTRACKED:
5885 if (!newpath) {
5886 report("No file to show");
5887 return REQ_NONE;
5888 }
5890 if (!suffixcmp(status->new.name, -1, "/")) {
5891 report("Cannot display a directory");
5892 return REQ_NONE;
5893 }
5895 if (!prepare_update_file(stage, newpath))
5896 return status_load_error(view, stage, newpath);
5897 info = "Untracked file %s";
5898 break;
5900 case LINE_STAT_HEAD:
5901 return REQ_NONE;
5903 default:
5904 die("line type %d not handled in switch", line->type);
5905 }
5907 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5908 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5909 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5910 if (status) {
5911 stage_status = *status;
5912 } else {
5913 memset(&stage_status, 0, sizeof(stage_status));
5914 }
5916 stage_line_type = line->type;
5917 stage_chunks = 0;
5918 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5919 }
5921 return REQ_NONE;
5922 }
5924 static bool
5925 status_exists(struct status *status, enum line_type type)
5926 {
5927 struct view *view = VIEW(REQ_VIEW_STATUS);
5928 unsigned long lineno;
5930 for (lineno = 0; lineno < view->lines; lineno++) {
5931 struct line *line = &view->line[lineno];
5932 struct status *pos = line->data;
5934 if (line->type != type)
5935 continue;
5936 if (!pos && (!status || !status->status) && line[1].data) {
5937 select_view_line(view, lineno);
5938 return TRUE;
5939 }
5940 if (pos && !strcmp(status->new.name, pos->new.name)) {
5941 select_view_line(view, lineno);
5942 return TRUE;
5943 }
5944 }
5946 return FALSE;
5947 }
5950 static bool
5951 status_update_prepare(struct io *io, enum line_type type)
5952 {
5953 const char *staged_argv[] = {
5954 "git", "update-index", "-z", "--index-info", NULL
5955 };
5956 const char *others_argv[] = {
5957 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5958 };
5960 switch (type) {
5961 case LINE_STAT_STAGED:
5962 return io_run(io, staged_argv, opt_cdup, IO_WR);
5964 case LINE_STAT_UNSTAGED:
5965 case LINE_STAT_UNTRACKED:
5966 return io_run(io, others_argv, opt_cdup, IO_WR);
5968 default:
5969 die("line type %d not handled in switch", type);
5970 return FALSE;
5971 }
5972 }
5974 static bool
5975 status_update_write(struct io *io, struct status *status, enum line_type type)
5976 {
5977 char buf[SIZEOF_STR];
5978 size_t bufsize = 0;
5980 switch (type) {
5981 case LINE_STAT_STAGED:
5982 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5983 status->old.mode,
5984 status->old.rev,
5985 status->old.name, 0))
5986 return FALSE;
5987 break;
5989 case LINE_STAT_UNSTAGED:
5990 case LINE_STAT_UNTRACKED:
5991 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5992 return FALSE;
5993 break;
5995 default:
5996 die("line type %d not handled in switch", type);
5997 }
5999 return io_write(io, buf, bufsize);
6000 }
6002 static bool
6003 status_update_file(struct status *status, enum line_type type)
6004 {
6005 struct io io = {};
6006 bool result;
6008 if (!status_update_prepare(&io, type))
6009 return FALSE;
6011 result = status_update_write(&io, status, type);
6012 return io_done(&io) && result;
6013 }
6015 static bool
6016 status_update_files(struct view *view, struct line *line)
6017 {
6018 char buf[sizeof(view->ref)];
6019 struct io io = {};
6020 bool result = TRUE;
6021 struct line *pos = view->line + view->lines;
6022 int files = 0;
6023 int file, done;
6024 int cursor_y = -1, cursor_x = -1;
6026 if (!status_update_prepare(&io, line->type))
6027 return FALSE;
6029 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6030 files++;
6032 string_copy(buf, view->ref);
6033 getsyx(cursor_y, cursor_x);
6034 for (file = 0, done = 5; result && file < files; line++, file++) {
6035 int almost_done = file * 100 / files;
6037 if (almost_done > done) {
6038 done = almost_done;
6039 string_format(view->ref, "updating file %u of %u (%d%% done)",
6040 file, files, done);
6041 update_view_title(view);
6042 setsyx(cursor_y, cursor_x);
6043 doupdate();
6044 }
6045 result = status_update_write(&io, line->data, line->type);
6046 }
6047 string_copy(view->ref, buf);
6049 return io_done(&io) && result;
6050 }
6052 static bool
6053 status_update(struct view *view)
6054 {
6055 struct line *line = &view->line[view->lineno];
6057 assert(view->lines);
6059 if (!line->data) {
6060 /* This should work even for the "On branch" line. */
6061 if (line < view->line + view->lines && !line[1].data) {
6062 report("Nothing to update");
6063 return FALSE;
6064 }
6066 if (!status_update_files(view, line + 1)) {
6067 report("Failed to update file status");
6068 return FALSE;
6069 }
6071 } else if (!status_update_file(line->data, line->type)) {
6072 report("Failed to update file status");
6073 return FALSE;
6074 }
6076 return TRUE;
6077 }
6079 static bool
6080 status_revert(struct status *status, enum line_type type, bool has_none)
6081 {
6082 if (!status || type != LINE_STAT_UNSTAGED) {
6083 if (type == LINE_STAT_STAGED) {
6084 report("Cannot revert changes to staged files");
6085 } else if (type == LINE_STAT_UNTRACKED) {
6086 report("Cannot revert changes to untracked files");
6087 } else if (has_none) {
6088 report("Nothing to revert");
6089 } else {
6090 report("Cannot revert changes to multiple files");
6091 }
6093 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6094 char mode[10] = "100644";
6095 const char *reset_argv[] = {
6096 "git", "update-index", "--cacheinfo", mode,
6097 status->old.rev, status->old.name, NULL
6098 };
6099 const char *checkout_argv[] = {
6100 "git", "checkout", "--", status->old.name, NULL
6101 };
6103 if (status->status == 'U') {
6104 string_format(mode, "%5o", status->old.mode);
6106 if (status->old.mode == 0 && status->new.mode == 0) {
6107 reset_argv[2] = "--force-remove";
6108 reset_argv[3] = status->old.name;
6109 reset_argv[4] = NULL;
6110 }
6112 if (!io_run_fg(reset_argv, opt_cdup))
6113 return FALSE;
6114 if (status->old.mode == 0 && status->new.mode == 0)
6115 return TRUE;
6116 }
6118 return io_run_fg(checkout_argv, opt_cdup);
6119 }
6121 return FALSE;
6122 }
6124 static enum request
6125 status_request(struct view *view, enum request request, struct line *line)
6126 {
6127 struct status *status = line->data;
6129 switch (request) {
6130 case REQ_STATUS_UPDATE:
6131 if (!status_update(view))
6132 return REQ_NONE;
6133 break;
6135 case REQ_STATUS_REVERT:
6136 if (!status_revert(status, line->type, status_has_none(view, line)))
6137 return REQ_NONE;
6138 break;
6140 case REQ_STATUS_MERGE:
6141 if (!status || status->status != 'U') {
6142 report("Merging only possible for files with unmerged status ('U').");
6143 return REQ_NONE;
6144 }
6145 open_mergetool(status->new.name);
6146 break;
6148 case REQ_EDIT:
6149 if (!status)
6150 return request;
6151 if (status->status == 'D') {
6152 report("File has been deleted.");
6153 return REQ_NONE;
6154 }
6156 open_editor(status->new.name);
6157 break;
6159 case REQ_VIEW_BLAME:
6160 if (status)
6161 opt_ref[0] = 0;
6162 return request;
6164 case REQ_ENTER:
6165 /* After returning the status view has been split to
6166 * show the stage view. No further reloading is
6167 * necessary. */
6168 return status_enter(view, line);
6170 case REQ_REFRESH:
6171 /* Simply reload the view. */
6172 break;
6174 default:
6175 return request;
6176 }
6178 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6180 return REQ_NONE;
6181 }
6183 static void
6184 status_select(struct view *view, struct line *line)
6185 {
6186 struct status *status = line->data;
6187 char file[SIZEOF_STR] = "all files";
6188 const char *text;
6189 const char *key;
6191 if (status && !string_format(file, "'%s'", status->new.name))
6192 return;
6194 if (!status && line[1].type == LINE_STAT_NONE)
6195 line++;
6197 switch (line->type) {
6198 case LINE_STAT_STAGED:
6199 text = "Press %s to unstage %s for commit";
6200 break;
6202 case LINE_STAT_UNSTAGED:
6203 text = "Press %s to stage %s for commit";
6204 break;
6206 case LINE_STAT_UNTRACKED:
6207 text = "Press %s to stage %s for addition";
6208 break;
6210 case LINE_STAT_HEAD:
6211 case LINE_STAT_NONE:
6212 text = "Nothing to update";
6213 break;
6215 default:
6216 die("line type %d not handled in switch", line->type);
6217 }
6219 if (status && status->status == 'U') {
6220 text = "Press %s to resolve conflict in %s";
6221 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6223 } else {
6224 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6225 }
6227 string_format(view->ref, text, key, file);
6228 if (status)
6229 string_copy(opt_file, status->new.name);
6230 }
6232 static bool
6233 status_grep(struct view *view, struct line *line)
6234 {
6235 struct status *status = line->data;
6237 if (status) {
6238 const char buf[2] = { status->status, 0 };
6239 const char *text[] = { status->new.name, buf, NULL };
6241 return grep_text(view, text);
6242 }
6244 return FALSE;
6245 }
6247 static struct view_ops status_ops = {
6248 "file",
6249 NULL,
6250 status_open,
6251 NULL,
6252 status_draw,
6253 status_request,
6254 status_grep,
6255 status_select,
6256 };
6259 static bool
6260 stage_diff_write(struct io *io, struct line *line, struct line *end)
6261 {
6262 while (line < end) {
6263 if (!io_write(io, line->data, strlen(line->data)) ||
6264 !io_write(io, "\n", 1))
6265 return FALSE;
6266 line++;
6267 if (line->type == LINE_DIFF_CHUNK ||
6268 line->type == LINE_DIFF_HEADER)
6269 break;
6270 }
6272 return TRUE;
6273 }
6275 static struct line *
6276 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6277 {
6278 for (; view->line < line; line--)
6279 if (line->type == type)
6280 return line;
6282 return NULL;
6283 }
6285 static bool
6286 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6287 {
6288 const char *apply_argv[SIZEOF_ARG] = {
6289 "git", "apply", "--whitespace=nowarn", NULL
6290 };
6291 struct line *diff_hdr;
6292 struct io io = {};
6293 int argc = 3;
6295 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6296 if (!diff_hdr)
6297 return FALSE;
6299 if (!revert)
6300 apply_argv[argc++] = "--cached";
6301 if (revert || stage_line_type == LINE_STAT_STAGED)
6302 apply_argv[argc++] = "-R";
6303 apply_argv[argc++] = "-";
6304 apply_argv[argc++] = NULL;
6305 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6306 return FALSE;
6308 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6309 !stage_diff_write(&io, chunk, view->line + view->lines))
6310 chunk = NULL;
6312 io_done(&io);
6313 io_run_bg(update_index_argv);
6315 return chunk ? TRUE : FALSE;
6316 }
6318 static bool
6319 stage_update(struct view *view, struct line *line)
6320 {
6321 struct line *chunk = NULL;
6323 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6324 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6326 if (chunk) {
6327 if (!stage_apply_chunk(view, chunk, FALSE)) {
6328 report("Failed to apply chunk");
6329 return FALSE;
6330 }
6332 } else if (!stage_status.status) {
6333 view = VIEW(REQ_VIEW_STATUS);
6335 for (line = view->line; line < view->line + view->lines; line++)
6336 if (line->type == stage_line_type)
6337 break;
6339 if (!status_update_files(view, line + 1)) {
6340 report("Failed to update files");
6341 return FALSE;
6342 }
6344 } else if (!status_update_file(&stage_status, stage_line_type)) {
6345 report("Failed to update file");
6346 return FALSE;
6347 }
6349 return TRUE;
6350 }
6352 static bool
6353 stage_revert(struct view *view, struct line *line)
6354 {
6355 struct line *chunk = NULL;
6357 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6358 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6360 if (chunk) {
6361 if (!prompt_yesno("Are you sure you want to revert changes?"))
6362 return FALSE;
6364 if (!stage_apply_chunk(view, chunk, TRUE)) {
6365 report("Failed to revert chunk");
6366 return FALSE;
6367 }
6368 return TRUE;
6370 } else {
6371 return status_revert(stage_status.status ? &stage_status : NULL,
6372 stage_line_type, FALSE);
6373 }
6374 }
6377 static void
6378 stage_next(struct view *view, struct line *line)
6379 {
6380 int i;
6382 if (!stage_chunks) {
6383 for (line = view->line; line < view->line + view->lines; line++) {
6384 if (line->type != LINE_DIFF_CHUNK)
6385 continue;
6387 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6388 report("Allocation failure");
6389 return;
6390 }
6392 stage_chunk[stage_chunks++] = line - view->line;
6393 }
6394 }
6396 for (i = 0; i < stage_chunks; i++) {
6397 if (stage_chunk[i] > view->lineno) {
6398 do_scroll_view(view, stage_chunk[i] - view->lineno);
6399 report("Chunk %d of %d", i + 1, stage_chunks);
6400 return;
6401 }
6402 }
6404 report("No next chunk found");
6405 }
6407 static enum request
6408 stage_request(struct view *view, enum request request, struct line *line)
6409 {
6410 switch (request) {
6411 case REQ_STATUS_UPDATE:
6412 if (!stage_update(view, line))
6413 return REQ_NONE;
6414 break;
6416 case REQ_STATUS_REVERT:
6417 if (!stage_revert(view, line))
6418 return REQ_NONE;
6419 break;
6421 case REQ_STAGE_NEXT:
6422 if (stage_line_type == LINE_STAT_UNTRACKED) {
6423 report("File is untracked; press %s to add",
6424 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6425 return REQ_NONE;
6426 }
6427 stage_next(view, line);
6428 return REQ_NONE;
6430 case REQ_EDIT:
6431 if (!stage_status.new.name[0])
6432 return request;
6433 if (stage_status.status == 'D') {
6434 report("File has been deleted.");
6435 return REQ_NONE;
6436 }
6438 open_editor(stage_status.new.name);
6439 break;
6441 case REQ_REFRESH:
6442 /* Reload everything ... */
6443 break;
6445 case REQ_VIEW_BLAME:
6446 if (stage_status.new.name[0]) {
6447 string_copy(opt_file, stage_status.new.name);
6448 opt_ref[0] = 0;
6449 }
6450 return request;
6452 case REQ_ENTER:
6453 return pager_request(view, request, line);
6455 default:
6456 return request;
6457 }
6459 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6460 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6462 /* Check whether the staged entry still exists, and close the
6463 * stage view if it doesn't. */
6464 if (!status_exists(&stage_status, stage_line_type)) {
6465 status_restore(VIEW(REQ_VIEW_STATUS));
6466 return REQ_VIEW_CLOSE;
6467 }
6469 if (stage_line_type == LINE_STAT_UNTRACKED) {
6470 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6471 report("Cannot display a directory");
6472 return REQ_NONE;
6473 }
6475 if (!prepare_update_file(view, stage_status.new.name)) {
6476 report("Failed to open file: %s", strerror(errno));
6477 return REQ_NONE;
6478 }
6479 }
6480 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6482 return REQ_NONE;
6483 }
6485 static struct view_ops stage_ops = {
6486 "line",
6487 NULL,
6488 NULL,
6489 pager_read,
6490 pager_draw,
6491 stage_request,
6492 pager_grep,
6493 pager_select,
6494 };
6497 /*
6498 * Revision graph
6499 */
6501 struct commit {
6502 char id[SIZEOF_REV]; /* SHA1 ID. */
6503 char title[128]; /* First line of the commit message. */
6504 const char *author; /* Author of the commit. */
6505 struct time time; /* Date from the author ident. */
6506 struct ref_list *refs; /* Repository references. */
6507 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6508 size_t graph_size; /* The width of the graph array. */
6509 bool has_parents; /* Rewritten --parents seen. */
6510 };
6512 /* Size of rev graph with no "padding" columns */
6513 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6515 struct rev_graph {
6516 struct rev_graph *prev, *next, *parents;
6517 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6518 size_t size;
6519 struct commit *commit;
6520 size_t pos;
6521 unsigned int boundary:1;
6522 };
6524 /* Parents of the commit being visualized. */
6525 static struct rev_graph graph_parents[4];
6527 /* The current stack of revisions on the graph. */
6528 static struct rev_graph graph_stacks[4] = {
6529 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6530 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6531 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6532 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6533 };
6535 static inline bool
6536 graph_parent_is_merge(struct rev_graph *graph)
6537 {
6538 return graph->parents->size > 1;
6539 }
6541 static inline void
6542 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6543 {
6544 struct commit *commit = graph->commit;
6546 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6547 commit->graph[commit->graph_size++] = symbol;
6548 }
6550 static void
6551 clear_rev_graph(struct rev_graph *graph)
6552 {
6553 graph->boundary = 0;
6554 graph->size = graph->pos = 0;
6555 graph->commit = NULL;
6556 memset(graph->parents, 0, sizeof(*graph->parents));
6557 }
6559 static void
6560 done_rev_graph(struct rev_graph *graph)
6561 {
6562 if (graph_parent_is_merge(graph) &&
6563 graph->pos < graph->size - 1 &&
6564 graph->next->size == graph->size + graph->parents->size - 1) {
6565 size_t i = graph->pos + graph->parents->size - 1;
6567 graph->commit->graph_size = i * 2;
6568 while (i < graph->next->size - 1) {
6569 append_to_rev_graph(graph, ' ');
6570 append_to_rev_graph(graph, '\\');
6571 i++;
6572 }
6573 }
6575 clear_rev_graph(graph);
6576 }
6578 static void
6579 push_rev_graph(struct rev_graph *graph, const char *parent)
6580 {
6581 int i;
6583 /* "Collapse" duplicate parents lines.
6584 *
6585 * FIXME: This needs to also update update the drawn graph but
6586 * for now it just serves as a method for pruning graph lines. */
6587 for (i = 0; i < graph->size; i++)
6588 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6589 return;
6591 if (graph->size < SIZEOF_REVITEMS) {
6592 string_copy_rev(graph->rev[graph->size++], parent);
6593 }
6594 }
6596 static chtype
6597 get_rev_graph_symbol(struct rev_graph *graph)
6598 {
6599 chtype symbol;
6601 if (graph->boundary)
6602 symbol = REVGRAPH_BOUND;
6603 else if (graph->parents->size == 0)
6604 symbol = REVGRAPH_INIT;
6605 else if (graph_parent_is_merge(graph))
6606 symbol = REVGRAPH_MERGE;
6607 else if (graph->pos >= graph->size)
6608 symbol = REVGRAPH_BRANCH;
6609 else
6610 symbol = REVGRAPH_COMMIT;
6612 return symbol;
6613 }
6615 static void
6616 draw_rev_graph(struct rev_graph *graph)
6617 {
6618 struct rev_filler {
6619 chtype separator, line;
6620 };
6621 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6622 static struct rev_filler fillers[] = {
6623 { ' ', '|' },
6624 { '`', '.' },
6625 { '\'', ' ' },
6626 { '/', ' ' },
6627 };
6628 chtype symbol = get_rev_graph_symbol(graph);
6629 struct rev_filler *filler;
6630 size_t i;
6632 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6633 filler = &fillers[DEFAULT];
6635 for (i = 0; i < graph->pos; i++) {
6636 append_to_rev_graph(graph, filler->line);
6637 if (graph_parent_is_merge(graph->prev) &&
6638 graph->prev->pos == i)
6639 filler = &fillers[RSHARP];
6641 append_to_rev_graph(graph, filler->separator);
6642 }
6644 /* Place the symbol for this revision. */
6645 append_to_rev_graph(graph, symbol);
6647 if (graph->prev->size > graph->size)
6648 filler = &fillers[RDIAG];
6649 else
6650 filler = &fillers[DEFAULT];
6652 i++;
6654 for (; i < graph->size; i++) {
6655 append_to_rev_graph(graph, filler->separator);
6656 append_to_rev_graph(graph, filler->line);
6657 if (graph_parent_is_merge(graph->prev) &&
6658 i < graph->prev->pos + graph->parents->size)
6659 filler = &fillers[RSHARP];
6660 if (graph->prev->size > graph->size)
6661 filler = &fillers[LDIAG];
6662 }
6664 if (graph->prev->size > graph->size) {
6665 append_to_rev_graph(graph, filler->separator);
6666 if (filler->line != ' ')
6667 append_to_rev_graph(graph, filler->line);
6668 }
6669 }
6671 /* Prepare the next rev graph */
6672 static void
6673 prepare_rev_graph(struct rev_graph *graph)
6674 {
6675 size_t i;
6677 /* First, traverse all lines of revisions up to the active one. */
6678 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6679 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6680 break;
6682 push_rev_graph(graph->next, graph->rev[graph->pos]);
6683 }
6685 /* Interleave the new revision parent(s). */
6686 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6687 push_rev_graph(graph->next, graph->parents->rev[i]);
6689 /* Lastly, put any remaining revisions. */
6690 for (i = graph->pos + 1; i < graph->size; i++)
6691 push_rev_graph(graph->next, graph->rev[i]);
6692 }
6694 static void
6695 update_rev_graph(struct view *view, struct rev_graph *graph)
6696 {
6697 /* If this is the finalizing update ... */
6698 if (graph->commit)
6699 prepare_rev_graph(graph);
6701 /* Graph visualization needs a one rev look-ahead,
6702 * so the first update doesn't visualize anything. */
6703 if (!graph->prev->commit)
6704 return;
6706 if (view->lines > 2)
6707 view->line[view->lines - 3].dirty = 1;
6708 if (view->lines > 1)
6709 view->line[view->lines - 2].dirty = 1;
6710 draw_rev_graph(graph->prev);
6711 done_rev_graph(graph->prev->prev);
6712 }
6715 /*
6716 * Main view backend
6717 */
6719 static const char *main_argv[SIZEOF_ARG] = {
6720 "git", "log", "--no-color", "--pretty=raw", "--parents",
6721 "--topo-order", "%(head)", NULL
6722 };
6724 static bool
6725 main_draw(struct view *view, struct line *line, unsigned int lineno)
6726 {
6727 struct commit *commit = line->data;
6729 if (!commit->author)
6730 return FALSE;
6732 if (opt_date && draw_date(view, &commit->time))
6733 return TRUE;
6735 if (opt_author && draw_author(view, commit->author))
6736 return TRUE;
6738 if (opt_rev_graph && commit->graph_size &&
6739 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6740 return TRUE;
6742 if (opt_show_refs && commit->refs) {
6743 size_t i;
6745 for (i = 0; i < commit->refs->size; i++) {
6746 struct ref *ref = commit->refs->refs[i];
6747 enum line_type type;
6749 if (ref->head)
6750 type = LINE_MAIN_HEAD;
6751 else if (ref->ltag)
6752 type = LINE_MAIN_LOCAL_TAG;
6753 else if (ref->tag)
6754 type = LINE_MAIN_TAG;
6755 else if (ref->tracked)
6756 type = LINE_MAIN_TRACKED;
6757 else if (ref->remote)
6758 type = LINE_MAIN_REMOTE;
6759 else
6760 type = LINE_MAIN_REF;
6762 if (draw_text(view, type, "[", TRUE) ||
6763 draw_text(view, type, ref->name, TRUE) ||
6764 draw_text(view, type, "]", TRUE))
6765 return TRUE;
6767 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6768 return TRUE;
6769 }
6770 }
6772 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6773 return TRUE;
6774 }
6776 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6777 static bool
6778 main_read(struct view *view, char *line)
6779 {
6780 static struct rev_graph *graph = graph_stacks;
6781 enum line_type type;
6782 struct commit *commit;
6784 if (!line) {
6785 int i;
6787 if (!view->lines && !view->prev)
6788 die("No revisions match the given arguments.");
6789 if (view->lines > 0) {
6790 commit = view->line[view->lines - 1].data;
6791 view->line[view->lines - 1].dirty = 1;
6792 if (!commit->author) {
6793 view->lines--;
6794 free(commit);
6795 graph->commit = NULL;
6796 }
6797 }
6798 update_rev_graph(view, graph);
6800 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6801 clear_rev_graph(&graph_stacks[i]);
6802 return TRUE;
6803 }
6805 type = get_line_type(line);
6806 if (type == LINE_COMMIT) {
6807 commit = calloc(1, sizeof(struct commit));
6808 if (!commit)
6809 return FALSE;
6811 line += STRING_SIZE("commit ");
6812 if (*line == '-') {
6813 graph->boundary = 1;
6814 line++;
6815 }
6817 string_copy_rev(commit->id, line);
6818 commit->refs = get_ref_list(commit->id);
6819 graph->commit = commit;
6820 add_line_data(view, commit, LINE_MAIN_COMMIT);
6822 while ((line = strchr(line, ' '))) {
6823 line++;
6824 push_rev_graph(graph->parents, line);
6825 commit->has_parents = TRUE;
6826 }
6827 return TRUE;
6828 }
6830 if (!view->lines)
6831 return TRUE;
6832 commit = view->line[view->lines - 1].data;
6834 switch (type) {
6835 case LINE_PARENT:
6836 if (commit->has_parents)
6837 break;
6838 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6839 break;
6841 case LINE_AUTHOR:
6842 parse_author_line(line + STRING_SIZE("author "),
6843 &commit->author, &commit->time);
6844 update_rev_graph(view, graph);
6845 graph = graph->next;
6846 break;
6848 default:
6849 /* Fill in the commit title if it has not already been set. */
6850 if (commit->title[0])
6851 break;
6853 /* Require titles to start with a non-space character at the
6854 * offset used by git log. */
6855 if (strncmp(line, " ", 4))
6856 break;
6857 line += 4;
6858 /* Well, if the title starts with a whitespace character,
6859 * try to be forgiving. Otherwise we end up with no title. */
6860 while (isspace(*line))
6861 line++;
6862 if (*line == '\0')
6863 break;
6864 /* FIXME: More graceful handling of titles; append "..." to
6865 * shortened titles, etc. */
6867 string_expand(commit->title, sizeof(commit->title), line, 1);
6868 view->line[view->lines - 1].dirty = 1;
6869 }
6871 return TRUE;
6872 }
6874 static enum request
6875 main_request(struct view *view, enum request request, struct line *line)
6876 {
6877 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6879 switch (request) {
6880 case REQ_ENTER:
6881 open_view(view, REQ_VIEW_DIFF, flags);
6882 break;
6883 case REQ_REFRESH:
6884 load_refs();
6885 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6886 break;
6887 default:
6888 return request;
6889 }
6891 return REQ_NONE;
6892 }
6894 static bool
6895 grep_refs(struct ref_list *list, regex_t *regex)
6896 {
6897 regmatch_t pmatch;
6898 size_t i;
6900 if (!opt_show_refs || !list)
6901 return FALSE;
6903 for (i = 0; i < list->size; i++) {
6904 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6905 return TRUE;
6906 }
6908 return FALSE;
6909 }
6911 static bool
6912 main_grep(struct view *view, struct line *line)
6913 {
6914 struct commit *commit = line->data;
6915 const char *text[] = {
6916 commit->title,
6917 opt_author ? commit->author : "",
6918 mkdate(&commit->time, opt_date),
6919 NULL
6920 };
6922 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6923 }
6925 static void
6926 main_select(struct view *view, struct line *line)
6927 {
6928 struct commit *commit = line->data;
6930 string_copy_rev(view->ref, commit->id);
6931 string_copy_rev(ref_commit, view->ref);
6932 }
6934 static struct view_ops main_ops = {
6935 "commit",
6936 main_argv,
6937 NULL,
6938 main_read,
6939 main_draw,
6940 main_request,
6941 main_grep,
6942 main_select,
6943 };
6946 /*
6947 * Status management
6948 */
6950 /* Whether or not the curses interface has been initialized. */
6951 static bool cursed = FALSE;
6953 /* Terminal hacks and workarounds. */
6954 static bool use_scroll_redrawwin;
6955 static bool use_scroll_status_wclear;
6957 /* The status window is used for polling keystrokes. */
6958 static WINDOW *status_win;
6960 /* Reading from the prompt? */
6961 static bool input_mode = FALSE;
6963 static bool status_empty = FALSE;
6965 /* Update status and title window. */
6966 static void
6967 report(const char *msg, ...)
6968 {
6969 struct view *view = display[current_view];
6971 if (input_mode)
6972 return;
6974 if (!view) {
6975 char buf[SIZEOF_STR];
6976 va_list args;
6978 va_start(args, msg);
6979 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6980 buf[sizeof(buf) - 1] = 0;
6981 buf[sizeof(buf) - 2] = '.';
6982 buf[sizeof(buf) - 3] = '.';
6983 buf[sizeof(buf) - 4] = '.';
6984 }
6985 va_end(args);
6986 die("%s", buf);
6987 }
6989 if (!status_empty || *msg) {
6990 va_list args;
6992 va_start(args, msg);
6994 wmove(status_win, 0, 0);
6995 if (view->has_scrolled && use_scroll_status_wclear)
6996 wclear(status_win);
6997 if (*msg) {
6998 vwprintw(status_win, msg, args);
6999 status_empty = FALSE;
7000 } else {
7001 status_empty = TRUE;
7002 }
7003 wclrtoeol(status_win);
7004 wnoutrefresh(status_win);
7006 va_end(args);
7007 }
7009 update_view_title(view);
7010 }
7012 static void
7013 init_display(void)
7014 {
7015 const char *term;
7016 int x, y;
7018 /* Initialize the curses library */
7019 if (isatty(STDIN_FILENO)) {
7020 cursed = !!initscr();
7021 opt_tty = stdin;
7022 } else {
7023 /* Leave stdin and stdout alone when acting as a pager. */
7024 opt_tty = fopen("/dev/tty", "r+");
7025 if (!opt_tty)
7026 die("Failed to open /dev/tty");
7027 cursed = !!newterm(NULL, opt_tty, opt_tty);
7028 }
7030 if (!cursed)
7031 die("Failed to initialize curses");
7033 nonl(); /* Disable conversion and detect newlines from input. */
7034 cbreak(); /* Take input chars one at a time, no wait for \n */
7035 noecho(); /* Don't echo input */
7036 leaveok(stdscr, FALSE);
7038 if (has_colors())
7039 init_colors();
7041 getmaxyx(stdscr, y, x);
7042 status_win = newwin(1, 0, y - 1, 0);
7043 if (!status_win)
7044 die("Failed to create status window");
7046 /* Enable keyboard mapping */
7047 keypad(status_win, TRUE);
7048 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7050 TABSIZE = opt_tab_size;
7052 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7053 if (term && !strcmp(term, "gnome-terminal")) {
7054 /* In the gnome-terminal-emulator, the message from
7055 * scrolling up one line when impossible followed by
7056 * scrolling down one line causes corruption of the
7057 * status line. This is fixed by calling wclear. */
7058 use_scroll_status_wclear = TRUE;
7059 use_scroll_redrawwin = FALSE;
7061 } else if (term && !strcmp(term, "xrvt-xpm")) {
7062 /* No problems with full optimizations in xrvt-(unicode)
7063 * and aterm. */
7064 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7066 } else {
7067 /* When scrolling in (u)xterm the last line in the
7068 * scrolling direction will update slowly. */
7069 use_scroll_redrawwin = TRUE;
7070 use_scroll_status_wclear = FALSE;
7071 }
7072 }
7074 static int
7075 get_input(int prompt_position)
7076 {
7077 struct view *view;
7078 int i, key, cursor_y, cursor_x;
7079 bool loading = FALSE;
7081 if (prompt_position)
7082 input_mode = TRUE;
7084 while (TRUE) {
7085 foreach_view (view, i) {
7086 update_view(view);
7087 if (view_is_displayed(view) && view->has_scrolled &&
7088 use_scroll_redrawwin)
7089 redrawwin(view->win);
7090 view->has_scrolled = FALSE;
7091 if (view->pipe)
7092 loading = TRUE;
7093 }
7095 /* Update the cursor position. */
7096 if (prompt_position) {
7097 getbegyx(status_win, cursor_y, cursor_x);
7098 cursor_x = prompt_position;
7099 } else {
7100 view = display[current_view];
7101 getbegyx(view->win, cursor_y, cursor_x);
7102 cursor_x = view->width - 1;
7103 cursor_y += view->lineno - view->offset;
7104 }
7105 setsyx(cursor_y, cursor_x);
7107 /* Refresh, accept single keystroke of input */
7108 doupdate();
7109 nodelay(status_win, loading);
7110 key = wgetch(status_win);
7112 /* wgetch() with nodelay() enabled returns ERR when
7113 * there's no input. */
7114 if (key == ERR) {
7116 } else if (key == KEY_RESIZE) {
7117 int height, width;
7119 getmaxyx(stdscr, height, width);
7121 wresize(status_win, 1, width);
7122 mvwin(status_win, height - 1, 0);
7123 wnoutrefresh(status_win);
7124 resize_display();
7125 redraw_display(TRUE);
7127 } else {
7128 input_mode = FALSE;
7129 return key;
7130 }
7131 }
7132 }
7134 static char *
7135 prompt_input(const char *prompt, input_handler handler, void *data)
7136 {
7137 enum input_status status = INPUT_OK;
7138 static char buf[SIZEOF_STR];
7139 size_t pos = 0;
7141 buf[pos] = 0;
7143 while (status == INPUT_OK || status == INPUT_SKIP) {
7144 int key;
7146 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7147 wclrtoeol(status_win);
7149 key = get_input(pos + 1);
7150 switch (key) {
7151 case KEY_RETURN:
7152 case KEY_ENTER:
7153 case '\n':
7154 status = pos ? INPUT_STOP : INPUT_CANCEL;
7155 break;
7157 case KEY_BACKSPACE:
7158 if (pos > 0)
7159 buf[--pos] = 0;
7160 else
7161 status = INPUT_CANCEL;
7162 break;
7164 case KEY_ESC:
7165 status = INPUT_CANCEL;
7166 break;
7168 default:
7169 if (pos >= sizeof(buf)) {
7170 report("Input string too long");
7171 return NULL;
7172 }
7174 status = handler(data, buf, key);
7175 if (status == INPUT_OK)
7176 buf[pos++] = (char) key;
7177 }
7178 }
7180 /* Clear the status window */
7181 status_empty = FALSE;
7182 report("");
7184 if (status == INPUT_CANCEL)
7185 return NULL;
7187 buf[pos++] = 0;
7189 return buf;
7190 }
7192 static enum input_status
7193 prompt_yesno_handler(void *data, char *buf, int c)
7194 {
7195 if (c == 'y' || c == 'Y')
7196 return INPUT_STOP;
7197 if (c == 'n' || c == 'N')
7198 return INPUT_CANCEL;
7199 return INPUT_SKIP;
7200 }
7202 static bool
7203 prompt_yesno(const char *prompt)
7204 {
7205 char prompt2[SIZEOF_STR];
7207 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7208 return FALSE;
7210 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7211 }
7213 static enum input_status
7214 read_prompt_handler(void *data, char *buf, int c)
7215 {
7216 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7217 }
7219 static char *
7220 read_prompt(const char *prompt)
7221 {
7222 return prompt_input(prompt, read_prompt_handler, NULL);
7223 }
7225 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7226 {
7227 enum input_status status = INPUT_OK;
7228 int size = 0;
7230 while (items[size].text)
7231 size++;
7233 while (status == INPUT_OK) {
7234 const struct menu_item *item = &items[*selected];
7235 int key;
7236 int i;
7238 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7239 prompt, *selected + 1, size);
7240 if (item->hotkey)
7241 wprintw(status_win, "[%c] ", (char) item->hotkey);
7242 wprintw(status_win, "%s", item->text);
7243 wclrtoeol(status_win);
7245 key = get_input(COLS - 1);
7246 switch (key) {
7247 case KEY_RETURN:
7248 case KEY_ENTER:
7249 case '\n':
7250 status = INPUT_STOP;
7251 break;
7253 case KEY_LEFT:
7254 case KEY_UP:
7255 *selected = *selected - 1;
7256 if (*selected < 0)
7257 *selected = size - 1;
7258 break;
7260 case KEY_RIGHT:
7261 case KEY_DOWN:
7262 *selected = (*selected + 1) % size;
7263 break;
7265 case KEY_ESC:
7266 status = INPUT_CANCEL;
7267 break;
7269 default:
7270 for (i = 0; items[i].text; i++)
7271 if (items[i].hotkey == key) {
7272 *selected = i;
7273 status = INPUT_STOP;
7274 break;
7275 }
7276 }
7277 }
7279 /* Clear the status window */
7280 status_empty = FALSE;
7281 report("");
7283 return status != INPUT_CANCEL;
7284 }
7286 /*
7287 * Repository properties
7288 */
7290 static struct ref **refs = NULL;
7291 static size_t refs_size = 0;
7292 static struct ref *refs_head = NULL;
7294 static struct ref_list **ref_lists = NULL;
7295 static size_t ref_lists_size = 0;
7297 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7298 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7299 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7301 static int
7302 compare_refs(const void *ref1_, const void *ref2_)
7303 {
7304 const struct ref *ref1 = *(const struct ref **)ref1_;
7305 const struct ref *ref2 = *(const struct ref **)ref2_;
7307 if (ref1->tag != ref2->tag)
7308 return ref2->tag - ref1->tag;
7309 if (ref1->ltag != ref2->ltag)
7310 return ref2->ltag - ref2->ltag;
7311 if (ref1->head != ref2->head)
7312 return ref2->head - ref1->head;
7313 if (ref1->tracked != ref2->tracked)
7314 return ref2->tracked - ref1->tracked;
7315 if (ref1->remote != ref2->remote)
7316 return ref2->remote - ref1->remote;
7317 return strcmp(ref1->name, ref2->name);
7318 }
7320 static void
7321 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7322 {
7323 size_t i;
7325 for (i = 0; i < refs_size; i++)
7326 if (!visitor(data, refs[i]))
7327 break;
7328 }
7330 static struct ref *
7331 get_ref_head()
7332 {
7333 return refs_head;
7334 }
7336 static struct ref_list *
7337 get_ref_list(const char *id)
7338 {
7339 struct ref_list *list;
7340 size_t i;
7342 for (i = 0; i < ref_lists_size; i++)
7343 if (!strcmp(id, ref_lists[i]->id))
7344 return ref_lists[i];
7346 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7347 return NULL;
7348 list = calloc(1, sizeof(*list));
7349 if (!list)
7350 return NULL;
7352 for (i = 0; i < refs_size; i++) {
7353 if (!strcmp(id, refs[i]->id) &&
7354 realloc_refs_list(&list->refs, list->size, 1))
7355 list->refs[list->size++] = refs[i];
7356 }
7358 if (!list->refs) {
7359 free(list);
7360 return NULL;
7361 }
7363 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7364 ref_lists[ref_lists_size++] = list;
7365 return list;
7366 }
7368 static int
7369 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7370 {
7371 struct ref *ref = NULL;
7372 bool tag = FALSE;
7373 bool ltag = FALSE;
7374 bool remote = FALSE;
7375 bool tracked = FALSE;
7376 bool head = FALSE;
7377 int from = 0, to = refs_size - 1;
7379 if (!prefixcmp(name, "refs/tags/")) {
7380 if (!suffixcmp(name, namelen, "^{}")) {
7381 namelen -= 3;
7382 name[namelen] = 0;
7383 } else {
7384 ltag = TRUE;
7385 }
7387 tag = TRUE;
7388 namelen -= STRING_SIZE("refs/tags/");
7389 name += STRING_SIZE("refs/tags/");
7391 } else if (!prefixcmp(name, "refs/remotes/")) {
7392 remote = TRUE;
7393 namelen -= STRING_SIZE("refs/remotes/");
7394 name += STRING_SIZE("refs/remotes/");
7395 tracked = !strcmp(opt_remote, name);
7397 } else if (!prefixcmp(name, "refs/heads/")) {
7398 namelen -= STRING_SIZE("refs/heads/");
7399 name += STRING_SIZE("refs/heads/");
7400 if (!strncmp(opt_head, name, namelen))
7401 return OK;
7403 } else if (!strcmp(name, "HEAD")) {
7404 head = TRUE;
7405 if (*opt_head) {
7406 namelen = strlen(opt_head);
7407 name = opt_head;
7408 }
7409 }
7411 /* If we are reloading or it's an annotated tag, replace the
7412 * previous SHA1 with the resolved commit id; relies on the fact
7413 * git-ls-remote lists the commit id of an annotated tag right
7414 * before the commit id it points to. */
7415 while (from <= to) {
7416 size_t pos = (to + from) / 2;
7417 int cmp = strcmp(name, refs[pos]->name);
7419 if (!cmp) {
7420 ref = refs[pos];
7421 break;
7422 }
7424 if (cmp < 0)
7425 to = pos - 1;
7426 else
7427 from = pos + 1;
7428 }
7430 if (!ref) {
7431 if (!realloc_refs(&refs, refs_size, 1))
7432 return ERR;
7433 ref = calloc(1, sizeof(*ref) + namelen);
7434 if (!ref)
7435 return ERR;
7436 memmove(refs + from + 1, refs + from,
7437 (refs_size - from) * sizeof(*refs));
7438 refs[from] = ref;
7439 strncpy(ref->name, name, namelen);
7440 refs_size++;
7441 }
7443 ref->head = head;
7444 ref->tag = tag;
7445 ref->ltag = ltag;
7446 ref->remote = remote;
7447 ref->tracked = tracked;
7448 string_copy_rev(ref->id, id);
7450 if (head)
7451 refs_head = ref;
7452 return OK;
7453 }
7455 static int
7456 load_refs(void)
7457 {
7458 const char *head_argv[] = {
7459 "git", "symbolic-ref", "HEAD", NULL
7460 };
7461 static const char *ls_remote_argv[SIZEOF_ARG] = {
7462 "git", "ls-remote", opt_git_dir, NULL
7463 };
7464 static bool init = FALSE;
7465 size_t i;
7467 if (!init) {
7468 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7469 die("TIG_LS_REMOTE contains too many arguments");
7470 init = TRUE;
7471 }
7473 if (!*opt_git_dir)
7474 return OK;
7476 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7477 !prefixcmp(opt_head, "refs/heads/")) {
7478 char *offset = opt_head + STRING_SIZE("refs/heads/");
7480 memmove(opt_head, offset, strlen(offset) + 1);
7481 }
7483 refs_head = NULL;
7484 for (i = 0; i < refs_size; i++)
7485 refs[i]->id[0] = 0;
7487 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7488 return ERR;
7490 /* Update the ref lists to reflect changes. */
7491 for (i = 0; i < ref_lists_size; i++) {
7492 struct ref_list *list = ref_lists[i];
7493 size_t old, new;
7495 for (old = new = 0; old < list->size; old++)
7496 if (!strcmp(list->id, list->refs[old]->id))
7497 list->refs[new++] = list->refs[old];
7498 list->size = new;
7499 }
7501 return OK;
7502 }
7504 static void
7505 set_remote_branch(const char *name, const char *value, size_t valuelen)
7506 {
7507 if (!strcmp(name, ".remote")) {
7508 string_ncopy(opt_remote, value, valuelen);
7510 } else if (*opt_remote && !strcmp(name, ".merge")) {
7511 size_t from = strlen(opt_remote);
7513 if (!prefixcmp(value, "refs/heads/"))
7514 value += STRING_SIZE("refs/heads/");
7516 if (!string_format_from(opt_remote, &from, "/%s", value))
7517 opt_remote[0] = 0;
7518 }
7519 }
7521 static void
7522 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7523 {
7524 const char *argv[SIZEOF_ARG] = { name, "=" };
7525 int argc = 1 + (cmd == option_set_command);
7526 int error = ERR;
7528 if (!argv_from_string(argv, &argc, value))
7529 config_msg = "Too many option arguments";
7530 else
7531 error = cmd(argc, argv);
7533 if (error == ERR)
7534 warn("Option 'tig.%s': %s", name, config_msg);
7535 }
7537 static bool
7538 set_environment_variable(const char *name, const char *value)
7539 {
7540 size_t len = strlen(name) + 1 + strlen(value) + 1;
7541 char *env = malloc(len);
7543 if (env &&
7544 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7545 putenv(env) == 0)
7546 return TRUE;
7547 free(env);
7548 return FALSE;
7549 }
7551 static void
7552 set_work_tree(const char *value)
7553 {
7554 char cwd[SIZEOF_STR];
7556 if (!getcwd(cwd, sizeof(cwd)))
7557 die("Failed to get cwd path: %s", strerror(errno));
7558 if (chdir(opt_git_dir) < 0)
7559 die("Failed to chdir(%s): %s", strerror(errno));
7560 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7561 die("Failed to get git path: %s", strerror(errno));
7562 if (chdir(cwd) < 0)
7563 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7564 if (chdir(value) < 0)
7565 die("Failed to chdir(%s): %s", value, strerror(errno));
7566 if (!getcwd(cwd, sizeof(cwd)))
7567 die("Failed to get cwd path: %s", strerror(errno));
7568 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7569 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7570 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7571 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7572 opt_is_inside_work_tree = TRUE;
7573 }
7575 static int
7576 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7577 {
7578 if (!strcmp(name, "i18n.commitencoding"))
7579 string_ncopy(opt_encoding, value, valuelen);
7581 else if (!strcmp(name, "core.editor"))
7582 string_ncopy(opt_editor, value, valuelen);
7584 else if (!strcmp(name, "core.worktree"))
7585 set_work_tree(value);
7587 else if (!prefixcmp(name, "tig.color."))
7588 set_repo_config_option(name + 10, value, option_color_command);
7590 else if (!prefixcmp(name, "tig.bind."))
7591 set_repo_config_option(name + 9, value, option_bind_command);
7593 else if (!prefixcmp(name, "tig."))
7594 set_repo_config_option(name + 4, value, option_set_command);
7596 else if (*opt_head && !prefixcmp(name, "branch.") &&
7597 !strncmp(name + 7, opt_head, strlen(opt_head)))
7598 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7600 return OK;
7601 }
7603 static int
7604 load_git_config(void)
7605 {
7606 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7608 return io_run_load(config_list_argv, "=", read_repo_config_option);
7609 }
7611 static int
7612 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7613 {
7614 if (!opt_git_dir[0]) {
7615 string_ncopy(opt_git_dir, name, namelen);
7617 } else if (opt_is_inside_work_tree == -1) {
7618 /* This can be 3 different values depending on the
7619 * version of git being used. If git-rev-parse does not
7620 * understand --is-inside-work-tree it will simply echo
7621 * the option else either "true" or "false" is printed.
7622 * Default to true for the unknown case. */
7623 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7625 } else if (*name == '.') {
7626 string_ncopy(opt_cdup, name, namelen);
7628 } else {
7629 string_ncopy(opt_prefix, name, namelen);
7630 }
7632 return OK;
7633 }
7635 static int
7636 load_repo_info(void)
7637 {
7638 const char *rev_parse_argv[] = {
7639 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7640 "--show-cdup", "--show-prefix", NULL
7641 };
7643 return io_run_load(rev_parse_argv, "=", read_repo_info);
7644 }
7647 /*
7648 * Main
7649 */
7651 static const char usage[] =
7652 "tig " TIG_VERSION " (" __DATE__ ")\n"
7653 "\n"
7654 "Usage: tig [options] [revs] [--] [paths]\n"
7655 " or: tig show [options] [revs] [--] [paths]\n"
7656 " or: tig blame [rev] path\n"
7657 " or: tig status\n"
7658 " or: tig < [git command output]\n"
7659 "\n"
7660 "Options:\n"
7661 " -v, --version Show version and exit\n"
7662 " -h, --help Show help message and exit";
7664 static void __NORETURN
7665 quit(int sig)
7666 {
7667 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7668 if (cursed)
7669 endwin();
7670 exit(0);
7671 }
7673 static void __NORETURN
7674 die(const char *err, ...)
7675 {
7676 va_list args;
7678 endwin();
7680 va_start(args, err);
7681 fputs("tig: ", stderr);
7682 vfprintf(stderr, err, args);
7683 fputs("\n", stderr);
7684 va_end(args);
7686 exit(1);
7687 }
7689 static void
7690 warn(const char *msg, ...)
7691 {
7692 va_list args;
7694 va_start(args, msg);
7695 fputs("tig warning: ", stderr);
7696 vfprintf(stderr, msg, args);
7697 fputs("\n", stderr);
7698 va_end(args);
7699 }
7701 static enum request
7702 parse_options(int argc, const char *argv[])
7703 {
7704 enum request request = REQ_VIEW_MAIN;
7705 const char *subcommand;
7706 bool seen_dashdash = FALSE;
7707 /* XXX: This is vulnerable to the user overriding options
7708 * required for the main view parser. */
7709 const char *custom_argv[SIZEOF_ARG] = {
7710 "git", "log", "--no-color", "--pretty=raw", "--parents",
7711 "--topo-order", NULL
7712 };
7713 int i, j = 6;
7715 if (!isatty(STDIN_FILENO)) {
7716 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7717 return REQ_VIEW_PAGER;
7718 }
7720 if (argc <= 1)
7721 return REQ_NONE;
7723 subcommand = argv[1];
7724 if (!strcmp(subcommand, "status")) {
7725 if (argc > 2)
7726 warn("ignoring arguments after `%s'", subcommand);
7727 return REQ_VIEW_STATUS;
7729 } else if (!strcmp(subcommand, "blame")) {
7730 if (argc <= 2 || argc > 4)
7731 die("invalid number of options to blame\n\n%s", usage);
7733 i = 2;
7734 if (argc == 4) {
7735 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7736 i++;
7737 }
7739 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7740 return REQ_VIEW_BLAME;
7742 } else if (!strcmp(subcommand, "show")) {
7743 request = REQ_VIEW_DIFF;
7745 } else {
7746 subcommand = NULL;
7747 }
7749 if (subcommand) {
7750 custom_argv[1] = subcommand;
7751 j = 2;
7752 }
7754 for (i = 1 + !!subcommand; i < argc; i++) {
7755 const char *opt = argv[i];
7757 if (seen_dashdash || !strcmp(opt, "--")) {
7758 seen_dashdash = TRUE;
7760 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7761 printf("tig version %s\n", TIG_VERSION);
7762 quit(0);
7764 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7765 printf("%s\n", usage);
7766 quit(0);
7767 }
7769 custom_argv[j++] = opt;
7770 if (j >= ARRAY_SIZE(custom_argv))
7771 die("command too long");
7772 }
7774 if (!prepare_update(VIEW(request), custom_argv, NULL))
7775 die("Failed to format arguments");
7777 return request;
7778 }
7780 int
7781 main(int argc, const char *argv[])
7782 {
7783 const char *codeset = "UTF-8";
7784 enum request request = parse_options(argc, argv);
7785 struct view *view;
7786 size_t i;
7788 signal(SIGINT, quit);
7789 signal(SIGPIPE, SIG_IGN);
7791 if (setlocale(LC_ALL, "")) {
7792 codeset = nl_langinfo(CODESET);
7793 }
7795 if (load_repo_info() == ERR)
7796 die("Failed to load repo info.");
7798 if (load_options() == ERR)
7799 die("Failed to load user config.");
7801 if (load_git_config() == ERR)
7802 die("Failed to load repo config.");
7804 /* Require a git repository unless when running in pager mode. */
7805 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7806 die("Not a git repository");
7808 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7809 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7810 if (opt_iconv_in == ICONV_NONE)
7811 die("Failed to initialize character set conversion");
7812 }
7814 if (codeset && strcmp(codeset, "UTF-8")) {
7815 opt_iconv_out = iconv_open(codeset, "UTF-8");
7816 if (opt_iconv_out == ICONV_NONE)
7817 die("Failed to initialize character set conversion");
7818 }
7820 if (load_refs() == ERR)
7821 die("Failed to load refs.");
7823 foreach_view (view, i)
7824 if (!argv_from_env(view->ops->argv, view->cmd_env))
7825 die("Too many arguments in the `%s` environment variable",
7826 view->cmd_env);
7828 init_display();
7830 if (request != REQ_NONE)
7831 open_view(NULL, request, OPEN_PREPARED);
7832 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7834 while (view_driver(display[current_view], request)) {
7835 int key = get_input(0);
7837 view = display[current_view];
7838 request = get_keybinding(view->keymap, key);
7840 /* Some low-level request handling. This keeps access to
7841 * status_win restricted. */
7842 switch (request) {
7843 case REQ_NONE:
7844 report("Unknown key, press %s for help",
7845 get_key(view->keymap, REQ_VIEW_HELP));
7846 break;
7847 case REQ_PROMPT:
7848 {
7849 char *cmd = read_prompt(":");
7851 if (cmd && isdigit(*cmd)) {
7852 int lineno = view->lineno + 1;
7854 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7855 select_view_line(view, lineno - 1);
7856 report("");
7857 } else {
7858 report("Unable to parse '%s' as a line number", cmd);
7859 }
7861 } else if (cmd) {
7862 struct view *next = VIEW(REQ_VIEW_PAGER);
7863 const char *argv[SIZEOF_ARG] = { "git" };
7864 int argc = 1;
7866 /* When running random commands, initially show the
7867 * command in the title. However, it maybe later be
7868 * overwritten if a commit line is selected. */
7869 string_ncopy(next->ref, cmd, strlen(cmd));
7871 if (!argv_from_string(argv, &argc, cmd)) {
7872 report("Too many arguments");
7873 } else if (!prepare_update(next, argv, NULL)) {
7874 report("Failed to format command");
7875 } else {
7876 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7877 }
7878 }
7880 request = REQ_NONE;
7881 break;
7882 }
7883 case REQ_SEARCH:
7884 case REQ_SEARCH_BACK:
7885 {
7886 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7887 char *search = read_prompt(prompt);
7889 if (search)
7890 string_ncopy(opt_search, search, strlen(search));
7891 else if (*opt_search)
7892 request = request == REQ_SEARCH ?
7893 REQ_FIND_NEXT :
7894 REQ_FIND_PREV;
7895 else
7896 request = REQ_NONE;
7897 break;
7898 }
7899 default:
7900 break;
7901 }
7902 }
7904 quit(0);
7906 return 0;
7907 }