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 input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 }
233 static char *
234 chomp_string(char *name)
235 {
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
246 }
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
250 {
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
262 }
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
272 {
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
287 }
289 return 0;
290 }
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
305 {
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
313 }
315 buf[bufpos] = 0;
316 return buf;
317 }
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
323 {
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
331 }
333 return FALSE;
334 }
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
344 {
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 }
352 /*
353 * Unicode / UTF-8 handling
354 *
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358 */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
362 {
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
382 }
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387 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,
388 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,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 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,
394 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,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
399 {
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
403 }
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
408 {
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
447 }
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
452 }
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
458 *
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
462 {
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
491 }
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
499 }
500 break;
501 }
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
506 }
508 return string - *start;
509 }
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
529 };
531 struct time {
532 time_t sec;
533 int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
537 {
538 return t1->sec - t2->sec;
539 }
541 static const char *
542 mkdate(const struct time *time, enum date date)
543 {
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
552 };
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
577 }
578 }
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
583 }
584 else {
585 gmtime_r(&time->sec, &tm);
586 }
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
674 static void
675 argv_free(const char *argv[])
676 {
677 int argc;
679 if (!argv)
680 return;
681 for (argc = 0; argv[argc]; argc++)
682 free((void *) argv[argc]);
683 argv[0] = NULL;
684 }
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
690 {
691 int argc = 0;
693 while (*argv && (*argv)[argc])
694 argc++;
696 if (!argv_realloc(argv, argc, 2))
697 return FALSE;
699 (*argv)[argc++] = strdup(arg);
700 (*argv)[argc] = NULL;
701 return TRUE;
702 }
704 static bool
705 argv_append_array(const char ***dst_argv, const char *src_argv[])
706 {
707 int i;
709 for (i = 0; src_argv && src_argv[i]; i++)
710 if (!argv_append(dst_argv, src_argv[i]))
711 return FALSE;
712 return TRUE;
713 }
715 static bool
716 argv_copy(const char ***dst, const char *src[])
717 {
718 int argc;
720 for (argc = 0; src[argc]; argc++)
721 if (!argv_append(dst, src[argc]))
722 return FALSE;
723 return TRUE;
724 }
727 /*
728 * Executing external commands.
729 */
731 enum io_type {
732 IO_FD, /* File descriptor based IO. */
733 IO_BG, /* Execute command in the background. */
734 IO_FG, /* Execute command with same std{in,out,err}. */
735 IO_RD, /* Read only fork+exec IO. */
736 IO_WR, /* Write only fork+exec IO. */
737 IO_AP, /* Append fork+exec output to file. */
738 };
740 struct io {
741 int pipe; /* Pipe end for reading or writing. */
742 pid_t pid; /* PID of spawned process. */
743 int error; /* Error status. */
744 char *buf; /* Read buffer. */
745 size_t bufalloc; /* Allocated buffer size. */
746 size_t bufsize; /* Buffer content size. */
747 char *bufpos; /* Current buffer position. */
748 unsigned int eof:1; /* Has end of file been reached. */
749 };
751 static void
752 io_init(struct io *io)
753 {
754 memset(io, 0, sizeof(*io));
755 io->pipe = -1;
756 }
758 static bool
759 io_open(struct io *io, const char *fmt, ...)
760 {
761 char name[SIZEOF_STR] = "";
762 bool fits;
763 va_list args;
765 io_init(io);
767 va_start(args, fmt);
768 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
769 va_end(args);
771 if (!fits) {
772 io->error = ENAMETOOLONG;
773 return FALSE;
774 }
775 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
776 if (io->pipe == -1)
777 io->error = errno;
778 return io->pipe != -1;
779 }
781 static bool
782 io_kill(struct io *io)
783 {
784 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
785 }
787 static bool
788 io_done(struct io *io)
789 {
790 pid_t pid = io->pid;
792 if (io->pipe != -1)
793 close(io->pipe);
794 free(io->buf);
795 io_init(io);
797 while (pid > 0) {
798 int status;
799 pid_t waiting = waitpid(pid, &status, 0);
801 if (waiting < 0) {
802 if (errno == EINTR)
803 continue;
804 io->error = errno;
805 return FALSE;
806 }
808 return waiting == pid &&
809 !WIFSIGNALED(status) &&
810 WIFEXITED(status) &&
811 !WEXITSTATUS(status);
812 }
814 return TRUE;
815 }
817 static bool
818 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
819 {
820 int pipefds[2] = { -1, -1 };
821 va_list args;
823 io_init(io);
825 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
826 io->error = errno;
827 return FALSE;
828 } else if (type == IO_AP) {
829 va_start(args, argv);
830 pipefds[1] = va_arg(args, int);
831 va_end(args);
832 }
834 if ((io->pid = fork())) {
835 if (io->pid == -1)
836 io->error = errno;
837 if (pipefds[!(type == IO_WR)] != -1)
838 close(pipefds[!(type == IO_WR)]);
839 if (io->pid != -1) {
840 io->pipe = pipefds[!!(type == IO_WR)];
841 return TRUE;
842 }
844 } else {
845 if (type != IO_FG) {
846 int devnull = open("/dev/null", O_RDWR);
847 int readfd = type == IO_WR ? pipefds[0] : devnull;
848 int writefd = (type == IO_RD || type == IO_AP)
849 ? pipefds[1] : devnull;
851 dup2(readfd, STDIN_FILENO);
852 dup2(writefd, STDOUT_FILENO);
853 dup2(devnull, STDERR_FILENO);
855 close(devnull);
856 if (pipefds[0] != -1)
857 close(pipefds[0]);
858 if (pipefds[1] != -1)
859 close(pipefds[1]);
860 }
862 if (dir && *dir && chdir(dir) == -1)
863 exit(errno);
865 execvp(argv[0], (char *const*) argv);
866 exit(errno);
867 }
869 if (pipefds[!!(type == IO_WR)] != -1)
870 close(pipefds[!!(type == IO_WR)]);
871 return FALSE;
872 }
874 static bool
875 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
876 {
877 struct io io;
879 return io_run(&io, type, dir, argv, fd) && io_done(&io);
880 }
882 static bool
883 io_run_bg(const char **argv)
884 {
885 return io_complete(IO_BG, argv, NULL, -1);
886 }
888 static bool
889 io_run_fg(const char **argv, const char *dir)
890 {
891 return io_complete(IO_FG, argv, dir, -1);
892 }
894 static bool
895 io_run_append(const char **argv, int fd)
896 {
897 return io_complete(IO_AP, argv, NULL, fd);
898 }
900 static bool
901 io_eof(struct io *io)
902 {
903 return io->eof;
904 }
906 static int
907 io_error(struct io *io)
908 {
909 return io->error;
910 }
912 static char *
913 io_strerror(struct io *io)
914 {
915 return strerror(io->error);
916 }
918 static bool
919 io_can_read(struct io *io)
920 {
921 struct timeval tv = { 0, 500 };
922 fd_set fds;
924 FD_ZERO(&fds);
925 FD_SET(io->pipe, &fds);
927 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 }
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
932 {
933 do {
934 ssize_t readsize = read(io->pipe, buf, bufsize);
936 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937 continue;
938 else if (readsize == -1)
939 io->error = errno;
940 else if (readsize == 0)
941 io->eof = 1;
942 return readsize;
943 } while (1);
944 }
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
950 {
951 char *eol;
952 ssize_t readsize;
954 while (TRUE) {
955 if (io->bufsize > 0) {
956 eol = memchr(io->bufpos, c, io->bufsize);
957 if (eol) {
958 char *line = io->bufpos;
960 *eol = 0;
961 io->bufpos = eol + 1;
962 io->bufsize -= io->bufpos - line;
963 return line;
964 }
965 }
967 if (io_eof(io)) {
968 if (io->bufsize) {
969 io->bufpos[io->bufsize] = 0;
970 io->bufsize = 0;
971 return io->bufpos;
972 }
973 return NULL;
974 }
976 if (!can_read)
977 return NULL;
979 if (io->bufsize > 0 && io->bufpos > io->buf)
980 memmove(io->buf, io->bufpos, io->bufsize);
982 if (io->bufalloc == io->bufsize) {
983 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984 return NULL;
985 io->bufalloc += BUFSIZ;
986 }
988 io->bufpos = io->buf;
989 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990 if (io_error(io))
991 return NULL;
992 io->bufsize += readsize;
993 }
994 }
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
998 {
999 size_t written = 0;
1001 while (!io_error(io) && written < bufsize) {
1002 ssize_t size;
1004 size = write(io->pipe, buf + written, bufsize - written);
1005 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006 continue;
1007 else if (size == -1)
1008 io->error = errno;
1009 else
1010 written += size;
1011 }
1013 return written == bufsize;
1014 }
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018 {
1019 char *result = io_get(io, '\n', TRUE);
1021 if (result) {
1022 result = chomp_string(result);
1023 string_ncopy_do(buf, bufsize, result, strlen(result));
1024 }
1026 return io_done(io) && result;
1027 }
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031 {
1032 struct io io;
1034 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1035 }
1037 static int
1038 io_load(struct io *io, const char *separators,
1039 int (*read_property)(char *, size_t, char *, size_t))
1040 {
1041 char *name;
1042 int state = OK;
1044 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1045 char *value;
1046 size_t namelen;
1047 size_t valuelen;
1049 name = chomp_string(name);
1050 namelen = strcspn(name, separators);
1052 if (name[namelen]) {
1053 name[namelen] = 0;
1054 value = chomp_string(name + namelen + 1);
1055 valuelen = strlen(value);
1057 } else {
1058 value = "";
1059 valuelen = 0;
1060 }
1062 state = read_property(name, namelen, value, valuelen);
1063 }
1065 if (state != ERR && io_error(io))
1066 state = ERR;
1067 io_done(io);
1069 return state;
1070 }
1072 static int
1073 io_run_load(const char **argv, const char *separators,
1074 int (*read_property)(char *, size_t, char *, size_t))
1075 {
1076 struct io io;
1078 if (!io_run(&io, IO_RD, NULL, argv))
1079 return ERR;
1080 return io_load(&io, separators, read_property);
1081 }
1084 /*
1085 * User requests
1086 */
1088 #define REQ_INFO \
1089 /* XXX: Keep the view request first and in sync with views[]. */ \
1090 REQ_GROUP("View switching") \
1091 REQ_(VIEW_MAIN, "Show main view"), \
1092 REQ_(VIEW_DIFF, "Show diff view"), \
1093 REQ_(VIEW_LOG, "Show log view"), \
1094 REQ_(VIEW_TREE, "Show tree view"), \
1095 REQ_(VIEW_BLOB, "Show blob view"), \
1096 REQ_(VIEW_BLAME, "Show blame view"), \
1097 REQ_(VIEW_BRANCH, "Show branch view"), \
1098 REQ_(VIEW_HELP, "Show help page"), \
1099 REQ_(VIEW_PAGER, "Show pager view"), \
1100 REQ_(VIEW_STATUS, "Show status view"), \
1101 REQ_(VIEW_STAGE, "Show stage view"), \
1102 \
1103 REQ_GROUP("View manipulation") \
1104 REQ_(ENTER, "Enter current line and scroll"), \
1105 REQ_(NEXT, "Move to next"), \
1106 REQ_(PREVIOUS, "Move to previous"), \
1107 REQ_(PARENT, "Move to parent"), \
1108 REQ_(VIEW_NEXT, "Move focus to next view"), \
1109 REQ_(REFRESH, "Reload and refresh"), \
1110 REQ_(MAXIMIZE, "Maximize the current view"), \
1111 REQ_(VIEW_CLOSE, "Close the current view"), \
1112 REQ_(QUIT, "Close all views and quit"), \
1113 \
1114 REQ_GROUP("View specific requests") \
1115 REQ_(STATUS_UPDATE, "Update file status"), \
1116 REQ_(STATUS_REVERT, "Revert file changes"), \
1117 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1118 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1119 \
1120 REQ_GROUP("Cursor navigation") \
1121 REQ_(MOVE_UP, "Move cursor one line up"), \
1122 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1123 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1124 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1125 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1126 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1127 \
1128 REQ_GROUP("Scrolling") \
1129 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1130 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1131 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1132 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1133 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1134 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1135 \
1136 REQ_GROUP("Searching") \
1137 REQ_(SEARCH, "Search the view"), \
1138 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1139 REQ_(FIND_NEXT, "Find next search match"), \
1140 REQ_(FIND_PREV, "Find previous search match"), \
1141 \
1142 REQ_GROUP("Option manipulation") \
1143 REQ_(OPTIONS, "Open option menu"), \
1144 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1145 REQ_(TOGGLE_DATE, "Toggle date display"), \
1146 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1147 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1148 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1149 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1150 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1151 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1152 \
1153 REQ_GROUP("Misc") \
1154 REQ_(PROMPT, "Bring up the prompt"), \
1155 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1156 REQ_(SHOW_VERSION, "Show version information"), \
1157 REQ_(STOP_LOADING, "Stop all loading views"), \
1158 REQ_(EDIT, "Open in editor"), \
1159 REQ_(NONE, "Do nothing")
1162 /* User action requests. */
1163 enum request {
1164 #define REQ_GROUP(help)
1165 #define REQ_(req, help) REQ_##req
1167 /* Offset all requests to avoid conflicts with ncurses getch values. */
1168 REQ_UNKNOWN = KEY_MAX + 1,
1169 REQ_OFFSET,
1170 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1174 };
1176 struct request_info {
1177 enum request request;
1178 const char *name;
1179 int namelen;
1180 const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186 REQ_INFO
1187 #undef REQ_GROUP
1188 #undef REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1193 {
1194 int namelen = strlen(name);
1195 int i;
1197 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198 if (enum_equals(req_info[i], name, namelen))
1199 return req_info[i].request;
1201 return REQ_UNKNOWN;
1202 }
1205 /*
1206 * Options
1207 */
1209 /* Option and state variables. */
1210 static enum date opt_date = DATE_DEFAULT;
1211 static enum author opt_author = AUTHOR_DEFAULT;
1212 static bool opt_line_number = FALSE;
1213 static bool opt_line_graphics = TRUE;
1214 static bool opt_rev_graph = FALSE;
1215 static bool opt_show_refs = TRUE;
1216 static int opt_num_interval = 5;
1217 static double opt_hscroll = 0.50;
1218 static double opt_scale_split_view = 2.0 / 3.0;
1219 static int opt_tab_size = 8;
1220 static int opt_author_cols = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR] = "";
1222 static char opt_file[SIZEOF_STR] = "";
1223 static char opt_ref[SIZEOF_REF] = "";
1224 static char opt_head[SIZEOF_REF] = "";
1225 static char opt_remote[SIZEOF_REF] = "";
1226 static char opt_encoding[20] = "UTF-8";
1227 static iconv_t opt_iconv_in = ICONV_NONE;
1228 static iconv_t opt_iconv_out = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR] = "";
1230 static char opt_cdup[SIZEOF_STR] = "";
1231 static char opt_prefix[SIZEOF_STR] = "";
1232 static char opt_git_dir[SIZEOF_STR] = "";
1233 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR] = "";
1235 static FILE *opt_tty = NULL;
1236 static const char **opt_diff_args = NULL;
1237 static const char **opt_rev_args = NULL;
1238 static const char **opt_file_args = NULL;
1240 #define is_initial_commit() (!get_ref_head())
1241 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1244 /*
1245 * Line-oriented content detection.
1246 */
1248 #define LINE_INFO \
1249 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1264 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1265 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1266 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1270 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1274 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1280 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1281 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1283 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1285 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1287 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1288 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1297 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1298 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1300 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1303 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1307 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1309 enum line_type {
1310 #define LINE(type, line, fg, bg, attr) \
1311 LINE_##type
1312 LINE_INFO,
1313 LINE_NONE
1314 #undef LINE
1315 };
1317 struct line_info {
1318 const char *name; /* Option name. */
1319 int namelen; /* Size of option name. */
1320 const char *line; /* The start of line to match. */
1321 int linelen; /* Size of string to match. */
1322 int fg, bg, attr; /* Color and text attributes for the lines. */
1323 };
1325 static struct line_info line_info[] = {
1326 #define LINE(type, line, fg, bg, attr) \
1327 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1328 LINE_INFO
1329 #undef LINE
1330 };
1332 static enum line_type
1333 get_line_type(const char *line)
1334 {
1335 int linelen = strlen(line);
1336 enum line_type type;
1338 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1339 /* Case insensitive search matches Signed-off-by lines better. */
1340 if (linelen >= line_info[type].linelen &&
1341 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1342 return type;
1344 return LINE_DEFAULT;
1345 }
1347 static inline int
1348 get_line_attr(enum line_type type)
1349 {
1350 assert(type < ARRAY_SIZE(line_info));
1351 return COLOR_PAIR(type) | line_info[type].attr;
1352 }
1354 static struct line_info *
1355 get_line_info(const char *name)
1356 {
1357 size_t namelen = strlen(name);
1358 enum line_type type;
1360 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1361 if (enum_equals(line_info[type], name, namelen))
1362 return &line_info[type];
1364 return NULL;
1365 }
1367 static void
1368 init_colors(void)
1369 {
1370 int default_bg = line_info[LINE_DEFAULT].bg;
1371 int default_fg = line_info[LINE_DEFAULT].fg;
1372 enum line_type type;
1374 start_color();
1376 if (assume_default_colors(default_fg, default_bg) == ERR) {
1377 default_bg = COLOR_BLACK;
1378 default_fg = COLOR_WHITE;
1379 }
1381 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1382 struct line_info *info = &line_info[type];
1383 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1384 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1386 init_pair(type, fg, bg);
1387 }
1388 }
1390 struct line {
1391 enum line_type type;
1393 /* State flags */
1394 unsigned int selected:1;
1395 unsigned int dirty:1;
1396 unsigned int cleareol:1;
1397 unsigned int other:16;
1399 void *data; /* User data */
1400 };
1403 /*
1404 * Keys
1405 */
1407 struct keybinding {
1408 int alias;
1409 enum request request;
1410 };
1412 static struct keybinding default_keybindings[] = {
1413 /* View switching */
1414 { 'm', REQ_VIEW_MAIN },
1415 { 'd', REQ_VIEW_DIFF },
1416 { 'l', REQ_VIEW_LOG },
1417 { 't', REQ_VIEW_TREE },
1418 { 'f', REQ_VIEW_BLOB },
1419 { 'B', REQ_VIEW_BLAME },
1420 { 'H', REQ_VIEW_BRANCH },
1421 { 'p', REQ_VIEW_PAGER },
1422 { 'h', REQ_VIEW_HELP },
1423 { 'S', REQ_VIEW_STATUS },
1424 { 'c', REQ_VIEW_STAGE },
1426 /* View manipulation */
1427 { 'q', REQ_VIEW_CLOSE },
1428 { KEY_TAB, REQ_VIEW_NEXT },
1429 { KEY_RETURN, REQ_ENTER },
1430 { KEY_UP, REQ_PREVIOUS },
1431 { KEY_DOWN, REQ_NEXT },
1432 { 'R', REQ_REFRESH },
1433 { KEY_F(5), REQ_REFRESH },
1434 { 'O', REQ_MAXIMIZE },
1436 /* Cursor navigation */
1437 { 'k', REQ_MOVE_UP },
1438 { 'j', REQ_MOVE_DOWN },
1439 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1440 { KEY_END, REQ_MOVE_LAST_LINE },
1441 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1442 { ' ', REQ_MOVE_PAGE_DOWN },
1443 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1444 { 'b', REQ_MOVE_PAGE_UP },
1445 { '-', REQ_MOVE_PAGE_UP },
1447 /* Scrolling */
1448 { KEY_LEFT, REQ_SCROLL_LEFT },
1449 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1450 { KEY_IC, REQ_SCROLL_LINE_UP },
1451 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1452 { 'w', REQ_SCROLL_PAGE_UP },
1453 { 's', REQ_SCROLL_PAGE_DOWN },
1455 /* Searching */
1456 { '/', REQ_SEARCH },
1457 { '?', REQ_SEARCH_BACK },
1458 { 'n', REQ_FIND_NEXT },
1459 { 'N', REQ_FIND_PREV },
1461 /* Misc */
1462 { 'Q', REQ_QUIT },
1463 { 'z', REQ_STOP_LOADING },
1464 { 'v', REQ_SHOW_VERSION },
1465 { 'r', REQ_SCREEN_REDRAW },
1466 { 'o', REQ_OPTIONS },
1467 { '.', REQ_TOGGLE_LINENO },
1468 { 'D', REQ_TOGGLE_DATE },
1469 { 'A', REQ_TOGGLE_AUTHOR },
1470 { 'g', REQ_TOGGLE_REV_GRAPH },
1471 { 'F', REQ_TOGGLE_REFS },
1472 { 'I', REQ_TOGGLE_SORT_ORDER },
1473 { 'i', REQ_TOGGLE_SORT_FIELD },
1474 { ':', REQ_PROMPT },
1475 { 'u', REQ_STATUS_UPDATE },
1476 { '!', REQ_STATUS_REVERT },
1477 { 'M', REQ_STATUS_MERGE },
1478 { '@', REQ_STAGE_NEXT },
1479 { ',', REQ_PARENT },
1480 { 'e', REQ_EDIT },
1481 };
1483 #define KEYMAP_INFO \
1484 KEYMAP_(GENERIC), \
1485 KEYMAP_(MAIN), \
1486 KEYMAP_(DIFF), \
1487 KEYMAP_(LOG), \
1488 KEYMAP_(TREE), \
1489 KEYMAP_(BLOB), \
1490 KEYMAP_(BLAME), \
1491 KEYMAP_(BRANCH), \
1492 KEYMAP_(PAGER), \
1493 KEYMAP_(HELP), \
1494 KEYMAP_(STATUS), \
1495 KEYMAP_(STAGE)
1497 enum keymap {
1498 #define KEYMAP_(name) KEYMAP_##name
1499 KEYMAP_INFO
1500 #undef KEYMAP_
1501 };
1503 static const struct enum_map keymap_table[] = {
1504 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1505 KEYMAP_INFO
1506 #undef KEYMAP_
1507 };
1509 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1511 struct keybinding_table {
1512 struct keybinding *data;
1513 size_t size;
1514 };
1516 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1518 static void
1519 add_keybinding(enum keymap keymap, enum request request, int key)
1520 {
1521 struct keybinding_table *table = &keybindings[keymap];
1522 size_t i;
1524 for (i = 0; i < keybindings[keymap].size; i++) {
1525 if (keybindings[keymap].data[i].alias == key) {
1526 keybindings[keymap].data[i].request = request;
1527 return;
1528 }
1529 }
1531 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1532 if (!table->data)
1533 die("Failed to allocate keybinding");
1534 table->data[table->size].alias = key;
1535 table->data[table->size++].request = request;
1537 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1538 int i;
1540 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541 if (default_keybindings[i].alias == key)
1542 default_keybindings[i].request = REQ_NONE;
1543 }
1544 }
1546 /* Looks for a key binding first in the given map, then in the generic map, and
1547 * lastly in the default keybindings. */
1548 static enum request
1549 get_keybinding(enum keymap keymap, int key)
1550 {
1551 size_t i;
1553 for (i = 0; i < keybindings[keymap].size; i++)
1554 if (keybindings[keymap].data[i].alias == key)
1555 return keybindings[keymap].data[i].request;
1557 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1558 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1559 return keybindings[KEYMAP_GENERIC].data[i].request;
1561 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562 if (default_keybindings[i].alias == key)
1563 return default_keybindings[i].request;
1565 return (enum request) key;
1566 }
1569 struct key {
1570 const char *name;
1571 int value;
1572 };
1574 static const struct key key_table[] = {
1575 { "Enter", KEY_RETURN },
1576 { "Space", ' ' },
1577 { "Backspace", KEY_BACKSPACE },
1578 { "Tab", KEY_TAB },
1579 { "Escape", KEY_ESC },
1580 { "Left", KEY_LEFT },
1581 { "Right", KEY_RIGHT },
1582 { "Up", KEY_UP },
1583 { "Down", KEY_DOWN },
1584 { "Insert", KEY_IC },
1585 { "Delete", KEY_DC },
1586 { "Hash", '#' },
1587 { "Home", KEY_HOME },
1588 { "End", KEY_END },
1589 { "PageUp", KEY_PPAGE },
1590 { "PageDown", KEY_NPAGE },
1591 { "F1", KEY_F(1) },
1592 { "F2", KEY_F(2) },
1593 { "F3", KEY_F(3) },
1594 { "F4", KEY_F(4) },
1595 { "F5", KEY_F(5) },
1596 { "F6", KEY_F(6) },
1597 { "F7", KEY_F(7) },
1598 { "F8", KEY_F(8) },
1599 { "F9", KEY_F(9) },
1600 { "F10", KEY_F(10) },
1601 { "F11", KEY_F(11) },
1602 { "F12", KEY_F(12) },
1603 };
1605 static int
1606 get_key_value(const char *name)
1607 {
1608 int i;
1610 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1611 if (!strcasecmp(key_table[i].name, name))
1612 return key_table[i].value;
1614 if (strlen(name) == 1 && isprint(*name))
1615 return (int) *name;
1617 return ERR;
1618 }
1620 static const char *
1621 get_key_name(int key_value)
1622 {
1623 static char key_char[] = "'X'";
1624 const char *seq = NULL;
1625 int key;
1627 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1628 if (key_table[key].value == key_value)
1629 seq = key_table[key].name;
1631 if (seq == NULL &&
1632 key_value < 127 &&
1633 isprint(key_value)) {
1634 key_char[1] = (char) key_value;
1635 seq = key_char;
1636 }
1638 return seq ? seq : "(no key)";
1639 }
1641 static bool
1642 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1643 {
1644 const char *sep = *pos > 0 ? ", " : "";
1645 const char *keyname = get_key_name(keybinding->alias);
1647 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1648 }
1650 static bool
1651 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1652 enum keymap keymap, bool all)
1653 {
1654 int i;
1656 for (i = 0; i < keybindings[keymap].size; i++) {
1657 if (keybindings[keymap].data[i].request == request) {
1658 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1659 return FALSE;
1660 if (!all)
1661 break;
1662 }
1663 }
1665 return TRUE;
1666 }
1668 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1670 static const char *
1671 get_keys(enum keymap keymap, enum request request, bool all)
1672 {
1673 static char buf[BUFSIZ];
1674 size_t pos = 0;
1675 int i;
1677 buf[pos] = 0;
1679 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1680 return "Too many keybindings!";
1681 if (pos > 0 && !all)
1682 return buf;
1684 if (keymap != KEYMAP_GENERIC) {
1685 /* Only the generic keymap includes the default keybindings when
1686 * listing all keys. */
1687 if (all)
1688 return buf;
1690 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1691 return "Too many keybindings!";
1692 if (pos)
1693 return buf;
1694 }
1696 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1697 if (default_keybindings[i].request == request) {
1698 if (!append_key(buf, &pos, &default_keybindings[i]))
1699 return "Too many keybindings!";
1700 if (!all)
1701 return buf;
1702 }
1703 }
1705 return buf;
1706 }
1708 struct run_request {
1709 enum keymap keymap;
1710 int key;
1711 const char **argv;
1712 };
1714 static struct run_request *run_request;
1715 static size_t run_requests;
1717 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1719 static enum request
1720 add_run_request(enum keymap keymap, int key, const char **argv)
1721 {
1722 struct run_request *req;
1724 if (!realloc_run_requests(&run_request, run_requests, 1))
1725 return REQ_NONE;
1727 req = &run_request[run_requests];
1728 req->keymap = keymap;
1729 req->key = key;
1730 req->argv = NULL;
1732 if (!argv_copy(&req->argv, argv))
1733 return REQ_NONE;
1735 return REQ_NONE + ++run_requests;
1736 }
1738 static struct run_request *
1739 get_run_request(enum request request)
1740 {
1741 if (request <= REQ_NONE)
1742 return NULL;
1743 return &run_request[request - REQ_NONE - 1];
1744 }
1746 static void
1747 add_builtin_run_requests(void)
1748 {
1749 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1750 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1751 const char *commit[] = { "git", "commit", NULL };
1752 const char *gc[] = { "git", "gc", NULL };
1753 struct run_request reqs[] = {
1754 { KEYMAP_MAIN, 'C', cherry_pick },
1755 { KEYMAP_STATUS, 'C', commit },
1756 { KEYMAP_BRANCH, 'C', checkout },
1757 { KEYMAP_GENERIC, 'G', gc },
1758 };
1759 int i;
1761 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1762 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1764 if (req != reqs[i].key)
1765 continue;
1766 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1767 if (req != REQ_NONE)
1768 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1769 }
1770 }
1772 /*
1773 * User config file handling.
1774 */
1776 static int config_lineno;
1777 static bool config_errors;
1778 static const char *config_msg;
1780 static const struct enum_map color_map[] = {
1781 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1782 COLOR_MAP(DEFAULT),
1783 COLOR_MAP(BLACK),
1784 COLOR_MAP(BLUE),
1785 COLOR_MAP(CYAN),
1786 COLOR_MAP(GREEN),
1787 COLOR_MAP(MAGENTA),
1788 COLOR_MAP(RED),
1789 COLOR_MAP(WHITE),
1790 COLOR_MAP(YELLOW),
1791 };
1793 static const struct enum_map attr_map[] = {
1794 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1795 ATTR_MAP(NORMAL),
1796 ATTR_MAP(BLINK),
1797 ATTR_MAP(BOLD),
1798 ATTR_MAP(DIM),
1799 ATTR_MAP(REVERSE),
1800 ATTR_MAP(STANDOUT),
1801 ATTR_MAP(UNDERLINE),
1802 };
1804 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1806 static int parse_step(double *opt, const char *arg)
1807 {
1808 *opt = atoi(arg);
1809 if (!strchr(arg, '%'))
1810 return OK;
1812 /* "Shift down" so 100% and 1 does not conflict. */
1813 *opt = (*opt - 1) / 100;
1814 if (*opt >= 1.0) {
1815 *opt = 0.99;
1816 config_msg = "Step value larger than 100%";
1817 return ERR;
1818 }
1819 if (*opt < 0.0) {
1820 *opt = 1;
1821 config_msg = "Invalid step value";
1822 return ERR;
1823 }
1824 return OK;
1825 }
1827 static int
1828 parse_int(int *opt, const char *arg, int min, int max)
1829 {
1830 int value = atoi(arg);
1832 if (min <= value && value <= max) {
1833 *opt = value;
1834 return OK;
1835 }
1837 config_msg = "Integer value out of bound";
1838 return ERR;
1839 }
1841 static bool
1842 set_color(int *color, const char *name)
1843 {
1844 if (map_enum(color, color_map, name))
1845 return TRUE;
1846 if (!prefixcmp(name, "color"))
1847 return parse_int(color, name + 5, 0, 255) == OK;
1848 return FALSE;
1849 }
1851 /* Wants: object fgcolor bgcolor [attribute] */
1852 static int
1853 option_color_command(int argc, const char *argv[])
1854 {
1855 struct line_info *info;
1857 if (argc < 3) {
1858 config_msg = "Wrong number of arguments given to color command";
1859 return ERR;
1860 }
1862 info = get_line_info(argv[0]);
1863 if (!info) {
1864 static const struct enum_map obsolete[] = {
1865 ENUM_MAP("main-delim", LINE_DELIMITER),
1866 ENUM_MAP("main-date", LINE_DATE),
1867 ENUM_MAP("main-author", LINE_AUTHOR),
1868 };
1869 int index;
1871 if (!map_enum(&index, obsolete, argv[0])) {
1872 config_msg = "Unknown color name";
1873 return ERR;
1874 }
1875 info = &line_info[index];
1876 }
1878 if (!set_color(&info->fg, argv[1]) ||
1879 !set_color(&info->bg, argv[2])) {
1880 config_msg = "Unknown color";
1881 return ERR;
1882 }
1884 info->attr = 0;
1885 while (argc-- > 3) {
1886 int attr;
1888 if (!set_attribute(&attr, argv[argc])) {
1889 config_msg = "Unknown attribute";
1890 return ERR;
1891 }
1892 info->attr |= attr;
1893 }
1895 return OK;
1896 }
1898 static int parse_bool(bool *opt, const char *arg)
1899 {
1900 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1901 ? TRUE : FALSE;
1902 return OK;
1903 }
1905 static int parse_enum_do(unsigned int *opt, const char *arg,
1906 const struct enum_map *map, size_t map_size)
1907 {
1908 bool is_true;
1910 assert(map_size > 1);
1912 if (map_enum_do(map, map_size, (int *) opt, arg))
1913 return OK;
1915 if (parse_bool(&is_true, arg) != OK)
1916 return ERR;
1918 *opt = is_true ? map[1].value : map[0].value;
1919 return OK;
1920 }
1922 #define parse_enum(opt, arg, map) \
1923 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1925 static int
1926 parse_string(char *opt, const char *arg, size_t optsize)
1927 {
1928 int arglen = strlen(arg);
1930 switch (arg[0]) {
1931 case '\"':
1932 case '\'':
1933 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1934 config_msg = "Unmatched quotation";
1935 return ERR;
1936 }
1937 arg += 1; arglen -= 2;
1938 default:
1939 string_ncopy_do(opt, optsize, arg, arglen);
1940 return OK;
1941 }
1942 }
1944 /* Wants: name = value */
1945 static int
1946 option_set_command(int argc, const char *argv[])
1947 {
1948 if (argc != 3) {
1949 config_msg = "Wrong number of arguments given to set command";
1950 return ERR;
1951 }
1953 if (strcmp(argv[1], "=")) {
1954 config_msg = "No value assigned";
1955 return ERR;
1956 }
1958 if (!strcmp(argv[0], "show-author"))
1959 return parse_enum(&opt_author, argv[2], author_map);
1961 if (!strcmp(argv[0], "show-date"))
1962 return parse_enum(&opt_date, argv[2], date_map);
1964 if (!strcmp(argv[0], "show-rev-graph"))
1965 return parse_bool(&opt_rev_graph, argv[2]);
1967 if (!strcmp(argv[0], "show-refs"))
1968 return parse_bool(&opt_show_refs, argv[2]);
1970 if (!strcmp(argv[0], "show-line-numbers"))
1971 return parse_bool(&opt_line_number, argv[2]);
1973 if (!strcmp(argv[0], "line-graphics"))
1974 return parse_bool(&opt_line_graphics, argv[2]);
1976 if (!strcmp(argv[0], "line-number-interval"))
1977 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1979 if (!strcmp(argv[0], "author-width"))
1980 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1982 if (!strcmp(argv[0], "horizontal-scroll"))
1983 return parse_step(&opt_hscroll, argv[2]);
1985 if (!strcmp(argv[0], "split-view-height"))
1986 return parse_step(&opt_scale_split_view, argv[2]);
1988 if (!strcmp(argv[0], "tab-size"))
1989 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1991 if (!strcmp(argv[0], "commit-encoding"))
1992 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1994 config_msg = "Unknown variable name";
1995 return ERR;
1996 }
1998 /* Wants: mode request key */
1999 static int
2000 option_bind_command(int argc, const char *argv[])
2001 {
2002 enum request request;
2003 int keymap = -1;
2004 int key;
2006 if (argc < 3) {
2007 config_msg = "Wrong number of arguments given to bind command";
2008 return ERR;
2009 }
2011 if (!set_keymap(&keymap, argv[0])) {
2012 config_msg = "Unknown key map";
2013 return ERR;
2014 }
2016 key = get_key_value(argv[1]);
2017 if (key == ERR) {
2018 config_msg = "Unknown key";
2019 return ERR;
2020 }
2022 request = get_request(argv[2]);
2023 if (request == REQ_UNKNOWN) {
2024 static const struct enum_map obsolete[] = {
2025 ENUM_MAP("cherry-pick", REQ_NONE),
2026 ENUM_MAP("screen-resize", REQ_NONE),
2027 ENUM_MAP("tree-parent", REQ_PARENT),
2028 };
2029 int alias;
2031 if (map_enum(&alias, obsolete, argv[2])) {
2032 if (alias != REQ_NONE)
2033 add_keybinding(keymap, alias, key);
2034 config_msg = "Obsolete request name";
2035 return ERR;
2036 }
2037 }
2038 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2039 request = add_run_request(keymap, key, argv + 2);
2040 if (request == REQ_UNKNOWN) {
2041 config_msg = "Unknown request name";
2042 return ERR;
2043 }
2045 add_keybinding(keymap, request, key);
2047 return OK;
2048 }
2050 static int
2051 set_option(const char *opt, char *value)
2052 {
2053 const char *argv[SIZEOF_ARG];
2054 int argc = 0;
2056 if (!argv_from_string(argv, &argc, value)) {
2057 config_msg = "Too many option arguments";
2058 return ERR;
2059 }
2061 if (!strcmp(opt, "color"))
2062 return option_color_command(argc, argv);
2064 if (!strcmp(opt, "set"))
2065 return option_set_command(argc, argv);
2067 if (!strcmp(opt, "bind"))
2068 return option_bind_command(argc, argv);
2070 config_msg = "Unknown option command";
2071 return ERR;
2072 }
2074 static int
2075 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2076 {
2077 int status = OK;
2079 config_lineno++;
2080 config_msg = "Internal error";
2082 /* Check for comment markers, since read_properties() will
2083 * only ensure opt and value are split at first " \t". */
2084 optlen = strcspn(opt, "#");
2085 if (optlen == 0)
2086 return OK;
2088 if (opt[optlen] != 0) {
2089 config_msg = "No option value";
2090 status = ERR;
2092 } else {
2093 /* Look for comment endings in the value. */
2094 size_t len = strcspn(value, "#");
2096 if (len < valuelen) {
2097 valuelen = len;
2098 value[valuelen] = 0;
2099 }
2101 status = set_option(opt, value);
2102 }
2104 if (status == ERR) {
2105 warn("Error on line %d, near '%.*s': %s",
2106 config_lineno, (int) optlen, opt, config_msg);
2107 config_errors = TRUE;
2108 }
2110 /* Always keep going if errors are encountered. */
2111 return OK;
2112 }
2114 static void
2115 load_option_file(const char *path)
2116 {
2117 struct io io;
2119 /* It's OK that the file doesn't exist. */
2120 if (!io_open(&io, "%s", path))
2121 return;
2123 config_lineno = 0;
2124 config_errors = FALSE;
2126 if (io_load(&io, " \t", read_option) == ERR ||
2127 config_errors == TRUE)
2128 warn("Errors while loading %s.", path);
2129 }
2131 static int
2132 load_options(void)
2133 {
2134 const char *home = getenv("HOME");
2135 const char *tigrc_user = getenv("TIGRC_USER");
2136 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2137 char buf[SIZEOF_STR];
2139 if (!tigrc_system)
2140 tigrc_system = SYSCONFDIR "/tigrc";
2141 load_option_file(tigrc_system);
2143 if (!tigrc_user) {
2144 if (!home || !string_format(buf, "%s/.tigrc", home))
2145 return ERR;
2146 tigrc_user = buf;
2147 }
2148 load_option_file(tigrc_user);
2150 /* Add _after_ loading config files to avoid adding run requests
2151 * that conflict with keybindings. */
2152 add_builtin_run_requests();
2154 return OK;
2155 }
2158 /*
2159 * The viewer
2160 */
2162 struct view;
2163 struct view_ops;
2165 /* The display array of active views and the index of the current view. */
2166 static struct view *display[2];
2167 static unsigned int current_view;
2169 #define foreach_displayed_view(view, i) \
2170 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2172 #define displayed_views() (display[1] != NULL ? 2 : 1)
2174 /* Current head and commit ID */
2175 static char ref_blob[SIZEOF_REF] = "";
2176 static char ref_commit[SIZEOF_REF] = "HEAD";
2177 static char ref_head[SIZEOF_REF] = "HEAD";
2178 static char ref_branch[SIZEOF_REF] = "";
2180 enum view_type {
2181 VIEW_MAIN,
2182 VIEW_DIFF,
2183 VIEW_LOG,
2184 VIEW_TREE,
2185 VIEW_BLOB,
2186 VIEW_BLAME,
2187 VIEW_BRANCH,
2188 VIEW_HELP,
2189 VIEW_PAGER,
2190 VIEW_STATUS,
2191 VIEW_STAGE,
2192 };
2194 struct view {
2195 enum view_type type; /* View type */
2196 const char *name; /* View name */
2197 const char *cmd_env; /* Command line set via environment */
2198 const char *id; /* Points to either of ref_{head,commit,blob} */
2200 struct view_ops *ops; /* View operations */
2202 enum keymap keymap; /* What keymap does this view have */
2203 bool git_dir; /* Whether the view requires a git directory. */
2205 char ref[SIZEOF_REF]; /* Hovered commit reference */
2206 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2208 int height, width; /* The width and height of the main window */
2209 WINDOW *win; /* The main window */
2210 WINDOW *title; /* The title window living below the main window */
2212 /* Navigation */
2213 unsigned long offset; /* Offset of the window top */
2214 unsigned long yoffset; /* Offset from the window side. */
2215 unsigned long lineno; /* Current line number */
2216 unsigned long p_offset; /* Previous offset of the window top */
2217 unsigned long p_yoffset;/* Previous offset from the window side */
2218 unsigned long p_lineno; /* Previous current line number */
2219 bool p_restore; /* Should the previous position be restored. */
2221 /* Searching */
2222 char grep[SIZEOF_STR]; /* Search string */
2223 regex_t *regex; /* Pre-compiled regexp */
2225 /* If non-NULL, points to the view that opened this view. If this view
2226 * is closed tig will switch back to the parent view. */
2227 struct view *parent;
2228 struct view *prev;
2230 /* Buffering */
2231 size_t lines; /* Total number of lines */
2232 struct line *line; /* Line index */
2233 unsigned int digits; /* Number of digits in the lines member. */
2235 /* Drawing */
2236 struct line *curline; /* Line currently being drawn. */
2237 enum line_type curtype; /* Attribute currently used for drawing. */
2238 unsigned long col; /* Column when drawing. */
2239 bool has_scrolled; /* View was scrolled. */
2241 /* Loading */
2242 const char **argv; /* Shell command arguments. */
2243 const char *dir; /* Directory from which to execute. */
2244 struct io io;
2245 struct io *pipe;
2246 time_t start_time;
2247 time_t update_secs;
2248 };
2250 struct view_ops {
2251 /* What type of content being displayed. Used in the title bar. */
2252 const char *type;
2253 /* Default command arguments. */
2254 const char **argv;
2255 /* Open and reads in all view content. */
2256 bool (*open)(struct view *view);
2257 /* Read one line; updates view->line. */
2258 bool (*read)(struct view *view, char *data);
2259 /* Draw one line; @lineno must be < view->height. */
2260 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2261 /* Depending on view handle a special requests. */
2262 enum request (*request)(struct view *view, enum request request, struct line *line);
2263 /* Search for regexp in a line. */
2264 bool (*grep)(struct view *view, struct line *line);
2265 /* Select line */
2266 void (*select)(struct view *view, struct line *line);
2267 /* Prepare view for loading */
2268 bool (*prepare)(struct view *view);
2269 };
2271 static struct view_ops blame_ops;
2272 static struct view_ops blob_ops;
2273 static struct view_ops diff_ops;
2274 static struct view_ops help_ops;
2275 static struct view_ops log_ops;
2276 static struct view_ops main_ops;
2277 static struct view_ops pager_ops;
2278 static struct view_ops stage_ops;
2279 static struct view_ops status_ops;
2280 static struct view_ops tree_ops;
2281 static struct view_ops branch_ops;
2283 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2284 { type, name, #env, ref, ops, map, git }
2286 #define VIEW_(id, name, ops, git, ref) \
2287 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2289 static struct view views[] = {
2290 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2291 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2292 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2293 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2294 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2295 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2296 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2297 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2298 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2299 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2300 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2301 };
2303 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2305 #define foreach_view(view, i) \
2306 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2308 #define view_is_displayed(view) \
2309 (view == display[0] || view == display[1])
2311 static enum request
2312 view_request(struct view *view, enum request request)
2313 {
2314 if (!view || !view->lines)
2315 return request;
2316 return view->ops->request(view, request, &view->line[view->lineno]);
2317 }
2320 /*
2321 * View drawing.
2322 */
2324 static inline void
2325 set_view_attr(struct view *view, enum line_type type)
2326 {
2327 if (!view->curline->selected && view->curtype != type) {
2328 (void) wattrset(view->win, get_line_attr(type));
2329 wchgat(view->win, -1, 0, type, NULL);
2330 view->curtype = type;
2331 }
2332 }
2334 static int
2335 draw_chars(struct view *view, enum line_type type, const char *string,
2336 int max_len, bool use_tilde)
2337 {
2338 static char out_buffer[BUFSIZ * 2];
2339 int len = 0;
2340 int col = 0;
2341 int trimmed = FALSE;
2342 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2344 if (max_len <= 0)
2345 return 0;
2347 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2349 set_view_attr(view, type);
2350 if (len > 0) {
2351 if (opt_iconv_out != ICONV_NONE) {
2352 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2353 size_t inlen = len + 1;
2355 char *outbuf = out_buffer;
2356 size_t outlen = sizeof(out_buffer);
2358 size_t ret;
2360 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2361 if (ret != (size_t) -1) {
2362 string = out_buffer;
2363 len = sizeof(out_buffer) - outlen;
2364 }
2365 }
2367 waddnstr(view->win, string, len);
2368 }
2369 if (trimmed && use_tilde) {
2370 set_view_attr(view, LINE_DELIMITER);
2371 waddch(view->win, '~');
2372 col++;
2373 }
2375 return col;
2376 }
2378 static int
2379 draw_space(struct view *view, enum line_type type, int max, int spaces)
2380 {
2381 static char space[] = " ";
2382 int col = 0;
2384 spaces = MIN(max, spaces);
2386 while (spaces > 0) {
2387 int len = MIN(spaces, sizeof(space) - 1);
2389 col += draw_chars(view, type, space, len, FALSE);
2390 spaces -= len;
2391 }
2393 return col;
2394 }
2396 static bool
2397 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2398 {
2399 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2400 return view->width + view->yoffset <= view->col;
2401 }
2403 static bool
2404 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2405 {
2406 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2407 int max = view->width + view->yoffset - view->col;
2408 int i;
2410 if (max < size)
2411 size = max;
2413 set_view_attr(view, type);
2414 /* Using waddch() instead of waddnstr() ensures that
2415 * they'll be rendered correctly for the cursor line. */
2416 for (i = skip; i < size; i++)
2417 waddch(view->win, graphic[i]);
2419 view->col += size;
2420 if (size < max && skip <= size)
2421 waddch(view->win, ' ');
2422 view->col++;
2424 return view->width + view->yoffset <= view->col;
2425 }
2427 static bool
2428 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2429 {
2430 int max = MIN(view->width + view->yoffset - view->col, len);
2431 int col;
2433 if (text)
2434 col = draw_chars(view, type, text, max - 1, trim);
2435 else
2436 col = draw_space(view, type, max - 1, max - 1);
2438 view->col += col;
2439 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2440 return view->width + view->yoffset <= view->col;
2441 }
2443 static bool
2444 draw_date(struct view *view, struct time *time)
2445 {
2446 const char *date = mkdate(time, opt_date);
2447 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2449 return draw_field(view, LINE_DATE, date, cols, FALSE);
2450 }
2452 static bool
2453 draw_author(struct view *view, const char *author)
2454 {
2455 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2456 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2458 if (abbreviate && author)
2459 author = get_author_initials(author);
2461 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2462 }
2464 static bool
2465 draw_mode(struct view *view, mode_t mode)
2466 {
2467 const char *str;
2469 if (S_ISDIR(mode))
2470 str = "drwxr-xr-x";
2471 else if (S_ISLNK(mode))
2472 str = "lrwxrwxrwx";
2473 else if (S_ISGITLINK(mode))
2474 str = "m---------";
2475 else if (S_ISREG(mode) && mode & S_IXUSR)
2476 str = "-rwxr-xr-x";
2477 else if (S_ISREG(mode))
2478 str = "-rw-r--r--";
2479 else
2480 str = "----------";
2482 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2483 }
2485 static bool
2486 draw_lineno(struct view *view, unsigned int lineno)
2487 {
2488 char number[10];
2489 int digits3 = view->digits < 3 ? 3 : view->digits;
2490 int max = MIN(view->width + view->yoffset - view->col, digits3);
2491 char *text = NULL;
2492 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2494 lineno += view->offset + 1;
2495 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2496 static char fmt[] = "%1ld";
2498 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2499 if (string_format(number, fmt, lineno))
2500 text = number;
2501 }
2502 if (text)
2503 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2504 else
2505 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2506 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2507 }
2509 static bool
2510 draw_view_line(struct view *view, unsigned int lineno)
2511 {
2512 struct line *line;
2513 bool selected = (view->offset + lineno == view->lineno);
2515 assert(view_is_displayed(view));
2517 if (view->offset + lineno >= view->lines)
2518 return FALSE;
2520 line = &view->line[view->offset + lineno];
2522 wmove(view->win, lineno, 0);
2523 if (line->cleareol)
2524 wclrtoeol(view->win);
2525 view->col = 0;
2526 view->curline = line;
2527 view->curtype = LINE_NONE;
2528 line->selected = FALSE;
2529 line->dirty = line->cleareol = 0;
2531 if (selected) {
2532 set_view_attr(view, LINE_CURSOR);
2533 line->selected = TRUE;
2534 view->ops->select(view, line);
2535 }
2537 return view->ops->draw(view, line, lineno);
2538 }
2540 static void
2541 redraw_view_dirty(struct view *view)
2542 {
2543 bool dirty = FALSE;
2544 int lineno;
2546 for (lineno = 0; lineno < view->height; lineno++) {
2547 if (view->offset + lineno >= view->lines)
2548 break;
2549 if (!view->line[view->offset + lineno].dirty)
2550 continue;
2551 dirty = TRUE;
2552 if (!draw_view_line(view, lineno))
2553 break;
2554 }
2556 if (!dirty)
2557 return;
2558 wnoutrefresh(view->win);
2559 }
2561 static void
2562 redraw_view_from(struct view *view, int lineno)
2563 {
2564 assert(0 <= lineno && lineno < view->height);
2566 for (; lineno < view->height; lineno++) {
2567 if (!draw_view_line(view, lineno))
2568 break;
2569 }
2571 wnoutrefresh(view->win);
2572 }
2574 static void
2575 redraw_view(struct view *view)
2576 {
2577 werase(view->win);
2578 redraw_view_from(view, 0);
2579 }
2582 static void
2583 update_view_title(struct view *view)
2584 {
2585 char buf[SIZEOF_STR];
2586 char state[SIZEOF_STR];
2587 size_t bufpos = 0, statelen = 0;
2589 assert(view_is_displayed(view));
2591 if (view->type != VIEW_STATUS && view->lines) {
2592 unsigned int view_lines = view->offset + view->height;
2593 unsigned int lines = view->lines
2594 ? MIN(view_lines, view->lines) * 100 / view->lines
2595 : 0;
2597 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2598 view->ops->type,
2599 view->lineno + 1,
2600 view->lines,
2601 lines);
2603 }
2605 if (view->pipe) {
2606 time_t secs = time(NULL) - view->start_time;
2608 /* Three git seconds are a long time ... */
2609 if (secs > 2)
2610 string_format_from(state, &statelen, " loading %lds", secs);
2611 }
2613 string_format_from(buf, &bufpos, "[%s]", view->name);
2614 if (*view->ref && bufpos < view->width) {
2615 size_t refsize = strlen(view->ref);
2616 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2618 if (minsize < view->width)
2619 refsize = view->width - minsize + 7;
2620 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2621 }
2623 if (statelen && bufpos < view->width) {
2624 string_format_from(buf, &bufpos, "%s", state);
2625 }
2627 if (view == display[current_view])
2628 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2629 else
2630 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2632 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2633 wclrtoeol(view->title);
2634 wnoutrefresh(view->title);
2635 }
2637 static int
2638 apply_step(double step, int value)
2639 {
2640 if (step >= 1)
2641 return (int) step;
2642 value *= step + 0.01;
2643 return value ? value : 1;
2644 }
2646 static void
2647 resize_display(void)
2648 {
2649 int offset, i;
2650 struct view *base = display[0];
2651 struct view *view = display[1] ? display[1] : display[0];
2653 /* Setup window dimensions */
2655 getmaxyx(stdscr, base->height, base->width);
2657 /* Make room for the status window. */
2658 base->height -= 1;
2660 if (view != base) {
2661 /* Horizontal split. */
2662 view->width = base->width;
2663 view->height = apply_step(opt_scale_split_view, base->height);
2664 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2665 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2666 base->height -= view->height;
2668 /* Make room for the title bar. */
2669 view->height -= 1;
2670 }
2672 /* Make room for the title bar. */
2673 base->height -= 1;
2675 offset = 0;
2677 foreach_displayed_view (view, i) {
2678 if (!view->win) {
2679 view->win = newwin(view->height, 0, offset, 0);
2680 if (!view->win)
2681 die("Failed to create %s view", view->name);
2683 scrollok(view->win, FALSE);
2685 view->title = newwin(1, 0, offset + view->height, 0);
2686 if (!view->title)
2687 die("Failed to create title window");
2689 } else {
2690 wresize(view->win, view->height, view->width);
2691 mvwin(view->win, offset, 0);
2692 mvwin(view->title, offset + view->height, 0);
2693 }
2695 offset += view->height + 1;
2696 }
2697 }
2699 static void
2700 redraw_display(bool clear)
2701 {
2702 struct view *view;
2703 int i;
2705 foreach_displayed_view (view, i) {
2706 if (clear)
2707 wclear(view->win);
2708 redraw_view(view);
2709 update_view_title(view);
2710 }
2711 }
2714 /*
2715 * Option management
2716 */
2718 static void
2719 toggle_enum_option_do(unsigned int *opt, const char *help,
2720 const struct enum_map *map, size_t size)
2721 {
2722 *opt = (*opt + 1) % size;
2723 redraw_display(FALSE);
2724 report("Displaying %s %s", enum_name(map[*opt]), help);
2725 }
2727 #define toggle_enum_option(opt, help, map) \
2728 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2730 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2731 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2733 static void
2734 toggle_view_option(bool *option, const char *help)
2735 {
2736 *option = !*option;
2737 redraw_display(FALSE);
2738 report("%sabling %s", *option ? "En" : "Dis", help);
2739 }
2741 static void
2742 open_option_menu(void)
2743 {
2744 const struct menu_item menu[] = {
2745 { '.', "line numbers", &opt_line_number },
2746 { 'D', "date display", &opt_date },
2747 { 'A', "author display", &opt_author },
2748 { 'g', "revision graph display", &opt_rev_graph },
2749 { 'F', "reference display", &opt_show_refs },
2750 { 0 }
2751 };
2752 int selected = 0;
2754 if (prompt_menu("Toggle option", menu, &selected)) {
2755 if (menu[selected].data == &opt_date)
2756 toggle_date();
2757 else if (menu[selected].data == &opt_author)
2758 toggle_author();
2759 else
2760 toggle_view_option(menu[selected].data, menu[selected].text);
2761 }
2762 }
2764 static void
2765 maximize_view(struct view *view)
2766 {
2767 memset(display, 0, sizeof(display));
2768 current_view = 0;
2769 display[current_view] = view;
2770 resize_display();
2771 redraw_display(FALSE);
2772 report("");
2773 }
2776 /*
2777 * Navigation
2778 */
2780 static bool
2781 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2782 {
2783 if (lineno >= view->lines)
2784 lineno = view->lines > 0 ? view->lines - 1 : 0;
2786 if (offset > lineno || offset + view->height <= lineno) {
2787 unsigned long half = view->height / 2;
2789 if (lineno > half)
2790 offset = lineno - half;
2791 else
2792 offset = 0;
2793 }
2795 if (offset != view->offset || lineno != view->lineno) {
2796 view->offset = offset;
2797 view->lineno = lineno;
2798 return TRUE;
2799 }
2801 return FALSE;
2802 }
2804 /* Scrolling backend */
2805 static void
2806 do_scroll_view(struct view *view, int lines)
2807 {
2808 bool redraw_current_line = FALSE;
2810 /* The rendering expects the new offset. */
2811 view->offset += lines;
2813 assert(0 <= view->offset && view->offset < view->lines);
2814 assert(lines);
2816 /* Move current line into the view. */
2817 if (view->lineno < view->offset) {
2818 view->lineno = view->offset;
2819 redraw_current_line = TRUE;
2820 } else if (view->lineno >= view->offset + view->height) {
2821 view->lineno = view->offset + view->height - 1;
2822 redraw_current_line = TRUE;
2823 }
2825 assert(view->offset <= view->lineno && view->lineno < view->lines);
2827 /* Redraw the whole screen if scrolling is pointless. */
2828 if (view->height < ABS(lines)) {
2829 redraw_view(view);
2831 } else {
2832 int line = lines > 0 ? view->height - lines : 0;
2833 int end = line + ABS(lines);
2835 scrollok(view->win, TRUE);
2836 wscrl(view->win, lines);
2837 scrollok(view->win, FALSE);
2839 while (line < end && draw_view_line(view, line))
2840 line++;
2842 if (redraw_current_line)
2843 draw_view_line(view, view->lineno - view->offset);
2844 wnoutrefresh(view->win);
2845 }
2847 view->has_scrolled = TRUE;
2848 report("");
2849 }
2851 /* Scroll frontend */
2852 static void
2853 scroll_view(struct view *view, enum request request)
2854 {
2855 int lines = 1;
2857 assert(view_is_displayed(view));
2859 switch (request) {
2860 case REQ_SCROLL_LEFT:
2861 if (view->yoffset == 0) {
2862 report("Cannot scroll beyond the first column");
2863 return;
2864 }
2865 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2866 view->yoffset = 0;
2867 else
2868 view->yoffset -= apply_step(opt_hscroll, view->width);
2869 redraw_view_from(view, 0);
2870 report("");
2871 return;
2872 case REQ_SCROLL_RIGHT:
2873 view->yoffset += apply_step(opt_hscroll, view->width);
2874 redraw_view(view);
2875 report("");
2876 return;
2877 case REQ_SCROLL_PAGE_DOWN:
2878 lines = view->height;
2879 case REQ_SCROLL_LINE_DOWN:
2880 if (view->offset + lines > view->lines)
2881 lines = view->lines - view->offset;
2883 if (lines == 0 || view->offset + view->height >= view->lines) {
2884 report("Cannot scroll beyond the last line");
2885 return;
2886 }
2887 break;
2889 case REQ_SCROLL_PAGE_UP:
2890 lines = view->height;
2891 case REQ_SCROLL_LINE_UP:
2892 if (lines > view->offset)
2893 lines = view->offset;
2895 if (lines == 0) {
2896 report("Cannot scroll beyond the first line");
2897 return;
2898 }
2900 lines = -lines;
2901 break;
2903 default:
2904 die("request %d not handled in switch", request);
2905 }
2907 do_scroll_view(view, lines);
2908 }
2910 /* Cursor moving */
2911 static void
2912 move_view(struct view *view, enum request request)
2913 {
2914 int scroll_steps = 0;
2915 int steps;
2917 switch (request) {
2918 case REQ_MOVE_FIRST_LINE:
2919 steps = -view->lineno;
2920 break;
2922 case REQ_MOVE_LAST_LINE:
2923 steps = view->lines - view->lineno - 1;
2924 break;
2926 case REQ_MOVE_PAGE_UP:
2927 steps = view->height > view->lineno
2928 ? -view->lineno : -view->height;
2929 break;
2931 case REQ_MOVE_PAGE_DOWN:
2932 steps = view->lineno + view->height >= view->lines
2933 ? view->lines - view->lineno - 1 : view->height;
2934 break;
2936 case REQ_MOVE_UP:
2937 steps = -1;
2938 break;
2940 case REQ_MOVE_DOWN:
2941 steps = 1;
2942 break;
2944 default:
2945 die("request %d not handled in switch", request);
2946 }
2948 if (steps <= 0 && view->lineno == 0) {
2949 report("Cannot move beyond the first line");
2950 return;
2952 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2953 report("Cannot move beyond the last line");
2954 return;
2955 }
2957 /* Move the current line */
2958 view->lineno += steps;
2959 assert(0 <= view->lineno && view->lineno < view->lines);
2961 /* Check whether the view needs to be scrolled */
2962 if (view->lineno < view->offset ||
2963 view->lineno >= view->offset + view->height) {
2964 scroll_steps = steps;
2965 if (steps < 0 && -steps > view->offset) {
2966 scroll_steps = -view->offset;
2968 } else if (steps > 0) {
2969 if (view->lineno == view->lines - 1 &&
2970 view->lines > view->height) {
2971 scroll_steps = view->lines - view->offset - 1;
2972 if (scroll_steps >= view->height)
2973 scroll_steps -= view->height - 1;
2974 }
2975 }
2976 }
2978 if (!view_is_displayed(view)) {
2979 view->offset += scroll_steps;
2980 assert(0 <= view->offset && view->offset < view->lines);
2981 view->ops->select(view, &view->line[view->lineno]);
2982 return;
2983 }
2985 /* Repaint the old "current" line if we be scrolling */
2986 if (ABS(steps) < view->height)
2987 draw_view_line(view, view->lineno - steps - view->offset);
2989 if (scroll_steps) {
2990 do_scroll_view(view, scroll_steps);
2991 return;
2992 }
2994 /* Draw the current line */
2995 draw_view_line(view, view->lineno - view->offset);
2997 wnoutrefresh(view->win);
2998 report("");
2999 }
3002 /*
3003 * Searching
3004 */
3006 static void search_view(struct view *view, enum request request);
3008 static bool
3009 grep_text(struct view *view, const char *text[])
3010 {
3011 regmatch_t pmatch;
3012 size_t i;
3014 for (i = 0; text[i]; i++)
3015 if (*text[i] &&
3016 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3017 return TRUE;
3018 return FALSE;
3019 }
3021 static void
3022 select_view_line(struct view *view, unsigned long lineno)
3023 {
3024 unsigned long old_lineno = view->lineno;
3025 unsigned long old_offset = view->offset;
3027 if (goto_view_line(view, view->offset, lineno)) {
3028 if (view_is_displayed(view)) {
3029 if (old_offset != view->offset) {
3030 redraw_view(view);
3031 } else {
3032 draw_view_line(view, old_lineno - view->offset);
3033 draw_view_line(view, view->lineno - view->offset);
3034 wnoutrefresh(view->win);
3035 }
3036 } else {
3037 view->ops->select(view, &view->line[view->lineno]);
3038 }
3039 }
3040 }
3042 static void
3043 find_next(struct view *view, enum request request)
3044 {
3045 unsigned long lineno = view->lineno;
3046 int direction;
3048 if (!*view->grep) {
3049 if (!*opt_search)
3050 report("No previous search");
3051 else
3052 search_view(view, request);
3053 return;
3054 }
3056 switch (request) {
3057 case REQ_SEARCH:
3058 case REQ_FIND_NEXT:
3059 direction = 1;
3060 break;
3062 case REQ_SEARCH_BACK:
3063 case REQ_FIND_PREV:
3064 direction = -1;
3065 break;
3067 default:
3068 return;
3069 }
3071 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3072 lineno += direction;
3074 /* Note, lineno is unsigned long so will wrap around in which case it
3075 * will become bigger than view->lines. */
3076 for (; lineno < view->lines; lineno += direction) {
3077 if (view->ops->grep(view, &view->line[lineno])) {
3078 select_view_line(view, lineno);
3079 report("Line %ld matches '%s'", lineno + 1, view->grep);
3080 return;
3081 }
3082 }
3084 report("No match found for '%s'", view->grep);
3085 }
3087 static void
3088 search_view(struct view *view, enum request request)
3089 {
3090 int regex_err;
3092 if (view->regex) {
3093 regfree(view->regex);
3094 *view->grep = 0;
3095 } else {
3096 view->regex = calloc(1, sizeof(*view->regex));
3097 if (!view->regex)
3098 return;
3099 }
3101 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3102 if (regex_err != 0) {
3103 char buf[SIZEOF_STR] = "unknown error";
3105 regerror(regex_err, view->regex, buf, sizeof(buf));
3106 report("Search failed: %s", buf);
3107 return;
3108 }
3110 string_copy(view->grep, opt_search);
3112 find_next(view, request);
3113 }
3115 /*
3116 * Incremental updating
3117 */
3119 static void
3120 reset_view(struct view *view)
3121 {
3122 int i;
3124 for (i = 0; i < view->lines; i++)
3125 free(view->line[i].data);
3126 free(view->line);
3128 view->p_offset = view->offset;
3129 view->p_yoffset = view->yoffset;
3130 view->p_lineno = view->lineno;
3132 view->line = NULL;
3133 view->offset = 0;
3134 view->yoffset = 0;
3135 view->lines = 0;
3136 view->lineno = 0;
3137 view->vid[0] = 0;
3138 view->update_secs = 0;
3139 }
3141 static const char *
3142 format_arg(const char *name)
3143 {
3144 static struct {
3145 const char *name;
3146 size_t namelen;
3147 const char *value;
3148 const char *value_if_empty;
3149 } vars[] = {
3150 #define FORMAT_VAR(name, value, value_if_empty) \
3151 { name, STRING_SIZE(name), value, value_if_empty }
3152 FORMAT_VAR("%(directory)", opt_path, ""),
3153 FORMAT_VAR("%(file)", opt_file, ""),
3154 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3155 FORMAT_VAR("%(head)", ref_head, ""),
3156 FORMAT_VAR("%(commit)", ref_commit, ""),
3157 FORMAT_VAR("%(blob)", ref_blob, ""),
3158 FORMAT_VAR("%(branch)", ref_branch, ""),
3159 };
3160 int i;
3162 for (i = 0; i < ARRAY_SIZE(vars); i++)
3163 if (!strncmp(name, vars[i].name, vars[i].namelen))
3164 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3166 report("Unknown replacement: `%s`", name);
3167 return NULL;
3168 }
3170 static bool
3171 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3172 {
3173 char buf[SIZEOF_STR];
3174 int argc;
3176 argv_free(*dst_argv);
3178 for (argc = 0; src_argv[argc]; argc++) {
3179 const char *arg = src_argv[argc];
3180 size_t bufpos = 0;
3182 if (!strcmp(arg, "%(file-args)")) {
3183 if (!argv_append_array(dst_argv, opt_file_args))
3184 break;
3185 continue;
3187 } else if (!strcmp(arg, "%(diff-args)")) {
3188 if (!argv_append_array(dst_argv, opt_diff_args))
3189 break;
3190 continue;
3192 } else if (!strcmp(arg, "%(rev-args)")) {
3193 if (!argv_append_array(dst_argv, opt_rev_args))
3194 break;
3195 continue;
3196 }
3198 while (arg) {
3199 char *next = strstr(arg, "%(");
3200 int len = next - arg;
3201 const char *value;
3203 if (!next || !replace) {
3204 len = strlen(arg);
3205 value = "";
3207 } else {
3208 value = format_arg(next);
3210 if (!value) {
3211 return FALSE;
3212 }
3213 }
3215 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3216 return FALSE;
3218 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3219 }
3221 if (!argv_append(dst_argv, buf))
3222 break;
3223 }
3225 return src_argv[argc] == NULL;
3226 }
3228 static bool
3229 restore_view_position(struct view *view)
3230 {
3231 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3232 return FALSE;
3234 /* Changing the view position cancels the restoring. */
3235 /* FIXME: Changing back to the first line is not detected. */
3236 if (view->offset != 0 || view->lineno != 0) {
3237 view->p_restore = FALSE;
3238 return FALSE;
3239 }
3241 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3242 view_is_displayed(view))
3243 werase(view->win);
3245 view->yoffset = view->p_yoffset;
3246 view->p_restore = FALSE;
3248 return TRUE;
3249 }
3251 static void
3252 end_update(struct view *view, bool force)
3253 {
3254 if (!view->pipe)
3255 return;
3256 while (!view->ops->read(view, NULL))
3257 if (!force)
3258 return;
3259 if (force)
3260 io_kill(view->pipe);
3261 io_done(view->pipe);
3262 view->pipe = NULL;
3263 }
3265 static void
3266 setup_update(struct view *view, const char *vid)
3267 {
3268 reset_view(view);
3269 string_copy_rev(view->vid, vid);
3270 view->pipe = &view->io;
3271 view->start_time = time(NULL);
3272 }
3274 static bool
3275 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3276 {
3277 view->dir = dir;
3278 return format_argv(&view->argv, argv, replace);
3279 }
3281 static bool
3282 prepare_update(struct view *view, const char *argv[], const char *dir)
3283 {
3284 if (view->pipe)
3285 end_update(view, TRUE);
3286 return prepare_io(view, dir, argv, FALSE);
3287 }
3289 static bool
3290 start_update(struct view *view, const char **argv, const char *dir)
3291 {
3292 if (view->pipe)
3293 io_done(view->pipe);
3294 return prepare_io(view, dir, argv, FALSE) &&
3295 io_run(&view->io, IO_RD, dir, view->argv);
3296 }
3298 static bool
3299 prepare_update_file(struct view *view, const char *name)
3300 {
3301 if (view->pipe)
3302 end_update(view, TRUE);
3303 argv_free(view->argv);
3304 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3305 }
3307 static bool
3308 begin_update(struct view *view, bool refresh)
3309 {
3310 if (view->pipe)
3311 end_update(view, TRUE);
3313 if (!refresh) {
3314 if (view->ops->prepare) {
3315 if (!view->ops->prepare(view))
3316 return FALSE;
3317 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3318 return FALSE;
3319 }
3321 /* Put the current ref_* value to the view title ref
3322 * member. This is needed by the blob view. Most other
3323 * views sets it automatically after loading because the
3324 * first line is a commit line. */
3325 string_copy_rev(view->ref, view->id);
3326 }
3328 if (view->argv && view->argv[0] &&
3329 !io_run(&view->io, IO_RD, view->dir, view->argv))
3330 return FALSE;
3332 setup_update(view, view->id);
3334 return TRUE;
3335 }
3337 static bool
3338 update_view(struct view *view)
3339 {
3340 char out_buffer[BUFSIZ * 2];
3341 char *line;
3342 /* Clear the view and redraw everything since the tree sorting
3343 * might have rearranged things. */
3344 bool redraw = view->lines == 0;
3345 bool can_read = TRUE;
3347 if (!view->pipe)
3348 return TRUE;
3350 if (!io_can_read(view->pipe)) {
3351 if (view->lines == 0 && view_is_displayed(view)) {
3352 time_t secs = time(NULL) - view->start_time;
3354 if (secs > 1 && secs > view->update_secs) {
3355 if (view->update_secs == 0)
3356 redraw_view(view);
3357 update_view_title(view);
3358 view->update_secs = secs;
3359 }
3360 }
3361 return TRUE;
3362 }
3364 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3365 if (opt_iconv_in != ICONV_NONE) {
3366 ICONV_CONST char *inbuf = line;
3367 size_t inlen = strlen(line) + 1;
3369 char *outbuf = out_buffer;
3370 size_t outlen = sizeof(out_buffer);
3372 size_t ret;
3374 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3375 if (ret != (size_t) -1)
3376 line = out_buffer;
3377 }
3379 if (!view->ops->read(view, line)) {
3380 report("Allocation failure");
3381 end_update(view, TRUE);
3382 return FALSE;
3383 }
3384 }
3386 {
3387 unsigned long lines = view->lines;
3388 int digits;
3390 for (digits = 0; lines; digits++)
3391 lines /= 10;
3393 /* Keep the displayed view in sync with line number scaling. */
3394 if (digits != view->digits) {
3395 view->digits = digits;
3396 if (opt_line_number || view->type == VIEW_BLAME)
3397 redraw = TRUE;
3398 }
3399 }
3401 if (io_error(view->pipe)) {
3402 report("Failed to read: %s", io_strerror(view->pipe));
3403 end_update(view, TRUE);
3405 } else if (io_eof(view->pipe)) {
3406 if (view_is_displayed(view))
3407 report("");
3408 end_update(view, FALSE);
3409 }
3411 if (restore_view_position(view))
3412 redraw = TRUE;
3414 if (!view_is_displayed(view))
3415 return TRUE;
3417 if (redraw)
3418 redraw_view_from(view, 0);
3419 else
3420 redraw_view_dirty(view);
3422 /* Update the title _after_ the redraw so that if the redraw picks up a
3423 * commit reference in view->ref it'll be available here. */
3424 update_view_title(view);
3425 return TRUE;
3426 }
3428 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3430 static struct line *
3431 add_line_data(struct view *view, void *data, enum line_type type)
3432 {
3433 struct line *line;
3435 if (!realloc_lines(&view->line, view->lines, 1))
3436 return NULL;
3438 line = &view->line[view->lines++];
3439 memset(line, 0, sizeof(*line));
3440 line->type = type;
3441 line->data = data;
3442 line->dirty = 1;
3444 return line;
3445 }
3447 static struct line *
3448 add_line_text(struct view *view, const char *text, enum line_type type)
3449 {
3450 char *data = text ? strdup(text) : NULL;
3452 return data ? add_line_data(view, data, type) : NULL;
3453 }
3455 static struct line *
3456 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3457 {
3458 char buf[SIZEOF_STR];
3459 va_list args;
3461 va_start(args, fmt);
3462 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3463 buf[0] = 0;
3464 va_end(args);
3466 return buf[0] ? add_line_text(view, buf, type) : NULL;
3467 }
3469 /*
3470 * View opening
3471 */
3473 enum open_flags {
3474 OPEN_DEFAULT = 0, /* Use default view switching. */
3475 OPEN_SPLIT = 1, /* Split current view. */
3476 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3477 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3478 OPEN_PREPARED = 32, /* Open already prepared command. */
3479 };
3481 static void
3482 open_view(struct view *prev, enum request request, enum open_flags flags)
3483 {
3484 bool split = !!(flags & OPEN_SPLIT);
3485 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3486 bool nomaximize = !!(flags & OPEN_REFRESH);
3487 struct view *view = VIEW(request);
3488 int nviews = displayed_views();
3489 struct view *base_view = display[0];
3491 if (view == prev && nviews == 1 && !reload) {
3492 report("Already in %s view", view->name);
3493 return;
3494 }
3496 if (view->git_dir && !opt_git_dir[0]) {
3497 report("The %s view is disabled in pager view", view->name);
3498 return;
3499 }
3501 if (split) {
3502 display[1] = view;
3503 current_view = 1;
3504 view->parent = prev;
3505 } else if (!nomaximize) {
3506 /* Maximize the current view. */
3507 memset(display, 0, sizeof(display));
3508 current_view = 0;
3509 display[current_view] = view;
3510 }
3512 /* No prev signals that this is the first loaded view. */
3513 if (prev && view != prev) {
3514 view->prev = prev;
3515 }
3517 /* Resize the view when switching between split- and full-screen,
3518 * or when switching between two different full-screen views. */
3519 if (nviews != displayed_views() ||
3520 (nviews == 1 && base_view != display[0]))
3521 resize_display();
3523 if (view->ops->open) {
3524 if (view->pipe)
3525 end_update(view, TRUE);
3526 if (!view->ops->open(view)) {
3527 report("Failed to load %s view", view->name);
3528 return;
3529 }
3530 restore_view_position(view);
3532 } else if ((reload || strcmp(view->vid, view->id)) &&
3533 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3534 report("Failed to load %s view", view->name);
3535 return;
3536 }
3538 if (split && prev->lineno - prev->offset >= prev->height) {
3539 /* Take the title line into account. */
3540 int lines = prev->lineno - prev->offset - prev->height + 1;
3542 /* Scroll the view that was split if the current line is
3543 * outside the new limited view. */
3544 do_scroll_view(prev, lines);
3545 }
3547 if (prev && view != prev && split && view_is_displayed(prev)) {
3548 /* "Blur" the previous view. */
3549 update_view_title(prev);
3550 }
3552 if (view->pipe && view->lines == 0) {
3553 /* Clear the old view and let the incremental updating refill
3554 * the screen. */
3555 werase(view->win);
3556 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3557 report("");
3558 } else if (view_is_displayed(view)) {
3559 redraw_view(view);
3560 report("");
3561 }
3562 }
3564 static void
3565 open_external_viewer(const char *argv[], const char *dir)
3566 {
3567 def_prog_mode(); /* save current tty modes */
3568 endwin(); /* restore original tty modes */
3569 io_run_fg(argv, dir);
3570 fprintf(stderr, "Press Enter to continue");
3571 getc(opt_tty);
3572 reset_prog_mode();
3573 redraw_display(TRUE);
3574 }
3576 static void
3577 open_mergetool(const char *file)
3578 {
3579 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3581 open_external_viewer(mergetool_argv, opt_cdup);
3582 }
3584 static void
3585 open_editor(const char *file)
3586 {
3587 const char *editor_argv[] = { "vi", file, NULL };
3588 const char *editor;
3590 editor = getenv("GIT_EDITOR");
3591 if (!editor && *opt_editor)
3592 editor = opt_editor;
3593 if (!editor)
3594 editor = getenv("VISUAL");
3595 if (!editor)
3596 editor = getenv("EDITOR");
3597 if (!editor)
3598 editor = "vi";
3600 editor_argv[0] = editor;
3601 open_external_viewer(editor_argv, opt_cdup);
3602 }
3604 static void
3605 open_run_request(enum request request)
3606 {
3607 struct run_request *req = get_run_request(request);
3608 const char **argv = NULL;
3610 if (!req) {
3611 report("Unknown run request");
3612 return;
3613 }
3615 if (format_argv(&argv, req->argv, TRUE))
3616 open_external_viewer(argv, NULL);
3617 if (argv)
3618 argv_free(argv);
3619 free(argv);
3620 }
3622 /*
3623 * User request switch noodle
3624 */
3626 static int
3627 view_driver(struct view *view, enum request request)
3628 {
3629 int i;
3631 if (request == REQ_NONE)
3632 return TRUE;
3634 if (request > REQ_NONE) {
3635 open_run_request(request);
3636 view_request(view, REQ_REFRESH);
3637 return TRUE;
3638 }
3640 request = view_request(view, request);
3641 if (request == REQ_NONE)
3642 return TRUE;
3644 switch (request) {
3645 case REQ_MOVE_UP:
3646 case REQ_MOVE_DOWN:
3647 case REQ_MOVE_PAGE_UP:
3648 case REQ_MOVE_PAGE_DOWN:
3649 case REQ_MOVE_FIRST_LINE:
3650 case REQ_MOVE_LAST_LINE:
3651 move_view(view, request);
3652 break;
3654 case REQ_SCROLL_LEFT:
3655 case REQ_SCROLL_RIGHT:
3656 case REQ_SCROLL_LINE_DOWN:
3657 case REQ_SCROLL_LINE_UP:
3658 case REQ_SCROLL_PAGE_DOWN:
3659 case REQ_SCROLL_PAGE_UP:
3660 scroll_view(view, request);
3661 break;
3663 case REQ_VIEW_BLAME:
3664 if (!opt_file[0]) {
3665 report("No file chosen, press %s to open tree view",
3666 get_key(view->keymap, REQ_VIEW_TREE));
3667 break;
3668 }
3669 open_view(view, request, OPEN_DEFAULT);
3670 break;
3672 case REQ_VIEW_BLOB:
3673 if (!ref_blob[0]) {
3674 report("No file chosen, press %s to open tree view",
3675 get_key(view->keymap, REQ_VIEW_TREE));
3676 break;
3677 }
3678 open_view(view, request, OPEN_DEFAULT);
3679 break;
3681 case REQ_VIEW_PAGER:
3682 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3683 report("No pager content, press %s to run command from prompt",
3684 get_key(view->keymap, REQ_PROMPT));
3685 break;
3686 }
3687 open_view(view, request, OPEN_DEFAULT);
3688 break;
3690 case REQ_VIEW_STAGE:
3691 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3692 report("No stage content, press %s to open the status view and choose file",
3693 get_key(view->keymap, REQ_VIEW_STATUS));
3694 break;
3695 }
3696 open_view(view, request, OPEN_DEFAULT);
3697 break;
3699 case REQ_VIEW_STATUS:
3700 if (opt_is_inside_work_tree == FALSE) {
3701 report("The status view requires a working tree");
3702 break;
3703 }
3704 open_view(view, request, OPEN_DEFAULT);
3705 break;
3707 case REQ_VIEW_MAIN:
3708 case REQ_VIEW_DIFF:
3709 case REQ_VIEW_LOG:
3710 case REQ_VIEW_TREE:
3711 case REQ_VIEW_HELP:
3712 case REQ_VIEW_BRANCH:
3713 open_view(view, request, OPEN_DEFAULT);
3714 break;
3716 case REQ_NEXT:
3717 case REQ_PREVIOUS:
3718 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3720 if (view->parent) {
3721 int line;
3723 view = view->parent;
3724 line = view->lineno;
3725 move_view(view, request);
3726 if (view_is_displayed(view))
3727 update_view_title(view);
3728 if (line != view->lineno)
3729 view_request(view, REQ_ENTER);
3730 } else {
3731 move_view(view, request);
3732 }
3733 break;
3735 case REQ_VIEW_NEXT:
3736 {
3737 int nviews = displayed_views();
3738 int next_view = (current_view + 1) % nviews;
3740 if (next_view == current_view) {
3741 report("Only one view is displayed");
3742 break;
3743 }
3745 current_view = next_view;
3746 /* Blur out the title of the previous view. */
3747 update_view_title(view);
3748 report("");
3749 break;
3750 }
3751 case REQ_REFRESH:
3752 report("Refreshing is not yet supported for the %s view", view->name);
3753 break;
3755 case REQ_MAXIMIZE:
3756 if (displayed_views() == 2)
3757 maximize_view(view);
3758 break;
3760 case REQ_OPTIONS:
3761 open_option_menu();
3762 break;
3764 case REQ_TOGGLE_LINENO:
3765 toggle_view_option(&opt_line_number, "line numbers");
3766 break;
3768 case REQ_TOGGLE_DATE:
3769 toggle_date();
3770 break;
3772 case REQ_TOGGLE_AUTHOR:
3773 toggle_author();
3774 break;
3776 case REQ_TOGGLE_REV_GRAPH:
3777 toggle_view_option(&opt_rev_graph, "revision graph display");
3778 break;
3780 case REQ_TOGGLE_REFS:
3781 toggle_view_option(&opt_show_refs, "reference display");
3782 break;
3784 case REQ_TOGGLE_SORT_FIELD:
3785 case REQ_TOGGLE_SORT_ORDER:
3786 report("Sorting is not yet supported for the %s view", view->name);
3787 break;
3789 case REQ_SEARCH:
3790 case REQ_SEARCH_BACK:
3791 search_view(view, request);
3792 break;
3794 case REQ_FIND_NEXT:
3795 case REQ_FIND_PREV:
3796 find_next(view, request);
3797 break;
3799 case REQ_STOP_LOADING:
3800 foreach_view(view, i) {
3801 if (view->pipe)
3802 report("Stopped loading the %s view", view->name),
3803 end_update(view, TRUE);
3804 }
3805 break;
3807 case REQ_SHOW_VERSION:
3808 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3809 return TRUE;
3811 case REQ_SCREEN_REDRAW:
3812 redraw_display(TRUE);
3813 break;
3815 case REQ_EDIT:
3816 report("Nothing to edit");
3817 break;
3819 case REQ_ENTER:
3820 report("Nothing to enter");
3821 break;
3823 case REQ_VIEW_CLOSE:
3824 /* XXX: Mark closed views by letting view->prev point to the
3825 * view itself. Parents to closed view should never be
3826 * followed. */
3827 if (view->prev && view->prev != view) {
3828 maximize_view(view->prev);
3829 view->prev = view;
3830 break;
3831 }
3832 /* Fall-through */
3833 case REQ_QUIT:
3834 return FALSE;
3836 default:
3837 report("Unknown key, press %s for help",
3838 get_key(view->keymap, REQ_VIEW_HELP));
3839 return TRUE;
3840 }
3842 return TRUE;
3843 }
3846 /*
3847 * View backend utilities
3848 */
3850 enum sort_field {
3851 ORDERBY_NAME,
3852 ORDERBY_DATE,
3853 ORDERBY_AUTHOR,
3854 };
3856 struct sort_state {
3857 const enum sort_field *fields;
3858 size_t size, current;
3859 bool reverse;
3860 };
3862 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3863 #define get_sort_field(state) ((state).fields[(state).current])
3864 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3866 static void
3867 sort_view(struct view *view, enum request request, struct sort_state *state,
3868 int (*compare)(const void *, const void *))
3869 {
3870 switch (request) {
3871 case REQ_TOGGLE_SORT_FIELD:
3872 state->current = (state->current + 1) % state->size;
3873 break;
3875 case REQ_TOGGLE_SORT_ORDER:
3876 state->reverse = !state->reverse;
3877 break;
3878 default:
3879 die("Not a sort request");
3880 }
3882 qsort(view->line, view->lines, sizeof(*view->line), compare);
3883 redraw_view(view);
3884 }
3886 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3888 /* Small author cache to reduce memory consumption. It uses binary
3889 * search to lookup or find place to position new entries. No entries
3890 * are ever freed. */
3891 static const char *
3892 get_author(const char *name)
3893 {
3894 static const char **authors;
3895 static size_t authors_size;
3896 int from = 0, to = authors_size - 1;
3898 while (from <= to) {
3899 size_t pos = (to + from) / 2;
3900 int cmp = strcmp(name, authors[pos]);
3902 if (!cmp)
3903 return authors[pos];
3905 if (cmp < 0)
3906 to = pos - 1;
3907 else
3908 from = pos + 1;
3909 }
3911 if (!realloc_authors(&authors, authors_size, 1))
3912 return NULL;
3913 name = strdup(name);
3914 if (!name)
3915 return NULL;
3917 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3918 authors[from] = name;
3919 authors_size++;
3921 return name;
3922 }
3924 static void
3925 parse_timesec(struct time *time, const char *sec)
3926 {
3927 time->sec = (time_t) atol(sec);
3928 }
3930 static void
3931 parse_timezone(struct time *time, const char *zone)
3932 {
3933 long tz;
3935 tz = ('0' - zone[1]) * 60 * 60 * 10;
3936 tz += ('0' - zone[2]) * 60 * 60;
3937 tz += ('0' - zone[3]) * 60 * 10;
3938 tz += ('0' - zone[4]) * 60;
3940 if (zone[0] == '-')
3941 tz = -tz;
3943 time->tz = tz;
3944 time->sec -= tz;
3945 }
3947 /* Parse author lines where the name may be empty:
3948 * author <email@address.tld> 1138474660 +0100
3949 */
3950 static void
3951 parse_author_line(char *ident, const char **author, struct time *time)
3952 {
3953 char *nameend = strchr(ident, '<');
3954 char *emailend = strchr(ident, '>');
3956 if (nameend && emailend)
3957 *nameend = *emailend = 0;
3958 ident = chomp_string(ident);
3959 if (!*ident) {
3960 if (nameend)
3961 ident = chomp_string(nameend + 1);
3962 if (!*ident)
3963 ident = "Unknown";
3964 }
3966 *author = get_author(ident);
3968 /* Parse epoch and timezone */
3969 if (emailend && emailend[1] == ' ') {
3970 char *secs = emailend + 2;
3971 char *zone = strchr(secs, ' ');
3973 parse_timesec(time, secs);
3975 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3976 parse_timezone(time, zone + 1);
3977 }
3978 }
3980 static bool
3981 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3982 {
3983 char rev[SIZEOF_REV];
3984 const char *revlist_argv[] = {
3985 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3986 };
3987 struct menu_item *items;
3988 char text[SIZEOF_STR];
3989 bool ok = TRUE;
3990 int i;
3992 items = calloc(*parents + 1, sizeof(*items));
3993 if (!items)
3994 return FALSE;
3996 for (i = 0; i < *parents; i++) {
3997 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3998 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3999 !(items[i].text = strdup(text))) {
4000 ok = FALSE;
4001 break;
4002 }
4003 }
4005 if (ok) {
4006 *parents = 0;
4007 ok = prompt_menu("Select parent", items, parents);
4008 }
4009 for (i = 0; items[i].text; i++)
4010 free((char *) items[i].text);
4011 free(items);
4012 return ok;
4013 }
4015 static bool
4016 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4017 {
4018 char buf[SIZEOF_STR * 4];
4019 const char *revlist_argv[] = {
4020 "git", "log", "--no-color", "-1",
4021 "--pretty=format:%P", id, "--", path, NULL
4022 };
4023 int parents;
4025 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4026 (parents = strlen(buf) / 40) < 0) {
4027 report("Failed to get parent information");
4028 return FALSE;
4030 } else if (parents == 0) {
4031 if (path)
4032 report("Path '%s' does not exist in the parent", path);
4033 else
4034 report("The selected commit has no parents");
4035 return FALSE;
4036 }
4038 if (parents == 1)
4039 parents = 0;
4040 else if (!open_commit_parent_menu(buf, &parents))
4041 return FALSE;
4043 string_copy_rev(rev, &buf[41 * parents]);
4044 return TRUE;
4045 }
4047 /*
4048 * Pager backend
4049 */
4051 static bool
4052 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4053 {
4054 char text[SIZEOF_STR];
4056 if (opt_line_number && draw_lineno(view, lineno))
4057 return TRUE;
4059 string_expand(text, sizeof(text), line->data, opt_tab_size);
4060 draw_text(view, line->type, text, TRUE);
4061 return TRUE;
4062 }
4064 static bool
4065 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4066 {
4067 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4068 char ref[SIZEOF_STR];
4070 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4071 return TRUE;
4073 /* This is the only fatal call, since it can "corrupt" the buffer. */
4074 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4075 return FALSE;
4077 return TRUE;
4078 }
4080 static void
4081 add_pager_refs(struct view *view, struct line *line)
4082 {
4083 char buf[SIZEOF_STR];
4084 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4085 struct ref_list *list;
4086 size_t bufpos = 0, i;
4087 const char *sep = "Refs: ";
4088 bool is_tag = FALSE;
4090 assert(line->type == LINE_COMMIT);
4092 list = get_ref_list(commit_id);
4093 if (!list) {
4094 if (view->type == VIEW_DIFF)
4095 goto try_add_describe_ref;
4096 return;
4097 }
4099 for (i = 0; i < list->size; i++) {
4100 struct ref *ref = list->refs[i];
4101 const char *fmt = ref->tag ? "%s[%s]" :
4102 ref->remote ? "%s<%s>" : "%s%s";
4104 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4105 return;
4106 sep = ", ";
4107 if (ref->tag)
4108 is_tag = TRUE;
4109 }
4111 if (!is_tag && view->type == VIEW_DIFF) {
4112 try_add_describe_ref:
4113 /* Add <tag>-g<commit_id> "fake" reference. */
4114 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4115 return;
4116 }
4118 if (bufpos == 0)
4119 return;
4121 add_line_text(view, buf, LINE_PP_REFS);
4122 }
4124 static bool
4125 pager_read(struct view *view, char *data)
4126 {
4127 struct line *line;
4129 if (!data)
4130 return TRUE;
4132 line = add_line_text(view, data, get_line_type(data));
4133 if (!line)
4134 return FALSE;
4136 if (line->type == LINE_COMMIT &&
4137 (view->type == VIEW_DIFF ||
4138 view->type == VIEW_LOG))
4139 add_pager_refs(view, line);
4141 return TRUE;
4142 }
4144 static enum request
4145 pager_request(struct view *view, enum request request, struct line *line)
4146 {
4147 int split = 0;
4149 if (request != REQ_ENTER)
4150 return request;
4152 if (line->type == LINE_COMMIT &&
4153 (view->type == VIEW_LOG ||
4154 view->type == VIEW_PAGER)) {
4155 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4156 split = 1;
4157 }
4159 /* Always scroll the view even if it was split. That way
4160 * you can use Enter to scroll through the log view and
4161 * split open each commit diff. */
4162 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4164 /* FIXME: A minor workaround. Scrolling the view will call report("")
4165 * but if we are scrolling a non-current view this won't properly
4166 * update the view title. */
4167 if (split)
4168 update_view_title(view);
4170 return REQ_NONE;
4171 }
4173 static bool
4174 pager_grep(struct view *view, struct line *line)
4175 {
4176 const char *text[] = { line->data, NULL };
4178 return grep_text(view, text);
4179 }
4181 static void
4182 pager_select(struct view *view, struct line *line)
4183 {
4184 if (line->type == LINE_COMMIT) {
4185 char *text = (char *)line->data + STRING_SIZE("commit ");
4187 if (view->type != VIEW_PAGER)
4188 string_copy_rev(view->ref, text);
4189 string_copy_rev(ref_commit, text);
4190 }
4191 }
4193 static struct view_ops pager_ops = {
4194 "line",
4195 NULL,
4196 NULL,
4197 pager_read,
4198 pager_draw,
4199 pager_request,
4200 pager_grep,
4201 pager_select,
4202 };
4204 static const char *log_argv[SIZEOF_ARG] = {
4205 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4206 };
4208 static enum request
4209 log_request(struct view *view, enum request request, struct line *line)
4210 {
4211 switch (request) {
4212 case REQ_REFRESH:
4213 load_refs();
4214 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4215 return REQ_NONE;
4216 default:
4217 return pager_request(view, request, line);
4218 }
4219 }
4221 static struct view_ops log_ops = {
4222 "line",
4223 log_argv,
4224 NULL,
4225 pager_read,
4226 pager_draw,
4227 log_request,
4228 pager_grep,
4229 pager_select,
4230 };
4232 static const char *diff_argv[SIZEOF_ARG] = {
4233 "git", "show", "--pretty=fuller", "--no-color", "--root",
4234 "--patch-with-stat", "--find-copies-harder", "-C",
4235 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4236 };
4238 static struct view_ops diff_ops = {
4239 "line",
4240 diff_argv,
4241 NULL,
4242 pager_read,
4243 pager_draw,
4244 pager_request,
4245 pager_grep,
4246 pager_select,
4247 };
4249 /*
4250 * Help backend
4251 */
4253 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4255 static bool
4256 help_open_keymap_title(struct view *view, enum keymap keymap)
4257 {
4258 struct line *line;
4260 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4261 help_keymap_hidden[keymap] ? '+' : '-',
4262 enum_name(keymap_table[keymap]));
4263 if (line)
4264 line->other = keymap;
4266 return help_keymap_hidden[keymap];
4267 }
4269 static void
4270 help_open_keymap(struct view *view, enum keymap keymap)
4271 {
4272 const char *group = NULL;
4273 char buf[SIZEOF_STR];
4274 size_t bufpos;
4275 bool add_title = TRUE;
4276 int i;
4278 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4279 const char *key = NULL;
4281 if (req_info[i].request == REQ_NONE)
4282 continue;
4284 if (!req_info[i].request) {
4285 group = req_info[i].help;
4286 continue;
4287 }
4289 key = get_keys(keymap, req_info[i].request, TRUE);
4290 if (!key || !*key)
4291 continue;
4293 if (add_title && help_open_keymap_title(view, keymap))
4294 return;
4295 add_title = FALSE;
4297 if (group) {
4298 add_line_text(view, group, LINE_HELP_GROUP);
4299 group = NULL;
4300 }
4302 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4303 enum_name(req_info[i]), req_info[i].help);
4304 }
4306 group = "External commands:";
4308 for (i = 0; i < run_requests; i++) {
4309 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4310 const char *key;
4311 int argc;
4313 if (!req || req->keymap != keymap)
4314 continue;
4316 key = get_key_name(req->key);
4317 if (!*key)
4318 key = "(no key defined)";
4320 if (add_title && help_open_keymap_title(view, keymap))
4321 return;
4322 if (group) {
4323 add_line_text(view, group, LINE_HELP_GROUP);
4324 group = NULL;
4325 }
4327 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4328 if (!string_format_from(buf, &bufpos, "%s%s",
4329 argc ? " " : "", req->argv[argc]))
4330 return;
4332 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4333 }
4334 }
4336 static bool
4337 help_open(struct view *view)
4338 {
4339 enum keymap keymap;
4341 reset_view(view);
4342 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4343 add_line_text(view, "", LINE_DEFAULT);
4345 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4346 help_open_keymap(view, keymap);
4348 return TRUE;
4349 }
4351 static enum request
4352 help_request(struct view *view, enum request request, struct line *line)
4353 {
4354 switch (request) {
4355 case REQ_ENTER:
4356 if (line->type == LINE_HELP_KEYMAP) {
4357 help_keymap_hidden[line->other] =
4358 !help_keymap_hidden[line->other];
4359 view->p_restore = TRUE;
4360 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4361 }
4363 return REQ_NONE;
4364 default:
4365 return pager_request(view, request, line);
4366 }
4367 }
4369 static struct view_ops help_ops = {
4370 "line",
4371 NULL,
4372 help_open,
4373 NULL,
4374 pager_draw,
4375 help_request,
4376 pager_grep,
4377 pager_select,
4378 };
4381 /*
4382 * Tree backend
4383 */
4385 struct tree_stack_entry {
4386 struct tree_stack_entry *prev; /* Entry below this in the stack */
4387 unsigned long lineno; /* Line number to restore */
4388 char *name; /* Position of name in opt_path */
4389 };
4391 /* The top of the path stack. */
4392 static struct tree_stack_entry *tree_stack = NULL;
4393 unsigned long tree_lineno = 0;
4395 static void
4396 pop_tree_stack_entry(void)
4397 {
4398 struct tree_stack_entry *entry = tree_stack;
4400 tree_lineno = entry->lineno;
4401 entry->name[0] = 0;
4402 tree_stack = entry->prev;
4403 free(entry);
4404 }
4406 static void
4407 push_tree_stack_entry(const char *name, unsigned long lineno)
4408 {
4409 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4410 size_t pathlen = strlen(opt_path);
4412 if (!entry)
4413 return;
4415 entry->prev = tree_stack;
4416 entry->name = opt_path + pathlen;
4417 tree_stack = entry;
4419 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4420 pop_tree_stack_entry();
4421 return;
4422 }
4424 /* Move the current line to the first tree entry. */
4425 tree_lineno = 1;
4426 entry->lineno = lineno;
4427 }
4429 /* Parse output from git-ls-tree(1):
4430 *
4431 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4432 */
4434 #define SIZEOF_TREE_ATTR \
4435 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4437 #define SIZEOF_TREE_MODE \
4438 STRING_SIZE("100644 ")
4440 #define TREE_ID_OFFSET \
4441 STRING_SIZE("100644 blob ")
4443 struct tree_entry {
4444 char id[SIZEOF_REV];
4445 mode_t mode;
4446 struct time time; /* Date from the author ident. */
4447 const char *author; /* Author of the commit. */
4448 char name[1];
4449 };
4451 static const char *
4452 tree_path(const struct line *line)
4453 {
4454 return ((struct tree_entry *) line->data)->name;
4455 }
4457 static int
4458 tree_compare_entry(const struct line *line1, const struct line *line2)
4459 {
4460 if (line1->type != line2->type)
4461 return line1->type == LINE_TREE_DIR ? -1 : 1;
4462 return strcmp(tree_path(line1), tree_path(line2));
4463 }
4465 static const enum sort_field tree_sort_fields[] = {
4466 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4467 };
4468 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4470 static int
4471 tree_compare(const void *l1, const void *l2)
4472 {
4473 const struct line *line1 = (const struct line *) l1;
4474 const struct line *line2 = (const struct line *) l2;
4475 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4476 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4478 if (line1->type == LINE_TREE_HEAD)
4479 return -1;
4480 if (line2->type == LINE_TREE_HEAD)
4481 return 1;
4483 switch (get_sort_field(tree_sort_state)) {
4484 case ORDERBY_DATE:
4485 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4487 case ORDERBY_AUTHOR:
4488 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4490 case ORDERBY_NAME:
4491 default:
4492 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4493 }
4494 }
4497 static struct line *
4498 tree_entry(struct view *view, enum line_type type, const char *path,
4499 const char *mode, const char *id)
4500 {
4501 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4502 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4504 if (!entry || !line) {
4505 free(entry);
4506 return NULL;
4507 }
4509 strncpy(entry->name, path, strlen(path));
4510 if (mode)
4511 entry->mode = strtoul(mode, NULL, 8);
4512 if (id)
4513 string_copy_rev(entry->id, id);
4515 return line;
4516 }
4518 static bool
4519 tree_read_date(struct view *view, char *text, bool *read_date)
4520 {
4521 static const char *author_name;
4522 static struct time author_time;
4524 if (!text && *read_date) {
4525 *read_date = FALSE;
4526 return TRUE;
4528 } else if (!text) {
4529 char *path = *opt_path ? opt_path : ".";
4530 /* Find next entry to process */
4531 const char *log_file[] = {
4532 "git", "log", "--no-color", "--pretty=raw",
4533 "--cc", "--raw", view->id, "--", path, NULL
4534 };
4536 if (!view->lines) {
4537 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4538 report("Tree is empty");
4539 return TRUE;
4540 }
4542 if (!start_update(view, log_file, opt_cdup)) {
4543 report("Failed to load tree data");
4544 return TRUE;
4545 }
4547 *read_date = TRUE;
4548 return FALSE;
4550 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4551 parse_author_line(text + STRING_SIZE("author "),
4552 &author_name, &author_time);
4554 } else if (*text == ':') {
4555 char *pos;
4556 size_t annotated = 1;
4557 size_t i;
4559 pos = strchr(text, '\t');
4560 if (!pos)
4561 return TRUE;
4562 text = pos + 1;
4563 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4564 text += strlen(opt_path);
4565 pos = strchr(text, '/');
4566 if (pos)
4567 *pos = 0;
4569 for (i = 1; i < view->lines; i++) {
4570 struct line *line = &view->line[i];
4571 struct tree_entry *entry = line->data;
4573 annotated += !!entry->author;
4574 if (entry->author || strcmp(entry->name, text))
4575 continue;
4577 entry->author = author_name;
4578 entry->time = author_time;
4579 line->dirty = 1;
4580 break;
4581 }
4583 if (annotated == view->lines)
4584 io_kill(view->pipe);
4585 }
4586 return TRUE;
4587 }
4589 static bool
4590 tree_read(struct view *view, char *text)
4591 {
4592 static bool read_date = FALSE;
4593 struct tree_entry *data;
4594 struct line *entry, *line;
4595 enum line_type type;
4596 size_t textlen = text ? strlen(text) : 0;
4597 char *path = text + SIZEOF_TREE_ATTR;
4599 if (read_date || !text)
4600 return tree_read_date(view, text, &read_date);
4602 if (textlen <= SIZEOF_TREE_ATTR)
4603 return FALSE;
4604 if (view->lines == 0 &&
4605 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4606 return FALSE;
4608 /* Strip the path part ... */
4609 if (*opt_path) {
4610 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4611 size_t striplen = strlen(opt_path);
4613 if (pathlen > striplen)
4614 memmove(path, path + striplen,
4615 pathlen - striplen + 1);
4617 /* Insert "link" to parent directory. */
4618 if (view->lines == 1 &&
4619 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4620 return FALSE;
4621 }
4623 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4624 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4625 if (!entry)
4626 return FALSE;
4627 data = entry->data;
4629 /* Skip "Directory ..." and ".." line. */
4630 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4631 if (tree_compare_entry(line, entry) <= 0)
4632 continue;
4634 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4636 line->data = data;
4637 line->type = type;
4638 for (; line <= entry; line++)
4639 line->dirty = line->cleareol = 1;
4640 return TRUE;
4641 }
4643 if (tree_lineno > view->lineno) {
4644 view->lineno = tree_lineno;
4645 tree_lineno = 0;
4646 }
4648 return TRUE;
4649 }
4651 static bool
4652 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4653 {
4654 struct tree_entry *entry = line->data;
4656 if (line->type == LINE_TREE_HEAD) {
4657 if (draw_text(view, line->type, "Directory path /", TRUE))
4658 return TRUE;
4659 } else {
4660 if (draw_mode(view, entry->mode))
4661 return TRUE;
4663 if (opt_author && draw_author(view, entry->author))
4664 return TRUE;
4666 if (opt_date && draw_date(view, &entry->time))
4667 return TRUE;
4668 }
4669 if (draw_text(view, line->type, entry->name, TRUE))
4670 return TRUE;
4671 return TRUE;
4672 }
4674 static void
4675 open_blob_editor(const char *id)
4676 {
4677 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4678 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4679 int fd = mkstemp(file);
4681 if (fd == -1)
4682 report("Failed to create temporary file");
4683 else if (!io_run_append(blob_argv, fd))
4684 report("Failed to save blob data to file");
4685 else
4686 open_editor(file);
4687 if (fd != -1)
4688 unlink(file);
4689 }
4691 static enum request
4692 tree_request(struct view *view, enum request request, struct line *line)
4693 {
4694 enum open_flags flags;
4695 struct tree_entry *entry = line->data;
4697 switch (request) {
4698 case REQ_VIEW_BLAME:
4699 if (line->type != LINE_TREE_FILE) {
4700 report("Blame only supported for files");
4701 return REQ_NONE;
4702 }
4704 string_copy(opt_ref, view->vid);
4705 return request;
4707 case REQ_EDIT:
4708 if (line->type != LINE_TREE_FILE) {
4709 report("Edit only supported for files");
4710 } else if (!is_head_commit(view->vid)) {
4711 open_blob_editor(entry->id);
4712 } else {
4713 open_editor(opt_file);
4714 }
4715 return REQ_NONE;
4717 case REQ_TOGGLE_SORT_FIELD:
4718 case REQ_TOGGLE_SORT_ORDER:
4719 sort_view(view, request, &tree_sort_state, tree_compare);
4720 return REQ_NONE;
4722 case REQ_PARENT:
4723 if (!*opt_path) {
4724 /* quit view if at top of tree */
4725 return REQ_VIEW_CLOSE;
4726 }
4727 /* fake 'cd ..' */
4728 line = &view->line[1];
4729 break;
4731 case REQ_ENTER:
4732 break;
4734 default:
4735 return request;
4736 }
4738 /* Cleanup the stack if the tree view is at a different tree. */
4739 while (!*opt_path && tree_stack)
4740 pop_tree_stack_entry();
4742 switch (line->type) {
4743 case LINE_TREE_DIR:
4744 /* Depending on whether it is a subdirectory or parent link
4745 * mangle the path buffer. */
4746 if (line == &view->line[1] && *opt_path) {
4747 pop_tree_stack_entry();
4749 } else {
4750 const char *basename = tree_path(line);
4752 push_tree_stack_entry(basename, view->lineno);
4753 }
4755 /* Trees and subtrees share the same ID, so they are not not
4756 * unique like blobs. */
4757 flags = OPEN_RELOAD;
4758 request = REQ_VIEW_TREE;
4759 break;
4761 case LINE_TREE_FILE:
4762 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4763 request = REQ_VIEW_BLOB;
4764 break;
4766 default:
4767 return REQ_NONE;
4768 }
4770 open_view(view, request, flags);
4771 if (request == REQ_VIEW_TREE)
4772 view->lineno = tree_lineno;
4774 return REQ_NONE;
4775 }
4777 static bool
4778 tree_grep(struct view *view, struct line *line)
4779 {
4780 struct tree_entry *entry = line->data;
4781 const char *text[] = {
4782 entry->name,
4783 opt_author ? entry->author : "",
4784 mkdate(&entry->time, opt_date),
4785 NULL
4786 };
4788 return grep_text(view, text);
4789 }
4791 static void
4792 tree_select(struct view *view, struct line *line)
4793 {
4794 struct tree_entry *entry = line->data;
4796 if (line->type == LINE_TREE_FILE) {
4797 string_copy_rev(ref_blob, entry->id);
4798 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4800 } else if (line->type != LINE_TREE_DIR) {
4801 return;
4802 }
4804 string_copy_rev(view->ref, entry->id);
4805 }
4807 static bool
4808 tree_prepare(struct view *view)
4809 {
4810 if (view->lines == 0 && opt_prefix[0]) {
4811 char *pos = opt_prefix;
4813 while (pos && *pos) {
4814 char *end = strchr(pos, '/');
4816 if (end)
4817 *end = 0;
4818 push_tree_stack_entry(pos, 0);
4819 pos = end;
4820 if (end) {
4821 *end = '/';
4822 pos++;
4823 }
4824 }
4826 } else if (strcmp(view->vid, view->id)) {
4827 opt_path[0] = 0;
4828 }
4830 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4831 }
4833 static const char *tree_argv[SIZEOF_ARG] = {
4834 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4835 };
4837 static struct view_ops tree_ops = {
4838 "file",
4839 tree_argv,
4840 NULL,
4841 tree_read,
4842 tree_draw,
4843 tree_request,
4844 tree_grep,
4845 tree_select,
4846 tree_prepare,
4847 };
4849 static bool
4850 blob_read(struct view *view, char *line)
4851 {
4852 if (!line)
4853 return TRUE;
4854 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4855 }
4857 static enum request
4858 blob_request(struct view *view, enum request request, struct line *line)
4859 {
4860 switch (request) {
4861 case REQ_EDIT:
4862 open_blob_editor(view->vid);
4863 return REQ_NONE;
4864 default:
4865 return pager_request(view, request, line);
4866 }
4867 }
4869 static const char *blob_argv[SIZEOF_ARG] = {
4870 "git", "cat-file", "blob", "%(blob)", NULL
4871 };
4873 static struct view_ops blob_ops = {
4874 "line",
4875 blob_argv,
4876 NULL,
4877 blob_read,
4878 pager_draw,
4879 blob_request,
4880 pager_grep,
4881 pager_select,
4882 };
4884 /*
4885 * Blame backend
4886 *
4887 * Loading the blame view is a two phase job:
4888 *
4889 * 1. File content is read either using opt_file from the
4890 * filesystem or using git-cat-file.
4891 * 2. Then blame information is incrementally added by
4892 * reading output from git-blame.
4893 */
4895 struct blame_commit {
4896 char id[SIZEOF_REV]; /* SHA1 ID. */
4897 char title[128]; /* First line of the commit message. */
4898 const char *author; /* Author of the commit. */
4899 struct time time; /* Date from the author ident. */
4900 char filename[128]; /* Name of file. */
4901 bool has_previous; /* Was a "previous" line detected. */
4902 };
4904 struct blame {
4905 struct blame_commit *commit;
4906 unsigned long lineno;
4907 char text[1];
4908 };
4910 static bool
4911 blame_open(struct view *view)
4912 {
4913 char path[SIZEOF_STR];
4915 if (!view->prev && *opt_prefix) {
4916 string_copy(path, opt_file);
4917 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4918 return FALSE;
4919 }
4921 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4922 const char *blame_cat_file_argv[] = {
4923 "git", "cat-file", "blob", path, NULL
4924 };
4926 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4927 !start_update(view, blame_cat_file_argv, opt_cdup))
4928 return FALSE;
4929 }
4931 setup_update(view, opt_file);
4932 string_format(view->ref, "%s ...", opt_file);
4934 return TRUE;
4935 }
4937 static struct blame_commit *
4938 get_blame_commit(struct view *view, const char *id)
4939 {
4940 size_t i;
4942 for (i = 0; i < view->lines; i++) {
4943 struct blame *blame = view->line[i].data;
4945 if (!blame->commit)
4946 continue;
4948 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4949 return blame->commit;
4950 }
4952 {
4953 struct blame_commit *commit = calloc(1, sizeof(*commit));
4955 if (commit)
4956 string_ncopy(commit->id, id, SIZEOF_REV);
4957 return commit;
4958 }
4959 }
4961 static bool
4962 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4963 {
4964 const char *pos = *posref;
4966 *posref = NULL;
4967 pos = strchr(pos + 1, ' ');
4968 if (!pos || !isdigit(pos[1]))
4969 return FALSE;
4970 *number = atoi(pos + 1);
4971 if (*number < min || *number > max)
4972 return FALSE;
4974 *posref = pos;
4975 return TRUE;
4976 }
4978 static struct blame_commit *
4979 parse_blame_commit(struct view *view, const char *text, int *blamed)
4980 {
4981 struct blame_commit *commit;
4982 struct blame *blame;
4983 const char *pos = text + SIZEOF_REV - 2;
4984 size_t orig_lineno = 0;
4985 size_t lineno;
4986 size_t group;
4988 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4989 return NULL;
4991 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4992 !parse_number(&pos, &lineno, 1, view->lines) ||
4993 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4994 return NULL;
4996 commit = get_blame_commit(view, text);
4997 if (!commit)
4998 return NULL;
5000 *blamed += group;
5001 while (group--) {
5002 struct line *line = &view->line[lineno + group - 1];
5004 blame = line->data;
5005 blame->commit = commit;
5006 blame->lineno = orig_lineno + group - 1;
5007 line->dirty = 1;
5008 }
5010 return commit;
5011 }
5013 static bool
5014 blame_read_file(struct view *view, const char *line, bool *read_file)
5015 {
5016 if (!line) {
5017 const char *blame_argv[] = {
5018 "git", "blame", "--incremental",
5019 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5020 };
5022 if (view->lines == 0 && !view->prev)
5023 die("No blame exist for %s", view->vid);
5025 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5026 report("Failed to load blame data");
5027 return TRUE;
5028 }
5030 *read_file = FALSE;
5031 return FALSE;
5033 } else {
5034 size_t linelen = strlen(line);
5035 struct blame *blame = malloc(sizeof(*blame) + linelen);
5037 if (!blame)
5038 return FALSE;
5040 blame->commit = NULL;
5041 strncpy(blame->text, line, linelen);
5042 blame->text[linelen] = 0;
5043 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5044 }
5045 }
5047 static bool
5048 match_blame_header(const char *name, char **line)
5049 {
5050 size_t namelen = strlen(name);
5051 bool matched = !strncmp(name, *line, namelen);
5053 if (matched)
5054 *line += namelen;
5056 return matched;
5057 }
5059 static bool
5060 blame_read(struct view *view, char *line)
5061 {
5062 static struct blame_commit *commit = NULL;
5063 static int blamed = 0;
5064 static bool read_file = TRUE;
5066 if (read_file)
5067 return blame_read_file(view, line, &read_file);
5069 if (!line) {
5070 /* Reset all! */
5071 commit = NULL;
5072 blamed = 0;
5073 read_file = TRUE;
5074 string_format(view->ref, "%s", view->vid);
5075 if (view_is_displayed(view)) {
5076 update_view_title(view);
5077 redraw_view_from(view, 0);
5078 }
5079 return TRUE;
5080 }
5082 if (!commit) {
5083 commit = parse_blame_commit(view, line, &blamed);
5084 string_format(view->ref, "%s %2d%%", view->vid,
5085 view->lines ? blamed * 100 / view->lines : 0);
5087 } else if (match_blame_header("author ", &line)) {
5088 commit->author = get_author(line);
5090 } else if (match_blame_header("author-time ", &line)) {
5091 parse_timesec(&commit->time, line);
5093 } else if (match_blame_header("author-tz ", &line)) {
5094 parse_timezone(&commit->time, line);
5096 } else if (match_blame_header("summary ", &line)) {
5097 string_ncopy(commit->title, line, strlen(line));
5099 } else if (match_blame_header("previous ", &line)) {
5100 commit->has_previous = TRUE;
5102 } else if (match_blame_header("filename ", &line)) {
5103 string_ncopy(commit->filename, line, strlen(line));
5104 commit = NULL;
5105 }
5107 return TRUE;
5108 }
5110 static bool
5111 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5112 {
5113 struct blame *blame = line->data;
5114 struct time *time = NULL;
5115 const char *id = NULL, *author = NULL;
5116 char text[SIZEOF_STR];
5118 if (blame->commit && *blame->commit->filename) {
5119 id = blame->commit->id;
5120 author = blame->commit->author;
5121 time = &blame->commit->time;
5122 }
5124 if (opt_date && draw_date(view, time))
5125 return TRUE;
5127 if (opt_author && draw_author(view, author))
5128 return TRUE;
5130 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5131 return TRUE;
5133 if (draw_lineno(view, lineno))
5134 return TRUE;
5136 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5137 draw_text(view, LINE_DEFAULT, text, TRUE);
5138 return TRUE;
5139 }
5141 static bool
5142 check_blame_commit(struct blame *blame, bool check_null_id)
5143 {
5144 if (!blame->commit)
5145 report("Commit data not loaded yet");
5146 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5147 report("No commit exist for the selected line");
5148 else
5149 return TRUE;
5150 return FALSE;
5151 }
5153 static void
5154 setup_blame_parent_line(struct view *view, struct blame *blame)
5155 {
5156 const char *diff_tree_argv[] = {
5157 "git", "diff-tree", "-U0", blame->commit->id,
5158 "--", blame->commit->filename, NULL
5159 };
5160 struct io io;
5161 int parent_lineno = -1;
5162 int blamed_lineno = -1;
5163 char *line;
5165 if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5166 return;
5168 while ((line = io_get(&io, '\n', TRUE))) {
5169 if (*line == '@') {
5170 char *pos = strchr(line, '+');
5172 parent_lineno = atoi(line + 4);
5173 if (pos)
5174 blamed_lineno = atoi(pos + 1);
5176 } else if (*line == '+' && parent_lineno != -1) {
5177 if (blame->lineno == blamed_lineno - 1 &&
5178 !strcmp(blame->text, line + 1)) {
5179 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5180 break;
5181 }
5182 blamed_lineno++;
5183 }
5184 }
5186 io_done(&io);
5187 }
5189 static enum request
5190 blame_request(struct view *view, enum request request, struct line *line)
5191 {
5192 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5193 struct blame *blame = line->data;
5195 switch (request) {
5196 case REQ_VIEW_BLAME:
5197 if (check_blame_commit(blame, TRUE)) {
5198 string_copy(opt_ref, blame->commit->id);
5199 string_copy(opt_file, blame->commit->filename);
5200 if (blame->lineno)
5201 view->lineno = blame->lineno;
5202 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5203 }
5204 break;
5206 case REQ_PARENT:
5207 if (check_blame_commit(blame, TRUE) &&
5208 select_commit_parent(blame->commit->id, opt_ref,
5209 blame->commit->filename)) {
5210 string_copy(opt_file, blame->commit->filename);
5211 setup_blame_parent_line(view, blame);
5212 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5213 }
5214 break;
5216 case REQ_ENTER:
5217 if (!check_blame_commit(blame, FALSE))
5218 break;
5220 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5221 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5222 break;
5224 if (!strcmp(blame->commit->id, NULL_ID)) {
5225 struct view *diff = VIEW(REQ_VIEW_DIFF);
5226 const char *diff_index_argv[] = {
5227 "git", "diff-index", "--root", "--patch-with-stat",
5228 "-C", "-M", "HEAD", "--", view->vid, NULL
5229 };
5231 if (!blame->commit->has_previous) {
5232 diff_index_argv[1] = "diff";
5233 diff_index_argv[2] = "--no-color";
5234 diff_index_argv[6] = "--";
5235 diff_index_argv[7] = "/dev/null";
5236 }
5238 if (!prepare_update(diff, diff_index_argv, NULL)) {
5239 report("Failed to allocate diff command");
5240 break;
5241 }
5242 flags |= OPEN_PREPARED;
5243 }
5245 open_view(view, REQ_VIEW_DIFF, flags);
5246 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5247 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5248 break;
5250 default:
5251 return request;
5252 }
5254 return REQ_NONE;
5255 }
5257 static bool
5258 blame_grep(struct view *view, struct line *line)
5259 {
5260 struct blame *blame = line->data;
5261 struct blame_commit *commit = blame->commit;
5262 const char *text[] = {
5263 blame->text,
5264 commit ? commit->title : "",
5265 commit ? commit->id : "",
5266 commit && opt_author ? commit->author : "",
5267 commit ? mkdate(&commit->time, opt_date) : "",
5268 NULL
5269 };
5271 return grep_text(view, text);
5272 }
5274 static void
5275 blame_select(struct view *view, struct line *line)
5276 {
5277 struct blame *blame = line->data;
5278 struct blame_commit *commit = blame->commit;
5280 if (!commit)
5281 return;
5283 if (!strcmp(commit->id, NULL_ID))
5284 string_ncopy(ref_commit, "HEAD", 4);
5285 else
5286 string_copy_rev(ref_commit, commit->id);
5287 }
5289 static struct view_ops blame_ops = {
5290 "line",
5291 NULL,
5292 blame_open,
5293 blame_read,
5294 blame_draw,
5295 blame_request,
5296 blame_grep,
5297 blame_select,
5298 };
5300 /*
5301 * Branch backend
5302 */
5304 struct branch {
5305 const char *author; /* Author of the last commit. */
5306 struct time time; /* Date of the last activity. */
5307 const struct ref *ref; /* Name and commit ID information. */
5308 };
5310 static const struct ref branch_all;
5312 static const enum sort_field branch_sort_fields[] = {
5313 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5314 };
5315 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5317 static int
5318 branch_compare(const void *l1, const void *l2)
5319 {
5320 const struct branch *branch1 = ((const struct line *) l1)->data;
5321 const struct branch *branch2 = ((const struct line *) l2)->data;
5323 switch (get_sort_field(branch_sort_state)) {
5324 case ORDERBY_DATE:
5325 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5327 case ORDERBY_AUTHOR:
5328 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5330 case ORDERBY_NAME:
5331 default:
5332 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5333 }
5334 }
5336 static bool
5337 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5338 {
5339 struct branch *branch = line->data;
5340 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5342 if (opt_date && draw_date(view, &branch->time))
5343 return TRUE;
5345 if (opt_author && draw_author(view, branch->author))
5346 return TRUE;
5348 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5349 return TRUE;
5350 }
5352 static enum request
5353 branch_request(struct view *view, enum request request, struct line *line)
5354 {
5355 struct branch *branch = line->data;
5357 switch (request) {
5358 case REQ_REFRESH:
5359 load_refs();
5360 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5361 return REQ_NONE;
5363 case REQ_TOGGLE_SORT_FIELD:
5364 case REQ_TOGGLE_SORT_ORDER:
5365 sort_view(view, request, &branch_sort_state, branch_compare);
5366 return REQ_NONE;
5368 case REQ_ENTER:
5369 {
5370 const struct ref *ref = branch->ref;
5371 const char *all_branches_argv[] = {
5372 "git", "log", "--no-color", "--pretty=raw", "--parents",
5373 "--topo-order",
5374 ref == &branch_all ? "--all" : ref->name, NULL
5375 };
5376 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5378 if (!prepare_update(main_view, all_branches_argv, NULL))
5379 report("Failed to load view of all branches");
5380 else
5381 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5382 return REQ_NONE;
5383 }
5384 default:
5385 return request;
5386 }
5387 }
5389 static bool
5390 branch_read(struct view *view, char *line)
5391 {
5392 static char id[SIZEOF_REV];
5393 struct branch *reference;
5394 size_t i;
5396 if (!line)
5397 return TRUE;
5399 switch (get_line_type(line)) {
5400 case LINE_COMMIT:
5401 string_copy_rev(id, line + STRING_SIZE("commit "));
5402 return TRUE;
5404 case LINE_AUTHOR:
5405 for (i = 0, reference = NULL; i < view->lines; i++) {
5406 struct branch *branch = view->line[i].data;
5408 if (strcmp(branch->ref->id, id))
5409 continue;
5411 view->line[i].dirty = TRUE;
5412 if (reference) {
5413 branch->author = reference->author;
5414 branch->time = reference->time;
5415 continue;
5416 }
5418 parse_author_line(line + STRING_SIZE("author "),
5419 &branch->author, &branch->time);
5420 reference = branch;
5421 }
5422 return TRUE;
5424 default:
5425 return TRUE;
5426 }
5428 }
5430 static bool
5431 branch_open_visitor(void *data, const struct ref *ref)
5432 {
5433 struct view *view = data;
5434 struct branch *branch;
5436 if (ref->tag || ref->ltag || ref->remote)
5437 return TRUE;
5439 branch = calloc(1, sizeof(*branch));
5440 if (!branch)
5441 return FALSE;
5443 branch->ref = ref;
5444 return !!add_line_data(view, branch, LINE_DEFAULT);
5445 }
5447 static bool
5448 branch_open(struct view *view)
5449 {
5450 const char *branch_log[] = {
5451 "git", "log", "--no-color", "--pretty=raw",
5452 "--simplify-by-decoration", "--all", NULL
5453 };
5455 if (!start_update(view, branch_log, NULL)) {
5456 report("Failed to load branch data");
5457 return TRUE;
5458 }
5460 setup_update(view, view->id);
5461 branch_open_visitor(view, &branch_all);
5462 foreach_ref(branch_open_visitor, view);
5463 view->p_restore = TRUE;
5465 return TRUE;
5466 }
5468 static bool
5469 branch_grep(struct view *view, struct line *line)
5470 {
5471 struct branch *branch = line->data;
5472 const char *text[] = {
5473 branch->ref->name,
5474 branch->author,
5475 NULL
5476 };
5478 return grep_text(view, text);
5479 }
5481 static void
5482 branch_select(struct view *view, struct line *line)
5483 {
5484 struct branch *branch = line->data;
5486 string_copy_rev(view->ref, branch->ref->id);
5487 string_copy_rev(ref_commit, branch->ref->id);
5488 string_copy_rev(ref_head, branch->ref->id);
5489 string_copy_rev(ref_branch, branch->ref->name);
5490 }
5492 static struct view_ops branch_ops = {
5493 "branch",
5494 NULL,
5495 branch_open,
5496 branch_read,
5497 branch_draw,
5498 branch_request,
5499 branch_grep,
5500 branch_select,
5501 };
5503 /*
5504 * Status backend
5505 */
5507 struct status {
5508 char status;
5509 struct {
5510 mode_t mode;
5511 char rev[SIZEOF_REV];
5512 char name[SIZEOF_STR];
5513 } old;
5514 struct {
5515 mode_t mode;
5516 char rev[SIZEOF_REV];
5517 char name[SIZEOF_STR];
5518 } new;
5519 };
5521 static char status_onbranch[SIZEOF_STR];
5522 static struct status stage_status;
5523 static enum line_type stage_line_type;
5524 static size_t stage_chunks;
5525 static int *stage_chunk;
5527 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5529 /* This should work even for the "On branch" line. */
5530 static inline bool
5531 status_has_none(struct view *view, struct line *line)
5532 {
5533 return line < view->line + view->lines && !line[1].data;
5534 }
5536 /* Get fields from the diff line:
5537 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5538 */
5539 static inline bool
5540 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5541 {
5542 const char *old_mode = buf + 1;
5543 const char *new_mode = buf + 8;
5544 const char *old_rev = buf + 15;
5545 const char *new_rev = buf + 56;
5546 const char *status = buf + 97;
5548 if (bufsize < 98 ||
5549 old_mode[-1] != ':' ||
5550 new_mode[-1] != ' ' ||
5551 old_rev[-1] != ' ' ||
5552 new_rev[-1] != ' ' ||
5553 status[-1] != ' ')
5554 return FALSE;
5556 file->status = *status;
5558 string_copy_rev(file->old.rev, old_rev);
5559 string_copy_rev(file->new.rev, new_rev);
5561 file->old.mode = strtoul(old_mode, NULL, 8);
5562 file->new.mode = strtoul(new_mode, NULL, 8);
5564 file->old.name[0] = file->new.name[0] = 0;
5566 return TRUE;
5567 }
5569 static bool
5570 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5571 {
5572 struct status *unmerged = NULL;
5573 char *buf;
5574 struct io io;
5576 if (!io_run(&io, IO_RD, opt_cdup, argv))
5577 return FALSE;
5579 add_line_data(view, NULL, type);
5581 while ((buf = io_get(&io, 0, TRUE))) {
5582 struct status *file = unmerged;
5584 if (!file) {
5585 file = calloc(1, sizeof(*file));
5586 if (!file || !add_line_data(view, file, type))
5587 goto error_out;
5588 }
5590 /* Parse diff info part. */
5591 if (status) {
5592 file->status = status;
5593 if (status == 'A')
5594 string_copy(file->old.rev, NULL_ID);
5596 } else if (!file->status || file == unmerged) {
5597 if (!status_get_diff(file, buf, strlen(buf)))
5598 goto error_out;
5600 buf = io_get(&io, 0, TRUE);
5601 if (!buf)
5602 break;
5604 /* Collapse all modified entries that follow an
5605 * associated unmerged entry. */
5606 if (unmerged == file) {
5607 unmerged->status = 'U';
5608 unmerged = NULL;
5609 } else if (file->status == 'U') {
5610 unmerged = file;
5611 }
5612 }
5614 /* Grab the old name for rename/copy. */
5615 if (!*file->old.name &&
5616 (file->status == 'R' || file->status == 'C')) {
5617 string_ncopy(file->old.name, buf, strlen(buf));
5619 buf = io_get(&io, 0, TRUE);
5620 if (!buf)
5621 break;
5622 }
5624 /* git-ls-files just delivers a NUL separated list of
5625 * file names similar to the second half of the
5626 * git-diff-* output. */
5627 string_ncopy(file->new.name, buf, strlen(buf));
5628 if (!*file->old.name)
5629 string_copy(file->old.name, file->new.name);
5630 file = NULL;
5631 }
5633 if (io_error(&io)) {
5634 error_out:
5635 io_done(&io);
5636 return FALSE;
5637 }
5639 if (!view->line[view->lines - 1].data)
5640 add_line_data(view, NULL, LINE_STAT_NONE);
5642 io_done(&io);
5643 return TRUE;
5644 }
5646 /* Don't show unmerged entries in the staged section. */
5647 static const char *status_diff_index_argv[] = {
5648 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5649 "--cached", "-M", "HEAD", NULL
5650 };
5652 static const char *status_diff_files_argv[] = {
5653 "git", "diff-files", "-z", NULL
5654 };
5656 static const char *status_list_other_argv[] = {
5657 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5658 };
5660 static const char *status_list_no_head_argv[] = {
5661 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5662 };
5664 static const char *update_index_argv[] = {
5665 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5666 };
5668 /* Restore the previous line number to stay in the context or select a
5669 * line with something that can be updated. */
5670 static void
5671 status_restore(struct view *view)
5672 {
5673 if (view->p_lineno >= view->lines)
5674 view->p_lineno = view->lines - 1;
5675 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5676 view->p_lineno++;
5677 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5678 view->p_lineno--;
5680 /* If the above fails, always skip the "On branch" line. */
5681 if (view->p_lineno < view->lines)
5682 view->lineno = view->p_lineno;
5683 else
5684 view->lineno = 1;
5686 if (view->lineno < view->offset)
5687 view->offset = view->lineno;
5688 else if (view->offset + view->height <= view->lineno)
5689 view->offset = view->lineno - view->height + 1;
5691 view->p_restore = FALSE;
5692 }
5694 static void
5695 status_update_onbranch(void)
5696 {
5697 static const char *paths[][2] = {
5698 { "rebase-apply/rebasing", "Rebasing" },
5699 { "rebase-apply/applying", "Applying mailbox" },
5700 { "rebase-apply/", "Rebasing mailbox" },
5701 { "rebase-merge/interactive", "Interactive rebase" },
5702 { "rebase-merge/", "Rebase merge" },
5703 { "MERGE_HEAD", "Merging" },
5704 { "BISECT_LOG", "Bisecting" },
5705 { "HEAD", "On branch" },
5706 };
5707 char buf[SIZEOF_STR];
5708 struct stat stat;
5709 int i;
5711 if (is_initial_commit()) {
5712 string_copy(status_onbranch, "Initial commit");
5713 return;
5714 }
5716 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5717 char *head = opt_head;
5719 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5720 lstat(buf, &stat) < 0)
5721 continue;
5723 if (!*opt_head) {
5724 struct io io;
5726 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5727 io_read_buf(&io, buf, sizeof(buf))) {
5728 head = buf;
5729 if (!prefixcmp(head, "refs/heads/"))
5730 head += STRING_SIZE("refs/heads/");
5731 }
5732 }
5734 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5735 string_copy(status_onbranch, opt_head);
5736 return;
5737 }
5739 string_copy(status_onbranch, "Not currently on any branch");
5740 }
5742 /* First parse staged info using git-diff-index(1), then parse unstaged
5743 * info using git-diff-files(1), and finally untracked files using
5744 * git-ls-files(1). */
5745 static bool
5746 status_open(struct view *view)
5747 {
5748 reset_view(view);
5750 add_line_data(view, NULL, LINE_STAT_HEAD);
5751 status_update_onbranch();
5753 io_run_bg(update_index_argv);
5755 if (is_initial_commit()) {
5756 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5757 return FALSE;
5758 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5759 return FALSE;
5760 }
5762 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5763 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5764 return FALSE;
5766 /* Restore the exact position or use the specialized restore
5767 * mode? */
5768 if (!view->p_restore)
5769 status_restore(view);
5770 return TRUE;
5771 }
5773 static bool
5774 status_draw(struct view *view, struct line *line, unsigned int lineno)
5775 {
5776 struct status *status = line->data;
5777 enum line_type type;
5778 const char *text;
5780 if (!status) {
5781 switch (line->type) {
5782 case LINE_STAT_STAGED:
5783 type = LINE_STAT_SECTION;
5784 text = "Changes to be committed:";
5785 break;
5787 case LINE_STAT_UNSTAGED:
5788 type = LINE_STAT_SECTION;
5789 text = "Changed but not updated:";
5790 break;
5792 case LINE_STAT_UNTRACKED:
5793 type = LINE_STAT_SECTION;
5794 text = "Untracked files:";
5795 break;
5797 case LINE_STAT_NONE:
5798 type = LINE_DEFAULT;
5799 text = " (no files)";
5800 break;
5802 case LINE_STAT_HEAD:
5803 type = LINE_STAT_HEAD;
5804 text = status_onbranch;
5805 break;
5807 default:
5808 return FALSE;
5809 }
5810 } else {
5811 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5813 buf[0] = status->status;
5814 if (draw_text(view, line->type, buf, TRUE))
5815 return TRUE;
5816 type = LINE_DEFAULT;
5817 text = status->new.name;
5818 }
5820 draw_text(view, type, text, TRUE);
5821 return TRUE;
5822 }
5824 static enum request
5825 status_load_error(struct view *view, struct view *stage, const char *path)
5826 {
5827 if (displayed_views() == 2 || display[current_view] != view)
5828 maximize_view(view);
5829 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5830 return REQ_NONE;
5831 }
5833 static enum request
5834 status_enter(struct view *view, struct line *line)
5835 {
5836 struct status *status = line->data;
5837 const char *oldpath = status ? status->old.name : NULL;
5838 /* Diffs for unmerged entries are empty when passing the new
5839 * path, so leave it empty. */
5840 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5841 const char *info;
5842 enum open_flags split;
5843 struct view *stage = VIEW(REQ_VIEW_STAGE);
5845 if (line->type == LINE_STAT_NONE ||
5846 (!status && line[1].type == LINE_STAT_NONE)) {
5847 report("No file to diff");
5848 return REQ_NONE;
5849 }
5851 switch (line->type) {
5852 case LINE_STAT_STAGED:
5853 if (is_initial_commit()) {
5854 const char *no_head_diff_argv[] = {
5855 "git", "diff", "--no-color", "--patch-with-stat",
5856 "--", "/dev/null", newpath, NULL
5857 };
5859 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5860 return status_load_error(view, stage, newpath);
5861 } else {
5862 const char *index_show_argv[] = {
5863 "git", "diff-index", "--root", "--patch-with-stat",
5864 "-C", "-M", "--cached", "HEAD", "--",
5865 oldpath, newpath, NULL
5866 };
5868 if (!prepare_update(stage, index_show_argv, opt_cdup))
5869 return status_load_error(view, stage, newpath);
5870 }
5872 if (status)
5873 info = "Staged changes to %s";
5874 else
5875 info = "Staged changes";
5876 break;
5878 case LINE_STAT_UNSTAGED:
5879 {
5880 const char *files_show_argv[] = {
5881 "git", "diff-files", "--root", "--patch-with-stat",
5882 "-C", "-M", "--", oldpath, newpath, NULL
5883 };
5885 if (!prepare_update(stage, files_show_argv, opt_cdup))
5886 return status_load_error(view, stage, newpath);
5887 if (status)
5888 info = "Unstaged changes to %s";
5889 else
5890 info = "Unstaged changes";
5891 break;
5892 }
5893 case LINE_STAT_UNTRACKED:
5894 if (!newpath) {
5895 report("No file to show");
5896 return REQ_NONE;
5897 }
5899 if (!suffixcmp(status->new.name, -1, "/")) {
5900 report("Cannot display a directory");
5901 return REQ_NONE;
5902 }
5904 if (!prepare_update_file(stage, newpath))
5905 return status_load_error(view, stage, newpath);
5906 info = "Untracked file %s";
5907 break;
5909 case LINE_STAT_HEAD:
5910 return REQ_NONE;
5912 default:
5913 die("line type %d not handled in switch", line->type);
5914 }
5916 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5917 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5918 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5919 if (status) {
5920 stage_status = *status;
5921 } else {
5922 memset(&stage_status, 0, sizeof(stage_status));
5923 }
5925 stage_line_type = line->type;
5926 stage_chunks = 0;
5927 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5928 }
5930 return REQ_NONE;
5931 }
5933 static bool
5934 status_exists(struct status *status, enum line_type type)
5935 {
5936 struct view *view = VIEW(REQ_VIEW_STATUS);
5937 unsigned long lineno;
5939 for (lineno = 0; lineno < view->lines; lineno++) {
5940 struct line *line = &view->line[lineno];
5941 struct status *pos = line->data;
5943 if (line->type != type)
5944 continue;
5945 if (!pos && (!status || !status->status) && line[1].data) {
5946 select_view_line(view, lineno);
5947 return TRUE;
5948 }
5949 if (pos && !strcmp(status->new.name, pos->new.name)) {
5950 select_view_line(view, lineno);
5951 return TRUE;
5952 }
5953 }
5955 return FALSE;
5956 }
5959 static bool
5960 status_update_prepare(struct io *io, enum line_type type)
5961 {
5962 const char *staged_argv[] = {
5963 "git", "update-index", "-z", "--index-info", NULL
5964 };
5965 const char *others_argv[] = {
5966 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5967 };
5969 switch (type) {
5970 case LINE_STAT_STAGED:
5971 return io_run(io, IO_WR, opt_cdup, staged_argv);
5973 case LINE_STAT_UNSTAGED:
5974 case LINE_STAT_UNTRACKED:
5975 return io_run(io, IO_WR, opt_cdup, others_argv);
5977 default:
5978 die("line type %d not handled in switch", type);
5979 return FALSE;
5980 }
5981 }
5983 static bool
5984 status_update_write(struct io *io, struct status *status, enum line_type type)
5985 {
5986 char buf[SIZEOF_STR];
5987 size_t bufsize = 0;
5989 switch (type) {
5990 case LINE_STAT_STAGED:
5991 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5992 status->old.mode,
5993 status->old.rev,
5994 status->old.name, 0))
5995 return FALSE;
5996 break;
5998 case LINE_STAT_UNSTAGED:
5999 case LINE_STAT_UNTRACKED:
6000 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6001 return FALSE;
6002 break;
6004 default:
6005 die("line type %d not handled in switch", type);
6006 }
6008 return io_write(io, buf, bufsize);
6009 }
6011 static bool
6012 status_update_file(struct status *status, enum line_type type)
6013 {
6014 struct io io;
6015 bool result;
6017 if (!status_update_prepare(&io, type))
6018 return FALSE;
6020 result = status_update_write(&io, status, type);
6021 return io_done(&io) && result;
6022 }
6024 static bool
6025 status_update_files(struct view *view, struct line *line)
6026 {
6027 char buf[sizeof(view->ref)];
6028 struct io io;
6029 bool result = TRUE;
6030 struct line *pos = view->line + view->lines;
6031 int files = 0;
6032 int file, done;
6033 int cursor_y = -1, cursor_x = -1;
6035 if (!status_update_prepare(&io, line->type))
6036 return FALSE;
6038 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6039 files++;
6041 string_copy(buf, view->ref);
6042 getsyx(cursor_y, cursor_x);
6043 for (file = 0, done = 5; result && file < files; line++, file++) {
6044 int almost_done = file * 100 / files;
6046 if (almost_done > done) {
6047 done = almost_done;
6048 string_format(view->ref, "updating file %u of %u (%d%% done)",
6049 file, files, done);
6050 update_view_title(view);
6051 setsyx(cursor_y, cursor_x);
6052 doupdate();
6053 }
6054 result = status_update_write(&io, line->data, line->type);
6055 }
6056 string_copy(view->ref, buf);
6058 return io_done(&io) && result;
6059 }
6061 static bool
6062 status_update(struct view *view)
6063 {
6064 struct line *line = &view->line[view->lineno];
6066 assert(view->lines);
6068 if (!line->data) {
6069 /* This should work even for the "On branch" line. */
6070 if (line < view->line + view->lines && !line[1].data) {
6071 report("Nothing to update");
6072 return FALSE;
6073 }
6075 if (!status_update_files(view, line + 1)) {
6076 report("Failed to update file status");
6077 return FALSE;
6078 }
6080 } else if (!status_update_file(line->data, line->type)) {
6081 report("Failed to update file status");
6082 return FALSE;
6083 }
6085 return TRUE;
6086 }
6088 static bool
6089 status_revert(struct status *status, enum line_type type, bool has_none)
6090 {
6091 if (!status || type != LINE_STAT_UNSTAGED) {
6092 if (type == LINE_STAT_STAGED) {
6093 report("Cannot revert changes to staged files");
6094 } else if (type == LINE_STAT_UNTRACKED) {
6095 report("Cannot revert changes to untracked files");
6096 } else if (has_none) {
6097 report("Nothing to revert");
6098 } else {
6099 report("Cannot revert changes to multiple files");
6100 }
6102 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6103 char mode[10] = "100644";
6104 const char *reset_argv[] = {
6105 "git", "update-index", "--cacheinfo", mode,
6106 status->old.rev, status->old.name, NULL
6107 };
6108 const char *checkout_argv[] = {
6109 "git", "checkout", "--", status->old.name, NULL
6110 };
6112 if (status->status == 'U') {
6113 string_format(mode, "%5o", status->old.mode);
6115 if (status->old.mode == 0 && status->new.mode == 0) {
6116 reset_argv[2] = "--force-remove";
6117 reset_argv[3] = status->old.name;
6118 reset_argv[4] = NULL;
6119 }
6121 if (!io_run_fg(reset_argv, opt_cdup))
6122 return FALSE;
6123 if (status->old.mode == 0 && status->new.mode == 0)
6124 return TRUE;
6125 }
6127 return io_run_fg(checkout_argv, opt_cdup);
6128 }
6130 return FALSE;
6131 }
6133 static enum request
6134 status_request(struct view *view, enum request request, struct line *line)
6135 {
6136 struct status *status = line->data;
6138 switch (request) {
6139 case REQ_STATUS_UPDATE:
6140 if (!status_update(view))
6141 return REQ_NONE;
6142 break;
6144 case REQ_STATUS_REVERT:
6145 if (!status_revert(status, line->type, status_has_none(view, line)))
6146 return REQ_NONE;
6147 break;
6149 case REQ_STATUS_MERGE:
6150 if (!status || status->status != 'U') {
6151 report("Merging only possible for files with unmerged status ('U').");
6152 return REQ_NONE;
6153 }
6154 open_mergetool(status->new.name);
6155 break;
6157 case REQ_EDIT:
6158 if (!status)
6159 return request;
6160 if (status->status == 'D') {
6161 report("File has been deleted.");
6162 return REQ_NONE;
6163 }
6165 open_editor(status->new.name);
6166 break;
6168 case REQ_VIEW_BLAME:
6169 if (status)
6170 opt_ref[0] = 0;
6171 return request;
6173 case REQ_ENTER:
6174 /* After returning the status view has been split to
6175 * show the stage view. No further reloading is
6176 * necessary. */
6177 return status_enter(view, line);
6179 case REQ_REFRESH:
6180 /* Simply reload the view. */
6181 break;
6183 default:
6184 return request;
6185 }
6187 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6189 return REQ_NONE;
6190 }
6192 static void
6193 status_select(struct view *view, struct line *line)
6194 {
6195 struct status *status = line->data;
6196 char file[SIZEOF_STR] = "all files";
6197 const char *text;
6198 const char *key;
6200 if (status && !string_format(file, "'%s'", status->new.name))
6201 return;
6203 if (!status && line[1].type == LINE_STAT_NONE)
6204 line++;
6206 switch (line->type) {
6207 case LINE_STAT_STAGED:
6208 text = "Press %s to unstage %s for commit";
6209 break;
6211 case LINE_STAT_UNSTAGED:
6212 text = "Press %s to stage %s for commit";
6213 break;
6215 case LINE_STAT_UNTRACKED:
6216 text = "Press %s to stage %s for addition";
6217 break;
6219 case LINE_STAT_HEAD:
6220 case LINE_STAT_NONE:
6221 text = "Nothing to update";
6222 break;
6224 default:
6225 die("line type %d not handled in switch", line->type);
6226 }
6228 if (status && status->status == 'U') {
6229 text = "Press %s to resolve conflict in %s";
6230 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6232 } else {
6233 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6234 }
6236 string_format(view->ref, text, key, file);
6237 if (status)
6238 string_copy(opt_file, status->new.name);
6239 }
6241 static bool
6242 status_grep(struct view *view, struct line *line)
6243 {
6244 struct status *status = line->data;
6246 if (status) {
6247 const char buf[2] = { status->status, 0 };
6248 const char *text[] = { status->new.name, buf, NULL };
6250 return grep_text(view, text);
6251 }
6253 return FALSE;
6254 }
6256 static struct view_ops status_ops = {
6257 "file",
6258 NULL,
6259 status_open,
6260 NULL,
6261 status_draw,
6262 status_request,
6263 status_grep,
6264 status_select,
6265 };
6268 static bool
6269 stage_diff_write(struct io *io, struct line *line, struct line *end)
6270 {
6271 while (line < end) {
6272 if (!io_write(io, line->data, strlen(line->data)) ||
6273 !io_write(io, "\n", 1))
6274 return FALSE;
6275 line++;
6276 if (line->type == LINE_DIFF_CHUNK ||
6277 line->type == LINE_DIFF_HEADER)
6278 break;
6279 }
6281 return TRUE;
6282 }
6284 static struct line *
6285 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6286 {
6287 for (; view->line < line; line--)
6288 if (line->type == type)
6289 return line;
6291 return NULL;
6292 }
6294 static bool
6295 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6296 {
6297 const char *apply_argv[SIZEOF_ARG] = {
6298 "git", "apply", "--whitespace=nowarn", NULL
6299 };
6300 struct line *diff_hdr;
6301 struct io io;
6302 int argc = 3;
6304 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6305 if (!diff_hdr)
6306 return FALSE;
6308 if (!revert)
6309 apply_argv[argc++] = "--cached";
6310 if (revert || stage_line_type == LINE_STAT_STAGED)
6311 apply_argv[argc++] = "-R";
6312 apply_argv[argc++] = "-";
6313 apply_argv[argc++] = NULL;
6314 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6315 return FALSE;
6317 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6318 !stage_diff_write(&io, chunk, view->line + view->lines))
6319 chunk = NULL;
6321 io_done(&io);
6322 io_run_bg(update_index_argv);
6324 return chunk ? TRUE : FALSE;
6325 }
6327 static bool
6328 stage_update(struct view *view, struct line *line)
6329 {
6330 struct line *chunk = NULL;
6332 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6333 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6335 if (chunk) {
6336 if (!stage_apply_chunk(view, chunk, FALSE)) {
6337 report("Failed to apply chunk");
6338 return FALSE;
6339 }
6341 } else if (!stage_status.status) {
6342 view = VIEW(REQ_VIEW_STATUS);
6344 for (line = view->line; line < view->line + view->lines; line++)
6345 if (line->type == stage_line_type)
6346 break;
6348 if (!status_update_files(view, line + 1)) {
6349 report("Failed to update files");
6350 return FALSE;
6351 }
6353 } else if (!status_update_file(&stage_status, stage_line_type)) {
6354 report("Failed to update file");
6355 return FALSE;
6356 }
6358 return TRUE;
6359 }
6361 static bool
6362 stage_revert(struct view *view, struct line *line)
6363 {
6364 struct line *chunk = NULL;
6366 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6367 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6369 if (chunk) {
6370 if (!prompt_yesno("Are you sure you want to revert changes?"))
6371 return FALSE;
6373 if (!stage_apply_chunk(view, chunk, TRUE)) {
6374 report("Failed to revert chunk");
6375 return FALSE;
6376 }
6377 return TRUE;
6379 } else {
6380 return status_revert(stage_status.status ? &stage_status : NULL,
6381 stage_line_type, FALSE);
6382 }
6383 }
6386 static void
6387 stage_next(struct view *view, struct line *line)
6388 {
6389 int i;
6391 if (!stage_chunks) {
6392 for (line = view->line; line < view->line + view->lines; line++) {
6393 if (line->type != LINE_DIFF_CHUNK)
6394 continue;
6396 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6397 report("Allocation failure");
6398 return;
6399 }
6401 stage_chunk[stage_chunks++] = line - view->line;
6402 }
6403 }
6405 for (i = 0; i < stage_chunks; i++) {
6406 if (stage_chunk[i] > view->lineno) {
6407 do_scroll_view(view, stage_chunk[i] - view->lineno);
6408 report("Chunk %d of %d", i + 1, stage_chunks);
6409 return;
6410 }
6411 }
6413 report("No next chunk found");
6414 }
6416 static enum request
6417 stage_request(struct view *view, enum request request, struct line *line)
6418 {
6419 switch (request) {
6420 case REQ_STATUS_UPDATE:
6421 if (!stage_update(view, line))
6422 return REQ_NONE;
6423 break;
6425 case REQ_STATUS_REVERT:
6426 if (!stage_revert(view, line))
6427 return REQ_NONE;
6428 break;
6430 case REQ_STAGE_NEXT:
6431 if (stage_line_type == LINE_STAT_UNTRACKED) {
6432 report("File is untracked; press %s to add",
6433 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6434 return REQ_NONE;
6435 }
6436 stage_next(view, line);
6437 return REQ_NONE;
6439 case REQ_EDIT:
6440 if (!stage_status.new.name[0])
6441 return request;
6442 if (stage_status.status == 'D') {
6443 report("File has been deleted.");
6444 return REQ_NONE;
6445 }
6447 open_editor(stage_status.new.name);
6448 break;
6450 case REQ_REFRESH:
6451 /* Reload everything ... */
6452 break;
6454 case REQ_VIEW_BLAME:
6455 if (stage_status.new.name[0]) {
6456 string_copy(opt_file, stage_status.new.name);
6457 opt_ref[0] = 0;
6458 }
6459 return request;
6461 case REQ_ENTER:
6462 return pager_request(view, request, line);
6464 default:
6465 return request;
6466 }
6468 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6469 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6471 /* Check whether the staged entry still exists, and close the
6472 * stage view if it doesn't. */
6473 if (!status_exists(&stage_status, stage_line_type)) {
6474 status_restore(VIEW(REQ_VIEW_STATUS));
6475 return REQ_VIEW_CLOSE;
6476 }
6478 if (stage_line_type == LINE_STAT_UNTRACKED) {
6479 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6480 report("Cannot display a directory");
6481 return REQ_NONE;
6482 }
6484 if (!prepare_update_file(view, stage_status.new.name)) {
6485 report("Failed to open file: %s", strerror(errno));
6486 return REQ_NONE;
6487 }
6488 }
6489 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6491 return REQ_NONE;
6492 }
6494 static struct view_ops stage_ops = {
6495 "line",
6496 NULL,
6497 NULL,
6498 pager_read,
6499 pager_draw,
6500 stage_request,
6501 pager_grep,
6502 pager_select,
6503 };
6506 /*
6507 * Revision graph
6508 */
6510 struct commit {
6511 char id[SIZEOF_REV]; /* SHA1 ID. */
6512 char title[128]; /* First line of the commit message. */
6513 const char *author; /* Author of the commit. */
6514 struct time time; /* Date from the author ident. */
6515 struct ref_list *refs; /* Repository references. */
6516 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6517 size_t graph_size; /* The width of the graph array. */
6518 bool has_parents; /* Rewritten --parents seen. */
6519 };
6521 /* Size of rev graph with no "padding" columns */
6522 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6524 struct rev_graph {
6525 struct rev_graph *prev, *next, *parents;
6526 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6527 size_t size;
6528 struct commit *commit;
6529 size_t pos;
6530 unsigned int boundary:1;
6531 };
6533 /* Parents of the commit being visualized. */
6534 static struct rev_graph graph_parents[4];
6536 /* The current stack of revisions on the graph. */
6537 static struct rev_graph graph_stacks[4] = {
6538 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6539 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6540 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6541 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6542 };
6544 static inline bool
6545 graph_parent_is_merge(struct rev_graph *graph)
6546 {
6547 return graph->parents->size > 1;
6548 }
6550 static inline void
6551 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6552 {
6553 struct commit *commit = graph->commit;
6555 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6556 commit->graph[commit->graph_size++] = symbol;
6557 }
6559 static void
6560 clear_rev_graph(struct rev_graph *graph)
6561 {
6562 graph->boundary = 0;
6563 graph->size = graph->pos = 0;
6564 graph->commit = NULL;
6565 memset(graph->parents, 0, sizeof(*graph->parents));
6566 }
6568 static void
6569 done_rev_graph(struct rev_graph *graph)
6570 {
6571 if (graph_parent_is_merge(graph) &&
6572 graph->pos < graph->size - 1 &&
6573 graph->next->size == graph->size + graph->parents->size - 1) {
6574 size_t i = graph->pos + graph->parents->size - 1;
6576 graph->commit->graph_size = i * 2;
6577 while (i < graph->next->size - 1) {
6578 append_to_rev_graph(graph, ' ');
6579 append_to_rev_graph(graph, '\\');
6580 i++;
6581 }
6582 }
6584 clear_rev_graph(graph);
6585 }
6587 static void
6588 push_rev_graph(struct rev_graph *graph, const char *parent)
6589 {
6590 int i;
6592 /* "Collapse" duplicate parents lines.
6593 *
6594 * FIXME: This needs to also update update the drawn graph but
6595 * for now it just serves as a method for pruning graph lines. */
6596 for (i = 0; i < graph->size; i++)
6597 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6598 return;
6600 if (graph->size < SIZEOF_REVITEMS) {
6601 string_copy_rev(graph->rev[graph->size++], parent);
6602 }
6603 }
6605 static chtype
6606 get_rev_graph_symbol(struct rev_graph *graph)
6607 {
6608 chtype symbol;
6610 if (graph->boundary)
6611 symbol = REVGRAPH_BOUND;
6612 else if (graph->parents->size == 0)
6613 symbol = REVGRAPH_INIT;
6614 else if (graph_parent_is_merge(graph))
6615 symbol = REVGRAPH_MERGE;
6616 else if (graph->pos >= graph->size)
6617 symbol = REVGRAPH_BRANCH;
6618 else
6619 symbol = REVGRAPH_COMMIT;
6621 return symbol;
6622 }
6624 static void
6625 draw_rev_graph(struct rev_graph *graph)
6626 {
6627 struct rev_filler {
6628 chtype separator, line;
6629 };
6630 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6631 static struct rev_filler fillers[] = {
6632 { ' ', '|' },
6633 { '`', '.' },
6634 { '\'', ' ' },
6635 { '/', ' ' },
6636 };
6637 chtype symbol = get_rev_graph_symbol(graph);
6638 struct rev_filler *filler;
6639 size_t i;
6641 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6642 filler = &fillers[DEFAULT];
6644 for (i = 0; i < graph->pos; i++) {
6645 append_to_rev_graph(graph, filler->line);
6646 if (graph_parent_is_merge(graph->prev) &&
6647 graph->prev->pos == i)
6648 filler = &fillers[RSHARP];
6650 append_to_rev_graph(graph, filler->separator);
6651 }
6653 /* Place the symbol for this revision. */
6654 append_to_rev_graph(graph, symbol);
6656 if (graph->prev->size > graph->size)
6657 filler = &fillers[RDIAG];
6658 else
6659 filler = &fillers[DEFAULT];
6661 i++;
6663 for (; i < graph->size; i++) {
6664 append_to_rev_graph(graph, filler->separator);
6665 append_to_rev_graph(graph, filler->line);
6666 if (graph_parent_is_merge(graph->prev) &&
6667 i < graph->prev->pos + graph->parents->size)
6668 filler = &fillers[RSHARP];
6669 if (graph->prev->size > graph->size)
6670 filler = &fillers[LDIAG];
6671 }
6673 if (graph->prev->size > graph->size) {
6674 append_to_rev_graph(graph, filler->separator);
6675 if (filler->line != ' ')
6676 append_to_rev_graph(graph, filler->line);
6677 }
6678 }
6680 /* Prepare the next rev graph */
6681 static void
6682 prepare_rev_graph(struct rev_graph *graph)
6683 {
6684 size_t i;
6686 /* First, traverse all lines of revisions up to the active one. */
6687 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6688 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6689 break;
6691 push_rev_graph(graph->next, graph->rev[graph->pos]);
6692 }
6694 /* Interleave the new revision parent(s). */
6695 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6696 push_rev_graph(graph->next, graph->parents->rev[i]);
6698 /* Lastly, put any remaining revisions. */
6699 for (i = graph->pos + 1; i < graph->size; i++)
6700 push_rev_graph(graph->next, graph->rev[i]);
6701 }
6703 static void
6704 update_rev_graph(struct view *view, struct rev_graph *graph)
6705 {
6706 /* If this is the finalizing update ... */
6707 if (graph->commit)
6708 prepare_rev_graph(graph);
6710 /* Graph visualization needs a one rev look-ahead,
6711 * so the first update doesn't visualize anything. */
6712 if (!graph->prev->commit)
6713 return;
6715 if (view->lines > 2)
6716 view->line[view->lines - 3].dirty = 1;
6717 if (view->lines > 1)
6718 view->line[view->lines - 2].dirty = 1;
6719 draw_rev_graph(graph->prev);
6720 done_rev_graph(graph->prev->prev);
6721 }
6724 /*
6725 * Main view backend
6726 */
6728 static const char *main_argv[SIZEOF_ARG] = {
6729 "git", "log", "--no-color", "--pretty=raw", "--parents",
6730 "--topo-order", "%(diff-args)", "%(rev-args)",
6731 "--", "%(file-args)", NULL
6732 };
6734 static bool
6735 main_draw(struct view *view, struct line *line, unsigned int lineno)
6736 {
6737 struct commit *commit = line->data;
6739 if (!commit->author)
6740 return FALSE;
6742 if (opt_date && draw_date(view, &commit->time))
6743 return TRUE;
6745 if (opt_author && draw_author(view, commit->author))
6746 return TRUE;
6748 if (opt_rev_graph && commit->graph_size &&
6749 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6750 return TRUE;
6752 if (opt_show_refs && commit->refs) {
6753 size_t i;
6755 for (i = 0; i < commit->refs->size; i++) {
6756 struct ref *ref = commit->refs->refs[i];
6757 enum line_type type;
6759 if (ref->head)
6760 type = LINE_MAIN_HEAD;
6761 else if (ref->ltag)
6762 type = LINE_MAIN_LOCAL_TAG;
6763 else if (ref->tag)
6764 type = LINE_MAIN_TAG;
6765 else if (ref->tracked)
6766 type = LINE_MAIN_TRACKED;
6767 else if (ref->remote)
6768 type = LINE_MAIN_REMOTE;
6769 else
6770 type = LINE_MAIN_REF;
6772 if (draw_text(view, type, "[", TRUE) ||
6773 draw_text(view, type, ref->name, TRUE) ||
6774 draw_text(view, type, "]", TRUE))
6775 return TRUE;
6777 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6778 return TRUE;
6779 }
6780 }
6782 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6783 return TRUE;
6784 }
6786 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6787 static bool
6788 main_read(struct view *view, char *line)
6789 {
6790 static struct rev_graph *graph = graph_stacks;
6791 enum line_type type;
6792 struct commit *commit;
6794 if (!line) {
6795 int i;
6797 if (!view->lines && !view->prev)
6798 die("No revisions match the given arguments.");
6799 if (view->lines > 0) {
6800 commit = view->line[view->lines - 1].data;
6801 view->line[view->lines - 1].dirty = 1;
6802 if (!commit->author) {
6803 view->lines--;
6804 free(commit);
6805 graph->commit = NULL;
6806 }
6807 }
6808 update_rev_graph(view, graph);
6810 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6811 clear_rev_graph(&graph_stacks[i]);
6812 return TRUE;
6813 }
6815 type = get_line_type(line);
6816 if (type == LINE_COMMIT) {
6817 commit = calloc(1, sizeof(struct commit));
6818 if (!commit)
6819 return FALSE;
6821 line += STRING_SIZE("commit ");
6822 if (*line == '-') {
6823 graph->boundary = 1;
6824 line++;
6825 }
6827 string_copy_rev(commit->id, line);
6828 commit->refs = get_ref_list(commit->id);
6829 graph->commit = commit;
6830 add_line_data(view, commit, LINE_MAIN_COMMIT);
6832 while ((line = strchr(line, ' '))) {
6833 line++;
6834 push_rev_graph(graph->parents, line);
6835 commit->has_parents = TRUE;
6836 }
6837 return TRUE;
6838 }
6840 if (!view->lines)
6841 return TRUE;
6842 commit = view->line[view->lines - 1].data;
6844 switch (type) {
6845 case LINE_PARENT:
6846 if (commit->has_parents)
6847 break;
6848 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6849 break;
6851 case LINE_AUTHOR:
6852 parse_author_line(line + STRING_SIZE("author "),
6853 &commit->author, &commit->time);
6854 update_rev_graph(view, graph);
6855 graph = graph->next;
6856 break;
6858 default:
6859 /* Fill in the commit title if it has not already been set. */
6860 if (commit->title[0])
6861 break;
6863 /* Require titles to start with a non-space character at the
6864 * offset used by git log. */
6865 if (strncmp(line, " ", 4))
6866 break;
6867 line += 4;
6868 /* Well, if the title starts with a whitespace character,
6869 * try to be forgiving. Otherwise we end up with no title. */
6870 while (isspace(*line))
6871 line++;
6872 if (*line == '\0')
6873 break;
6874 /* FIXME: More graceful handling of titles; append "..." to
6875 * shortened titles, etc. */
6877 string_expand(commit->title, sizeof(commit->title), line, 1);
6878 view->line[view->lines - 1].dirty = 1;
6879 }
6881 return TRUE;
6882 }
6884 static enum request
6885 main_request(struct view *view, enum request request, struct line *line)
6886 {
6887 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6889 switch (request) {
6890 case REQ_ENTER:
6891 open_view(view, REQ_VIEW_DIFF, flags);
6892 break;
6893 case REQ_REFRESH:
6894 load_refs();
6895 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6896 break;
6897 default:
6898 return request;
6899 }
6901 return REQ_NONE;
6902 }
6904 static bool
6905 grep_refs(struct ref_list *list, regex_t *regex)
6906 {
6907 regmatch_t pmatch;
6908 size_t i;
6910 if (!opt_show_refs || !list)
6911 return FALSE;
6913 for (i = 0; i < list->size; i++) {
6914 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6915 return TRUE;
6916 }
6918 return FALSE;
6919 }
6921 static bool
6922 main_grep(struct view *view, struct line *line)
6923 {
6924 struct commit *commit = line->data;
6925 const char *text[] = {
6926 commit->title,
6927 opt_author ? commit->author : "",
6928 mkdate(&commit->time, opt_date),
6929 NULL
6930 };
6932 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6933 }
6935 static void
6936 main_select(struct view *view, struct line *line)
6937 {
6938 struct commit *commit = line->data;
6940 string_copy_rev(view->ref, commit->id);
6941 string_copy_rev(ref_commit, view->ref);
6942 }
6944 static struct view_ops main_ops = {
6945 "commit",
6946 main_argv,
6947 NULL,
6948 main_read,
6949 main_draw,
6950 main_request,
6951 main_grep,
6952 main_select,
6953 };
6956 /*
6957 * Status management
6958 */
6960 /* Whether or not the curses interface has been initialized. */
6961 static bool cursed = FALSE;
6963 /* Terminal hacks and workarounds. */
6964 static bool use_scroll_redrawwin;
6965 static bool use_scroll_status_wclear;
6967 /* The status window is used for polling keystrokes. */
6968 static WINDOW *status_win;
6970 /* Reading from the prompt? */
6971 static bool input_mode = FALSE;
6973 static bool status_empty = FALSE;
6975 /* Update status and title window. */
6976 static void
6977 report(const char *msg, ...)
6978 {
6979 struct view *view = display[current_view];
6981 if (input_mode)
6982 return;
6984 if (!view) {
6985 char buf[SIZEOF_STR];
6986 va_list args;
6988 va_start(args, msg);
6989 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6990 buf[sizeof(buf) - 1] = 0;
6991 buf[sizeof(buf) - 2] = '.';
6992 buf[sizeof(buf) - 3] = '.';
6993 buf[sizeof(buf) - 4] = '.';
6994 }
6995 va_end(args);
6996 die("%s", buf);
6997 }
6999 if (!status_empty || *msg) {
7000 va_list args;
7002 va_start(args, msg);
7004 wmove(status_win, 0, 0);
7005 if (view->has_scrolled && use_scroll_status_wclear)
7006 wclear(status_win);
7007 if (*msg) {
7008 vwprintw(status_win, msg, args);
7009 status_empty = FALSE;
7010 } else {
7011 status_empty = TRUE;
7012 }
7013 wclrtoeol(status_win);
7014 wnoutrefresh(status_win);
7016 va_end(args);
7017 }
7019 update_view_title(view);
7020 }
7022 static void
7023 init_display(void)
7024 {
7025 const char *term;
7026 int x, y;
7028 /* Initialize the curses library */
7029 if (isatty(STDIN_FILENO)) {
7030 cursed = !!initscr();
7031 opt_tty = stdin;
7032 } else {
7033 /* Leave stdin and stdout alone when acting as a pager. */
7034 opt_tty = fopen("/dev/tty", "r+");
7035 if (!opt_tty)
7036 die("Failed to open /dev/tty");
7037 cursed = !!newterm(NULL, opt_tty, opt_tty);
7038 }
7040 if (!cursed)
7041 die("Failed to initialize curses");
7043 nonl(); /* Disable conversion and detect newlines from input. */
7044 cbreak(); /* Take input chars one at a time, no wait for \n */
7045 noecho(); /* Don't echo input */
7046 leaveok(stdscr, FALSE);
7048 if (has_colors())
7049 init_colors();
7051 getmaxyx(stdscr, y, x);
7052 status_win = newwin(1, 0, y - 1, 0);
7053 if (!status_win)
7054 die("Failed to create status window");
7056 /* Enable keyboard mapping */
7057 keypad(status_win, TRUE);
7058 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7060 TABSIZE = opt_tab_size;
7062 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7063 if (term && !strcmp(term, "gnome-terminal")) {
7064 /* In the gnome-terminal-emulator, the message from
7065 * scrolling up one line when impossible followed by
7066 * scrolling down one line causes corruption of the
7067 * status line. This is fixed by calling wclear. */
7068 use_scroll_status_wclear = TRUE;
7069 use_scroll_redrawwin = FALSE;
7071 } else if (term && !strcmp(term, "xrvt-xpm")) {
7072 /* No problems with full optimizations in xrvt-(unicode)
7073 * and aterm. */
7074 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7076 } else {
7077 /* When scrolling in (u)xterm the last line in the
7078 * scrolling direction will update slowly. */
7079 use_scroll_redrawwin = TRUE;
7080 use_scroll_status_wclear = FALSE;
7081 }
7082 }
7084 static int
7085 get_input(int prompt_position)
7086 {
7087 struct view *view;
7088 int i, key, cursor_y, cursor_x;
7089 bool loading = FALSE;
7091 if (prompt_position)
7092 input_mode = TRUE;
7094 while (TRUE) {
7095 foreach_view (view, i) {
7096 update_view(view);
7097 if (view_is_displayed(view) && view->has_scrolled &&
7098 use_scroll_redrawwin)
7099 redrawwin(view->win);
7100 view->has_scrolled = FALSE;
7101 if (view->pipe)
7102 loading = TRUE;
7103 }
7105 /* Update the cursor position. */
7106 if (prompt_position) {
7107 getbegyx(status_win, cursor_y, cursor_x);
7108 cursor_x = prompt_position;
7109 } else {
7110 view = display[current_view];
7111 getbegyx(view->win, cursor_y, cursor_x);
7112 cursor_x = view->width - 1;
7113 cursor_y += view->lineno - view->offset;
7114 }
7115 setsyx(cursor_y, cursor_x);
7117 /* Refresh, accept single keystroke of input */
7118 doupdate();
7119 nodelay(status_win, loading);
7120 key = wgetch(status_win);
7122 /* wgetch() with nodelay() enabled returns ERR when
7123 * there's no input. */
7124 if (key == ERR) {
7126 } else if (key == KEY_RESIZE) {
7127 int height, width;
7129 getmaxyx(stdscr, height, width);
7131 wresize(status_win, 1, width);
7132 mvwin(status_win, height - 1, 0);
7133 wnoutrefresh(status_win);
7134 resize_display();
7135 redraw_display(TRUE);
7137 } else {
7138 input_mode = FALSE;
7139 return key;
7140 }
7141 }
7142 }
7144 static char *
7145 prompt_input(const char *prompt, input_handler handler, void *data)
7146 {
7147 enum input_status status = INPUT_OK;
7148 static char buf[SIZEOF_STR];
7149 size_t pos = 0;
7151 buf[pos] = 0;
7153 while (status == INPUT_OK || status == INPUT_SKIP) {
7154 int key;
7156 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7157 wclrtoeol(status_win);
7159 key = get_input(pos + 1);
7160 switch (key) {
7161 case KEY_RETURN:
7162 case KEY_ENTER:
7163 case '\n':
7164 status = pos ? INPUT_STOP : INPUT_CANCEL;
7165 break;
7167 case KEY_BACKSPACE:
7168 if (pos > 0)
7169 buf[--pos] = 0;
7170 else
7171 status = INPUT_CANCEL;
7172 break;
7174 case KEY_ESC:
7175 status = INPUT_CANCEL;
7176 break;
7178 default:
7179 if (pos >= sizeof(buf)) {
7180 report("Input string too long");
7181 return NULL;
7182 }
7184 status = handler(data, buf, key);
7185 if (status == INPUT_OK)
7186 buf[pos++] = (char) key;
7187 }
7188 }
7190 /* Clear the status window */
7191 status_empty = FALSE;
7192 report("");
7194 if (status == INPUT_CANCEL)
7195 return NULL;
7197 buf[pos++] = 0;
7199 return buf;
7200 }
7202 static enum input_status
7203 prompt_yesno_handler(void *data, char *buf, int c)
7204 {
7205 if (c == 'y' || c == 'Y')
7206 return INPUT_STOP;
7207 if (c == 'n' || c == 'N')
7208 return INPUT_CANCEL;
7209 return INPUT_SKIP;
7210 }
7212 static bool
7213 prompt_yesno(const char *prompt)
7214 {
7215 char prompt2[SIZEOF_STR];
7217 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7218 return FALSE;
7220 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7221 }
7223 static enum input_status
7224 read_prompt_handler(void *data, char *buf, int c)
7225 {
7226 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7227 }
7229 static char *
7230 read_prompt(const char *prompt)
7231 {
7232 return prompt_input(prompt, read_prompt_handler, NULL);
7233 }
7235 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7236 {
7237 enum input_status status = INPUT_OK;
7238 int size = 0;
7240 while (items[size].text)
7241 size++;
7243 while (status == INPUT_OK) {
7244 const struct menu_item *item = &items[*selected];
7245 int key;
7246 int i;
7248 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7249 prompt, *selected + 1, size);
7250 if (item->hotkey)
7251 wprintw(status_win, "[%c] ", (char) item->hotkey);
7252 wprintw(status_win, "%s", item->text);
7253 wclrtoeol(status_win);
7255 key = get_input(COLS - 1);
7256 switch (key) {
7257 case KEY_RETURN:
7258 case KEY_ENTER:
7259 case '\n':
7260 status = INPUT_STOP;
7261 break;
7263 case KEY_LEFT:
7264 case KEY_UP:
7265 *selected = *selected - 1;
7266 if (*selected < 0)
7267 *selected = size - 1;
7268 break;
7270 case KEY_RIGHT:
7271 case KEY_DOWN:
7272 *selected = (*selected + 1) % size;
7273 break;
7275 case KEY_ESC:
7276 status = INPUT_CANCEL;
7277 break;
7279 default:
7280 for (i = 0; items[i].text; i++)
7281 if (items[i].hotkey == key) {
7282 *selected = i;
7283 status = INPUT_STOP;
7284 break;
7285 }
7286 }
7287 }
7289 /* Clear the status window */
7290 status_empty = FALSE;
7291 report("");
7293 return status != INPUT_CANCEL;
7294 }
7296 /*
7297 * Repository properties
7298 */
7300 static struct ref **refs = NULL;
7301 static size_t refs_size = 0;
7302 static struct ref *refs_head = NULL;
7304 static struct ref_list **ref_lists = NULL;
7305 static size_t ref_lists_size = 0;
7307 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7308 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7309 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7311 static int
7312 compare_refs(const void *ref1_, const void *ref2_)
7313 {
7314 const struct ref *ref1 = *(const struct ref **)ref1_;
7315 const struct ref *ref2 = *(const struct ref **)ref2_;
7317 if (ref1->tag != ref2->tag)
7318 return ref2->tag - ref1->tag;
7319 if (ref1->ltag != ref2->ltag)
7320 return ref2->ltag - ref2->ltag;
7321 if (ref1->head != ref2->head)
7322 return ref2->head - ref1->head;
7323 if (ref1->tracked != ref2->tracked)
7324 return ref2->tracked - ref1->tracked;
7325 if (ref1->remote != ref2->remote)
7326 return ref2->remote - ref1->remote;
7327 return strcmp(ref1->name, ref2->name);
7328 }
7330 static void
7331 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7332 {
7333 size_t i;
7335 for (i = 0; i < refs_size; i++)
7336 if (!visitor(data, refs[i]))
7337 break;
7338 }
7340 static struct ref *
7341 get_ref_head()
7342 {
7343 return refs_head;
7344 }
7346 static struct ref_list *
7347 get_ref_list(const char *id)
7348 {
7349 struct ref_list *list;
7350 size_t i;
7352 for (i = 0; i < ref_lists_size; i++)
7353 if (!strcmp(id, ref_lists[i]->id))
7354 return ref_lists[i];
7356 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7357 return NULL;
7358 list = calloc(1, sizeof(*list));
7359 if (!list)
7360 return NULL;
7362 for (i = 0; i < refs_size; i++) {
7363 if (!strcmp(id, refs[i]->id) &&
7364 realloc_refs_list(&list->refs, list->size, 1))
7365 list->refs[list->size++] = refs[i];
7366 }
7368 if (!list->refs) {
7369 free(list);
7370 return NULL;
7371 }
7373 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7374 ref_lists[ref_lists_size++] = list;
7375 return list;
7376 }
7378 static int
7379 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7380 {
7381 struct ref *ref = NULL;
7382 bool tag = FALSE;
7383 bool ltag = FALSE;
7384 bool remote = FALSE;
7385 bool tracked = FALSE;
7386 bool head = FALSE;
7387 int from = 0, to = refs_size - 1;
7389 if (!prefixcmp(name, "refs/tags/")) {
7390 if (!suffixcmp(name, namelen, "^{}")) {
7391 namelen -= 3;
7392 name[namelen] = 0;
7393 } else {
7394 ltag = TRUE;
7395 }
7397 tag = TRUE;
7398 namelen -= STRING_SIZE("refs/tags/");
7399 name += STRING_SIZE("refs/tags/");
7401 } else if (!prefixcmp(name, "refs/remotes/")) {
7402 remote = TRUE;
7403 namelen -= STRING_SIZE("refs/remotes/");
7404 name += STRING_SIZE("refs/remotes/");
7405 tracked = !strcmp(opt_remote, name);
7407 } else if (!prefixcmp(name, "refs/heads/")) {
7408 namelen -= STRING_SIZE("refs/heads/");
7409 name += STRING_SIZE("refs/heads/");
7410 if (!strncmp(opt_head, name, namelen))
7411 return OK;
7413 } else if (!strcmp(name, "HEAD")) {
7414 head = TRUE;
7415 if (*opt_head) {
7416 namelen = strlen(opt_head);
7417 name = opt_head;
7418 }
7419 }
7421 /* If we are reloading or it's an annotated tag, replace the
7422 * previous SHA1 with the resolved commit id; relies on the fact
7423 * git-ls-remote lists the commit id of an annotated tag right
7424 * before the commit id it points to. */
7425 while (from <= to) {
7426 size_t pos = (to + from) / 2;
7427 int cmp = strcmp(name, refs[pos]->name);
7429 if (!cmp) {
7430 ref = refs[pos];
7431 break;
7432 }
7434 if (cmp < 0)
7435 to = pos - 1;
7436 else
7437 from = pos + 1;
7438 }
7440 if (!ref) {
7441 if (!realloc_refs(&refs, refs_size, 1))
7442 return ERR;
7443 ref = calloc(1, sizeof(*ref) + namelen);
7444 if (!ref)
7445 return ERR;
7446 memmove(refs + from + 1, refs + from,
7447 (refs_size - from) * sizeof(*refs));
7448 refs[from] = ref;
7449 strncpy(ref->name, name, namelen);
7450 refs_size++;
7451 }
7453 ref->head = head;
7454 ref->tag = tag;
7455 ref->ltag = ltag;
7456 ref->remote = remote;
7457 ref->tracked = tracked;
7458 string_copy_rev(ref->id, id);
7460 if (head)
7461 refs_head = ref;
7462 return OK;
7463 }
7465 static int
7466 load_refs(void)
7467 {
7468 const char *head_argv[] = {
7469 "git", "symbolic-ref", "HEAD", NULL
7470 };
7471 static const char *ls_remote_argv[SIZEOF_ARG] = {
7472 "git", "ls-remote", opt_git_dir, NULL
7473 };
7474 static bool init = FALSE;
7475 size_t i;
7477 if (!init) {
7478 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7479 die("TIG_LS_REMOTE contains too many arguments");
7480 init = TRUE;
7481 }
7483 if (!*opt_git_dir)
7484 return OK;
7486 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7487 !prefixcmp(opt_head, "refs/heads/")) {
7488 char *offset = opt_head + STRING_SIZE("refs/heads/");
7490 memmove(opt_head, offset, strlen(offset) + 1);
7491 }
7493 refs_head = NULL;
7494 for (i = 0; i < refs_size; i++)
7495 refs[i]->id[0] = 0;
7497 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7498 return ERR;
7500 /* Update the ref lists to reflect changes. */
7501 for (i = 0; i < ref_lists_size; i++) {
7502 struct ref_list *list = ref_lists[i];
7503 size_t old, new;
7505 for (old = new = 0; old < list->size; old++)
7506 if (!strcmp(list->id, list->refs[old]->id))
7507 list->refs[new++] = list->refs[old];
7508 list->size = new;
7509 }
7511 return OK;
7512 }
7514 static void
7515 set_remote_branch(const char *name, const char *value, size_t valuelen)
7516 {
7517 if (!strcmp(name, ".remote")) {
7518 string_ncopy(opt_remote, value, valuelen);
7520 } else if (*opt_remote && !strcmp(name, ".merge")) {
7521 size_t from = strlen(opt_remote);
7523 if (!prefixcmp(value, "refs/heads/"))
7524 value += STRING_SIZE("refs/heads/");
7526 if (!string_format_from(opt_remote, &from, "/%s", value))
7527 opt_remote[0] = 0;
7528 }
7529 }
7531 static void
7532 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7533 {
7534 const char *argv[SIZEOF_ARG] = { name, "=" };
7535 int argc = 1 + (cmd == option_set_command);
7536 int error = ERR;
7538 if (!argv_from_string(argv, &argc, value))
7539 config_msg = "Too many option arguments";
7540 else
7541 error = cmd(argc, argv);
7543 if (error == ERR)
7544 warn("Option 'tig.%s': %s", name, config_msg);
7545 }
7547 static bool
7548 set_environment_variable(const char *name, const char *value)
7549 {
7550 size_t len = strlen(name) + 1 + strlen(value) + 1;
7551 char *env = malloc(len);
7553 if (env &&
7554 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7555 putenv(env) == 0)
7556 return TRUE;
7557 free(env);
7558 return FALSE;
7559 }
7561 static void
7562 set_work_tree(const char *value)
7563 {
7564 char cwd[SIZEOF_STR];
7566 if (!getcwd(cwd, sizeof(cwd)))
7567 die("Failed to get cwd path: %s", strerror(errno));
7568 if (chdir(opt_git_dir) < 0)
7569 die("Failed to chdir(%s): %s", strerror(errno));
7570 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7571 die("Failed to get git path: %s", strerror(errno));
7572 if (chdir(cwd) < 0)
7573 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7574 if (chdir(value) < 0)
7575 die("Failed to chdir(%s): %s", value, strerror(errno));
7576 if (!getcwd(cwd, sizeof(cwd)))
7577 die("Failed to get cwd path: %s", strerror(errno));
7578 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7579 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7580 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7581 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7582 opt_is_inside_work_tree = TRUE;
7583 }
7585 static int
7586 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7587 {
7588 if (!strcmp(name, "i18n.commitencoding"))
7589 string_ncopy(opt_encoding, value, valuelen);
7591 else if (!strcmp(name, "core.editor"))
7592 string_ncopy(opt_editor, value, valuelen);
7594 else if (!strcmp(name, "core.worktree"))
7595 set_work_tree(value);
7597 else if (!prefixcmp(name, "tig.color."))
7598 set_repo_config_option(name + 10, value, option_color_command);
7600 else if (!prefixcmp(name, "tig.bind."))
7601 set_repo_config_option(name + 9, value, option_bind_command);
7603 else if (!prefixcmp(name, "tig."))
7604 set_repo_config_option(name + 4, value, option_set_command);
7606 else if (*opt_head && !prefixcmp(name, "branch.") &&
7607 !strncmp(name + 7, opt_head, strlen(opt_head)))
7608 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7610 return OK;
7611 }
7613 static int
7614 load_git_config(void)
7615 {
7616 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7618 return io_run_load(config_list_argv, "=", read_repo_config_option);
7619 }
7621 static int
7622 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7623 {
7624 if (!opt_git_dir[0]) {
7625 string_ncopy(opt_git_dir, name, namelen);
7627 } else if (opt_is_inside_work_tree == -1) {
7628 /* This can be 3 different values depending on the
7629 * version of git being used. If git-rev-parse does not
7630 * understand --is-inside-work-tree it will simply echo
7631 * the option else either "true" or "false" is printed.
7632 * Default to true for the unknown case. */
7633 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7635 } else if (*name == '.') {
7636 string_ncopy(opt_cdup, name, namelen);
7638 } else {
7639 string_ncopy(opt_prefix, name, namelen);
7640 }
7642 return OK;
7643 }
7645 static int
7646 load_repo_info(void)
7647 {
7648 const char *rev_parse_argv[] = {
7649 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7650 "--show-cdup", "--show-prefix", NULL
7651 };
7653 return io_run_load(rev_parse_argv, "=", read_repo_info);
7654 }
7657 /*
7658 * Main
7659 */
7661 static const char usage[] =
7662 "tig " TIG_VERSION " (" __DATE__ ")\n"
7663 "\n"
7664 "Usage: tig [options] [revs] [--] [paths]\n"
7665 " or: tig show [options] [revs] [--] [paths]\n"
7666 " or: tig blame [rev] path\n"
7667 " or: tig status\n"
7668 " or: tig < [git command output]\n"
7669 "\n"
7670 "Options:\n"
7671 " -v, --version Show version and exit\n"
7672 " -h, --help Show help message and exit";
7674 static void __NORETURN
7675 quit(int sig)
7676 {
7677 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7678 if (cursed)
7679 endwin();
7680 exit(0);
7681 }
7683 static void __NORETURN
7684 die(const char *err, ...)
7685 {
7686 va_list args;
7688 endwin();
7690 va_start(args, err);
7691 fputs("tig: ", stderr);
7692 vfprintf(stderr, err, args);
7693 fputs("\n", stderr);
7694 va_end(args);
7696 exit(1);
7697 }
7699 static void
7700 warn(const char *msg, ...)
7701 {
7702 va_list args;
7704 va_start(args, msg);
7705 fputs("tig warning: ", stderr);
7706 vfprintf(stderr, msg, args);
7707 fputs("\n", stderr);
7708 va_end(args);
7709 }
7711 static const char ***filter_args;
7713 static int
7714 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7715 {
7716 return argv_append(filter_args, name) ? OK : ERR;
7717 }
7719 static void
7720 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7721 {
7722 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7723 const char **all_argv = NULL;
7725 filter_args = args;
7726 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7727 !argv_append_array(&all_argv, argv) ||
7728 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7729 die("Failed to split arguments");
7730 argv_free(all_argv);
7731 free(all_argv);
7732 }
7734 static void
7735 filter_options(const char *argv[])
7736 {
7737 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7738 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7739 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7740 }
7742 static enum request
7743 parse_options(int argc, const char *argv[])
7744 {
7745 enum request request = REQ_VIEW_MAIN;
7746 const char *subcommand;
7747 bool seen_dashdash = FALSE;
7748 const char **filter_argv = NULL;
7749 int i;
7751 if (!isatty(STDIN_FILENO)) {
7752 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7753 return REQ_VIEW_PAGER;
7754 }
7756 if (argc <= 1)
7757 return REQ_VIEW_MAIN;
7759 subcommand = argv[1];
7760 if (!strcmp(subcommand, "status")) {
7761 if (argc > 2)
7762 warn("ignoring arguments after `%s'", subcommand);
7763 return REQ_VIEW_STATUS;
7765 } else if (!strcmp(subcommand, "blame")) {
7766 if (argc <= 2 || argc > 4)
7767 die("invalid number of options to blame\n\n%s", usage);
7769 i = 2;
7770 if (argc == 4) {
7771 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7772 i++;
7773 }
7775 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7776 return REQ_VIEW_BLAME;
7778 } else if (!strcmp(subcommand, "show")) {
7779 request = REQ_VIEW_DIFF;
7781 } else {
7782 subcommand = NULL;
7783 }
7785 for (i = 1 + !!subcommand; i < argc; i++) {
7786 const char *opt = argv[i];
7788 if (seen_dashdash) {
7789 argv_append(&opt_file_args, opt);
7790 continue;
7792 } else if (!strcmp(opt, "--")) {
7793 seen_dashdash = TRUE;
7794 continue;
7796 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7797 printf("tig version %s\n", TIG_VERSION);
7798 quit(0);
7800 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7801 printf("%s\n", usage);
7802 quit(0);
7804 } else if (!strcmp(opt, "--all")) {
7805 argv_append(&opt_rev_args, opt);
7806 continue;
7807 }
7809 if (!argv_append(&filter_argv, opt))
7810 die("command too long");
7811 }
7813 if (filter_argv)
7814 filter_options(filter_argv);
7816 return request;
7817 }
7819 int
7820 main(int argc, const char *argv[])
7821 {
7822 const char *codeset = "UTF-8";
7823 enum request request = parse_options(argc, argv);
7824 struct view *view;
7825 size_t i;
7827 signal(SIGINT, quit);
7828 signal(SIGPIPE, SIG_IGN);
7830 if (setlocale(LC_ALL, "")) {
7831 codeset = nl_langinfo(CODESET);
7832 }
7834 if (load_repo_info() == ERR)
7835 die("Failed to load repo info.");
7837 if (load_options() == ERR)
7838 die("Failed to load user config.");
7840 if (load_git_config() == ERR)
7841 die("Failed to load repo config.");
7843 /* Require a git repository unless when running in pager mode. */
7844 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7845 die("Not a git repository");
7847 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7848 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7849 if (opt_iconv_in == ICONV_NONE)
7850 die("Failed to initialize character set conversion");
7851 }
7853 if (codeset && strcmp(codeset, "UTF-8")) {
7854 opt_iconv_out = iconv_open(codeset, "UTF-8");
7855 if (opt_iconv_out == ICONV_NONE)
7856 die("Failed to initialize character set conversion");
7857 }
7859 if (load_refs() == ERR)
7860 die("Failed to load refs.");
7862 foreach_view (view, i)
7863 if (!argv_from_env(view->ops->argv, view->cmd_env))
7864 die("Too many arguments in the `%s` environment variable",
7865 view->cmd_env);
7867 init_display();
7869 while (view_driver(display[current_view], request)) {
7870 int key = get_input(0);
7872 view = display[current_view];
7873 request = get_keybinding(view->keymap, key);
7875 /* Some low-level request handling. This keeps access to
7876 * status_win restricted. */
7877 switch (request) {
7878 case REQ_NONE:
7879 report("Unknown key, press %s for help",
7880 get_key(view->keymap, REQ_VIEW_HELP));
7881 break;
7882 case REQ_PROMPT:
7883 {
7884 char *cmd = read_prompt(":");
7886 if (cmd && isdigit(*cmd)) {
7887 int lineno = view->lineno + 1;
7889 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7890 select_view_line(view, lineno - 1);
7891 report("");
7892 } else {
7893 report("Unable to parse '%s' as a line number", cmd);
7894 }
7896 } else if (cmd) {
7897 struct view *next = VIEW(REQ_VIEW_PAGER);
7898 const char *argv[SIZEOF_ARG] = { "git" };
7899 int argc = 1;
7901 /* When running random commands, initially show the
7902 * command in the title. However, it maybe later be
7903 * overwritten if a commit line is selected. */
7904 string_ncopy(next->ref, cmd, strlen(cmd));
7906 if (!argv_from_string(argv, &argc, cmd)) {
7907 report("Too many arguments");
7908 } else if (!prepare_update(next, argv, NULL)) {
7909 report("Failed to format command");
7910 } else {
7911 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7912 }
7913 }
7915 request = REQ_NONE;
7916 break;
7917 }
7918 case REQ_SEARCH:
7919 case REQ_SEARCH_BACK:
7920 {
7921 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7922 char *search = read_prompt(prompt);
7924 if (search)
7925 string_ncopy(opt_search, search, strlen(search));
7926 else if (*opt_search)
7927 request = request == REQ_SEARCH ?
7928 REQ_FIND_NEXT :
7929 REQ_FIND_PREV;
7930 else
7931 request = REQ_NONE;
7932 break;
7933 }
7934 default:
7935 break;
7936 }
7937 }
7939 quit(0);
7941 return 0;
7942 }