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_open(struct io *io, const char *fmt, ...)
758 {
759 char name[SIZEOF_STR] = "";
760 bool fits;
761 va_list args;
763 io_init(io, NULL, IO_FD);
765 va_start(args, fmt);
766 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
767 va_end(args);
769 if (!fits) {
770 io->error = ENAMETOOLONG;
771 return FALSE;
772 }
773 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
774 if (io->pipe == -1)
775 io->error = errno;
776 return io->pipe != -1;
777 }
779 static bool
780 io_kill(struct io *io)
781 {
782 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
783 }
785 static bool
786 io_done(struct io *io)
787 {
788 pid_t pid = io->pid;
790 if (io->pipe != -1)
791 close(io->pipe);
792 free(io->buf);
793 io_reset(io);
795 while (pid > 0) {
796 int status;
797 pid_t waiting = waitpid(pid, &status, 0);
799 if (waiting < 0) {
800 if (errno == EINTR)
801 continue;
802 io->error = errno;
803 return FALSE;
804 }
806 return waiting == pid &&
807 !WIFSIGNALED(status) &&
808 WIFEXITED(status) &&
809 !WEXITSTATUS(status);
810 }
812 return TRUE;
813 }
815 static bool
816 io_start(struct io *io)
817 {
818 int pipefds[2] = { -1, -1 };
820 if (io->type == IO_FD)
821 return TRUE;
823 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
824 io->error = errno;
825 return FALSE;
826 } else if (io->type == IO_AP) {
827 pipefds[1] = io->pipe;
828 }
830 if ((io->pid = fork())) {
831 if (io->pid == -1)
832 io->error = errno;
833 if (pipefds[!(io->type == IO_WR)] != -1)
834 close(pipefds[!(io->type == IO_WR)]);
835 if (io->pid != -1) {
836 io->pipe = pipefds[!!(io->type == IO_WR)];
837 return TRUE;
838 }
840 } else {
841 if (io->type != IO_FG) {
842 int devnull = open("/dev/null", O_RDWR);
843 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
844 int writefd = (io->type == IO_RD || io->type == IO_AP)
845 ? pipefds[1] : devnull;
847 dup2(readfd, STDIN_FILENO);
848 dup2(writefd, STDOUT_FILENO);
849 dup2(devnull, STDERR_FILENO);
851 close(devnull);
852 if (pipefds[0] != -1)
853 close(pipefds[0]);
854 if (pipefds[1] != -1)
855 close(pipefds[1]);
856 }
858 if (io->dir && *io->dir && chdir(io->dir) == -1)
859 exit(errno);
861 execvp(io->argv[0], (char *const*) io->argv);
862 exit(errno);
863 }
865 if (pipefds[!!(io->type == IO_WR)] != -1)
866 close(pipefds[!!(io->type == IO_WR)]);
867 return FALSE;
868 }
870 static bool
871 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
872 {
873 io_prepare(io, dir, type, argv);
874 return io_start(io);
875 }
877 static bool
878 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
879 {
880 struct io io = {};
882 io_prepare(&io, dir, type, argv);
883 io.pipe = fd;
884 return io_start(&io) && io_done(&io);
885 }
887 static bool
888 io_run_bg(const char **argv)
889 {
890 return io_complete(IO_BG, argv, NULL, -1);
891 }
893 static bool
894 io_run_fg(const char **argv, const char *dir)
895 {
896 return io_complete(IO_FG, argv, dir, -1);
897 }
899 static bool
900 io_run_append(const char **argv, int fd)
901 {
902 return io_complete(IO_AP, argv, NULL, -1);
903 }
905 static bool
906 io_eof(struct io *io)
907 {
908 return io->eof;
909 }
911 static int
912 io_error(struct io *io)
913 {
914 return io->error;
915 }
917 static char *
918 io_strerror(struct io *io)
919 {
920 return strerror(io->error);
921 }
923 static bool
924 io_can_read(struct io *io)
925 {
926 struct timeval tv = { 0, 500 };
927 fd_set fds;
929 FD_ZERO(&fds);
930 FD_SET(io->pipe, &fds);
932 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
933 }
935 static ssize_t
936 io_read(struct io *io, void *buf, size_t bufsize)
937 {
938 do {
939 ssize_t readsize = read(io->pipe, buf, bufsize);
941 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
942 continue;
943 else if (readsize == -1)
944 io->error = errno;
945 else if (readsize == 0)
946 io->eof = 1;
947 return readsize;
948 } while (1);
949 }
951 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
953 static char *
954 io_get(struct io *io, int c, bool can_read)
955 {
956 char *eol;
957 ssize_t readsize;
959 while (TRUE) {
960 if (io->bufsize > 0) {
961 eol = memchr(io->bufpos, c, io->bufsize);
962 if (eol) {
963 char *line = io->bufpos;
965 *eol = 0;
966 io->bufpos = eol + 1;
967 io->bufsize -= io->bufpos - line;
968 return line;
969 }
970 }
972 if (io_eof(io)) {
973 if (io->bufsize) {
974 io->bufpos[io->bufsize] = 0;
975 io->bufsize = 0;
976 return io->bufpos;
977 }
978 return NULL;
979 }
981 if (!can_read)
982 return NULL;
984 if (io->bufsize > 0 && io->bufpos > io->buf)
985 memmove(io->buf, io->bufpos, io->bufsize);
987 if (io->bufalloc == io->bufsize) {
988 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
989 return NULL;
990 io->bufalloc += BUFSIZ;
991 }
993 io->bufpos = io->buf;
994 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
995 if (io_error(io))
996 return NULL;
997 io->bufsize += readsize;
998 }
999 }
1001 static bool
1002 io_write(struct io *io, const void *buf, size_t bufsize)
1003 {
1004 size_t written = 0;
1006 while (!io_error(io) && written < bufsize) {
1007 ssize_t size;
1009 size = write(io->pipe, buf + written, bufsize - written);
1010 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1011 continue;
1012 else if (size == -1)
1013 io->error = errno;
1014 else
1015 written += size;
1016 }
1018 return written == bufsize;
1019 }
1021 static bool
1022 io_read_buf(struct io *io, char buf[], size_t bufsize)
1023 {
1024 char *result = io_get(io, '\n', TRUE);
1026 if (result) {
1027 result = chomp_string(result);
1028 string_ncopy_do(buf, bufsize, result, strlen(result));
1029 }
1031 return io_done(io) && result;
1032 }
1034 static bool
1035 io_run_buf(const char **argv, char buf[], size_t bufsize)
1036 {
1037 struct io io = {};
1039 io_prepare(&io, NULL, IO_RD, argv);
1040 return io_start(&io) && io_read_buf(&io, buf, bufsize);
1041 }
1043 static int
1044 io_load(struct io *io, const char *separators,
1045 int (*read_property)(char *, size_t, char *, size_t))
1046 {
1047 char *name;
1048 int state = OK;
1050 if (!io_start(io))
1051 return ERR;
1053 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054 char *value;
1055 size_t namelen;
1056 size_t valuelen;
1058 name = chomp_string(name);
1059 namelen = strcspn(name, separators);
1061 if (name[namelen]) {
1062 name[namelen] = 0;
1063 value = chomp_string(name + namelen + 1);
1064 valuelen = strlen(value);
1066 } else {
1067 value = "";
1068 valuelen = 0;
1069 }
1071 state = read_property(name, namelen, value, valuelen);
1072 }
1074 if (state != ERR && io_error(io))
1075 state = ERR;
1076 io_done(io);
1078 return state;
1079 }
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083 int (*read_property)(char *, size_t, char *, size_t))
1084 {
1085 struct io io = {};
1087 io_prepare(&io, NULL, IO_RD, argv);
1088 return io_load(&io, separators, read_property);
1089 }
1092 /*
1093 * User requests
1094 */
1096 #define REQ_INFO \
1097 /* XXX: Keep the view request first and in sync with views[]. */ \
1098 REQ_GROUP("View switching") \
1099 REQ_(VIEW_MAIN, "Show main view"), \
1100 REQ_(VIEW_DIFF, "Show diff view"), \
1101 REQ_(VIEW_LOG, "Show log view"), \
1102 REQ_(VIEW_TREE, "Show tree view"), \
1103 REQ_(VIEW_BLOB, "Show blob view"), \
1104 REQ_(VIEW_BLAME, "Show blame view"), \
1105 REQ_(VIEW_BRANCH, "Show branch view"), \
1106 REQ_(VIEW_HELP, "Show help page"), \
1107 REQ_(VIEW_PAGER, "Show pager view"), \
1108 REQ_(VIEW_STATUS, "Show status view"), \
1109 REQ_(VIEW_STAGE, "Show stage view"), \
1110 \
1111 REQ_GROUP("View manipulation") \
1112 REQ_(ENTER, "Enter current line and scroll"), \
1113 REQ_(NEXT, "Move to next"), \
1114 REQ_(PREVIOUS, "Move to previous"), \
1115 REQ_(PARENT, "Move to parent"), \
1116 REQ_(VIEW_NEXT, "Move focus to next view"), \
1117 REQ_(REFRESH, "Reload and refresh"), \
1118 REQ_(MAXIMIZE, "Maximize the current view"), \
1119 REQ_(VIEW_CLOSE, "Close the current view"), \
1120 REQ_(QUIT, "Close all views and quit"), \
1121 \
1122 REQ_GROUP("View specific requests") \
1123 REQ_(STATUS_UPDATE, "Update file status"), \
1124 REQ_(STATUS_REVERT, "Revert file changes"), \
1125 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1126 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1127 \
1128 REQ_GROUP("Cursor navigation") \
1129 REQ_(MOVE_UP, "Move cursor one line up"), \
1130 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1131 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1132 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1133 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1134 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1135 \
1136 REQ_GROUP("Scrolling") \
1137 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1138 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1139 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1140 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1141 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1142 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1143 \
1144 REQ_GROUP("Searching") \
1145 REQ_(SEARCH, "Search the view"), \
1146 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1147 REQ_(FIND_NEXT, "Find next search match"), \
1148 REQ_(FIND_PREV, "Find previous search match"), \
1149 \
1150 REQ_GROUP("Option manipulation") \
1151 REQ_(OPTIONS, "Open option menu"), \
1152 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1153 REQ_(TOGGLE_DATE, "Toggle date display"), \
1154 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1155 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1156 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1157 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1158 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160 \
1161 REQ_GROUP("Misc") \
1162 REQ_(PROMPT, "Bring up the prompt"), \
1163 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1164 REQ_(SHOW_VERSION, "Show version information"), \
1165 REQ_(STOP_LOADING, "Stop all loading views"), \
1166 REQ_(EDIT, "Open in editor"), \
1167 REQ_(NONE, "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175 /* Offset all requests to avoid conflicts with ncurses getch values. */
1176 REQ_UNKNOWN = KEY_MAX + 1,
1177 REQ_OFFSET,
1178 REQ_INFO
1180 #undef REQ_GROUP
1181 #undef REQ_
1182 };
1184 struct request_info {
1185 enum request request;
1186 const char *name;
1187 int namelen;
1188 const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194 REQ_INFO
1195 #undef REQ_GROUP
1196 #undef REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1201 {
1202 int namelen = strlen(name);
1203 int i;
1205 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206 if (enum_equals(req_info[i], name, namelen))
1207 return req_info[i].request;
1209 return REQ_UNKNOWN;
1210 }
1213 /*
1214 * Options
1215 */
1217 /* Option and state variables. */
1218 static enum date opt_date = DATE_DEFAULT;
1219 static enum author opt_author = AUTHOR_DEFAULT;
1220 static bool opt_line_number = FALSE;
1221 static bool opt_line_graphics = TRUE;
1222 static bool opt_rev_graph = FALSE;
1223 static bool opt_show_refs = TRUE;
1224 static int opt_num_interval = 5;
1225 static double opt_hscroll = 0.50;
1226 static double opt_scale_split_view = 2.0 / 3.0;
1227 static int opt_tab_size = 8;
1228 static int opt_author_cols = AUTHOR_COLS;
1229 static char opt_path[SIZEOF_STR] = "";
1230 static char opt_file[SIZEOF_STR] = "";
1231 static char opt_ref[SIZEOF_REF] = "";
1232 static char opt_head[SIZEOF_REF] = "";
1233 static char opt_remote[SIZEOF_REF] = "";
1234 static char opt_encoding[20] = "UTF-8";
1235 static iconv_t opt_iconv_in = ICONV_NONE;
1236 static iconv_t opt_iconv_out = ICONV_NONE;
1237 static char opt_search[SIZEOF_STR] = "";
1238 static char opt_cdup[SIZEOF_STR] = "";
1239 static char opt_prefix[SIZEOF_STR] = "";
1240 static char opt_git_dir[SIZEOF_STR] = "";
1241 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1242 static char opt_editor[SIZEOF_STR] = "";
1243 static FILE *opt_tty = NULL;
1245 #define is_initial_commit() (!get_ref_head())
1246 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1249 /*
1250 * Line-oriented content detection.
1251 */
1253 #define LINE_INFO \
1254 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1268 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1269 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1270 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1275 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1276 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1278 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1279 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1280 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1283 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1284 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1285 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1286 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1289 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1290 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1291 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1292 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1293 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1297 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1298 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1300 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1302 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1303 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1304 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1307 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1308 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1310 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1311 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1312 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1314 enum line_type {
1315 #define LINE(type, line, fg, bg, attr) \
1316 LINE_##type
1317 LINE_INFO,
1318 LINE_NONE
1319 #undef LINE
1320 };
1322 struct line_info {
1323 const char *name; /* Option name. */
1324 int namelen; /* Size of option name. */
1325 const char *line; /* The start of line to match. */
1326 int linelen; /* Size of string to match. */
1327 int fg, bg, attr; /* Color and text attributes for the lines. */
1328 };
1330 static struct line_info line_info[] = {
1331 #define LINE(type, line, fg, bg, attr) \
1332 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1333 LINE_INFO
1334 #undef LINE
1335 };
1337 static enum line_type
1338 get_line_type(const char *line)
1339 {
1340 int linelen = strlen(line);
1341 enum line_type type;
1343 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1344 /* Case insensitive search matches Signed-off-by lines better. */
1345 if (linelen >= line_info[type].linelen &&
1346 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1347 return type;
1349 return LINE_DEFAULT;
1350 }
1352 static inline int
1353 get_line_attr(enum line_type type)
1354 {
1355 assert(type < ARRAY_SIZE(line_info));
1356 return COLOR_PAIR(type) | line_info[type].attr;
1357 }
1359 static struct line_info *
1360 get_line_info(const char *name)
1361 {
1362 size_t namelen = strlen(name);
1363 enum line_type type;
1365 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1366 if (enum_equals(line_info[type], name, namelen))
1367 return &line_info[type];
1369 return NULL;
1370 }
1372 static void
1373 init_colors(void)
1374 {
1375 int default_bg = line_info[LINE_DEFAULT].bg;
1376 int default_fg = line_info[LINE_DEFAULT].fg;
1377 enum line_type type;
1379 start_color();
1381 if (assume_default_colors(default_fg, default_bg) == ERR) {
1382 default_bg = COLOR_BLACK;
1383 default_fg = COLOR_WHITE;
1384 }
1386 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1387 struct line_info *info = &line_info[type];
1388 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1389 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1391 init_pair(type, fg, bg);
1392 }
1393 }
1395 struct line {
1396 enum line_type type;
1398 /* State flags */
1399 unsigned int selected:1;
1400 unsigned int dirty:1;
1401 unsigned int cleareol:1;
1402 unsigned int other:16;
1404 void *data; /* User data */
1405 };
1408 /*
1409 * Keys
1410 */
1412 struct keybinding {
1413 int alias;
1414 enum request request;
1415 };
1417 static struct keybinding default_keybindings[] = {
1418 /* View switching */
1419 { 'm', REQ_VIEW_MAIN },
1420 { 'd', REQ_VIEW_DIFF },
1421 { 'l', REQ_VIEW_LOG },
1422 { 't', REQ_VIEW_TREE },
1423 { 'f', REQ_VIEW_BLOB },
1424 { 'B', REQ_VIEW_BLAME },
1425 { 'H', REQ_VIEW_BRANCH },
1426 { 'p', REQ_VIEW_PAGER },
1427 { 'h', REQ_VIEW_HELP },
1428 { 'S', REQ_VIEW_STATUS },
1429 { 'c', REQ_VIEW_STAGE },
1431 /* View manipulation */
1432 { 'q', REQ_VIEW_CLOSE },
1433 { KEY_TAB, REQ_VIEW_NEXT },
1434 { KEY_RETURN, REQ_ENTER },
1435 { KEY_UP, REQ_PREVIOUS },
1436 { KEY_DOWN, REQ_NEXT },
1437 { 'R', REQ_REFRESH },
1438 { KEY_F(5), REQ_REFRESH },
1439 { 'O', REQ_MAXIMIZE },
1441 /* Cursor navigation */
1442 { 'k', REQ_MOVE_UP },
1443 { 'j', REQ_MOVE_DOWN },
1444 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1445 { KEY_END, REQ_MOVE_LAST_LINE },
1446 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1447 { ' ', REQ_MOVE_PAGE_DOWN },
1448 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1449 { 'b', REQ_MOVE_PAGE_UP },
1450 { '-', REQ_MOVE_PAGE_UP },
1452 /* Scrolling */
1453 { KEY_LEFT, REQ_SCROLL_LEFT },
1454 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1455 { KEY_IC, REQ_SCROLL_LINE_UP },
1456 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1457 { 'w', REQ_SCROLL_PAGE_UP },
1458 { 's', REQ_SCROLL_PAGE_DOWN },
1460 /* Searching */
1461 { '/', REQ_SEARCH },
1462 { '?', REQ_SEARCH_BACK },
1463 { 'n', REQ_FIND_NEXT },
1464 { 'N', REQ_FIND_PREV },
1466 /* Misc */
1467 { 'Q', REQ_QUIT },
1468 { 'z', REQ_STOP_LOADING },
1469 { 'v', REQ_SHOW_VERSION },
1470 { 'r', REQ_SCREEN_REDRAW },
1471 { 'o', REQ_OPTIONS },
1472 { '.', REQ_TOGGLE_LINENO },
1473 { 'D', REQ_TOGGLE_DATE },
1474 { 'A', REQ_TOGGLE_AUTHOR },
1475 { 'g', REQ_TOGGLE_REV_GRAPH },
1476 { 'F', REQ_TOGGLE_REFS },
1477 { 'I', REQ_TOGGLE_SORT_ORDER },
1478 { 'i', REQ_TOGGLE_SORT_FIELD },
1479 { ':', REQ_PROMPT },
1480 { 'u', REQ_STATUS_UPDATE },
1481 { '!', REQ_STATUS_REVERT },
1482 { 'M', REQ_STATUS_MERGE },
1483 { '@', REQ_STAGE_NEXT },
1484 { ',', REQ_PARENT },
1485 { 'e', REQ_EDIT },
1486 };
1488 #define KEYMAP_INFO \
1489 KEYMAP_(GENERIC), \
1490 KEYMAP_(MAIN), \
1491 KEYMAP_(DIFF), \
1492 KEYMAP_(LOG), \
1493 KEYMAP_(TREE), \
1494 KEYMAP_(BLOB), \
1495 KEYMAP_(BLAME), \
1496 KEYMAP_(BRANCH), \
1497 KEYMAP_(PAGER), \
1498 KEYMAP_(HELP), \
1499 KEYMAP_(STATUS), \
1500 KEYMAP_(STAGE)
1502 enum keymap {
1503 #define KEYMAP_(name) KEYMAP_##name
1504 KEYMAP_INFO
1505 #undef KEYMAP_
1506 };
1508 static const struct enum_map keymap_table[] = {
1509 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1510 KEYMAP_INFO
1511 #undef KEYMAP_
1512 };
1514 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1516 struct keybinding_table {
1517 struct keybinding *data;
1518 size_t size;
1519 };
1521 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1523 static void
1524 add_keybinding(enum keymap keymap, enum request request, int key)
1525 {
1526 struct keybinding_table *table = &keybindings[keymap];
1527 size_t i;
1529 for (i = 0; i < keybindings[keymap].size; i++) {
1530 if (keybindings[keymap].data[i].alias == key) {
1531 keybindings[keymap].data[i].request = request;
1532 return;
1533 }
1534 }
1536 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1537 if (!table->data)
1538 die("Failed to allocate keybinding");
1539 table->data[table->size].alias = key;
1540 table->data[table->size++].request = request;
1542 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1543 int i;
1545 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1546 if (default_keybindings[i].alias == key)
1547 default_keybindings[i].request = REQ_NONE;
1548 }
1549 }
1551 /* Looks for a key binding first in the given map, then in the generic map, and
1552 * lastly in the default keybindings. */
1553 static enum request
1554 get_keybinding(enum keymap keymap, int key)
1555 {
1556 size_t i;
1558 for (i = 0; i < keybindings[keymap].size; i++)
1559 if (keybindings[keymap].data[i].alias == key)
1560 return keybindings[keymap].data[i].request;
1562 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1563 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1564 return keybindings[KEYMAP_GENERIC].data[i].request;
1566 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1567 if (default_keybindings[i].alias == key)
1568 return default_keybindings[i].request;
1570 return (enum request) key;
1571 }
1574 struct key {
1575 const char *name;
1576 int value;
1577 };
1579 static const struct key key_table[] = {
1580 { "Enter", KEY_RETURN },
1581 { "Space", ' ' },
1582 { "Backspace", KEY_BACKSPACE },
1583 { "Tab", KEY_TAB },
1584 { "Escape", KEY_ESC },
1585 { "Left", KEY_LEFT },
1586 { "Right", KEY_RIGHT },
1587 { "Up", KEY_UP },
1588 { "Down", KEY_DOWN },
1589 { "Insert", KEY_IC },
1590 { "Delete", KEY_DC },
1591 { "Hash", '#' },
1592 { "Home", KEY_HOME },
1593 { "End", KEY_END },
1594 { "PageUp", KEY_PPAGE },
1595 { "PageDown", KEY_NPAGE },
1596 { "F1", KEY_F(1) },
1597 { "F2", KEY_F(2) },
1598 { "F3", KEY_F(3) },
1599 { "F4", KEY_F(4) },
1600 { "F5", KEY_F(5) },
1601 { "F6", KEY_F(6) },
1602 { "F7", KEY_F(7) },
1603 { "F8", KEY_F(8) },
1604 { "F9", KEY_F(9) },
1605 { "F10", KEY_F(10) },
1606 { "F11", KEY_F(11) },
1607 { "F12", KEY_F(12) },
1608 };
1610 static int
1611 get_key_value(const char *name)
1612 {
1613 int i;
1615 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1616 if (!strcasecmp(key_table[i].name, name))
1617 return key_table[i].value;
1619 if (strlen(name) == 1 && isprint(*name))
1620 return (int) *name;
1622 return ERR;
1623 }
1625 static const char *
1626 get_key_name(int key_value)
1627 {
1628 static char key_char[] = "'X'";
1629 const char *seq = NULL;
1630 int key;
1632 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1633 if (key_table[key].value == key_value)
1634 seq = key_table[key].name;
1636 if (seq == NULL &&
1637 key_value < 127 &&
1638 isprint(key_value)) {
1639 key_char[1] = (char) key_value;
1640 seq = key_char;
1641 }
1643 return seq ? seq : "(no key)";
1644 }
1646 static bool
1647 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1648 {
1649 const char *sep = *pos > 0 ? ", " : "";
1650 const char *keyname = get_key_name(keybinding->alias);
1652 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1653 }
1655 static bool
1656 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1657 enum keymap keymap, bool all)
1658 {
1659 int i;
1661 for (i = 0; i < keybindings[keymap].size; i++) {
1662 if (keybindings[keymap].data[i].request == request) {
1663 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1664 return FALSE;
1665 if (!all)
1666 break;
1667 }
1668 }
1670 return TRUE;
1671 }
1673 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1675 static const char *
1676 get_keys(enum keymap keymap, enum request request, bool all)
1677 {
1678 static char buf[BUFSIZ];
1679 size_t pos = 0;
1680 int i;
1682 buf[pos] = 0;
1684 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1685 return "Too many keybindings!";
1686 if (pos > 0 && !all)
1687 return buf;
1689 if (keymap != KEYMAP_GENERIC) {
1690 /* Only the generic keymap includes the default keybindings when
1691 * listing all keys. */
1692 if (all)
1693 return buf;
1695 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1696 return "Too many keybindings!";
1697 if (pos)
1698 return buf;
1699 }
1701 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1702 if (default_keybindings[i].request == request) {
1703 if (!append_key(buf, &pos, &default_keybindings[i]))
1704 return "Too many keybindings!";
1705 if (!all)
1706 return buf;
1707 }
1708 }
1710 return buf;
1711 }
1713 struct run_request {
1714 enum keymap keymap;
1715 int key;
1716 const char *argv[SIZEOF_ARG];
1717 };
1719 static struct run_request *run_request;
1720 static size_t run_requests;
1722 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1724 static enum request
1725 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1726 {
1727 struct run_request *req;
1729 if (argc >= ARRAY_SIZE(req->argv) - 1)
1730 return REQ_NONE;
1732 if (!realloc_run_requests(&run_request, run_requests, 1))
1733 return REQ_NONE;
1735 req = &run_request[run_requests];
1736 req->keymap = keymap;
1737 req->key = key;
1738 req->argv[0] = NULL;
1740 if (!argv_copy(req->argv, argv, TRUE))
1741 return REQ_NONE;
1743 return REQ_NONE + ++run_requests;
1744 }
1746 static struct run_request *
1747 get_run_request(enum request request)
1748 {
1749 if (request <= REQ_NONE)
1750 return NULL;
1751 return &run_request[request - REQ_NONE - 1];
1752 }
1754 static void
1755 add_builtin_run_requests(void)
1756 {
1757 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1758 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1759 const char *commit[] = { "git", "commit", NULL };
1760 const char *gc[] = { "git", "gc", NULL };
1761 struct {
1762 enum keymap keymap;
1763 int key;
1764 int argc;
1765 const char **argv;
1766 } reqs[] = {
1767 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1768 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1769 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1770 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1771 };
1772 int i;
1774 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1775 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1777 if (req != reqs[i].key)
1778 continue;
1779 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1780 if (req != REQ_NONE)
1781 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1782 }
1783 }
1785 /*
1786 * User config file handling.
1787 */
1789 static int config_lineno;
1790 static bool config_errors;
1791 static const char *config_msg;
1793 static const struct enum_map color_map[] = {
1794 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1795 COLOR_MAP(DEFAULT),
1796 COLOR_MAP(BLACK),
1797 COLOR_MAP(BLUE),
1798 COLOR_MAP(CYAN),
1799 COLOR_MAP(GREEN),
1800 COLOR_MAP(MAGENTA),
1801 COLOR_MAP(RED),
1802 COLOR_MAP(WHITE),
1803 COLOR_MAP(YELLOW),
1804 };
1806 static const struct enum_map attr_map[] = {
1807 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1808 ATTR_MAP(NORMAL),
1809 ATTR_MAP(BLINK),
1810 ATTR_MAP(BOLD),
1811 ATTR_MAP(DIM),
1812 ATTR_MAP(REVERSE),
1813 ATTR_MAP(STANDOUT),
1814 ATTR_MAP(UNDERLINE),
1815 };
1817 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1819 static int parse_step(double *opt, const char *arg)
1820 {
1821 *opt = atoi(arg);
1822 if (!strchr(arg, '%'))
1823 return OK;
1825 /* "Shift down" so 100% and 1 does not conflict. */
1826 *opt = (*opt - 1) / 100;
1827 if (*opt >= 1.0) {
1828 *opt = 0.99;
1829 config_msg = "Step value larger than 100%";
1830 return ERR;
1831 }
1832 if (*opt < 0.0) {
1833 *opt = 1;
1834 config_msg = "Invalid step value";
1835 return ERR;
1836 }
1837 return OK;
1838 }
1840 static int
1841 parse_int(int *opt, const char *arg, int min, int max)
1842 {
1843 int value = atoi(arg);
1845 if (min <= value && value <= max) {
1846 *opt = value;
1847 return OK;
1848 }
1850 config_msg = "Integer value out of bound";
1851 return ERR;
1852 }
1854 static bool
1855 set_color(int *color, const char *name)
1856 {
1857 if (map_enum(color, color_map, name))
1858 return TRUE;
1859 if (!prefixcmp(name, "color"))
1860 return parse_int(color, name + 5, 0, 255) == OK;
1861 return FALSE;
1862 }
1864 /* Wants: object fgcolor bgcolor [attribute] */
1865 static int
1866 option_color_command(int argc, const char *argv[])
1867 {
1868 struct line_info *info;
1870 if (argc < 3) {
1871 config_msg = "Wrong number of arguments given to color command";
1872 return ERR;
1873 }
1875 info = get_line_info(argv[0]);
1876 if (!info) {
1877 static const struct enum_map obsolete[] = {
1878 ENUM_MAP("main-delim", LINE_DELIMITER),
1879 ENUM_MAP("main-date", LINE_DATE),
1880 ENUM_MAP("main-author", LINE_AUTHOR),
1881 };
1882 int index;
1884 if (!map_enum(&index, obsolete, argv[0])) {
1885 config_msg = "Unknown color name";
1886 return ERR;
1887 }
1888 info = &line_info[index];
1889 }
1891 if (!set_color(&info->fg, argv[1]) ||
1892 !set_color(&info->bg, argv[2])) {
1893 config_msg = "Unknown color";
1894 return ERR;
1895 }
1897 info->attr = 0;
1898 while (argc-- > 3) {
1899 int attr;
1901 if (!set_attribute(&attr, argv[argc])) {
1902 config_msg = "Unknown attribute";
1903 return ERR;
1904 }
1905 info->attr |= attr;
1906 }
1908 return OK;
1909 }
1911 static int parse_bool(bool *opt, const char *arg)
1912 {
1913 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1914 ? TRUE : FALSE;
1915 return OK;
1916 }
1918 static int parse_enum_do(unsigned int *opt, const char *arg,
1919 const struct enum_map *map, size_t map_size)
1920 {
1921 bool is_true;
1923 assert(map_size > 1);
1925 if (map_enum_do(map, map_size, (int *) opt, arg))
1926 return OK;
1928 if (parse_bool(&is_true, arg) != OK)
1929 return ERR;
1931 *opt = is_true ? map[1].value : map[0].value;
1932 return OK;
1933 }
1935 #define parse_enum(opt, arg, map) \
1936 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1938 static int
1939 parse_string(char *opt, const char *arg, size_t optsize)
1940 {
1941 int arglen = strlen(arg);
1943 switch (arg[0]) {
1944 case '\"':
1945 case '\'':
1946 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1947 config_msg = "Unmatched quotation";
1948 return ERR;
1949 }
1950 arg += 1; arglen -= 2;
1951 default:
1952 string_ncopy_do(opt, optsize, arg, arglen);
1953 return OK;
1954 }
1955 }
1957 /* Wants: name = value */
1958 static int
1959 option_set_command(int argc, const char *argv[])
1960 {
1961 if (argc != 3) {
1962 config_msg = "Wrong number of arguments given to set command";
1963 return ERR;
1964 }
1966 if (strcmp(argv[1], "=")) {
1967 config_msg = "No value assigned";
1968 return ERR;
1969 }
1971 if (!strcmp(argv[0], "show-author"))
1972 return parse_enum(&opt_author, argv[2], author_map);
1974 if (!strcmp(argv[0], "show-date"))
1975 return parse_enum(&opt_date, argv[2], date_map);
1977 if (!strcmp(argv[0], "show-rev-graph"))
1978 return parse_bool(&opt_rev_graph, argv[2]);
1980 if (!strcmp(argv[0], "show-refs"))
1981 return parse_bool(&opt_show_refs, argv[2]);
1983 if (!strcmp(argv[0], "show-line-numbers"))
1984 return parse_bool(&opt_line_number, argv[2]);
1986 if (!strcmp(argv[0], "line-graphics"))
1987 return parse_bool(&opt_line_graphics, argv[2]);
1989 if (!strcmp(argv[0], "line-number-interval"))
1990 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1992 if (!strcmp(argv[0], "author-width"))
1993 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1995 if (!strcmp(argv[0], "horizontal-scroll"))
1996 return parse_step(&opt_hscroll, argv[2]);
1998 if (!strcmp(argv[0], "split-view-height"))
1999 return parse_step(&opt_scale_split_view, argv[2]);
2001 if (!strcmp(argv[0], "tab-size"))
2002 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2004 if (!strcmp(argv[0], "commit-encoding"))
2005 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2007 config_msg = "Unknown variable name";
2008 return ERR;
2009 }
2011 /* Wants: mode request key */
2012 static int
2013 option_bind_command(int argc, const char *argv[])
2014 {
2015 enum request request;
2016 int keymap = -1;
2017 int key;
2019 if (argc < 3) {
2020 config_msg = "Wrong number of arguments given to bind command";
2021 return ERR;
2022 }
2024 if (!set_keymap(&keymap, argv[0])) {
2025 config_msg = "Unknown key map";
2026 return ERR;
2027 }
2029 key = get_key_value(argv[1]);
2030 if (key == ERR) {
2031 config_msg = "Unknown key";
2032 return ERR;
2033 }
2035 request = get_request(argv[2]);
2036 if (request == REQ_UNKNOWN) {
2037 static const struct enum_map obsolete[] = {
2038 ENUM_MAP("cherry-pick", REQ_NONE),
2039 ENUM_MAP("screen-resize", REQ_NONE),
2040 ENUM_MAP("tree-parent", REQ_PARENT),
2041 };
2042 int alias;
2044 if (map_enum(&alias, obsolete, argv[2])) {
2045 if (alias != REQ_NONE)
2046 add_keybinding(keymap, alias, key);
2047 config_msg = "Obsolete request name";
2048 return ERR;
2049 }
2050 }
2051 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2052 request = add_run_request(keymap, key, argc - 2, argv + 2);
2053 if (request == REQ_UNKNOWN) {
2054 config_msg = "Unknown request name";
2055 return ERR;
2056 }
2058 add_keybinding(keymap, request, key);
2060 return OK;
2061 }
2063 static int
2064 set_option(const char *opt, char *value)
2065 {
2066 const char *argv[SIZEOF_ARG];
2067 int argc = 0;
2069 if (!argv_from_string(argv, &argc, value)) {
2070 config_msg = "Too many option arguments";
2071 return ERR;
2072 }
2074 if (!strcmp(opt, "color"))
2075 return option_color_command(argc, argv);
2077 if (!strcmp(opt, "set"))
2078 return option_set_command(argc, argv);
2080 if (!strcmp(opt, "bind"))
2081 return option_bind_command(argc, argv);
2083 config_msg = "Unknown option command";
2084 return ERR;
2085 }
2087 static int
2088 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2089 {
2090 int status = OK;
2092 config_lineno++;
2093 config_msg = "Internal error";
2095 /* Check for comment markers, since read_properties() will
2096 * only ensure opt and value are split at first " \t". */
2097 optlen = strcspn(opt, "#");
2098 if (optlen == 0)
2099 return OK;
2101 if (opt[optlen] != 0) {
2102 config_msg = "No option value";
2103 status = ERR;
2105 } else {
2106 /* Look for comment endings in the value. */
2107 size_t len = strcspn(value, "#");
2109 if (len < valuelen) {
2110 valuelen = len;
2111 value[valuelen] = 0;
2112 }
2114 status = set_option(opt, value);
2115 }
2117 if (status == ERR) {
2118 warn("Error on line %d, near '%.*s': %s",
2119 config_lineno, (int) optlen, opt, config_msg);
2120 config_errors = TRUE;
2121 }
2123 /* Always keep going if errors are encountered. */
2124 return OK;
2125 }
2127 static void
2128 load_option_file(const char *path)
2129 {
2130 struct io io = {};
2132 /* It's OK that the file doesn't exist. */
2133 if (!io_open(&io, "%s", path))
2134 return;
2136 config_lineno = 0;
2137 config_errors = FALSE;
2139 if (io_load(&io, " \t", read_option) == ERR ||
2140 config_errors == TRUE)
2141 warn("Errors while loading %s.", path);
2142 }
2144 static int
2145 load_options(void)
2146 {
2147 const char *home = getenv("HOME");
2148 const char *tigrc_user = getenv("TIGRC_USER");
2149 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2150 char buf[SIZEOF_STR];
2152 if (!tigrc_system)
2153 tigrc_system = SYSCONFDIR "/tigrc";
2154 load_option_file(tigrc_system);
2156 if (!tigrc_user) {
2157 if (!home || !string_format(buf, "%s/.tigrc", home))
2158 return ERR;
2159 tigrc_user = buf;
2160 }
2161 load_option_file(tigrc_user);
2163 /* Add _after_ loading config files to avoid adding run requests
2164 * that conflict with keybindings. */
2165 add_builtin_run_requests();
2167 return OK;
2168 }
2171 /*
2172 * The viewer
2173 */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views() (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF] = "";
2189 static char ref_commit[SIZEOF_REF] = "HEAD";
2190 static char ref_head[SIZEOF_REF] = "HEAD";
2191 static char ref_branch[SIZEOF_REF] = "";
2193 enum view_type {
2194 VIEW_MAIN,
2195 VIEW_DIFF,
2196 VIEW_LOG,
2197 VIEW_TREE,
2198 VIEW_BLOB,
2199 VIEW_BLAME,
2200 VIEW_BRANCH,
2201 VIEW_HELP,
2202 VIEW_PAGER,
2203 VIEW_STATUS,
2204 VIEW_STAGE,
2205 };
2207 struct view {
2208 enum view_type type; /* View type */
2209 const char *name; /* View name */
2210 const char *cmd_env; /* Command line set via environment */
2211 const char *id; /* Points to either of ref_{head,commit,blob} */
2213 struct view_ops *ops; /* View operations */
2215 enum keymap keymap; /* What keymap does this view have */
2216 bool git_dir; /* Whether the view requires a git directory. */
2218 char ref[SIZEOF_REF]; /* Hovered commit reference */
2219 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2221 int height, width; /* The width and height of the main window */
2222 WINDOW *win; /* The main window */
2223 WINDOW *title; /* The title window living below the main window */
2225 /* Navigation */
2226 unsigned long offset; /* Offset of the window top */
2227 unsigned long yoffset; /* Offset from the window side. */
2228 unsigned long lineno; /* Current line number */
2229 unsigned long p_offset; /* Previous offset of the window top */
2230 unsigned long p_yoffset;/* Previous offset from the window side */
2231 unsigned long p_lineno; /* Previous current line number */
2232 bool p_restore; /* Should the previous position be restored. */
2234 /* Searching */
2235 char grep[SIZEOF_STR]; /* Search string */
2236 regex_t *regex; /* Pre-compiled regexp */
2238 /* If non-NULL, points to the view that opened this view. If this view
2239 * is closed tig will switch back to the parent view. */
2240 struct view *parent;
2241 struct view *prev;
2243 /* Buffering */
2244 size_t lines; /* Total number of lines */
2245 struct line *line; /* Line index */
2246 unsigned int digits; /* Number of digits in the lines member. */
2248 /* Drawing */
2249 struct line *curline; /* Line currently being drawn. */
2250 enum line_type curtype; /* Attribute currently used for drawing. */
2251 unsigned long col; /* Column when drawing. */
2252 bool has_scrolled; /* View was scrolled. */
2254 /* Loading */
2255 struct io io;
2256 struct io *pipe;
2257 time_t start_time;
2258 time_t update_secs;
2259 };
2261 struct view_ops {
2262 /* What type of content being displayed. Used in the title bar. */
2263 const char *type;
2264 /* Default command arguments. */
2265 const char **argv;
2266 /* Open and reads in all view content. */
2267 bool (*open)(struct view *view);
2268 /* Read one line; updates view->line. */
2269 bool (*read)(struct view *view, char *data);
2270 /* Draw one line; @lineno must be < view->height. */
2271 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2272 /* Depending on view handle a special requests. */
2273 enum request (*request)(struct view *view, enum request request, struct line *line);
2274 /* Search for regexp in a line. */
2275 bool (*grep)(struct view *view, struct line *line);
2276 /* Select line */
2277 void (*select)(struct view *view, struct line *line);
2278 /* Prepare view for loading */
2279 bool (*prepare)(struct view *view);
2280 };
2282 static struct view_ops blame_ops;
2283 static struct view_ops blob_ops;
2284 static struct view_ops diff_ops;
2285 static struct view_ops help_ops;
2286 static struct view_ops log_ops;
2287 static struct view_ops main_ops;
2288 static struct view_ops pager_ops;
2289 static struct view_ops stage_ops;
2290 static struct view_ops status_ops;
2291 static struct view_ops tree_ops;
2292 static struct view_ops branch_ops;
2294 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2295 { type, name, #env, ref, ops, map, git }
2297 #define VIEW_(id, name, ops, git, ref) \
2298 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2300 static struct view views[] = {
2301 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2302 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2303 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2304 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2305 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2306 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2307 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2308 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2309 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2310 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2311 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2312 };
2314 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2316 #define foreach_view(view, i) \
2317 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2319 #define view_is_displayed(view) \
2320 (view == display[0] || view == display[1])
2322 static enum request
2323 view_request(struct view *view, enum request request)
2324 {
2325 if (!view || !view->lines)
2326 return request;
2327 return view->ops->request(view, request, &view->line[view->lineno]);
2328 }
2331 /*
2332 * View drawing.
2333 */
2335 static inline void
2336 set_view_attr(struct view *view, enum line_type type)
2337 {
2338 if (!view->curline->selected && view->curtype != type) {
2339 (void) wattrset(view->win, get_line_attr(type));
2340 wchgat(view->win, -1, 0, type, NULL);
2341 view->curtype = type;
2342 }
2343 }
2345 static int
2346 draw_chars(struct view *view, enum line_type type, const char *string,
2347 int max_len, bool use_tilde)
2348 {
2349 static char out_buffer[BUFSIZ * 2];
2350 int len = 0;
2351 int col = 0;
2352 int trimmed = FALSE;
2353 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2355 if (max_len <= 0)
2356 return 0;
2358 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2360 set_view_attr(view, type);
2361 if (len > 0) {
2362 if (opt_iconv_out != ICONV_NONE) {
2363 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2364 size_t inlen = len + 1;
2366 char *outbuf = out_buffer;
2367 size_t outlen = sizeof(out_buffer);
2369 size_t ret;
2371 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2372 if (ret != (size_t) -1) {
2373 string = out_buffer;
2374 len = sizeof(out_buffer) - outlen;
2375 }
2376 }
2378 waddnstr(view->win, string, len);
2379 }
2380 if (trimmed && use_tilde) {
2381 set_view_attr(view, LINE_DELIMITER);
2382 waddch(view->win, '~');
2383 col++;
2384 }
2386 return col;
2387 }
2389 static int
2390 draw_space(struct view *view, enum line_type type, int max, int spaces)
2391 {
2392 static char space[] = " ";
2393 int col = 0;
2395 spaces = MIN(max, spaces);
2397 while (spaces > 0) {
2398 int len = MIN(spaces, sizeof(space) - 1);
2400 col += draw_chars(view, type, space, len, FALSE);
2401 spaces -= len;
2402 }
2404 return col;
2405 }
2407 static bool
2408 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2409 {
2410 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2411 return view->width + view->yoffset <= view->col;
2412 }
2414 static bool
2415 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2416 {
2417 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2418 int max = view->width + view->yoffset - view->col;
2419 int i;
2421 if (max < size)
2422 size = max;
2424 set_view_attr(view, type);
2425 /* Using waddch() instead of waddnstr() ensures that
2426 * they'll be rendered correctly for the cursor line. */
2427 for (i = skip; i < size; i++)
2428 waddch(view->win, graphic[i]);
2430 view->col += size;
2431 if (size < max && skip <= size)
2432 waddch(view->win, ' ');
2433 view->col++;
2435 return view->width + view->yoffset <= view->col;
2436 }
2438 static bool
2439 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2440 {
2441 int max = MIN(view->width + view->yoffset - view->col, len);
2442 int col;
2444 if (text)
2445 col = draw_chars(view, type, text, max - 1, trim);
2446 else
2447 col = draw_space(view, type, max - 1, max - 1);
2449 view->col += col;
2450 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2451 return view->width + view->yoffset <= view->col;
2452 }
2454 static bool
2455 draw_date(struct view *view, struct time *time)
2456 {
2457 const char *date = mkdate(time, opt_date);
2458 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2460 return draw_field(view, LINE_DATE, date, cols, FALSE);
2461 }
2463 static bool
2464 draw_author(struct view *view, const char *author)
2465 {
2466 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2467 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2469 if (abbreviate && author)
2470 author = get_author_initials(author);
2472 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2473 }
2475 static bool
2476 draw_mode(struct view *view, mode_t mode)
2477 {
2478 const char *str;
2480 if (S_ISDIR(mode))
2481 str = "drwxr-xr-x";
2482 else if (S_ISLNK(mode))
2483 str = "lrwxrwxrwx";
2484 else if (S_ISGITLINK(mode))
2485 str = "m---------";
2486 else if (S_ISREG(mode) && mode & S_IXUSR)
2487 str = "-rwxr-xr-x";
2488 else if (S_ISREG(mode))
2489 str = "-rw-r--r--";
2490 else
2491 str = "----------";
2493 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2494 }
2496 static bool
2497 draw_lineno(struct view *view, unsigned int lineno)
2498 {
2499 char number[10];
2500 int digits3 = view->digits < 3 ? 3 : view->digits;
2501 int max = MIN(view->width + view->yoffset - view->col, digits3);
2502 char *text = NULL;
2503 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2505 lineno += view->offset + 1;
2506 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2507 static char fmt[] = "%1ld";
2509 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2510 if (string_format(number, fmt, lineno))
2511 text = number;
2512 }
2513 if (text)
2514 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2515 else
2516 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2517 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2518 }
2520 static bool
2521 draw_view_line(struct view *view, unsigned int lineno)
2522 {
2523 struct line *line;
2524 bool selected = (view->offset + lineno == view->lineno);
2526 assert(view_is_displayed(view));
2528 if (view->offset + lineno >= view->lines)
2529 return FALSE;
2531 line = &view->line[view->offset + lineno];
2533 wmove(view->win, lineno, 0);
2534 if (line->cleareol)
2535 wclrtoeol(view->win);
2536 view->col = 0;
2537 view->curline = line;
2538 view->curtype = LINE_NONE;
2539 line->selected = FALSE;
2540 line->dirty = line->cleareol = 0;
2542 if (selected) {
2543 set_view_attr(view, LINE_CURSOR);
2544 line->selected = TRUE;
2545 view->ops->select(view, line);
2546 }
2548 return view->ops->draw(view, line, lineno);
2549 }
2551 static void
2552 redraw_view_dirty(struct view *view)
2553 {
2554 bool dirty = FALSE;
2555 int lineno;
2557 for (lineno = 0; lineno < view->height; lineno++) {
2558 if (view->offset + lineno >= view->lines)
2559 break;
2560 if (!view->line[view->offset + lineno].dirty)
2561 continue;
2562 dirty = TRUE;
2563 if (!draw_view_line(view, lineno))
2564 break;
2565 }
2567 if (!dirty)
2568 return;
2569 wnoutrefresh(view->win);
2570 }
2572 static void
2573 redraw_view_from(struct view *view, int lineno)
2574 {
2575 assert(0 <= lineno && lineno < view->height);
2577 for (; lineno < view->height; lineno++) {
2578 if (!draw_view_line(view, lineno))
2579 break;
2580 }
2582 wnoutrefresh(view->win);
2583 }
2585 static void
2586 redraw_view(struct view *view)
2587 {
2588 werase(view->win);
2589 redraw_view_from(view, 0);
2590 }
2593 static void
2594 update_view_title(struct view *view)
2595 {
2596 char buf[SIZEOF_STR];
2597 char state[SIZEOF_STR];
2598 size_t bufpos = 0, statelen = 0;
2600 assert(view_is_displayed(view));
2602 if (view->type != VIEW_STATUS && view->lines) {
2603 unsigned int view_lines = view->offset + view->height;
2604 unsigned int lines = view->lines
2605 ? MIN(view_lines, view->lines) * 100 / view->lines
2606 : 0;
2608 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2609 view->ops->type,
2610 view->lineno + 1,
2611 view->lines,
2612 lines);
2614 }
2616 if (view->pipe) {
2617 time_t secs = time(NULL) - view->start_time;
2619 /* Three git seconds are a long time ... */
2620 if (secs > 2)
2621 string_format_from(state, &statelen, " loading %lds", secs);
2622 }
2624 string_format_from(buf, &bufpos, "[%s]", view->name);
2625 if (*view->ref && bufpos < view->width) {
2626 size_t refsize = strlen(view->ref);
2627 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2629 if (minsize < view->width)
2630 refsize = view->width - minsize + 7;
2631 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2632 }
2634 if (statelen && bufpos < view->width) {
2635 string_format_from(buf, &bufpos, "%s", state);
2636 }
2638 if (view == display[current_view])
2639 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2640 else
2641 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2643 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2644 wclrtoeol(view->title);
2645 wnoutrefresh(view->title);
2646 }
2648 static int
2649 apply_step(double step, int value)
2650 {
2651 if (step >= 1)
2652 return (int) step;
2653 value *= step + 0.01;
2654 return value ? value : 1;
2655 }
2657 static void
2658 resize_display(void)
2659 {
2660 int offset, i;
2661 struct view *base = display[0];
2662 struct view *view = display[1] ? display[1] : display[0];
2664 /* Setup window dimensions */
2666 getmaxyx(stdscr, base->height, base->width);
2668 /* Make room for the status window. */
2669 base->height -= 1;
2671 if (view != base) {
2672 /* Horizontal split. */
2673 view->width = base->width;
2674 view->height = apply_step(opt_scale_split_view, base->height);
2675 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2676 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2677 base->height -= view->height;
2679 /* Make room for the title bar. */
2680 view->height -= 1;
2681 }
2683 /* Make room for the title bar. */
2684 base->height -= 1;
2686 offset = 0;
2688 foreach_displayed_view (view, i) {
2689 if (!view->win) {
2690 view->win = newwin(view->height, 0, offset, 0);
2691 if (!view->win)
2692 die("Failed to create %s view", view->name);
2694 scrollok(view->win, FALSE);
2696 view->title = newwin(1, 0, offset + view->height, 0);
2697 if (!view->title)
2698 die("Failed to create title window");
2700 } else {
2701 wresize(view->win, view->height, view->width);
2702 mvwin(view->win, offset, 0);
2703 mvwin(view->title, offset + view->height, 0);
2704 }
2706 offset += view->height + 1;
2707 }
2708 }
2710 static void
2711 redraw_display(bool clear)
2712 {
2713 struct view *view;
2714 int i;
2716 foreach_displayed_view (view, i) {
2717 if (clear)
2718 wclear(view->win);
2719 redraw_view(view);
2720 update_view_title(view);
2721 }
2722 }
2725 /*
2726 * Option management
2727 */
2729 static void
2730 toggle_enum_option_do(unsigned int *opt, const char *help,
2731 const struct enum_map *map, size_t size)
2732 {
2733 *opt = (*opt + 1) % size;
2734 redraw_display(FALSE);
2735 report("Displaying %s %s", enum_name(map[*opt]), help);
2736 }
2738 #define toggle_enum_option(opt, help, map) \
2739 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2741 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2742 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2744 static void
2745 toggle_view_option(bool *option, const char *help)
2746 {
2747 *option = !*option;
2748 redraw_display(FALSE);
2749 report("%sabling %s", *option ? "En" : "Dis", help);
2750 }
2752 static void
2753 open_option_menu(void)
2754 {
2755 const struct menu_item menu[] = {
2756 { '.', "line numbers", &opt_line_number },
2757 { 'D', "date display", &opt_date },
2758 { 'A', "author display", &opt_author },
2759 { 'g', "revision graph display", &opt_rev_graph },
2760 { 'F', "reference display", &opt_show_refs },
2761 { 0 }
2762 };
2763 int selected = 0;
2765 if (prompt_menu("Toggle option", menu, &selected)) {
2766 if (menu[selected].data == &opt_date)
2767 toggle_date();
2768 else if (menu[selected].data == &opt_author)
2769 toggle_author();
2770 else
2771 toggle_view_option(menu[selected].data, menu[selected].text);
2772 }
2773 }
2775 static void
2776 maximize_view(struct view *view)
2777 {
2778 memset(display, 0, sizeof(display));
2779 current_view = 0;
2780 display[current_view] = view;
2781 resize_display();
2782 redraw_display(FALSE);
2783 report("");
2784 }
2787 /*
2788 * Navigation
2789 */
2791 static bool
2792 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2793 {
2794 if (lineno >= view->lines)
2795 lineno = view->lines > 0 ? view->lines - 1 : 0;
2797 if (offset > lineno || offset + view->height <= lineno) {
2798 unsigned long half = view->height / 2;
2800 if (lineno > half)
2801 offset = lineno - half;
2802 else
2803 offset = 0;
2804 }
2806 if (offset != view->offset || lineno != view->lineno) {
2807 view->offset = offset;
2808 view->lineno = lineno;
2809 return TRUE;
2810 }
2812 return FALSE;
2813 }
2815 /* Scrolling backend */
2816 static void
2817 do_scroll_view(struct view *view, int lines)
2818 {
2819 bool redraw_current_line = FALSE;
2821 /* The rendering expects the new offset. */
2822 view->offset += lines;
2824 assert(0 <= view->offset && view->offset < view->lines);
2825 assert(lines);
2827 /* Move current line into the view. */
2828 if (view->lineno < view->offset) {
2829 view->lineno = view->offset;
2830 redraw_current_line = TRUE;
2831 } else if (view->lineno >= view->offset + view->height) {
2832 view->lineno = view->offset + view->height - 1;
2833 redraw_current_line = TRUE;
2834 }
2836 assert(view->offset <= view->lineno && view->lineno < view->lines);
2838 /* Redraw the whole screen if scrolling is pointless. */
2839 if (view->height < ABS(lines)) {
2840 redraw_view(view);
2842 } else {
2843 int line = lines > 0 ? view->height - lines : 0;
2844 int end = line + ABS(lines);
2846 scrollok(view->win, TRUE);
2847 wscrl(view->win, lines);
2848 scrollok(view->win, FALSE);
2850 while (line < end && draw_view_line(view, line))
2851 line++;
2853 if (redraw_current_line)
2854 draw_view_line(view, view->lineno - view->offset);
2855 wnoutrefresh(view->win);
2856 }
2858 view->has_scrolled = TRUE;
2859 report("");
2860 }
2862 /* Scroll frontend */
2863 static void
2864 scroll_view(struct view *view, enum request request)
2865 {
2866 int lines = 1;
2868 assert(view_is_displayed(view));
2870 switch (request) {
2871 case REQ_SCROLL_LEFT:
2872 if (view->yoffset == 0) {
2873 report("Cannot scroll beyond the first column");
2874 return;
2875 }
2876 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2877 view->yoffset = 0;
2878 else
2879 view->yoffset -= apply_step(opt_hscroll, view->width);
2880 redraw_view_from(view, 0);
2881 report("");
2882 return;
2883 case REQ_SCROLL_RIGHT:
2884 view->yoffset += apply_step(opt_hscroll, view->width);
2885 redraw_view(view);
2886 report("");
2887 return;
2888 case REQ_SCROLL_PAGE_DOWN:
2889 lines = view->height;
2890 case REQ_SCROLL_LINE_DOWN:
2891 if (view->offset + lines > view->lines)
2892 lines = view->lines - view->offset;
2894 if (lines == 0 || view->offset + view->height >= view->lines) {
2895 report("Cannot scroll beyond the last line");
2896 return;
2897 }
2898 break;
2900 case REQ_SCROLL_PAGE_UP:
2901 lines = view->height;
2902 case REQ_SCROLL_LINE_UP:
2903 if (lines > view->offset)
2904 lines = view->offset;
2906 if (lines == 0) {
2907 report("Cannot scroll beyond the first line");
2908 return;
2909 }
2911 lines = -lines;
2912 break;
2914 default:
2915 die("request %d not handled in switch", request);
2916 }
2918 do_scroll_view(view, lines);
2919 }
2921 /* Cursor moving */
2922 static void
2923 move_view(struct view *view, enum request request)
2924 {
2925 int scroll_steps = 0;
2926 int steps;
2928 switch (request) {
2929 case REQ_MOVE_FIRST_LINE:
2930 steps = -view->lineno;
2931 break;
2933 case REQ_MOVE_LAST_LINE:
2934 steps = view->lines - view->lineno - 1;
2935 break;
2937 case REQ_MOVE_PAGE_UP:
2938 steps = view->height > view->lineno
2939 ? -view->lineno : -view->height;
2940 break;
2942 case REQ_MOVE_PAGE_DOWN:
2943 steps = view->lineno + view->height >= view->lines
2944 ? view->lines - view->lineno - 1 : view->height;
2945 break;
2947 case REQ_MOVE_UP:
2948 steps = -1;
2949 break;
2951 case REQ_MOVE_DOWN:
2952 steps = 1;
2953 break;
2955 default:
2956 die("request %d not handled in switch", request);
2957 }
2959 if (steps <= 0 && view->lineno == 0) {
2960 report("Cannot move beyond the first line");
2961 return;
2963 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2964 report("Cannot move beyond the last line");
2965 return;
2966 }
2968 /* Move the current line */
2969 view->lineno += steps;
2970 assert(0 <= view->lineno && view->lineno < view->lines);
2972 /* Check whether the view needs to be scrolled */
2973 if (view->lineno < view->offset ||
2974 view->lineno >= view->offset + view->height) {
2975 scroll_steps = steps;
2976 if (steps < 0 && -steps > view->offset) {
2977 scroll_steps = -view->offset;
2979 } else if (steps > 0) {
2980 if (view->lineno == view->lines - 1 &&
2981 view->lines > view->height) {
2982 scroll_steps = view->lines - view->offset - 1;
2983 if (scroll_steps >= view->height)
2984 scroll_steps -= view->height - 1;
2985 }
2986 }
2987 }
2989 if (!view_is_displayed(view)) {
2990 view->offset += scroll_steps;
2991 assert(0 <= view->offset && view->offset < view->lines);
2992 view->ops->select(view, &view->line[view->lineno]);
2993 return;
2994 }
2996 /* Repaint the old "current" line if we be scrolling */
2997 if (ABS(steps) < view->height)
2998 draw_view_line(view, view->lineno - steps - view->offset);
3000 if (scroll_steps) {
3001 do_scroll_view(view, scroll_steps);
3002 return;
3003 }
3005 /* Draw the current line */
3006 draw_view_line(view, view->lineno - view->offset);
3008 wnoutrefresh(view->win);
3009 report("");
3010 }
3013 /*
3014 * Searching
3015 */
3017 static void search_view(struct view *view, enum request request);
3019 static bool
3020 grep_text(struct view *view, const char *text[])
3021 {
3022 regmatch_t pmatch;
3023 size_t i;
3025 for (i = 0; text[i]; i++)
3026 if (*text[i] &&
3027 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3028 return TRUE;
3029 return FALSE;
3030 }
3032 static void
3033 select_view_line(struct view *view, unsigned long lineno)
3034 {
3035 unsigned long old_lineno = view->lineno;
3036 unsigned long old_offset = view->offset;
3038 if (goto_view_line(view, view->offset, lineno)) {
3039 if (view_is_displayed(view)) {
3040 if (old_offset != view->offset) {
3041 redraw_view(view);
3042 } else {
3043 draw_view_line(view, old_lineno - view->offset);
3044 draw_view_line(view, view->lineno - view->offset);
3045 wnoutrefresh(view->win);
3046 }
3047 } else {
3048 view->ops->select(view, &view->line[view->lineno]);
3049 }
3050 }
3051 }
3053 static void
3054 find_next(struct view *view, enum request request)
3055 {
3056 unsigned long lineno = view->lineno;
3057 int direction;
3059 if (!*view->grep) {
3060 if (!*opt_search)
3061 report("No previous search");
3062 else
3063 search_view(view, request);
3064 return;
3065 }
3067 switch (request) {
3068 case REQ_SEARCH:
3069 case REQ_FIND_NEXT:
3070 direction = 1;
3071 break;
3073 case REQ_SEARCH_BACK:
3074 case REQ_FIND_PREV:
3075 direction = -1;
3076 break;
3078 default:
3079 return;
3080 }
3082 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3083 lineno += direction;
3085 /* Note, lineno is unsigned long so will wrap around in which case it
3086 * will become bigger than view->lines. */
3087 for (; lineno < view->lines; lineno += direction) {
3088 if (view->ops->grep(view, &view->line[lineno])) {
3089 select_view_line(view, lineno);
3090 report("Line %ld matches '%s'", lineno + 1, view->grep);
3091 return;
3092 }
3093 }
3095 report("No match found for '%s'", view->grep);
3096 }
3098 static void
3099 search_view(struct view *view, enum request request)
3100 {
3101 int regex_err;
3103 if (view->regex) {
3104 regfree(view->regex);
3105 *view->grep = 0;
3106 } else {
3107 view->regex = calloc(1, sizeof(*view->regex));
3108 if (!view->regex)
3109 return;
3110 }
3112 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3113 if (regex_err != 0) {
3114 char buf[SIZEOF_STR] = "unknown error";
3116 regerror(regex_err, view->regex, buf, sizeof(buf));
3117 report("Search failed: %s", buf);
3118 return;
3119 }
3121 string_copy(view->grep, opt_search);
3123 find_next(view, request);
3124 }
3126 /*
3127 * Incremental updating
3128 */
3130 static void
3131 reset_view(struct view *view)
3132 {
3133 int i;
3135 for (i = 0; i < view->lines; i++)
3136 free(view->line[i].data);
3137 free(view->line);
3139 view->p_offset = view->offset;
3140 view->p_yoffset = view->yoffset;
3141 view->p_lineno = view->lineno;
3143 view->line = NULL;
3144 view->offset = 0;
3145 view->yoffset = 0;
3146 view->lines = 0;
3147 view->lineno = 0;
3148 view->vid[0] = 0;
3149 view->update_secs = 0;
3150 }
3152 static const char *
3153 format_arg(const char *name)
3154 {
3155 static struct {
3156 const char *name;
3157 size_t namelen;
3158 const char *value;
3159 const char *value_if_empty;
3160 } vars[] = {
3161 #define FORMAT_VAR(name, value, value_if_empty) \
3162 { name, STRING_SIZE(name), value, value_if_empty }
3163 FORMAT_VAR("%(directory)", opt_path, ""),
3164 FORMAT_VAR("%(file)", opt_file, ""),
3165 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3166 FORMAT_VAR("%(head)", ref_head, ""),
3167 FORMAT_VAR("%(commit)", ref_commit, ""),
3168 FORMAT_VAR("%(blob)", ref_blob, ""),
3169 FORMAT_VAR("%(branch)", ref_branch, ""),
3170 };
3171 int i;
3173 for (i = 0; i < ARRAY_SIZE(vars); i++)
3174 if (!strncmp(name, vars[i].name, vars[i].namelen))
3175 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3177 report("Unknown replacement: `%s`", name);
3178 return NULL;
3179 }
3181 static bool
3182 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3183 {
3184 char buf[SIZEOF_STR];
3185 int argc;
3186 bool noreplace = flags == FORMAT_NONE;
3188 argv_free(dst_argv);
3190 for (argc = 0; src_argv[argc]; argc++) {
3191 const char *arg = src_argv[argc];
3192 size_t bufpos = 0;
3194 while (arg) {
3195 char *next = strstr(arg, "%(");
3196 int len = next - arg;
3197 const char *value;
3199 if (!next || noreplace) {
3200 len = strlen(arg);
3201 value = "";
3203 } else {
3204 value = format_arg(next);
3206 if (!value) {
3207 return FALSE;
3208 }
3209 }
3211 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3212 return FALSE;
3214 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3215 }
3217 dst_argv[argc] = strdup(buf);
3218 if (!dst_argv[argc])
3219 break;
3220 }
3222 dst_argv[argc] = NULL;
3224 return src_argv[argc] == NULL;
3225 }
3227 static bool
3228 restore_view_position(struct view *view)
3229 {
3230 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3231 return FALSE;
3233 /* Changing the view position cancels the restoring. */
3234 /* FIXME: Changing back to the first line is not detected. */
3235 if (view->offset != 0 || view->lineno != 0) {
3236 view->p_restore = FALSE;
3237 return FALSE;
3238 }
3240 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3241 view_is_displayed(view))
3242 werase(view->win);
3244 view->yoffset = view->p_yoffset;
3245 view->p_restore = FALSE;
3247 return TRUE;
3248 }
3250 static void
3251 end_update(struct view *view, bool force)
3252 {
3253 if (!view->pipe)
3254 return;
3255 while (!view->ops->read(view, NULL))
3256 if (!force)
3257 return;
3258 if (force)
3259 io_kill(view->pipe);
3260 io_done(view->pipe);
3261 view->pipe = NULL;
3262 }
3264 static void
3265 setup_update(struct view *view, const char *vid)
3266 {
3267 reset_view(view);
3268 string_copy_rev(view->vid, vid);
3269 view->pipe = &view->io;
3270 view->start_time = time(NULL);
3271 }
3273 static bool
3274 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3275 {
3276 io_init(&view->io, dir, IO_RD);
3277 return format_argv(view->io.argv, argv, replace ? FORMAT_ALL : FORMAT_NONE);
3278 }
3280 static bool
3281 prepare_update(struct view *view, const char *argv[], const char *dir)
3282 {
3283 if (view->pipe)
3284 end_update(view, TRUE);
3285 return prepare_io(view, dir, argv, FALSE);
3286 }
3288 static bool
3289 start_update(struct view *view, const char **argv, const char *dir)
3290 {
3291 if (view->pipe)
3292 io_done(view->pipe);
3293 return prepare_io(view, dir, argv, FALSE) &&
3294 io_start(&view->io);
3295 }
3297 static bool
3298 prepare_update_file(struct view *view, const char *name)
3299 {
3300 if (view->pipe)
3301 end_update(view, TRUE);
3302 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3303 }
3305 static bool
3306 begin_update(struct view *view, bool refresh)
3307 {
3308 if (view->pipe)
3309 end_update(view, TRUE);
3311 if (!refresh) {
3312 if (view->ops->prepare) {
3313 if (!view->ops->prepare(view))
3314 return FALSE;
3315 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3316 return FALSE;
3317 }
3319 /* Put the current ref_* value to the view title ref
3320 * member. This is needed by the blob view. Most other
3321 * views sets it automatically after loading because the
3322 * first line is a commit line. */
3323 string_copy_rev(view->ref, view->id);
3324 }
3326 if (!io_start(&view->io))
3327 return FALSE;
3329 setup_update(view, view->id);
3331 return TRUE;
3332 }
3334 static bool
3335 update_view(struct view *view)
3336 {
3337 char out_buffer[BUFSIZ * 2];
3338 char *line;
3339 /* Clear the view and redraw everything since the tree sorting
3340 * might have rearranged things. */
3341 bool redraw = view->lines == 0;
3342 bool can_read = TRUE;
3344 if (!view->pipe)
3345 return TRUE;
3347 if (!io_can_read(view->pipe)) {
3348 if (view->lines == 0 && view_is_displayed(view)) {
3349 time_t secs = time(NULL) - view->start_time;
3351 if (secs > 1 && secs > view->update_secs) {
3352 if (view->update_secs == 0)
3353 redraw_view(view);
3354 update_view_title(view);
3355 view->update_secs = secs;
3356 }
3357 }
3358 return TRUE;
3359 }
3361 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3362 if (opt_iconv_in != ICONV_NONE) {
3363 ICONV_CONST char *inbuf = line;
3364 size_t inlen = strlen(line) + 1;
3366 char *outbuf = out_buffer;
3367 size_t outlen = sizeof(out_buffer);
3369 size_t ret;
3371 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3372 if (ret != (size_t) -1)
3373 line = out_buffer;
3374 }
3376 if (!view->ops->read(view, line)) {
3377 report("Allocation failure");
3378 end_update(view, TRUE);
3379 return FALSE;
3380 }
3381 }
3383 {
3384 unsigned long lines = view->lines;
3385 int digits;
3387 for (digits = 0; lines; digits++)
3388 lines /= 10;
3390 /* Keep the displayed view in sync with line number scaling. */
3391 if (digits != view->digits) {
3392 view->digits = digits;
3393 if (opt_line_number || view->type == VIEW_BLAME)
3394 redraw = TRUE;
3395 }
3396 }
3398 if (io_error(view->pipe)) {
3399 report("Failed to read: %s", io_strerror(view->pipe));
3400 end_update(view, TRUE);
3402 } else if (io_eof(view->pipe)) {
3403 if (view_is_displayed(view))
3404 report("");
3405 end_update(view, FALSE);
3406 }
3408 if (restore_view_position(view))
3409 redraw = TRUE;
3411 if (!view_is_displayed(view))
3412 return TRUE;
3414 if (redraw)
3415 redraw_view_from(view, 0);
3416 else
3417 redraw_view_dirty(view);
3419 /* Update the title _after_ the redraw so that if the redraw picks up a
3420 * commit reference in view->ref it'll be available here. */
3421 update_view_title(view);
3422 return TRUE;
3423 }
3425 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3427 static struct line *
3428 add_line_data(struct view *view, void *data, enum line_type type)
3429 {
3430 struct line *line;
3432 if (!realloc_lines(&view->line, view->lines, 1))
3433 return NULL;
3435 line = &view->line[view->lines++];
3436 memset(line, 0, sizeof(*line));
3437 line->type = type;
3438 line->data = data;
3439 line->dirty = 1;
3441 return line;
3442 }
3444 static struct line *
3445 add_line_text(struct view *view, const char *text, enum line_type type)
3446 {
3447 char *data = text ? strdup(text) : NULL;
3449 return data ? add_line_data(view, data, type) : NULL;
3450 }
3452 static struct line *
3453 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3454 {
3455 char buf[SIZEOF_STR];
3456 va_list args;
3458 va_start(args, fmt);
3459 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3460 buf[0] = 0;
3461 va_end(args);
3463 return buf[0] ? add_line_text(view, buf, type) : NULL;
3464 }
3466 /*
3467 * View opening
3468 */
3470 enum open_flags {
3471 OPEN_DEFAULT = 0, /* Use default view switching. */
3472 OPEN_SPLIT = 1, /* Split current view. */
3473 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3474 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3475 OPEN_PREPARED = 32, /* Open already prepared command. */
3476 };
3478 static void
3479 open_view(struct view *prev, enum request request, enum open_flags flags)
3480 {
3481 bool split = !!(flags & OPEN_SPLIT);
3482 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3483 bool nomaximize = !!(flags & OPEN_REFRESH);
3484 struct view *view = VIEW(request);
3485 int nviews = displayed_views();
3486 struct view *base_view = display[0];
3488 if (view == prev && nviews == 1 && !reload) {
3489 report("Already in %s view", view->name);
3490 return;
3491 }
3493 if (view->git_dir && !opt_git_dir[0]) {
3494 report("The %s view is disabled in pager view", view->name);
3495 return;
3496 }
3498 if (split) {
3499 display[1] = view;
3500 current_view = 1;
3501 view->parent = prev;
3502 } else if (!nomaximize) {
3503 /* Maximize the current view. */
3504 memset(display, 0, sizeof(display));
3505 current_view = 0;
3506 display[current_view] = view;
3507 }
3509 /* No prev signals that this is the first loaded view. */
3510 if (prev && view != prev) {
3511 view->prev = prev;
3512 }
3514 /* Resize the view when switching between split- and full-screen,
3515 * or when switching between two different full-screen views. */
3516 if (nviews != displayed_views() ||
3517 (nviews == 1 && base_view != display[0]))
3518 resize_display();
3520 if (view->ops->open) {
3521 if (view->pipe)
3522 end_update(view, TRUE);
3523 if (!view->ops->open(view)) {
3524 report("Failed to load %s view", view->name);
3525 return;
3526 }
3527 restore_view_position(view);
3529 } else if ((reload || strcmp(view->vid, view->id)) &&
3530 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3531 report("Failed to load %s view", view->name);
3532 return;
3533 }
3535 if (split && prev->lineno - prev->offset >= prev->height) {
3536 /* Take the title line into account. */
3537 int lines = prev->lineno - prev->offset - prev->height + 1;
3539 /* Scroll the view that was split if the current line is
3540 * outside the new limited view. */
3541 do_scroll_view(prev, lines);
3542 }
3544 if (prev && view != prev && split && view_is_displayed(prev)) {
3545 /* "Blur" the previous view. */
3546 update_view_title(prev);
3547 }
3549 if (view->pipe && view->lines == 0) {
3550 /* Clear the old view and let the incremental updating refill
3551 * the screen. */
3552 werase(view->win);
3553 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3554 report("");
3555 } else if (view_is_displayed(view)) {
3556 redraw_view(view);
3557 report("");
3558 }
3559 }
3561 static void
3562 open_external_viewer(const char *argv[], const char *dir)
3563 {
3564 def_prog_mode(); /* save current tty modes */
3565 endwin(); /* restore original tty modes */
3566 io_run_fg(argv, dir);
3567 fprintf(stderr, "Press Enter to continue");
3568 getc(opt_tty);
3569 reset_prog_mode();
3570 redraw_display(TRUE);
3571 }
3573 static void
3574 open_mergetool(const char *file)
3575 {
3576 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3578 open_external_viewer(mergetool_argv, opt_cdup);
3579 }
3581 static void
3582 open_editor(const char *file)
3583 {
3584 const char *editor_argv[] = { "vi", file, NULL };
3585 const char *editor;
3587 editor = getenv("GIT_EDITOR");
3588 if (!editor && *opt_editor)
3589 editor = opt_editor;
3590 if (!editor)
3591 editor = getenv("VISUAL");
3592 if (!editor)
3593 editor = getenv("EDITOR");
3594 if (!editor)
3595 editor = "vi";
3597 editor_argv[0] = editor;
3598 open_external_viewer(editor_argv, opt_cdup);
3599 }
3601 static void
3602 open_run_request(enum request request)
3603 {
3604 struct run_request *req = get_run_request(request);
3605 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3607 if (!req) {
3608 report("Unknown run request");
3609 return;
3610 }
3612 if (format_argv(argv, req->argv, FORMAT_ALL))
3613 open_external_viewer(argv, NULL);
3614 argv_free(argv);
3615 }
3617 /*
3618 * User request switch noodle
3619 */
3621 static int
3622 view_driver(struct view *view, enum request request)
3623 {
3624 int i;
3626 if (request == REQ_NONE)
3627 return TRUE;
3629 if (request > REQ_NONE) {
3630 open_run_request(request);
3631 view_request(view, REQ_REFRESH);
3632 return TRUE;
3633 }
3635 request = view_request(view, request);
3636 if (request == REQ_NONE)
3637 return TRUE;
3639 switch (request) {
3640 case REQ_MOVE_UP:
3641 case REQ_MOVE_DOWN:
3642 case REQ_MOVE_PAGE_UP:
3643 case REQ_MOVE_PAGE_DOWN:
3644 case REQ_MOVE_FIRST_LINE:
3645 case REQ_MOVE_LAST_LINE:
3646 move_view(view, request);
3647 break;
3649 case REQ_SCROLL_LEFT:
3650 case REQ_SCROLL_RIGHT:
3651 case REQ_SCROLL_LINE_DOWN:
3652 case REQ_SCROLL_LINE_UP:
3653 case REQ_SCROLL_PAGE_DOWN:
3654 case REQ_SCROLL_PAGE_UP:
3655 scroll_view(view, request);
3656 break;
3658 case REQ_VIEW_BLAME:
3659 if (!opt_file[0]) {
3660 report("No file chosen, press %s to open tree view",
3661 get_key(view->keymap, REQ_VIEW_TREE));
3662 break;
3663 }
3664 open_view(view, request, OPEN_DEFAULT);
3665 break;
3667 case REQ_VIEW_BLOB:
3668 if (!ref_blob[0]) {
3669 report("No file chosen, press %s to open tree view",
3670 get_key(view->keymap, REQ_VIEW_TREE));
3671 break;
3672 }
3673 open_view(view, request, OPEN_DEFAULT);
3674 break;
3676 case REQ_VIEW_PAGER:
3677 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3678 report("No pager content, press %s to run command from prompt",
3679 get_key(view->keymap, REQ_PROMPT));
3680 break;
3681 }
3682 open_view(view, request, OPEN_DEFAULT);
3683 break;
3685 case REQ_VIEW_STAGE:
3686 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3687 report("No stage content, press %s to open the status view and choose file",
3688 get_key(view->keymap, REQ_VIEW_STATUS));
3689 break;
3690 }
3691 open_view(view, request, OPEN_DEFAULT);
3692 break;
3694 case REQ_VIEW_STATUS:
3695 if (opt_is_inside_work_tree == FALSE) {
3696 report("The status view requires a working tree");
3697 break;
3698 }
3699 open_view(view, request, OPEN_DEFAULT);
3700 break;
3702 case REQ_VIEW_MAIN:
3703 case REQ_VIEW_DIFF:
3704 case REQ_VIEW_LOG:
3705 case REQ_VIEW_TREE:
3706 case REQ_VIEW_HELP:
3707 case REQ_VIEW_BRANCH:
3708 open_view(view, request, OPEN_DEFAULT);
3709 break;
3711 case REQ_NEXT:
3712 case REQ_PREVIOUS:
3713 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3715 if (view->parent) {
3716 int line;
3718 view = view->parent;
3719 line = view->lineno;
3720 move_view(view, request);
3721 if (view_is_displayed(view))
3722 update_view_title(view);
3723 if (line != view->lineno)
3724 view_request(view, REQ_ENTER);
3725 } else {
3726 move_view(view, request);
3727 }
3728 break;
3730 case REQ_VIEW_NEXT:
3731 {
3732 int nviews = displayed_views();
3733 int next_view = (current_view + 1) % nviews;
3735 if (next_view == current_view) {
3736 report("Only one view is displayed");
3737 break;
3738 }
3740 current_view = next_view;
3741 /* Blur out the title of the previous view. */
3742 update_view_title(view);
3743 report("");
3744 break;
3745 }
3746 case REQ_REFRESH:
3747 report("Refreshing is not yet supported for the %s view", view->name);
3748 break;
3750 case REQ_MAXIMIZE:
3751 if (displayed_views() == 2)
3752 maximize_view(view);
3753 break;
3755 case REQ_OPTIONS:
3756 open_option_menu();
3757 break;
3759 case REQ_TOGGLE_LINENO:
3760 toggle_view_option(&opt_line_number, "line numbers");
3761 break;
3763 case REQ_TOGGLE_DATE:
3764 toggle_date();
3765 break;
3767 case REQ_TOGGLE_AUTHOR:
3768 toggle_author();
3769 break;
3771 case REQ_TOGGLE_REV_GRAPH:
3772 toggle_view_option(&opt_rev_graph, "revision graph display");
3773 break;
3775 case REQ_TOGGLE_REFS:
3776 toggle_view_option(&opt_show_refs, "reference display");
3777 break;
3779 case REQ_TOGGLE_SORT_FIELD:
3780 case REQ_TOGGLE_SORT_ORDER:
3781 report("Sorting is not yet supported for the %s view", view->name);
3782 break;
3784 case REQ_SEARCH:
3785 case REQ_SEARCH_BACK:
3786 search_view(view, request);
3787 break;
3789 case REQ_FIND_NEXT:
3790 case REQ_FIND_PREV:
3791 find_next(view, request);
3792 break;
3794 case REQ_STOP_LOADING:
3795 foreach_view(view, i) {
3796 if (view->pipe)
3797 report("Stopped loading the %s view", view->name),
3798 end_update(view, TRUE);
3799 }
3800 break;
3802 case REQ_SHOW_VERSION:
3803 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3804 return TRUE;
3806 case REQ_SCREEN_REDRAW:
3807 redraw_display(TRUE);
3808 break;
3810 case REQ_EDIT:
3811 report("Nothing to edit");
3812 break;
3814 case REQ_ENTER:
3815 report("Nothing to enter");
3816 break;
3818 case REQ_VIEW_CLOSE:
3819 /* XXX: Mark closed views by letting view->prev point to the
3820 * view itself. Parents to closed view should never be
3821 * followed. */
3822 if (view->prev && view->prev != view) {
3823 maximize_view(view->prev);
3824 view->prev = view;
3825 break;
3826 }
3827 /* Fall-through */
3828 case REQ_QUIT:
3829 return FALSE;
3831 default:
3832 report("Unknown key, press %s for help",
3833 get_key(view->keymap, REQ_VIEW_HELP));
3834 return TRUE;
3835 }
3837 return TRUE;
3838 }
3841 /*
3842 * View backend utilities
3843 */
3845 enum sort_field {
3846 ORDERBY_NAME,
3847 ORDERBY_DATE,
3848 ORDERBY_AUTHOR,
3849 };
3851 struct sort_state {
3852 const enum sort_field *fields;
3853 size_t size, current;
3854 bool reverse;
3855 };
3857 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3858 #define get_sort_field(state) ((state).fields[(state).current])
3859 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3861 static void
3862 sort_view(struct view *view, enum request request, struct sort_state *state,
3863 int (*compare)(const void *, const void *))
3864 {
3865 switch (request) {
3866 case REQ_TOGGLE_SORT_FIELD:
3867 state->current = (state->current + 1) % state->size;
3868 break;
3870 case REQ_TOGGLE_SORT_ORDER:
3871 state->reverse = !state->reverse;
3872 break;
3873 default:
3874 die("Not a sort request");
3875 }
3877 qsort(view->line, view->lines, sizeof(*view->line), compare);
3878 redraw_view(view);
3879 }
3881 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3883 /* Small author cache to reduce memory consumption. It uses binary
3884 * search to lookup or find place to position new entries. No entries
3885 * are ever freed. */
3886 static const char *
3887 get_author(const char *name)
3888 {
3889 static const char **authors;
3890 static size_t authors_size;
3891 int from = 0, to = authors_size - 1;
3893 while (from <= to) {
3894 size_t pos = (to + from) / 2;
3895 int cmp = strcmp(name, authors[pos]);
3897 if (!cmp)
3898 return authors[pos];
3900 if (cmp < 0)
3901 to = pos - 1;
3902 else
3903 from = pos + 1;
3904 }
3906 if (!realloc_authors(&authors, authors_size, 1))
3907 return NULL;
3908 name = strdup(name);
3909 if (!name)
3910 return NULL;
3912 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3913 authors[from] = name;
3914 authors_size++;
3916 return name;
3917 }
3919 static void
3920 parse_timesec(struct time *time, const char *sec)
3921 {
3922 time->sec = (time_t) atol(sec);
3923 }
3925 static void
3926 parse_timezone(struct time *time, const char *zone)
3927 {
3928 long tz;
3930 tz = ('0' - zone[1]) * 60 * 60 * 10;
3931 tz += ('0' - zone[2]) * 60 * 60;
3932 tz += ('0' - zone[3]) * 60 * 10;
3933 tz += ('0' - zone[4]) * 60;
3935 if (zone[0] == '-')
3936 tz = -tz;
3938 time->tz = tz;
3939 time->sec -= tz;
3940 }
3942 /* Parse author lines where the name may be empty:
3943 * author <email@address.tld> 1138474660 +0100
3944 */
3945 static void
3946 parse_author_line(char *ident, const char **author, struct time *time)
3947 {
3948 char *nameend = strchr(ident, '<');
3949 char *emailend = strchr(ident, '>');
3951 if (nameend && emailend)
3952 *nameend = *emailend = 0;
3953 ident = chomp_string(ident);
3954 if (!*ident) {
3955 if (nameend)
3956 ident = chomp_string(nameend + 1);
3957 if (!*ident)
3958 ident = "Unknown";
3959 }
3961 *author = get_author(ident);
3963 /* Parse epoch and timezone */
3964 if (emailend && emailend[1] == ' ') {
3965 char *secs = emailend + 2;
3966 char *zone = strchr(secs, ' ');
3968 parse_timesec(time, secs);
3970 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3971 parse_timezone(time, zone + 1);
3972 }
3973 }
3975 static bool
3976 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3977 {
3978 char rev[SIZEOF_REV];
3979 const char *revlist_argv[] = {
3980 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3981 };
3982 struct menu_item *items;
3983 char text[SIZEOF_STR];
3984 bool ok = TRUE;
3985 int i;
3987 items = calloc(*parents + 1, sizeof(*items));
3988 if (!items)
3989 return FALSE;
3991 for (i = 0; i < *parents; i++) {
3992 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3993 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3994 !(items[i].text = strdup(text))) {
3995 ok = FALSE;
3996 break;
3997 }
3998 }
4000 if (ok) {
4001 *parents = 0;
4002 ok = prompt_menu("Select parent", items, parents);
4003 }
4004 for (i = 0; items[i].text; i++)
4005 free((char *) items[i].text);
4006 free(items);
4007 return ok;
4008 }
4010 static bool
4011 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4012 {
4013 char buf[SIZEOF_STR * 4];
4014 const char *revlist_argv[] = {
4015 "git", "log", "--no-color", "-1",
4016 "--pretty=format:%P", id, "--", path, NULL
4017 };
4018 int parents;
4020 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4021 (parents = strlen(buf) / 40) < 0) {
4022 report("Failed to get parent information");
4023 return FALSE;
4025 } else if (parents == 0) {
4026 if (path)
4027 report("Path '%s' does not exist in the parent", path);
4028 else
4029 report("The selected commit has no parents");
4030 return FALSE;
4031 }
4033 if (parents == 1)
4034 parents = 0;
4035 else if (!open_commit_parent_menu(buf, &parents))
4036 return FALSE;
4038 string_copy_rev(rev, &buf[41 * parents]);
4039 return TRUE;
4040 }
4042 /*
4043 * Pager backend
4044 */
4046 static bool
4047 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4048 {
4049 char text[SIZEOF_STR];
4051 if (opt_line_number && draw_lineno(view, lineno))
4052 return TRUE;
4054 string_expand(text, sizeof(text), line->data, opt_tab_size);
4055 draw_text(view, line->type, text, TRUE);
4056 return TRUE;
4057 }
4059 static bool
4060 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4061 {
4062 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4063 char ref[SIZEOF_STR];
4065 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4066 return TRUE;
4068 /* This is the only fatal call, since it can "corrupt" the buffer. */
4069 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4070 return FALSE;
4072 return TRUE;
4073 }
4075 static void
4076 add_pager_refs(struct view *view, struct line *line)
4077 {
4078 char buf[SIZEOF_STR];
4079 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4080 struct ref_list *list;
4081 size_t bufpos = 0, i;
4082 const char *sep = "Refs: ";
4083 bool is_tag = FALSE;
4085 assert(line->type == LINE_COMMIT);
4087 list = get_ref_list(commit_id);
4088 if (!list) {
4089 if (view->type == VIEW_DIFF)
4090 goto try_add_describe_ref;
4091 return;
4092 }
4094 for (i = 0; i < list->size; i++) {
4095 struct ref *ref = list->refs[i];
4096 const char *fmt = ref->tag ? "%s[%s]" :
4097 ref->remote ? "%s<%s>" : "%s%s";
4099 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4100 return;
4101 sep = ", ";
4102 if (ref->tag)
4103 is_tag = TRUE;
4104 }
4106 if (!is_tag && view->type == VIEW_DIFF) {
4107 try_add_describe_ref:
4108 /* Add <tag>-g<commit_id> "fake" reference. */
4109 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4110 return;
4111 }
4113 if (bufpos == 0)
4114 return;
4116 add_line_text(view, buf, LINE_PP_REFS);
4117 }
4119 static bool
4120 pager_read(struct view *view, char *data)
4121 {
4122 struct line *line;
4124 if (!data)
4125 return TRUE;
4127 line = add_line_text(view, data, get_line_type(data));
4128 if (!line)
4129 return FALSE;
4131 if (line->type == LINE_COMMIT &&
4132 (view->type == VIEW_DIFF ||
4133 view->type == VIEW_LOG))
4134 add_pager_refs(view, line);
4136 return TRUE;
4137 }
4139 static enum request
4140 pager_request(struct view *view, enum request request, struct line *line)
4141 {
4142 int split = 0;
4144 if (request != REQ_ENTER)
4145 return request;
4147 if (line->type == LINE_COMMIT &&
4148 (view->type == VIEW_LOG ||
4149 view->type == VIEW_PAGER)) {
4150 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4151 split = 1;
4152 }
4154 /* Always scroll the view even if it was split. That way
4155 * you can use Enter to scroll through the log view and
4156 * split open each commit diff. */
4157 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4159 /* FIXME: A minor workaround. Scrolling the view will call report("")
4160 * but if we are scrolling a non-current view this won't properly
4161 * update the view title. */
4162 if (split)
4163 update_view_title(view);
4165 return REQ_NONE;
4166 }
4168 static bool
4169 pager_grep(struct view *view, struct line *line)
4170 {
4171 const char *text[] = { line->data, NULL };
4173 return grep_text(view, text);
4174 }
4176 static void
4177 pager_select(struct view *view, struct line *line)
4178 {
4179 if (line->type == LINE_COMMIT) {
4180 char *text = (char *)line->data + STRING_SIZE("commit ");
4182 if (view->type != VIEW_PAGER)
4183 string_copy_rev(view->ref, text);
4184 string_copy_rev(ref_commit, text);
4185 }
4186 }
4188 static struct view_ops pager_ops = {
4189 "line",
4190 NULL,
4191 NULL,
4192 pager_read,
4193 pager_draw,
4194 pager_request,
4195 pager_grep,
4196 pager_select,
4197 };
4199 static const char *log_argv[SIZEOF_ARG] = {
4200 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4201 };
4203 static enum request
4204 log_request(struct view *view, enum request request, struct line *line)
4205 {
4206 switch (request) {
4207 case REQ_REFRESH:
4208 load_refs();
4209 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4210 return REQ_NONE;
4211 default:
4212 return pager_request(view, request, line);
4213 }
4214 }
4216 static struct view_ops log_ops = {
4217 "line",
4218 log_argv,
4219 NULL,
4220 pager_read,
4221 pager_draw,
4222 log_request,
4223 pager_grep,
4224 pager_select,
4225 };
4227 static const char *diff_argv[SIZEOF_ARG] = {
4228 "git", "show", "--pretty=fuller", "--no-color", "--root",
4229 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4230 };
4232 static struct view_ops diff_ops = {
4233 "line",
4234 diff_argv,
4235 NULL,
4236 pager_read,
4237 pager_draw,
4238 pager_request,
4239 pager_grep,
4240 pager_select,
4241 };
4243 /*
4244 * Help backend
4245 */
4247 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4249 static bool
4250 help_open_keymap_title(struct view *view, enum keymap keymap)
4251 {
4252 struct line *line;
4254 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4255 help_keymap_hidden[keymap] ? '+' : '-',
4256 enum_name(keymap_table[keymap]));
4257 if (line)
4258 line->other = keymap;
4260 return help_keymap_hidden[keymap];
4261 }
4263 static void
4264 help_open_keymap(struct view *view, enum keymap keymap)
4265 {
4266 const char *group = NULL;
4267 char buf[SIZEOF_STR];
4268 size_t bufpos;
4269 bool add_title = TRUE;
4270 int i;
4272 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4273 const char *key = NULL;
4275 if (req_info[i].request == REQ_NONE)
4276 continue;
4278 if (!req_info[i].request) {
4279 group = req_info[i].help;
4280 continue;
4281 }
4283 key = get_keys(keymap, req_info[i].request, TRUE);
4284 if (!key || !*key)
4285 continue;
4287 if (add_title && help_open_keymap_title(view, keymap))
4288 return;
4289 add_title = FALSE;
4291 if (group) {
4292 add_line_text(view, group, LINE_HELP_GROUP);
4293 group = NULL;
4294 }
4296 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4297 enum_name(req_info[i]), req_info[i].help);
4298 }
4300 group = "External commands:";
4302 for (i = 0; i < run_requests; i++) {
4303 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4304 const char *key;
4305 int argc;
4307 if (!req || req->keymap != keymap)
4308 continue;
4310 key = get_key_name(req->key);
4311 if (!*key)
4312 key = "(no key defined)";
4314 if (add_title && help_open_keymap_title(view, keymap))
4315 return;
4316 if (group) {
4317 add_line_text(view, group, LINE_HELP_GROUP);
4318 group = NULL;
4319 }
4321 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4322 if (!string_format_from(buf, &bufpos, "%s%s",
4323 argc ? " " : "", req->argv[argc]))
4324 return;
4326 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4327 }
4328 }
4330 static bool
4331 help_open(struct view *view)
4332 {
4333 enum keymap keymap;
4335 reset_view(view);
4336 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4337 add_line_text(view, "", LINE_DEFAULT);
4339 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4340 help_open_keymap(view, keymap);
4342 return TRUE;
4343 }
4345 static enum request
4346 help_request(struct view *view, enum request request, struct line *line)
4347 {
4348 switch (request) {
4349 case REQ_ENTER:
4350 if (line->type == LINE_HELP_KEYMAP) {
4351 help_keymap_hidden[line->other] =
4352 !help_keymap_hidden[line->other];
4353 view->p_restore = TRUE;
4354 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4355 }
4357 return REQ_NONE;
4358 default:
4359 return pager_request(view, request, line);
4360 }
4361 }
4363 static struct view_ops help_ops = {
4364 "line",
4365 NULL,
4366 help_open,
4367 NULL,
4368 pager_draw,
4369 help_request,
4370 pager_grep,
4371 pager_select,
4372 };
4375 /*
4376 * Tree backend
4377 */
4379 struct tree_stack_entry {
4380 struct tree_stack_entry *prev; /* Entry below this in the stack */
4381 unsigned long lineno; /* Line number to restore */
4382 char *name; /* Position of name in opt_path */
4383 };
4385 /* The top of the path stack. */
4386 static struct tree_stack_entry *tree_stack = NULL;
4387 unsigned long tree_lineno = 0;
4389 static void
4390 pop_tree_stack_entry(void)
4391 {
4392 struct tree_stack_entry *entry = tree_stack;
4394 tree_lineno = entry->lineno;
4395 entry->name[0] = 0;
4396 tree_stack = entry->prev;
4397 free(entry);
4398 }
4400 static void
4401 push_tree_stack_entry(const char *name, unsigned long lineno)
4402 {
4403 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4404 size_t pathlen = strlen(opt_path);
4406 if (!entry)
4407 return;
4409 entry->prev = tree_stack;
4410 entry->name = opt_path + pathlen;
4411 tree_stack = entry;
4413 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4414 pop_tree_stack_entry();
4415 return;
4416 }
4418 /* Move the current line to the first tree entry. */
4419 tree_lineno = 1;
4420 entry->lineno = lineno;
4421 }
4423 /* Parse output from git-ls-tree(1):
4424 *
4425 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4426 */
4428 #define SIZEOF_TREE_ATTR \
4429 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4431 #define SIZEOF_TREE_MODE \
4432 STRING_SIZE("100644 ")
4434 #define TREE_ID_OFFSET \
4435 STRING_SIZE("100644 blob ")
4437 struct tree_entry {
4438 char id[SIZEOF_REV];
4439 mode_t mode;
4440 struct time time; /* Date from the author ident. */
4441 const char *author; /* Author of the commit. */
4442 char name[1];
4443 };
4445 static const char *
4446 tree_path(const struct line *line)
4447 {
4448 return ((struct tree_entry *) line->data)->name;
4449 }
4451 static int
4452 tree_compare_entry(const struct line *line1, const struct line *line2)
4453 {
4454 if (line1->type != line2->type)
4455 return line1->type == LINE_TREE_DIR ? -1 : 1;
4456 return strcmp(tree_path(line1), tree_path(line2));
4457 }
4459 static const enum sort_field tree_sort_fields[] = {
4460 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4461 };
4462 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4464 static int
4465 tree_compare(const void *l1, const void *l2)
4466 {
4467 const struct line *line1 = (const struct line *) l1;
4468 const struct line *line2 = (const struct line *) l2;
4469 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4470 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4472 if (line1->type == LINE_TREE_HEAD)
4473 return -1;
4474 if (line2->type == LINE_TREE_HEAD)
4475 return 1;
4477 switch (get_sort_field(tree_sort_state)) {
4478 case ORDERBY_DATE:
4479 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4481 case ORDERBY_AUTHOR:
4482 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4484 case ORDERBY_NAME:
4485 default:
4486 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4487 }
4488 }
4491 static struct line *
4492 tree_entry(struct view *view, enum line_type type, const char *path,
4493 const char *mode, const char *id)
4494 {
4495 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4496 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4498 if (!entry || !line) {
4499 free(entry);
4500 return NULL;
4501 }
4503 strncpy(entry->name, path, strlen(path));
4504 if (mode)
4505 entry->mode = strtoul(mode, NULL, 8);
4506 if (id)
4507 string_copy_rev(entry->id, id);
4509 return line;
4510 }
4512 static bool
4513 tree_read_date(struct view *view, char *text, bool *read_date)
4514 {
4515 static const char *author_name;
4516 static struct time author_time;
4518 if (!text && *read_date) {
4519 *read_date = FALSE;
4520 return TRUE;
4522 } else if (!text) {
4523 char *path = *opt_path ? opt_path : ".";
4524 /* Find next entry to process */
4525 const char *log_file[] = {
4526 "git", "log", "--no-color", "--pretty=raw",
4527 "--cc", "--raw", view->id, "--", path, NULL
4528 };
4530 if (!view->lines) {
4531 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4532 report("Tree is empty");
4533 return TRUE;
4534 }
4536 if (!start_update(view, log_file, opt_cdup)) {
4537 report("Failed to load tree data");
4538 return TRUE;
4539 }
4541 *read_date = TRUE;
4542 return FALSE;
4544 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4545 parse_author_line(text + STRING_SIZE("author "),
4546 &author_name, &author_time);
4548 } else if (*text == ':') {
4549 char *pos;
4550 size_t annotated = 1;
4551 size_t i;
4553 pos = strchr(text, '\t');
4554 if (!pos)
4555 return TRUE;
4556 text = pos + 1;
4557 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4558 text += strlen(opt_path);
4559 pos = strchr(text, '/');
4560 if (pos)
4561 *pos = 0;
4563 for (i = 1; i < view->lines; i++) {
4564 struct line *line = &view->line[i];
4565 struct tree_entry *entry = line->data;
4567 annotated += !!entry->author;
4568 if (entry->author || strcmp(entry->name, text))
4569 continue;
4571 entry->author = author_name;
4572 entry->time = author_time;
4573 line->dirty = 1;
4574 break;
4575 }
4577 if (annotated == view->lines)
4578 io_kill(view->pipe);
4579 }
4580 return TRUE;
4581 }
4583 static bool
4584 tree_read(struct view *view, char *text)
4585 {
4586 static bool read_date = FALSE;
4587 struct tree_entry *data;
4588 struct line *entry, *line;
4589 enum line_type type;
4590 size_t textlen = text ? strlen(text) : 0;
4591 char *path = text + SIZEOF_TREE_ATTR;
4593 if (read_date || !text)
4594 return tree_read_date(view, text, &read_date);
4596 if (textlen <= SIZEOF_TREE_ATTR)
4597 return FALSE;
4598 if (view->lines == 0 &&
4599 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4600 return FALSE;
4602 /* Strip the path part ... */
4603 if (*opt_path) {
4604 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4605 size_t striplen = strlen(opt_path);
4607 if (pathlen > striplen)
4608 memmove(path, path + striplen,
4609 pathlen - striplen + 1);
4611 /* Insert "link" to parent directory. */
4612 if (view->lines == 1 &&
4613 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4614 return FALSE;
4615 }
4617 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4618 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4619 if (!entry)
4620 return FALSE;
4621 data = entry->data;
4623 /* Skip "Directory ..." and ".." line. */
4624 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4625 if (tree_compare_entry(line, entry) <= 0)
4626 continue;
4628 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4630 line->data = data;
4631 line->type = type;
4632 for (; line <= entry; line++)
4633 line->dirty = line->cleareol = 1;
4634 return TRUE;
4635 }
4637 if (tree_lineno > view->lineno) {
4638 view->lineno = tree_lineno;
4639 tree_lineno = 0;
4640 }
4642 return TRUE;
4643 }
4645 static bool
4646 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4647 {
4648 struct tree_entry *entry = line->data;
4650 if (line->type == LINE_TREE_HEAD) {
4651 if (draw_text(view, line->type, "Directory path /", TRUE))
4652 return TRUE;
4653 } else {
4654 if (draw_mode(view, entry->mode))
4655 return TRUE;
4657 if (opt_author && draw_author(view, entry->author))
4658 return TRUE;
4660 if (opt_date && draw_date(view, &entry->time))
4661 return TRUE;
4662 }
4663 if (draw_text(view, line->type, entry->name, TRUE))
4664 return TRUE;
4665 return TRUE;
4666 }
4668 static void
4669 open_blob_editor(const char *id)
4670 {
4671 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4672 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4673 int fd = mkstemp(file);
4675 if (fd == -1)
4676 report("Failed to create temporary file");
4677 else if (!io_run_append(blob_argv, fd))
4678 report("Failed to save blob data to file");
4679 else
4680 open_editor(file);
4681 if (fd != -1)
4682 unlink(file);
4683 }
4685 static enum request
4686 tree_request(struct view *view, enum request request, struct line *line)
4687 {
4688 enum open_flags flags;
4689 struct tree_entry *entry = line->data;
4691 switch (request) {
4692 case REQ_VIEW_BLAME:
4693 if (line->type != LINE_TREE_FILE) {
4694 report("Blame only supported for files");
4695 return REQ_NONE;
4696 }
4698 string_copy(opt_ref, view->vid);
4699 return request;
4701 case REQ_EDIT:
4702 if (line->type != LINE_TREE_FILE) {
4703 report("Edit only supported for files");
4704 } else if (!is_head_commit(view->vid)) {
4705 open_blob_editor(entry->id);
4706 } else {
4707 open_editor(opt_file);
4708 }
4709 return REQ_NONE;
4711 case REQ_TOGGLE_SORT_FIELD:
4712 case REQ_TOGGLE_SORT_ORDER:
4713 sort_view(view, request, &tree_sort_state, tree_compare);
4714 return REQ_NONE;
4716 case REQ_PARENT:
4717 if (!*opt_path) {
4718 /* quit view if at top of tree */
4719 return REQ_VIEW_CLOSE;
4720 }
4721 /* fake 'cd ..' */
4722 line = &view->line[1];
4723 break;
4725 case REQ_ENTER:
4726 break;
4728 default:
4729 return request;
4730 }
4732 /* Cleanup the stack if the tree view is at a different tree. */
4733 while (!*opt_path && tree_stack)
4734 pop_tree_stack_entry();
4736 switch (line->type) {
4737 case LINE_TREE_DIR:
4738 /* Depending on whether it is a subdirectory or parent link
4739 * mangle the path buffer. */
4740 if (line == &view->line[1] && *opt_path) {
4741 pop_tree_stack_entry();
4743 } else {
4744 const char *basename = tree_path(line);
4746 push_tree_stack_entry(basename, view->lineno);
4747 }
4749 /* Trees and subtrees share the same ID, so they are not not
4750 * unique like blobs. */
4751 flags = OPEN_RELOAD;
4752 request = REQ_VIEW_TREE;
4753 break;
4755 case LINE_TREE_FILE:
4756 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4757 request = REQ_VIEW_BLOB;
4758 break;
4760 default:
4761 return REQ_NONE;
4762 }
4764 open_view(view, request, flags);
4765 if (request == REQ_VIEW_TREE)
4766 view->lineno = tree_lineno;
4768 return REQ_NONE;
4769 }
4771 static bool
4772 tree_grep(struct view *view, struct line *line)
4773 {
4774 struct tree_entry *entry = line->data;
4775 const char *text[] = {
4776 entry->name,
4777 opt_author ? entry->author : "",
4778 mkdate(&entry->time, opt_date),
4779 NULL
4780 };
4782 return grep_text(view, text);
4783 }
4785 static void
4786 tree_select(struct view *view, struct line *line)
4787 {
4788 struct tree_entry *entry = line->data;
4790 if (line->type == LINE_TREE_FILE) {
4791 string_copy_rev(ref_blob, entry->id);
4792 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4794 } else if (line->type != LINE_TREE_DIR) {
4795 return;
4796 }
4798 string_copy_rev(view->ref, entry->id);
4799 }
4801 static bool
4802 tree_prepare(struct view *view)
4803 {
4804 if (view->lines == 0 && opt_prefix[0]) {
4805 char *pos = opt_prefix;
4807 while (pos && *pos) {
4808 char *end = strchr(pos, '/');
4810 if (end)
4811 *end = 0;
4812 push_tree_stack_entry(pos, 0);
4813 pos = end;
4814 if (end) {
4815 *end = '/';
4816 pos++;
4817 }
4818 }
4820 } else if (strcmp(view->vid, view->id)) {
4821 opt_path[0] = 0;
4822 }
4824 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4825 }
4827 static const char *tree_argv[SIZEOF_ARG] = {
4828 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4829 };
4831 static struct view_ops tree_ops = {
4832 "file",
4833 tree_argv,
4834 NULL,
4835 tree_read,
4836 tree_draw,
4837 tree_request,
4838 tree_grep,
4839 tree_select,
4840 tree_prepare,
4841 };
4843 static bool
4844 blob_read(struct view *view, char *line)
4845 {
4846 if (!line)
4847 return TRUE;
4848 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4849 }
4851 static enum request
4852 blob_request(struct view *view, enum request request, struct line *line)
4853 {
4854 switch (request) {
4855 case REQ_EDIT:
4856 open_blob_editor(view->vid);
4857 return REQ_NONE;
4858 default:
4859 return pager_request(view, request, line);
4860 }
4861 }
4863 static const char *blob_argv[SIZEOF_ARG] = {
4864 "git", "cat-file", "blob", "%(blob)", NULL
4865 };
4867 static struct view_ops blob_ops = {
4868 "line",
4869 blob_argv,
4870 NULL,
4871 blob_read,
4872 pager_draw,
4873 blob_request,
4874 pager_grep,
4875 pager_select,
4876 };
4878 /*
4879 * Blame backend
4880 *
4881 * Loading the blame view is a two phase job:
4882 *
4883 * 1. File content is read either using opt_file from the
4884 * filesystem or using git-cat-file.
4885 * 2. Then blame information is incrementally added by
4886 * reading output from git-blame.
4887 */
4889 struct blame_commit {
4890 char id[SIZEOF_REV]; /* SHA1 ID. */
4891 char title[128]; /* First line of the commit message. */
4892 const char *author; /* Author of the commit. */
4893 struct time time; /* Date from the author ident. */
4894 char filename[128]; /* Name of file. */
4895 bool has_previous; /* Was a "previous" line detected. */
4896 };
4898 struct blame {
4899 struct blame_commit *commit;
4900 unsigned long lineno;
4901 char text[1];
4902 };
4904 static bool
4905 blame_open(struct view *view)
4906 {
4907 char path[SIZEOF_STR];
4909 if (!view->prev && *opt_prefix) {
4910 string_copy(path, opt_file);
4911 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4912 return FALSE;
4913 }
4915 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4916 const char *blame_cat_file_argv[] = {
4917 "git", "cat-file", "blob", path, NULL
4918 };
4920 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4921 !start_update(view, blame_cat_file_argv, opt_cdup))
4922 return FALSE;
4923 }
4925 setup_update(view, opt_file);
4926 string_format(view->ref, "%s ...", opt_file);
4928 return TRUE;
4929 }
4931 static struct blame_commit *
4932 get_blame_commit(struct view *view, const char *id)
4933 {
4934 size_t i;
4936 for (i = 0; i < view->lines; i++) {
4937 struct blame *blame = view->line[i].data;
4939 if (!blame->commit)
4940 continue;
4942 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4943 return blame->commit;
4944 }
4946 {
4947 struct blame_commit *commit = calloc(1, sizeof(*commit));
4949 if (commit)
4950 string_ncopy(commit->id, id, SIZEOF_REV);
4951 return commit;
4952 }
4953 }
4955 static bool
4956 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4957 {
4958 const char *pos = *posref;
4960 *posref = NULL;
4961 pos = strchr(pos + 1, ' ');
4962 if (!pos || !isdigit(pos[1]))
4963 return FALSE;
4964 *number = atoi(pos + 1);
4965 if (*number < min || *number > max)
4966 return FALSE;
4968 *posref = pos;
4969 return TRUE;
4970 }
4972 static struct blame_commit *
4973 parse_blame_commit(struct view *view, const char *text, int *blamed)
4974 {
4975 struct blame_commit *commit;
4976 struct blame *blame;
4977 const char *pos = text + SIZEOF_REV - 2;
4978 size_t orig_lineno = 0;
4979 size_t lineno;
4980 size_t group;
4982 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4983 return NULL;
4985 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4986 !parse_number(&pos, &lineno, 1, view->lines) ||
4987 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4988 return NULL;
4990 commit = get_blame_commit(view, text);
4991 if (!commit)
4992 return NULL;
4994 *blamed += group;
4995 while (group--) {
4996 struct line *line = &view->line[lineno + group - 1];
4998 blame = line->data;
4999 blame->commit = commit;
5000 blame->lineno = orig_lineno + group - 1;
5001 line->dirty = 1;
5002 }
5004 return commit;
5005 }
5007 static bool
5008 blame_read_file(struct view *view, const char *line, bool *read_file)
5009 {
5010 if (!line) {
5011 const char *blame_argv[] = {
5012 "git", "blame", "--incremental",
5013 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5014 };
5016 if (view->lines == 0 && !view->prev)
5017 die("No blame exist for %s", view->vid);
5019 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5020 report("Failed to load blame data");
5021 return TRUE;
5022 }
5024 *read_file = FALSE;
5025 return FALSE;
5027 } else {
5028 size_t linelen = strlen(line);
5029 struct blame *blame = malloc(sizeof(*blame) + linelen);
5031 if (!blame)
5032 return FALSE;
5034 blame->commit = NULL;
5035 strncpy(blame->text, line, linelen);
5036 blame->text[linelen] = 0;
5037 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5038 }
5039 }
5041 static bool
5042 match_blame_header(const char *name, char **line)
5043 {
5044 size_t namelen = strlen(name);
5045 bool matched = !strncmp(name, *line, namelen);
5047 if (matched)
5048 *line += namelen;
5050 return matched;
5051 }
5053 static bool
5054 blame_read(struct view *view, char *line)
5055 {
5056 static struct blame_commit *commit = NULL;
5057 static int blamed = 0;
5058 static bool read_file = TRUE;
5060 if (read_file)
5061 return blame_read_file(view, line, &read_file);
5063 if (!line) {
5064 /* Reset all! */
5065 commit = NULL;
5066 blamed = 0;
5067 read_file = TRUE;
5068 string_format(view->ref, "%s", view->vid);
5069 if (view_is_displayed(view)) {
5070 update_view_title(view);
5071 redraw_view_from(view, 0);
5072 }
5073 return TRUE;
5074 }
5076 if (!commit) {
5077 commit = parse_blame_commit(view, line, &blamed);
5078 string_format(view->ref, "%s %2d%%", view->vid,
5079 view->lines ? blamed * 100 / view->lines : 0);
5081 } else if (match_blame_header("author ", &line)) {
5082 commit->author = get_author(line);
5084 } else if (match_blame_header("author-time ", &line)) {
5085 parse_timesec(&commit->time, line);
5087 } else if (match_blame_header("author-tz ", &line)) {
5088 parse_timezone(&commit->time, line);
5090 } else if (match_blame_header("summary ", &line)) {
5091 string_ncopy(commit->title, line, strlen(line));
5093 } else if (match_blame_header("previous ", &line)) {
5094 commit->has_previous = TRUE;
5096 } else if (match_blame_header("filename ", &line)) {
5097 string_ncopy(commit->filename, line, strlen(line));
5098 commit = NULL;
5099 }
5101 return TRUE;
5102 }
5104 static bool
5105 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5106 {
5107 struct blame *blame = line->data;
5108 struct time *time = NULL;
5109 const char *id = NULL, *author = NULL;
5110 char text[SIZEOF_STR];
5112 if (blame->commit && *blame->commit->filename) {
5113 id = blame->commit->id;
5114 author = blame->commit->author;
5115 time = &blame->commit->time;
5116 }
5118 if (opt_date && draw_date(view, time))
5119 return TRUE;
5121 if (opt_author && draw_author(view, author))
5122 return TRUE;
5124 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5125 return TRUE;
5127 if (draw_lineno(view, lineno))
5128 return TRUE;
5130 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5131 draw_text(view, LINE_DEFAULT, text, TRUE);
5132 return TRUE;
5133 }
5135 static bool
5136 check_blame_commit(struct blame *blame, bool check_null_id)
5137 {
5138 if (!blame->commit)
5139 report("Commit data not loaded yet");
5140 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5141 report("No commit exist for the selected line");
5142 else
5143 return TRUE;
5144 return FALSE;
5145 }
5147 static void
5148 setup_blame_parent_line(struct view *view, struct blame *blame)
5149 {
5150 const char *diff_tree_argv[] = {
5151 "git", "diff-tree", "-U0", blame->commit->id,
5152 "--", blame->commit->filename, NULL
5153 };
5154 struct io io = {};
5155 int parent_lineno = -1;
5156 int blamed_lineno = -1;
5157 char *line;
5159 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5160 return;
5162 while ((line = io_get(&io, '\n', TRUE))) {
5163 if (*line == '@') {
5164 char *pos = strchr(line, '+');
5166 parent_lineno = atoi(line + 4);
5167 if (pos)
5168 blamed_lineno = atoi(pos + 1);
5170 } else if (*line == '+' && parent_lineno != -1) {
5171 if (blame->lineno == blamed_lineno - 1 &&
5172 !strcmp(blame->text, line + 1)) {
5173 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5174 break;
5175 }
5176 blamed_lineno++;
5177 }
5178 }
5180 io_done(&io);
5181 }
5183 static enum request
5184 blame_request(struct view *view, enum request request, struct line *line)
5185 {
5186 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5187 struct blame *blame = line->data;
5189 switch (request) {
5190 case REQ_VIEW_BLAME:
5191 if (check_blame_commit(blame, TRUE)) {
5192 string_copy(opt_ref, blame->commit->id);
5193 string_copy(opt_file, blame->commit->filename);
5194 if (blame->lineno)
5195 view->lineno = blame->lineno;
5196 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5197 }
5198 break;
5200 case REQ_PARENT:
5201 if (check_blame_commit(blame, TRUE) &&
5202 select_commit_parent(blame->commit->id, opt_ref,
5203 blame->commit->filename)) {
5204 string_copy(opt_file, blame->commit->filename);
5205 setup_blame_parent_line(view, blame);
5206 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5207 }
5208 break;
5210 case REQ_ENTER:
5211 if (!check_blame_commit(blame, FALSE))
5212 break;
5214 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5215 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5216 break;
5218 if (!strcmp(blame->commit->id, NULL_ID)) {
5219 struct view *diff = VIEW(REQ_VIEW_DIFF);
5220 const char *diff_index_argv[] = {
5221 "git", "diff-index", "--root", "--patch-with-stat",
5222 "-C", "-M", "HEAD", "--", view->vid, NULL
5223 };
5225 if (!blame->commit->has_previous) {
5226 diff_index_argv[1] = "diff";
5227 diff_index_argv[2] = "--no-color";
5228 diff_index_argv[6] = "--";
5229 diff_index_argv[7] = "/dev/null";
5230 }
5232 if (!prepare_update(diff, diff_index_argv, NULL)) {
5233 report("Failed to allocate diff command");
5234 break;
5235 }
5236 flags |= OPEN_PREPARED;
5237 }
5239 open_view(view, REQ_VIEW_DIFF, flags);
5240 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5241 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5242 break;
5244 default:
5245 return request;
5246 }
5248 return REQ_NONE;
5249 }
5251 static bool
5252 blame_grep(struct view *view, struct line *line)
5253 {
5254 struct blame *blame = line->data;
5255 struct blame_commit *commit = blame->commit;
5256 const char *text[] = {
5257 blame->text,
5258 commit ? commit->title : "",
5259 commit ? commit->id : "",
5260 commit && opt_author ? commit->author : "",
5261 commit ? mkdate(&commit->time, opt_date) : "",
5262 NULL
5263 };
5265 return grep_text(view, text);
5266 }
5268 static void
5269 blame_select(struct view *view, struct line *line)
5270 {
5271 struct blame *blame = line->data;
5272 struct blame_commit *commit = blame->commit;
5274 if (!commit)
5275 return;
5277 if (!strcmp(commit->id, NULL_ID))
5278 string_ncopy(ref_commit, "HEAD", 4);
5279 else
5280 string_copy_rev(ref_commit, commit->id);
5281 }
5283 static struct view_ops blame_ops = {
5284 "line",
5285 NULL,
5286 blame_open,
5287 blame_read,
5288 blame_draw,
5289 blame_request,
5290 blame_grep,
5291 blame_select,
5292 };
5294 /*
5295 * Branch backend
5296 */
5298 struct branch {
5299 const char *author; /* Author of the last commit. */
5300 struct time time; /* Date of the last activity. */
5301 const struct ref *ref; /* Name and commit ID information. */
5302 };
5304 static const struct ref branch_all;
5306 static const enum sort_field branch_sort_fields[] = {
5307 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5308 };
5309 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5311 static int
5312 branch_compare(const void *l1, const void *l2)
5313 {
5314 const struct branch *branch1 = ((const struct line *) l1)->data;
5315 const struct branch *branch2 = ((const struct line *) l2)->data;
5317 switch (get_sort_field(branch_sort_state)) {
5318 case ORDERBY_DATE:
5319 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5321 case ORDERBY_AUTHOR:
5322 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5324 case ORDERBY_NAME:
5325 default:
5326 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5327 }
5328 }
5330 static bool
5331 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5332 {
5333 struct branch *branch = line->data;
5334 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5336 if (opt_date && draw_date(view, &branch->time))
5337 return TRUE;
5339 if (opt_author && draw_author(view, branch->author))
5340 return TRUE;
5342 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5343 return TRUE;
5344 }
5346 static enum request
5347 branch_request(struct view *view, enum request request, struct line *line)
5348 {
5349 struct branch *branch = line->data;
5351 switch (request) {
5352 case REQ_REFRESH:
5353 load_refs();
5354 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5355 return REQ_NONE;
5357 case REQ_TOGGLE_SORT_FIELD:
5358 case REQ_TOGGLE_SORT_ORDER:
5359 sort_view(view, request, &branch_sort_state, branch_compare);
5360 return REQ_NONE;
5362 case REQ_ENTER:
5363 if (branch->ref == &branch_all) {
5364 const char *all_branches_argv[] = {
5365 "git", "log", "--no-color", "--pretty=raw", "--parents",
5366 "--topo-order", "--all", NULL
5367 };
5368 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5370 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5371 report("Failed to load view of all branches");
5372 return REQ_NONE;
5373 }
5374 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5375 } else {
5376 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5377 }
5378 return REQ_NONE;
5380 default:
5381 return request;
5382 }
5383 }
5385 static bool
5386 branch_read(struct view *view, char *line)
5387 {
5388 static char id[SIZEOF_REV];
5389 struct branch *reference;
5390 size_t i;
5392 if (!line)
5393 return TRUE;
5395 switch (get_line_type(line)) {
5396 case LINE_COMMIT:
5397 string_copy_rev(id, line + STRING_SIZE("commit "));
5398 return TRUE;
5400 case LINE_AUTHOR:
5401 for (i = 0, reference = NULL; i < view->lines; i++) {
5402 struct branch *branch = view->line[i].data;
5404 if (strcmp(branch->ref->id, id))
5405 continue;
5407 view->line[i].dirty = TRUE;
5408 if (reference) {
5409 branch->author = reference->author;
5410 branch->time = reference->time;
5411 continue;
5412 }
5414 parse_author_line(line + STRING_SIZE("author "),
5415 &branch->author, &branch->time);
5416 reference = branch;
5417 }
5418 return TRUE;
5420 default:
5421 return TRUE;
5422 }
5424 }
5426 static bool
5427 branch_open_visitor(void *data, const struct ref *ref)
5428 {
5429 struct view *view = data;
5430 struct branch *branch;
5432 if (ref->tag || ref->ltag || ref->remote)
5433 return TRUE;
5435 branch = calloc(1, sizeof(*branch));
5436 if (!branch)
5437 return FALSE;
5439 branch->ref = ref;
5440 return !!add_line_data(view, branch, LINE_DEFAULT);
5441 }
5443 static bool
5444 branch_open(struct view *view)
5445 {
5446 const char *branch_log[] = {
5447 "git", "log", "--no-color", "--pretty=raw",
5448 "--simplify-by-decoration", "--all", NULL
5449 };
5451 if (!start_update(view, branch_log, NULL)) {
5452 report("Failed to load branch data");
5453 return TRUE;
5454 }
5456 setup_update(view, view->id);
5457 branch_open_visitor(view, &branch_all);
5458 foreach_ref(branch_open_visitor, view);
5459 view->p_restore = TRUE;
5461 return TRUE;
5462 }
5464 static bool
5465 branch_grep(struct view *view, struct line *line)
5466 {
5467 struct branch *branch = line->data;
5468 const char *text[] = {
5469 branch->ref->name,
5470 branch->author,
5471 NULL
5472 };
5474 return grep_text(view, text);
5475 }
5477 static void
5478 branch_select(struct view *view, struct line *line)
5479 {
5480 struct branch *branch = line->data;
5482 string_copy_rev(view->ref, branch->ref->id);
5483 string_copy_rev(ref_commit, branch->ref->id);
5484 string_copy_rev(ref_head, branch->ref->id);
5485 string_copy_rev(ref_branch, branch->ref->name);
5486 }
5488 static struct view_ops branch_ops = {
5489 "branch",
5490 NULL,
5491 branch_open,
5492 branch_read,
5493 branch_draw,
5494 branch_request,
5495 branch_grep,
5496 branch_select,
5497 };
5499 /*
5500 * Status backend
5501 */
5503 struct status {
5504 char status;
5505 struct {
5506 mode_t mode;
5507 char rev[SIZEOF_REV];
5508 char name[SIZEOF_STR];
5509 } old;
5510 struct {
5511 mode_t mode;
5512 char rev[SIZEOF_REV];
5513 char name[SIZEOF_STR];
5514 } new;
5515 };
5517 static char status_onbranch[SIZEOF_STR];
5518 static struct status stage_status;
5519 static enum line_type stage_line_type;
5520 static size_t stage_chunks;
5521 static int *stage_chunk;
5523 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5525 /* This should work even for the "On branch" line. */
5526 static inline bool
5527 status_has_none(struct view *view, struct line *line)
5528 {
5529 return line < view->line + view->lines && !line[1].data;
5530 }
5532 /* Get fields from the diff line:
5533 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5534 */
5535 static inline bool
5536 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5537 {
5538 const char *old_mode = buf + 1;
5539 const char *new_mode = buf + 8;
5540 const char *old_rev = buf + 15;
5541 const char *new_rev = buf + 56;
5542 const char *status = buf + 97;
5544 if (bufsize < 98 ||
5545 old_mode[-1] != ':' ||
5546 new_mode[-1] != ' ' ||
5547 old_rev[-1] != ' ' ||
5548 new_rev[-1] != ' ' ||
5549 status[-1] != ' ')
5550 return FALSE;
5552 file->status = *status;
5554 string_copy_rev(file->old.rev, old_rev);
5555 string_copy_rev(file->new.rev, new_rev);
5557 file->old.mode = strtoul(old_mode, NULL, 8);
5558 file->new.mode = strtoul(new_mode, NULL, 8);
5560 file->old.name[0] = file->new.name[0] = 0;
5562 return TRUE;
5563 }
5565 static bool
5566 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5567 {
5568 struct status *unmerged = NULL;
5569 char *buf;
5570 struct io io = {};
5572 if (!io_run(&io, argv, opt_cdup, IO_RD))
5573 return FALSE;
5575 add_line_data(view, NULL, type);
5577 while ((buf = io_get(&io, 0, TRUE))) {
5578 struct status *file = unmerged;
5580 if (!file) {
5581 file = calloc(1, sizeof(*file));
5582 if (!file || !add_line_data(view, file, type))
5583 goto error_out;
5584 }
5586 /* Parse diff info part. */
5587 if (status) {
5588 file->status = status;
5589 if (status == 'A')
5590 string_copy(file->old.rev, NULL_ID);
5592 } else if (!file->status || file == unmerged) {
5593 if (!status_get_diff(file, buf, strlen(buf)))
5594 goto error_out;
5596 buf = io_get(&io, 0, TRUE);
5597 if (!buf)
5598 break;
5600 /* Collapse all modified entries that follow an
5601 * associated unmerged entry. */
5602 if (unmerged == file) {
5603 unmerged->status = 'U';
5604 unmerged = NULL;
5605 } else if (file->status == 'U') {
5606 unmerged = file;
5607 }
5608 }
5610 /* Grab the old name for rename/copy. */
5611 if (!*file->old.name &&
5612 (file->status == 'R' || file->status == 'C')) {
5613 string_ncopy(file->old.name, buf, strlen(buf));
5615 buf = io_get(&io, 0, TRUE);
5616 if (!buf)
5617 break;
5618 }
5620 /* git-ls-files just delivers a NUL separated list of
5621 * file names similar to the second half of the
5622 * git-diff-* output. */
5623 string_ncopy(file->new.name, buf, strlen(buf));
5624 if (!*file->old.name)
5625 string_copy(file->old.name, file->new.name);
5626 file = NULL;
5627 }
5629 if (io_error(&io)) {
5630 error_out:
5631 io_done(&io);
5632 return FALSE;
5633 }
5635 if (!view->line[view->lines - 1].data)
5636 add_line_data(view, NULL, LINE_STAT_NONE);
5638 io_done(&io);
5639 return TRUE;
5640 }
5642 /* Don't show unmerged entries in the staged section. */
5643 static const char *status_diff_index_argv[] = {
5644 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5645 "--cached", "-M", "HEAD", NULL
5646 };
5648 static const char *status_diff_files_argv[] = {
5649 "git", "diff-files", "-z", NULL
5650 };
5652 static const char *status_list_other_argv[] = {
5653 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5654 };
5656 static const char *status_list_no_head_argv[] = {
5657 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5658 };
5660 static const char *update_index_argv[] = {
5661 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5662 };
5664 /* Restore the previous line number to stay in the context or select a
5665 * line with something that can be updated. */
5666 static void
5667 status_restore(struct view *view)
5668 {
5669 if (view->p_lineno >= view->lines)
5670 view->p_lineno = view->lines - 1;
5671 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5672 view->p_lineno++;
5673 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5674 view->p_lineno--;
5676 /* If the above fails, always skip the "On branch" line. */
5677 if (view->p_lineno < view->lines)
5678 view->lineno = view->p_lineno;
5679 else
5680 view->lineno = 1;
5682 if (view->lineno < view->offset)
5683 view->offset = view->lineno;
5684 else if (view->offset + view->height <= view->lineno)
5685 view->offset = view->lineno - view->height + 1;
5687 view->p_restore = FALSE;
5688 }
5690 static void
5691 status_update_onbranch(void)
5692 {
5693 static const char *paths[][2] = {
5694 { "rebase-apply/rebasing", "Rebasing" },
5695 { "rebase-apply/applying", "Applying mailbox" },
5696 { "rebase-apply/", "Rebasing mailbox" },
5697 { "rebase-merge/interactive", "Interactive rebase" },
5698 { "rebase-merge/", "Rebase merge" },
5699 { "MERGE_HEAD", "Merging" },
5700 { "BISECT_LOG", "Bisecting" },
5701 { "HEAD", "On branch" },
5702 };
5703 char buf[SIZEOF_STR];
5704 struct stat stat;
5705 int i;
5707 if (is_initial_commit()) {
5708 string_copy(status_onbranch, "Initial commit");
5709 return;
5710 }
5712 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5713 char *head = opt_head;
5715 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5716 lstat(buf, &stat) < 0)
5717 continue;
5719 if (!*opt_head) {
5720 struct io io = {};
5722 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5723 io_read_buf(&io, buf, sizeof(buf))) {
5724 head = buf;
5725 if (!prefixcmp(head, "refs/heads/"))
5726 head += STRING_SIZE("refs/heads/");
5727 }
5728 }
5730 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5731 string_copy(status_onbranch, opt_head);
5732 return;
5733 }
5735 string_copy(status_onbranch, "Not currently on any branch");
5736 }
5738 /* First parse staged info using git-diff-index(1), then parse unstaged
5739 * info using git-diff-files(1), and finally untracked files using
5740 * git-ls-files(1). */
5741 static bool
5742 status_open(struct view *view)
5743 {
5744 reset_view(view);
5746 add_line_data(view, NULL, LINE_STAT_HEAD);
5747 status_update_onbranch();
5749 io_run_bg(update_index_argv);
5751 if (is_initial_commit()) {
5752 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5753 return FALSE;
5754 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5755 return FALSE;
5756 }
5758 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5759 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5760 return FALSE;
5762 /* Restore the exact position or use the specialized restore
5763 * mode? */
5764 if (!view->p_restore)
5765 status_restore(view);
5766 return TRUE;
5767 }
5769 static bool
5770 status_draw(struct view *view, struct line *line, unsigned int lineno)
5771 {
5772 struct status *status = line->data;
5773 enum line_type type;
5774 const char *text;
5776 if (!status) {
5777 switch (line->type) {
5778 case LINE_STAT_STAGED:
5779 type = LINE_STAT_SECTION;
5780 text = "Changes to be committed:";
5781 break;
5783 case LINE_STAT_UNSTAGED:
5784 type = LINE_STAT_SECTION;
5785 text = "Changed but not updated:";
5786 break;
5788 case LINE_STAT_UNTRACKED:
5789 type = LINE_STAT_SECTION;
5790 text = "Untracked files:";
5791 break;
5793 case LINE_STAT_NONE:
5794 type = LINE_DEFAULT;
5795 text = " (no files)";
5796 break;
5798 case LINE_STAT_HEAD:
5799 type = LINE_STAT_HEAD;
5800 text = status_onbranch;
5801 break;
5803 default:
5804 return FALSE;
5805 }
5806 } else {
5807 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5809 buf[0] = status->status;
5810 if (draw_text(view, line->type, buf, TRUE))
5811 return TRUE;
5812 type = LINE_DEFAULT;
5813 text = status->new.name;
5814 }
5816 draw_text(view, type, text, TRUE);
5817 return TRUE;
5818 }
5820 static enum request
5821 status_load_error(struct view *view, struct view *stage, const char *path)
5822 {
5823 if (displayed_views() == 2 || display[current_view] != view)
5824 maximize_view(view);
5825 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5826 return REQ_NONE;
5827 }
5829 static enum request
5830 status_enter(struct view *view, struct line *line)
5831 {
5832 struct status *status = line->data;
5833 const char *oldpath = status ? status->old.name : NULL;
5834 /* Diffs for unmerged entries are empty when passing the new
5835 * path, so leave it empty. */
5836 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5837 const char *info;
5838 enum open_flags split;
5839 struct view *stage = VIEW(REQ_VIEW_STAGE);
5841 if (line->type == LINE_STAT_NONE ||
5842 (!status && line[1].type == LINE_STAT_NONE)) {
5843 report("No file to diff");
5844 return REQ_NONE;
5845 }
5847 switch (line->type) {
5848 case LINE_STAT_STAGED:
5849 if (is_initial_commit()) {
5850 const char *no_head_diff_argv[] = {
5851 "git", "diff", "--no-color", "--patch-with-stat",
5852 "--", "/dev/null", newpath, NULL
5853 };
5855 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5856 return status_load_error(view, stage, newpath);
5857 } else {
5858 const char *index_show_argv[] = {
5859 "git", "diff-index", "--root", "--patch-with-stat",
5860 "-C", "-M", "--cached", "HEAD", "--",
5861 oldpath, newpath, NULL
5862 };
5864 if (!prepare_update(stage, index_show_argv, opt_cdup))
5865 return status_load_error(view, stage, newpath);
5866 }
5868 if (status)
5869 info = "Staged changes to %s";
5870 else
5871 info = "Staged changes";
5872 break;
5874 case LINE_STAT_UNSTAGED:
5875 {
5876 const char *files_show_argv[] = {
5877 "git", "diff-files", "--root", "--patch-with-stat",
5878 "-C", "-M", "--", oldpath, newpath, NULL
5879 };
5881 if (!prepare_update(stage, files_show_argv, opt_cdup))
5882 return status_load_error(view, stage, newpath);
5883 if (status)
5884 info = "Unstaged changes to %s";
5885 else
5886 info = "Unstaged changes";
5887 break;
5888 }
5889 case LINE_STAT_UNTRACKED:
5890 if (!newpath) {
5891 report("No file to show");
5892 return REQ_NONE;
5893 }
5895 if (!suffixcmp(status->new.name, -1, "/")) {
5896 report("Cannot display a directory");
5897 return REQ_NONE;
5898 }
5900 if (!prepare_update_file(stage, newpath))
5901 return status_load_error(view, stage, newpath);
5902 info = "Untracked file %s";
5903 break;
5905 case LINE_STAT_HEAD:
5906 return REQ_NONE;
5908 default:
5909 die("line type %d not handled in switch", line->type);
5910 }
5912 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5913 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5914 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5915 if (status) {
5916 stage_status = *status;
5917 } else {
5918 memset(&stage_status, 0, sizeof(stage_status));
5919 }
5921 stage_line_type = line->type;
5922 stage_chunks = 0;
5923 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5924 }
5926 return REQ_NONE;
5927 }
5929 static bool
5930 status_exists(struct status *status, enum line_type type)
5931 {
5932 struct view *view = VIEW(REQ_VIEW_STATUS);
5933 unsigned long lineno;
5935 for (lineno = 0; lineno < view->lines; lineno++) {
5936 struct line *line = &view->line[lineno];
5937 struct status *pos = line->data;
5939 if (line->type != type)
5940 continue;
5941 if (!pos && (!status || !status->status) && line[1].data) {
5942 select_view_line(view, lineno);
5943 return TRUE;
5944 }
5945 if (pos && !strcmp(status->new.name, pos->new.name)) {
5946 select_view_line(view, lineno);
5947 return TRUE;
5948 }
5949 }
5951 return FALSE;
5952 }
5955 static bool
5956 status_update_prepare(struct io *io, enum line_type type)
5957 {
5958 const char *staged_argv[] = {
5959 "git", "update-index", "-z", "--index-info", NULL
5960 };
5961 const char *others_argv[] = {
5962 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5963 };
5965 switch (type) {
5966 case LINE_STAT_STAGED:
5967 return io_run(io, staged_argv, opt_cdup, IO_WR);
5969 case LINE_STAT_UNSTAGED:
5970 case LINE_STAT_UNTRACKED:
5971 return io_run(io, others_argv, opt_cdup, IO_WR);
5973 default:
5974 die("line type %d not handled in switch", type);
5975 return FALSE;
5976 }
5977 }
5979 static bool
5980 status_update_write(struct io *io, struct status *status, enum line_type type)
5981 {
5982 char buf[SIZEOF_STR];
5983 size_t bufsize = 0;
5985 switch (type) {
5986 case LINE_STAT_STAGED:
5987 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5988 status->old.mode,
5989 status->old.rev,
5990 status->old.name, 0))
5991 return FALSE;
5992 break;
5994 case LINE_STAT_UNSTAGED:
5995 case LINE_STAT_UNTRACKED:
5996 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5997 return FALSE;
5998 break;
6000 default:
6001 die("line type %d not handled in switch", type);
6002 }
6004 return io_write(io, buf, bufsize);
6005 }
6007 static bool
6008 status_update_file(struct status *status, enum line_type type)
6009 {
6010 struct io io = {};
6011 bool result;
6013 if (!status_update_prepare(&io, type))
6014 return FALSE;
6016 result = status_update_write(&io, status, type);
6017 return io_done(&io) && result;
6018 }
6020 static bool
6021 status_update_files(struct view *view, struct line *line)
6022 {
6023 char buf[sizeof(view->ref)];
6024 struct io io = {};
6025 bool result = TRUE;
6026 struct line *pos = view->line + view->lines;
6027 int files = 0;
6028 int file, done;
6029 int cursor_y = -1, cursor_x = -1;
6031 if (!status_update_prepare(&io, line->type))
6032 return FALSE;
6034 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6035 files++;
6037 string_copy(buf, view->ref);
6038 getsyx(cursor_y, cursor_x);
6039 for (file = 0, done = 5; result && file < files; line++, file++) {
6040 int almost_done = file * 100 / files;
6042 if (almost_done > done) {
6043 done = almost_done;
6044 string_format(view->ref, "updating file %u of %u (%d%% done)",
6045 file, files, done);
6046 update_view_title(view);
6047 setsyx(cursor_y, cursor_x);
6048 doupdate();
6049 }
6050 result = status_update_write(&io, line->data, line->type);
6051 }
6052 string_copy(view->ref, buf);
6054 return io_done(&io) && result;
6055 }
6057 static bool
6058 status_update(struct view *view)
6059 {
6060 struct line *line = &view->line[view->lineno];
6062 assert(view->lines);
6064 if (!line->data) {
6065 /* This should work even for the "On branch" line. */
6066 if (line < view->line + view->lines && !line[1].data) {
6067 report("Nothing to update");
6068 return FALSE;
6069 }
6071 if (!status_update_files(view, line + 1)) {
6072 report("Failed to update file status");
6073 return FALSE;
6074 }
6076 } else if (!status_update_file(line->data, line->type)) {
6077 report("Failed to update file status");
6078 return FALSE;
6079 }
6081 return TRUE;
6082 }
6084 static bool
6085 status_revert(struct status *status, enum line_type type, bool has_none)
6086 {
6087 if (!status || type != LINE_STAT_UNSTAGED) {
6088 if (type == LINE_STAT_STAGED) {
6089 report("Cannot revert changes to staged files");
6090 } else if (type == LINE_STAT_UNTRACKED) {
6091 report("Cannot revert changes to untracked files");
6092 } else if (has_none) {
6093 report("Nothing to revert");
6094 } else {
6095 report("Cannot revert changes to multiple files");
6096 }
6098 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6099 char mode[10] = "100644";
6100 const char *reset_argv[] = {
6101 "git", "update-index", "--cacheinfo", mode,
6102 status->old.rev, status->old.name, NULL
6103 };
6104 const char *checkout_argv[] = {
6105 "git", "checkout", "--", status->old.name, NULL
6106 };
6108 if (status->status == 'U') {
6109 string_format(mode, "%5o", status->old.mode);
6111 if (status->old.mode == 0 && status->new.mode == 0) {
6112 reset_argv[2] = "--force-remove";
6113 reset_argv[3] = status->old.name;
6114 reset_argv[4] = NULL;
6115 }
6117 if (!io_run_fg(reset_argv, opt_cdup))
6118 return FALSE;
6119 if (status->old.mode == 0 && status->new.mode == 0)
6120 return TRUE;
6121 }
6123 return io_run_fg(checkout_argv, opt_cdup);
6124 }
6126 return FALSE;
6127 }
6129 static enum request
6130 status_request(struct view *view, enum request request, struct line *line)
6131 {
6132 struct status *status = line->data;
6134 switch (request) {
6135 case REQ_STATUS_UPDATE:
6136 if (!status_update(view))
6137 return REQ_NONE;
6138 break;
6140 case REQ_STATUS_REVERT:
6141 if (!status_revert(status, line->type, status_has_none(view, line)))
6142 return REQ_NONE;
6143 break;
6145 case REQ_STATUS_MERGE:
6146 if (!status || status->status != 'U') {
6147 report("Merging only possible for files with unmerged status ('U').");
6148 return REQ_NONE;
6149 }
6150 open_mergetool(status->new.name);
6151 break;
6153 case REQ_EDIT:
6154 if (!status)
6155 return request;
6156 if (status->status == 'D') {
6157 report("File has been deleted.");
6158 return REQ_NONE;
6159 }
6161 open_editor(status->new.name);
6162 break;
6164 case REQ_VIEW_BLAME:
6165 if (status)
6166 opt_ref[0] = 0;
6167 return request;
6169 case REQ_ENTER:
6170 /* After returning the status view has been split to
6171 * show the stage view. No further reloading is
6172 * necessary. */
6173 return status_enter(view, line);
6175 case REQ_REFRESH:
6176 /* Simply reload the view. */
6177 break;
6179 default:
6180 return request;
6181 }
6183 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6185 return REQ_NONE;
6186 }
6188 static void
6189 status_select(struct view *view, struct line *line)
6190 {
6191 struct status *status = line->data;
6192 char file[SIZEOF_STR] = "all files";
6193 const char *text;
6194 const char *key;
6196 if (status && !string_format(file, "'%s'", status->new.name))
6197 return;
6199 if (!status && line[1].type == LINE_STAT_NONE)
6200 line++;
6202 switch (line->type) {
6203 case LINE_STAT_STAGED:
6204 text = "Press %s to unstage %s for commit";
6205 break;
6207 case LINE_STAT_UNSTAGED:
6208 text = "Press %s to stage %s for commit";
6209 break;
6211 case LINE_STAT_UNTRACKED:
6212 text = "Press %s to stage %s for addition";
6213 break;
6215 case LINE_STAT_HEAD:
6216 case LINE_STAT_NONE:
6217 text = "Nothing to update";
6218 break;
6220 default:
6221 die("line type %d not handled in switch", line->type);
6222 }
6224 if (status && status->status == 'U') {
6225 text = "Press %s to resolve conflict in %s";
6226 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6228 } else {
6229 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6230 }
6232 string_format(view->ref, text, key, file);
6233 if (status)
6234 string_copy(opt_file, status->new.name);
6235 }
6237 static bool
6238 status_grep(struct view *view, struct line *line)
6239 {
6240 struct status *status = line->data;
6242 if (status) {
6243 const char buf[2] = { status->status, 0 };
6244 const char *text[] = { status->new.name, buf, NULL };
6246 return grep_text(view, text);
6247 }
6249 return FALSE;
6250 }
6252 static struct view_ops status_ops = {
6253 "file",
6254 NULL,
6255 status_open,
6256 NULL,
6257 status_draw,
6258 status_request,
6259 status_grep,
6260 status_select,
6261 };
6264 static bool
6265 stage_diff_write(struct io *io, struct line *line, struct line *end)
6266 {
6267 while (line < end) {
6268 if (!io_write(io, line->data, strlen(line->data)) ||
6269 !io_write(io, "\n", 1))
6270 return FALSE;
6271 line++;
6272 if (line->type == LINE_DIFF_CHUNK ||
6273 line->type == LINE_DIFF_HEADER)
6274 break;
6275 }
6277 return TRUE;
6278 }
6280 static struct line *
6281 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6282 {
6283 for (; view->line < line; line--)
6284 if (line->type == type)
6285 return line;
6287 return NULL;
6288 }
6290 static bool
6291 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6292 {
6293 const char *apply_argv[SIZEOF_ARG] = {
6294 "git", "apply", "--whitespace=nowarn", NULL
6295 };
6296 struct line *diff_hdr;
6297 struct io io = {};
6298 int argc = 3;
6300 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6301 if (!diff_hdr)
6302 return FALSE;
6304 if (!revert)
6305 apply_argv[argc++] = "--cached";
6306 if (revert || stage_line_type == LINE_STAT_STAGED)
6307 apply_argv[argc++] = "-R";
6308 apply_argv[argc++] = "-";
6309 apply_argv[argc++] = NULL;
6310 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6311 return FALSE;
6313 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6314 !stage_diff_write(&io, chunk, view->line + view->lines))
6315 chunk = NULL;
6317 io_done(&io);
6318 io_run_bg(update_index_argv);
6320 return chunk ? TRUE : FALSE;
6321 }
6323 static bool
6324 stage_update(struct view *view, struct line *line)
6325 {
6326 struct line *chunk = NULL;
6328 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6329 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6331 if (chunk) {
6332 if (!stage_apply_chunk(view, chunk, FALSE)) {
6333 report("Failed to apply chunk");
6334 return FALSE;
6335 }
6337 } else if (!stage_status.status) {
6338 view = VIEW(REQ_VIEW_STATUS);
6340 for (line = view->line; line < view->line + view->lines; line++)
6341 if (line->type == stage_line_type)
6342 break;
6344 if (!status_update_files(view, line + 1)) {
6345 report("Failed to update files");
6346 return FALSE;
6347 }
6349 } else if (!status_update_file(&stage_status, stage_line_type)) {
6350 report("Failed to update file");
6351 return FALSE;
6352 }
6354 return TRUE;
6355 }
6357 static bool
6358 stage_revert(struct view *view, struct line *line)
6359 {
6360 struct line *chunk = NULL;
6362 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6363 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6365 if (chunk) {
6366 if (!prompt_yesno("Are you sure you want to revert changes?"))
6367 return FALSE;
6369 if (!stage_apply_chunk(view, chunk, TRUE)) {
6370 report("Failed to revert chunk");
6371 return FALSE;
6372 }
6373 return TRUE;
6375 } else {
6376 return status_revert(stage_status.status ? &stage_status : NULL,
6377 stage_line_type, FALSE);
6378 }
6379 }
6382 static void
6383 stage_next(struct view *view, struct line *line)
6384 {
6385 int i;
6387 if (!stage_chunks) {
6388 for (line = view->line; line < view->line + view->lines; line++) {
6389 if (line->type != LINE_DIFF_CHUNK)
6390 continue;
6392 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6393 report("Allocation failure");
6394 return;
6395 }
6397 stage_chunk[stage_chunks++] = line - view->line;
6398 }
6399 }
6401 for (i = 0; i < stage_chunks; i++) {
6402 if (stage_chunk[i] > view->lineno) {
6403 do_scroll_view(view, stage_chunk[i] - view->lineno);
6404 report("Chunk %d of %d", i + 1, stage_chunks);
6405 return;
6406 }
6407 }
6409 report("No next chunk found");
6410 }
6412 static enum request
6413 stage_request(struct view *view, enum request request, struct line *line)
6414 {
6415 switch (request) {
6416 case REQ_STATUS_UPDATE:
6417 if (!stage_update(view, line))
6418 return REQ_NONE;
6419 break;
6421 case REQ_STATUS_REVERT:
6422 if (!stage_revert(view, line))
6423 return REQ_NONE;
6424 break;
6426 case REQ_STAGE_NEXT:
6427 if (stage_line_type == LINE_STAT_UNTRACKED) {
6428 report("File is untracked; press %s to add",
6429 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6430 return REQ_NONE;
6431 }
6432 stage_next(view, line);
6433 return REQ_NONE;
6435 case REQ_EDIT:
6436 if (!stage_status.new.name[0])
6437 return request;
6438 if (stage_status.status == 'D') {
6439 report("File has been deleted.");
6440 return REQ_NONE;
6441 }
6443 open_editor(stage_status.new.name);
6444 break;
6446 case REQ_REFRESH:
6447 /* Reload everything ... */
6448 break;
6450 case REQ_VIEW_BLAME:
6451 if (stage_status.new.name[0]) {
6452 string_copy(opt_file, stage_status.new.name);
6453 opt_ref[0] = 0;
6454 }
6455 return request;
6457 case REQ_ENTER:
6458 return pager_request(view, request, line);
6460 default:
6461 return request;
6462 }
6464 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6465 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6467 /* Check whether the staged entry still exists, and close the
6468 * stage view if it doesn't. */
6469 if (!status_exists(&stage_status, stage_line_type)) {
6470 status_restore(VIEW(REQ_VIEW_STATUS));
6471 return REQ_VIEW_CLOSE;
6472 }
6474 if (stage_line_type == LINE_STAT_UNTRACKED) {
6475 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6476 report("Cannot display a directory");
6477 return REQ_NONE;
6478 }
6480 if (!prepare_update_file(view, stage_status.new.name)) {
6481 report("Failed to open file: %s", strerror(errno));
6482 return REQ_NONE;
6483 }
6484 }
6485 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6487 return REQ_NONE;
6488 }
6490 static struct view_ops stage_ops = {
6491 "line",
6492 NULL,
6493 NULL,
6494 pager_read,
6495 pager_draw,
6496 stage_request,
6497 pager_grep,
6498 pager_select,
6499 };
6502 /*
6503 * Revision graph
6504 */
6506 struct commit {
6507 char id[SIZEOF_REV]; /* SHA1 ID. */
6508 char title[128]; /* First line of the commit message. */
6509 const char *author; /* Author of the commit. */
6510 struct time time; /* Date from the author ident. */
6511 struct ref_list *refs; /* Repository references. */
6512 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6513 size_t graph_size; /* The width of the graph array. */
6514 bool has_parents; /* Rewritten --parents seen. */
6515 };
6517 /* Size of rev graph with no "padding" columns */
6518 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6520 struct rev_graph {
6521 struct rev_graph *prev, *next, *parents;
6522 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6523 size_t size;
6524 struct commit *commit;
6525 size_t pos;
6526 unsigned int boundary:1;
6527 };
6529 /* Parents of the commit being visualized. */
6530 static struct rev_graph graph_parents[4];
6532 /* The current stack of revisions on the graph. */
6533 static struct rev_graph graph_stacks[4] = {
6534 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6535 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6536 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6537 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6538 };
6540 static inline bool
6541 graph_parent_is_merge(struct rev_graph *graph)
6542 {
6543 return graph->parents->size > 1;
6544 }
6546 static inline void
6547 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6548 {
6549 struct commit *commit = graph->commit;
6551 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6552 commit->graph[commit->graph_size++] = symbol;
6553 }
6555 static void
6556 clear_rev_graph(struct rev_graph *graph)
6557 {
6558 graph->boundary = 0;
6559 graph->size = graph->pos = 0;
6560 graph->commit = NULL;
6561 memset(graph->parents, 0, sizeof(*graph->parents));
6562 }
6564 static void
6565 done_rev_graph(struct rev_graph *graph)
6566 {
6567 if (graph_parent_is_merge(graph) &&
6568 graph->pos < graph->size - 1 &&
6569 graph->next->size == graph->size + graph->parents->size - 1) {
6570 size_t i = graph->pos + graph->parents->size - 1;
6572 graph->commit->graph_size = i * 2;
6573 while (i < graph->next->size - 1) {
6574 append_to_rev_graph(graph, ' ');
6575 append_to_rev_graph(graph, '\\');
6576 i++;
6577 }
6578 }
6580 clear_rev_graph(graph);
6581 }
6583 static void
6584 push_rev_graph(struct rev_graph *graph, const char *parent)
6585 {
6586 int i;
6588 /* "Collapse" duplicate parents lines.
6589 *
6590 * FIXME: This needs to also update update the drawn graph but
6591 * for now it just serves as a method for pruning graph lines. */
6592 for (i = 0; i < graph->size; i++)
6593 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6594 return;
6596 if (graph->size < SIZEOF_REVITEMS) {
6597 string_copy_rev(graph->rev[graph->size++], parent);
6598 }
6599 }
6601 static chtype
6602 get_rev_graph_symbol(struct rev_graph *graph)
6603 {
6604 chtype symbol;
6606 if (graph->boundary)
6607 symbol = REVGRAPH_BOUND;
6608 else if (graph->parents->size == 0)
6609 symbol = REVGRAPH_INIT;
6610 else if (graph_parent_is_merge(graph))
6611 symbol = REVGRAPH_MERGE;
6612 else if (graph->pos >= graph->size)
6613 symbol = REVGRAPH_BRANCH;
6614 else
6615 symbol = REVGRAPH_COMMIT;
6617 return symbol;
6618 }
6620 static void
6621 draw_rev_graph(struct rev_graph *graph)
6622 {
6623 struct rev_filler {
6624 chtype separator, line;
6625 };
6626 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6627 static struct rev_filler fillers[] = {
6628 { ' ', '|' },
6629 { '`', '.' },
6630 { '\'', ' ' },
6631 { '/', ' ' },
6632 };
6633 chtype symbol = get_rev_graph_symbol(graph);
6634 struct rev_filler *filler;
6635 size_t i;
6637 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6638 filler = &fillers[DEFAULT];
6640 for (i = 0; i < graph->pos; i++) {
6641 append_to_rev_graph(graph, filler->line);
6642 if (graph_parent_is_merge(graph->prev) &&
6643 graph->prev->pos == i)
6644 filler = &fillers[RSHARP];
6646 append_to_rev_graph(graph, filler->separator);
6647 }
6649 /* Place the symbol for this revision. */
6650 append_to_rev_graph(graph, symbol);
6652 if (graph->prev->size > graph->size)
6653 filler = &fillers[RDIAG];
6654 else
6655 filler = &fillers[DEFAULT];
6657 i++;
6659 for (; i < graph->size; i++) {
6660 append_to_rev_graph(graph, filler->separator);
6661 append_to_rev_graph(graph, filler->line);
6662 if (graph_parent_is_merge(graph->prev) &&
6663 i < graph->prev->pos + graph->parents->size)
6664 filler = &fillers[RSHARP];
6665 if (graph->prev->size > graph->size)
6666 filler = &fillers[LDIAG];
6667 }
6669 if (graph->prev->size > graph->size) {
6670 append_to_rev_graph(graph, filler->separator);
6671 if (filler->line != ' ')
6672 append_to_rev_graph(graph, filler->line);
6673 }
6674 }
6676 /* Prepare the next rev graph */
6677 static void
6678 prepare_rev_graph(struct rev_graph *graph)
6679 {
6680 size_t i;
6682 /* First, traverse all lines of revisions up to the active one. */
6683 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6684 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6685 break;
6687 push_rev_graph(graph->next, graph->rev[graph->pos]);
6688 }
6690 /* Interleave the new revision parent(s). */
6691 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6692 push_rev_graph(graph->next, graph->parents->rev[i]);
6694 /* Lastly, put any remaining revisions. */
6695 for (i = graph->pos + 1; i < graph->size; i++)
6696 push_rev_graph(graph->next, graph->rev[i]);
6697 }
6699 static void
6700 update_rev_graph(struct view *view, struct rev_graph *graph)
6701 {
6702 /* If this is the finalizing update ... */
6703 if (graph->commit)
6704 prepare_rev_graph(graph);
6706 /* Graph visualization needs a one rev look-ahead,
6707 * so the first update doesn't visualize anything. */
6708 if (!graph->prev->commit)
6709 return;
6711 if (view->lines > 2)
6712 view->line[view->lines - 3].dirty = 1;
6713 if (view->lines > 1)
6714 view->line[view->lines - 2].dirty = 1;
6715 draw_rev_graph(graph->prev);
6716 done_rev_graph(graph->prev->prev);
6717 }
6720 /*
6721 * Main view backend
6722 */
6724 static const char *main_argv[SIZEOF_ARG] = {
6725 "git", "log", "--no-color", "--pretty=raw", "--parents",
6726 "--topo-order", "%(head)", NULL
6727 };
6729 static bool
6730 main_draw(struct view *view, struct line *line, unsigned int lineno)
6731 {
6732 struct commit *commit = line->data;
6734 if (!commit->author)
6735 return FALSE;
6737 if (opt_date && draw_date(view, &commit->time))
6738 return TRUE;
6740 if (opt_author && draw_author(view, commit->author))
6741 return TRUE;
6743 if (opt_rev_graph && commit->graph_size &&
6744 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6745 return TRUE;
6747 if (opt_show_refs && commit->refs) {
6748 size_t i;
6750 for (i = 0; i < commit->refs->size; i++) {
6751 struct ref *ref = commit->refs->refs[i];
6752 enum line_type type;
6754 if (ref->head)
6755 type = LINE_MAIN_HEAD;
6756 else if (ref->ltag)
6757 type = LINE_MAIN_LOCAL_TAG;
6758 else if (ref->tag)
6759 type = LINE_MAIN_TAG;
6760 else if (ref->tracked)
6761 type = LINE_MAIN_TRACKED;
6762 else if (ref->remote)
6763 type = LINE_MAIN_REMOTE;
6764 else
6765 type = LINE_MAIN_REF;
6767 if (draw_text(view, type, "[", TRUE) ||
6768 draw_text(view, type, ref->name, TRUE) ||
6769 draw_text(view, type, "]", TRUE))
6770 return TRUE;
6772 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6773 return TRUE;
6774 }
6775 }
6777 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6778 return TRUE;
6779 }
6781 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6782 static bool
6783 main_read(struct view *view, char *line)
6784 {
6785 static struct rev_graph *graph = graph_stacks;
6786 enum line_type type;
6787 struct commit *commit;
6789 if (!line) {
6790 int i;
6792 if (!view->lines && !view->prev)
6793 die("No revisions match the given arguments.");
6794 if (view->lines > 0) {
6795 commit = view->line[view->lines - 1].data;
6796 view->line[view->lines - 1].dirty = 1;
6797 if (!commit->author) {
6798 view->lines--;
6799 free(commit);
6800 graph->commit = NULL;
6801 }
6802 }
6803 update_rev_graph(view, graph);
6805 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6806 clear_rev_graph(&graph_stacks[i]);
6807 return TRUE;
6808 }
6810 type = get_line_type(line);
6811 if (type == LINE_COMMIT) {
6812 commit = calloc(1, sizeof(struct commit));
6813 if (!commit)
6814 return FALSE;
6816 line += STRING_SIZE("commit ");
6817 if (*line == '-') {
6818 graph->boundary = 1;
6819 line++;
6820 }
6822 string_copy_rev(commit->id, line);
6823 commit->refs = get_ref_list(commit->id);
6824 graph->commit = commit;
6825 add_line_data(view, commit, LINE_MAIN_COMMIT);
6827 while ((line = strchr(line, ' '))) {
6828 line++;
6829 push_rev_graph(graph->parents, line);
6830 commit->has_parents = TRUE;
6831 }
6832 return TRUE;
6833 }
6835 if (!view->lines)
6836 return TRUE;
6837 commit = view->line[view->lines - 1].data;
6839 switch (type) {
6840 case LINE_PARENT:
6841 if (commit->has_parents)
6842 break;
6843 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6844 break;
6846 case LINE_AUTHOR:
6847 parse_author_line(line + STRING_SIZE("author "),
6848 &commit->author, &commit->time);
6849 update_rev_graph(view, graph);
6850 graph = graph->next;
6851 break;
6853 default:
6854 /* Fill in the commit title if it has not already been set. */
6855 if (commit->title[0])
6856 break;
6858 /* Require titles to start with a non-space character at the
6859 * offset used by git log. */
6860 if (strncmp(line, " ", 4))
6861 break;
6862 line += 4;
6863 /* Well, if the title starts with a whitespace character,
6864 * try to be forgiving. Otherwise we end up with no title. */
6865 while (isspace(*line))
6866 line++;
6867 if (*line == '\0')
6868 break;
6869 /* FIXME: More graceful handling of titles; append "..." to
6870 * shortened titles, etc. */
6872 string_expand(commit->title, sizeof(commit->title), line, 1);
6873 view->line[view->lines - 1].dirty = 1;
6874 }
6876 return TRUE;
6877 }
6879 static enum request
6880 main_request(struct view *view, enum request request, struct line *line)
6881 {
6882 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6884 switch (request) {
6885 case REQ_ENTER:
6886 open_view(view, REQ_VIEW_DIFF, flags);
6887 break;
6888 case REQ_REFRESH:
6889 load_refs();
6890 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6891 break;
6892 default:
6893 return request;
6894 }
6896 return REQ_NONE;
6897 }
6899 static bool
6900 grep_refs(struct ref_list *list, regex_t *regex)
6901 {
6902 regmatch_t pmatch;
6903 size_t i;
6905 if (!opt_show_refs || !list)
6906 return FALSE;
6908 for (i = 0; i < list->size; i++) {
6909 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6910 return TRUE;
6911 }
6913 return FALSE;
6914 }
6916 static bool
6917 main_grep(struct view *view, struct line *line)
6918 {
6919 struct commit *commit = line->data;
6920 const char *text[] = {
6921 commit->title,
6922 opt_author ? commit->author : "",
6923 mkdate(&commit->time, opt_date),
6924 NULL
6925 };
6927 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6928 }
6930 static void
6931 main_select(struct view *view, struct line *line)
6932 {
6933 struct commit *commit = line->data;
6935 string_copy_rev(view->ref, commit->id);
6936 string_copy_rev(ref_commit, view->ref);
6937 }
6939 static struct view_ops main_ops = {
6940 "commit",
6941 main_argv,
6942 NULL,
6943 main_read,
6944 main_draw,
6945 main_request,
6946 main_grep,
6947 main_select,
6948 };
6951 /*
6952 * Status management
6953 */
6955 /* Whether or not the curses interface has been initialized. */
6956 static bool cursed = FALSE;
6958 /* Terminal hacks and workarounds. */
6959 static bool use_scroll_redrawwin;
6960 static bool use_scroll_status_wclear;
6962 /* The status window is used for polling keystrokes. */
6963 static WINDOW *status_win;
6965 /* Reading from the prompt? */
6966 static bool input_mode = FALSE;
6968 static bool status_empty = FALSE;
6970 /* Update status and title window. */
6971 static void
6972 report(const char *msg, ...)
6973 {
6974 struct view *view = display[current_view];
6976 if (input_mode)
6977 return;
6979 if (!view) {
6980 char buf[SIZEOF_STR];
6981 va_list args;
6983 va_start(args, msg);
6984 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6985 buf[sizeof(buf) - 1] = 0;
6986 buf[sizeof(buf) - 2] = '.';
6987 buf[sizeof(buf) - 3] = '.';
6988 buf[sizeof(buf) - 4] = '.';
6989 }
6990 va_end(args);
6991 die("%s", buf);
6992 }
6994 if (!status_empty || *msg) {
6995 va_list args;
6997 va_start(args, msg);
6999 wmove(status_win, 0, 0);
7000 if (view->has_scrolled && use_scroll_status_wclear)
7001 wclear(status_win);
7002 if (*msg) {
7003 vwprintw(status_win, msg, args);
7004 status_empty = FALSE;
7005 } else {
7006 status_empty = TRUE;
7007 }
7008 wclrtoeol(status_win);
7009 wnoutrefresh(status_win);
7011 va_end(args);
7012 }
7014 update_view_title(view);
7015 }
7017 static void
7018 init_display(void)
7019 {
7020 const char *term;
7021 int x, y;
7023 /* Initialize the curses library */
7024 if (isatty(STDIN_FILENO)) {
7025 cursed = !!initscr();
7026 opt_tty = stdin;
7027 } else {
7028 /* Leave stdin and stdout alone when acting as a pager. */
7029 opt_tty = fopen("/dev/tty", "r+");
7030 if (!opt_tty)
7031 die("Failed to open /dev/tty");
7032 cursed = !!newterm(NULL, opt_tty, opt_tty);
7033 }
7035 if (!cursed)
7036 die("Failed to initialize curses");
7038 nonl(); /* Disable conversion and detect newlines from input. */
7039 cbreak(); /* Take input chars one at a time, no wait for \n */
7040 noecho(); /* Don't echo input */
7041 leaveok(stdscr, FALSE);
7043 if (has_colors())
7044 init_colors();
7046 getmaxyx(stdscr, y, x);
7047 status_win = newwin(1, 0, y - 1, 0);
7048 if (!status_win)
7049 die("Failed to create status window");
7051 /* Enable keyboard mapping */
7052 keypad(status_win, TRUE);
7053 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7055 TABSIZE = opt_tab_size;
7057 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7058 if (term && !strcmp(term, "gnome-terminal")) {
7059 /* In the gnome-terminal-emulator, the message from
7060 * scrolling up one line when impossible followed by
7061 * scrolling down one line causes corruption of the
7062 * status line. This is fixed by calling wclear. */
7063 use_scroll_status_wclear = TRUE;
7064 use_scroll_redrawwin = FALSE;
7066 } else if (term && !strcmp(term, "xrvt-xpm")) {
7067 /* No problems with full optimizations in xrvt-(unicode)
7068 * and aterm. */
7069 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7071 } else {
7072 /* When scrolling in (u)xterm the last line in the
7073 * scrolling direction will update slowly. */
7074 use_scroll_redrawwin = TRUE;
7075 use_scroll_status_wclear = FALSE;
7076 }
7077 }
7079 static int
7080 get_input(int prompt_position)
7081 {
7082 struct view *view;
7083 int i, key, cursor_y, cursor_x;
7084 bool loading = FALSE;
7086 if (prompt_position)
7087 input_mode = TRUE;
7089 while (TRUE) {
7090 foreach_view (view, i) {
7091 update_view(view);
7092 if (view_is_displayed(view) && view->has_scrolled &&
7093 use_scroll_redrawwin)
7094 redrawwin(view->win);
7095 view->has_scrolled = FALSE;
7096 if (view->pipe)
7097 loading = TRUE;
7098 }
7100 /* Update the cursor position. */
7101 if (prompt_position) {
7102 getbegyx(status_win, cursor_y, cursor_x);
7103 cursor_x = prompt_position;
7104 } else {
7105 view = display[current_view];
7106 getbegyx(view->win, cursor_y, cursor_x);
7107 cursor_x = view->width - 1;
7108 cursor_y += view->lineno - view->offset;
7109 }
7110 setsyx(cursor_y, cursor_x);
7112 /* Refresh, accept single keystroke of input */
7113 doupdate();
7114 nodelay(status_win, loading);
7115 key = wgetch(status_win);
7117 /* wgetch() with nodelay() enabled returns ERR when
7118 * there's no input. */
7119 if (key == ERR) {
7121 } else if (key == KEY_RESIZE) {
7122 int height, width;
7124 getmaxyx(stdscr, height, width);
7126 wresize(status_win, 1, width);
7127 mvwin(status_win, height - 1, 0);
7128 wnoutrefresh(status_win);
7129 resize_display();
7130 redraw_display(TRUE);
7132 } else {
7133 input_mode = FALSE;
7134 return key;
7135 }
7136 }
7137 }
7139 static char *
7140 prompt_input(const char *prompt, input_handler handler, void *data)
7141 {
7142 enum input_status status = INPUT_OK;
7143 static char buf[SIZEOF_STR];
7144 size_t pos = 0;
7146 buf[pos] = 0;
7148 while (status == INPUT_OK || status == INPUT_SKIP) {
7149 int key;
7151 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7152 wclrtoeol(status_win);
7154 key = get_input(pos + 1);
7155 switch (key) {
7156 case KEY_RETURN:
7157 case KEY_ENTER:
7158 case '\n':
7159 status = pos ? INPUT_STOP : INPUT_CANCEL;
7160 break;
7162 case KEY_BACKSPACE:
7163 if (pos > 0)
7164 buf[--pos] = 0;
7165 else
7166 status = INPUT_CANCEL;
7167 break;
7169 case KEY_ESC:
7170 status = INPUT_CANCEL;
7171 break;
7173 default:
7174 if (pos >= sizeof(buf)) {
7175 report("Input string too long");
7176 return NULL;
7177 }
7179 status = handler(data, buf, key);
7180 if (status == INPUT_OK)
7181 buf[pos++] = (char) key;
7182 }
7183 }
7185 /* Clear the status window */
7186 status_empty = FALSE;
7187 report("");
7189 if (status == INPUT_CANCEL)
7190 return NULL;
7192 buf[pos++] = 0;
7194 return buf;
7195 }
7197 static enum input_status
7198 prompt_yesno_handler(void *data, char *buf, int c)
7199 {
7200 if (c == 'y' || c == 'Y')
7201 return INPUT_STOP;
7202 if (c == 'n' || c == 'N')
7203 return INPUT_CANCEL;
7204 return INPUT_SKIP;
7205 }
7207 static bool
7208 prompt_yesno(const char *prompt)
7209 {
7210 char prompt2[SIZEOF_STR];
7212 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7213 return FALSE;
7215 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7216 }
7218 static enum input_status
7219 read_prompt_handler(void *data, char *buf, int c)
7220 {
7221 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7222 }
7224 static char *
7225 read_prompt(const char *prompt)
7226 {
7227 return prompt_input(prompt, read_prompt_handler, NULL);
7228 }
7230 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7231 {
7232 enum input_status status = INPUT_OK;
7233 int size = 0;
7235 while (items[size].text)
7236 size++;
7238 while (status == INPUT_OK) {
7239 const struct menu_item *item = &items[*selected];
7240 int key;
7241 int i;
7243 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7244 prompt, *selected + 1, size);
7245 if (item->hotkey)
7246 wprintw(status_win, "[%c] ", (char) item->hotkey);
7247 wprintw(status_win, "%s", item->text);
7248 wclrtoeol(status_win);
7250 key = get_input(COLS - 1);
7251 switch (key) {
7252 case KEY_RETURN:
7253 case KEY_ENTER:
7254 case '\n':
7255 status = INPUT_STOP;
7256 break;
7258 case KEY_LEFT:
7259 case KEY_UP:
7260 *selected = *selected - 1;
7261 if (*selected < 0)
7262 *selected = size - 1;
7263 break;
7265 case KEY_RIGHT:
7266 case KEY_DOWN:
7267 *selected = (*selected + 1) % size;
7268 break;
7270 case KEY_ESC:
7271 status = INPUT_CANCEL;
7272 break;
7274 default:
7275 for (i = 0; items[i].text; i++)
7276 if (items[i].hotkey == key) {
7277 *selected = i;
7278 status = INPUT_STOP;
7279 break;
7280 }
7281 }
7282 }
7284 /* Clear the status window */
7285 status_empty = FALSE;
7286 report("");
7288 return status != INPUT_CANCEL;
7289 }
7291 /*
7292 * Repository properties
7293 */
7295 static struct ref **refs = NULL;
7296 static size_t refs_size = 0;
7297 static struct ref *refs_head = NULL;
7299 static struct ref_list **ref_lists = NULL;
7300 static size_t ref_lists_size = 0;
7302 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7303 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7304 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7306 static int
7307 compare_refs(const void *ref1_, const void *ref2_)
7308 {
7309 const struct ref *ref1 = *(const struct ref **)ref1_;
7310 const struct ref *ref2 = *(const struct ref **)ref2_;
7312 if (ref1->tag != ref2->tag)
7313 return ref2->tag - ref1->tag;
7314 if (ref1->ltag != ref2->ltag)
7315 return ref2->ltag - ref2->ltag;
7316 if (ref1->head != ref2->head)
7317 return ref2->head - ref1->head;
7318 if (ref1->tracked != ref2->tracked)
7319 return ref2->tracked - ref1->tracked;
7320 if (ref1->remote != ref2->remote)
7321 return ref2->remote - ref1->remote;
7322 return strcmp(ref1->name, ref2->name);
7323 }
7325 static void
7326 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7327 {
7328 size_t i;
7330 for (i = 0; i < refs_size; i++)
7331 if (!visitor(data, refs[i]))
7332 break;
7333 }
7335 static struct ref *
7336 get_ref_head()
7337 {
7338 return refs_head;
7339 }
7341 static struct ref_list *
7342 get_ref_list(const char *id)
7343 {
7344 struct ref_list *list;
7345 size_t i;
7347 for (i = 0; i < ref_lists_size; i++)
7348 if (!strcmp(id, ref_lists[i]->id))
7349 return ref_lists[i];
7351 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7352 return NULL;
7353 list = calloc(1, sizeof(*list));
7354 if (!list)
7355 return NULL;
7357 for (i = 0; i < refs_size; i++) {
7358 if (!strcmp(id, refs[i]->id) &&
7359 realloc_refs_list(&list->refs, list->size, 1))
7360 list->refs[list->size++] = refs[i];
7361 }
7363 if (!list->refs) {
7364 free(list);
7365 return NULL;
7366 }
7368 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7369 ref_lists[ref_lists_size++] = list;
7370 return list;
7371 }
7373 static int
7374 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7375 {
7376 struct ref *ref = NULL;
7377 bool tag = FALSE;
7378 bool ltag = FALSE;
7379 bool remote = FALSE;
7380 bool tracked = FALSE;
7381 bool head = FALSE;
7382 int from = 0, to = refs_size - 1;
7384 if (!prefixcmp(name, "refs/tags/")) {
7385 if (!suffixcmp(name, namelen, "^{}")) {
7386 namelen -= 3;
7387 name[namelen] = 0;
7388 } else {
7389 ltag = TRUE;
7390 }
7392 tag = TRUE;
7393 namelen -= STRING_SIZE("refs/tags/");
7394 name += STRING_SIZE("refs/tags/");
7396 } else if (!prefixcmp(name, "refs/remotes/")) {
7397 remote = TRUE;
7398 namelen -= STRING_SIZE("refs/remotes/");
7399 name += STRING_SIZE("refs/remotes/");
7400 tracked = !strcmp(opt_remote, name);
7402 } else if (!prefixcmp(name, "refs/heads/")) {
7403 namelen -= STRING_SIZE("refs/heads/");
7404 name += STRING_SIZE("refs/heads/");
7405 if (!strncmp(opt_head, name, namelen))
7406 return OK;
7408 } else if (!strcmp(name, "HEAD")) {
7409 head = TRUE;
7410 if (*opt_head) {
7411 namelen = strlen(opt_head);
7412 name = opt_head;
7413 }
7414 }
7416 /* If we are reloading or it's an annotated tag, replace the
7417 * previous SHA1 with the resolved commit id; relies on the fact
7418 * git-ls-remote lists the commit id of an annotated tag right
7419 * before the commit id it points to. */
7420 while (from <= to) {
7421 size_t pos = (to + from) / 2;
7422 int cmp = strcmp(name, refs[pos]->name);
7424 if (!cmp) {
7425 ref = refs[pos];
7426 break;
7427 }
7429 if (cmp < 0)
7430 to = pos - 1;
7431 else
7432 from = pos + 1;
7433 }
7435 if (!ref) {
7436 if (!realloc_refs(&refs, refs_size, 1))
7437 return ERR;
7438 ref = calloc(1, sizeof(*ref) + namelen);
7439 if (!ref)
7440 return ERR;
7441 memmove(refs + from + 1, refs + from,
7442 (refs_size - from) * sizeof(*refs));
7443 refs[from] = ref;
7444 strncpy(ref->name, name, namelen);
7445 refs_size++;
7446 }
7448 ref->head = head;
7449 ref->tag = tag;
7450 ref->ltag = ltag;
7451 ref->remote = remote;
7452 ref->tracked = tracked;
7453 string_copy_rev(ref->id, id);
7455 if (head)
7456 refs_head = ref;
7457 return OK;
7458 }
7460 static int
7461 load_refs(void)
7462 {
7463 const char *head_argv[] = {
7464 "git", "symbolic-ref", "HEAD", NULL
7465 };
7466 static const char *ls_remote_argv[SIZEOF_ARG] = {
7467 "git", "ls-remote", opt_git_dir, NULL
7468 };
7469 static bool init = FALSE;
7470 size_t i;
7472 if (!init) {
7473 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7474 die("TIG_LS_REMOTE contains too many arguments");
7475 init = TRUE;
7476 }
7478 if (!*opt_git_dir)
7479 return OK;
7481 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7482 !prefixcmp(opt_head, "refs/heads/")) {
7483 char *offset = opt_head + STRING_SIZE("refs/heads/");
7485 memmove(opt_head, offset, strlen(offset) + 1);
7486 }
7488 refs_head = NULL;
7489 for (i = 0; i < refs_size; i++)
7490 refs[i]->id[0] = 0;
7492 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7493 return ERR;
7495 /* Update the ref lists to reflect changes. */
7496 for (i = 0; i < ref_lists_size; i++) {
7497 struct ref_list *list = ref_lists[i];
7498 size_t old, new;
7500 for (old = new = 0; old < list->size; old++)
7501 if (!strcmp(list->id, list->refs[old]->id))
7502 list->refs[new++] = list->refs[old];
7503 list->size = new;
7504 }
7506 return OK;
7507 }
7509 static void
7510 set_remote_branch(const char *name, const char *value, size_t valuelen)
7511 {
7512 if (!strcmp(name, ".remote")) {
7513 string_ncopy(opt_remote, value, valuelen);
7515 } else if (*opt_remote && !strcmp(name, ".merge")) {
7516 size_t from = strlen(opt_remote);
7518 if (!prefixcmp(value, "refs/heads/"))
7519 value += STRING_SIZE("refs/heads/");
7521 if (!string_format_from(opt_remote, &from, "/%s", value))
7522 opt_remote[0] = 0;
7523 }
7524 }
7526 static void
7527 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7528 {
7529 const char *argv[SIZEOF_ARG] = { name, "=" };
7530 int argc = 1 + (cmd == option_set_command);
7531 int error = ERR;
7533 if (!argv_from_string(argv, &argc, value))
7534 config_msg = "Too many option arguments";
7535 else
7536 error = cmd(argc, argv);
7538 if (error == ERR)
7539 warn("Option 'tig.%s': %s", name, config_msg);
7540 }
7542 static bool
7543 set_environment_variable(const char *name, const char *value)
7544 {
7545 size_t len = strlen(name) + 1 + strlen(value) + 1;
7546 char *env = malloc(len);
7548 if (env &&
7549 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7550 putenv(env) == 0)
7551 return TRUE;
7552 free(env);
7553 return FALSE;
7554 }
7556 static void
7557 set_work_tree(const char *value)
7558 {
7559 char cwd[SIZEOF_STR];
7561 if (!getcwd(cwd, sizeof(cwd)))
7562 die("Failed to get cwd path: %s", strerror(errno));
7563 if (chdir(opt_git_dir) < 0)
7564 die("Failed to chdir(%s): %s", strerror(errno));
7565 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7566 die("Failed to get git path: %s", strerror(errno));
7567 if (chdir(cwd) < 0)
7568 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7569 if (chdir(value) < 0)
7570 die("Failed to chdir(%s): %s", value, strerror(errno));
7571 if (!getcwd(cwd, sizeof(cwd)))
7572 die("Failed to get cwd path: %s", strerror(errno));
7573 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7574 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7575 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7576 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7577 opt_is_inside_work_tree = TRUE;
7578 }
7580 static int
7581 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7582 {
7583 if (!strcmp(name, "i18n.commitencoding"))
7584 string_ncopy(opt_encoding, value, valuelen);
7586 else if (!strcmp(name, "core.editor"))
7587 string_ncopy(opt_editor, value, valuelen);
7589 else if (!strcmp(name, "core.worktree"))
7590 set_work_tree(value);
7592 else if (!prefixcmp(name, "tig.color."))
7593 set_repo_config_option(name + 10, value, option_color_command);
7595 else if (!prefixcmp(name, "tig.bind."))
7596 set_repo_config_option(name + 9, value, option_bind_command);
7598 else if (!prefixcmp(name, "tig."))
7599 set_repo_config_option(name + 4, value, option_set_command);
7601 else if (*opt_head && !prefixcmp(name, "branch.") &&
7602 !strncmp(name + 7, opt_head, strlen(opt_head)))
7603 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7605 return OK;
7606 }
7608 static int
7609 load_git_config(void)
7610 {
7611 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7613 return io_run_load(config_list_argv, "=", read_repo_config_option);
7614 }
7616 static int
7617 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7618 {
7619 if (!opt_git_dir[0]) {
7620 string_ncopy(opt_git_dir, name, namelen);
7622 } else if (opt_is_inside_work_tree == -1) {
7623 /* This can be 3 different values depending on the
7624 * version of git being used. If git-rev-parse does not
7625 * understand --is-inside-work-tree it will simply echo
7626 * the option else either "true" or "false" is printed.
7627 * Default to true for the unknown case. */
7628 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7630 } else if (*name == '.') {
7631 string_ncopy(opt_cdup, name, namelen);
7633 } else {
7634 string_ncopy(opt_prefix, name, namelen);
7635 }
7637 return OK;
7638 }
7640 static int
7641 load_repo_info(void)
7642 {
7643 const char *rev_parse_argv[] = {
7644 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7645 "--show-cdup", "--show-prefix", NULL
7646 };
7648 return io_run_load(rev_parse_argv, "=", read_repo_info);
7649 }
7652 /*
7653 * Main
7654 */
7656 static const char usage[] =
7657 "tig " TIG_VERSION " (" __DATE__ ")\n"
7658 "\n"
7659 "Usage: tig [options] [revs] [--] [paths]\n"
7660 " or: tig show [options] [revs] [--] [paths]\n"
7661 " or: tig blame [rev] path\n"
7662 " or: tig status\n"
7663 " or: tig < [git command output]\n"
7664 "\n"
7665 "Options:\n"
7666 " -v, --version Show version and exit\n"
7667 " -h, --help Show help message and exit";
7669 static void __NORETURN
7670 quit(int sig)
7671 {
7672 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7673 if (cursed)
7674 endwin();
7675 exit(0);
7676 }
7678 static void __NORETURN
7679 die(const char *err, ...)
7680 {
7681 va_list args;
7683 endwin();
7685 va_start(args, err);
7686 fputs("tig: ", stderr);
7687 vfprintf(stderr, err, args);
7688 fputs("\n", stderr);
7689 va_end(args);
7691 exit(1);
7692 }
7694 static void
7695 warn(const char *msg, ...)
7696 {
7697 va_list args;
7699 va_start(args, msg);
7700 fputs("tig warning: ", stderr);
7701 vfprintf(stderr, msg, args);
7702 fputs("\n", stderr);
7703 va_end(args);
7704 }
7706 static enum request
7707 parse_options(int argc, const char *argv[])
7708 {
7709 enum request request = REQ_VIEW_MAIN;
7710 const char *subcommand;
7711 bool seen_dashdash = FALSE;
7712 /* XXX: This is vulnerable to the user overriding options
7713 * required for the main view parser. */
7714 const char *custom_argv[SIZEOF_ARG] = {
7715 "git", "log", "--no-color", "--pretty=raw", "--parents",
7716 "--topo-order", NULL
7717 };
7718 int i, j = 6;
7720 if (!isatty(STDIN_FILENO)) {
7721 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7722 return REQ_VIEW_PAGER;
7723 }
7725 if (argc <= 1)
7726 return REQ_NONE;
7728 subcommand = argv[1];
7729 if (!strcmp(subcommand, "status")) {
7730 if (argc > 2)
7731 warn("ignoring arguments after `%s'", subcommand);
7732 return REQ_VIEW_STATUS;
7734 } else if (!strcmp(subcommand, "blame")) {
7735 if (argc <= 2 || argc > 4)
7736 die("invalid number of options to blame\n\n%s", usage);
7738 i = 2;
7739 if (argc == 4) {
7740 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7741 i++;
7742 }
7744 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7745 return REQ_VIEW_BLAME;
7747 } else if (!strcmp(subcommand, "show")) {
7748 request = REQ_VIEW_DIFF;
7750 } else {
7751 subcommand = NULL;
7752 }
7754 if (subcommand) {
7755 custom_argv[1] = subcommand;
7756 j = 2;
7757 }
7759 for (i = 1 + !!subcommand; i < argc; i++) {
7760 const char *opt = argv[i];
7762 if (seen_dashdash || !strcmp(opt, "--")) {
7763 seen_dashdash = TRUE;
7765 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7766 printf("tig version %s\n", TIG_VERSION);
7767 quit(0);
7769 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7770 printf("%s\n", usage);
7771 quit(0);
7772 }
7774 custom_argv[j++] = opt;
7775 if (j >= ARRAY_SIZE(custom_argv))
7776 die("command too long");
7777 }
7779 if (!prepare_update(VIEW(request), custom_argv, NULL))
7780 die("Failed to format arguments");
7782 return request;
7783 }
7785 int
7786 main(int argc, const char *argv[])
7787 {
7788 const char *codeset = "UTF-8";
7789 enum request request = parse_options(argc, argv);
7790 struct view *view;
7791 size_t i;
7793 signal(SIGINT, quit);
7794 signal(SIGPIPE, SIG_IGN);
7796 if (setlocale(LC_ALL, "")) {
7797 codeset = nl_langinfo(CODESET);
7798 }
7800 if (load_repo_info() == ERR)
7801 die("Failed to load repo info.");
7803 if (load_options() == ERR)
7804 die("Failed to load user config.");
7806 if (load_git_config() == ERR)
7807 die("Failed to load repo config.");
7809 /* Require a git repository unless when running in pager mode. */
7810 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7811 die("Not a git repository");
7813 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7814 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7815 if (opt_iconv_in == ICONV_NONE)
7816 die("Failed to initialize character set conversion");
7817 }
7819 if (codeset && strcmp(codeset, "UTF-8")) {
7820 opt_iconv_out = iconv_open(codeset, "UTF-8");
7821 if (opt_iconv_out == ICONV_NONE)
7822 die("Failed to initialize character set conversion");
7823 }
7825 if (load_refs() == ERR)
7826 die("Failed to load refs.");
7828 foreach_view (view, i)
7829 if (!argv_from_env(view->ops->argv, view->cmd_env))
7830 die("Too many arguments in the `%s` environment variable",
7831 view->cmd_env);
7833 init_display();
7835 if (request != REQ_NONE)
7836 open_view(NULL, request, OPEN_PREPARED);
7837 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7839 while (view_driver(display[current_view], request)) {
7840 int key = get_input(0);
7842 view = display[current_view];
7843 request = get_keybinding(view->keymap, key);
7845 /* Some low-level request handling. This keeps access to
7846 * status_win restricted. */
7847 switch (request) {
7848 case REQ_NONE:
7849 report("Unknown key, press %s for help",
7850 get_key(view->keymap, REQ_VIEW_HELP));
7851 break;
7852 case REQ_PROMPT:
7853 {
7854 char *cmd = read_prompt(":");
7856 if (cmd && isdigit(*cmd)) {
7857 int lineno = view->lineno + 1;
7859 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7860 select_view_line(view, lineno - 1);
7861 report("");
7862 } else {
7863 report("Unable to parse '%s' as a line number", cmd);
7864 }
7866 } else if (cmd) {
7867 struct view *next = VIEW(REQ_VIEW_PAGER);
7868 const char *argv[SIZEOF_ARG] = { "git" };
7869 int argc = 1;
7871 /* When running random commands, initially show the
7872 * command in the title. However, it maybe later be
7873 * overwritten if a commit line is selected. */
7874 string_ncopy(next->ref, cmd, strlen(cmd));
7876 if (!argv_from_string(argv, &argc, cmd)) {
7877 report("Too many arguments");
7878 } else if (!prepare_update(next, argv, NULL)) {
7879 report("Failed to format command");
7880 } else {
7881 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7882 }
7883 }
7885 request = REQ_NONE;
7886 break;
7887 }
7888 case REQ_SEARCH:
7889 case REQ_SEARCH_BACK:
7890 {
7891 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7892 char *search = read_prompt(prompt);
7894 if (search)
7895 string_ncopy(opt_search, search, strlen(search));
7896 else if (*opt_search)
7897 request = request == REQ_SEARCH ?
7898 REQ_FIND_NEXT :
7899 REQ_FIND_PREV;
7900 else
7901 request = REQ_NONE;
7902 break;
7903 }
7904 default:
7905 break;
7906 }
7907 }
7909 quit(0);
7911 return 0;
7912 }