1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_NONE /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151 INPUT_OK,
152 INPUT_SKIP,
153 INPUT_STOP,
154 INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163 int hotkey;
164 const char *text;
165 void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171 * Allocation helpers ... Entering macro hell to never be seen again.
172 */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
175 static type * \
176 name(type **mem, size_t size, size_t increase) \
177 { \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180 type *tmp = *mem; \
181 \
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184 if (tmp) \
185 *mem = tmp; \
186 } \
187 \
188 return tmp; \
189 }
191 /*
192 * String helpers
193 */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
197 {
198 if (srclen > dstlen - 1)
199 srclen = dstlen - 1;
201 strncpy(dst, src, srclen);
202 dst[srclen] = 0;
203 }
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
221 {
222 size_t size, pos;
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
231 size += expanded;
232 } else {
233 dst[size++] = src[pos];
234 }
235 }
237 dst[size] = 0;
238 }
240 static char *
241 chomp_string(char *name)
242 {
243 int namelen;
245 while (isspace(*name))
246 name++;
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
250 name[namelen--] = 0;
252 return name;
253 }
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
257 {
258 va_list args;
259 size_t pos = bufpos ? *bufpos : 0;
261 va_start(args, fmt);
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 va_end(args);
265 if (bufpos)
266 *bufpos = pos;
268 return pos >= bufsize ? FALSE : TRUE;
269 }
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
279 {
280 size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
287 continue;
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
291 continue;
293 return str1[i] - str2[i];
294 }
296 return 0;
297 }
299 #define enum_equals(entry, str, len) \
300 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
312 {
313 static char buf[SIZEOF_STR];
314 int bufpos;
316 for (bufpos = 0; bufpos <= namelen; bufpos++) {
317 buf[bufpos] = tolower(name[bufpos]);
318 if (buf[bufpos] == '_')
319 buf[bufpos] = '-';
320 }
322 buf[bufpos] = 0;
323 return buf;
324 }
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
330 {
331 size_t namelen = strlen(name);
332 int i;
334 for (i = 0; i < map_size; i++)
335 if (enum_equals(map[i], name, namelen)) {
336 *value = map[i].value;
337 return TRUE;
338 }
340 return FALSE;
341 }
343 #define map_enum(attr, map, name) \
344 map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347 strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
351 {
352 size_t len = slen >= 0 ? slen : strlen(str);
353 size_t suffixlen = strlen(suffix);
355 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
356 }
359 /*
360 * Unicode / UTF-8 handling
361 *
362 * NOTE: Much of the following code for dealing with Unicode is derived from
363 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365 */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
369 {
370 if (c >= 0x1100 &&
371 (c <= 0x115f /* Hangul Jamo */
372 || c == 0x2329
373 || c == 0x232a
374 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
375 /* CJK ... Yi */
376 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
377 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
378 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
379 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
380 || (c >= 0xffe0 && c <= 0xffe6)
381 || (c >= 0x20000 && c <= 0x2fffd)
382 || (c >= 0x30000 && c <= 0x3fffd)))
383 return 2;
385 if (c == '\t')
386 return tab_size;
388 return 1;
389 }
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392 * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
406 {
407 int c = *(unsigned char *) string;
409 return utf8_bytes[c];
410 }
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
415 {
416 unsigned long unicode;
418 switch (length) {
419 case 1:
420 unicode = string[0];
421 break;
422 case 2:
423 unicode = (string[0] & 0x1f) << 6;
424 unicode += (string[1] & 0x3f);
425 break;
426 case 3:
427 unicode = (string[0] & 0x0f) << 12;
428 unicode += ((string[1] & 0x3f) << 6);
429 unicode += (string[2] & 0x3f);
430 break;
431 case 4:
432 unicode = (string[0] & 0x0f) << 18;
433 unicode += ((string[1] & 0x3f) << 12);
434 unicode += ((string[2] & 0x3f) << 6);
435 unicode += (string[3] & 0x3f);
436 break;
437 case 5:
438 unicode = (string[0] & 0x0f) << 24;
439 unicode += ((string[1] & 0x3f) << 18);
440 unicode += ((string[2] & 0x3f) << 12);
441 unicode += ((string[3] & 0x3f) << 6);
442 unicode += (string[4] & 0x3f);
443 break;
444 case 6:
445 unicode = (string[0] & 0x01) << 30;
446 unicode += ((string[1] & 0x3f) << 24);
447 unicode += ((string[2] & 0x3f) << 18);
448 unicode += ((string[3] & 0x3f) << 12);
449 unicode += ((string[4] & 0x3f) << 6);
450 unicode += (string[5] & 0x3f);
451 break;
452 default:
453 return 0;
454 }
456 /* Invalid characters could return the special 0xfffd value but NUL
457 * should be just as good. */
458 return unicode > 0xffff ? 0 : unicode;
459 }
461 /* Calculates how much of string can be shown within the given maximum width
462 * and sets trimmed parameter to non-zero value if all of string could not be
463 * shown. If the reserve flag is TRUE, it will reserve at least one
464 * trailing character, which can be useful when drawing a delimiter.
465 *
466 * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
469 {
470 const char *string = *start;
471 const char *end = strchr(string, '\0');
472 unsigned char last_bytes = 0;
473 size_t last_ucwidth = 0;
475 *width = 0;
476 *trimmed = 0;
478 while (string < end) {
479 unsigned char bytes = utf8_char_length(string, end);
480 size_t ucwidth;
481 unsigned long unicode;
483 if (string + bytes > end)
484 break;
486 /* Change representation to figure out whether
487 * it is a single- or double-width character. */
489 unicode = utf8_to_unicode(string, bytes);
490 /* FIXME: Graceful handling of invalid Unicode character. */
491 if (!unicode)
492 break;
494 ucwidth = unicode_width(unicode, tab_size);
495 if (skip > 0) {
496 skip -= ucwidth <= skip ? ucwidth : skip;
497 *start += bytes;
498 }
499 *width += ucwidth;
500 if (*width > max_width) {
501 *trimmed = 1;
502 *width -= ucwidth;
503 if (reserve && *width == max_width) {
504 string -= last_bytes;
505 *width -= last_ucwidth;
506 }
507 break;
508 }
510 string += bytes;
511 last_bytes = ucwidth ? bytes : 0;
512 last_ucwidth = ucwidth;
513 }
515 return string - *start;
516 }
519 #define DATE_INFO \
520 DATE_(NO), \
521 DATE_(DEFAULT), \
522 DATE_(LOCAL), \
523 DATE_(RELATIVE), \
524 DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528 DATE_INFO
529 #undef DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534 DATE_INFO
535 #undef DATE_
536 };
538 struct time {
539 time_t sec;
540 int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
544 {
545 return t1->sec - t2->sec;
546 }
548 static const char *
549 mkdate(const struct time *time, enum date date)
550 {
551 static char buf[DATE_COLS + 1];
552 static const struct enum_map reldate[] = {
553 { "second", 1, 60 * 2 },
554 { "minute", 60, 60 * 60 * 2 },
555 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
556 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
557 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
558 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
559 };
560 struct tm tm;
562 if (!date || !time || !time->sec)
563 return "";
565 if (date == DATE_RELATIVE) {
566 struct timeval now;
567 time_t date = time->sec + time->tz;
568 time_t seconds;
569 int i;
571 gettimeofday(&now, NULL);
572 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574 if (seconds >= reldate[i].value)
575 continue;
577 seconds /= reldate[i].namelen;
578 if (!string_format(buf, "%ld %s%s %s",
579 seconds, reldate[i].name,
580 seconds > 1 ? "s" : "",
581 now.tv_sec >= date ? "ago" : "ahead"))
582 break;
583 return buf;
584 }
585 }
587 if (date == DATE_LOCAL) {
588 time_t date = time->sec + time->tz;
589 localtime_r(&date, &tm);
590 }
591 else {
592 gmtime_r(&time->sec, &tm);
593 }
594 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
595 }
598 #define AUTHOR_VALUES \
599 AUTHOR_(NO), \
600 AUTHOR_(FULL), \
601 AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605 AUTHOR_VALUES,
606 #undef AUTHOR_
607 AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612 AUTHOR_VALUES
613 #undef AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
618 {
619 static char initials[AUTHOR_COLS * 6 + 1];
620 size_t pos = 0;
621 const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625 memset(initials, 0, sizeof(initials));
626 while (author < end) {
627 unsigned char bytes;
628 size_t i;
630 while (is_initial_sep(*author))
631 author++;
633 bytes = utf8_char_length(author, end);
634 if (bytes < sizeof(initials) - 1 - pos) {
635 while (bytes--) {
636 initials[pos++] = *author++;
637 }
638 }
640 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641 if (i < sizeof(initials) - 1)
642 initials[i++] = *author;
643 }
645 initials[i++] = 0;
646 }
648 return initials;
649 }
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
654 {
655 int valuelen;
657 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658 bool advance = cmd[valuelen] != 0;
660 cmd[valuelen] = 0;
661 argv[(*argc)++] = chomp_string(cmd);
662 cmd = chomp_string(cmd + valuelen + advance);
663 }
665 if (*argc < SIZEOF_ARG)
666 argv[*argc] = NULL;
667 return *argc < SIZEOF_ARG;
668 }
670 static bool
671 argv_from_env(const char **argv, const char *name)
672 {
673 char *env = argv ? getenv(name) : NULL;
674 int argc = 0;
676 if (env && *env)
677 env = strdup(env);
678 return !env || argv_from_string(argv, &argc, env);
679 }
681 static void
682 argv_free(const char *argv[])
683 {
684 int argc;
686 for (argc = 0; argv[argc]; argc++)
687 free((void *) argv[argc]);
688 argv[0] = NULL;
689 }
691 static bool
692 argv_copy(const char *dst[], const char *src[], bool allocate)
693 {
694 int argc;
696 for (argc = 0; src[argc]; argc++)
697 if (!(dst[argc] = allocate ? strdup(src[argc]) : src[argc]))
698 return FALSE;
699 return TRUE;
700 }
703 /*
704 * Executing external commands.
705 */
707 enum io_type {
708 IO_FD, /* File descriptor based IO. */
709 IO_BG, /* Execute command in the background. */
710 IO_FG, /* Execute command with same std{in,out,err}. */
711 IO_RD, /* Read only fork+exec IO. */
712 IO_WR, /* Write only fork+exec IO. */
713 IO_AP, /* Append fork+exec output to file. */
714 };
716 struct io {
717 enum io_type type; /* The requested type of pipe. */
718 const char *dir; /* Directory from which to execute. */
719 pid_t pid; /* PID of spawned process. */
720 int pipe; /* Pipe end for reading or writing. */
721 int error; /* Error status. */
722 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
723 char *buf; /* Read buffer. */
724 size_t bufalloc; /* Allocated buffer size. */
725 size_t bufsize; /* Buffer content size. */
726 char *bufpos; /* Current buffer position. */
727 unsigned int eof:1; /* Has end of file been reached. */
728 };
730 static void
731 io_reset(struct io *io)
732 {
733 io->pipe = -1;
734 io->pid = 0;
735 io->buf = io->bufpos = NULL;
736 io->bufalloc = io->bufsize = 0;
737 io->error = 0;
738 io->eof = 0;
739 }
741 static void
742 io_init(struct io *io, const char *dir, enum io_type type)
743 {
744 io_reset(io);
745 io->type = type;
746 io->dir = dir;
747 }
749 static void
750 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
751 {
752 io_init(io, dir, type);
753 argv_copy(io->argv, argv, FALSE);
754 }
756 static bool
757 io_format(struct io *io, const char *dir, enum io_type type,
758 const char *argv[], enum format_flags flags)
759 {
760 io_init(io, dir, type);
761 return format_argv(io->argv, argv, flags);
762 }
764 static bool
765 io_open(struct io *io, const char *fmt, ...)
766 {
767 char name[SIZEOF_STR] = "";
768 bool fits;
769 va_list args;
771 io_init(io, NULL, IO_FD);
773 va_start(args, fmt);
774 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
775 va_end(args);
777 if (!fits) {
778 io->error = ENAMETOOLONG;
779 return FALSE;
780 }
781 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
782 if (io->pipe == -1)
783 io->error = errno;
784 return io->pipe != -1;
785 }
787 static bool
788 io_kill(struct io *io)
789 {
790 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
791 }
793 static bool
794 io_done(struct io *io)
795 {
796 pid_t pid = io->pid;
798 if (io->pipe != -1)
799 close(io->pipe);
800 free(io->buf);
801 io_reset(io);
803 while (pid > 0) {
804 int status;
805 pid_t waiting = waitpid(pid, &status, 0);
807 if (waiting < 0) {
808 if (errno == EINTR)
809 continue;
810 io->error = errno;
811 return FALSE;
812 }
814 return waiting == pid &&
815 !WIFSIGNALED(status) &&
816 WIFEXITED(status) &&
817 !WEXITSTATUS(status);
818 }
820 return TRUE;
821 }
823 static bool
824 io_start(struct io *io)
825 {
826 int pipefds[2] = { -1, -1 };
828 if (io->type == IO_FD)
829 return TRUE;
831 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
832 io->error = errno;
833 return FALSE;
834 } else if (io->type == IO_AP) {
835 pipefds[1] = io->pipe;
836 }
838 if ((io->pid = fork())) {
839 if (io->pid == -1)
840 io->error = errno;
841 if (pipefds[!(io->type == IO_WR)] != -1)
842 close(pipefds[!(io->type == IO_WR)]);
843 if (io->pid != -1) {
844 io->pipe = pipefds[!!(io->type == IO_WR)];
845 return TRUE;
846 }
848 } else {
849 if (io->type != IO_FG) {
850 int devnull = open("/dev/null", O_RDWR);
851 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
852 int writefd = (io->type == IO_RD || io->type == IO_AP)
853 ? pipefds[1] : devnull;
855 dup2(readfd, STDIN_FILENO);
856 dup2(writefd, STDOUT_FILENO);
857 dup2(devnull, STDERR_FILENO);
859 close(devnull);
860 if (pipefds[0] != -1)
861 close(pipefds[0]);
862 if (pipefds[1] != -1)
863 close(pipefds[1]);
864 }
866 if (io->dir && *io->dir && chdir(io->dir) == -1)
867 exit(errno);
869 execvp(io->argv[0], (char *const*) io->argv);
870 exit(errno);
871 }
873 if (pipefds[!!(io->type == IO_WR)] != -1)
874 close(pipefds[!!(io->type == IO_WR)]);
875 return FALSE;
876 }
878 static bool
879 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
880 {
881 io_prepare(io, dir, type, argv);
882 return io_start(io);
883 }
885 static bool
886 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
887 {
888 struct io io = {};
890 io_prepare(&io, dir, type, argv);
891 io.pipe = fd;
892 return io_start(&io) && io_done(&io);
893 }
895 static bool
896 io_run_bg(const char **argv)
897 {
898 return io_complete(IO_BG, argv, NULL, -1);
899 }
901 static bool
902 io_run_fg(const char **argv, const char *dir)
903 {
904 return io_complete(IO_FG, argv, dir, -1);
905 }
907 static bool
908 io_run_append(const char **argv, int fd)
909 {
910 return io_complete(IO_AP, argv, NULL, -1);
911 }
913 static bool
914 io_eof(struct io *io)
915 {
916 return io->eof;
917 }
919 static int
920 io_error(struct io *io)
921 {
922 return io->error;
923 }
925 static char *
926 io_strerror(struct io *io)
927 {
928 return strerror(io->error);
929 }
931 static bool
932 io_can_read(struct io *io)
933 {
934 struct timeval tv = { 0, 500 };
935 fd_set fds;
937 FD_ZERO(&fds);
938 FD_SET(io->pipe, &fds);
940 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
941 }
943 static ssize_t
944 io_read(struct io *io, void *buf, size_t bufsize)
945 {
946 do {
947 ssize_t readsize = read(io->pipe, buf, bufsize);
949 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
950 continue;
951 else if (readsize == -1)
952 io->error = errno;
953 else if (readsize == 0)
954 io->eof = 1;
955 return readsize;
956 } while (1);
957 }
959 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
961 static char *
962 io_get(struct io *io, int c, bool can_read)
963 {
964 char *eol;
965 ssize_t readsize;
967 while (TRUE) {
968 if (io->bufsize > 0) {
969 eol = memchr(io->bufpos, c, io->bufsize);
970 if (eol) {
971 char *line = io->bufpos;
973 *eol = 0;
974 io->bufpos = eol + 1;
975 io->bufsize -= io->bufpos - line;
976 return line;
977 }
978 }
980 if (io_eof(io)) {
981 if (io->bufsize) {
982 io->bufpos[io->bufsize] = 0;
983 io->bufsize = 0;
984 return io->bufpos;
985 }
986 return NULL;
987 }
989 if (!can_read)
990 return NULL;
992 if (io->bufsize > 0 && io->bufpos > io->buf)
993 memmove(io->buf, io->bufpos, io->bufsize);
995 if (io->bufalloc == io->bufsize) {
996 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
997 return NULL;
998 io->bufalloc += BUFSIZ;
999 }
1001 io->bufpos = io->buf;
1002 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1003 if (io_error(io))
1004 return NULL;
1005 io->bufsize += readsize;
1006 }
1007 }
1009 static bool
1010 io_write(struct io *io, const void *buf, size_t bufsize)
1011 {
1012 size_t written = 0;
1014 while (!io_error(io) && written < bufsize) {
1015 ssize_t size;
1017 size = write(io->pipe, buf + written, bufsize - written);
1018 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1019 continue;
1020 else if (size == -1)
1021 io->error = errno;
1022 else
1023 written += size;
1024 }
1026 return written == bufsize;
1027 }
1029 static bool
1030 io_read_buf(struct io *io, char buf[], size_t bufsize)
1031 {
1032 char *result = io_get(io, '\n', TRUE);
1034 if (result) {
1035 result = chomp_string(result);
1036 string_ncopy_do(buf, bufsize, result, strlen(result));
1037 }
1039 return io_done(io) && result;
1040 }
1042 static bool
1043 io_run_buf(const char **argv, char buf[], size_t bufsize)
1044 {
1045 struct io io = {};
1047 io_prepare(&io, NULL, IO_RD, argv);
1048 return io_start(&io) && io_read_buf(&io, buf, bufsize);
1049 }
1051 static int
1052 io_load(struct io *io, const char *separators,
1053 int (*read_property)(char *, size_t, char *, size_t))
1054 {
1055 char *name;
1056 int state = OK;
1058 if (!io_start(io))
1059 return ERR;
1061 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1062 char *value;
1063 size_t namelen;
1064 size_t valuelen;
1066 name = chomp_string(name);
1067 namelen = strcspn(name, separators);
1069 if (name[namelen]) {
1070 name[namelen] = 0;
1071 value = chomp_string(name + namelen + 1);
1072 valuelen = strlen(value);
1074 } else {
1075 value = "";
1076 valuelen = 0;
1077 }
1079 state = read_property(name, namelen, value, valuelen);
1080 }
1082 if (state != ERR && io_error(io))
1083 state = ERR;
1084 io_done(io);
1086 return state;
1087 }
1089 static int
1090 io_run_load(const char **argv, const char *separators,
1091 int (*read_property)(char *, size_t, char *, size_t))
1092 {
1093 struct io io = {};
1095 io_prepare(&io, NULL, IO_RD, argv);
1096 return io_load(&io, separators, read_property);
1097 }
1100 /*
1101 * User requests
1102 */
1104 #define REQ_INFO \
1105 /* XXX: Keep the view request first and in sync with views[]. */ \
1106 REQ_GROUP("View switching") \
1107 REQ_(VIEW_MAIN, "Show main view"), \
1108 REQ_(VIEW_DIFF, "Show diff view"), \
1109 REQ_(VIEW_LOG, "Show log view"), \
1110 REQ_(VIEW_TREE, "Show tree view"), \
1111 REQ_(VIEW_BLOB, "Show blob view"), \
1112 REQ_(VIEW_BLAME, "Show blame view"), \
1113 REQ_(VIEW_BRANCH, "Show branch view"), \
1114 REQ_(VIEW_HELP, "Show help page"), \
1115 REQ_(VIEW_PAGER, "Show pager view"), \
1116 REQ_(VIEW_STATUS, "Show status view"), \
1117 REQ_(VIEW_STAGE, "Show stage view"), \
1118 \
1119 REQ_GROUP("View manipulation") \
1120 REQ_(ENTER, "Enter current line and scroll"), \
1121 REQ_(NEXT, "Move to next"), \
1122 REQ_(PREVIOUS, "Move to previous"), \
1123 REQ_(PARENT, "Move to parent"), \
1124 REQ_(VIEW_NEXT, "Move focus to next view"), \
1125 REQ_(REFRESH, "Reload and refresh"), \
1126 REQ_(MAXIMIZE, "Maximize the current view"), \
1127 REQ_(VIEW_CLOSE, "Close the current view"), \
1128 REQ_(QUIT, "Close all views and quit"), \
1129 \
1130 REQ_GROUP("View specific requests") \
1131 REQ_(STATUS_UPDATE, "Update file status"), \
1132 REQ_(STATUS_REVERT, "Revert file changes"), \
1133 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1134 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1135 \
1136 REQ_GROUP("Cursor navigation") \
1137 REQ_(MOVE_UP, "Move cursor one line up"), \
1138 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1139 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1140 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1141 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1142 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1143 \
1144 REQ_GROUP("Scrolling") \
1145 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1146 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1147 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1148 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1149 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1150 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1151 \
1152 REQ_GROUP("Searching") \
1153 REQ_(SEARCH, "Search the view"), \
1154 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1155 REQ_(FIND_NEXT, "Find next search match"), \
1156 REQ_(FIND_PREV, "Find previous search match"), \
1157 \
1158 REQ_GROUP("Option manipulation") \
1159 REQ_(OPTIONS, "Open option menu"), \
1160 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1161 REQ_(TOGGLE_DATE, "Toggle date display"), \
1162 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1163 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1164 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1165 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1166 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1167 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1168 \
1169 REQ_GROUP("Misc") \
1170 REQ_(PROMPT, "Bring up the prompt"), \
1171 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1172 REQ_(SHOW_VERSION, "Show version information"), \
1173 REQ_(STOP_LOADING, "Stop all loading views"), \
1174 REQ_(EDIT, "Open in editor"), \
1175 REQ_(NONE, "Do nothing")
1178 /* User action requests. */
1179 enum request {
1180 #define REQ_GROUP(help)
1181 #define REQ_(req, help) REQ_##req
1183 /* Offset all requests to avoid conflicts with ncurses getch values. */
1184 REQ_UNKNOWN = KEY_MAX + 1,
1185 REQ_OFFSET,
1186 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1190 };
1192 struct request_info {
1193 enum request request;
1194 const char *name;
1195 int namelen;
1196 const char *help;
1197 };
1199 static const struct request_info req_info[] = {
1200 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1201 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1202 REQ_INFO
1203 #undef REQ_GROUP
1204 #undef REQ_
1205 };
1207 static enum request
1208 get_request(const char *name)
1209 {
1210 int namelen = strlen(name);
1211 int i;
1213 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1214 if (enum_equals(req_info[i], name, namelen))
1215 return req_info[i].request;
1217 return REQ_UNKNOWN;
1218 }
1221 /*
1222 * Options
1223 */
1225 /* Option and state variables. */
1226 static enum date opt_date = DATE_DEFAULT;
1227 static enum author opt_author = AUTHOR_DEFAULT;
1228 static bool opt_line_number = FALSE;
1229 static bool opt_line_graphics = TRUE;
1230 static bool opt_rev_graph = FALSE;
1231 static bool opt_show_refs = TRUE;
1232 static int opt_num_interval = 5;
1233 static double opt_hscroll = 0.50;
1234 static double opt_scale_split_view = 2.0 / 3.0;
1235 static int opt_tab_size = 8;
1236 static int opt_author_cols = AUTHOR_COLS;
1237 static char opt_path[SIZEOF_STR] = "";
1238 static char opt_file[SIZEOF_STR] = "";
1239 static char opt_ref[SIZEOF_REF] = "";
1240 static char opt_head[SIZEOF_REF] = "";
1241 static char opt_remote[SIZEOF_REF] = "";
1242 static char opt_encoding[20] = "UTF-8";
1243 static iconv_t opt_iconv_in = ICONV_NONE;
1244 static iconv_t opt_iconv_out = ICONV_NONE;
1245 static char opt_search[SIZEOF_STR] = "";
1246 static char opt_cdup[SIZEOF_STR] = "";
1247 static char opt_prefix[SIZEOF_STR] = "";
1248 static char opt_git_dir[SIZEOF_STR] = "";
1249 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1250 static char opt_editor[SIZEOF_STR] = "";
1251 static FILE *opt_tty = NULL;
1253 #define is_initial_commit() (!get_ref_head())
1254 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1257 /*
1258 * Line-oriented content detection.
1259 */
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1276 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1277 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1283 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1284 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1286 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1291 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1294 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1295 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1297 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1298 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1300 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1301 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1310 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1311 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1312 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1313 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1314 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1315 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1317 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1318 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1319 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1320 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324 LINE_##type
1325 LINE_INFO,
1326 LINE_NONE
1327 #undef LINE
1328 };
1330 struct line_info {
1331 const char *name; /* Option name. */
1332 int namelen; /* Size of option name. */
1333 const char *line; /* The start of line to match. */
1334 int linelen; /* Size of string to match. */
1335 int fg, bg, attr; /* Color and text attributes for the lines. */
1336 };
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341 LINE_INFO
1342 #undef LINE
1343 };
1345 static enum line_type
1346 get_line_type(const char *line)
1347 {
1348 int linelen = strlen(line);
1349 enum line_type type;
1351 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352 /* Case insensitive search matches Signed-off-by lines better. */
1353 if (linelen >= line_info[type].linelen &&
1354 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355 return type;
1357 return LINE_DEFAULT;
1358 }
1360 static inline int
1361 get_line_attr(enum line_type type)
1362 {
1363 assert(type < ARRAY_SIZE(line_info));
1364 return COLOR_PAIR(type) | line_info[type].attr;
1365 }
1367 static struct line_info *
1368 get_line_info(const char *name)
1369 {
1370 size_t namelen = strlen(name);
1371 enum line_type type;
1373 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374 if (enum_equals(line_info[type], name, namelen))
1375 return &line_info[type];
1377 return NULL;
1378 }
1380 static void
1381 init_colors(void)
1382 {
1383 int default_bg = line_info[LINE_DEFAULT].bg;
1384 int default_fg = line_info[LINE_DEFAULT].fg;
1385 enum line_type type;
1387 start_color();
1389 if (assume_default_colors(default_fg, default_bg) == ERR) {
1390 default_bg = COLOR_BLACK;
1391 default_fg = COLOR_WHITE;
1392 }
1394 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395 struct line_info *info = &line_info[type];
1396 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399 init_pair(type, fg, bg);
1400 }
1401 }
1403 struct line {
1404 enum line_type type;
1406 /* State flags */
1407 unsigned int selected:1;
1408 unsigned int dirty:1;
1409 unsigned int cleareol:1;
1410 unsigned int other:16;
1412 void *data; /* User data */
1413 };
1416 /*
1417 * Keys
1418 */
1420 struct keybinding {
1421 int alias;
1422 enum request request;
1423 };
1425 static struct keybinding default_keybindings[] = {
1426 /* View switching */
1427 { 'm', REQ_VIEW_MAIN },
1428 { 'd', REQ_VIEW_DIFF },
1429 { 'l', REQ_VIEW_LOG },
1430 { 't', REQ_VIEW_TREE },
1431 { 'f', REQ_VIEW_BLOB },
1432 { 'B', REQ_VIEW_BLAME },
1433 { 'H', REQ_VIEW_BRANCH },
1434 { 'p', REQ_VIEW_PAGER },
1435 { 'h', REQ_VIEW_HELP },
1436 { 'S', REQ_VIEW_STATUS },
1437 { 'c', REQ_VIEW_STAGE },
1439 /* View manipulation */
1440 { 'q', REQ_VIEW_CLOSE },
1441 { KEY_TAB, REQ_VIEW_NEXT },
1442 { KEY_RETURN, REQ_ENTER },
1443 { KEY_UP, REQ_PREVIOUS },
1444 { KEY_DOWN, REQ_NEXT },
1445 { 'R', REQ_REFRESH },
1446 { KEY_F(5), REQ_REFRESH },
1447 { 'O', REQ_MAXIMIZE },
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP },
1451 { 'j', REQ_MOVE_DOWN },
1452 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1453 { KEY_END, REQ_MOVE_LAST_LINE },
1454 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1455 { ' ', REQ_MOVE_PAGE_DOWN },
1456 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1457 { 'b', REQ_MOVE_PAGE_UP },
1458 { '-', REQ_MOVE_PAGE_UP },
1460 /* Scrolling */
1461 { KEY_LEFT, REQ_SCROLL_LEFT },
1462 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1463 { KEY_IC, REQ_SCROLL_LINE_UP },
1464 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1465 { 'w', REQ_SCROLL_PAGE_UP },
1466 { 's', REQ_SCROLL_PAGE_DOWN },
1468 /* Searching */
1469 { '/', REQ_SEARCH },
1470 { '?', REQ_SEARCH_BACK },
1471 { 'n', REQ_FIND_NEXT },
1472 { 'N', REQ_FIND_PREV },
1474 /* Misc */
1475 { 'Q', REQ_QUIT },
1476 { 'z', REQ_STOP_LOADING },
1477 { 'v', REQ_SHOW_VERSION },
1478 { 'r', REQ_SCREEN_REDRAW },
1479 { 'o', REQ_OPTIONS },
1480 { '.', REQ_TOGGLE_LINENO },
1481 { 'D', REQ_TOGGLE_DATE },
1482 { 'A', REQ_TOGGLE_AUTHOR },
1483 { 'g', REQ_TOGGLE_REV_GRAPH },
1484 { 'F', REQ_TOGGLE_REFS },
1485 { 'I', REQ_TOGGLE_SORT_ORDER },
1486 { 'i', REQ_TOGGLE_SORT_FIELD },
1487 { ':', REQ_PROMPT },
1488 { 'u', REQ_STATUS_UPDATE },
1489 { '!', REQ_STATUS_REVERT },
1490 { 'M', REQ_STATUS_MERGE },
1491 { '@', REQ_STAGE_NEXT },
1492 { ',', REQ_PARENT },
1493 { 'e', REQ_EDIT },
1494 };
1496 #define KEYMAP_INFO \
1497 KEYMAP_(GENERIC), \
1498 KEYMAP_(MAIN), \
1499 KEYMAP_(DIFF), \
1500 KEYMAP_(LOG), \
1501 KEYMAP_(TREE), \
1502 KEYMAP_(BLOB), \
1503 KEYMAP_(BLAME), \
1504 KEYMAP_(BRANCH), \
1505 KEYMAP_(PAGER), \
1506 KEYMAP_(HELP), \
1507 KEYMAP_(STATUS), \
1508 KEYMAP_(STAGE)
1510 enum keymap {
1511 #define KEYMAP_(name) KEYMAP_##name
1512 KEYMAP_INFO
1513 #undef KEYMAP_
1514 };
1516 static const struct enum_map keymap_table[] = {
1517 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1518 KEYMAP_INFO
1519 #undef KEYMAP_
1520 };
1522 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1524 struct keybinding_table {
1525 struct keybinding *data;
1526 size_t size;
1527 };
1529 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1531 static void
1532 add_keybinding(enum keymap keymap, enum request request, int key)
1533 {
1534 struct keybinding_table *table = &keybindings[keymap];
1535 size_t i;
1537 for (i = 0; i < keybindings[keymap].size; i++) {
1538 if (keybindings[keymap].data[i].alias == key) {
1539 keybindings[keymap].data[i].request = request;
1540 return;
1541 }
1542 }
1544 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1545 if (!table->data)
1546 die("Failed to allocate keybinding");
1547 table->data[table->size].alias = key;
1548 table->data[table->size++].request = request;
1550 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1551 int i;
1553 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1554 if (default_keybindings[i].alias == key)
1555 default_keybindings[i].request = REQ_NONE;
1556 }
1557 }
1559 /* Looks for a key binding first in the given map, then in the generic map, and
1560 * lastly in the default keybindings. */
1561 static enum request
1562 get_keybinding(enum keymap keymap, int key)
1563 {
1564 size_t i;
1566 for (i = 0; i < keybindings[keymap].size; i++)
1567 if (keybindings[keymap].data[i].alias == key)
1568 return keybindings[keymap].data[i].request;
1570 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1571 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1572 return keybindings[KEYMAP_GENERIC].data[i].request;
1574 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1575 if (default_keybindings[i].alias == key)
1576 return default_keybindings[i].request;
1578 return (enum request) key;
1579 }
1582 struct key {
1583 const char *name;
1584 int value;
1585 };
1587 static const struct key key_table[] = {
1588 { "Enter", KEY_RETURN },
1589 { "Space", ' ' },
1590 { "Backspace", KEY_BACKSPACE },
1591 { "Tab", KEY_TAB },
1592 { "Escape", KEY_ESC },
1593 { "Left", KEY_LEFT },
1594 { "Right", KEY_RIGHT },
1595 { "Up", KEY_UP },
1596 { "Down", KEY_DOWN },
1597 { "Insert", KEY_IC },
1598 { "Delete", KEY_DC },
1599 { "Hash", '#' },
1600 { "Home", KEY_HOME },
1601 { "End", KEY_END },
1602 { "PageUp", KEY_PPAGE },
1603 { "PageDown", KEY_NPAGE },
1604 { "F1", KEY_F(1) },
1605 { "F2", KEY_F(2) },
1606 { "F3", KEY_F(3) },
1607 { "F4", KEY_F(4) },
1608 { "F5", KEY_F(5) },
1609 { "F6", KEY_F(6) },
1610 { "F7", KEY_F(7) },
1611 { "F8", KEY_F(8) },
1612 { "F9", KEY_F(9) },
1613 { "F10", KEY_F(10) },
1614 { "F11", KEY_F(11) },
1615 { "F12", KEY_F(12) },
1616 };
1618 static int
1619 get_key_value(const char *name)
1620 {
1621 int i;
1623 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1624 if (!strcasecmp(key_table[i].name, name))
1625 return key_table[i].value;
1627 if (strlen(name) == 1 && isprint(*name))
1628 return (int) *name;
1630 return ERR;
1631 }
1633 static const char *
1634 get_key_name(int key_value)
1635 {
1636 static char key_char[] = "'X'";
1637 const char *seq = NULL;
1638 int key;
1640 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1641 if (key_table[key].value == key_value)
1642 seq = key_table[key].name;
1644 if (seq == NULL &&
1645 key_value < 127 &&
1646 isprint(key_value)) {
1647 key_char[1] = (char) key_value;
1648 seq = key_char;
1649 }
1651 return seq ? seq : "(no key)";
1652 }
1654 static bool
1655 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1656 {
1657 const char *sep = *pos > 0 ? ", " : "";
1658 const char *keyname = get_key_name(keybinding->alias);
1660 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1661 }
1663 static bool
1664 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1665 enum keymap keymap, bool all)
1666 {
1667 int i;
1669 for (i = 0; i < keybindings[keymap].size; i++) {
1670 if (keybindings[keymap].data[i].request == request) {
1671 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1672 return FALSE;
1673 if (!all)
1674 break;
1675 }
1676 }
1678 return TRUE;
1679 }
1681 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1683 static const char *
1684 get_keys(enum keymap keymap, enum request request, bool all)
1685 {
1686 static char buf[BUFSIZ];
1687 size_t pos = 0;
1688 int i;
1690 buf[pos] = 0;
1692 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1693 return "Too many keybindings!";
1694 if (pos > 0 && !all)
1695 return buf;
1697 if (keymap != KEYMAP_GENERIC) {
1698 /* Only the generic keymap includes the default keybindings when
1699 * listing all keys. */
1700 if (all)
1701 return buf;
1703 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1704 return "Too many keybindings!";
1705 if (pos)
1706 return buf;
1707 }
1709 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1710 if (default_keybindings[i].request == request) {
1711 if (!append_key(buf, &pos, &default_keybindings[i]))
1712 return "Too many keybindings!";
1713 if (!all)
1714 return buf;
1715 }
1716 }
1718 return buf;
1719 }
1721 struct run_request {
1722 enum keymap keymap;
1723 int key;
1724 const char *argv[SIZEOF_ARG];
1725 };
1727 static struct run_request *run_request;
1728 static size_t run_requests;
1730 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1732 static enum request
1733 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1734 {
1735 struct run_request *req;
1737 if (argc >= ARRAY_SIZE(req->argv) - 1)
1738 return REQ_NONE;
1740 if (!realloc_run_requests(&run_request, run_requests, 1))
1741 return REQ_NONE;
1743 req = &run_request[run_requests];
1744 req->keymap = keymap;
1745 req->key = key;
1746 req->argv[0] = NULL;
1748 if (!argv_copy(req->argv, argv, TRUE))
1749 return REQ_NONE;
1751 return REQ_NONE + ++run_requests;
1752 }
1754 static struct run_request *
1755 get_run_request(enum request request)
1756 {
1757 if (request <= REQ_NONE)
1758 return NULL;
1759 return &run_request[request - REQ_NONE - 1];
1760 }
1762 static void
1763 add_builtin_run_requests(void)
1764 {
1765 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1766 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1767 const char *commit[] = { "git", "commit", NULL };
1768 const char *gc[] = { "git", "gc", NULL };
1769 struct {
1770 enum keymap keymap;
1771 int key;
1772 int argc;
1773 const char **argv;
1774 } reqs[] = {
1775 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1776 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1777 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1778 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1779 };
1780 int i;
1782 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1783 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1785 if (req != reqs[i].key)
1786 continue;
1787 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1788 if (req != REQ_NONE)
1789 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1790 }
1791 }
1793 /*
1794 * User config file handling.
1795 */
1797 static int config_lineno;
1798 static bool config_errors;
1799 static const char *config_msg;
1801 static const struct enum_map color_map[] = {
1802 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1803 COLOR_MAP(DEFAULT),
1804 COLOR_MAP(BLACK),
1805 COLOR_MAP(BLUE),
1806 COLOR_MAP(CYAN),
1807 COLOR_MAP(GREEN),
1808 COLOR_MAP(MAGENTA),
1809 COLOR_MAP(RED),
1810 COLOR_MAP(WHITE),
1811 COLOR_MAP(YELLOW),
1812 };
1814 static const struct enum_map attr_map[] = {
1815 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1816 ATTR_MAP(NORMAL),
1817 ATTR_MAP(BLINK),
1818 ATTR_MAP(BOLD),
1819 ATTR_MAP(DIM),
1820 ATTR_MAP(REVERSE),
1821 ATTR_MAP(STANDOUT),
1822 ATTR_MAP(UNDERLINE),
1823 };
1825 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1827 static int parse_step(double *opt, const char *arg)
1828 {
1829 *opt = atoi(arg);
1830 if (!strchr(arg, '%'))
1831 return OK;
1833 /* "Shift down" so 100% and 1 does not conflict. */
1834 *opt = (*opt - 1) / 100;
1835 if (*opt >= 1.0) {
1836 *opt = 0.99;
1837 config_msg = "Step value larger than 100%";
1838 return ERR;
1839 }
1840 if (*opt < 0.0) {
1841 *opt = 1;
1842 config_msg = "Invalid step value";
1843 return ERR;
1844 }
1845 return OK;
1846 }
1848 static int
1849 parse_int(int *opt, const char *arg, int min, int max)
1850 {
1851 int value = atoi(arg);
1853 if (min <= value && value <= max) {
1854 *opt = value;
1855 return OK;
1856 }
1858 config_msg = "Integer value out of bound";
1859 return ERR;
1860 }
1862 static bool
1863 set_color(int *color, const char *name)
1864 {
1865 if (map_enum(color, color_map, name))
1866 return TRUE;
1867 if (!prefixcmp(name, "color"))
1868 return parse_int(color, name + 5, 0, 255) == OK;
1869 return FALSE;
1870 }
1872 /* Wants: object fgcolor bgcolor [attribute] */
1873 static int
1874 option_color_command(int argc, const char *argv[])
1875 {
1876 struct line_info *info;
1878 if (argc < 3) {
1879 config_msg = "Wrong number of arguments given to color command";
1880 return ERR;
1881 }
1883 info = get_line_info(argv[0]);
1884 if (!info) {
1885 static const struct enum_map obsolete[] = {
1886 ENUM_MAP("main-delim", LINE_DELIMITER),
1887 ENUM_MAP("main-date", LINE_DATE),
1888 ENUM_MAP("main-author", LINE_AUTHOR),
1889 };
1890 int index;
1892 if (!map_enum(&index, obsolete, argv[0])) {
1893 config_msg = "Unknown color name";
1894 return ERR;
1895 }
1896 info = &line_info[index];
1897 }
1899 if (!set_color(&info->fg, argv[1]) ||
1900 !set_color(&info->bg, argv[2])) {
1901 config_msg = "Unknown color";
1902 return ERR;
1903 }
1905 info->attr = 0;
1906 while (argc-- > 3) {
1907 int attr;
1909 if (!set_attribute(&attr, argv[argc])) {
1910 config_msg = "Unknown attribute";
1911 return ERR;
1912 }
1913 info->attr |= attr;
1914 }
1916 return OK;
1917 }
1919 static int parse_bool(bool *opt, const char *arg)
1920 {
1921 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1922 ? TRUE : FALSE;
1923 return OK;
1924 }
1926 static int parse_enum_do(unsigned int *opt, const char *arg,
1927 const struct enum_map *map, size_t map_size)
1928 {
1929 bool is_true;
1931 assert(map_size > 1);
1933 if (map_enum_do(map, map_size, (int *) opt, arg))
1934 return OK;
1936 if (parse_bool(&is_true, arg) != OK)
1937 return ERR;
1939 *opt = is_true ? map[1].value : map[0].value;
1940 return OK;
1941 }
1943 #define parse_enum(opt, arg, map) \
1944 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1946 static int
1947 parse_string(char *opt, const char *arg, size_t optsize)
1948 {
1949 int arglen = strlen(arg);
1951 switch (arg[0]) {
1952 case '\"':
1953 case '\'':
1954 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1955 config_msg = "Unmatched quotation";
1956 return ERR;
1957 }
1958 arg += 1; arglen -= 2;
1959 default:
1960 string_ncopy_do(opt, optsize, arg, arglen);
1961 return OK;
1962 }
1963 }
1965 /* Wants: name = value */
1966 static int
1967 option_set_command(int argc, const char *argv[])
1968 {
1969 if (argc != 3) {
1970 config_msg = "Wrong number of arguments given to set command";
1971 return ERR;
1972 }
1974 if (strcmp(argv[1], "=")) {
1975 config_msg = "No value assigned";
1976 return ERR;
1977 }
1979 if (!strcmp(argv[0], "show-author"))
1980 return parse_enum(&opt_author, argv[2], author_map);
1982 if (!strcmp(argv[0], "show-date"))
1983 return parse_enum(&opt_date, argv[2], date_map);
1985 if (!strcmp(argv[0], "show-rev-graph"))
1986 return parse_bool(&opt_rev_graph, argv[2]);
1988 if (!strcmp(argv[0], "show-refs"))
1989 return parse_bool(&opt_show_refs, argv[2]);
1991 if (!strcmp(argv[0], "show-line-numbers"))
1992 return parse_bool(&opt_line_number, argv[2]);
1994 if (!strcmp(argv[0], "line-graphics"))
1995 return parse_bool(&opt_line_graphics, argv[2]);
1997 if (!strcmp(argv[0], "line-number-interval"))
1998 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2000 if (!strcmp(argv[0], "author-width"))
2001 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2003 if (!strcmp(argv[0], "horizontal-scroll"))
2004 return parse_step(&opt_hscroll, argv[2]);
2006 if (!strcmp(argv[0], "split-view-height"))
2007 return parse_step(&opt_scale_split_view, argv[2]);
2009 if (!strcmp(argv[0], "tab-size"))
2010 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2012 if (!strcmp(argv[0], "commit-encoding"))
2013 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2015 config_msg = "Unknown variable name";
2016 return ERR;
2017 }
2019 /* Wants: mode request key */
2020 static int
2021 option_bind_command(int argc, const char *argv[])
2022 {
2023 enum request request;
2024 int keymap = -1;
2025 int key;
2027 if (argc < 3) {
2028 config_msg = "Wrong number of arguments given to bind command";
2029 return ERR;
2030 }
2032 if (!set_keymap(&keymap, argv[0])) {
2033 config_msg = "Unknown key map";
2034 return ERR;
2035 }
2037 key = get_key_value(argv[1]);
2038 if (key == ERR) {
2039 config_msg = "Unknown key";
2040 return ERR;
2041 }
2043 request = get_request(argv[2]);
2044 if (request == REQ_UNKNOWN) {
2045 static const struct enum_map obsolete[] = {
2046 ENUM_MAP("cherry-pick", REQ_NONE),
2047 ENUM_MAP("screen-resize", REQ_NONE),
2048 ENUM_MAP("tree-parent", REQ_PARENT),
2049 };
2050 int alias;
2052 if (map_enum(&alias, obsolete, argv[2])) {
2053 if (alias != REQ_NONE)
2054 add_keybinding(keymap, alias, key);
2055 config_msg = "Obsolete request name";
2056 return ERR;
2057 }
2058 }
2059 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2060 request = add_run_request(keymap, key, argc - 2, argv + 2);
2061 if (request == REQ_UNKNOWN) {
2062 config_msg = "Unknown request name";
2063 return ERR;
2064 }
2066 add_keybinding(keymap, request, key);
2068 return OK;
2069 }
2071 static int
2072 set_option(const char *opt, char *value)
2073 {
2074 const char *argv[SIZEOF_ARG];
2075 int argc = 0;
2077 if (!argv_from_string(argv, &argc, value)) {
2078 config_msg = "Too many option arguments";
2079 return ERR;
2080 }
2082 if (!strcmp(opt, "color"))
2083 return option_color_command(argc, argv);
2085 if (!strcmp(opt, "set"))
2086 return option_set_command(argc, argv);
2088 if (!strcmp(opt, "bind"))
2089 return option_bind_command(argc, argv);
2091 config_msg = "Unknown option command";
2092 return ERR;
2093 }
2095 static int
2096 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2097 {
2098 int status = OK;
2100 config_lineno++;
2101 config_msg = "Internal error";
2103 /* Check for comment markers, since read_properties() will
2104 * only ensure opt and value are split at first " \t". */
2105 optlen = strcspn(opt, "#");
2106 if (optlen == 0)
2107 return OK;
2109 if (opt[optlen] != 0) {
2110 config_msg = "No option value";
2111 status = ERR;
2113 } else {
2114 /* Look for comment endings in the value. */
2115 size_t len = strcspn(value, "#");
2117 if (len < valuelen) {
2118 valuelen = len;
2119 value[valuelen] = 0;
2120 }
2122 status = set_option(opt, value);
2123 }
2125 if (status == ERR) {
2126 warn("Error on line %d, near '%.*s': %s",
2127 config_lineno, (int) optlen, opt, config_msg);
2128 config_errors = TRUE;
2129 }
2131 /* Always keep going if errors are encountered. */
2132 return OK;
2133 }
2135 static void
2136 load_option_file(const char *path)
2137 {
2138 struct io io = {};
2140 /* It's OK that the file doesn't exist. */
2141 if (!io_open(&io, "%s", path))
2142 return;
2144 config_lineno = 0;
2145 config_errors = FALSE;
2147 if (io_load(&io, " \t", read_option) == ERR ||
2148 config_errors == TRUE)
2149 warn("Errors while loading %s.", path);
2150 }
2152 static int
2153 load_options(void)
2154 {
2155 const char *home = getenv("HOME");
2156 const char *tigrc_user = getenv("TIGRC_USER");
2157 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2158 char buf[SIZEOF_STR];
2160 if (!tigrc_system)
2161 tigrc_system = SYSCONFDIR "/tigrc";
2162 load_option_file(tigrc_system);
2164 if (!tigrc_user) {
2165 if (!home || !string_format(buf, "%s/.tigrc", home))
2166 return ERR;
2167 tigrc_user = buf;
2168 }
2169 load_option_file(tigrc_user);
2171 /* Add _after_ loading config files to avoid adding run requests
2172 * that conflict with keybindings. */
2173 add_builtin_run_requests();
2175 return OK;
2176 }
2179 /*
2180 * The viewer
2181 */
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views() (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF] = "";
2197 static char ref_commit[SIZEOF_REF] = "HEAD";
2198 static char ref_head[SIZEOF_REF] = "HEAD";
2199 static char ref_branch[SIZEOF_REF] = "";
2201 enum view_type {
2202 VIEW_MAIN,
2203 VIEW_DIFF,
2204 VIEW_LOG,
2205 VIEW_TREE,
2206 VIEW_BLOB,
2207 VIEW_BLAME,
2208 VIEW_BRANCH,
2209 VIEW_HELP,
2210 VIEW_PAGER,
2211 VIEW_STATUS,
2212 VIEW_STAGE,
2213 };
2215 struct view {
2216 enum view_type type; /* View type */
2217 const char *name; /* View name */
2218 const char *cmd_env; /* Command line set via environment */
2219 const char *id; /* Points to either of ref_{head,commit,blob} */
2221 struct view_ops *ops; /* View operations */
2223 enum keymap keymap; /* What keymap does this view have */
2224 bool git_dir; /* Whether the view requires a git directory. */
2226 char ref[SIZEOF_REF]; /* Hovered commit reference */
2227 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2229 int height, width; /* The width and height of the main window */
2230 WINDOW *win; /* The main window */
2231 WINDOW *title; /* The title window living below the main window */
2233 /* Navigation */
2234 unsigned long offset; /* Offset of the window top */
2235 unsigned long yoffset; /* Offset from the window side. */
2236 unsigned long lineno; /* Current line number */
2237 unsigned long p_offset; /* Previous offset of the window top */
2238 unsigned long p_yoffset;/* Previous offset from the window side */
2239 unsigned long p_lineno; /* Previous current line number */
2240 bool p_restore; /* Should the previous position be restored. */
2242 /* Searching */
2243 char grep[SIZEOF_STR]; /* Search string */
2244 regex_t *regex; /* Pre-compiled regexp */
2246 /* If non-NULL, points to the view that opened this view. If this view
2247 * is closed tig will switch back to the parent view. */
2248 struct view *parent;
2249 struct view *prev;
2251 /* Buffering */
2252 size_t lines; /* Total number of lines */
2253 struct line *line; /* Line index */
2254 unsigned int digits; /* Number of digits in the lines member. */
2256 /* Drawing */
2257 struct line *curline; /* Line currently being drawn. */
2258 enum line_type curtype; /* Attribute currently used for drawing. */
2259 unsigned long col; /* Column when drawing. */
2260 bool has_scrolled; /* View was scrolled. */
2262 /* Loading */
2263 struct io io;
2264 struct io *pipe;
2265 time_t start_time;
2266 time_t update_secs;
2267 };
2269 struct view_ops {
2270 /* What type of content being displayed. Used in the title bar. */
2271 const char *type;
2272 /* Default command arguments. */
2273 const char **argv;
2274 /* Open and reads in all view content. */
2275 bool (*open)(struct view *view);
2276 /* Read one line; updates view->line. */
2277 bool (*read)(struct view *view, char *data);
2278 /* Draw one line; @lineno must be < view->height. */
2279 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2280 /* Depending on view handle a special requests. */
2281 enum request (*request)(struct view *view, enum request request, struct line *line);
2282 /* Search for regexp in a line. */
2283 bool (*grep)(struct view *view, struct line *line);
2284 /* Select line */
2285 void (*select)(struct view *view, struct line *line);
2286 /* Prepare view for loading */
2287 bool (*prepare)(struct view *view);
2288 };
2290 static struct view_ops blame_ops;
2291 static struct view_ops blob_ops;
2292 static struct view_ops diff_ops;
2293 static struct view_ops help_ops;
2294 static struct view_ops log_ops;
2295 static struct view_ops main_ops;
2296 static struct view_ops pager_ops;
2297 static struct view_ops stage_ops;
2298 static struct view_ops status_ops;
2299 static struct view_ops tree_ops;
2300 static struct view_ops branch_ops;
2302 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2303 { type, name, #env, ref, ops, map, git }
2305 #define VIEW_(id, name, ops, git, ref) \
2306 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2308 static struct view views[] = {
2309 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2310 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2311 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2312 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2313 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2314 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2315 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2316 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2317 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2318 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2319 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2320 };
2322 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2324 #define foreach_view(view, i) \
2325 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2327 #define view_is_displayed(view) \
2328 (view == display[0] || view == display[1])
2330 static enum request
2331 view_request(struct view *view, enum request request)
2332 {
2333 if (!view || !view->lines)
2334 return request;
2335 return view->ops->request(view, request, &view->line[view->lineno]);
2336 }
2339 /*
2340 * View drawing.
2341 */
2343 static inline void
2344 set_view_attr(struct view *view, enum line_type type)
2345 {
2346 if (!view->curline->selected && view->curtype != type) {
2347 (void) wattrset(view->win, get_line_attr(type));
2348 wchgat(view->win, -1, 0, type, NULL);
2349 view->curtype = type;
2350 }
2351 }
2353 static int
2354 draw_chars(struct view *view, enum line_type type, const char *string,
2355 int max_len, bool use_tilde)
2356 {
2357 static char out_buffer[BUFSIZ * 2];
2358 int len = 0;
2359 int col = 0;
2360 int trimmed = FALSE;
2361 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363 if (max_len <= 0)
2364 return 0;
2366 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2368 set_view_attr(view, type);
2369 if (len > 0) {
2370 if (opt_iconv_out != ICONV_NONE) {
2371 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2372 size_t inlen = len + 1;
2374 char *outbuf = out_buffer;
2375 size_t outlen = sizeof(out_buffer);
2377 size_t ret;
2379 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2380 if (ret != (size_t) -1) {
2381 string = out_buffer;
2382 len = sizeof(out_buffer) - outlen;
2383 }
2384 }
2386 waddnstr(view->win, string, len);
2387 }
2388 if (trimmed && use_tilde) {
2389 set_view_attr(view, LINE_DELIMITER);
2390 waddch(view->win, '~');
2391 col++;
2392 }
2394 return col;
2395 }
2397 static int
2398 draw_space(struct view *view, enum line_type type, int max, int spaces)
2399 {
2400 static char space[] = " ";
2401 int col = 0;
2403 spaces = MIN(max, spaces);
2405 while (spaces > 0) {
2406 int len = MIN(spaces, sizeof(space) - 1);
2408 col += draw_chars(view, type, space, len, FALSE);
2409 spaces -= len;
2410 }
2412 return col;
2413 }
2415 static bool
2416 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2417 {
2418 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2419 return view->width + view->yoffset <= view->col;
2420 }
2422 static bool
2423 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2424 {
2425 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2426 int max = view->width + view->yoffset - view->col;
2427 int i;
2429 if (max < size)
2430 size = max;
2432 set_view_attr(view, type);
2433 /* Using waddch() instead of waddnstr() ensures that
2434 * they'll be rendered correctly for the cursor line. */
2435 for (i = skip; i < size; i++)
2436 waddch(view->win, graphic[i]);
2438 view->col += size;
2439 if (size < max && skip <= size)
2440 waddch(view->win, ' ');
2441 view->col++;
2443 return view->width + view->yoffset <= view->col;
2444 }
2446 static bool
2447 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2448 {
2449 int max = MIN(view->width + view->yoffset - view->col, len);
2450 int col;
2452 if (text)
2453 col = draw_chars(view, type, text, max - 1, trim);
2454 else
2455 col = draw_space(view, type, max - 1, max - 1);
2457 view->col += col;
2458 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2459 return view->width + view->yoffset <= view->col;
2460 }
2462 static bool
2463 draw_date(struct view *view, struct time *time)
2464 {
2465 const char *date = mkdate(time, opt_date);
2466 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2468 return draw_field(view, LINE_DATE, date, cols, FALSE);
2469 }
2471 static bool
2472 draw_author(struct view *view, const char *author)
2473 {
2474 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2475 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2477 if (abbreviate && author)
2478 author = get_author_initials(author);
2480 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2481 }
2483 static bool
2484 draw_mode(struct view *view, mode_t mode)
2485 {
2486 const char *str;
2488 if (S_ISDIR(mode))
2489 str = "drwxr-xr-x";
2490 else if (S_ISLNK(mode))
2491 str = "lrwxrwxrwx";
2492 else if (S_ISGITLINK(mode))
2493 str = "m---------";
2494 else if (S_ISREG(mode) && mode & S_IXUSR)
2495 str = "-rwxr-xr-x";
2496 else if (S_ISREG(mode))
2497 str = "-rw-r--r--";
2498 else
2499 str = "----------";
2501 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2502 }
2504 static bool
2505 draw_lineno(struct view *view, unsigned int lineno)
2506 {
2507 char number[10];
2508 int digits3 = view->digits < 3 ? 3 : view->digits;
2509 int max = MIN(view->width + view->yoffset - view->col, digits3);
2510 char *text = NULL;
2511 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2513 lineno += view->offset + 1;
2514 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2515 static char fmt[] = "%1ld";
2517 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2518 if (string_format(number, fmt, lineno))
2519 text = number;
2520 }
2521 if (text)
2522 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2523 else
2524 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2525 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2526 }
2528 static bool
2529 draw_view_line(struct view *view, unsigned int lineno)
2530 {
2531 struct line *line;
2532 bool selected = (view->offset + lineno == view->lineno);
2534 assert(view_is_displayed(view));
2536 if (view->offset + lineno >= view->lines)
2537 return FALSE;
2539 line = &view->line[view->offset + lineno];
2541 wmove(view->win, lineno, 0);
2542 if (line->cleareol)
2543 wclrtoeol(view->win);
2544 view->col = 0;
2545 view->curline = line;
2546 view->curtype = LINE_NONE;
2547 line->selected = FALSE;
2548 line->dirty = line->cleareol = 0;
2550 if (selected) {
2551 set_view_attr(view, LINE_CURSOR);
2552 line->selected = TRUE;
2553 view->ops->select(view, line);
2554 }
2556 return view->ops->draw(view, line, lineno);
2557 }
2559 static void
2560 redraw_view_dirty(struct view *view)
2561 {
2562 bool dirty = FALSE;
2563 int lineno;
2565 for (lineno = 0; lineno < view->height; lineno++) {
2566 if (view->offset + lineno >= view->lines)
2567 break;
2568 if (!view->line[view->offset + lineno].dirty)
2569 continue;
2570 dirty = TRUE;
2571 if (!draw_view_line(view, lineno))
2572 break;
2573 }
2575 if (!dirty)
2576 return;
2577 wnoutrefresh(view->win);
2578 }
2580 static void
2581 redraw_view_from(struct view *view, int lineno)
2582 {
2583 assert(0 <= lineno && lineno < view->height);
2585 for (; lineno < view->height; lineno++) {
2586 if (!draw_view_line(view, lineno))
2587 break;
2588 }
2590 wnoutrefresh(view->win);
2591 }
2593 static void
2594 redraw_view(struct view *view)
2595 {
2596 werase(view->win);
2597 redraw_view_from(view, 0);
2598 }
2601 static void
2602 update_view_title(struct view *view)
2603 {
2604 char buf[SIZEOF_STR];
2605 char state[SIZEOF_STR];
2606 size_t bufpos = 0, statelen = 0;
2608 assert(view_is_displayed(view));
2610 if (view->type != VIEW_STATUS && view->lines) {
2611 unsigned int view_lines = view->offset + view->height;
2612 unsigned int lines = view->lines
2613 ? MIN(view_lines, view->lines) * 100 / view->lines
2614 : 0;
2616 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2617 view->ops->type,
2618 view->lineno + 1,
2619 view->lines,
2620 lines);
2622 }
2624 if (view->pipe) {
2625 time_t secs = time(NULL) - view->start_time;
2627 /* Three git seconds are a long time ... */
2628 if (secs > 2)
2629 string_format_from(state, &statelen, " loading %lds", secs);
2630 }
2632 string_format_from(buf, &bufpos, "[%s]", view->name);
2633 if (*view->ref && bufpos < view->width) {
2634 size_t refsize = strlen(view->ref);
2635 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2637 if (minsize < view->width)
2638 refsize = view->width - minsize + 7;
2639 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2640 }
2642 if (statelen && bufpos < view->width) {
2643 string_format_from(buf, &bufpos, "%s", state);
2644 }
2646 if (view == display[current_view])
2647 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2648 else
2649 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2651 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2652 wclrtoeol(view->title);
2653 wnoutrefresh(view->title);
2654 }
2656 static int
2657 apply_step(double step, int value)
2658 {
2659 if (step >= 1)
2660 return (int) step;
2661 value *= step + 0.01;
2662 return value ? value : 1;
2663 }
2665 static void
2666 resize_display(void)
2667 {
2668 int offset, i;
2669 struct view *base = display[0];
2670 struct view *view = display[1] ? display[1] : display[0];
2672 /* Setup window dimensions */
2674 getmaxyx(stdscr, base->height, base->width);
2676 /* Make room for the status window. */
2677 base->height -= 1;
2679 if (view != base) {
2680 /* Horizontal split. */
2681 view->width = base->width;
2682 view->height = apply_step(opt_scale_split_view, base->height);
2683 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2684 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2685 base->height -= view->height;
2687 /* Make room for the title bar. */
2688 view->height -= 1;
2689 }
2691 /* Make room for the title bar. */
2692 base->height -= 1;
2694 offset = 0;
2696 foreach_displayed_view (view, i) {
2697 if (!view->win) {
2698 view->win = newwin(view->height, 0, offset, 0);
2699 if (!view->win)
2700 die("Failed to create %s view", view->name);
2702 scrollok(view->win, FALSE);
2704 view->title = newwin(1, 0, offset + view->height, 0);
2705 if (!view->title)
2706 die("Failed to create title window");
2708 } else {
2709 wresize(view->win, view->height, view->width);
2710 mvwin(view->win, offset, 0);
2711 mvwin(view->title, offset + view->height, 0);
2712 }
2714 offset += view->height + 1;
2715 }
2716 }
2718 static void
2719 redraw_display(bool clear)
2720 {
2721 struct view *view;
2722 int i;
2724 foreach_displayed_view (view, i) {
2725 if (clear)
2726 wclear(view->win);
2727 redraw_view(view);
2728 update_view_title(view);
2729 }
2730 }
2733 /*
2734 * Option management
2735 */
2737 static void
2738 toggle_enum_option_do(unsigned int *opt, const char *help,
2739 const struct enum_map *map, size_t size)
2740 {
2741 *opt = (*opt + 1) % size;
2742 redraw_display(FALSE);
2743 report("Displaying %s %s", enum_name(map[*opt]), help);
2744 }
2746 #define toggle_enum_option(opt, help, map) \
2747 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2749 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2750 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2752 static void
2753 toggle_view_option(bool *option, const char *help)
2754 {
2755 *option = !*option;
2756 redraw_display(FALSE);
2757 report("%sabling %s", *option ? "En" : "Dis", help);
2758 }
2760 static void
2761 open_option_menu(void)
2762 {
2763 const struct menu_item menu[] = {
2764 { '.', "line numbers", &opt_line_number },
2765 { 'D', "date display", &opt_date },
2766 { 'A', "author display", &opt_author },
2767 { 'g', "revision graph display", &opt_rev_graph },
2768 { 'F', "reference display", &opt_show_refs },
2769 { 0 }
2770 };
2771 int selected = 0;
2773 if (prompt_menu("Toggle option", menu, &selected)) {
2774 if (menu[selected].data == &opt_date)
2775 toggle_date();
2776 else if (menu[selected].data == &opt_author)
2777 toggle_author();
2778 else
2779 toggle_view_option(menu[selected].data, menu[selected].text);
2780 }
2781 }
2783 static void
2784 maximize_view(struct view *view)
2785 {
2786 memset(display, 0, sizeof(display));
2787 current_view = 0;
2788 display[current_view] = view;
2789 resize_display();
2790 redraw_display(FALSE);
2791 report("");
2792 }
2795 /*
2796 * Navigation
2797 */
2799 static bool
2800 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2801 {
2802 if (lineno >= view->lines)
2803 lineno = view->lines > 0 ? view->lines - 1 : 0;
2805 if (offset > lineno || offset + view->height <= lineno) {
2806 unsigned long half = view->height / 2;
2808 if (lineno > half)
2809 offset = lineno - half;
2810 else
2811 offset = 0;
2812 }
2814 if (offset != view->offset || lineno != view->lineno) {
2815 view->offset = offset;
2816 view->lineno = lineno;
2817 return TRUE;
2818 }
2820 return FALSE;
2821 }
2823 /* Scrolling backend */
2824 static void
2825 do_scroll_view(struct view *view, int lines)
2826 {
2827 bool redraw_current_line = FALSE;
2829 /* The rendering expects the new offset. */
2830 view->offset += lines;
2832 assert(0 <= view->offset && view->offset < view->lines);
2833 assert(lines);
2835 /* Move current line into the view. */
2836 if (view->lineno < view->offset) {
2837 view->lineno = view->offset;
2838 redraw_current_line = TRUE;
2839 } else if (view->lineno >= view->offset + view->height) {
2840 view->lineno = view->offset + view->height - 1;
2841 redraw_current_line = TRUE;
2842 }
2844 assert(view->offset <= view->lineno && view->lineno < view->lines);
2846 /* Redraw the whole screen if scrolling is pointless. */
2847 if (view->height < ABS(lines)) {
2848 redraw_view(view);
2850 } else {
2851 int line = lines > 0 ? view->height - lines : 0;
2852 int end = line + ABS(lines);
2854 scrollok(view->win, TRUE);
2855 wscrl(view->win, lines);
2856 scrollok(view->win, FALSE);
2858 while (line < end && draw_view_line(view, line))
2859 line++;
2861 if (redraw_current_line)
2862 draw_view_line(view, view->lineno - view->offset);
2863 wnoutrefresh(view->win);
2864 }
2866 view->has_scrolled = TRUE;
2867 report("");
2868 }
2870 /* Scroll frontend */
2871 static void
2872 scroll_view(struct view *view, enum request request)
2873 {
2874 int lines = 1;
2876 assert(view_is_displayed(view));
2878 switch (request) {
2879 case REQ_SCROLL_LEFT:
2880 if (view->yoffset == 0) {
2881 report("Cannot scroll beyond the first column");
2882 return;
2883 }
2884 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2885 view->yoffset = 0;
2886 else
2887 view->yoffset -= apply_step(opt_hscroll, view->width);
2888 redraw_view_from(view, 0);
2889 report("");
2890 return;
2891 case REQ_SCROLL_RIGHT:
2892 view->yoffset += apply_step(opt_hscroll, view->width);
2893 redraw_view(view);
2894 report("");
2895 return;
2896 case REQ_SCROLL_PAGE_DOWN:
2897 lines = view->height;
2898 case REQ_SCROLL_LINE_DOWN:
2899 if (view->offset + lines > view->lines)
2900 lines = view->lines - view->offset;
2902 if (lines == 0 || view->offset + view->height >= view->lines) {
2903 report("Cannot scroll beyond the last line");
2904 return;
2905 }
2906 break;
2908 case REQ_SCROLL_PAGE_UP:
2909 lines = view->height;
2910 case REQ_SCROLL_LINE_UP:
2911 if (lines > view->offset)
2912 lines = view->offset;
2914 if (lines == 0) {
2915 report("Cannot scroll beyond the first line");
2916 return;
2917 }
2919 lines = -lines;
2920 break;
2922 default:
2923 die("request %d not handled in switch", request);
2924 }
2926 do_scroll_view(view, lines);
2927 }
2929 /* Cursor moving */
2930 static void
2931 move_view(struct view *view, enum request request)
2932 {
2933 int scroll_steps = 0;
2934 int steps;
2936 switch (request) {
2937 case REQ_MOVE_FIRST_LINE:
2938 steps = -view->lineno;
2939 break;
2941 case REQ_MOVE_LAST_LINE:
2942 steps = view->lines - view->lineno - 1;
2943 break;
2945 case REQ_MOVE_PAGE_UP:
2946 steps = view->height > view->lineno
2947 ? -view->lineno : -view->height;
2948 break;
2950 case REQ_MOVE_PAGE_DOWN:
2951 steps = view->lineno + view->height >= view->lines
2952 ? view->lines - view->lineno - 1 : view->height;
2953 break;
2955 case REQ_MOVE_UP:
2956 steps = -1;
2957 break;
2959 case REQ_MOVE_DOWN:
2960 steps = 1;
2961 break;
2963 default:
2964 die("request %d not handled in switch", request);
2965 }
2967 if (steps <= 0 && view->lineno == 0) {
2968 report("Cannot move beyond the first line");
2969 return;
2971 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2972 report("Cannot move beyond the last line");
2973 return;
2974 }
2976 /* Move the current line */
2977 view->lineno += steps;
2978 assert(0 <= view->lineno && view->lineno < view->lines);
2980 /* Check whether the view needs to be scrolled */
2981 if (view->lineno < view->offset ||
2982 view->lineno >= view->offset + view->height) {
2983 scroll_steps = steps;
2984 if (steps < 0 && -steps > view->offset) {
2985 scroll_steps = -view->offset;
2987 } else if (steps > 0) {
2988 if (view->lineno == view->lines - 1 &&
2989 view->lines > view->height) {
2990 scroll_steps = view->lines - view->offset - 1;
2991 if (scroll_steps >= view->height)
2992 scroll_steps -= view->height - 1;
2993 }
2994 }
2995 }
2997 if (!view_is_displayed(view)) {
2998 view->offset += scroll_steps;
2999 assert(0 <= view->offset && view->offset < view->lines);
3000 view->ops->select(view, &view->line[view->lineno]);
3001 return;
3002 }
3004 /* Repaint the old "current" line if we be scrolling */
3005 if (ABS(steps) < view->height)
3006 draw_view_line(view, view->lineno - steps - view->offset);
3008 if (scroll_steps) {
3009 do_scroll_view(view, scroll_steps);
3010 return;
3011 }
3013 /* Draw the current line */
3014 draw_view_line(view, view->lineno - view->offset);
3016 wnoutrefresh(view->win);
3017 report("");
3018 }
3021 /*
3022 * Searching
3023 */
3025 static void search_view(struct view *view, enum request request);
3027 static bool
3028 grep_text(struct view *view, const char *text[])
3029 {
3030 regmatch_t pmatch;
3031 size_t i;
3033 for (i = 0; text[i]; i++)
3034 if (*text[i] &&
3035 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3036 return TRUE;
3037 return FALSE;
3038 }
3040 static void
3041 select_view_line(struct view *view, unsigned long lineno)
3042 {
3043 unsigned long old_lineno = view->lineno;
3044 unsigned long old_offset = view->offset;
3046 if (goto_view_line(view, view->offset, lineno)) {
3047 if (view_is_displayed(view)) {
3048 if (old_offset != view->offset) {
3049 redraw_view(view);
3050 } else {
3051 draw_view_line(view, old_lineno - view->offset);
3052 draw_view_line(view, view->lineno - view->offset);
3053 wnoutrefresh(view->win);
3054 }
3055 } else {
3056 view->ops->select(view, &view->line[view->lineno]);
3057 }
3058 }
3059 }
3061 static void
3062 find_next(struct view *view, enum request request)
3063 {
3064 unsigned long lineno = view->lineno;
3065 int direction;
3067 if (!*view->grep) {
3068 if (!*opt_search)
3069 report("No previous search");
3070 else
3071 search_view(view, request);
3072 return;
3073 }
3075 switch (request) {
3076 case REQ_SEARCH:
3077 case REQ_FIND_NEXT:
3078 direction = 1;
3079 break;
3081 case REQ_SEARCH_BACK:
3082 case REQ_FIND_PREV:
3083 direction = -1;
3084 break;
3086 default:
3087 return;
3088 }
3090 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3091 lineno += direction;
3093 /* Note, lineno is unsigned long so will wrap around in which case it
3094 * will become bigger than view->lines. */
3095 for (; lineno < view->lines; lineno += direction) {
3096 if (view->ops->grep(view, &view->line[lineno])) {
3097 select_view_line(view, lineno);
3098 report("Line %ld matches '%s'", lineno + 1, view->grep);
3099 return;
3100 }
3101 }
3103 report("No match found for '%s'", view->grep);
3104 }
3106 static void
3107 search_view(struct view *view, enum request request)
3108 {
3109 int regex_err;
3111 if (view->regex) {
3112 regfree(view->regex);
3113 *view->grep = 0;
3114 } else {
3115 view->regex = calloc(1, sizeof(*view->regex));
3116 if (!view->regex)
3117 return;
3118 }
3120 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3121 if (regex_err != 0) {
3122 char buf[SIZEOF_STR] = "unknown error";
3124 regerror(regex_err, view->regex, buf, sizeof(buf));
3125 report("Search failed: %s", buf);
3126 return;
3127 }
3129 string_copy(view->grep, opt_search);
3131 find_next(view, request);
3132 }
3134 /*
3135 * Incremental updating
3136 */
3138 static void
3139 reset_view(struct view *view)
3140 {
3141 int i;
3143 for (i = 0; i < view->lines; i++)
3144 free(view->line[i].data);
3145 free(view->line);
3147 view->p_offset = view->offset;
3148 view->p_yoffset = view->yoffset;
3149 view->p_lineno = view->lineno;
3151 view->line = NULL;
3152 view->offset = 0;
3153 view->yoffset = 0;
3154 view->lines = 0;
3155 view->lineno = 0;
3156 view->vid[0] = 0;
3157 view->update_secs = 0;
3158 }
3160 static const char *
3161 format_arg(const char *name)
3162 {
3163 static struct {
3164 const char *name;
3165 size_t namelen;
3166 const char *value;
3167 const char *value_if_empty;
3168 } vars[] = {
3169 #define FORMAT_VAR(name, value, value_if_empty) \
3170 { name, STRING_SIZE(name), value, value_if_empty }
3171 FORMAT_VAR("%(directory)", opt_path, ""),
3172 FORMAT_VAR("%(file)", opt_file, ""),
3173 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3174 FORMAT_VAR("%(head)", ref_head, ""),
3175 FORMAT_VAR("%(commit)", ref_commit, ""),
3176 FORMAT_VAR("%(blob)", ref_blob, ""),
3177 FORMAT_VAR("%(branch)", ref_branch, ""),
3178 };
3179 int i;
3181 for (i = 0; i < ARRAY_SIZE(vars); i++)
3182 if (!strncmp(name, vars[i].name, vars[i].namelen))
3183 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3185 report("Unknown replacement: `%s`", name);
3186 return NULL;
3187 }
3189 static bool
3190 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3191 {
3192 char buf[SIZEOF_STR];
3193 int argc;
3194 bool noreplace = flags == FORMAT_NONE;
3196 argv_free(dst_argv);
3198 for (argc = 0; src_argv[argc]; argc++) {
3199 const char *arg = src_argv[argc];
3200 size_t bufpos = 0;
3202 while (arg) {
3203 char *next = strstr(arg, "%(");
3204 int len = next - arg;
3205 const char *value;
3207 if (!next || noreplace) {
3208 len = strlen(arg);
3209 value = "";
3211 } else {
3212 value = format_arg(next);
3214 if (!value) {
3215 return FALSE;
3216 }
3217 }
3219 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3220 return FALSE;
3222 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3223 }
3225 dst_argv[argc] = strdup(buf);
3226 if (!dst_argv[argc])
3227 break;
3228 }
3230 dst_argv[argc] = NULL;
3232 return src_argv[argc] == NULL;
3233 }
3235 static bool
3236 restore_view_position(struct view *view)
3237 {
3238 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3239 return FALSE;
3241 /* Changing the view position cancels the restoring. */
3242 /* FIXME: Changing back to the first line is not detected. */
3243 if (view->offset != 0 || view->lineno != 0) {
3244 view->p_restore = FALSE;
3245 return FALSE;
3246 }
3248 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3249 view_is_displayed(view))
3250 werase(view->win);
3252 view->yoffset = view->p_yoffset;
3253 view->p_restore = FALSE;
3255 return TRUE;
3256 }
3258 static void
3259 end_update(struct view *view, bool force)
3260 {
3261 if (!view->pipe)
3262 return;
3263 while (!view->ops->read(view, NULL))
3264 if (!force)
3265 return;
3266 if (force)
3267 io_kill(view->pipe);
3268 io_done(view->pipe);
3269 view->pipe = NULL;
3270 }
3272 static void
3273 setup_update(struct view *view, const char *vid)
3274 {
3275 reset_view(view);
3276 string_copy_rev(view->vid, vid);
3277 view->pipe = &view->io;
3278 view->start_time = time(NULL);
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 io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
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 io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE) &&
3295 io_start(&view->io);
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 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3304 }
3306 static bool
3307 begin_update(struct view *view, bool refresh)
3308 {
3309 if (view->pipe)
3310 end_update(view, TRUE);
3312 if (!refresh) {
3313 if (view->ops->prepare) {
3314 if (!view->ops->prepare(view))
3315 return FALSE;
3316 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3317 return FALSE;
3318 }
3320 /* Put the current ref_* value to the view title ref
3321 * member. This is needed by the blob view. Most other
3322 * views sets it automatically after loading because the
3323 * first line is a commit line. */
3324 string_copy_rev(view->ref, view->id);
3325 }
3327 if (!io_start(&view->io))
3328 return FALSE;
3330 setup_update(view, view->id);
3332 return TRUE;
3333 }
3335 static bool
3336 update_view(struct view *view)
3337 {
3338 char out_buffer[BUFSIZ * 2];
3339 char *line;
3340 /* Clear the view and redraw everything since the tree sorting
3341 * might have rearranged things. */
3342 bool redraw = view->lines == 0;
3343 bool can_read = TRUE;
3345 if (!view->pipe)
3346 return TRUE;
3348 if (!io_can_read(view->pipe)) {
3349 if (view->lines == 0 && view_is_displayed(view)) {
3350 time_t secs = time(NULL) - view->start_time;
3352 if (secs > 1 && secs > view->update_secs) {
3353 if (view->update_secs == 0)
3354 redraw_view(view);
3355 update_view_title(view);
3356 view->update_secs = secs;
3357 }
3358 }
3359 return TRUE;
3360 }
3362 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3363 if (opt_iconv_in != ICONV_NONE) {
3364 ICONV_CONST char *inbuf = line;
3365 size_t inlen = strlen(line) + 1;
3367 char *outbuf = out_buffer;
3368 size_t outlen = sizeof(out_buffer);
3370 size_t ret;
3372 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3373 if (ret != (size_t) -1)
3374 line = out_buffer;
3375 }
3377 if (!view->ops->read(view, line)) {
3378 report("Allocation failure");
3379 end_update(view, TRUE);
3380 return FALSE;
3381 }
3382 }
3384 {
3385 unsigned long lines = view->lines;
3386 int digits;
3388 for (digits = 0; lines; digits++)
3389 lines /= 10;
3391 /* Keep the displayed view in sync with line number scaling. */
3392 if (digits != view->digits) {
3393 view->digits = digits;
3394 if (opt_line_number || view->type == VIEW_BLAME)
3395 redraw = TRUE;
3396 }
3397 }
3399 if (io_error(view->pipe)) {
3400 report("Failed to read: %s", io_strerror(view->pipe));
3401 end_update(view, TRUE);
3403 } else if (io_eof(view->pipe)) {
3404 if (view_is_displayed(view))
3405 report("");
3406 end_update(view, FALSE);
3407 }
3409 if (restore_view_position(view))
3410 redraw = TRUE;
3412 if (!view_is_displayed(view))
3413 return TRUE;
3415 if (redraw)
3416 redraw_view_from(view, 0);
3417 else
3418 redraw_view_dirty(view);
3420 /* Update the title _after_ the redraw so that if the redraw picks up a
3421 * commit reference in view->ref it'll be available here. */
3422 update_view_title(view);
3423 return TRUE;
3424 }
3426 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3428 static struct line *
3429 add_line_data(struct view *view, void *data, enum line_type type)
3430 {
3431 struct line *line;
3433 if (!realloc_lines(&view->line, view->lines, 1))
3434 return NULL;
3436 line = &view->line[view->lines++];
3437 memset(line, 0, sizeof(*line));
3438 line->type = type;
3439 line->data = data;
3440 line->dirty = 1;
3442 return line;
3443 }
3445 static struct line *
3446 add_line_text(struct view *view, const char *text, enum line_type type)
3447 {
3448 char *data = text ? strdup(text) : NULL;
3450 return data ? add_line_data(view, data, type) : NULL;
3451 }
3453 static struct line *
3454 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3455 {
3456 char buf[SIZEOF_STR];
3457 va_list args;
3459 va_start(args, fmt);
3460 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3461 buf[0] = 0;
3462 va_end(args);
3464 return buf[0] ? add_line_text(view, buf, type) : NULL;
3465 }
3467 /*
3468 * View opening
3469 */
3471 enum open_flags {
3472 OPEN_DEFAULT = 0, /* Use default view switching. */
3473 OPEN_SPLIT = 1, /* Split current view. */
3474 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3475 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3476 OPEN_PREPARED = 32, /* Open already prepared command. */
3477 };
3479 static void
3480 open_view(struct view *prev, enum request request, enum open_flags flags)
3481 {
3482 bool split = !!(flags & OPEN_SPLIT);
3483 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3484 bool nomaximize = !!(flags & OPEN_REFRESH);
3485 struct view *view = VIEW(request);
3486 int nviews = displayed_views();
3487 struct view *base_view = display[0];
3489 if (view == prev && nviews == 1 && !reload) {
3490 report("Already in %s view", view->name);
3491 return;
3492 }
3494 if (view->git_dir && !opt_git_dir[0]) {
3495 report("The %s view is disabled in pager view", view->name);
3496 return;
3497 }
3499 if (split) {
3500 display[1] = view;
3501 current_view = 1;
3502 view->parent = prev;
3503 } else if (!nomaximize) {
3504 /* Maximize the current view. */
3505 memset(display, 0, sizeof(display));
3506 current_view = 0;
3507 display[current_view] = view;
3508 }
3510 /* No prev signals that this is the first loaded view. */
3511 if (prev && view != prev) {
3512 view->prev = prev;
3513 }
3515 /* Resize the view when switching between split- and full-screen,
3516 * or when switching between two different full-screen views. */
3517 if (nviews != displayed_views() ||
3518 (nviews == 1 && base_view != display[0]))
3519 resize_display();
3521 if (view->ops->open) {
3522 if (view->pipe)
3523 end_update(view, TRUE);
3524 if (!view->ops->open(view)) {
3525 report("Failed to load %s view", view->name);
3526 return;
3527 }
3528 restore_view_position(view);
3530 } else if ((reload || strcmp(view->vid, view->id)) &&
3531 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3532 report("Failed to load %s view", view->name);
3533 return;
3534 }
3536 if (split && prev->lineno - prev->offset >= prev->height) {
3537 /* Take the title line into account. */
3538 int lines = prev->lineno - prev->offset - prev->height + 1;
3540 /* Scroll the view that was split if the current line is
3541 * outside the new limited view. */
3542 do_scroll_view(prev, lines);
3543 }
3545 if (prev && view != prev && split && view_is_displayed(prev)) {
3546 /* "Blur" the previous view. */
3547 update_view_title(prev);
3548 }
3550 if (view->pipe && view->lines == 0) {
3551 /* Clear the old view and let the incremental updating refill
3552 * the screen. */
3553 werase(view->win);
3554 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3555 report("");
3556 } else if (view_is_displayed(view)) {
3557 redraw_view(view);
3558 report("");
3559 }
3560 }
3562 static void
3563 open_external_viewer(const char *argv[], const char *dir)
3564 {
3565 def_prog_mode(); /* save current tty modes */
3566 endwin(); /* restore original tty modes */
3567 io_run_fg(argv, dir);
3568 fprintf(stderr, "Press Enter to continue");
3569 getc(opt_tty);
3570 reset_prog_mode();
3571 redraw_display(TRUE);
3572 }
3574 static void
3575 open_mergetool(const char *file)
3576 {
3577 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3579 open_external_viewer(mergetool_argv, opt_cdup);
3580 }
3582 static void
3583 open_editor(const char *file)
3584 {
3585 const char *editor_argv[] = { "vi", file, NULL };
3586 const char *editor;
3588 editor = getenv("GIT_EDITOR");
3589 if (!editor && *opt_editor)
3590 editor = opt_editor;
3591 if (!editor)
3592 editor = getenv("VISUAL");
3593 if (!editor)
3594 editor = getenv("EDITOR");
3595 if (!editor)
3596 editor = "vi";
3598 editor_argv[0] = editor;
3599 open_external_viewer(editor_argv, opt_cdup);
3600 }
3602 static void
3603 open_run_request(enum request request)
3604 {
3605 struct run_request *req = get_run_request(request);
3606 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3608 if (!req) {
3609 report("Unknown run request");
3610 return;
3611 }
3613 if (format_argv(argv, req->argv, FORMAT_ALL))
3614 open_external_viewer(argv, NULL);
3615 argv_free(argv);
3616 }
3618 /*
3619 * User request switch noodle
3620 */
3622 static int
3623 view_driver(struct view *view, enum request request)
3624 {
3625 int i;
3627 if (request == REQ_NONE)
3628 return TRUE;
3630 if (request > REQ_NONE) {
3631 open_run_request(request);
3632 view_request(view, REQ_REFRESH);
3633 return TRUE;
3634 }
3636 request = view_request(view, request);
3637 if (request == REQ_NONE)
3638 return TRUE;
3640 switch (request) {
3641 case REQ_MOVE_UP:
3642 case REQ_MOVE_DOWN:
3643 case REQ_MOVE_PAGE_UP:
3644 case REQ_MOVE_PAGE_DOWN:
3645 case REQ_MOVE_FIRST_LINE:
3646 case REQ_MOVE_LAST_LINE:
3647 move_view(view, request);
3648 break;
3650 case REQ_SCROLL_LEFT:
3651 case REQ_SCROLL_RIGHT:
3652 case REQ_SCROLL_LINE_DOWN:
3653 case REQ_SCROLL_LINE_UP:
3654 case REQ_SCROLL_PAGE_DOWN:
3655 case REQ_SCROLL_PAGE_UP:
3656 scroll_view(view, request);
3657 break;
3659 case REQ_VIEW_BLAME:
3660 if (!opt_file[0]) {
3661 report("No file chosen, press %s to open tree view",
3662 get_key(view->keymap, REQ_VIEW_TREE));
3663 break;
3664 }
3665 open_view(view, request, OPEN_DEFAULT);
3666 break;
3668 case REQ_VIEW_BLOB:
3669 if (!ref_blob[0]) {
3670 report("No file chosen, press %s to open tree view",
3671 get_key(view->keymap, REQ_VIEW_TREE));
3672 break;
3673 }
3674 open_view(view, request, OPEN_DEFAULT);
3675 break;
3677 case REQ_VIEW_PAGER:
3678 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3679 report("No pager content, press %s to run command from prompt",
3680 get_key(view->keymap, REQ_PROMPT));
3681 break;
3682 }
3683 open_view(view, request, OPEN_DEFAULT);
3684 break;
3686 case REQ_VIEW_STAGE:
3687 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3688 report("No stage content, press %s to open the status view and choose file",
3689 get_key(view->keymap, REQ_VIEW_STATUS));
3690 break;
3691 }
3692 open_view(view, request, OPEN_DEFAULT);
3693 break;
3695 case REQ_VIEW_STATUS:
3696 if (opt_is_inside_work_tree == FALSE) {
3697 report("The status view requires a working tree");
3698 break;
3699 }
3700 open_view(view, request, OPEN_DEFAULT);
3701 break;
3703 case REQ_VIEW_MAIN:
3704 case REQ_VIEW_DIFF:
3705 case REQ_VIEW_LOG:
3706 case REQ_VIEW_TREE:
3707 case REQ_VIEW_HELP:
3708 case REQ_VIEW_BRANCH:
3709 open_view(view, request, OPEN_DEFAULT);
3710 break;
3712 case REQ_NEXT:
3713 case REQ_PREVIOUS:
3714 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3716 if (view->parent) {
3717 int line;
3719 view = view->parent;
3720 line = view->lineno;
3721 move_view(view, request);
3722 if (view_is_displayed(view))
3723 update_view_title(view);
3724 if (line != view->lineno)
3725 view_request(view, REQ_ENTER);
3726 } else {
3727 move_view(view, request);
3728 }
3729 break;
3731 case REQ_VIEW_NEXT:
3732 {
3733 int nviews = displayed_views();
3734 int next_view = (current_view + 1) % nviews;
3736 if (next_view == current_view) {
3737 report("Only one view is displayed");
3738 break;
3739 }
3741 current_view = next_view;
3742 /* Blur out the title of the previous view. */
3743 update_view_title(view);
3744 report("");
3745 break;
3746 }
3747 case REQ_REFRESH:
3748 report("Refreshing is not yet supported for the %s view", view->name);
3749 break;
3751 case REQ_MAXIMIZE:
3752 if (displayed_views() == 2)
3753 maximize_view(view);
3754 break;
3756 case REQ_OPTIONS:
3757 open_option_menu();
3758 break;
3760 case REQ_TOGGLE_LINENO:
3761 toggle_view_option(&opt_line_number, "line numbers");
3762 break;
3764 case REQ_TOGGLE_DATE:
3765 toggle_date();
3766 break;
3768 case REQ_TOGGLE_AUTHOR:
3769 toggle_author();
3770 break;
3772 case REQ_TOGGLE_REV_GRAPH:
3773 toggle_view_option(&opt_rev_graph, "revision graph display");
3774 break;
3776 case REQ_TOGGLE_REFS:
3777 toggle_view_option(&opt_show_refs, "reference display");
3778 break;
3780 case REQ_TOGGLE_SORT_FIELD:
3781 case REQ_TOGGLE_SORT_ORDER:
3782 report("Sorting is not yet supported for the %s view", view->name);
3783 break;
3785 case REQ_SEARCH:
3786 case REQ_SEARCH_BACK:
3787 search_view(view, request);
3788 break;
3790 case REQ_FIND_NEXT:
3791 case REQ_FIND_PREV:
3792 find_next(view, request);
3793 break;
3795 case REQ_STOP_LOADING:
3796 foreach_view(view, i) {
3797 if (view->pipe)
3798 report("Stopped loading the %s view", view->name),
3799 end_update(view, TRUE);
3800 }
3801 break;
3803 case REQ_SHOW_VERSION:
3804 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3805 return TRUE;
3807 case REQ_SCREEN_REDRAW:
3808 redraw_display(TRUE);
3809 break;
3811 case REQ_EDIT:
3812 report("Nothing to edit");
3813 break;
3815 case REQ_ENTER:
3816 report("Nothing to enter");
3817 break;
3819 case REQ_VIEW_CLOSE:
3820 /* XXX: Mark closed views by letting view->prev point to the
3821 * view itself. Parents to closed view should never be
3822 * followed. */
3823 if (view->prev && view->prev != view) {
3824 maximize_view(view->prev);
3825 view->prev = view;
3826 break;
3827 }
3828 /* Fall-through */
3829 case REQ_QUIT:
3830 return FALSE;
3832 default:
3833 report("Unknown key, press %s for help",
3834 get_key(view->keymap, REQ_VIEW_HELP));
3835 return TRUE;
3836 }
3838 return TRUE;
3839 }
3842 /*
3843 * View backend utilities
3844 */
3846 enum sort_field {
3847 ORDERBY_NAME,
3848 ORDERBY_DATE,
3849 ORDERBY_AUTHOR,
3850 };
3852 struct sort_state {
3853 const enum sort_field *fields;
3854 size_t size, current;
3855 bool reverse;
3856 };
3858 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3859 #define get_sort_field(state) ((state).fields[(state).current])
3860 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3862 static void
3863 sort_view(struct view *view, enum request request, struct sort_state *state,
3864 int (*compare)(const void *, const void *))
3865 {
3866 switch (request) {
3867 case REQ_TOGGLE_SORT_FIELD:
3868 state->current = (state->current + 1) % state->size;
3869 break;
3871 case REQ_TOGGLE_SORT_ORDER:
3872 state->reverse = !state->reverse;
3873 break;
3874 default:
3875 die("Not a sort request");
3876 }
3878 qsort(view->line, view->lines, sizeof(*view->line), compare);
3879 redraw_view(view);
3880 }
3882 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3884 /* Small author cache to reduce memory consumption. It uses binary
3885 * search to lookup or find place to position new entries. No entries
3886 * are ever freed. */
3887 static const char *
3888 get_author(const char *name)
3889 {
3890 static const char **authors;
3891 static size_t authors_size;
3892 int from = 0, to = authors_size - 1;
3894 while (from <= to) {
3895 size_t pos = (to + from) / 2;
3896 int cmp = strcmp(name, authors[pos]);
3898 if (!cmp)
3899 return authors[pos];
3901 if (cmp < 0)
3902 to = pos - 1;
3903 else
3904 from = pos + 1;
3905 }
3907 if (!realloc_authors(&authors, authors_size, 1))
3908 return NULL;
3909 name = strdup(name);
3910 if (!name)
3911 return NULL;
3913 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3914 authors[from] = name;
3915 authors_size++;
3917 return name;
3918 }
3920 static void
3921 parse_timesec(struct time *time, const char *sec)
3922 {
3923 time->sec = (time_t) atol(sec);
3924 }
3926 static void
3927 parse_timezone(struct time *time, const char *zone)
3928 {
3929 long tz;
3931 tz = ('0' - zone[1]) * 60 * 60 * 10;
3932 tz += ('0' - zone[2]) * 60 * 60;
3933 tz += ('0' - zone[3]) * 60 * 10;
3934 tz += ('0' - zone[4]) * 60;
3936 if (zone[0] == '-')
3937 tz = -tz;
3939 time->tz = tz;
3940 time->sec -= tz;
3941 }
3943 /* Parse author lines where the name may be empty:
3944 * author <email@address.tld> 1138474660 +0100
3945 */
3946 static void
3947 parse_author_line(char *ident, const char **author, struct time *time)
3948 {
3949 char *nameend = strchr(ident, '<');
3950 char *emailend = strchr(ident, '>');
3952 if (nameend && emailend)
3953 *nameend = *emailend = 0;
3954 ident = chomp_string(ident);
3955 if (!*ident) {
3956 if (nameend)
3957 ident = chomp_string(nameend + 1);
3958 if (!*ident)
3959 ident = "Unknown";
3960 }
3962 *author = get_author(ident);
3964 /* Parse epoch and timezone */
3965 if (emailend && emailend[1] == ' ') {
3966 char *secs = emailend + 2;
3967 char *zone = strchr(secs, ' ');
3969 parse_timesec(time, secs);
3971 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3972 parse_timezone(time, zone + 1);
3973 }
3974 }
3976 static bool
3977 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3978 {
3979 char rev[SIZEOF_REV];
3980 const char *revlist_argv[] = {
3981 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3982 };
3983 struct menu_item *items;
3984 char text[SIZEOF_STR];
3985 bool ok = TRUE;
3986 int i;
3988 items = calloc(*parents + 1, sizeof(*items));
3989 if (!items)
3990 return FALSE;
3992 for (i = 0; i < *parents; i++) {
3993 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3994 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3995 !(items[i].text = strdup(text))) {
3996 ok = FALSE;
3997 break;
3998 }
3999 }
4001 if (ok) {
4002 *parents = 0;
4003 ok = prompt_menu("Select parent", items, parents);
4004 }
4005 for (i = 0; items[i].text; i++)
4006 free((char *) items[i].text);
4007 free(items);
4008 return ok;
4009 }
4011 static bool
4012 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4013 {
4014 char buf[SIZEOF_STR * 4];
4015 const char *revlist_argv[] = {
4016 "git", "log", "--no-color", "-1",
4017 "--pretty=format:%P", id, "--", path, NULL
4018 };
4019 int parents;
4021 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4022 (parents = strlen(buf) / 40) < 0) {
4023 report("Failed to get parent information");
4024 return FALSE;
4026 } else if (parents == 0) {
4027 if (path)
4028 report("Path '%s' does not exist in the parent", path);
4029 else
4030 report("The selected commit has no parents");
4031 return FALSE;
4032 }
4034 if (parents == 1)
4035 parents = 0;
4036 else if (!open_commit_parent_menu(buf, &parents))
4037 return FALSE;
4039 string_copy_rev(rev, &buf[41 * parents]);
4040 return TRUE;
4041 }
4043 /*
4044 * Pager backend
4045 */
4047 static bool
4048 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4049 {
4050 char text[SIZEOF_STR];
4052 if (opt_line_number && draw_lineno(view, lineno))
4053 return TRUE;
4055 string_expand(text, sizeof(text), line->data, opt_tab_size);
4056 draw_text(view, line->type, text, TRUE);
4057 return TRUE;
4058 }
4060 static bool
4061 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4062 {
4063 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4064 char ref[SIZEOF_STR];
4066 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4067 return TRUE;
4069 /* This is the only fatal call, since it can "corrupt" the buffer. */
4070 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4071 return FALSE;
4073 return TRUE;
4074 }
4076 static void
4077 add_pager_refs(struct view *view, struct line *line)
4078 {
4079 char buf[SIZEOF_STR];
4080 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4081 struct ref_list *list;
4082 size_t bufpos = 0, i;
4083 const char *sep = "Refs: ";
4084 bool is_tag = FALSE;
4086 assert(line->type == LINE_COMMIT);
4088 list = get_ref_list(commit_id);
4089 if (!list) {
4090 if (view->type == VIEW_DIFF)
4091 goto try_add_describe_ref;
4092 return;
4093 }
4095 for (i = 0; i < list->size; i++) {
4096 struct ref *ref = list->refs[i];
4097 const char *fmt = ref->tag ? "%s[%s]" :
4098 ref->remote ? "%s<%s>" : "%s%s";
4100 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4101 return;
4102 sep = ", ";
4103 if (ref->tag)
4104 is_tag = TRUE;
4105 }
4107 if (!is_tag && view->type == VIEW_DIFF) {
4108 try_add_describe_ref:
4109 /* Add <tag>-g<commit_id> "fake" reference. */
4110 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4111 return;
4112 }
4114 if (bufpos == 0)
4115 return;
4117 add_line_text(view, buf, LINE_PP_REFS);
4118 }
4120 static bool
4121 pager_read(struct view *view, char *data)
4122 {
4123 struct line *line;
4125 if (!data)
4126 return TRUE;
4128 line = add_line_text(view, data, get_line_type(data));
4129 if (!line)
4130 return FALSE;
4132 if (line->type == LINE_COMMIT &&
4133 (view->type == VIEW_DIFF ||
4134 view->type == VIEW_LOG))
4135 add_pager_refs(view, line);
4137 return TRUE;
4138 }
4140 static enum request
4141 pager_request(struct view *view, enum request request, struct line *line)
4142 {
4143 int split = 0;
4145 if (request != REQ_ENTER)
4146 return request;
4148 if (line->type == LINE_COMMIT &&
4149 (view->type == VIEW_LOG ||
4150 view->type == VIEW_PAGER)) {
4151 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4152 split = 1;
4153 }
4155 /* Always scroll the view even if it was split. That way
4156 * you can use Enter to scroll through the log view and
4157 * split open each commit diff. */
4158 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4160 /* FIXME: A minor workaround. Scrolling the view will call report("")
4161 * but if we are scrolling a non-current view this won't properly
4162 * update the view title. */
4163 if (split)
4164 update_view_title(view);
4166 return REQ_NONE;
4167 }
4169 static bool
4170 pager_grep(struct view *view, struct line *line)
4171 {
4172 const char *text[] = { line->data, NULL };
4174 return grep_text(view, text);
4175 }
4177 static void
4178 pager_select(struct view *view, struct line *line)
4179 {
4180 if (line->type == LINE_COMMIT) {
4181 char *text = (char *)line->data + STRING_SIZE("commit ");
4183 if (view->type != VIEW_PAGER)
4184 string_copy_rev(view->ref, text);
4185 string_copy_rev(ref_commit, text);
4186 }
4187 }
4189 static struct view_ops pager_ops = {
4190 "line",
4191 NULL,
4192 NULL,
4193 pager_read,
4194 pager_draw,
4195 pager_request,
4196 pager_grep,
4197 pager_select,
4198 };
4200 static const char *log_argv[SIZEOF_ARG] = {
4201 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4202 };
4204 static enum request
4205 log_request(struct view *view, enum request request, struct line *line)
4206 {
4207 switch (request) {
4208 case REQ_REFRESH:
4209 load_refs();
4210 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4211 return REQ_NONE;
4212 default:
4213 return pager_request(view, request, line);
4214 }
4215 }
4217 static struct view_ops log_ops = {
4218 "line",
4219 log_argv,
4220 NULL,
4221 pager_read,
4222 pager_draw,
4223 log_request,
4224 pager_grep,
4225 pager_select,
4226 };
4228 static const char *diff_argv[SIZEOF_ARG] = {
4229 "git", "show", "--pretty=fuller", "--no-color", "--root",
4230 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4231 };
4233 static struct view_ops diff_ops = {
4234 "line",
4235 diff_argv,
4236 NULL,
4237 pager_read,
4238 pager_draw,
4239 pager_request,
4240 pager_grep,
4241 pager_select,
4242 };
4244 /*
4245 * Help backend
4246 */
4248 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4250 static bool
4251 help_open_keymap_title(struct view *view, enum keymap keymap)
4252 {
4253 struct line *line;
4255 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4256 help_keymap_hidden[keymap] ? '+' : '-',
4257 enum_name(keymap_table[keymap]));
4258 if (line)
4259 line->other = keymap;
4261 return help_keymap_hidden[keymap];
4262 }
4264 static void
4265 help_open_keymap(struct view *view, enum keymap keymap)
4266 {
4267 const char *group = NULL;
4268 char buf[SIZEOF_STR];
4269 size_t bufpos;
4270 bool add_title = TRUE;
4271 int i;
4273 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4274 const char *key = NULL;
4276 if (req_info[i].request == REQ_NONE)
4277 continue;
4279 if (!req_info[i].request) {
4280 group = req_info[i].help;
4281 continue;
4282 }
4284 key = get_keys(keymap, req_info[i].request, TRUE);
4285 if (!key || !*key)
4286 continue;
4288 if (add_title && help_open_keymap_title(view, keymap))
4289 return;
4290 add_title = FALSE;
4292 if (group) {
4293 add_line_text(view, group, LINE_HELP_GROUP);
4294 group = NULL;
4295 }
4297 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4298 enum_name(req_info[i]), req_info[i].help);
4299 }
4301 group = "External commands:";
4303 for (i = 0; i < run_requests; i++) {
4304 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4305 const char *key;
4306 int argc;
4308 if (!req || req->keymap != keymap)
4309 continue;
4311 key = get_key_name(req->key);
4312 if (!*key)
4313 key = "(no key defined)";
4315 if (add_title && help_open_keymap_title(view, keymap))
4316 return;
4317 if (group) {
4318 add_line_text(view, group, LINE_HELP_GROUP);
4319 group = NULL;
4320 }
4322 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4323 if (!string_format_from(buf, &bufpos, "%s%s",
4324 argc ? " " : "", req->argv[argc]))
4325 return;
4327 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4328 }
4329 }
4331 static bool
4332 help_open(struct view *view)
4333 {
4334 enum keymap keymap;
4336 reset_view(view);
4337 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4338 add_line_text(view, "", LINE_DEFAULT);
4340 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4341 help_open_keymap(view, keymap);
4343 return TRUE;
4344 }
4346 static enum request
4347 help_request(struct view *view, enum request request, struct line *line)
4348 {
4349 switch (request) {
4350 case REQ_ENTER:
4351 if (line->type == LINE_HELP_KEYMAP) {
4352 help_keymap_hidden[line->other] =
4353 !help_keymap_hidden[line->other];
4354 view->p_restore = TRUE;
4355 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4356 }
4358 return REQ_NONE;
4359 default:
4360 return pager_request(view, request, line);
4361 }
4362 }
4364 static struct view_ops help_ops = {
4365 "line",
4366 NULL,
4367 help_open,
4368 NULL,
4369 pager_draw,
4370 help_request,
4371 pager_grep,
4372 pager_select,
4373 };
4376 /*
4377 * Tree backend
4378 */
4380 struct tree_stack_entry {
4381 struct tree_stack_entry *prev; /* Entry below this in the stack */
4382 unsigned long lineno; /* Line number to restore */
4383 char *name; /* Position of name in opt_path */
4384 };
4386 /* The top of the path stack. */
4387 static struct tree_stack_entry *tree_stack = NULL;
4388 unsigned long tree_lineno = 0;
4390 static void
4391 pop_tree_stack_entry(void)
4392 {
4393 struct tree_stack_entry *entry = tree_stack;
4395 tree_lineno = entry->lineno;
4396 entry->name[0] = 0;
4397 tree_stack = entry->prev;
4398 free(entry);
4399 }
4401 static void
4402 push_tree_stack_entry(const char *name, unsigned long lineno)
4403 {
4404 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4405 size_t pathlen = strlen(opt_path);
4407 if (!entry)
4408 return;
4410 entry->prev = tree_stack;
4411 entry->name = opt_path + pathlen;
4412 tree_stack = entry;
4414 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4415 pop_tree_stack_entry();
4416 return;
4417 }
4419 /* Move the current line to the first tree entry. */
4420 tree_lineno = 1;
4421 entry->lineno = lineno;
4422 }
4424 /* Parse output from git-ls-tree(1):
4425 *
4426 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4427 */
4429 #define SIZEOF_TREE_ATTR \
4430 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4432 #define SIZEOF_TREE_MODE \
4433 STRING_SIZE("100644 ")
4435 #define TREE_ID_OFFSET \
4436 STRING_SIZE("100644 blob ")
4438 struct tree_entry {
4439 char id[SIZEOF_REV];
4440 mode_t mode;
4441 struct time time; /* Date from the author ident. */
4442 const char *author; /* Author of the commit. */
4443 char name[1];
4444 };
4446 static const char *
4447 tree_path(const struct line *line)
4448 {
4449 return ((struct tree_entry *) line->data)->name;
4450 }
4452 static int
4453 tree_compare_entry(const struct line *line1, const struct line *line2)
4454 {
4455 if (line1->type != line2->type)
4456 return line1->type == LINE_TREE_DIR ? -1 : 1;
4457 return strcmp(tree_path(line1), tree_path(line2));
4458 }
4460 static const enum sort_field tree_sort_fields[] = {
4461 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4462 };
4463 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4465 static int
4466 tree_compare(const void *l1, const void *l2)
4467 {
4468 const struct line *line1 = (const struct line *) l1;
4469 const struct line *line2 = (const struct line *) l2;
4470 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4471 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4473 if (line1->type == LINE_TREE_HEAD)
4474 return -1;
4475 if (line2->type == LINE_TREE_HEAD)
4476 return 1;
4478 switch (get_sort_field(tree_sort_state)) {
4479 case ORDERBY_DATE:
4480 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4482 case ORDERBY_AUTHOR:
4483 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4485 case ORDERBY_NAME:
4486 default:
4487 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4488 }
4489 }
4492 static struct line *
4493 tree_entry(struct view *view, enum line_type type, const char *path,
4494 const char *mode, const char *id)
4495 {
4496 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4497 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4499 if (!entry || !line) {
4500 free(entry);
4501 return NULL;
4502 }
4504 strncpy(entry->name, path, strlen(path));
4505 if (mode)
4506 entry->mode = strtoul(mode, NULL, 8);
4507 if (id)
4508 string_copy_rev(entry->id, id);
4510 return line;
4511 }
4513 static bool
4514 tree_read_date(struct view *view, char *text, bool *read_date)
4515 {
4516 static const char *author_name;
4517 static struct time author_time;
4519 if (!text && *read_date) {
4520 *read_date = FALSE;
4521 return TRUE;
4523 } else if (!text) {
4524 char *path = *opt_path ? opt_path : ".";
4525 /* Find next entry to process */
4526 const char *log_file[] = {
4527 "git", "log", "--no-color", "--pretty=raw",
4528 "--cc", "--raw", view->id, "--", path, NULL
4529 };
4531 if (!view->lines) {
4532 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4533 report("Tree is empty");
4534 return TRUE;
4535 }
4537 if (!start_update(view, log_file, opt_cdup)) {
4538 report("Failed to load tree data");
4539 return TRUE;
4540 }
4542 *read_date = TRUE;
4543 return FALSE;
4545 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4546 parse_author_line(text + STRING_SIZE("author "),
4547 &author_name, &author_time);
4549 } else if (*text == ':') {
4550 char *pos;
4551 size_t annotated = 1;
4552 size_t i;
4554 pos = strchr(text, '\t');
4555 if (!pos)
4556 return TRUE;
4557 text = pos + 1;
4558 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4559 text += strlen(opt_path);
4560 pos = strchr(text, '/');
4561 if (pos)
4562 *pos = 0;
4564 for (i = 1; i < view->lines; i++) {
4565 struct line *line = &view->line[i];
4566 struct tree_entry *entry = line->data;
4568 annotated += !!entry->author;
4569 if (entry->author || strcmp(entry->name, text))
4570 continue;
4572 entry->author = author_name;
4573 entry->time = author_time;
4574 line->dirty = 1;
4575 break;
4576 }
4578 if (annotated == view->lines)
4579 io_kill(view->pipe);
4580 }
4581 return TRUE;
4582 }
4584 static bool
4585 tree_read(struct view *view, char *text)
4586 {
4587 static bool read_date = FALSE;
4588 struct tree_entry *data;
4589 struct line *entry, *line;
4590 enum line_type type;
4591 size_t textlen = text ? strlen(text) : 0;
4592 char *path = text + SIZEOF_TREE_ATTR;
4594 if (read_date || !text)
4595 return tree_read_date(view, text, &read_date);
4597 if (textlen <= SIZEOF_TREE_ATTR)
4598 return FALSE;
4599 if (view->lines == 0 &&
4600 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4601 return FALSE;
4603 /* Strip the path part ... */
4604 if (*opt_path) {
4605 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4606 size_t striplen = strlen(opt_path);
4608 if (pathlen > striplen)
4609 memmove(path, path + striplen,
4610 pathlen - striplen + 1);
4612 /* Insert "link" to parent directory. */
4613 if (view->lines == 1 &&
4614 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4615 return FALSE;
4616 }
4618 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4619 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4620 if (!entry)
4621 return FALSE;
4622 data = entry->data;
4624 /* Skip "Directory ..." and ".." line. */
4625 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4626 if (tree_compare_entry(line, entry) <= 0)
4627 continue;
4629 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4631 line->data = data;
4632 line->type = type;
4633 for (; line <= entry; line++)
4634 line->dirty = line->cleareol = 1;
4635 return TRUE;
4636 }
4638 if (tree_lineno > view->lineno) {
4639 view->lineno = tree_lineno;
4640 tree_lineno = 0;
4641 }
4643 return TRUE;
4644 }
4646 static bool
4647 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4648 {
4649 struct tree_entry *entry = line->data;
4651 if (line->type == LINE_TREE_HEAD) {
4652 if (draw_text(view, line->type, "Directory path /", TRUE))
4653 return TRUE;
4654 } else {
4655 if (draw_mode(view, entry->mode))
4656 return TRUE;
4658 if (opt_author && draw_author(view, entry->author))
4659 return TRUE;
4661 if (opt_date && draw_date(view, &entry->time))
4662 return TRUE;
4663 }
4664 if (draw_text(view, line->type, entry->name, TRUE))
4665 return TRUE;
4666 return TRUE;
4667 }
4669 static void
4670 open_blob_editor(const char *id)
4671 {
4672 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4673 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4674 int fd = mkstemp(file);
4676 if (fd == -1)
4677 report("Failed to create temporary file");
4678 else if (!io_run_append(blob_argv, fd))
4679 report("Failed to save blob data to file");
4680 else
4681 open_editor(file);
4682 if (fd != -1)
4683 unlink(file);
4684 }
4686 static enum request
4687 tree_request(struct view *view, enum request request, struct line *line)
4688 {
4689 enum open_flags flags;
4690 struct tree_entry *entry = line->data;
4692 switch (request) {
4693 case REQ_VIEW_BLAME:
4694 if (line->type != LINE_TREE_FILE) {
4695 report("Blame only supported for files");
4696 return REQ_NONE;
4697 }
4699 string_copy(opt_ref, view->vid);
4700 return request;
4702 case REQ_EDIT:
4703 if (line->type != LINE_TREE_FILE) {
4704 report("Edit only supported for files");
4705 } else if (!is_head_commit(view->vid)) {
4706 open_blob_editor(entry->id);
4707 } else {
4708 open_editor(opt_file);
4709 }
4710 return REQ_NONE;
4712 case REQ_TOGGLE_SORT_FIELD:
4713 case REQ_TOGGLE_SORT_ORDER:
4714 sort_view(view, request, &tree_sort_state, tree_compare);
4715 return REQ_NONE;
4717 case REQ_PARENT:
4718 if (!*opt_path) {
4719 /* quit view if at top of tree */
4720 return REQ_VIEW_CLOSE;
4721 }
4722 /* fake 'cd ..' */
4723 line = &view->line[1];
4724 break;
4726 case REQ_ENTER:
4727 break;
4729 default:
4730 return request;
4731 }
4733 /* Cleanup the stack if the tree view is at a different tree. */
4734 while (!*opt_path && tree_stack)
4735 pop_tree_stack_entry();
4737 switch (line->type) {
4738 case LINE_TREE_DIR:
4739 /* Depending on whether it is a subdirectory or parent link
4740 * mangle the path buffer. */
4741 if (line == &view->line[1] && *opt_path) {
4742 pop_tree_stack_entry();
4744 } else {
4745 const char *basename = tree_path(line);
4747 push_tree_stack_entry(basename, view->lineno);
4748 }
4750 /* Trees and subtrees share the same ID, so they are not not
4751 * unique like blobs. */
4752 flags = OPEN_RELOAD;
4753 request = REQ_VIEW_TREE;
4754 break;
4756 case LINE_TREE_FILE:
4757 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4758 request = REQ_VIEW_BLOB;
4759 break;
4761 default:
4762 return REQ_NONE;
4763 }
4765 open_view(view, request, flags);
4766 if (request == REQ_VIEW_TREE)
4767 view->lineno = tree_lineno;
4769 return REQ_NONE;
4770 }
4772 static bool
4773 tree_grep(struct view *view, struct line *line)
4774 {
4775 struct tree_entry *entry = line->data;
4776 const char *text[] = {
4777 entry->name,
4778 opt_author ? entry->author : "",
4779 mkdate(&entry->time, opt_date),
4780 NULL
4781 };
4783 return grep_text(view, text);
4784 }
4786 static void
4787 tree_select(struct view *view, struct line *line)
4788 {
4789 struct tree_entry *entry = line->data;
4791 if (line->type == LINE_TREE_FILE) {
4792 string_copy_rev(ref_blob, entry->id);
4793 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4795 } else if (line->type != LINE_TREE_DIR) {
4796 return;
4797 }
4799 string_copy_rev(view->ref, entry->id);
4800 }
4802 static bool
4803 tree_prepare(struct view *view)
4804 {
4805 if (view->lines == 0 && opt_prefix[0]) {
4806 char *pos = opt_prefix;
4808 while (pos && *pos) {
4809 char *end = strchr(pos, '/');
4811 if (end)
4812 *end = 0;
4813 push_tree_stack_entry(pos, 0);
4814 pos = end;
4815 if (end) {
4816 *end = '/';
4817 pos++;
4818 }
4819 }
4821 } else if (strcmp(view->vid, view->id)) {
4822 opt_path[0] = 0;
4823 }
4825 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4826 }
4828 static const char *tree_argv[SIZEOF_ARG] = {
4829 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4830 };
4832 static struct view_ops tree_ops = {
4833 "file",
4834 tree_argv,
4835 NULL,
4836 tree_read,
4837 tree_draw,
4838 tree_request,
4839 tree_grep,
4840 tree_select,
4841 tree_prepare,
4842 };
4844 static bool
4845 blob_read(struct view *view, char *line)
4846 {
4847 if (!line)
4848 return TRUE;
4849 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4850 }
4852 static enum request
4853 blob_request(struct view *view, enum request request, struct line *line)
4854 {
4855 switch (request) {
4856 case REQ_EDIT:
4857 open_blob_editor(view->vid);
4858 return REQ_NONE;
4859 default:
4860 return pager_request(view, request, line);
4861 }
4862 }
4864 static const char *blob_argv[SIZEOF_ARG] = {
4865 "git", "cat-file", "blob", "%(blob)", NULL
4866 };
4868 static struct view_ops blob_ops = {
4869 "line",
4870 blob_argv,
4871 NULL,
4872 blob_read,
4873 pager_draw,
4874 blob_request,
4875 pager_grep,
4876 pager_select,
4877 };
4879 /*
4880 * Blame backend
4881 *
4882 * Loading the blame view is a two phase job:
4883 *
4884 * 1. File content is read either using opt_file from the
4885 * filesystem or using git-cat-file.
4886 * 2. Then blame information is incrementally added by
4887 * reading output from git-blame.
4888 */
4890 struct blame_commit {
4891 char id[SIZEOF_REV]; /* SHA1 ID. */
4892 char title[128]; /* First line of the commit message. */
4893 const char *author; /* Author of the commit. */
4894 struct time time; /* Date from the author ident. */
4895 char filename[128]; /* Name of file. */
4896 bool has_previous; /* Was a "previous" line detected. */
4897 };
4899 struct blame {
4900 struct blame_commit *commit;
4901 unsigned long lineno;
4902 char text[1];
4903 };
4905 static bool
4906 blame_open(struct view *view)
4907 {
4908 char path[SIZEOF_STR];
4910 if (!view->prev && *opt_prefix) {
4911 string_copy(path, opt_file);
4912 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4913 return FALSE;
4914 }
4916 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4917 const char *blame_cat_file_argv[] = {
4918 "git", "cat-file", "blob", path, NULL
4919 };
4921 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4922 !start_update(view, blame_cat_file_argv, opt_cdup))
4923 return FALSE;
4924 }
4926 setup_update(view, opt_file);
4927 string_format(view->ref, "%s ...", opt_file);
4929 return TRUE;
4930 }
4932 static struct blame_commit *
4933 get_blame_commit(struct view *view, const char *id)
4934 {
4935 size_t i;
4937 for (i = 0; i < view->lines; i++) {
4938 struct blame *blame = view->line[i].data;
4940 if (!blame->commit)
4941 continue;
4943 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4944 return blame->commit;
4945 }
4947 {
4948 struct blame_commit *commit = calloc(1, sizeof(*commit));
4950 if (commit)
4951 string_ncopy(commit->id, id, SIZEOF_REV);
4952 return commit;
4953 }
4954 }
4956 static bool
4957 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4958 {
4959 const char *pos = *posref;
4961 *posref = NULL;
4962 pos = strchr(pos + 1, ' ');
4963 if (!pos || !isdigit(pos[1]))
4964 return FALSE;
4965 *number = atoi(pos + 1);
4966 if (*number < min || *number > max)
4967 return FALSE;
4969 *posref = pos;
4970 return TRUE;
4971 }
4973 static struct blame_commit *
4974 parse_blame_commit(struct view *view, const char *text, int *blamed)
4975 {
4976 struct blame_commit *commit;
4977 struct blame *blame;
4978 const char *pos = text + SIZEOF_REV - 2;
4979 size_t orig_lineno = 0;
4980 size_t lineno;
4981 size_t group;
4983 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4984 return NULL;
4986 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4987 !parse_number(&pos, &lineno, 1, view->lines) ||
4988 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4989 return NULL;
4991 commit = get_blame_commit(view, text);
4992 if (!commit)
4993 return NULL;
4995 *blamed += group;
4996 while (group--) {
4997 struct line *line = &view->line[lineno + group - 1];
4999 blame = line->data;
5000 blame->commit = commit;
5001 blame->lineno = orig_lineno + group - 1;
5002 line->dirty = 1;
5003 }
5005 return commit;
5006 }
5008 static bool
5009 blame_read_file(struct view *view, const char *line, bool *read_file)
5010 {
5011 if (!line) {
5012 const char *blame_argv[] = {
5013 "git", "blame", "--incremental",
5014 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5015 };
5017 if (view->lines == 0 && !view->prev)
5018 die("No blame exist for %s", view->vid);
5020 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5021 report("Failed to load blame data");
5022 return TRUE;
5023 }
5025 *read_file = FALSE;
5026 return FALSE;
5028 } else {
5029 size_t linelen = strlen(line);
5030 struct blame *blame = malloc(sizeof(*blame) + linelen);
5032 if (!blame)
5033 return FALSE;
5035 blame->commit = NULL;
5036 strncpy(blame->text, line, linelen);
5037 blame->text[linelen] = 0;
5038 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5039 }
5040 }
5042 static bool
5043 match_blame_header(const char *name, char **line)
5044 {
5045 size_t namelen = strlen(name);
5046 bool matched = !strncmp(name, *line, namelen);
5048 if (matched)
5049 *line += namelen;
5051 return matched;
5052 }
5054 static bool
5055 blame_read(struct view *view, char *line)
5056 {
5057 static struct blame_commit *commit = NULL;
5058 static int blamed = 0;
5059 static bool read_file = TRUE;
5061 if (read_file)
5062 return blame_read_file(view, line, &read_file);
5064 if (!line) {
5065 /* Reset all! */
5066 commit = NULL;
5067 blamed = 0;
5068 read_file = TRUE;
5069 string_format(view->ref, "%s", view->vid);
5070 if (view_is_displayed(view)) {
5071 update_view_title(view);
5072 redraw_view_from(view, 0);
5073 }
5074 return TRUE;
5075 }
5077 if (!commit) {
5078 commit = parse_blame_commit(view, line, &blamed);
5079 string_format(view->ref, "%s %2d%%", view->vid,
5080 view->lines ? blamed * 100 / view->lines : 0);
5082 } else if (match_blame_header("author ", &line)) {
5083 commit->author = get_author(line);
5085 } else if (match_blame_header("author-time ", &line)) {
5086 parse_timesec(&commit->time, line);
5088 } else if (match_blame_header("author-tz ", &line)) {
5089 parse_timezone(&commit->time, line);
5091 } else if (match_blame_header("summary ", &line)) {
5092 string_ncopy(commit->title, line, strlen(line));
5094 } else if (match_blame_header("previous ", &line)) {
5095 commit->has_previous = TRUE;
5097 } else if (match_blame_header("filename ", &line)) {
5098 string_ncopy(commit->filename, line, strlen(line));
5099 commit = NULL;
5100 }
5102 return TRUE;
5103 }
5105 static bool
5106 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5107 {
5108 struct blame *blame = line->data;
5109 struct time *time = NULL;
5110 const char *id = NULL, *author = NULL;
5111 char text[SIZEOF_STR];
5113 if (blame->commit && *blame->commit->filename) {
5114 id = blame->commit->id;
5115 author = blame->commit->author;
5116 time = &blame->commit->time;
5117 }
5119 if (opt_date && draw_date(view, time))
5120 return TRUE;
5122 if (opt_author && draw_author(view, author))
5123 return TRUE;
5125 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5126 return TRUE;
5128 if (draw_lineno(view, lineno))
5129 return TRUE;
5131 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5132 draw_text(view, LINE_DEFAULT, text, TRUE);
5133 return TRUE;
5134 }
5136 static bool
5137 check_blame_commit(struct blame *blame, bool check_null_id)
5138 {
5139 if (!blame->commit)
5140 report("Commit data not loaded yet");
5141 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5142 report("No commit exist for the selected line");
5143 else
5144 return TRUE;
5145 return FALSE;
5146 }
5148 static void
5149 setup_blame_parent_line(struct view *view, struct blame *blame)
5150 {
5151 const char *diff_tree_argv[] = {
5152 "git", "diff-tree", "-U0", blame->commit->id,
5153 "--", blame->commit->filename, NULL
5154 };
5155 struct io io = {};
5156 int parent_lineno = -1;
5157 int blamed_lineno = -1;
5158 char *line;
5160 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5161 return;
5163 while ((line = io_get(&io, '\n', TRUE))) {
5164 if (*line == '@') {
5165 char *pos = strchr(line, '+');
5167 parent_lineno = atoi(line + 4);
5168 if (pos)
5169 blamed_lineno = atoi(pos + 1);
5171 } else if (*line == '+' && parent_lineno != -1) {
5172 if (blame->lineno == blamed_lineno - 1 &&
5173 !strcmp(blame->text, line + 1)) {
5174 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5175 break;
5176 }
5177 blamed_lineno++;
5178 }
5179 }
5181 io_done(&io);
5182 }
5184 static enum request
5185 blame_request(struct view *view, enum request request, struct line *line)
5186 {
5187 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5188 struct blame *blame = line->data;
5190 switch (request) {
5191 case REQ_VIEW_BLAME:
5192 if (check_blame_commit(blame, TRUE)) {
5193 string_copy(opt_ref, blame->commit->id);
5194 string_copy(opt_file, blame->commit->filename);
5195 if (blame->lineno)
5196 view->lineno = blame->lineno;
5197 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5198 }
5199 break;
5201 case REQ_PARENT:
5202 if (check_blame_commit(blame, TRUE) &&
5203 select_commit_parent(blame->commit->id, opt_ref,
5204 blame->commit->filename)) {
5205 string_copy(opt_file, blame->commit->filename);
5206 setup_blame_parent_line(view, blame);
5207 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5208 }
5209 break;
5211 case REQ_ENTER:
5212 if (!check_blame_commit(blame, FALSE))
5213 break;
5215 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5216 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5217 break;
5219 if (!strcmp(blame->commit->id, NULL_ID)) {
5220 struct view *diff = VIEW(REQ_VIEW_DIFF);
5221 const char *diff_index_argv[] = {
5222 "git", "diff-index", "--root", "--patch-with-stat",
5223 "-C", "-M", "HEAD", "--", view->vid, NULL
5224 };
5226 if (!blame->commit->has_previous) {
5227 diff_index_argv[1] = "diff";
5228 diff_index_argv[2] = "--no-color";
5229 diff_index_argv[6] = "--";
5230 diff_index_argv[7] = "/dev/null";
5231 }
5233 if (!prepare_update(diff, diff_index_argv, NULL)) {
5234 report("Failed to allocate diff command");
5235 break;
5236 }
5237 flags |= OPEN_PREPARED;
5238 }
5240 open_view(view, REQ_VIEW_DIFF, flags);
5241 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5242 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5243 break;
5245 default:
5246 return request;
5247 }
5249 return REQ_NONE;
5250 }
5252 static bool
5253 blame_grep(struct view *view, struct line *line)
5254 {
5255 struct blame *blame = line->data;
5256 struct blame_commit *commit = blame->commit;
5257 const char *text[] = {
5258 blame->text,
5259 commit ? commit->title : "",
5260 commit ? commit->id : "",
5261 commit && opt_author ? commit->author : "",
5262 commit ? mkdate(&commit->time, opt_date) : "",
5263 NULL
5264 };
5266 return grep_text(view, text);
5267 }
5269 static void
5270 blame_select(struct view *view, struct line *line)
5271 {
5272 struct blame *blame = line->data;
5273 struct blame_commit *commit = blame->commit;
5275 if (!commit)
5276 return;
5278 if (!strcmp(commit->id, NULL_ID))
5279 string_ncopy(ref_commit, "HEAD", 4);
5280 else
5281 string_copy_rev(ref_commit, commit->id);
5282 }
5284 static struct view_ops blame_ops = {
5285 "line",
5286 NULL,
5287 blame_open,
5288 blame_read,
5289 blame_draw,
5290 blame_request,
5291 blame_grep,
5292 blame_select,
5293 };
5295 /*
5296 * Branch backend
5297 */
5299 struct branch {
5300 const char *author; /* Author of the last commit. */
5301 struct time time; /* Date of the last activity. */
5302 const struct ref *ref; /* Name and commit ID information. */
5303 };
5305 static const struct ref branch_all;
5307 static const enum sort_field branch_sort_fields[] = {
5308 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5309 };
5310 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5312 static int
5313 branch_compare(const void *l1, const void *l2)
5314 {
5315 const struct branch *branch1 = ((const struct line *) l1)->data;
5316 const struct branch *branch2 = ((const struct line *) l2)->data;
5318 switch (get_sort_field(branch_sort_state)) {
5319 case ORDERBY_DATE:
5320 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5322 case ORDERBY_AUTHOR:
5323 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5325 case ORDERBY_NAME:
5326 default:
5327 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5328 }
5329 }
5331 static bool
5332 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5333 {
5334 struct branch *branch = line->data;
5335 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5337 if (opt_date && draw_date(view, &branch->time))
5338 return TRUE;
5340 if (opt_author && draw_author(view, branch->author))
5341 return TRUE;
5343 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5344 return TRUE;
5345 }
5347 static enum request
5348 branch_request(struct view *view, enum request request, struct line *line)
5349 {
5350 struct branch *branch = line->data;
5352 switch (request) {
5353 case REQ_REFRESH:
5354 load_refs();
5355 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5356 return REQ_NONE;
5358 case REQ_TOGGLE_SORT_FIELD:
5359 case REQ_TOGGLE_SORT_ORDER:
5360 sort_view(view, request, &branch_sort_state, branch_compare);
5361 return REQ_NONE;
5363 case REQ_ENTER:
5364 if (branch->ref == &branch_all) {
5365 const char *all_branches_argv[] = {
5366 "git", "log", "--no-color", "--pretty=raw", "--parents",
5367 "--topo-order", "--all", NULL
5368 };
5369 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5371 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5372 report("Failed to load view of all branches");
5373 return REQ_NONE;
5374 }
5375 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5376 } else {
5377 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5378 }
5379 return REQ_NONE;
5381 default:
5382 return request;
5383 }
5384 }
5386 static bool
5387 branch_read(struct view *view, char *line)
5388 {
5389 static char id[SIZEOF_REV];
5390 struct branch *reference;
5391 size_t i;
5393 if (!line)
5394 return TRUE;
5396 switch (get_line_type(line)) {
5397 case LINE_COMMIT:
5398 string_copy_rev(id, line + STRING_SIZE("commit "));
5399 return TRUE;
5401 case LINE_AUTHOR:
5402 for (i = 0, reference = NULL; i < view->lines; i++) {
5403 struct branch *branch = view->line[i].data;
5405 if (strcmp(branch->ref->id, id))
5406 continue;
5408 view->line[i].dirty = TRUE;
5409 if (reference) {
5410 branch->author = reference->author;
5411 branch->time = reference->time;
5412 continue;
5413 }
5415 parse_author_line(line + STRING_SIZE("author "),
5416 &branch->author, &branch->time);
5417 reference = branch;
5418 }
5419 return TRUE;
5421 default:
5422 return TRUE;
5423 }
5425 }
5427 static bool
5428 branch_open_visitor(void *data, const struct ref *ref)
5429 {
5430 struct view *view = data;
5431 struct branch *branch;
5433 if (ref->tag || ref->ltag || ref->remote)
5434 return TRUE;
5436 branch = calloc(1, sizeof(*branch));
5437 if (!branch)
5438 return FALSE;
5440 branch->ref = ref;
5441 return !!add_line_data(view, branch, LINE_DEFAULT);
5442 }
5444 static bool
5445 branch_open(struct view *view)
5446 {
5447 const char *branch_log[] = {
5448 "git", "log", "--no-color", "--pretty=raw",
5449 "--simplify-by-decoration", "--all", NULL
5450 };
5452 if (!start_update(view, branch_log, NULL)) {
5453 report("Failed to load branch data");
5454 return TRUE;
5455 }
5457 setup_update(view, view->id);
5458 branch_open_visitor(view, &branch_all);
5459 foreach_ref(branch_open_visitor, view);
5460 view->p_restore = TRUE;
5462 return TRUE;
5463 }
5465 static bool
5466 branch_grep(struct view *view, struct line *line)
5467 {
5468 struct branch *branch = line->data;
5469 const char *text[] = {
5470 branch->ref->name,
5471 branch->author,
5472 NULL
5473 };
5475 return grep_text(view, text);
5476 }
5478 static void
5479 branch_select(struct view *view, struct line *line)
5480 {
5481 struct branch *branch = line->data;
5483 string_copy_rev(view->ref, branch->ref->id);
5484 string_copy_rev(ref_commit, branch->ref->id);
5485 string_copy_rev(ref_head, branch->ref->id);
5486 string_copy_rev(ref_branch, branch->ref->name);
5487 }
5489 static struct view_ops branch_ops = {
5490 "branch",
5491 NULL,
5492 branch_open,
5493 branch_read,
5494 branch_draw,
5495 branch_request,
5496 branch_grep,
5497 branch_select,
5498 };
5500 /*
5501 * Status backend
5502 */
5504 struct status {
5505 char status;
5506 struct {
5507 mode_t mode;
5508 char rev[SIZEOF_REV];
5509 char name[SIZEOF_STR];
5510 } old;
5511 struct {
5512 mode_t mode;
5513 char rev[SIZEOF_REV];
5514 char name[SIZEOF_STR];
5515 } new;
5516 };
5518 static char status_onbranch[SIZEOF_STR];
5519 static struct status stage_status;
5520 static enum line_type stage_line_type;
5521 static size_t stage_chunks;
5522 static int *stage_chunk;
5524 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5526 /* This should work even for the "On branch" line. */
5527 static inline bool
5528 status_has_none(struct view *view, struct line *line)
5529 {
5530 return line < view->line + view->lines && !line[1].data;
5531 }
5533 /* Get fields from the diff line:
5534 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5535 */
5536 static inline bool
5537 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5538 {
5539 const char *old_mode = buf + 1;
5540 const char *new_mode = buf + 8;
5541 const char *old_rev = buf + 15;
5542 const char *new_rev = buf + 56;
5543 const char *status = buf + 97;
5545 if (bufsize < 98 ||
5546 old_mode[-1] != ':' ||
5547 new_mode[-1] != ' ' ||
5548 old_rev[-1] != ' ' ||
5549 new_rev[-1] != ' ' ||
5550 status[-1] != ' ')
5551 return FALSE;
5553 file->status = *status;
5555 string_copy_rev(file->old.rev, old_rev);
5556 string_copy_rev(file->new.rev, new_rev);
5558 file->old.mode = strtoul(old_mode, NULL, 8);
5559 file->new.mode = strtoul(new_mode, NULL, 8);
5561 file->old.name[0] = file->new.name[0] = 0;
5563 return TRUE;
5564 }
5566 static bool
5567 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5568 {
5569 struct status *unmerged = NULL;
5570 char *buf;
5571 struct io io = {};
5573 if (!io_run(&io, argv, opt_cdup, IO_RD))
5574 return FALSE;
5576 add_line_data(view, NULL, type);
5578 while ((buf = io_get(&io, 0, TRUE))) {
5579 struct status *file = unmerged;
5581 if (!file) {
5582 file = calloc(1, sizeof(*file));
5583 if (!file || !add_line_data(view, file, type))
5584 goto error_out;
5585 }
5587 /* Parse diff info part. */
5588 if (status) {
5589 file->status = status;
5590 if (status == 'A')
5591 string_copy(file->old.rev, NULL_ID);
5593 } else if (!file->status || file == unmerged) {
5594 if (!status_get_diff(file, buf, strlen(buf)))
5595 goto error_out;
5597 buf = io_get(&io, 0, TRUE);
5598 if (!buf)
5599 break;
5601 /* Collapse all modified entries that follow an
5602 * associated unmerged entry. */
5603 if (unmerged == file) {
5604 unmerged->status = 'U';
5605 unmerged = NULL;
5606 } else if (file->status == 'U') {
5607 unmerged = file;
5608 }
5609 }
5611 /* Grab the old name for rename/copy. */
5612 if (!*file->old.name &&
5613 (file->status == 'R' || file->status == 'C')) {
5614 string_ncopy(file->old.name, buf, strlen(buf));
5616 buf = io_get(&io, 0, TRUE);
5617 if (!buf)
5618 break;
5619 }
5621 /* git-ls-files just delivers a NUL separated list of
5622 * file names similar to the second half of the
5623 * git-diff-* output. */
5624 string_ncopy(file->new.name, buf, strlen(buf));
5625 if (!*file->old.name)
5626 string_copy(file->old.name, file->new.name);
5627 file = NULL;
5628 }
5630 if (io_error(&io)) {
5631 error_out:
5632 io_done(&io);
5633 return FALSE;
5634 }
5636 if (!view->line[view->lines - 1].data)
5637 add_line_data(view, NULL, LINE_STAT_NONE);
5639 io_done(&io);
5640 return TRUE;
5641 }
5643 /* Don't show unmerged entries in the staged section. */
5644 static const char *status_diff_index_argv[] = {
5645 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5646 "--cached", "-M", "HEAD", NULL
5647 };
5649 static const char *status_diff_files_argv[] = {
5650 "git", "diff-files", "-z", NULL
5651 };
5653 static const char *status_list_other_argv[] = {
5654 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5655 };
5657 static const char *status_list_no_head_argv[] = {
5658 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5659 };
5661 static const char *update_index_argv[] = {
5662 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5663 };
5665 /* Restore the previous line number to stay in the context or select a
5666 * line with something that can be updated. */
5667 static void
5668 status_restore(struct view *view)
5669 {
5670 if (view->p_lineno >= view->lines)
5671 view->p_lineno = view->lines - 1;
5672 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5673 view->p_lineno++;
5674 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5675 view->p_lineno--;
5677 /* If the above fails, always skip the "On branch" line. */
5678 if (view->p_lineno < view->lines)
5679 view->lineno = view->p_lineno;
5680 else
5681 view->lineno = 1;
5683 if (view->lineno < view->offset)
5684 view->offset = view->lineno;
5685 else if (view->offset + view->height <= view->lineno)
5686 view->offset = view->lineno - view->height + 1;
5688 view->p_restore = FALSE;
5689 }
5691 static void
5692 status_update_onbranch(void)
5693 {
5694 static const char *paths[][2] = {
5695 { "rebase-apply/rebasing", "Rebasing" },
5696 { "rebase-apply/applying", "Applying mailbox" },
5697 { "rebase-apply/", "Rebasing mailbox" },
5698 { "rebase-merge/interactive", "Interactive rebase" },
5699 { "rebase-merge/", "Rebase merge" },
5700 { "MERGE_HEAD", "Merging" },
5701 { "BISECT_LOG", "Bisecting" },
5702 { "HEAD", "On branch" },
5703 };
5704 char buf[SIZEOF_STR];
5705 struct stat stat;
5706 int i;
5708 if (is_initial_commit()) {
5709 string_copy(status_onbranch, "Initial commit");
5710 return;
5711 }
5713 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5714 char *head = opt_head;
5716 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5717 lstat(buf, &stat) < 0)
5718 continue;
5720 if (!*opt_head) {
5721 struct io io = {};
5723 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5724 io_read_buf(&io, buf, sizeof(buf))) {
5725 head = buf;
5726 if (!prefixcmp(head, "refs/heads/"))
5727 head += STRING_SIZE("refs/heads/");
5728 }
5729 }
5731 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5732 string_copy(status_onbranch, opt_head);
5733 return;
5734 }
5736 string_copy(status_onbranch, "Not currently on any branch");
5737 }
5739 /* First parse staged info using git-diff-index(1), then parse unstaged
5740 * info using git-diff-files(1), and finally untracked files using
5741 * git-ls-files(1). */
5742 static bool
5743 status_open(struct view *view)
5744 {
5745 reset_view(view);
5747 add_line_data(view, NULL, LINE_STAT_HEAD);
5748 status_update_onbranch();
5750 io_run_bg(update_index_argv);
5752 if (is_initial_commit()) {
5753 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5754 return FALSE;
5755 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5756 return FALSE;
5757 }
5759 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5760 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5761 return FALSE;
5763 /* Restore the exact position or use the specialized restore
5764 * mode? */
5765 if (!view->p_restore)
5766 status_restore(view);
5767 return TRUE;
5768 }
5770 static bool
5771 status_draw(struct view *view, struct line *line, unsigned int lineno)
5772 {
5773 struct status *status = line->data;
5774 enum line_type type;
5775 const char *text;
5777 if (!status) {
5778 switch (line->type) {
5779 case LINE_STAT_STAGED:
5780 type = LINE_STAT_SECTION;
5781 text = "Changes to be committed:";
5782 break;
5784 case LINE_STAT_UNSTAGED:
5785 type = LINE_STAT_SECTION;
5786 text = "Changed but not updated:";
5787 break;
5789 case LINE_STAT_UNTRACKED:
5790 type = LINE_STAT_SECTION;
5791 text = "Untracked files:";
5792 break;
5794 case LINE_STAT_NONE:
5795 type = LINE_DEFAULT;
5796 text = " (no files)";
5797 break;
5799 case LINE_STAT_HEAD:
5800 type = LINE_STAT_HEAD;
5801 text = status_onbranch;
5802 break;
5804 default:
5805 return FALSE;
5806 }
5807 } else {
5808 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5810 buf[0] = status->status;
5811 if (draw_text(view, line->type, buf, TRUE))
5812 return TRUE;
5813 type = LINE_DEFAULT;
5814 text = status->new.name;
5815 }
5817 draw_text(view, type, text, TRUE);
5818 return TRUE;
5819 }
5821 static enum request
5822 status_load_error(struct view *view, struct view *stage, const char *path)
5823 {
5824 if (displayed_views() == 2 || display[current_view] != view)
5825 maximize_view(view);
5826 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5827 return REQ_NONE;
5828 }
5830 static enum request
5831 status_enter(struct view *view, struct line *line)
5832 {
5833 struct status *status = line->data;
5834 const char *oldpath = status ? status->old.name : NULL;
5835 /* Diffs for unmerged entries are empty when passing the new
5836 * path, so leave it empty. */
5837 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5838 const char *info;
5839 enum open_flags split;
5840 struct view *stage = VIEW(REQ_VIEW_STAGE);
5842 if (line->type == LINE_STAT_NONE ||
5843 (!status && line[1].type == LINE_STAT_NONE)) {
5844 report("No file to diff");
5845 return REQ_NONE;
5846 }
5848 switch (line->type) {
5849 case LINE_STAT_STAGED:
5850 if (is_initial_commit()) {
5851 const char *no_head_diff_argv[] = {
5852 "git", "diff", "--no-color", "--patch-with-stat",
5853 "--", "/dev/null", newpath, NULL
5854 };
5856 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5857 return status_load_error(view, stage, newpath);
5858 } else {
5859 const char *index_show_argv[] = {
5860 "git", "diff-index", "--root", "--patch-with-stat",
5861 "-C", "-M", "--cached", "HEAD", "--",
5862 oldpath, newpath, NULL
5863 };
5865 if (!prepare_update(stage, index_show_argv, opt_cdup))
5866 return status_load_error(view, stage, newpath);
5867 }
5869 if (status)
5870 info = "Staged changes to %s";
5871 else
5872 info = "Staged changes";
5873 break;
5875 case LINE_STAT_UNSTAGED:
5876 {
5877 const char *files_show_argv[] = {
5878 "git", "diff-files", "--root", "--patch-with-stat",
5879 "-C", "-M", "--", oldpath, newpath, NULL
5880 };
5882 if (!prepare_update(stage, files_show_argv, opt_cdup))
5883 return status_load_error(view, stage, newpath);
5884 if (status)
5885 info = "Unstaged changes to %s";
5886 else
5887 info = "Unstaged changes";
5888 break;
5889 }
5890 case LINE_STAT_UNTRACKED:
5891 if (!newpath) {
5892 report("No file to show");
5893 return REQ_NONE;
5894 }
5896 if (!suffixcmp(status->new.name, -1, "/")) {
5897 report("Cannot display a directory");
5898 return REQ_NONE;
5899 }
5901 if (!prepare_update_file(stage, newpath))
5902 return status_load_error(view, stage, newpath);
5903 info = "Untracked file %s";
5904 break;
5906 case LINE_STAT_HEAD:
5907 return REQ_NONE;
5909 default:
5910 die("line type %d not handled in switch", line->type);
5911 }
5913 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5914 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5915 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5916 if (status) {
5917 stage_status = *status;
5918 } else {
5919 memset(&stage_status, 0, sizeof(stage_status));
5920 }
5922 stage_line_type = line->type;
5923 stage_chunks = 0;
5924 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5925 }
5927 return REQ_NONE;
5928 }
5930 static bool
5931 status_exists(struct status *status, enum line_type type)
5932 {
5933 struct view *view = VIEW(REQ_VIEW_STATUS);
5934 unsigned long lineno;
5936 for (lineno = 0; lineno < view->lines; lineno++) {
5937 struct line *line = &view->line[lineno];
5938 struct status *pos = line->data;
5940 if (line->type != type)
5941 continue;
5942 if (!pos && (!status || !status->status) && line[1].data) {
5943 select_view_line(view, lineno);
5944 return TRUE;
5945 }
5946 if (pos && !strcmp(status->new.name, pos->new.name)) {
5947 select_view_line(view, lineno);
5948 return TRUE;
5949 }
5950 }
5952 return FALSE;
5953 }
5956 static bool
5957 status_update_prepare(struct io *io, enum line_type type)
5958 {
5959 const char *staged_argv[] = {
5960 "git", "update-index", "-z", "--index-info", NULL
5961 };
5962 const char *others_argv[] = {
5963 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5964 };
5966 switch (type) {
5967 case LINE_STAT_STAGED:
5968 return io_run(io, staged_argv, opt_cdup, IO_WR);
5970 case LINE_STAT_UNSTAGED:
5971 case LINE_STAT_UNTRACKED:
5972 return io_run(io, others_argv, opt_cdup, IO_WR);
5974 default:
5975 die("line type %d not handled in switch", type);
5976 return FALSE;
5977 }
5978 }
5980 static bool
5981 status_update_write(struct io *io, struct status *status, enum line_type type)
5982 {
5983 char buf[SIZEOF_STR];
5984 size_t bufsize = 0;
5986 switch (type) {
5987 case LINE_STAT_STAGED:
5988 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5989 status->old.mode,
5990 status->old.rev,
5991 status->old.name, 0))
5992 return FALSE;
5993 break;
5995 case LINE_STAT_UNSTAGED:
5996 case LINE_STAT_UNTRACKED:
5997 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5998 return FALSE;
5999 break;
6001 default:
6002 die("line type %d not handled in switch", type);
6003 }
6005 return io_write(io, buf, bufsize);
6006 }
6008 static bool
6009 status_update_file(struct status *status, enum line_type type)
6010 {
6011 struct io io = {};
6012 bool result;
6014 if (!status_update_prepare(&io, type))
6015 return FALSE;
6017 result = status_update_write(&io, status, type);
6018 return io_done(&io) && result;
6019 }
6021 static bool
6022 status_update_files(struct view *view, struct line *line)
6023 {
6024 char buf[sizeof(view->ref)];
6025 struct io io = {};
6026 bool result = TRUE;
6027 struct line *pos = view->line + view->lines;
6028 int files = 0;
6029 int file, done;
6030 int cursor_y = -1, cursor_x = -1;
6032 if (!status_update_prepare(&io, line->type))
6033 return FALSE;
6035 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6036 files++;
6038 string_copy(buf, view->ref);
6039 getsyx(cursor_y, cursor_x);
6040 for (file = 0, done = 5; result && file < files; line++, file++) {
6041 int almost_done = file * 100 / files;
6043 if (almost_done > done) {
6044 done = almost_done;
6045 string_format(view->ref, "updating file %u of %u (%d%% done)",
6046 file, files, done);
6047 update_view_title(view);
6048 setsyx(cursor_y, cursor_x);
6049 doupdate();
6050 }
6051 result = status_update_write(&io, line->data, line->type);
6052 }
6053 string_copy(view->ref, buf);
6055 return io_done(&io) && result;
6056 }
6058 static bool
6059 status_update(struct view *view)
6060 {
6061 struct line *line = &view->line[view->lineno];
6063 assert(view->lines);
6065 if (!line->data) {
6066 /* This should work even for the "On branch" line. */
6067 if (line < view->line + view->lines && !line[1].data) {
6068 report("Nothing to update");
6069 return FALSE;
6070 }
6072 if (!status_update_files(view, line + 1)) {
6073 report("Failed to update file status");
6074 return FALSE;
6075 }
6077 } else if (!status_update_file(line->data, line->type)) {
6078 report("Failed to update file status");
6079 return FALSE;
6080 }
6082 return TRUE;
6083 }
6085 static bool
6086 status_revert(struct status *status, enum line_type type, bool has_none)
6087 {
6088 if (!status || type != LINE_STAT_UNSTAGED) {
6089 if (type == LINE_STAT_STAGED) {
6090 report("Cannot revert changes to staged files");
6091 } else if (type == LINE_STAT_UNTRACKED) {
6092 report("Cannot revert changes to untracked files");
6093 } else if (has_none) {
6094 report("Nothing to revert");
6095 } else {
6096 report("Cannot revert changes to multiple files");
6097 }
6099 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6100 char mode[10] = "100644";
6101 const char *reset_argv[] = {
6102 "git", "update-index", "--cacheinfo", mode,
6103 status->old.rev, status->old.name, NULL
6104 };
6105 const char *checkout_argv[] = {
6106 "git", "checkout", "--", status->old.name, NULL
6107 };
6109 if (status->status == 'U') {
6110 string_format(mode, "%5o", status->old.mode);
6112 if (status->old.mode == 0 && status->new.mode == 0) {
6113 reset_argv[2] = "--force-remove";
6114 reset_argv[3] = status->old.name;
6115 reset_argv[4] = NULL;
6116 }
6118 if (!io_run_fg(reset_argv, opt_cdup))
6119 return FALSE;
6120 if (status->old.mode == 0 && status->new.mode == 0)
6121 return TRUE;
6122 }
6124 return io_run_fg(checkout_argv, opt_cdup);
6125 }
6127 return FALSE;
6128 }
6130 static enum request
6131 status_request(struct view *view, enum request request, struct line *line)
6132 {
6133 struct status *status = line->data;
6135 switch (request) {
6136 case REQ_STATUS_UPDATE:
6137 if (!status_update(view))
6138 return REQ_NONE;
6139 break;
6141 case REQ_STATUS_REVERT:
6142 if (!status_revert(status, line->type, status_has_none(view, line)))
6143 return REQ_NONE;
6144 break;
6146 case REQ_STATUS_MERGE:
6147 if (!status || status->status != 'U') {
6148 report("Merging only possible for files with unmerged status ('U').");
6149 return REQ_NONE;
6150 }
6151 open_mergetool(status->new.name);
6152 break;
6154 case REQ_EDIT:
6155 if (!status)
6156 return request;
6157 if (status->status == 'D') {
6158 report("File has been deleted.");
6159 return REQ_NONE;
6160 }
6162 open_editor(status->new.name);
6163 break;
6165 case REQ_VIEW_BLAME:
6166 if (status)
6167 opt_ref[0] = 0;
6168 return request;
6170 case REQ_ENTER:
6171 /* After returning the status view has been split to
6172 * show the stage view. No further reloading is
6173 * necessary. */
6174 return status_enter(view, line);
6176 case REQ_REFRESH:
6177 /* Simply reload the view. */
6178 break;
6180 default:
6181 return request;
6182 }
6184 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6186 return REQ_NONE;
6187 }
6189 static void
6190 status_select(struct view *view, struct line *line)
6191 {
6192 struct status *status = line->data;
6193 char file[SIZEOF_STR] = "all files";
6194 const char *text;
6195 const char *key;
6197 if (status && !string_format(file, "'%s'", status->new.name))
6198 return;
6200 if (!status && line[1].type == LINE_STAT_NONE)
6201 line++;
6203 switch (line->type) {
6204 case LINE_STAT_STAGED:
6205 text = "Press %s to unstage %s for commit";
6206 break;
6208 case LINE_STAT_UNSTAGED:
6209 text = "Press %s to stage %s for commit";
6210 break;
6212 case LINE_STAT_UNTRACKED:
6213 text = "Press %s to stage %s for addition";
6214 break;
6216 case LINE_STAT_HEAD:
6217 case LINE_STAT_NONE:
6218 text = "Nothing to update";
6219 break;
6221 default:
6222 die("line type %d not handled in switch", line->type);
6223 }
6225 if (status && status->status == 'U') {
6226 text = "Press %s to resolve conflict in %s";
6227 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6229 } else {
6230 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6231 }
6233 string_format(view->ref, text, key, file);
6234 if (status)
6235 string_copy(opt_file, status->new.name);
6236 }
6238 static bool
6239 status_grep(struct view *view, struct line *line)
6240 {
6241 struct status *status = line->data;
6243 if (status) {
6244 const char buf[2] = { status->status, 0 };
6245 const char *text[] = { status->new.name, buf, NULL };
6247 return grep_text(view, text);
6248 }
6250 return FALSE;
6251 }
6253 static struct view_ops status_ops = {
6254 "file",
6255 NULL,
6256 status_open,
6257 NULL,
6258 status_draw,
6259 status_request,
6260 status_grep,
6261 status_select,
6262 };
6265 static bool
6266 stage_diff_write(struct io *io, struct line *line, struct line *end)
6267 {
6268 while (line < end) {
6269 if (!io_write(io, line->data, strlen(line->data)) ||
6270 !io_write(io, "\n", 1))
6271 return FALSE;
6272 line++;
6273 if (line->type == LINE_DIFF_CHUNK ||
6274 line->type == LINE_DIFF_HEADER)
6275 break;
6276 }
6278 return TRUE;
6279 }
6281 static struct line *
6282 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6283 {
6284 for (; view->line < line; line--)
6285 if (line->type == type)
6286 return line;
6288 return NULL;
6289 }
6291 static bool
6292 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6293 {
6294 const char *apply_argv[SIZEOF_ARG] = {
6295 "git", "apply", "--whitespace=nowarn", NULL
6296 };
6297 struct line *diff_hdr;
6298 struct io io = {};
6299 int argc = 3;
6301 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6302 if (!diff_hdr)
6303 return FALSE;
6305 if (!revert)
6306 apply_argv[argc++] = "--cached";
6307 if (revert || stage_line_type == LINE_STAT_STAGED)
6308 apply_argv[argc++] = "-R";
6309 apply_argv[argc++] = "-";
6310 apply_argv[argc++] = NULL;
6311 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6312 return FALSE;
6314 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6315 !stage_diff_write(&io, chunk, view->line + view->lines))
6316 chunk = NULL;
6318 io_done(&io);
6319 io_run_bg(update_index_argv);
6321 return chunk ? TRUE : FALSE;
6322 }
6324 static bool
6325 stage_update(struct view *view, struct line *line)
6326 {
6327 struct line *chunk = NULL;
6329 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6330 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6332 if (chunk) {
6333 if (!stage_apply_chunk(view, chunk, FALSE)) {
6334 report("Failed to apply chunk");
6335 return FALSE;
6336 }
6338 } else if (!stage_status.status) {
6339 view = VIEW(REQ_VIEW_STATUS);
6341 for (line = view->line; line < view->line + view->lines; line++)
6342 if (line->type == stage_line_type)
6343 break;
6345 if (!status_update_files(view, line + 1)) {
6346 report("Failed to update files");
6347 return FALSE;
6348 }
6350 } else if (!status_update_file(&stage_status, stage_line_type)) {
6351 report("Failed to update file");
6352 return FALSE;
6353 }
6355 return TRUE;
6356 }
6358 static bool
6359 stage_revert(struct view *view, struct line *line)
6360 {
6361 struct line *chunk = NULL;
6363 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6364 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6366 if (chunk) {
6367 if (!prompt_yesno("Are you sure you want to revert changes?"))
6368 return FALSE;
6370 if (!stage_apply_chunk(view, chunk, TRUE)) {
6371 report("Failed to revert chunk");
6372 return FALSE;
6373 }
6374 return TRUE;
6376 } else {
6377 return status_revert(stage_status.status ? &stage_status : NULL,
6378 stage_line_type, FALSE);
6379 }
6380 }
6383 static void
6384 stage_next(struct view *view, struct line *line)
6385 {
6386 int i;
6388 if (!stage_chunks) {
6389 for (line = view->line; line < view->line + view->lines; line++) {
6390 if (line->type != LINE_DIFF_CHUNK)
6391 continue;
6393 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6394 report("Allocation failure");
6395 return;
6396 }
6398 stage_chunk[stage_chunks++] = line - view->line;
6399 }
6400 }
6402 for (i = 0; i < stage_chunks; i++) {
6403 if (stage_chunk[i] > view->lineno) {
6404 do_scroll_view(view, stage_chunk[i] - view->lineno);
6405 report("Chunk %d of %d", i + 1, stage_chunks);
6406 return;
6407 }
6408 }
6410 report("No next chunk found");
6411 }
6413 static enum request
6414 stage_request(struct view *view, enum request request, struct line *line)
6415 {
6416 switch (request) {
6417 case REQ_STATUS_UPDATE:
6418 if (!stage_update(view, line))
6419 return REQ_NONE;
6420 break;
6422 case REQ_STATUS_REVERT:
6423 if (!stage_revert(view, line))
6424 return REQ_NONE;
6425 break;
6427 case REQ_STAGE_NEXT:
6428 if (stage_line_type == LINE_STAT_UNTRACKED) {
6429 report("File is untracked; press %s to add",
6430 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6431 return REQ_NONE;
6432 }
6433 stage_next(view, line);
6434 return REQ_NONE;
6436 case REQ_EDIT:
6437 if (!stage_status.new.name[0])
6438 return request;
6439 if (stage_status.status == 'D') {
6440 report("File has been deleted.");
6441 return REQ_NONE;
6442 }
6444 open_editor(stage_status.new.name);
6445 break;
6447 case REQ_REFRESH:
6448 /* Reload everything ... */
6449 break;
6451 case REQ_VIEW_BLAME:
6452 if (stage_status.new.name[0]) {
6453 string_copy(opt_file, stage_status.new.name);
6454 opt_ref[0] = 0;
6455 }
6456 return request;
6458 case REQ_ENTER:
6459 return pager_request(view, request, line);
6461 default:
6462 return request;
6463 }
6465 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6466 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6468 /* Check whether the staged entry still exists, and close the
6469 * stage view if it doesn't. */
6470 if (!status_exists(&stage_status, stage_line_type)) {
6471 status_restore(VIEW(REQ_VIEW_STATUS));
6472 return REQ_VIEW_CLOSE;
6473 }
6475 if (stage_line_type == LINE_STAT_UNTRACKED) {
6476 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6477 report("Cannot display a directory");
6478 return REQ_NONE;
6479 }
6481 if (!prepare_update_file(view, stage_status.new.name)) {
6482 report("Failed to open file: %s", strerror(errno));
6483 return REQ_NONE;
6484 }
6485 }
6486 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6488 return REQ_NONE;
6489 }
6491 static struct view_ops stage_ops = {
6492 "line",
6493 NULL,
6494 NULL,
6495 pager_read,
6496 pager_draw,
6497 stage_request,
6498 pager_grep,
6499 pager_select,
6500 };
6503 /*
6504 * Revision graph
6505 */
6507 struct commit {
6508 char id[SIZEOF_REV]; /* SHA1 ID. */
6509 char title[128]; /* First line of the commit message. */
6510 const char *author; /* Author of the commit. */
6511 struct time time; /* Date from the author ident. */
6512 struct ref_list *refs; /* Repository references. */
6513 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6514 size_t graph_size; /* The width of the graph array. */
6515 bool has_parents; /* Rewritten --parents seen. */
6516 };
6518 /* Size of rev graph with no "padding" columns */
6519 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6521 struct rev_graph {
6522 struct rev_graph *prev, *next, *parents;
6523 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6524 size_t size;
6525 struct commit *commit;
6526 size_t pos;
6527 unsigned int boundary:1;
6528 };
6530 /* Parents of the commit being visualized. */
6531 static struct rev_graph graph_parents[4];
6533 /* The current stack of revisions on the graph. */
6534 static struct rev_graph graph_stacks[4] = {
6535 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6536 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6537 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6538 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6539 };
6541 static inline bool
6542 graph_parent_is_merge(struct rev_graph *graph)
6543 {
6544 return graph->parents->size > 1;
6545 }
6547 static inline void
6548 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6549 {
6550 struct commit *commit = graph->commit;
6552 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6553 commit->graph[commit->graph_size++] = symbol;
6554 }
6556 static void
6557 clear_rev_graph(struct rev_graph *graph)
6558 {
6559 graph->boundary = 0;
6560 graph->size = graph->pos = 0;
6561 graph->commit = NULL;
6562 memset(graph->parents, 0, sizeof(*graph->parents));
6563 }
6565 static void
6566 done_rev_graph(struct rev_graph *graph)
6567 {
6568 if (graph_parent_is_merge(graph) &&
6569 graph->pos < graph->size - 1 &&
6570 graph->next->size == graph->size + graph->parents->size - 1) {
6571 size_t i = graph->pos + graph->parents->size - 1;
6573 graph->commit->graph_size = i * 2;
6574 while (i < graph->next->size - 1) {
6575 append_to_rev_graph(graph, ' ');
6576 append_to_rev_graph(graph, '\\');
6577 i++;
6578 }
6579 }
6581 clear_rev_graph(graph);
6582 }
6584 static void
6585 push_rev_graph(struct rev_graph *graph, const char *parent)
6586 {
6587 int i;
6589 /* "Collapse" duplicate parents lines.
6590 *
6591 * FIXME: This needs to also update update the drawn graph but
6592 * for now it just serves as a method for pruning graph lines. */
6593 for (i = 0; i < graph->size; i++)
6594 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6595 return;
6597 if (graph->size < SIZEOF_REVITEMS) {
6598 string_copy_rev(graph->rev[graph->size++], parent);
6599 }
6600 }
6602 static chtype
6603 get_rev_graph_symbol(struct rev_graph *graph)
6604 {
6605 chtype symbol;
6607 if (graph->boundary)
6608 symbol = REVGRAPH_BOUND;
6609 else if (graph->parents->size == 0)
6610 symbol = REVGRAPH_INIT;
6611 else if (graph_parent_is_merge(graph))
6612 symbol = REVGRAPH_MERGE;
6613 else if (graph->pos >= graph->size)
6614 symbol = REVGRAPH_BRANCH;
6615 else
6616 symbol = REVGRAPH_COMMIT;
6618 return symbol;
6619 }
6621 static void
6622 draw_rev_graph(struct rev_graph *graph)
6623 {
6624 struct rev_filler {
6625 chtype separator, line;
6626 };
6627 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6628 static struct rev_filler fillers[] = {
6629 { ' ', '|' },
6630 { '`', '.' },
6631 { '\'', ' ' },
6632 { '/', ' ' },
6633 };
6634 chtype symbol = get_rev_graph_symbol(graph);
6635 struct rev_filler *filler;
6636 size_t i;
6638 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6639 filler = &fillers[DEFAULT];
6641 for (i = 0; i < graph->pos; i++) {
6642 append_to_rev_graph(graph, filler->line);
6643 if (graph_parent_is_merge(graph->prev) &&
6644 graph->prev->pos == i)
6645 filler = &fillers[RSHARP];
6647 append_to_rev_graph(graph, filler->separator);
6648 }
6650 /* Place the symbol for this revision. */
6651 append_to_rev_graph(graph, symbol);
6653 if (graph->prev->size > graph->size)
6654 filler = &fillers[RDIAG];
6655 else
6656 filler = &fillers[DEFAULT];
6658 i++;
6660 for (; i < graph->size; i++) {
6661 append_to_rev_graph(graph, filler->separator);
6662 append_to_rev_graph(graph, filler->line);
6663 if (graph_parent_is_merge(graph->prev) &&
6664 i < graph->prev->pos + graph->parents->size)
6665 filler = &fillers[RSHARP];
6666 if (graph->prev->size > graph->size)
6667 filler = &fillers[LDIAG];
6668 }
6670 if (graph->prev->size > graph->size) {
6671 append_to_rev_graph(graph, filler->separator);
6672 if (filler->line != ' ')
6673 append_to_rev_graph(graph, filler->line);
6674 }
6675 }
6677 /* Prepare the next rev graph */
6678 static void
6679 prepare_rev_graph(struct rev_graph *graph)
6680 {
6681 size_t i;
6683 /* First, traverse all lines of revisions up to the active one. */
6684 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6685 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6686 break;
6688 push_rev_graph(graph->next, graph->rev[graph->pos]);
6689 }
6691 /* Interleave the new revision parent(s). */
6692 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6693 push_rev_graph(graph->next, graph->parents->rev[i]);
6695 /* Lastly, put any remaining revisions. */
6696 for (i = graph->pos + 1; i < graph->size; i++)
6697 push_rev_graph(graph->next, graph->rev[i]);
6698 }
6700 static void
6701 update_rev_graph(struct view *view, struct rev_graph *graph)
6702 {
6703 /* If this is the finalizing update ... */
6704 if (graph->commit)
6705 prepare_rev_graph(graph);
6707 /* Graph visualization needs a one rev look-ahead,
6708 * so the first update doesn't visualize anything. */
6709 if (!graph->prev->commit)
6710 return;
6712 if (view->lines > 2)
6713 view->line[view->lines - 3].dirty = 1;
6714 if (view->lines > 1)
6715 view->line[view->lines - 2].dirty = 1;
6716 draw_rev_graph(graph->prev);
6717 done_rev_graph(graph->prev->prev);
6718 }
6721 /*
6722 * Main view backend
6723 */
6725 static const char *main_argv[SIZEOF_ARG] = {
6726 "git", "log", "--no-color", "--pretty=raw", "--parents",
6727 "--topo-order", "%(head)", NULL
6728 };
6730 static bool
6731 main_draw(struct view *view, struct line *line, unsigned int lineno)
6732 {
6733 struct commit *commit = line->data;
6735 if (!commit->author)
6736 return FALSE;
6738 if (opt_date && draw_date(view, &commit->time))
6739 return TRUE;
6741 if (opt_author && draw_author(view, commit->author))
6742 return TRUE;
6744 if (opt_rev_graph && commit->graph_size &&
6745 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6746 return TRUE;
6748 if (opt_show_refs && commit->refs) {
6749 size_t i;
6751 for (i = 0; i < commit->refs->size; i++) {
6752 struct ref *ref = commit->refs->refs[i];
6753 enum line_type type;
6755 if (ref->head)
6756 type = LINE_MAIN_HEAD;
6757 else if (ref->ltag)
6758 type = LINE_MAIN_LOCAL_TAG;
6759 else if (ref->tag)
6760 type = LINE_MAIN_TAG;
6761 else if (ref->tracked)
6762 type = LINE_MAIN_TRACKED;
6763 else if (ref->remote)
6764 type = LINE_MAIN_REMOTE;
6765 else
6766 type = LINE_MAIN_REF;
6768 if (draw_text(view, type, "[", TRUE) ||
6769 draw_text(view, type, ref->name, TRUE) ||
6770 draw_text(view, type, "]", TRUE))
6771 return TRUE;
6773 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6774 return TRUE;
6775 }
6776 }
6778 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6779 return TRUE;
6780 }
6782 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6783 static bool
6784 main_read(struct view *view, char *line)
6785 {
6786 static struct rev_graph *graph = graph_stacks;
6787 enum line_type type;
6788 struct commit *commit;
6790 if (!line) {
6791 int i;
6793 if (!view->lines && !view->prev)
6794 die("No revisions match the given arguments.");
6795 if (view->lines > 0) {
6796 commit = view->line[view->lines - 1].data;
6797 view->line[view->lines - 1].dirty = 1;
6798 if (!commit->author) {
6799 view->lines--;
6800 free(commit);
6801 graph->commit = NULL;
6802 }
6803 }
6804 update_rev_graph(view, graph);
6806 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6807 clear_rev_graph(&graph_stacks[i]);
6808 return TRUE;
6809 }
6811 type = get_line_type(line);
6812 if (type == LINE_COMMIT) {
6813 commit = calloc(1, sizeof(struct commit));
6814 if (!commit)
6815 return FALSE;
6817 line += STRING_SIZE("commit ");
6818 if (*line == '-') {
6819 graph->boundary = 1;
6820 line++;
6821 }
6823 string_copy_rev(commit->id, line);
6824 commit->refs = get_ref_list(commit->id);
6825 graph->commit = commit;
6826 add_line_data(view, commit, LINE_MAIN_COMMIT);
6828 while ((line = strchr(line, ' '))) {
6829 line++;
6830 push_rev_graph(graph->parents, line);
6831 commit->has_parents = TRUE;
6832 }
6833 return TRUE;
6834 }
6836 if (!view->lines)
6837 return TRUE;
6838 commit = view->line[view->lines - 1].data;
6840 switch (type) {
6841 case LINE_PARENT:
6842 if (commit->has_parents)
6843 break;
6844 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6845 break;
6847 case LINE_AUTHOR:
6848 parse_author_line(line + STRING_SIZE("author "),
6849 &commit->author, &commit->time);
6850 update_rev_graph(view, graph);
6851 graph = graph->next;
6852 break;
6854 default:
6855 /* Fill in the commit title if it has not already been set. */
6856 if (commit->title[0])
6857 break;
6859 /* Require titles to start with a non-space character at the
6860 * offset used by git log. */
6861 if (strncmp(line, " ", 4))
6862 break;
6863 line += 4;
6864 /* Well, if the title starts with a whitespace character,
6865 * try to be forgiving. Otherwise we end up with no title. */
6866 while (isspace(*line))
6867 line++;
6868 if (*line == '\0')
6869 break;
6870 /* FIXME: More graceful handling of titles; append "..." to
6871 * shortened titles, etc. */
6873 string_expand(commit->title, sizeof(commit->title), line, 1);
6874 view->line[view->lines - 1].dirty = 1;
6875 }
6877 return TRUE;
6878 }
6880 static enum request
6881 main_request(struct view *view, enum request request, struct line *line)
6882 {
6883 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6885 switch (request) {
6886 case REQ_ENTER:
6887 open_view(view, REQ_VIEW_DIFF, flags);
6888 break;
6889 case REQ_REFRESH:
6890 load_refs();
6891 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6892 break;
6893 default:
6894 return request;
6895 }
6897 return REQ_NONE;
6898 }
6900 static bool
6901 grep_refs(struct ref_list *list, regex_t *regex)
6902 {
6903 regmatch_t pmatch;
6904 size_t i;
6906 if (!opt_show_refs || !list)
6907 return FALSE;
6909 for (i = 0; i < list->size; i++) {
6910 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6911 return TRUE;
6912 }
6914 return FALSE;
6915 }
6917 static bool
6918 main_grep(struct view *view, struct line *line)
6919 {
6920 struct commit *commit = line->data;
6921 const char *text[] = {
6922 commit->title,
6923 opt_author ? commit->author : "",
6924 mkdate(&commit->time, opt_date),
6925 NULL
6926 };
6928 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6929 }
6931 static void
6932 main_select(struct view *view, struct line *line)
6933 {
6934 struct commit *commit = line->data;
6936 string_copy_rev(view->ref, commit->id);
6937 string_copy_rev(ref_commit, view->ref);
6938 }
6940 static struct view_ops main_ops = {
6941 "commit",
6942 main_argv,
6943 NULL,
6944 main_read,
6945 main_draw,
6946 main_request,
6947 main_grep,
6948 main_select,
6949 };
6952 /*
6953 * Status management
6954 */
6956 /* Whether or not the curses interface has been initialized. */
6957 static bool cursed = FALSE;
6959 /* Terminal hacks and workarounds. */
6960 static bool use_scroll_redrawwin;
6961 static bool use_scroll_status_wclear;
6963 /* The status window is used for polling keystrokes. */
6964 static WINDOW *status_win;
6966 /* Reading from the prompt? */
6967 static bool input_mode = FALSE;
6969 static bool status_empty = FALSE;
6971 /* Update status and title window. */
6972 static void
6973 report(const char *msg, ...)
6974 {
6975 struct view *view = display[current_view];
6977 if (input_mode)
6978 return;
6980 if (!view) {
6981 char buf[SIZEOF_STR];
6982 va_list args;
6984 va_start(args, msg);
6985 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6986 buf[sizeof(buf) - 1] = 0;
6987 buf[sizeof(buf) - 2] = '.';
6988 buf[sizeof(buf) - 3] = '.';
6989 buf[sizeof(buf) - 4] = '.';
6990 }
6991 va_end(args);
6992 die("%s", buf);
6993 }
6995 if (!status_empty || *msg) {
6996 va_list args;
6998 va_start(args, msg);
7000 wmove(status_win, 0, 0);
7001 if (view->has_scrolled && use_scroll_status_wclear)
7002 wclear(status_win);
7003 if (*msg) {
7004 vwprintw(status_win, msg, args);
7005 status_empty = FALSE;
7006 } else {
7007 status_empty = TRUE;
7008 }
7009 wclrtoeol(status_win);
7010 wnoutrefresh(status_win);
7012 va_end(args);
7013 }
7015 update_view_title(view);
7016 }
7018 static void
7019 init_display(void)
7020 {
7021 const char *term;
7022 int x, y;
7024 /* Initialize the curses library */
7025 if (isatty(STDIN_FILENO)) {
7026 cursed = !!initscr();
7027 opt_tty = stdin;
7028 } else {
7029 /* Leave stdin and stdout alone when acting as a pager. */
7030 opt_tty = fopen("/dev/tty", "r+");
7031 if (!opt_tty)
7032 die("Failed to open /dev/tty");
7033 cursed = !!newterm(NULL, opt_tty, opt_tty);
7034 }
7036 if (!cursed)
7037 die("Failed to initialize curses");
7039 nonl(); /* Disable conversion and detect newlines from input. */
7040 cbreak(); /* Take input chars one at a time, no wait for \n */
7041 noecho(); /* Don't echo input */
7042 leaveok(stdscr, FALSE);
7044 if (has_colors())
7045 init_colors();
7047 getmaxyx(stdscr, y, x);
7048 status_win = newwin(1, 0, y - 1, 0);
7049 if (!status_win)
7050 die("Failed to create status window");
7052 /* Enable keyboard mapping */
7053 keypad(status_win, TRUE);
7054 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7056 TABSIZE = opt_tab_size;
7058 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7059 if (term && !strcmp(term, "gnome-terminal")) {
7060 /* In the gnome-terminal-emulator, the message from
7061 * scrolling up one line when impossible followed by
7062 * scrolling down one line causes corruption of the
7063 * status line. This is fixed by calling wclear. */
7064 use_scroll_status_wclear = TRUE;
7065 use_scroll_redrawwin = FALSE;
7067 } else if (term && !strcmp(term, "xrvt-xpm")) {
7068 /* No problems with full optimizations in xrvt-(unicode)
7069 * and aterm. */
7070 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7072 } else {
7073 /* When scrolling in (u)xterm the last line in the
7074 * scrolling direction will update slowly. */
7075 use_scroll_redrawwin = TRUE;
7076 use_scroll_status_wclear = FALSE;
7077 }
7078 }
7080 static int
7081 get_input(int prompt_position)
7082 {
7083 struct view *view;
7084 int i, key, cursor_y, cursor_x;
7085 bool loading = FALSE;
7087 if (prompt_position)
7088 input_mode = TRUE;
7090 while (TRUE) {
7091 foreach_view (view, i) {
7092 update_view(view);
7093 if (view_is_displayed(view) && view->has_scrolled &&
7094 use_scroll_redrawwin)
7095 redrawwin(view->win);
7096 view->has_scrolled = FALSE;
7097 if (view->pipe)
7098 loading = TRUE;
7099 }
7101 /* Update the cursor position. */
7102 if (prompt_position) {
7103 getbegyx(status_win, cursor_y, cursor_x);
7104 cursor_x = prompt_position;
7105 } else {
7106 view = display[current_view];
7107 getbegyx(view->win, cursor_y, cursor_x);
7108 cursor_x = view->width - 1;
7109 cursor_y += view->lineno - view->offset;
7110 }
7111 setsyx(cursor_y, cursor_x);
7113 /* Refresh, accept single keystroke of input */
7114 doupdate();
7115 nodelay(status_win, loading);
7116 key = wgetch(status_win);
7118 /* wgetch() with nodelay() enabled returns ERR when
7119 * there's no input. */
7120 if (key == ERR) {
7122 } else if (key == KEY_RESIZE) {
7123 int height, width;
7125 getmaxyx(stdscr, height, width);
7127 wresize(status_win, 1, width);
7128 mvwin(status_win, height - 1, 0);
7129 wnoutrefresh(status_win);
7130 resize_display();
7131 redraw_display(TRUE);
7133 } else {
7134 input_mode = FALSE;
7135 return key;
7136 }
7137 }
7138 }
7140 static char *
7141 prompt_input(const char *prompt, input_handler handler, void *data)
7142 {
7143 enum input_status status = INPUT_OK;
7144 static char buf[SIZEOF_STR];
7145 size_t pos = 0;
7147 buf[pos] = 0;
7149 while (status == INPUT_OK || status == INPUT_SKIP) {
7150 int key;
7152 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7153 wclrtoeol(status_win);
7155 key = get_input(pos + 1);
7156 switch (key) {
7157 case KEY_RETURN:
7158 case KEY_ENTER:
7159 case '\n':
7160 status = pos ? INPUT_STOP : INPUT_CANCEL;
7161 break;
7163 case KEY_BACKSPACE:
7164 if (pos > 0)
7165 buf[--pos] = 0;
7166 else
7167 status = INPUT_CANCEL;
7168 break;
7170 case KEY_ESC:
7171 status = INPUT_CANCEL;
7172 break;
7174 default:
7175 if (pos >= sizeof(buf)) {
7176 report("Input string too long");
7177 return NULL;
7178 }
7180 status = handler(data, buf, key);
7181 if (status == INPUT_OK)
7182 buf[pos++] = (char) key;
7183 }
7184 }
7186 /* Clear the status window */
7187 status_empty = FALSE;
7188 report("");
7190 if (status == INPUT_CANCEL)
7191 return NULL;
7193 buf[pos++] = 0;
7195 return buf;
7196 }
7198 static enum input_status
7199 prompt_yesno_handler(void *data, char *buf, int c)
7200 {
7201 if (c == 'y' || c == 'Y')
7202 return INPUT_STOP;
7203 if (c == 'n' || c == 'N')
7204 return INPUT_CANCEL;
7205 return INPUT_SKIP;
7206 }
7208 static bool
7209 prompt_yesno(const char *prompt)
7210 {
7211 char prompt2[SIZEOF_STR];
7213 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7214 return FALSE;
7216 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7217 }
7219 static enum input_status
7220 read_prompt_handler(void *data, char *buf, int c)
7221 {
7222 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7223 }
7225 static char *
7226 read_prompt(const char *prompt)
7227 {
7228 return prompt_input(prompt, read_prompt_handler, NULL);
7229 }
7231 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7232 {
7233 enum input_status status = INPUT_OK;
7234 int size = 0;
7236 while (items[size].text)
7237 size++;
7239 while (status == INPUT_OK) {
7240 const struct menu_item *item = &items[*selected];
7241 int key;
7242 int i;
7244 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7245 prompt, *selected + 1, size);
7246 if (item->hotkey)
7247 wprintw(status_win, "[%c] ", (char) item->hotkey);
7248 wprintw(status_win, "%s", item->text);
7249 wclrtoeol(status_win);
7251 key = get_input(COLS - 1);
7252 switch (key) {
7253 case KEY_RETURN:
7254 case KEY_ENTER:
7255 case '\n':
7256 status = INPUT_STOP;
7257 break;
7259 case KEY_LEFT:
7260 case KEY_UP:
7261 *selected = *selected - 1;
7262 if (*selected < 0)
7263 *selected = size - 1;
7264 break;
7266 case KEY_RIGHT:
7267 case KEY_DOWN:
7268 *selected = (*selected + 1) % size;
7269 break;
7271 case KEY_ESC:
7272 status = INPUT_CANCEL;
7273 break;
7275 default:
7276 for (i = 0; items[i].text; i++)
7277 if (items[i].hotkey == key) {
7278 *selected = i;
7279 status = INPUT_STOP;
7280 break;
7281 }
7282 }
7283 }
7285 /* Clear the status window */
7286 status_empty = FALSE;
7287 report("");
7289 return status != INPUT_CANCEL;
7290 }
7292 /*
7293 * Repository properties
7294 */
7296 static struct ref **refs = NULL;
7297 static size_t refs_size = 0;
7298 static struct ref *refs_head = NULL;
7300 static struct ref_list **ref_lists = NULL;
7301 static size_t ref_lists_size = 0;
7303 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7304 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7305 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7307 static int
7308 compare_refs(const void *ref1_, const void *ref2_)
7309 {
7310 const struct ref *ref1 = *(const struct ref **)ref1_;
7311 const struct ref *ref2 = *(const struct ref **)ref2_;
7313 if (ref1->tag != ref2->tag)
7314 return ref2->tag - ref1->tag;
7315 if (ref1->ltag != ref2->ltag)
7316 return ref2->ltag - ref2->ltag;
7317 if (ref1->head != ref2->head)
7318 return ref2->head - ref1->head;
7319 if (ref1->tracked != ref2->tracked)
7320 return ref2->tracked - ref1->tracked;
7321 if (ref1->remote != ref2->remote)
7322 return ref2->remote - ref1->remote;
7323 return strcmp(ref1->name, ref2->name);
7324 }
7326 static void
7327 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7328 {
7329 size_t i;
7331 for (i = 0; i < refs_size; i++)
7332 if (!visitor(data, refs[i]))
7333 break;
7334 }
7336 static struct ref *
7337 get_ref_head()
7338 {
7339 return refs_head;
7340 }
7342 static struct ref_list *
7343 get_ref_list(const char *id)
7344 {
7345 struct ref_list *list;
7346 size_t i;
7348 for (i = 0; i < ref_lists_size; i++)
7349 if (!strcmp(id, ref_lists[i]->id))
7350 return ref_lists[i];
7352 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7353 return NULL;
7354 list = calloc(1, sizeof(*list));
7355 if (!list)
7356 return NULL;
7358 for (i = 0; i < refs_size; i++) {
7359 if (!strcmp(id, refs[i]->id) &&
7360 realloc_refs_list(&list->refs, list->size, 1))
7361 list->refs[list->size++] = refs[i];
7362 }
7364 if (!list->refs) {
7365 free(list);
7366 return NULL;
7367 }
7369 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7370 ref_lists[ref_lists_size++] = list;
7371 return list;
7372 }
7374 static int
7375 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7376 {
7377 struct ref *ref = NULL;
7378 bool tag = FALSE;
7379 bool ltag = FALSE;
7380 bool remote = FALSE;
7381 bool tracked = FALSE;
7382 bool head = FALSE;
7383 int from = 0, to = refs_size - 1;
7385 if (!prefixcmp(name, "refs/tags/")) {
7386 if (!suffixcmp(name, namelen, "^{}")) {
7387 namelen -= 3;
7388 name[namelen] = 0;
7389 } else {
7390 ltag = TRUE;
7391 }
7393 tag = TRUE;
7394 namelen -= STRING_SIZE("refs/tags/");
7395 name += STRING_SIZE("refs/tags/");
7397 } else if (!prefixcmp(name, "refs/remotes/")) {
7398 remote = TRUE;
7399 namelen -= STRING_SIZE("refs/remotes/");
7400 name += STRING_SIZE("refs/remotes/");
7401 tracked = !strcmp(opt_remote, name);
7403 } else if (!prefixcmp(name, "refs/heads/")) {
7404 namelen -= STRING_SIZE("refs/heads/");
7405 name += STRING_SIZE("refs/heads/");
7406 if (!strncmp(opt_head, name, namelen))
7407 return OK;
7409 } else if (!strcmp(name, "HEAD")) {
7410 head = TRUE;
7411 if (*opt_head) {
7412 namelen = strlen(opt_head);
7413 name = opt_head;
7414 }
7415 }
7417 /* If we are reloading or it's an annotated tag, replace the
7418 * previous SHA1 with the resolved commit id; relies on the fact
7419 * git-ls-remote lists the commit id of an annotated tag right
7420 * before the commit id it points to. */
7421 while (from <= to) {
7422 size_t pos = (to + from) / 2;
7423 int cmp = strcmp(name, refs[pos]->name);
7425 if (!cmp) {
7426 ref = refs[pos];
7427 break;
7428 }
7430 if (cmp < 0)
7431 to = pos - 1;
7432 else
7433 from = pos + 1;
7434 }
7436 if (!ref) {
7437 if (!realloc_refs(&refs, refs_size, 1))
7438 return ERR;
7439 ref = calloc(1, sizeof(*ref) + namelen);
7440 if (!ref)
7441 return ERR;
7442 memmove(refs + from + 1, refs + from,
7443 (refs_size - from) * sizeof(*refs));
7444 refs[from] = ref;
7445 strncpy(ref->name, name, namelen);
7446 refs_size++;
7447 }
7449 ref->head = head;
7450 ref->tag = tag;
7451 ref->ltag = ltag;
7452 ref->remote = remote;
7453 ref->tracked = tracked;
7454 string_copy_rev(ref->id, id);
7456 if (head)
7457 refs_head = ref;
7458 return OK;
7459 }
7461 static int
7462 load_refs(void)
7463 {
7464 const char *head_argv[] = {
7465 "git", "symbolic-ref", "HEAD", NULL
7466 };
7467 static const char *ls_remote_argv[SIZEOF_ARG] = {
7468 "git", "ls-remote", opt_git_dir, NULL
7469 };
7470 static bool init = FALSE;
7471 size_t i;
7473 if (!init) {
7474 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7475 die("TIG_LS_REMOTE contains too many arguments");
7476 init = TRUE;
7477 }
7479 if (!*opt_git_dir)
7480 return OK;
7482 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7483 !prefixcmp(opt_head, "refs/heads/")) {
7484 char *offset = opt_head + STRING_SIZE("refs/heads/");
7486 memmove(opt_head, offset, strlen(offset) + 1);
7487 }
7489 refs_head = NULL;
7490 for (i = 0; i < refs_size; i++)
7491 refs[i]->id[0] = 0;
7493 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7494 return ERR;
7496 /* Update the ref lists to reflect changes. */
7497 for (i = 0; i < ref_lists_size; i++) {
7498 struct ref_list *list = ref_lists[i];
7499 size_t old, new;
7501 for (old = new = 0; old < list->size; old++)
7502 if (!strcmp(list->id, list->refs[old]->id))
7503 list->refs[new++] = list->refs[old];
7504 list->size = new;
7505 }
7507 return OK;
7508 }
7510 static void
7511 set_remote_branch(const char *name, const char *value, size_t valuelen)
7512 {
7513 if (!strcmp(name, ".remote")) {
7514 string_ncopy(opt_remote, value, valuelen);
7516 } else if (*opt_remote && !strcmp(name, ".merge")) {
7517 size_t from = strlen(opt_remote);
7519 if (!prefixcmp(value, "refs/heads/"))
7520 value += STRING_SIZE("refs/heads/");
7522 if (!string_format_from(opt_remote, &from, "/%s", value))
7523 opt_remote[0] = 0;
7524 }
7525 }
7527 static void
7528 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7529 {
7530 const char *argv[SIZEOF_ARG] = { name, "=" };
7531 int argc = 1 + (cmd == option_set_command);
7532 int error = ERR;
7534 if (!argv_from_string(argv, &argc, value))
7535 config_msg = "Too many option arguments";
7536 else
7537 error = cmd(argc, argv);
7539 if (error == ERR)
7540 warn("Option 'tig.%s': %s", name, config_msg);
7541 }
7543 static bool
7544 set_environment_variable(const char *name, const char *value)
7545 {
7546 size_t len = strlen(name) + 1 + strlen(value) + 1;
7547 char *env = malloc(len);
7549 if (env &&
7550 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7551 putenv(env) == 0)
7552 return TRUE;
7553 free(env);
7554 return FALSE;
7555 }
7557 static void
7558 set_work_tree(const char *value)
7559 {
7560 char cwd[SIZEOF_STR];
7562 if (!getcwd(cwd, sizeof(cwd)))
7563 die("Failed to get cwd path: %s", strerror(errno));
7564 if (chdir(opt_git_dir) < 0)
7565 die("Failed to chdir(%s): %s", strerror(errno));
7566 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7567 die("Failed to get git path: %s", strerror(errno));
7568 if (chdir(cwd) < 0)
7569 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7570 if (chdir(value) < 0)
7571 die("Failed to chdir(%s): %s", value, strerror(errno));
7572 if (!getcwd(cwd, sizeof(cwd)))
7573 die("Failed to get cwd path: %s", strerror(errno));
7574 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7575 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7576 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7577 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7578 opt_is_inside_work_tree = TRUE;
7579 }
7581 static int
7582 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7583 {
7584 if (!strcmp(name, "i18n.commitencoding"))
7585 string_ncopy(opt_encoding, value, valuelen);
7587 else if (!strcmp(name, "core.editor"))
7588 string_ncopy(opt_editor, value, valuelen);
7590 else if (!strcmp(name, "core.worktree"))
7591 set_work_tree(value);
7593 else if (!prefixcmp(name, "tig.color."))
7594 set_repo_config_option(name + 10, value, option_color_command);
7596 else if (!prefixcmp(name, "tig.bind."))
7597 set_repo_config_option(name + 9, value, option_bind_command);
7599 else if (!prefixcmp(name, "tig."))
7600 set_repo_config_option(name + 4, value, option_set_command);
7602 else if (*opt_head && !prefixcmp(name, "branch.") &&
7603 !strncmp(name + 7, opt_head, strlen(opt_head)))
7604 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7606 return OK;
7607 }
7609 static int
7610 load_git_config(void)
7611 {
7612 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7614 return io_run_load(config_list_argv, "=", read_repo_config_option);
7615 }
7617 static int
7618 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7619 {
7620 if (!opt_git_dir[0]) {
7621 string_ncopy(opt_git_dir, name, namelen);
7623 } else if (opt_is_inside_work_tree == -1) {
7624 /* This can be 3 different values depending on the
7625 * version of git being used. If git-rev-parse does not
7626 * understand --is-inside-work-tree it will simply echo
7627 * the option else either "true" or "false" is printed.
7628 * Default to true for the unknown case. */
7629 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7631 } else if (*name == '.') {
7632 string_ncopy(opt_cdup, name, namelen);
7634 } else {
7635 string_ncopy(opt_prefix, name, namelen);
7636 }
7638 return OK;
7639 }
7641 static int
7642 load_repo_info(void)
7643 {
7644 const char *rev_parse_argv[] = {
7645 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7646 "--show-cdup", "--show-prefix", NULL
7647 };
7649 return io_run_load(rev_parse_argv, "=", read_repo_info);
7650 }
7653 /*
7654 * Main
7655 */
7657 static const char usage[] =
7658 "tig " TIG_VERSION " (" __DATE__ ")\n"
7659 "\n"
7660 "Usage: tig [options] [revs] [--] [paths]\n"
7661 " or: tig show [options] [revs] [--] [paths]\n"
7662 " or: tig blame [rev] path\n"
7663 " or: tig status\n"
7664 " or: tig < [git command output]\n"
7665 "\n"
7666 "Options:\n"
7667 " -v, --version Show version and exit\n"
7668 " -h, --help Show help message and exit";
7670 static void __NORETURN
7671 quit(int sig)
7672 {
7673 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7674 if (cursed)
7675 endwin();
7676 exit(0);
7677 }
7679 static void __NORETURN
7680 die(const char *err, ...)
7681 {
7682 va_list args;
7684 endwin();
7686 va_start(args, err);
7687 fputs("tig: ", stderr);
7688 vfprintf(stderr, err, args);
7689 fputs("\n", stderr);
7690 va_end(args);
7692 exit(1);
7693 }
7695 static void
7696 warn(const char *msg, ...)
7697 {
7698 va_list args;
7700 va_start(args, msg);
7701 fputs("tig warning: ", stderr);
7702 vfprintf(stderr, msg, args);
7703 fputs("\n", stderr);
7704 va_end(args);
7705 }
7707 static enum request
7708 parse_options(int argc, const char *argv[])
7709 {
7710 enum request request = REQ_VIEW_MAIN;
7711 const char *subcommand;
7712 bool seen_dashdash = FALSE;
7713 /* XXX: This is vulnerable to the user overriding options
7714 * required for the main view parser. */
7715 const char *custom_argv[SIZEOF_ARG] = {
7716 "git", "log", "--no-color", "--pretty=raw", "--parents",
7717 "--topo-order", NULL
7718 };
7719 int i, j = 6;
7721 if (!isatty(STDIN_FILENO)) {
7722 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7723 return REQ_VIEW_PAGER;
7724 }
7726 if (argc <= 1)
7727 return REQ_NONE;
7729 subcommand = argv[1];
7730 if (!strcmp(subcommand, "status")) {
7731 if (argc > 2)
7732 warn("ignoring arguments after `%s'", subcommand);
7733 return REQ_VIEW_STATUS;
7735 } else if (!strcmp(subcommand, "blame")) {
7736 if (argc <= 2 || argc > 4)
7737 die("invalid number of options to blame\n\n%s", usage);
7739 i = 2;
7740 if (argc == 4) {
7741 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7742 i++;
7743 }
7745 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7746 return REQ_VIEW_BLAME;
7748 } else if (!strcmp(subcommand, "show")) {
7749 request = REQ_VIEW_DIFF;
7751 } else {
7752 subcommand = NULL;
7753 }
7755 if (subcommand) {
7756 custom_argv[1] = subcommand;
7757 j = 2;
7758 }
7760 for (i = 1 + !!subcommand; i < argc; i++) {
7761 const char *opt = argv[i];
7763 if (seen_dashdash || !strcmp(opt, "--")) {
7764 seen_dashdash = TRUE;
7766 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7767 printf("tig version %s\n", TIG_VERSION);
7768 quit(0);
7770 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7771 printf("%s\n", usage);
7772 quit(0);
7773 }
7775 custom_argv[j++] = opt;
7776 if (j >= ARRAY_SIZE(custom_argv))
7777 die("command too long");
7778 }
7780 if (!prepare_update(VIEW(request), custom_argv, NULL))
7781 die("Failed to format arguments");
7783 return request;
7784 }
7786 int
7787 main(int argc, const char *argv[])
7788 {
7789 const char *codeset = "UTF-8";
7790 enum request request = parse_options(argc, argv);
7791 struct view *view;
7792 size_t i;
7794 signal(SIGINT, quit);
7795 signal(SIGPIPE, SIG_IGN);
7797 if (setlocale(LC_ALL, "")) {
7798 codeset = nl_langinfo(CODESET);
7799 }
7801 if (load_repo_info() == ERR)
7802 die("Failed to load repo info.");
7804 if (load_options() == ERR)
7805 die("Failed to load user config.");
7807 if (load_git_config() == ERR)
7808 die("Failed to load repo config.");
7810 /* Require a git repository unless when running in pager mode. */
7811 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7812 die("Not a git repository");
7814 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7815 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7816 if (opt_iconv_in == ICONV_NONE)
7817 die("Failed to initialize character set conversion");
7818 }
7820 if (codeset && strcmp(codeset, "UTF-8")) {
7821 opt_iconv_out = iconv_open(codeset, "UTF-8");
7822 if (opt_iconv_out == ICONV_NONE)
7823 die("Failed to initialize character set conversion");
7824 }
7826 if (load_refs() == ERR)
7827 die("Failed to load refs.");
7829 foreach_view (view, i)
7830 if (!argv_from_env(view->ops->argv, view->cmd_env))
7831 die("Too many arguments in the `%s` environment variable",
7832 view->cmd_env);
7834 init_display();
7836 if (request != REQ_NONE)
7837 open_view(NULL, request, OPEN_PREPARED);
7838 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7840 while (view_driver(display[current_view], request)) {
7841 int key = get_input(0);
7843 view = display[current_view];
7844 request = get_keybinding(view->keymap, key);
7846 /* Some low-level request handling. This keeps access to
7847 * status_win restricted. */
7848 switch (request) {
7849 case REQ_NONE:
7850 report("Unknown key, press %s for help",
7851 get_key(view->keymap, REQ_VIEW_HELP));
7852 break;
7853 case REQ_PROMPT:
7854 {
7855 char *cmd = read_prompt(":");
7857 if (cmd && isdigit(*cmd)) {
7858 int lineno = view->lineno + 1;
7860 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7861 select_view_line(view, lineno - 1);
7862 report("");
7863 } else {
7864 report("Unable to parse '%s' as a line number", cmd);
7865 }
7867 } else if (cmd) {
7868 struct view *next = VIEW(REQ_VIEW_PAGER);
7869 const char *argv[SIZEOF_ARG] = { "git" };
7870 int argc = 1;
7872 /* When running random commands, initially show the
7873 * command in the title. However, it maybe later be
7874 * overwritten if a commit line is selected. */
7875 string_ncopy(next->ref, cmd, strlen(cmd));
7877 if (!argv_from_string(argv, &argc, cmd)) {
7878 report("Too many arguments");
7879 } else if (!prepare_update(next, argv, NULL)) {
7880 report("Failed to format command");
7881 } else {
7882 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7883 }
7884 }
7886 request = REQ_NONE;
7887 break;
7888 }
7889 case REQ_SEARCH:
7890 case REQ_SEARCH_BACK:
7891 {
7892 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7893 char *search = read_prompt(prompt);
7895 if (search)
7896 string_ncopy(opt_search, search, strlen(search));
7897 else if (*opt_search)
7898 request = request == REQ_SEARCH ?
7899 REQ_FIND_NEXT :
7900 REQ_FIND_PREV;
7901 else
7902 request = REQ_NONE;
7903 break;
7904 }
7905 default:
7906 break;
7907 }
7908 }
7910 quit(0);
7912 return 0;
7913 }