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_(RELATIVE), \
523 DATE_(SHORT)
525 enum date {
526 #define DATE_(name) DATE_##name
527 DATE_INFO
528 #undef DATE_
529 };
531 static const struct enum_map date_map[] = {
532 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
533 DATE_INFO
534 #undef DATE_
535 };
537 struct time {
538 time_t sec;
539 int tz;
540 };
542 static inline int timecmp(const struct time *t1, const struct time *t2)
543 {
544 return t1->sec - t2->sec;
545 }
547 static const char *
548 mkdate(const struct time *time, enum date date)
549 {
550 static char buf[DATE_COLS + 1];
551 static const struct enum_map reldate[] = {
552 { "second", 1, 60 * 2 },
553 { "minute", 60, 60 * 60 * 2 },
554 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
555 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
556 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
557 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
558 };
559 struct tm tm;
561 if (!date || !time || !time->sec)
562 return "";
564 if (date == DATE_RELATIVE) {
565 struct timeval now;
566 time_t date = time->sec + time->tz;
567 time_t seconds;
568 int i;
570 gettimeofday(&now, NULL);
571 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
572 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
573 if (seconds >= reldate[i].value)
574 continue;
576 seconds /= reldate[i].namelen;
577 if (!string_format(buf, "%ld %s%s %s",
578 seconds, reldate[i].name,
579 seconds > 1 ? "s" : "",
580 now.tv_sec >= date ? "ago" : "ahead"))
581 break;
582 return buf;
583 }
584 }
586 gmtime_r(&time->sec, &tm);
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
675 /*
676 * Executing external commands.
677 */
679 enum io_type {
680 IO_FD, /* File descriptor based IO. */
681 IO_BG, /* Execute command in the background. */
682 IO_FG, /* Execute command with same std{in,out,err}. */
683 IO_RD, /* Read only fork+exec IO. */
684 IO_WR, /* Write only fork+exec IO. */
685 IO_AP, /* Append fork+exec output to file. */
686 };
688 struct io {
689 enum io_type type; /* The requested type of pipe. */
690 const char *dir; /* Directory from which to execute. */
691 pid_t pid; /* PID of spawned process. */
692 int pipe; /* Pipe end for reading or writing. */
693 int error; /* Error status. */
694 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
695 char *buf; /* Read buffer. */
696 size_t bufalloc; /* Allocated buffer size. */
697 size_t bufsize; /* Buffer content size. */
698 char *bufpos; /* Current buffer position. */
699 unsigned int eof:1; /* Has end of file been reached. */
700 };
702 static void
703 io_reset(struct io *io)
704 {
705 io->pipe = -1;
706 io->pid = 0;
707 io->buf = io->bufpos = NULL;
708 io->bufalloc = io->bufsize = 0;
709 io->error = 0;
710 io->eof = 0;
711 }
713 static void
714 io_init(struct io *io, const char *dir, enum io_type type)
715 {
716 io_reset(io);
717 io->type = type;
718 io->dir = dir;
719 }
721 static bool
722 io_format(struct io *io, const char *dir, enum io_type type,
723 const char *argv[], enum format_flags flags)
724 {
725 io_init(io, dir, type);
726 return format_argv(io->argv, argv, flags);
727 }
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
731 {
732 char name[SIZEOF_STR] = "";
733 bool fits;
734 va_list args;
736 io_init(io, NULL, IO_FD);
738 va_start(args, fmt);
739 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740 va_end(args);
742 if (!fits) {
743 io->error = ENAMETOOLONG;
744 return FALSE;
745 }
746 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747 if (io->pipe == -1)
748 io->error = errno;
749 return io->pipe != -1;
750 }
752 static bool
753 io_kill(struct io *io)
754 {
755 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
756 }
758 static bool
759 io_done(struct io *io)
760 {
761 pid_t pid = io->pid;
763 if (io->pipe != -1)
764 close(io->pipe);
765 free(io->buf);
766 io_reset(io);
768 while (pid > 0) {
769 int status;
770 pid_t waiting = waitpid(pid, &status, 0);
772 if (waiting < 0) {
773 if (errno == EINTR)
774 continue;
775 io->error = errno;
776 return FALSE;
777 }
779 return waiting == pid &&
780 !WIFSIGNALED(status) &&
781 WIFEXITED(status) &&
782 !WEXITSTATUS(status);
783 }
785 return TRUE;
786 }
788 static bool
789 io_start(struct io *io)
790 {
791 int pipefds[2] = { -1, -1 };
793 if (io->type == IO_FD)
794 return TRUE;
796 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
797 io->error = errno;
798 return FALSE;
799 } else if (io->type == IO_AP) {
800 pipefds[1] = io->pipe;
801 }
803 if ((io->pid = fork())) {
804 if (io->pid == -1)
805 io->error = errno;
806 if (pipefds[!(io->type == IO_WR)] != -1)
807 close(pipefds[!(io->type == IO_WR)]);
808 if (io->pid != -1) {
809 io->pipe = pipefds[!!(io->type == IO_WR)];
810 return TRUE;
811 }
813 } else {
814 if (io->type != IO_FG) {
815 int devnull = open("/dev/null", O_RDWR);
816 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
817 int writefd = (io->type == IO_RD || io->type == IO_AP)
818 ? pipefds[1] : devnull;
820 dup2(readfd, STDIN_FILENO);
821 dup2(writefd, STDOUT_FILENO);
822 dup2(devnull, STDERR_FILENO);
824 close(devnull);
825 if (pipefds[0] != -1)
826 close(pipefds[0]);
827 if (pipefds[1] != -1)
828 close(pipefds[1]);
829 }
831 if (io->dir && *io->dir && chdir(io->dir) == -1)
832 exit(errno);
834 execvp(io->argv[0], (char *const*) io->argv);
835 exit(errno);
836 }
838 if (pipefds[!!(io->type == IO_WR)] != -1)
839 close(pipefds[!!(io->type == IO_WR)]);
840 return FALSE;
841 }
843 static bool
844 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
845 {
846 io_init(io, dir, type);
847 if (!format_argv(io->argv, argv, FORMAT_NONE))
848 return FALSE;
849 return io_start(io);
850 }
852 static int
853 io_complete(struct io *io)
854 {
855 return io_start(io) && io_done(io);
856 }
858 static int
859 io_run_bg(const char **argv)
860 {
861 struct io io = {};
863 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
864 return FALSE;
865 return io_complete(&io);
866 }
868 static bool
869 io_run_fg(const char **argv, const char *dir)
870 {
871 struct io io = {};
873 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
874 return FALSE;
875 return io_complete(&io);
876 }
878 static bool
879 io_run_append(const char **argv, enum format_flags flags, int fd)
880 {
881 struct io io = {};
883 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
884 close(fd);
885 return FALSE;
886 }
888 io.pipe = fd;
889 return io_complete(&io);
890 }
892 static bool
893 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
894 {
895 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
896 }
898 static bool
899 io_eof(struct io *io)
900 {
901 return io->eof;
902 }
904 static int
905 io_error(struct io *io)
906 {
907 return io->error;
908 }
910 static char *
911 io_strerror(struct io *io)
912 {
913 return strerror(io->error);
914 }
916 static bool
917 io_can_read(struct io *io)
918 {
919 struct timeval tv = { 0, 500 };
920 fd_set fds;
922 FD_ZERO(&fds);
923 FD_SET(io->pipe, &fds);
925 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
926 }
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
930 {
931 do {
932 ssize_t readsize = read(io->pipe, buf, bufsize);
934 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935 continue;
936 else if (readsize == -1)
937 io->error = errno;
938 else if (readsize == 0)
939 io->eof = 1;
940 return readsize;
941 } while (1);
942 }
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
948 {
949 char *eol;
950 ssize_t readsize;
952 while (TRUE) {
953 if (io->bufsize > 0) {
954 eol = memchr(io->bufpos, c, io->bufsize);
955 if (eol) {
956 char *line = io->bufpos;
958 *eol = 0;
959 io->bufpos = eol + 1;
960 io->bufsize -= io->bufpos - line;
961 return line;
962 }
963 }
965 if (io_eof(io)) {
966 if (io->bufsize) {
967 io->bufpos[io->bufsize] = 0;
968 io->bufsize = 0;
969 return io->bufpos;
970 }
971 return NULL;
972 }
974 if (!can_read)
975 return NULL;
977 if (io->bufsize > 0 && io->bufpos > io->buf)
978 memmove(io->buf, io->bufpos, io->bufsize);
980 if (io->bufalloc == io->bufsize) {
981 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982 return NULL;
983 io->bufalloc += BUFSIZ;
984 }
986 io->bufpos = io->buf;
987 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988 if (io_error(io))
989 return NULL;
990 io->bufsize += readsize;
991 }
992 }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
996 {
997 size_t written = 0;
999 while (!io_error(io) && written < bufsize) {
1000 ssize_t size;
1002 size = write(io->pipe, buf + written, bufsize - written);
1003 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004 continue;
1005 else if (size == -1)
1006 io->error = errno;
1007 else
1008 written += size;
1009 }
1011 return written == bufsize;
1012 }
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1016 {
1017 char *result = io_get(io, '\n', TRUE);
1019 if (result) {
1020 result = chomp_string(result);
1021 string_ncopy_do(buf, bufsize, result, strlen(result));
1022 }
1024 return io_done(io) && result;
1025 }
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1029 {
1030 struct io io = {};
1032 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1033 && io_read_buf(&io, buf, bufsize);
1034 }
1036 static int
1037 io_load(struct io *io, const char *separators,
1038 int (*read_property)(char *, size_t, char *, size_t))
1039 {
1040 char *name;
1041 int state = OK;
1043 if (!io_start(io))
1044 return ERR;
1046 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047 char *value;
1048 size_t namelen;
1049 size_t valuelen;
1051 name = chomp_string(name);
1052 namelen = strcspn(name, separators);
1054 if (name[namelen]) {
1055 name[namelen] = 0;
1056 value = chomp_string(name + namelen + 1);
1057 valuelen = strlen(value);
1059 } else {
1060 value = "";
1061 valuelen = 0;
1062 }
1064 state = read_property(name, namelen, value, valuelen);
1065 }
1067 if (state != ERR && io_error(io))
1068 state = ERR;
1069 io_done(io);
1071 return state;
1072 }
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076 int (*read_property)(char *, size_t, char *, size_t))
1077 {
1078 struct io io = {};
1080 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1081 ? io_load(&io, separators, read_property) : ERR;
1082 }
1085 /*
1086 * User requests
1087 */
1089 #define REQ_INFO \
1090 /* XXX: Keep the view request first and in sync with views[]. */ \
1091 REQ_GROUP("View switching") \
1092 REQ_(VIEW_MAIN, "Show main view"), \
1093 REQ_(VIEW_DIFF, "Show diff view"), \
1094 REQ_(VIEW_LOG, "Show log view"), \
1095 REQ_(VIEW_TREE, "Show tree view"), \
1096 REQ_(VIEW_BLOB, "Show blob view"), \
1097 REQ_(VIEW_BLAME, "Show blame view"), \
1098 REQ_(VIEW_BRANCH, "Show branch view"), \
1099 REQ_(VIEW_HELP, "Show help page"), \
1100 REQ_(VIEW_PAGER, "Show pager view"), \
1101 REQ_(VIEW_STATUS, "Show status view"), \
1102 REQ_(VIEW_STAGE, "Show stage view"), \
1103 \
1104 REQ_GROUP("View manipulation") \
1105 REQ_(ENTER, "Enter current line and scroll"), \
1106 REQ_(NEXT, "Move to next"), \
1107 REQ_(PREVIOUS, "Move to previous"), \
1108 REQ_(PARENT, "Move to parent"), \
1109 REQ_(VIEW_NEXT, "Move focus to next view"), \
1110 REQ_(REFRESH, "Reload and refresh"), \
1111 REQ_(MAXIMIZE, "Maximize the current view"), \
1112 REQ_(VIEW_CLOSE, "Close the current view"), \
1113 REQ_(QUIT, "Close all views and quit"), \
1114 \
1115 REQ_GROUP("View specific requests") \
1116 REQ_(STATUS_UPDATE, "Update file status"), \
1117 REQ_(STATUS_REVERT, "Revert file changes"), \
1118 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1119 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1120 \
1121 REQ_GROUP("Cursor navigation") \
1122 REQ_(MOVE_UP, "Move cursor one line up"), \
1123 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1124 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1125 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1126 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1127 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1128 \
1129 REQ_GROUP("Scrolling") \
1130 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1131 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1132 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1133 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1134 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1135 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1136 \
1137 REQ_GROUP("Searching") \
1138 REQ_(SEARCH, "Search the view"), \
1139 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1140 REQ_(FIND_NEXT, "Find next search match"), \
1141 REQ_(FIND_PREV, "Find previous search match"), \
1142 \
1143 REQ_GROUP("Option manipulation") \
1144 REQ_(OPTIONS, "Open option menu"), \
1145 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1146 REQ_(TOGGLE_DATE, "Toggle date display"), \
1147 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1149 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1150 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1151 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153 \
1154 REQ_GROUP("Misc") \
1155 REQ_(PROMPT, "Bring up the prompt"), \
1156 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1157 REQ_(SHOW_VERSION, "Show version information"), \
1158 REQ_(STOP_LOADING, "Stop all loading views"), \
1159 REQ_(EDIT, "Open in editor"), \
1160 REQ_(NONE, "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168 /* Offset all requests to avoid conflicts with ncurses getch values. */
1169 REQ_OFFSET = KEY_MAX + 1,
1170 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1174 };
1176 struct request_info {
1177 enum request request;
1178 const char *name;
1179 int namelen;
1180 const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186 REQ_INFO
1187 #undef REQ_GROUP
1188 #undef REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1193 {
1194 int namelen = strlen(name);
1195 int i;
1197 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198 if (enum_equals(req_info[i], name, namelen))
1199 return req_info[i].request;
1201 return REQ_NONE;
1202 }
1205 /*
1206 * Options
1207 */
1209 /* Option and state variables. */
1210 static enum date opt_date = DATE_DEFAULT;
1211 static enum author opt_author = AUTHOR_DEFAULT;
1212 static bool opt_line_number = FALSE;
1213 static bool opt_line_graphics = TRUE;
1214 static bool opt_rev_graph = FALSE;
1215 static bool opt_show_refs = TRUE;
1216 static int opt_num_interval = 5;
1217 static double opt_hscroll = 0.50;
1218 static double opt_scale_split_view = 2.0 / 3.0;
1219 static int opt_tab_size = 8;
1220 static int opt_author_cols = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR] = "";
1222 static char opt_file[SIZEOF_STR] = "";
1223 static char opt_ref[SIZEOF_REF] = "";
1224 static char opt_head[SIZEOF_REF] = "";
1225 static char opt_remote[SIZEOF_REF] = "";
1226 static char opt_encoding[20] = "UTF-8";
1227 static iconv_t opt_iconv_in = ICONV_NONE;
1228 static iconv_t opt_iconv_out = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR] = "";
1230 static char opt_cdup[SIZEOF_STR] = "";
1231 static char opt_prefix[SIZEOF_STR] = "";
1232 static char opt_git_dir[SIZEOF_STR] = "";
1233 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR] = "";
1235 static FILE *opt_tty = NULL;
1237 #define is_initial_commit() (!get_ref_head())
1238 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1241 /*
1242 * Line-oriented content detection.
1243 */
1245 #define LINE_INFO \
1246 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1247 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1260 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1261 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1267 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1268 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1269 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1272 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1275 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1276 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1277 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1280 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1281 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1282 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1283 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1284 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1285 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1288 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1292 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1294 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1295 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1297 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1298 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1302 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1304 enum line_type {
1305 #define LINE(type, line, fg, bg, attr) \
1306 LINE_##type
1307 LINE_INFO,
1308 LINE_NONE
1309 #undef LINE
1310 };
1312 struct line_info {
1313 const char *name; /* Option name. */
1314 int namelen; /* Size of option name. */
1315 const char *line; /* The start of line to match. */
1316 int linelen; /* Size of string to match. */
1317 int fg, bg, attr; /* Color and text attributes for the lines. */
1318 };
1320 static struct line_info line_info[] = {
1321 #define LINE(type, line, fg, bg, attr) \
1322 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1323 LINE_INFO
1324 #undef LINE
1325 };
1327 static enum line_type
1328 get_line_type(const char *line)
1329 {
1330 int linelen = strlen(line);
1331 enum line_type type;
1333 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1334 /* Case insensitive search matches Signed-off-by lines better. */
1335 if (linelen >= line_info[type].linelen &&
1336 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1337 return type;
1339 return LINE_DEFAULT;
1340 }
1342 static inline int
1343 get_line_attr(enum line_type type)
1344 {
1345 assert(type < ARRAY_SIZE(line_info));
1346 return COLOR_PAIR(type) | line_info[type].attr;
1347 }
1349 static struct line_info *
1350 get_line_info(const char *name)
1351 {
1352 size_t namelen = strlen(name);
1353 enum line_type type;
1355 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1356 if (enum_equals(line_info[type], name, namelen))
1357 return &line_info[type];
1359 return NULL;
1360 }
1362 static void
1363 init_colors(void)
1364 {
1365 int default_bg = line_info[LINE_DEFAULT].bg;
1366 int default_fg = line_info[LINE_DEFAULT].fg;
1367 enum line_type type;
1369 start_color();
1371 if (assume_default_colors(default_fg, default_bg) == ERR) {
1372 default_bg = COLOR_BLACK;
1373 default_fg = COLOR_WHITE;
1374 }
1376 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1377 struct line_info *info = &line_info[type];
1378 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1379 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1381 init_pair(type, fg, bg);
1382 }
1383 }
1385 struct line {
1386 enum line_type type;
1388 /* State flags */
1389 unsigned int selected:1;
1390 unsigned int dirty:1;
1391 unsigned int cleareol:1;
1392 unsigned int other:16;
1394 void *data; /* User data */
1395 };
1398 /*
1399 * Keys
1400 */
1402 struct keybinding {
1403 int alias;
1404 enum request request;
1405 };
1407 static const struct keybinding default_keybindings[] = {
1408 /* View switching */
1409 { 'm', REQ_VIEW_MAIN },
1410 { 'd', REQ_VIEW_DIFF },
1411 { 'l', REQ_VIEW_LOG },
1412 { 't', REQ_VIEW_TREE },
1413 { 'f', REQ_VIEW_BLOB },
1414 { 'B', REQ_VIEW_BLAME },
1415 { 'H', REQ_VIEW_BRANCH },
1416 { 'p', REQ_VIEW_PAGER },
1417 { 'h', REQ_VIEW_HELP },
1418 { 'S', REQ_VIEW_STATUS },
1419 { 'c', REQ_VIEW_STAGE },
1421 /* View manipulation */
1422 { 'q', REQ_VIEW_CLOSE },
1423 { KEY_TAB, REQ_VIEW_NEXT },
1424 { KEY_RETURN, REQ_ENTER },
1425 { KEY_UP, REQ_PREVIOUS },
1426 { KEY_DOWN, REQ_NEXT },
1427 { 'R', REQ_REFRESH },
1428 { KEY_F(5), REQ_REFRESH },
1429 { 'O', REQ_MAXIMIZE },
1431 /* Cursor navigation */
1432 { 'k', REQ_MOVE_UP },
1433 { 'j', REQ_MOVE_DOWN },
1434 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1435 { KEY_END, REQ_MOVE_LAST_LINE },
1436 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1437 { ' ', REQ_MOVE_PAGE_DOWN },
1438 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1439 { 'b', REQ_MOVE_PAGE_UP },
1440 { '-', REQ_MOVE_PAGE_UP },
1442 /* Scrolling */
1443 { KEY_LEFT, REQ_SCROLL_LEFT },
1444 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1445 { KEY_IC, REQ_SCROLL_LINE_UP },
1446 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1447 { 'w', REQ_SCROLL_PAGE_UP },
1448 { 's', REQ_SCROLL_PAGE_DOWN },
1450 /* Searching */
1451 { '/', REQ_SEARCH },
1452 { '?', REQ_SEARCH_BACK },
1453 { 'n', REQ_FIND_NEXT },
1454 { 'N', REQ_FIND_PREV },
1456 /* Misc */
1457 { 'Q', REQ_QUIT },
1458 { 'z', REQ_STOP_LOADING },
1459 { 'v', REQ_SHOW_VERSION },
1460 { 'r', REQ_SCREEN_REDRAW },
1461 { 'o', REQ_OPTIONS },
1462 { '.', REQ_TOGGLE_LINENO },
1463 { 'D', REQ_TOGGLE_DATE },
1464 { 'A', REQ_TOGGLE_AUTHOR },
1465 { 'g', REQ_TOGGLE_REV_GRAPH },
1466 { 'F', REQ_TOGGLE_REFS },
1467 { 'I', REQ_TOGGLE_SORT_ORDER },
1468 { 'i', REQ_TOGGLE_SORT_FIELD },
1469 { ':', REQ_PROMPT },
1470 { 'u', REQ_STATUS_UPDATE },
1471 { '!', REQ_STATUS_REVERT },
1472 { 'M', REQ_STATUS_MERGE },
1473 { '@', REQ_STAGE_NEXT },
1474 { ',', REQ_PARENT },
1475 { 'e', REQ_EDIT },
1476 };
1478 #define KEYMAP_INFO \
1479 KEYMAP_(GENERIC), \
1480 KEYMAP_(MAIN), \
1481 KEYMAP_(DIFF), \
1482 KEYMAP_(LOG), \
1483 KEYMAP_(TREE), \
1484 KEYMAP_(BLOB), \
1485 KEYMAP_(BLAME), \
1486 KEYMAP_(BRANCH), \
1487 KEYMAP_(PAGER), \
1488 KEYMAP_(HELP), \
1489 KEYMAP_(STATUS), \
1490 KEYMAP_(STAGE)
1492 enum keymap {
1493 #define KEYMAP_(name) KEYMAP_##name
1494 KEYMAP_INFO
1495 #undef KEYMAP_
1496 };
1498 static const struct enum_map keymap_table[] = {
1499 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1500 KEYMAP_INFO
1501 #undef KEYMAP_
1502 };
1504 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1506 struct keybinding_table {
1507 struct keybinding *data;
1508 size_t size;
1509 };
1511 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1513 static void
1514 add_keybinding(enum keymap keymap, enum request request, int key)
1515 {
1516 struct keybinding_table *table = &keybindings[keymap];
1518 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1519 if (!table->data)
1520 die("Failed to allocate keybinding");
1521 table->data[table->size].alias = key;
1522 table->data[table->size++].request = request;
1523 }
1525 /* Looks for a key binding first in the given map, then in the generic map, and
1526 * lastly in the default keybindings. */
1527 static enum request
1528 get_keybinding(enum keymap keymap, int key)
1529 {
1530 size_t i;
1532 for (i = 0; i < keybindings[keymap].size; i++)
1533 if (keybindings[keymap].data[i].alias == key)
1534 return keybindings[keymap].data[i].request;
1536 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1537 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1538 return keybindings[KEYMAP_GENERIC].data[i].request;
1540 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541 if (default_keybindings[i].alias == key)
1542 return default_keybindings[i].request;
1544 return (enum request) key;
1545 }
1548 struct key {
1549 const char *name;
1550 int value;
1551 };
1553 static const struct key key_table[] = {
1554 { "Enter", KEY_RETURN },
1555 { "Space", ' ' },
1556 { "Backspace", KEY_BACKSPACE },
1557 { "Tab", KEY_TAB },
1558 { "Escape", KEY_ESC },
1559 { "Left", KEY_LEFT },
1560 { "Right", KEY_RIGHT },
1561 { "Up", KEY_UP },
1562 { "Down", KEY_DOWN },
1563 { "Insert", KEY_IC },
1564 { "Delete", KEY_DC },
1565 { "Hash", '#' },
1566 { "Home", KEY_HOME },
1567 { "End", KEY_END },
1568 { "PageUp", KEY_PPAGE },
1569 { "PageDown", KEY_NPAGE },
1570 { "F1", KEY_F(1) },
1571 { "F2", KEY_F(2) },
1572 { "F3", KEY_F(3) },
1573 { "F4", KEY_F(4) },
1574 { "F5", KEY_F(5) },
1575 { "F6", KEY_F(6) },
1576 { "F7", KEY_F(7) },
1577 { "F8", KEY_F(8) },
1578 { "F9", KEY_F(9) },
1579 { "F10", KEY_F(10) },
1580 { "F11", KEY_F(11) },
1581 { "F12", KEY_F(12) },
1582 };
1584 static int
1585 get_key_value(const char *name)
1586 {
1587 int i;
1589 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1590 if (!strcasecmp(key_table[i].name, name))
1591 return key_table[i].value;
1593 if (strlen(name) == 1 && isprint(*name))
1594 return (int) *name;
1596 return ERR;
1597 }
1599 static const char *
1600 get_key_name(int key_value)
1601 {
1602 static char key_char[] = "'X'";
1603 const char *seq = NULL;
1604 int key;
1606 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1607 if (key_table[key].value == key_value)
1608 seq = key_table[key].name;
1610 if (seq == NULL &&
1611 key_value < 127 &&
1612 isprint(key_value)) {
1613 key_char[1] = (char) key_value;
1614 seq = key_char;
1615 }
1617 return seq ? seq : "(no key)";
1618 }
1620 static bool
1621 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1622 {
1623 const char *sep = *pos > 0 ? ", " : "";
1624 const char *keyname = get_key_name(keybinding->alias);
1626 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1627 }
1629 static bool
1630 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1631 enum keymap keymap, bool all)
1632 {
1633 int i;
1635 for (i = 0; i < keybindings[keymap].size; i++) {
1636 if (keybindings[keymap].data[i].request == request) {
1637 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1638 return FALSE;
1639 if (!all)
1640 break;
1641 }
1642 }
1644 return TRUE;
1645 }
1647 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1649 static const char *
1650 get_keys(enum keymap keymap, enum request request, bool all)
1651 {
1652 static char buf[BUFSIZ];
1653 size_t pos = 0;
1654 int i;
1656 buf[pos] = 0;
1658 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1659 return "Too many keybindings!";
1660 if (pos > 0 && !all)
1661 return buf;
1663 if (keymap != KEYMAP_GENERIC) {
1664 /* Only the generic keymap includes the default keybindings when
1665 * listing all keys. */
1666 if (all)
1667 return buf;
1669 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1670 return "Too many keybindings!";
1671 if (pos)
1672 return buf;
1673 }
1675 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1676 if (default_keybindings[i].request == request) {
1677 if (!append_key(buf, &pos, &default_keybindings[i]))
1678 return "Too many keybindings!";
1679 if (!all)
1680 return buf;
1681 }
1682 }
1684 return buf;
1685 }
1687 struct run_request {
1688 enum keymap keymap;
1689 int key;
1690 const char *argv[SIZEOF_ARG];
1691 };
1693 static struct run_request *run_request;
1694 static size_t run_requests;
1696 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1698 static enum request
1699 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1700 {
1701 struct run_request *req;
1703 if (argc >= ARRAY_SIZE(req->argv) - 1)
1704 return REQ_NONE;
1706 if (!realloc_run_requests(&run_request, run_requests, 1))
1707 return REQ_NONE;
1709 req = &run_request[run_requests];
1710 req->keymap = keymap;
1711 req->key = key;
1712 req->argv[0] = NULL;
1714 if (!format_argv(req->argv, argv, FORMAT_NONE))
1715 return REQ_NONE;
1717 return REQ_NONE + ++run_requests;
1718 }
1720 static struct run_request *
1721 get_run_request(enum request request)
1722 {
1723 if (request <= REQ_NONE)
1724 return NULL;
1725 return &run_request[request - REQ_NONE - 1];
1726 }
1728 static void
1729 add_builtin_run_requests(void)
1730 {
1731 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1732 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1733 const char *commit[] = { "git", "commit", NULL };
1734 const char *gc[] = { "git", "gc", NULL };
1735 struct {
1736 enum keymap keymap;
1737 int key;
1738 int argc;
1739 const char **argv;
1740 } reqs[] = {
1741 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1743 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1744 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745 };
1746 int i;
1748 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749 enum request req;
1751 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752 if (req != REQ_NONE)
1753 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1754 }
1755 }
1757 /*
1758 * User config file handling.
1759 */
1761 static int config_lineno;
1762 static bool config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767 COLOR_MAP(DEFAULT),
1768 COLOR_MAP(BLACK),
1769 COLOR_MAP(BLUE),
1770 COLOR_MAP(CYAN),
1771 COLOR_MAP(GREEN),
1772 COLOR_MAP(MAGENTA),
1773 COLOR_MAP(RED),
1774 COLOR_MAP(WHITE),
1775 COLOR_MAP(YELLOW),
1776 };
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780 ATTR_MAP(NORMAL),
1781 ATTR_MAP(BLINK),
1782 ATTR_MAP(BOLD),
1783 ATTR_MAP(DIM),
1784 ATTR_MAP(REVERSE),
1785 ATTR_MAP(STANDOUT),
1786 ATTR_MAP(UNDERLINE),
1787 };
1789 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1792 {
1793 *opt = atoi(arg);
1794 if (!strchr(arg, '%'))
1795 return OK;
1797 /* "Shift down" so 100% and 1 does not conflict. */
1798 *opt = (*opt - 1) / 100;
1799 if (*opt >= 1.0) {
1800 *opt = 0.99;
1801 config_msg = "Step value larger than 100%";
1802 return ERR;
1803 }
1804 if (*opt < 0.0) {
1805 *opt = 1;
1806 config_msg = "Invalid step value";
1807 return ERR;
1808 }
1809 return OK;
1810 }
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1814 {
1815 int value = atoi(arg);
1817 if (min <= value && value <= max) {
1818 *opt = value;
1819 return OK;
1820 }
1822 config_msg = "Integer value out of bound";
1823 return ERR;
1824 }
1826 static bool
1827 set_color(int *color, const char *name)
1828 {
1829 if (map_enum(color, color_map, name))
1830 return TRUE;
1831 if (!prefixcmp(name, "color"))
1832 return parse_int(color, name + 5, 0, 255) == OK;
1833 return FALSE;
1834 }
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1839 {
1840 struct line_info *info;
1842 if (argc < 3) {
1843 config_msg = "Wrong number of arguments given to color command";
1844 return ERR;
1845 }
1847 info = get_line_info(argv[0]);
1848 if (!info) {
1849 static const struct enum_map obsolete[] = {
1850 ENUM_MAP("main-delim", LINE_DELIMITER),
1851 ENUM_MAP("main-date", LINE_DATE),
1852 ENUM_MAP("main-author", LINE_AUTHOR),
1853 };
1854 int index;
1856 if (!map_enum(&index, obsolete, argv[0])) {
1857 config_msg = "Unknown color name";
1858 return ERR;
1859 }
1860 info = &line_info[index];
1861 }
1863 if (!set_color(&info->fg, argv[1]) ||
1864 !set_color(&info->bg, argv[2])) {
1865 config_msg = "Unknown color";
1866 return ERR;
1867 }
1869 info->attr = 0;
1870 while (argc-- > 3) {
1871 int attr;
1873 if (!set_attribute(&attr, argv[argc])) {
1874 config_msg = "Unknown attribute";
1875 return ERR;
1876 }
1877 info->attr |= attr;
1878 }
1880 return OK;
1881 }
1883 static int parse_bool(bool *opt, const char *arg)
1884 {
1885 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886 ? TRUE : FALSE;
1887 return OK;
1888 }
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891 const struct enum_map *map, size_t map_size)
1892 {
1893 bool is_true;
1895 assert(map_size > 1);
1897 if (map_enum_do(map, map_size, (int *) opt, arg))
1898 return OK;
1900 if (parse_bool(&is_true, arg) != OK)
1901 return ERR;
1903 *opt = is_true ? map[1].value : map[0].value;
1904 return OK;
1905 }
1907 #define parse_enum(opt, arg, map) \
1908 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1912 {
1913 int arglen = strlen(arg);
1915 switch (arg[0]) {
1916 case '\"':
1917 case '\'':
1918 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919 config_msg = "Unmatched quotation";
1920 return ERR;
1921 }
1922 arg += 1; arglen -= 2;
1923 default:
1924 string_ncopy_do(opt, optsize, arg, arglen);
1925 return OK;
1926 }
1927 }
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1932 {
1933 if (argc != 3) {
1934 config_msg = "Wrong number of arguments given to set command";
1935 return ERR;
1936 }
1938 if (strcmp(argv[1], "=")) {
1939 config_msg = "No value assigned";
1940 return ERR;
1941 }
1943 if (!strcmp(argv[0], "show-author"))
1944 return parse_enum(&opt_author, argv[2], author_map);
1946 if (!strcmp(argv[0], "show-date"))
1947 return parse_enum(&opt_date, argv[2], date_map);
1949 if (!strcmp(argv[0], "show-rev-graph"))
1950 return parse_bool(&opt_rev_graph, argv[2]);
1952 if (!strcmp(argv[0], "show-refs"))
1953 return parse_bool(&opt_show_refs, argv[2]);
1955 if (!strcmp(argv[0], "show-line-numbers"))
1956 return parse_bool(&opt_line_number, argv[2]);
1958 if (!strcmp(argv[0], "line-graphics"))
1959 return parse_bool(&opt_line_graphics, argv[2]);
1961 if (!strcmp(argv[0], "line-number-interval"))
1962 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964 if (!strcmp(argv[0], "author-width"))
1965 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967 if (!strcmp(argv[0], "horizontal-scroll"))
1968 return parse_step(&opt_hscroll, argv[2]);
1970 if (!strcmp(argv[0], "split-view-height"))
1971 return parse_step(&opt_scale_split_view, argv[2]);
1973 if (!strcmp(argv[0], "tab-size"))
1974 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976 if (!strcmp(argv[0], "commit-encoding"))
1977 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979 config_msg = "Unknown variable name";
1980 return ERR;
1981 }
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1986 {
1987 enum request request;
1988 int keymap = -1;
1989 int key;
1991 if (argc < 3) {
1992 config_msg = "Wrong number of arguments given to bind command";
1993 return ERR;
1994 }
1996 if (set_keymap(&keymap, argv[0]) == ERR) {
1997 config_msg = "Unknown key map";
1998 return ERR;
1999 }
2001 key = get_key_value(argv[1]);
2002 if (key == ERR) {
2003 config_msg = "Unknown key";
2004 return ERR;
2005 }
2007 request = get_request(argv[2]);
2008 if (request == REQ_NONE) {
2009 static const struct enum_map obsolete[] = {
2010 ENUM_MAP("cherry-pick", REQ_NONE),
2011 ENUM_MAP("screen-resize", REQ_NONE),
2012 ENUM_MAP("tree-parent", REQ_PARENT),
2013 };
2014 int alias;
2016 if (map_enum(&alias, obsolete, argv[2])) {
2017 if (alias != REQ_NONE)
2018 add_keybinding(keymap, alias, key);
2019 config_msg = "Obsolete request name";
2020 return ERR;
2021 }
2022 }
2023 if (request == REQ_NONE && *argv[2]++ == '!')
2024 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025 if (request == REQ_NONE) {
2026 config_msg = "Unknown request name";
2027 return ERR;
2028 }
2030 add_keybinding(keymap, request, key);
2032 return OK;
2033 }
2035 static int
2036 set_option(const char *opt, char *value)
2037 {
2038 const char *argv[SIZEOF_ARG];
2039 int argc = 0;
2041 if (!argv_from_string(argv, &argc, value)) {
2042 config_msg = "Too many option arguments";
2043 return ERR;
2044 }
2046 if (!strcmp(opt, "color"))
2047 return option_color_command(argc, argv);
2049 if (!strcmp(opt, "set"))
2050 return option_set_command(argc, argv);
2052 if (!strcmp(opt, "bind"))
2053 return option_bind_command(argc, argv);
2055 config_msg = "Unknown option command";
2056 return ERR;
2057 }
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2061 {
2062 int status = OK;
2064 config_lineno++;
2065 config_msg = "Internal error";
2067 /* Check for comment markers, since read_properties() will
2068 * only ensure opt and value are split at first " \t". */
2069 optlen = strcspn(opt, "#");
2070 if (optlen == 0)
2071 return OK;
2073 if (opt[optlen] != 0) {
2074 config_msg = "No option value";
2075 status = ERR;
2077 } else {
2078 /* Look for comment endings in the value. */
2079 size_t len = strcspn(value, "#");
2081 if (len < valuelen) {
2082 valuelen = len;
2083 value[valuelen] = 0;
2084 }
2086 status = set_option(opt, value);
2087 }
2089 if (status == ERR) {
2090 warn("Error on line %d, near '%.*s': %s",
2091 config_lineno, (int) optlen, opt, config_msg);
2092 config_errors = TRUE;
2093 }
2095 /* Always keep going if errors are encountered. */
2096 return OK;
2097 }
2099 static void
2100 load_option_file(const char *path)
2101 {
2102 struct io io = {};
2104 /* It's OK that the file doesn't exist. */
2105 if (!io_open(&io, "%s", path))
2106 return;
2108 config_lineno = 0;
2109 config_errors = FALSE;
2111 if (io_load(&io, " \t", read_option) == ERR ||
2112 config_errors == TRUE)
2113 warn("Errors while loading %s.", path);
2114 }
2116 static int
2117 load_options(void)
2118 {
2119 const char *home = getenv("HOME");
2120 const char *tigrc_user = getenv("TIGRC_USER");
2121 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122 char buf[SIZEOF_STR];
2124 add_builtin_run_requests();
2126 if (!tigrc_system)
2127 tigrc_system = SYSCONFDIR "/tigrc";
2128 load_option_file(tigrc_system);
2130 if (!tigrc_user) {
2131 if (!home || !string_format(buf, "%s/.tigrc", home))
2132 return ERR;
2133 tigrc_user = buf;
2134 }
2135 load_option_file(tigrc_user);
2137 return OK;
2138 }
2141 /*
2142 * The viewer
2143 */
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views() (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF] = "";
2159 static char ref_commit[SIZEOF_REF] = "HEAD";
2160 static char ref_head[SIZEOF_REF] = "HEAD";
2161 static char ref_branch[SIZEOF_REF] = "";
2163 struct view {
2164 const char *name; /* View name */
2165 const char *cmd_env; /* Command line set via environment */
2166 const char *id; /* Points to either of ref_{head,commit,blob} */
2168 struct view_ops *ops; /* View operations */
2170 enum keymap keymap; /* What keymap does this view have */
2171 bool git_dir; /* Whether the view requires a git directory. */
2172 bool refresh; /* Whether the view supports refreshing. */
2174 char ref[SIZEOF_REF]; /* Hovered commit reference */
2175 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2177 int height, width; /* The width and height of the main window */
2178 WINDOW *win; /* The main window */
2179 WINDOW *title; /* The title window living below the main window */
2181 /* Navigation */
2182 unsigned long offset; /* Offset of the window top */
2183 unsigned long yoffset; /* Offset from the window side. */
2184 unsigned long lineno; /* Current line number */
2185 unsigned long p_offset; /* Previous offset of the window top */
2186 unsigned long p_yoffset;/* Previous offset from the window side */
2187 unsigned long p_lineno; /* Previous current line number */
2188 bool p_restore; /* Should the previous position be restored. */
2190 /* Searching */
2191 char grep[SIZEOF_STR]; /* Search string */
2192 regex_t *regex; /* Pre-compiled regexp */
2194 /* If non-NULL, points to the view that opened this view. If this view
2195 * is closed tig will switch back to the parent view. */
2196 struct view *parent;
2198 /* Buffering */
2199 size_t lines; /* Total number of lines */
2200 struct line *line; /* Line index */
2201 unsigned int digits; /* Number of digits in the lines member. */
2203 /* Drawing */
2204 struct line *curline; /* Line currently being drawn. */
2205 enum line_type curtype; /* Attribute currently used for drawing. */
2206 unsigned long col; /* Column when drawing. */
2207 bool has_scrolled; /* View was scrolled. */
2209 /* Loading */
2210 struct io io;
2211 struct io *pipe;
2212 time_t start_time;
2213 time_t update_secs;
2214 };
2216 struct view_ops {
2217 /* What type of content being displayed. Used in the title bar. */
2218 const char *type;
2219 /* Default command arguments. */
2220 const char **argv;
2221 /* Open and reads in all view content. */
2222 bool (*open)(struct view *view);
2223 /* Read one line; updates view->line. */
2224 bool (*read)(struct view *view, char *data);
2225 /* Draw one line; @lineno must be < view->height. */
2226 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2227 /* Depending on view handle a special requests. */
2228 enum request (*request)(struct view *view, enum request request, struct line *line);
2229 /* Search for regexp in a line. */
2230 bool (*grep)(struct view *view, struct line *line);
2231 /* Select line */
2232 void (*select)(struct view *view, struct line *line);
2233 /* Prepare view for loading */
2234 bool (*prepare)(struct view *view);
2235 };
2237 static struct view_ops blame_ops;
2238 static struct view_ops blob_ops;
2239 static struct view_ops diff_ops;
2240 static struct view_ops help_ops;
2241 static struct view_ops log_ops;
2242 static struct view_ops main_ops;
2243 static struct view_ops pager_ops;
2244 static struct view_ops stage_ops;
2245 static struct view_ops status_ops;
2246 static struct view_ops tree_ops;
2247 static struct view_ops branch_ops;
2249 #define VIEW_STR(name, env, ref, ops, map, git, refresh) \
2250 { name, #env, ref, ops, map, git, refresh }
2252 #define VIEW_(id, name, ops, git, refresh, ref) \
2253 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2255 static struct view views[] = {
2256 VIEW_(MAIN, "main", &main_ops, TRUE, TRUE, ref_head),
2257 VIEW_(DIFF, "diff", &diff_ops, TRUE, FALSE, ref_commit),
2258 VIEW_(LOG, "log", &log_ops, TRUE, TRUE, ref_head),
2259 VIEW_(TREE, "tree", &tree_ops, TRUE, FALSE, ref_commit),
2260 VIEW_(BLOB, "blob", &blob_ops, TRUE, FALSE, ref_blob),
2261 VIEW_(BLAME, "blame", &blame_ops, TRUE, FALSE, ref_commit),
2262 VIEW_(BRANCH, "branch", &branch_ops, TRUE, TRUE, ref_head),
2263 VIEW_(HELP, "help", &help_ops, FALSE, FALSE, ""),
2264 VIEW_(PAGER, "pager", &pager_ops, FALSE, FALSE, "stdin"),
2265 VIEW_(STATUS, "status", &status_ops, TRUE, TRUE, ""),
2266 VIEW_(STAGE, "stage", &stage_ops, TRUE, TRUE, ""),
2267 };
2269 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2271 #define foreach_view(view, i) \
2272 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2274 #define view_is_displayed(view) \
2275 (view == display[0] || view == display[1])
2278 static inline void
2279 set_view_attr(struct view *view, enum line_type type)
2280 {
2281 if (!view->curline->selected && view->curtype != type) {
2282 (void) wattrset(view->win, get_line_attr(type));
2283 wchgat(view->win, -1, 0, type, NULL);
2284 view->curtype = type;
2285 }
2286 }
2288 static int
2289 draw_chars(struct view *view, enum line_type type, const char *string,
2290 int max_len, bool use_tilde)
2291 {
2292 static char out_buffer[BUFSIZ * 2];
2293 int len = 0;
2294 int col = 0;
2295 int trimmed = FALSE;
2296 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2298 if (max_len <= 0)
2299 return 0;
2301 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2303 set_view_attr(view, type);
2304 if (len > 0) {
2305 if (opt_iconv_out != ICONV_NONE) {
2306 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2307 size_t inlen = len + 1;
2309 char *outbuf = out_buffer;
2310 size_t outlen = sizeof(out_buffer);
2312 size_t ret;
2314 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2315 if (ret != (size_t) -1) {
2316 string = out_buffer;
2317 len = sizeof(out_buffer) - outlen;
2318 }
2319 }
2321 waddnstr(view->win, string, len);
2322 }
2323 if (trimmed && use_tilde) {
2324 set_view_attr(view, LINE_DELIMITER);
2325 waddch(view->win, '~');
2326 col++;
2327 }
2329 return col;
2330 }
2332 static int
2333 draw_space(struct view *view, enum line_type type, int max, int spaces)
2334 {
2335 static char space[] = " ";
2336 int col = 0;
2338 spaces = MIN(max, spaces);
2340 while (spaces > 0) {
2341 int len = MIN(spaces, sizeof(space) - 1);
2343 col += draw_chars(view, type, space, len, FALSE);
2344 spaces -= len;
2345 }
2347 return col;
2348 }
2350 static bool
2351 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2352 {
2353 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2354 return view->width + view->yoffset <= view->col;
2355 }
2357 static bool
2358 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2359 {
2360 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2361 int max = view->width + view->yoffset - view->col;
2362 int i;
2364 if (max < size)
2365 size = max;
2367 set_view_attr(view, type);
2368 /* Using waddch() instead of waddnstr() ensures that
2369 * they'll be rendered correctly for the cursor line. */
2370 for (i = skip; i < size; i++)
2371 waddch(view->win, graphic[i]);
2373 view->col += size;
2374 if (size < max && skip <= size)
2375 waddch(view->win, ' ');
2376 view->col++;
2378 return view->width + view->yoffset <= view->col;
2379 }
2381 static bool
2382 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2383 {
2384 int max = MIN(view->width + view->yoffset - view->col, len);
2385 int col;
2387 if (text)
2388 col = draw_chars(view, type, text, max - 1, trim);
2389 else
2390 col = draw_space(view, type, max - 1, max - 1);
2392 view->col += col;
2393 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2394 return view->width + view->yoffset <= view->col;
2395 }
2397 static bool
2398 draw_date(struct view *view, struct time *time)
2399 {
2400 const char *date = mkdate(time, opt_date);
2401 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2403 return draw_field(view, LINE_DATE, date, cols, FALSE);
2404 }
2406 static bool
2407 draw_author(struct view *view, const char *author)
2408 {
2409 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2410 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2412 if (abbreviate && author)
2413 author = get_author_initials(author);
2415 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2416 }
2418 static bool
2419 draw_mode(struct view *view, mode_t mode)
2420 {
2421 const char *str;
2423 if (S_ISDIR(mode))
2424 str = "drwxr-xr-x";
2425 else if (S_ISLNK(mode))
2426 str = "lrwxrwxrwx";
2427 else if (S_ISGITLINK(mode))
2428 str = "m---------";
2429 else if (S_ISREG(mode) && mode & S_IXUSR)
2430 str = "-rwxr-xr-x";
2431 else if (S_ISREG(mode))
2432 str = "-rw-r--r--";
2433 else
2434 str = "----------";
2436 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2437 }
2439 static bool
2440 draw_lineno(struct view *view, unsigned int lineno)
2441 {
2442 char number[10];
2443 int digits3 = view->digits < 3 ? 3 : view->digits;
2444 int max = MIN(view->width + view->yoffset - view->col, digits3);
2445 char *text = NULL;
2446 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2448 lineno += view->offset + 1;
2449 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2450 static char fmt[] = "%1ld";
2452 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2453 if (string_format(number, fmt, lineno))
2454 text = number;
2455 }
2456 if (text)
2457 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2458 else
2459 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2460 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2461 }
2463 static bool
2464 draw_view_line(struct view *view, unsigned int lineno)
2465 {
2466 struct line *line;
2467 bool selected = (view->offset + lineno == view->lineno);
2469 assert(view_is_displayed(view));
2471 if (view->offset + lineno >= view->lines)
2472 return FALSE;
2474 line = &view->line[view->offset + lineno];
2476 wmove(view->win, lineno, 0);
2477 if (line->cleareol)
2478 wclrtoeol(view->win);
2479 view->col = 0;
2480 view->curline = line;
2481 view->curtype = LINE_NONE;
2482 line->selected = FALSE;
2483 line->dirty = line->cleareol = 0;
2485 if (selected) {
2486 set_view_attr(view, LINE_CURSOR);
2487 line->selected = TRUE;
2488 view->ops->select(view, line);
2489 }
2491 return view->ops->draw(view, line, lineno);
2492 }
2494 static void
2495 redraw_view_dirty(struct view *view)
2496 {
2497 bool dirty = FALSE;
2498 int lineno;
2500 for (lineno = 0; lineno < view->height; lineno++) {
2501 if (view->offset + lineno >= view->lines)
2502 break;
2503 if (!view->line[view->offset + lineno].dirty)
2504 continue;
2505 dirty = TRUE;
2506 if (!draw_view_line(view, lineno))
2507 break;
2508 }
2510 if (!dirty)
2511 return;
2512 wnoutrefresh(view->win);
2513 }
2515 static void
2516 redraw_view_from(struct view *view, int lineno)
2517 {
2518 assert(0 <= lineno && lineno < view->height);
2520 for (; lineno < view->height; lineno++) {
2521 if (!draw_view_line(view, lineno))
2522 break;
2523 }
2525 wnoutrefresh(view->win);
2526 }
2528 static void
2529 redraw_view(struct view *view)
2530 {
2531 werase(view->win);
2532 redraw_view_from(view, 0);
2533 }
2536 static void
2537 update_view_title(struct view *view)
2538 {
2539 char buf[SIZEOF_STR];
2540 char state[SIZEOF_STR];
2541 size_t bufpos = 0, statelen = 0;
2543 assert(view_is_displayed(view));
2545 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2546 unsigned int view_lines = view->offset + view->height;
2547 unsigned int lines = view->lines
2548 ? MIN(view_lines, view->lines) * 100 / view->lines
2549 : 0;
2551 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2552 view->ops->type,
2553 view->lineno + 1,
2554 view->lines,
2555 lines);
2557 }
2559 if (view->pipe) {
2560 time_t secs = time(NULL) - view->start_time;
2562 /* Three git seconds are a long time ... */
2563 if (secs > 2)
2564 string_format_from(state, &statelen, " loading %lds", secs);
2565 }
2567 string_format_from(buf, &bufpos, "[%s]", view->name);
2568 if (*view->ref && bufpos < view->width) {
2569 size_t refsize = strlen(view->ref);
2570 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2572 if (minsize < view->width)
2573 refsize = view->width - minsize + 7;
2574 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2575 }
2577 if (statelen && bufpos < view->width) {
2578 string_format_from(buf, &bufpos, "%s", state);
2579 }
2581 if (view == display[current_view])
2582 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2583 else
2584 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2586 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2587 wclrtoeol(view->title);
2588 wnoutrefresh(view->title);
2589 }
2591 static int
2592 apply_step(double step, int value)
2593 {
2594 if (step >= 1)
2595 return (int) step;
2596 value *= step + 0.01;
2597 return value ? value : 1;
2598 }
2600 static void
2601 resize_display(void)
2602 {
2603 int offset, i;
2604 struct view *base = display[0];
2605 struct view *view = display[1] ? display[1] : display[0];
2607 /* Setup window dimensions */
2609 getmaxyx(stdscr, base->height, base->width);
2611 /* Make room for the status window. */
2612 base->height -= 1;
2614 if (view != base) {
2615 /* Horizontal split. */
2616 view->width = base->width;
2617 view->height = apply_step(opt_scale_split_view, base->height);
2618 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2619 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2620 base->height -= view->height;
2622 /* Make room for the title bar. */
2623 view->height -= 1;
2624 }
2626 /* Make room for the title bar. */
2627 base->height -= 1;
2629 offset = 0;
2631 foreach_displayed_view (view, i) {
2632 if (!view->win) {
2633 view->win = newwin(view->height, 0, offset, 0);
2634 if (!view->win)
2635 die("Failed to create %s view", view->name);
2637 scrollok(view->win, FALSE);
2639 view->title = newwin(1, 0, offset + view->height, 0);
2640 if (!view->title)
2641 die("Failed to create title window");
2643 } else {
2644 wresize(view->win, view->height, view->width);
2645 mvwin(view->win, offset, 0);
2646 mvwin(view->title, offset + view->height, 0);
2647 }
2649 offset += view->height + 1;
2650 }
2651 }
2653 static void
2654 redraw_display(bool clear)
2655 {
2656 struct view *view;
2657 int i;
2659 foreach_displayed_view (view, i) {
2660 if (clear)
2661 wclear(view->win);
2662 redraw_view(view);
2663 update_view_title(view);
2664 }
2665 }
2667 static void
2668 toggle_enum_option_do(unsigned int *opt, const char *help,
2669 const struct enum_map *map, size_t size)
2670 {
2671 *opt = (*opt + 1) % size;
2672 redraw_display(FALSE);
2673 report("Displaying %s %s", enum_name(map[*opt]), help);
2674 }
2676 #define toggle_enum_option(opt, help, map) \
2677 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2679 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2680 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2682 static void
2683 toggle_view_option(bool *option, const char *help)
2684 {
2685 *option = !*option;
2686 redraw_display(FALSE);
2687 report("%sabling %s", *option ? "En" : "Dis", help);
2688 }
2690 static void
2691 open_option_menu(void)
2692 {
2693 const struct menu_item menu[] = {
2694 { '.', "line numbers", &opt_line_number },
2695 { 'D', "date display", &opt_date },
2696 { 'A', "author display", &opt_author },
2697 { 'g', "revision graph display", &opt_rev_graph },
2698 { 'F', "reference display", &opt_show_refs },
2699 { 0 }
2700 };
2701 int selected = 0;
2703 if (prompt_menu("Toggle option", menu, &selected)) {
2704 if (menu[selected].data == &opt_date)
2705 toggle_date();
2706 else if (menu[selected].data == &opt_author)
2707 toggle_author();
2708 else
2709 toggle_view_option(menu[selected].data, menu[selected].text);
2710 }
2711 }
2713 static void
2714 maximize_view(struct view *view)
2715 {
2716 memset(display, 0, sizeof(display));
2717 current_view = 0;
2718 display[current_view] = view;
2719 resize_display();
2720 redraw_display(FALSE);
2721 report("");
2722 }
2725 /*
2726 * Navigation
2727 */
2729 static bool
2730 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2731 {
2732 if (lineno >= view->lines)
2733 lineno = view->lines > 0 ? view->lines - 1 : 0;
2735 if (offset > lineno || offset + view->height <= lineno) {
2736 unsigned long half = view->height / 2;
2738 if (lineno > half)
2739 offset = lineno - half;
2740 else
2741 offset = 0;
2742 }
2744 if (offset != view->offset || lineno != view->lineno) {
2745 view->offset = offset;
2746 view->lineno = lineno;
2747 return TRUE;
2748 }
2750 return FALSE;
2751 }
2753 /* Scrolling backend */
2754 static void
2755 do_scroll_view(struct view *view, int lines)
2756 {
2757 bool redraw_current_line = FALSE;
2759 /* The rendering expects the new offset. */
2760 view->offset += lines;
2762 assert(0 <= view->offset && view->offset < view->lines);
2763 assert(lines);
2765 /* Move current line into the view. */
2766 if (view->lineno < view->offset) {
2767 view->lineno = view->offset;
2768 redraw_current_line = TRUE;
2769 } else if (view->lineno >= view->offset + view->height) {
2770 view->lineno = view->offset + view->height - 1;
2771 redraw_current_line = TRUE;
2772 }
2774 assert(view->offset <= view->lineno && view->lineno < view->lines);
2776 /* Redraw the whole screen if scrolling is pointless. */
2777 if (view->height < ABS(lines)) {
2778 redraw_view(view);
2780 } else {
2781 int line = lines > 0 ? view->height - lines : 0;
2782 int end = line + ABS(lines);
2784 scrollok(view->win, TRUE);
2785 wscrl(view->win, lines);
2786 scrollok(view->win, FALSE);
2788 while (line < end && draw_view_line(view, line))
2789 line++;
2791 if (redraw_current_line)
2792 draw_view_line(view, view->lineno - view->offset);
2793 wnoutrefresh(view->win);
2794 }
2796 view->has_scrolled = TRUE;
2797 report("");
2798 }
2800 /* Scroll frontend */
2801 static void
2802 scroll_view(struct view *view, enum request request)
2803 {
2804 int lines = 1;
2806 assert(view_is_displayed(view));
2808 switch (request) {
2809 case REQ_SCROLL_LEFT:
2810 if (view->yoffset == 0) {
2811 report("Cannot scroll beyond the first column");
2812 return;
2813 }
2814 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2815 view->yoffset = 0;
2816 else
2817 view->yoffset -= apply_step(opt_hscroll, view->width);
2818 redraw_view_from(view, 0);
2819 report("");
2820 return;
2821 case REQ_SCROLL_RIGHT:
2822 view->yoffset += apply_step(opt_hscroll, view->width);
2823 redraw_view(view);
2824 report("");
2825 return;
2826 case REQ_SCROLL_PAGE_DOWN:
2827 lines = view->height;
2828 case REQ_SCROLL_LINE_DOWN:
2829 if (view->offset + lines > view->lines)
2830 lines = view->lines - view->offset;
2832 if (lines == 0 || view->offset + view->height >= view->lines) {
2833 report("Cannot scroll beyond the last line");
2834 return;
2835 }
2836 break;
2838 case REQ_SCROLL_PAGE_UP:
2839 lines = view->height;
2840 case REQ_SCROLL_LINE_UP:
2841 if (lines > view->offset)
2842 lines = view->offset;
2844 if (lines == 0) {
2845 report("Cannot scroll beyond the first line");
2846 return;
2847 }
2849 lines = -lines;
2850 break;
2852 default:
2853 die("request %d not handled in switch", request);
2854 }
2856 do_scroll_view(view, lines);
2857 }
2859 /* Cursor moving */
2860 static void
2861 move_view(struct view *view, enum request request)
2862 {
2863 int scroll_steps = 0;
2864 int steps;
2866 switch (request) {
2867 case REQ_MOVE_FIRST_LINE:
2868 steps = -view->lineno;
2869 break;
2871 case REQ_MOVE_LAST_LINE:
2872 steps = view->lines - view->lineno - 1;
2873 break;
2875 case REQ_MOVE_PAGE_UP:
2876 steps = view->height > view->lineno
2877 ? -view->lineno : -view->height;
2878 break;
2880 case REQ_MOVE_PAGE_DOWN:
2881 steps = view->lineno + view->height >= view->lines
2882 ? view->lines - view->lineno - 1 : view->height;
2883 break;
2885 case REQ_MOVE_UP:
2886 steps = -1;
2887 break;
2889 case REQ_MOVE_DOWN:
2890 steps = 1;
2891 break;
2893 default:
2894 die("request %d not handled in switch", request);
2895 }
2897 if (steps <= 0 && view->lineno == 0) {
2898 report("Cannot move beyond the first line");
2899 return;
2901 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2902 report("Cannot move beyond the last line");
2903 return;
2904 }
2906 /* Move the current line */
2907 view->lineno += steps;
2908 assert(0 <= view->lineno && view->lineno < view->lines);
2910 /* Check whether the view needs to be scrolled */
2911 if (view->lineno < view->offset ||
2912 view->lineno >= view->offset + view->height) {
2913 scroll_steps = steps;
2914 if (steps < 0 && -steps > view->offset) {
2915 scroll_steps = -view->offset;
2917 } else if (steps > 0) {
2918 if (view->lineno == view->lines - 1 &&
2919 view->lines > view->height) {
2920 scroll_steps = view->lines - view->offset - 1;
2921 if (scroll_steps >= view->height)
2922 scroll_steps -= view->height - 1;
2923 }
2924 }
2925 }
2927 if (!view_is_displayed(view)) {
2928 view->offset += scroll_steps;
2929 assert(0 <= view->offset && view->offset < view->lines);
2930 view->ops->select(view, &view->line[view->lineno]);
2931 return;
2932 }
2934 /* Repaint the old "current" line if we be scrolling */
2935 if (ABS(steps) < view->height)
2936 draw_view_line(view, view->lineno - steps - view->offset);
2938 if (scroll_steps) {
2939 do_scroll_view(view, scroll_steps);
2940 return;
2941 }
2943 /* Draw the current line */
2944 draw_view_line(view, view->lineno - view->offset);
2946 wnoutrefresh(view->win);
2947 report("");
2948 }
2951 /*
2952 * Searching
2953 */
2955 static void search_view(struct view *view, enum request request);
2957 static bool
2958 grep_text(struct view *view, const char *text[])
2959 {
2960 regmatch_t pmatch;
2961 size_t i;
2963 for (i = 0; text[i]; i++)
2964 if (*text[i] &&
2965 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2966 return TRUE;
2967 return FALSE;
2968 }
2970 static void
2971 select_view_line(struct view *view, unsigned long lineno)
2972 {
2973 unsigned long old_lineno = view->lineno;
2974 unsigned long old_offset = view->offset;
2976 if (goto_view_line(view, view->offset, lineno)) {
2977 if (view_is_displayed(view)) {
2978 if (old_offset != view->offset) {
2979 redraw_view(view);
2980 } else {
2981 draw_view_line(view, old_lineno - view->offset);
2982 draw_view_line(view, view->lineno - view->offset);
2983 wnoutrefresh(view->win);
2984 }
2985 } else {
2986 view->ops->select(view, &view->line[view->lineno]);
2987 }
2988 }
2989 }
2991 static void
2992 find_next(struct view *view, enum request request)
2993 {
2994 unsigned long lineno = view->lineno;
2995 int direction;
2997 if (!*view->grep) {
2998 if (!*opt_search)
2999 report("No previous search");
3000 else
3001 search_view(view, request);
3002 return;
3003 }
3005 switch (request) {
3006 case REQ_SEARCH:
3007 case REQ_FIND_NEXT:
3008 direction = 1;
3009 break;
3011 case REQ_SEARCH_BACK:
3012 case REQ_FIND_PREV:
3013 direction = -1;
3014 break;
3016 default:
3017 return;
3018 }
3020 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3021 lineno += direction;
3023 /* Note, lineno is unsigned long so will wrap around in which case it
3024 * will become bigger than view->lines. */
3025 for (; lineno < view->lines; lineno += direction) {
3026 if (view->ops->grep(view, &view->line[lineno])) {
3027 select_view_line(view, lineno);
3028 report("Line %ld matches '%s'", lineno + 1, view->grep);
3029 return;
3030 }
3031 }
3033 report("No match found for '%s'", view->grep);
3034 }
3036 static void
3037 search_view(struct view *view, enum request request)
3038 {
3039 int regex_err;
3041 if (view->regex) {
3042 regfree(view->regex);
3043 *view->grep = 0;
3044 } else {
3045 view->regex = calloc(1, sizeof(*view->regex));
3046 if (!view->regex)
3047 return;
3048 }
3050 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3051 if (regex_err != 0) {
3052 char buf[SIZEOF_STR] = "unknown error";
3054 regerror(regex_err, view->regex, buf, sizeof(buf));
3055 report("Search failed: %s", buf);
3056 return;
3057 }
3059 string_copy(view->grep, opt_search);
3061 find_next(view, request);
3062 }
3064 /*
3065 * Incremental updating
3066 */
3068 static void
3069 reset_view(struct view *view)
3070 {
3071 int i;
3073 for (i = 0; i < view->lines; i++)
3074 free(view->line[i].data);
3075 free(view->line);
3077 view->p_offset = view->offset;
3078 view->p_yoffset = view->yoffset;
3079 view->p_lineno = view->lineno;
3081 view->line = NULL;
3082 view->offset = 0;
3083 view->yoffset = 0;
3084 view->lines = 0;
3085 view->lineno = 0;
3086 view->vid[0] = 0;
3087 view->update_secs = 0;
3088 }
3090 static void
3091 free_argv(const char *argv[])
3092 {
3093 int argc;
3095 for (argc = 0; argv[argc]; argc++)
3096 free((void *) argv[argc]);
3097 }
3099 static const char *
3100 format_arg(const char *name)
3101 {
3102 static struct {
3103 const char *name;
3104 size_t namelen;
3105 const char *value;
3106 const char *value_if_empty;
3107 } vars[] = {
3108 #define FORMAT_VAR(name, value, value_if_empty) \
3109 { name, STRING_SIZE(name), value, value_if_empty }
3110 FORMAT_VAR("%(directory)", opt_path, ""),
3111 FORMAT_VAR("%(file)", opt_file, ""),
3112 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3113 FORMAT_VAR("%(head)", ref_head, ""),
3114 FORMAT_VAR("%(commit)", ref_commit, ""),
3115 FORMAT_VAR("%(blob)", ref_blob, ""),
3116 FORMAT_VAR("%(branch)", ref_branch, ""),
3117 };
3118 int i;
3120 for (i = 0; i < ARRAY_SIZE(vars); i++)
3121 if (!strncmp(name, vars[i].name, vars[i].namelen))
3122 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3124 report("Unknown replacement: `%s`", name);
3125 return NULL;
3126 }
3128 static bool
3129 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3130 {
3131 char buf[SIZEOF_STR];
3132 int argc;
3133 bool noreplace = flags == FORMAT_NONE;
3135 free_argv(dst_argv);
3137 for (argc = 0; src_argv[argc]; argc++) {
3138 const char *arg = src_argv[argc];
3139 size_t bufpos = 0;
3141 while (arg) {
3142 char *next = strstr(arg, "%(");
3143 int len = next - arg;
3144 const char *value;
3146 if (!next || noreplace) {
3147 len = strlen(arg);
3148 value = "";
3150 } else {
3151 value = format_arg(next);
3153 if (!value) {
3154 return FALSE;
3155 }
3156 }
3158 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3159 return FALSE;
3161 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3162 }
3164 dst_argv[argc] = strdup(buf);
3165 if (!dst_argv[argc])
3166 break;
3167 }
3169 dst_argv[argc] = NULL;
3171 return src_argv[argc] == NULL;
3172 }
3174 static bool
3175 restore_view_position(struct view *view)
3176 {
3177 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3178 return FALSE;
3180 /* Changing the view position cancels the restoring. */
3181 /* FIXME: Changing back to the first line is not detected. */
3182 if (view->offset != 0 || view->lineno != 0) {
3183 view->p_restore = FALSE;
3184 return FALSE;
3185 }
3187 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3188 view_is_displayed(view))
3189 werase(view->win);
3191 view->yoffset = view->p_yoffset;
3192 view->p_restore = FALSE;
3194 return TRUE;
3195 }
3197 static void
3198 end_update(struct view *view, bool force)
3199 {
3200 if (!view->pipe)
3201 return;
3202 while (!view->ops->read(view, NULL))
3203 if (!force)
3204 return;
3205 if (force)
3206 io_kill(view->pipe);
3207 io_done(view->pipe);
3208 view->pipe = NULL;
3209 }
3211 static void
3212 setup_update(struct view *view, const char *vid)
3213 {
3214 reset_view(view);
3215 string_copy_rev(view->vid, vid);
3216 view->pipe = &view->io;
3217 view->start_time = time(NULL);
3218 }
3220 static bool
3221 prepare_update(struct view *view, const char *argv[], const char *dir)
3222 {
3223 if (view->pipe)
3224 end_update(view, TRUE);
3225 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3226 }
3228 static bool
3229 prepare_update_file(struct view *view, const char *name)
3230 {
3231 if (view->pipe)
3232 end_update(view, TRUE);
3233 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3234 }
3236 static bool
3237 begin_update(struct view *view, bool refresh)
3238 {
3239 if (view->pipe)
3240 end_update(view, TRUE);
3242 if (!refresh) {
3243 if (view->ops->prepare) {
3244 if (!view->ops->prepare(view))
3245 return FALSE;
3246 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3247 return FALSE;
3248 }
3250 /* Put the current ref_* value to the view title ref
3251 * member. This is needed by the blob view. Most other
3252 * views sets it automatically after loading because the
3253 * first line is a commit line. */
3254 string_copy_rev(view->ref, view->id);
3255 }
3257 if (!io_start(&view->io))
3258 return FALSE;
3260 setup_update(view, view->id);
3262 return TRUE;
3263 }
3265 static bool
3266 update_view(struct view *view)
3267 {
3268 char out_buffer[BUFSIZ * 2];
3269 char *line;
3270 /* Clear the view and redraw everything since the tree sorting
3271 * might have rearranged things. */
3272 bool redraw = view->lines == 0;
3273 bool can_read = TRUE;
3275 if (!view->pipe)
3276 return TRUE;
3278 if (!io_can_read(view->pipe)) {
3279 if (view->lines == 0 && view_is_displayed(view)) {
3280 time_t secs = time(NULL) - view->start_time;
3282 if (secs > 1 && secs > view->update_secs) {
3283 if (view->update_secs == 0)
3284 redraw_view(view);
3285 update_view_title(view);
3286 view->update_secs = secs;
3287 }
3288 }
3289 return TRUE;
3290 }
3292 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3293 if (opt_iconv_in != ICONV_NONE) {
3294 ICONV_CONST char *inbuf = line;
3295 size_t inlen = strlen(line) + 1;
3297 char *outbuf = out_buffer;
3298 size_t outlen = sizeof(out_buffer);
3300 size_t ret;
3302 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3303 if (ret != (size_t) -1)
3304 line = out_buffer;
3305 }
3307 if (!view->ops->read(view, line)) {
3308 report("Allocation failure");
3309 end_update(view, TRUE);
3310 return FALSE;
3311 }
3312 }
3314 {
3315 unsigned long lines = view->lines;
3316 int digits;
3318 for (digits = 0; lines; digits++)
3319 lines /= 10;
3321 /* Keep the displayed view in sync with line number scaling. */
3322 if (digits != view->digits) {
3323 view->digits = digits;
3324 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3325 redraw = TRUE;
3326 }
3327 }
3329 if (io_error(view->pipe)) {
3330 report("Failed to read: %s", io_strerror(view->pipe));
3331 end_update(view, TRUE);
3333 } else if (io_eof(view->pipe)) {
3334 report("");
3335 end_update(view, FALSE);
3336 }
3338 if (restore_view_position(view))
3339 redraw = TRUE;
3341 if (!view_is_displayed(view))
3342 return TRUE;
3344 if (redraw)
3345 redraw_view_from(view, 0);
3346 else
3347 redraw_view_dirty(view);
3349 /* Update the title _after_ the redraw so that if the redraw picks up a
3350 * commit reference in view->ref it'll be available here. */
3351 update_view_title(view);
3352 return TRUE;
3353 }
3355 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3357 static struct line *
3358 add_line_data(struct view *view, void *data, enum line_type type)
3359 {
3360 struct line *line;
3362 if (!realloc_lines(&view->line, view->lines, 1))
3363 return NULL;
3365 line = &view->line[view->lines++];
3366 memset(line, 0, sizeof(*line));
3367 line->type = type;
3368 line->data = data;
3369 line->dirty = 1;
3371 return line;
3372 }
3374 static struct line *
3375 add_line_text(struct view *view, const char *text, enum line_type type)
3376 {
3377 char *data = text ? strdup(text) : NULL;
3379 return data ? add_line_data(view, data, type) : NULL;
3380 }
3382 static struct line *
3383 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3384 {
3385 char buf[SIZEOF_STR];
3386 va_list args;
3388 va_start(args, fmt);
3389 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3390 buf[0] = 0;
3391 va_end(args);
3393 return buf[0] ? add_line_text(view, buf, type) : NULL;
3394 }
3396 /*
3397 * View opening
3398 */
3400 enum open_flags {
3401 OPEN_DEFAULT = 0, /* Use default view switching. */
3402 OPEN_SPLIT = 1, /* Split current view. */
3403 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3404 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3405 OPEN_PREPARED = 32, /* Open already prepared command. */
3406 };
3408 static void
3409 open_view(struct view *prev, enum request request, enum open_flags flags)
3410 {
3411 bool split = !!(flags & OPEN_SPLIT);
3412 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3413 bool nomaximize = !!(flags & OPEN_REFRESH);
3414 struct view *view = VIEW(request);
3415 int nviews = displayed_views();
3416 struct view *base_view = display[0];
3418 if (view == prev && nviews == 1 && !reload) {
3419 report("Already in %s view", view->name);
3420 return;
3421 }
3423 if (view->git_dir && !opt_git_dir[0]) {
3424 report("The %s view is disabled in pager view", view->name);
3425 return;
3426 }
3428 if (split) {
3429 display[1] = view;
3430 current_view = 1;
3431 } else if (!nomaximize) {
3432 /* Maximize the current view. */
3433 memset(display, 0, sizeof(display));
3434 current_view = 0;
3435 display[current_view] = view;
3436 }
3438 /* No parent signals that this is the first loaded view. */
3439 if (prev && view != prev) {
3440 view->parent = prev;
3441 }
3443 /* Resize the view when switching between split- and full-screen,
3444 * or when switching between two different full-screen views. */
3445 if (nviews != displayed_views() ||
3446 (nviews == 1 && base_view != display[0]))
3447 resize_display();
3449 if (view->ops->open) {
3450 if (view->pipe)
3451 end_update(view, TRUE);
3452 if (!view->ops->open(view)) {
3453 report("Failed to load %s view", view->name);
3454 return;
3455 }
3456 restore_view_position(view);
3458 } else if ((reload || strcmp(view->vid, view->id)) &&
3459 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3460 report("Failed to load %s view", view->name);
3461 return;
3462 }
3464 if (split && prev->lineno - prev->offset >= prev->height) {
3465 /* Take the title line into account. */
3466 int lines = prev->lineno - prev->offset - prev->height + 1;
3468 /* Scroll the view that was split if the current line is
3469 * outside the new limited view. */
3470 do_scroll_view(prev, lines);
3471 }
3473 if (prev && view != prev && split && view_is_displayed(prev)) {
3474 /* "Blur" the previous view. */
3475 update_view_title(prev);
3476 }
3478 if (view->pipe && view->lines == 0) {
3479 /* Clear the old view and let the incremental updating refill
3480 * the screen. */
3481 werase(view->win);
3482 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3483 report("");
3484 } else if (view_is_displayed(view)) {
3485 redraw_view(view);
3486 report("");
3487 }
3488 }
3490 static void
3491 open_external_viewer(const char *argv[], const char *dir)
3492 {
3493 def_prog_mode(); /* save current tty modes */
3494 endwin(); /* restore original tty modes */
3495 io_run_fg(argv, dir);
3496 fprintf(stderr, "Press Enter to continue");
3497 getc(opt_tty);
3498 reset_prog_mode();
3499 redraw_display(TRUE);
3500 }
3502 static void
3503 open_mergetool(const char *file)
3504 {
3505 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3507 open_external_viewer(mergetool_argv, opt_cdup);
3508 }
3510 static void
3511 open_editor(const char *file)
3512 {
3513 const char *editor_argv[] = { "vi", file, NULL };
3514 const char *editor;
3516 editor = getenv("GIT_EDITOR");
3517 if (!editor && *opt_editor)
3518 editor = opt_editor;
3519 if (!editor)
3520 editor = getenv("VISUAL");
3521 if (!editor)
3522 editor = getenv("EDITOR");
3523 if (!editor)
3524 editor = "vi";
3526 editor_argv[0] = editor;
3527 open_external_viewer(editor_argv, opt_cdup);
3528 }
3530 static void
3531 open_run_request(enum request request)
3532 {
3533 struct run_request *req = get_run_request(request);
3534 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3536 if (!req) {
3537 report("Unknown run request");
3538 return;
3539 }
3541 if (format_argv(argv, req->argv, FORMAT_ALL))
3542 open_external_viewer(argv, NULL);
3543 free_argv(argv);
3544 }
3546 /*
3547 * User request switch noodle
3548 */
3550 static int
3551 view_driver(struct view *view, enum request request)
3552 {
3553 int i;
3555 if (request == REQ_NONE)
3556 return TRUE;
3558 if (request > REQ_NONE) {
3559 open_run_request(request);
3560 /* FIXME: When all views can refresh always do this. */
3561 if (view->refresh)
3562 request = REQ_REFRESH;
3563 else
3564 return TRUE;
3565 }
3567 if (view && view->lines) {
3568 request = view->ops->request(view, request, &view->line[view->lineno]);
3569 if (request == REQ_NONE)
3570 return TRUE;
3571 }
3573 switch (request) {
3574 case REQ_MOVE_UP:
3575 case REQ_MOVE_DOWN:
3576 case REQ_MOVE_PAGE_UP:
3577 case REQ_MOVE_PAGE_DOWN:
3578 case REQ_MOVE_FIRST_LINE:
3579 case REQ_MOVE_LAST_LINE:
3580 move_view(view, request);
3581 break;
3583 case REQ_SCROLL_LEFT:
3584 case REQ_SCROLL_RIGHT:
3585 case REQ_SCROLL_LINE_DOWN:
3586 case REQ_SCROLL_LINE_UP:
3587 case REQ_SCROLL_PAGE_DOWN:
3588 case REQ_SCROLL_PAGE_UP:
3589 scroll_view(view, request);
3590 break;
3592 case REQ_VIEW_BLAME:
3593 if (!opt_file[0]) {
3594 report("No file chosen, press %s to open tree view",
3595 get_key(view->keymap, REQ_VIEW_TREE));
3596 break;
3597 }
3598 open_view(view, request, OPEN_DEFAULT);
3599 break;
3601 case REQ_VIEW_BLOB:
3602 if (!ref_blob[0]) {
3603 report("No file chosen, press %s to open tree view",
3604 get_key(view->keymap, REQ_VIEW_TREE));
3605 break;
3606 }
3607 open_view(view, request, OPEN_DEFAULT);
3608 break;
3610 case REQ_VIEW_PAGER:
3611 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3612 report("No pager content, press %s to run command from prompt",
3613 get_key(view->keymap, REQ_PROMPT));
3614 break;
3615 }
3616 open_view(view, request, OPEN_DEFAULT);
3617 break;
3619 case REQ_VIEW_STAGE:
3620 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3621 report("No stage content, press %s to open the status view and choose file",
3622 get_key(view->keymap, REQ_VIEW_STATUS));
3623 break;
3624 }
3625 open_view(view, request, OPEN_DEFAULT);
3626 break;
3628 case REQ_VIEW_STATUS:
3629 if (opt_is_inside_work_tree == FALSE) {
3630 report("The status view requires a working tree");
3631 break;
3632 }
3633 open_view(view, request, OPEN_DEFAULT);
3634 break;
3636 case REQ_VIEW_MAIN:
3637 case REQ_VIEW_DIFF:
3638 case REQ_VIEW_LOG:
3639 case REQ_VIEW_TREE:
3640 case REQ_VIEW_HELP:
3641 case REQ_VIEW_BRANCH:
3642 open_view(view, request, OPEN_DEFAULT);
3643 break;
3645 case REQ_NEXT:
3646 case REQ_PREVIOUS:
3647 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3649 if ((view == VIEW(REQ_VIEW_DIFF) &&
3650 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3651 (view == VIEW(REQ_VIEW_DIFF) &&
3652 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3653 (view == VIEW(REQ_VIEW_STAGE) &&
3654 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3655 (view == VIEW(REQ_VIEW_BLOB) &&
3656 view->parent == VIEW(REQ_VIEW_TREE)) ||
3657 (view == VIEW(REQ_VIEW_MAIN) &&
3658 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3659 int line;
3661 view = view->parent;
3662 line = view->lineno;
3663 move_view(view, request);
3664 if (view_is_displayed(view))
3665 update_view_title(view);
3666 if (line != view->lineno)
3667 view->ops->request(view, REQ_ENTER,
3668 &view->line[view->lineno]);
3670 } else {
3671 move_view(view, request);
3672 }
3673 break;
3675 case REQ_VIEW_NEXT:
3676 {
3677 int nviews = displayed_views();
3678 int next_view = (current_view + 1) % nviews;
3680 if (next_view == current_view) {
3681 report("Only one view is displayed");
3682 break;
3683 }
3685 current_view = next_view;
3686 /* Blur out the title of the previous view. */
3687 update_view_title(view);
3688 report("");
3689 break;
3690 }
3691 case REQ_REFRESH:
3692 report("Refreshing is not yet supported for the %s view", view->name);
3693 break;
3695 case REQ_MAXIMIZE:
3696 if (displayed_views() == 2)
3697 maximize_view(view);
3698 break;
3700 case REQ_OPTIONS:
3701 open_option_menu();
3702 break;
3704 case REQ_TOGGLE_LINENO:
3705 toggle_view_option(&opt_line_number, "line numbers");
3706 break;
3708 case REQ_TOGGLE_DATE:
3709 toggle_date();
3710 break;
3712 case REQ_TOGGLE_AUTHOR:
3713 toggle_author();
3714 break;
3716 case REQ_TOGGLE_REV_GRAPH:
3717 toggle_view_option(&opt_rev_graph, "revision graph display");
3718 break;
3720 case REQ_TOGGLE_REFS:
3721 toggle_view_option(&opt_show_refs, "reference display");
3722 break;
3724 case REQ_TOGGLE_SORT_FIELD:
3725 case REQ_TOGGLE_SORT_ORDER:
3726 report("Sorting is not yet supported for the %s view", view->name);
3727 break;
3729 case REQ_SEARCH:
3730 case REQ_SEARCH_BACK:
3731 search_view(view, request);
3732 break;
3734 case REQ_FIND_NEXT:
3735 case REQ_FIND_PREV:
3736 find_next(view, request);
3737 break;
3739 case REQ_STOP_LOADING:
3740 for (i = 0; i < ARRAY_SIZE(views); i++) {
3741 view = &views[i];
3742 if (view->pipe)
3743 report("Stopped loading the %s view", view->name),
3744 end_update(view, TRUE);
3745 }
3746 break;
3748 case REQ_SHOW_VERSION:
3749 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3750 return TRUE;
3752 case REQ_SCREEN_REDRAW:
3753 redraw_display(TRUE);
3754 break;
3756 case REQ_EDIT:
3757 report("Nothing to edit");
3758 break;
3760 case REQ_ENTER:
3761 report("Nothing to enter");
3762 break;
3764 case REQ_VIEW_CLOSE:
3765 /* XXX: Mark closed views by letting view->parent point to the
3766 * view itself. Parents to closed view should never be
3767 * followed. */
3768 if (view->parent &&
3769 view->parent->parent != view->parent) {
3770 maximize_view(view->parent);
3771 view->parent = view;
3772 break;
3773 }
3774 /* Fall-through */
3775 case REQ_QUIT:
3776 return FALSE;
3778 default:
3779 report("Unknown key, press %s for help",
3780 get_key(view->keymap, REQ_VIEW_HELP));
3781 return TRUE;
3782 }
3784 return TRUE;
3785 }
3788 /*
3789 * View backend utilities
3790 */
3792 enum sort_field {
3793 ORDERBY_NAME,
3794 ORDERBY_DATE,
3795 ORDERBY_AUTHOR,
3796 };
3798 struct sort_state {
3799 const enum sort_field *fields;
3800 size_t size, current;
3801 bool reverse;
3802 };
3804 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3805 #define get_sort_field(state) ((state).fields[(state).current])
3806 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3808 static void
3809 sort_view(struct view *view, enum request request, struct sort_state *state,
3810 int (*compare)(const void *, const void *))
3811 {
3812 switch (request) {
3813 case REQ_TOGGLE_SORT_FIELD:
3814 state->current = (state->current + 1) % state->size;
3815 break;
3817 case REQ_TOGGLE_SORT_ORDER:
3818 state->reverse = !state->reverse;
3819 break;
3820 default:
3821 die("Not a sort request");
3822 }
3824 qsort(view->line, view->lines, sizeof(*view->line), compare);
3825 redraw_view(view);
3826 }
3828 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3830 /* Small author cache to reduce memory consumption. It uses binary
3831 * search to lookup or find place to position new entries. No entries
3832 * are ever freed. */
3833 static const char *
3834 get_author(const char *name)
3835 {
3836 static const char **authors;
3837 static size_t authors_size;
3838 int from = 0, to = authors_size - 1;
3840 while (from <= to) {
3841 size_t pos = (to + from) / 2;
3842 int cmp = strcmp(name, authors[pos]);
3844 if (!cmp)
3845 return authors[pos];
3847 if (cmp < 0)
3848 to = pos - 1;
3849 else
3850 from = pos + 1;
3851 }
3853 if (!realloc_authors(&authors, authors_size, 1))
3854 return NULL;
3855 name = strdup(name);
3856 if (!name)
3857 return NULL;
3859 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3860 authors[from] = name;
3861 authors_size++;
3863 return name;
3864 }
3866 static void
3867 parse_timesec(struct time *time, const char *sec)
3868 {
3869 time->sec = (time_t) atol(sec);
3870 }
3872 static void
3873 parse_timezone(struct time *time, const char *zone)
3874 {
3875 long tz;
3877 tz = ('0' - zone[1]) * 60 * 60 * 10;
3878 tz += ('0' - zone[2]) * 60 * 60;
3879 tz += ('0' - zone[3]) * 60;
3880 tz += ('0' - zone[4]);
3882 if (zone[0] == '-')
3883 tz = -tz;
3885 time->tz = tz;
3886 time->sec -= tz;
3887 }
3889 /* Parse author lines where the name may be empty:
3890 * author <email@address.tld> 1138474660 +0100
3891 */
3892 static void
3893 parse_author_line(char *ident, const char **author, struct time *time)
3894 {
3895 char *nameend = strchr(ident, '<');
3896 char *emailend = strchr(ident, '>');
3898 if (nameend && emailend)
3899 *nameend = *emailend = 0;
3900 ident = chomp_string(ident);
3901 if (!*ident) {
3902 if (nameend)
3903 ident = chomp_string(nameend + 1);
3904 if (!*ident)
3905 ident = "Unknown";
3906 }
3908 *author = get_author(ident);
3910 /* Parse epoch and timezone */
3911 if (emailend && emailend[1] == ' ') {
3912 char *secs = emailend + 2;
3913 char *zone = strchr(secs, ' ');
3915 parse_timesec(time, secs);
3917 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3918 parse_timezone(time, zone + 1);
3919 }
3920 }
3922 static bool
3923 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3924 {
3925 char rev[SIZEOF_REV];
3926 const char *revlist_argv[] = {
3927 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3928 };
3929 struct menu_item *items;
3930 char text[SIZEOF_STR];
3931 bool ok = TRUE;
3932 int i;
3934 items = calloc(*parents + 1, sizeof(*items));
3935 if (!items)
3936 return FALSE;
3938 for (i = 0; i < *parents; i++) {
3939 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3940 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3941 !(items[i].text = strdup(text))) {
3942 ok = FALSE;
3943 break;
3944 }
3945 }
3947 if (ok) {
3948 *parents = 0;
3949 ok = prompt_menu("Select parent", items, parents);
3950 }
3951 for (i = 0; items[i].text; i++)
3952 free((char *) items[i].text);
3953 free(items);
3954 return ok;
3955 }
3957 static bool
3958 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3959 {
3960 char buf[SIZEOF_STR * 4];
3961 const char *revlist_argv[] = {
3962 "git", "log", "--no-color", "-1",
3963 "--pretty=format:%P", id, "--", path, NULL
3964 };
3965 int parents;
3967 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3968 (parents = strlen(buf) / 40) < 0) {
3969 report("Failed to get parent information");
3970 return FALSE;
3972 } else if (parents == 0) {
3973 if (path)
3974 report("Path '%s' does not exist in the parent", path);
3975 else
3976 report("The selected commit has no parents");
3977 return FALSE;
3978 }
3980 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3981 return FALSE;
3983 string_copy_rev(rev, &buf[41 * parents]);
3984 return TRUE;
3985 }
3987 /*
3988 * Pager backend
3989 */
3991 static bool
3992 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3993 {
3994 char text[SIZEOF_STR];
3996 if (opt_line_number && draw_lineno(view, lineno))
3997 return TRUE;
3999 string_expand(text, sizeof(text), line->data, opt_tab_size);
4000 draw_text(view, line->type, text, TRUE);
4001 return TRUE;
4002 }
4004 static bool
4005 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4006 {
4007 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4008 char ref[SIZEOF_STR];
4010 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4011 return TRUE;
4013 /* This is the only fatal call, since it can "corrupt" the buffer. */
4014 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4015 return FALSE;
4017 return TRUE;
4018 }
4020 static void
4021 add_pager_refs(struct view *view, struct line *line)
4022 {
4023 char buf[SIZEOF_STR];
4024 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4025 struct ref_list *list;
4026 size_t bufpos = 0, i;
4027 const char *sep = "Refs: ";
4028 bool is_tag = FALSE;
4030 assert(line->type == LINE_COMMIT);
4032 list = get_ref_list(commit_id);
4033 if (!list) {
4034 if (view == VIEW(REQ_VIEW_DIFF))
4035 goto try_add_describe_ref;
4036 return;
4037 }
4039 for (i = 0; i < list->size; i++) {
4040 struct ref *ref = list->refs[i];
4041 const char *fmt = ref->tag ? "%s[%s]" :
4042 ref->remote ? "%s<%s>" : "%s%s";
4044 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4045 return;
4046 sep = ", ";
4047 if (ref->tag)
4048 is_tag = TRUE;
4049 }
4051 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4052 try_add_describe_ref:
4053 /* Add <tag>-g<commit_id> "fake" reference. */
4054 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4055 return;
4056 }
4058 if (bufpos == 0)
4059 return;
4061 add_line_text(view, buf, LINE_PP_REFS);
4062 }
4064 static bool
4065 pager_read(struct view *view, char *data)
4066 {
4067 struct line *line;
4069 if (!data)
4070 return TRUE;
4072 line = add_line_text(view, data, get_line_type(data));
4073 if (!line)
4074 return FALSE;
4076 if (line->type == LINE_COMMIT &&
4077 (view == VIEW(REQ_VIEW_DIFF) ||
4078 view == VIEW(REQ_VIEW_LOG)))
4079 add_pager_refs(view, line);
4081 return TRUE;
4082 }
4084 static enum request
4085 pager_request(struct view *view, enum request request, struct line *line)
4086 {
4087 int split = 0;
4089 if (request != REQ_ENTER)
4090 return request;
4092 if (line->type == LINE_COMMIT &&
4093 (view == VIEW(REQ_VIEW_LOG) ||
4094 view == VIEW(REQ_VIEW_PAGER))) {
4095 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4096 split = 1;
4097 }
4099 /* Always scroll the view even if it was split. That way
4100 * you can use Enter to scroll through the log view and
4101 * split open each commit diff. */
4102 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4104 /* FIXME: A minor workaround. Scrolling the view will call report("")
4105 * but if we are scrolling a non-current view this won't properly
4106 * update the view title. */
4107 if (split)
4108 update_view_title(view);
4110 return REQ_NONE;
4111 }
4113 static bool
4114 pager_grep(struct view *view, struct line *line)
4115 {
4116 const char *text[] = { line->data, NULL };
4118 return grep_text(view, text);
4119 }
4121 static void
4122 pager_select(struct view *view, struct line *line)
4123 {
4124 if (line->type == LINE_COMMIT) {
4125 char *text = (char *)line->data + STRING_SIZE("commit ");
4127 if (view != VIEW(REQ_VIEW_PAGER))
4128 string_copy_rev(view->ref, text);
4129 string_copy_rev(ref_commit, text);
4130 }
4131 }
4133 static struct view_ops pager_ops = {
4134 "line",
4135 NULL,
4136 NULL,
4137 pager_read,
4138 pager_draw,
4139 pager_request,
4140 pager_grep,
4141 pager_select,
4142 };
4144 static const char *log_argv[SIZEOF_ARG] = {
4145 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4146 };
4148 static enum request
4149 log_request(struct view *view, enum request request, struct line *line)
4150 {
4151 switch (request) {
4152 case REQ_REFRESH:
4153 load_refs();
4154 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4155 return REQ_NONE;
4156 default:
4157 return pager_request(view, request, line);
4158 }
4159 }
4161 static struct view_ops log_ops = {
4162 "line",
4163 log_argv,
4164 NULL,
4165 pager_read,
4166 pager_draw,
4167 log_request,
4168 pager_grep,
4169 pager_select,
4170 };
4172 static const char *diff_argv[SIZEOF_ARG] = {
4173 "git", "show", "--pretty=fuller", "--no-color", "--root",
4174 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4175 };
4177 static struct view_ops diff_ops = {
4178 "line",
4179 diff_argv,
4180 NULL,
4181 pager_read,
4182 pager_draw,
4183 pager_request,
4184 pager_grep,
4185 pager_select,
4186 };
4188 /*
4189 * Help backend
4190 */
4192 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4194 static bool
4195 help_open_keymap_title(struct view *view, enum keymap keymap)
4196 {
4197 struct line *line;
4199 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4200 help_keymap_hidden[keymap] ? '+' : '-',
4201 enum_name(keymap_table[keymap]));
4202 if (line)
4203 line->other = keymap;
4205 return help_keymap_hidden[keymap];
4206 }
4208 static void
4209 help_open_keymap(struct view *view, enum keymap keymap)
4210 {
4211 const char *group = NULL;
4212 char buf[SIZEOF_STR];
4213 size_t bufpos;
4214 bool add_title = TRUE;
4215 int i;
4217 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4218 const char *key = NULL;
4220 if (req_info[i].request == REQ_NONE)
4221 continue;
4223 if (!req_info[i].request) {
4224 group = req_info[i].help;
4225 continue;
4226 }
4228 key = get_keys(keymap, req_info[i].request, TRUE);
4229 if (!key || !*key)
4230 continue;
4232 if (add_title && help_open_keymap_title(view, keymap))
4233 return;
4234 add_title = FALSE;
4236 if (group) {
4237 add_line_text(view, group, LINE_HELP_GROUP);
4238 group = NULL;
4239 }
4241 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4242 enum_name(req_info[i]), req_info[i].help);
4243 }
4245 group = "External commands:";
4247 for (i = 0; i < run_requests; i++) {
4248 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4249 const char *key;
4250 int argc;
4252 if (!req || req->keymap != keymap)
4253 continue;
4255 key = get_key_name(req->key);
4256 if (!*key)
4257 key = "(no key defined)";
4259 if (add_title && help_open_keymap_title(view, keymap))
4260 return;
4261 if (group) {
4262 add_line_text(view, group, LINE_HELP_GROUP);
4263 group = NULL;
4264 }
4266 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4267 if (!string_format_from(buf, &bufpos, "%s%s",
4268 argc ? " " : "", req->argv[argc]))
4269 return;
4271 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4272 }
4273 }
4275 static bool
4276 help_open(struct view *view)
4277 {
4278 enum keymap keymap;
4280 reset_view(view);
4281 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4282 add_line_text(view, "", LINE_DEFAULT);
4284 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4285 help_open_keymap(view, keymap);
4287 return TRUE;
4288 }
4290 static enum request
4291 help_request(struct view *view, enum request request, struct line *line)
4292 {
4293 switch (request) {
4294 case REQ_ENTER:
4295 if (line->type == LINE_HELP_KEYMAP) {
4296 help_keymap_hidden[line->other] =
4297 !help_keymap_hidden[line->other];
4298 view->p_restore = TRUE;
4299 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4300 }
4302 return REQ_NONE;
4303 default:
4304 return pager_request(view, request, line);
4305 }
4306 }
4308 static struct view_ops help_ops = {
4309 "line",
4310 NULL,
4311 help_open,
4312 NULL,
4313 pager_draw,
4314 help_request,
4315 pager_grep,
4316 pager_select,
4317 };
4320 /*
4321 * Tree backend
4322 */
4324 struct tree_stack_entry {
4325 struct tree_stack_entry *prev; /* Entry below this in the stack */
4326 unsigned long lineno; /* Line number to restore */
4327 char *name; /* Position of name in opt_path */
4328 };
4330 /* The top of the path stack. */
4331 static struct tree_stack_entry *tree_stack = NULL;
4332 unsigned long tree_lineno = 0;
4334 static void
4335 pop_tree_stack_entry(void)
4336 {
4337 struct tree_stack_entry *entry = tree_stack;
4339 tree_lineno = entry->lineno;
4340 entry->name[0] = 0;
4341 tree_stack = entry->prev;
4342 free(entry);
4343 }
4345 static void
4346 push_tree_stack_entry(const char *name, unsigned long lineno)
4347 {
4348 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4349 size_t pathlen = strlen(opt_path);
4351 if (!entry)
4352 return;
4354 entry->prev = tree_stack;
4355 entry->name = opt_path + pathlen;
4356 tree_stack = entry;
4358 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4359 pop_tree_stack_entry();
4360 return;
4361 }
4363 /* Move the current line to the first tree entry. */
4364 tree_lineno = 1;
4365 entry->lineno = lineno;
4366 }
4368 /* Parse output from git-ls-tree(1):
4369 *
4370 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4371 */
4373 #define SIZEOF_TREE_ATTR \
4374 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4376 #define SIZEOF_TREE_MODE \
4377 STRING_SIZE("100644 ")
4379 #define TREE_ID_OFFSET \
4380 STRING_SIZE("100644 blob ")
4382 struct tree_entry {
4383 char id[SIZEOF_REV];
4384 mode_t mode;
4385 struct time time; /* Date from the author ident. */
4386 const char *author; /* Author of the commit. */
4387 char name[1];
4388 };
4390 static const char *
4391 tree_path(const struct line *line)
4392 {
4393 return ((struct tree_entry *) line->data)->name;
4394 }
4396 static int
4397 tree_compare_entry(const struct line *line1, const struct line *line2)
4398 {
4399 if (line1->type != line2->type)
4400 return line1->type == LINE_TREE_DIR ? -1 : 1;
4401 return strcmp(tree_path(line1), tree_path(line2));
4402 }
4404 static const enum sort_field tree_sort_fields[] = {
4405 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4406 };
4407 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4409 static int
4410 tree_compare(const void *l1, const void *l2)
4411 {
4412 const struct line *line1 = (const struct line *) l1;
4413 const struct line *line2 = (const struct line *) l2;
4414 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4415 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4417 if (line1->type == LINE_TREE_HEAD)
4418 return -1;
4419 if (line2->type == LINE_TREE_HEAD)
4420 return 1;
4422 switch (get_sort_field(tree_sort_state)) {
4423 case ORDERBY_DATE:
4424 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4426 case ORDERBY_AUTHOR:
4427 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4429 case ORDERBY_NAME:
4430 default:
4431 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4432 }
4433 }
4436 static struct line *
4437 tree_entry(struct view *view, enum line_type type, const char *path,
4438 const char *mode, const char *id)
4439 {
4440 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4441 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4443 if (!entry || !line) {
4444 free(entry);
4445 return NULL;
4446 }
4448 strncpy(entry->name, path, strlen(path));
4449 if (mode)
4450 entry->mode = strtoul(mode, NULL, 8);
4451 if (id)
4452 string_copy_rev(entry->id, id);
4454 return line;
4455 }
4457 static bool
4458 tree_read_date(struct view *view, char *text, bool *read_date)
4459 {
4460 static const char *author_name;
4461 static struct time author_time;
4463 if (!text && *read_date) {
4464 *read_date = FALSE;
4465 return TRUE;
4467 } else if (!text) {
4468 char *path = *opt_path ? opt_path : ".";
4469 /* Find next entry to process */
4470 const char *log_file[] = {
4471 "git", "log", "--no-color", "--pretty=raw",
4472 "--cc", "--raw", view->id, "--", path, NULL
4473 };
4474 struct io io = {};
4476 if (!view->lines) {
4477 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4478 report("Tree is empty");
4479 return TRUE;
4480 }
4482 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4483 report("Failed to load tree data");
4484 return TRUE;
4485 }
4487 io_done(view->pipe);
4488 view->io = io;
4489 *read_date = TRUE;
4490 return FALSE;
4492 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4493 parse_author_line(text + STRING_SIZE("author "),
4494 &author_name, &author_time);
4496 } else if (*text == ':') {
4497 char *pos;
4498 size_t annotated = 1;
4499 size_t i;
4501 pos = strchr(text, '\t');
4502 if (!pos)
4503 return TRUE;
4504 text = pos + 1;
4505 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4506 text += strlen(opt_path);
4507 pos = strchr(text, '/');
4508 if (pos)
4509 *pos = 0;
4511 for (i = 1; i < view->lines; i++) {
4512 struct line *line = &view->line[i];
4513 struct tree_entry *entry = line->data;
4515 annotated += !!entry->author;
4516 if (entry->author || strcmp(entry->name, text))
4517 continue;
4519 entry->author = author_name;
4520 entry->time = author_time;
4521 line->dirty = 1;
4522 break;
4523 }
4525 if (annotated == view->lines)
4526 io_kill(view->pipe);
4527 }
4528 return TRUE;
4529 }
4531 static bool
4532 tree_read(struct view *view, char *text)
4533 {
4534 static bool read_date = FALSE;
4535 struct tree_entry *data;
4536 struct line *entry, *line;
4537 enum line_type type;
4538 size_t textlen = text ? strlen(text) : 0;
4539 char *path = text + SIZEOF_TREE_ATTR;
4541 if (read_date || !text)
4542 return tree_read_date(view, text, &read_date);
4544 if (textlen <= SIZEOF_TREE_ATTR)
4545 return FALSE;
4546 if (view->lines == 0 &&
4547 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4548 return FALSE;
4550 /* Strip the path part ... */
4551 if (*opt_path) {
4552 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4553 size_t striplen = strlen(opt_path);
4555 if (pathlen > striplen)
4556 memmove(path, path + striplen,
4557 pathlen - striplen + 1);
4559 /* Insert "link" to parent directory. */
4560 if (view->lines == 1 &&
4561 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4562 return FALSE;
4563 }
4565 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4566 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4567 if (!entry)
4568 return FALSE;
4569 data = entry->data;
4571 /* Skip "Directory ..." and ".." line. */
4572 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4573 if (tree_compare_entry(line, entry) <= 0)
4574 continue;
4576 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4578 line->data = data;
4579 line->type = type;
4580 for (; line <= entry; line++)
4581 line->dirty = line->cleareol = 1;
4582 return TRUE;
4583 }
4585 if (tree_lineno > view->lineno) {
4586 view->lineno = tree_lineno;
4587 tree_lineno = 0;
4588 }
4590 return TRUE;
4591 }
4593 static bool
4594 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4595 {
4596 struct tree_entry *entry = line->data;
4598 if (line->type == LINE_TREE_HEAD) {
4599 if (draw_text(view, line->type, "Directory path /", TRUE))
4600 return TRUE;
4601 } else {
4602 if (draw_mode(view, entry->mode))
4603 return TRUE;
4605 if (opt_author && draw_author(view, entry->author))
4606 return TRUE;
4608 if (opt_date && draw_date(view, &entry->time))
4609 return TRUE;
4610 }
4611 if (draw_text(view, line->type, entry->name, TRUE))
4612 return TRUE;
4613 return TRUE;
4614 }
4616 static void
4617 open_blob_editor()
4618 {
4619 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4620 int fd = mkstemp(file);
4622 if (fd == -1)
4623 report("Failed to create temporary file");
4624 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4625 report("Failed to save blob data to file");
4626 else
4627 open_editor(file);
4628 if (fd != -1)
4629 unlink(file);
4630 }
4632 static enum request
4633 tree_request(struct view *view, enum request request, struct line *line)
4634 {
4635 enum open_flags flags;
4637 switch (request) {
4638 case REQ_VIEW_BLAME:
4639 if (line->type != LINE_TREE_FILE) {
4640 report("Blame only supported for files");
4641 return REQ_NONE;
4642 }
4644 string_copy(opt_ref, view->vid);
4645 return request;
4647 case REQ_EDIT:
4648 if (line->type != LINE_TREE_FILE) {
4649 report("Edit only supported for files");
4650 } else if (!is_head_commit(view->vid)) {
4651 open_blob_editor();
4652 } else {
4653 open_editor(opt_file);
4654 }
4655 return REQ_NONE;
4657 case REQ_TOGGLE_SORT_FIELD:
4658 case REQ_TOGGLE_SORT_ORDER:
4659 sort_view(view, request, &tree_sort_state, tree_compare);
4660 return REQ_NONE;
4662 case REQ_PARENT:
4663 if (!*opt_path) {
4664 /* quit view if at top of tree */
4665 return REQ_VIEW_CLOSE;
4666 }
4667 /* fake 'cd ..' */
4668 line = &view->line[1];
4669 break;
4671 case REQ_ENTER:
4672 break;
4674 default:
4675 return request;
4676 }
4678 /* Cleanup the stack if the tree view is at a different tree. */
4679 while (!*opt_path && tree_stack)
4680 pop_tree_stack_entry();
4682 switch (line->type) {
4683 case LINE_TREE_DIR:
4684 /* Depending on whether it is a subdirectory or parent link
4685 * mangle the path buffer. */
4686 if (line == &view->line[1] && *opt_path) {
4687 pop_tree_stack_entry();
4689 } else {
4690 const char *basename = tree_path(line);
4692 push_tree_stack_entry(basename, view->lineno);
4693 }
4695 /* Trees and subtrees share the same ID, so they are not not
4696 * unique like blobs. */
4697 flags = OPEN_RELOAD;
4698 request = REQ_VIEW_TREE;
4699 break;
4701 case LINE_TREE_FILE:
4702 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4703 request = REQ_VIEW_BLOB;
4704 break;
4706 default:
4707 return REQ_NONE;
4708 }
4710 open_view(view, request, flags);
4711 if (request == REQ_VIEW_TREE)
4712 view->lineno = tree_lineno;
4714 return REQ_NONE;
4715 }
4717 static bool
4718 tree_grep(struct view *view, struct line *line)
4719 {
4720 struct tree_entry *entry = line->data;
4721 const char *text[] = {
4722 entry->name,
4723 opt_author ? entry->author : "",
4724 mkdate(&entry->time, opt_date),
4725 NULL
4726 };
4728 return grep_text(view, text);
4729 }
4731 static void
4732 tree_select(struct view *view, struct line *line)
4733 {
4734 struct tree_entry *entry = line->data;
4736 if (line->type == LINE_TREE_FILE) {
4737 string_copy_rev(ref_blob, entry->id);
4738 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4740 } else if (line->type != LINE_TREE_DIR) {
4741 return;
4742 }
4744 string_copy_rev(view->ref, entry->id);
4745 }
4747 static bool
4748 tree_prepare(struct view *view)
4749 {
4750 if (view->lines == 0 && opt_prefix[0]) {
4751 char *pos = opt_prefix;
4753 while (pos && *pos) {
4754 char *end = strchr(pos, '/');
4756 if (end)
4757 *end = 0;
4758 push_tree_stack_entry(pos, 0);
4759 pos = end;
4760 if (end) {
4761 *end = '/';
4762 pos++;
4763 }
4764 }
4766 } else if (strcmp(view->vid, view->id)) {
4767 opt_path[0] = 0;
4768 }
4770 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4771 }
4773 static const char *tree_argv[SIZEOF_ARG] = {
4774 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4775 };
4777 static struct view_ops tree_ops = {
4778 "file",
4779 tree_argv,
4780 NULL,
4781 tree_read,
4782 tree_draw,
4783 tree_request,
4784 tree_grep,
4785 tree_select,
4786 tree_prepare,
4787 };
4789 static bool
4790 blob_read(struct view *view, char *line)
4791 {
4792 if (!line)
4793 return TRUE;
4794 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4795 }
4797 static enum request
4798 blob_request(struct view *view, enum request request, struct line *line)
4799 {
4800 switch (request) {
4801 case REQ_EDIT:
4802 open_blob_editor();
4803 return REQ_NONE;
4804 default:
4805 return pager_request(view, request, line);
4806 }
4807 }
4809 static const char *blob_argv[SIZEOF_ARG] = {
4810 "git", "cat-file", "blob", "%(blob)", NULL
4811 };
4813 static struct view_ops blob_ops = {
4814 "line",
4815 blob_argv,
4816 NULL,
4817 blob_read,
4818 pager_draw,
4819 blob_request,
4820 pager_grep,
4821 pager_select,
4822 };
4824 /*
4825 * Blame backend
4826 *
4827 * Loading the blame view is a two phase job:
4828 *
4829 * 1. File content is read either using opt_file from the
4830 * filesystem or using git-cat-file.
4831 * 2. Then blame information is incrementally added by
4832 * reading output from git-blame.
4833 */
4835 static const char *blame_head_argv[] = {
4836 "git", "blame", "--incremental", "--", "%(file)", NULL
4837 };
4839 static const char *blame_ref_argv[] = {
4840 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4841 };
4843 static const char *blame_cat_file_argv[] = {
4844 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4845 };
4847 struct blame_commit {
4848 char id[SIZEOF_REV]; /* SHA1 ID. */
4849 char title[128]; /* First line of the commit message. */
4850 const char *author; /* Author of the commit. */
4851 struct time time; /* Date from the author ident. */
4852 char filename[128]; /* Name of file. */
4853 bool has_previous; /* Was a "previous" line detected. */
4854 };
4856 struct blame {
4857 struct blame_commit *commit;
4858 unsigned long lineno;
4859 char text[1];
4860 };
4862 static bool
4863 blame_open(struct view *view)
4864 {
4865 char path[SIZEOF_STR];
4867 if (!view->parent && *opt_prefix) {
4868 string_copy(path, opt_file);
4869 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4870 return FALSE;
4871 }
4873 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4874 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4875 return FALSE;
4876 }
4878 setup_update(view, opt_file);
4879 string_format(view->ref, "%s ...", opt_file);
4881 return TRUE;
4882 }
4884 static struct blame_commit *
4885 get_blame_commit(struct view *view, const char *id)
4886 {
4887 size_t i;
4889 for (i = 0; i < view->lines; i++) {
4890 struct blame *blame = view->line[i].data;
4892 if (!blame->commit)
4893 continue;
4895 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4896 return blame->commit;
4897 }
4899 {
4900 struct blame_commit *commit = calloc(1, sizeof(*commit));
4902 if (commit)
4903 string_ncopy(commit->id, id, SIZEOF_REV);
4904 return commit;
4905 }
4906 }
4908 static bool
4909 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4910 {
4911 const char *pos = *posref;
4913 *posref = NULL;
4914 pos = strchr(pos + 1, ' ');
4915 if (!pos || !isdigit(pos[1]))
4916 return FALSE;
4917 *number = atoi(pos + 1);
4918 if (*number < min || *number > max)
4919 return FALSE;
4921 *posref = pos;
4922 return TRUE;
4923 }
4925 static struct blame_commit *
4926 parse_blame_commit(struct view *view, const char *text, int *blamed)
4927 {
4928 struct blame_commit *commit;
4929 struct blame *blame;
4930 const char *pos = text + SIZEOF_REV - 2;
4931 size_t orig_lineno = 0;
4932 size_t lineno;
4933 size_t group;
4935 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4936 return NULL;
4938 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4939 !parse_number(&pos, &lineno, 1, view->lines) ||
4940 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4941 return NULL;
4943 commit = get_blame_commit(view, text);
4944 if (!commit)
4945 return NULL;
4947 *blamed += group;
4948 while (group--) {
4949 struct line *line = &view->line[lineno + group - 1];
4951 blame = line->data;
4952 blame->commit = commit;
4953 blame->lineno = orig_lineno + group - 1;
4954 line->dirty = 1;
4955 }
4957 return commit;
4958 }
4960 static bool
4961 blame_read_file(struct view *view, const char *line, bool *read_file)
4962 {
4963 if (!line) {
4964 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4965 struct io io = {};
4967 if (view->lines == 0 && !view->parent)
4968 die("No blame exist for %s", view->vid);
4970 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4971 report("Failed to load blame data");
4972 return TRUE;
4973 }
4975 io_done(view->pipe);
4976 view->io = io;
4977 *read_file = FALSE;
4978 return FALSE;
4980 } else {
4981 size_t linelen = strlen(line);
4982 struct blame *blame = malloc(sizeof(*blame) + linelen);
4984 if (!blame)
4985 return FALSE;
4987 blame->commit = NULL;
4988 strncpy(blame->text, line, linelen);
4989 blame->text[linelen] = 0;
4990 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4991 }
4992 }
4994 static bool
4995 match_blame_header(const char *name, char **line)
4996 {
4997 size_t namelen = strlen(name);
4998 bool matched = !strncmp(name, *line, namelen);
5000 if (matched)
5001 *line += namelen;
5003 return matched;
5004 }
5006 static bool
5007 blame_read(struct view *view, char *line)
5008 {
5009 static struct blame_commit *commit = NULL;
5010 static int blamed = 0;
5011 static bool read_file = TRUE;
5013 if (read_file)
5014 return blame_read_file(view, line, &read_file);
5016 if (!line) {
5017 /* Reset all! */
5018 commit = NULL;
5019 blamed = 0;
5020 read_file = TRUE;
5021 string_format(view->ref, "%s", view->vid);
5022 if (view_is_displayed(view)) {
5023 update_view_title(view);
5024 redraw_view_from(view, 0);
5025 }
5026 return TRUE;
5027 }
5029 if (!commit) {
5030 commit = parse_blame_commit(view, line, &blamed);
5031 string_format(view->ref, "%s %2d%%", view->vid,
5032 view->lines ? blamed * 100 / view->lines : 0);
5034 } else if (match_blame_header("author ", &line)) {
5035 commit->author = get_author(line);
5037 } else if (match_blame_header("author-time ", &line)) {
5038 parse_timesec(&commit->time, line);
5040 } else if (match_blame_header("author-tz ", &line)) {
5041 parse_timezone(&commit->time, line);
5043 } else if (match_blame_header("summary ", &line)) {
5044 string_ncopy(commit->title, line, strlen(line));
5046 } else if (match_blame_header("previous ", &line)) {
5047 commit->has_previous = TRUE;
5049 } else if (match_blame_header("filename ", &line)) {
5050 string_ncopy(commit->filename, line, strlen(line));
5051 commit = NULL;
5052 }
5054 return TRUE;
5055 }
5057 static bool
5058 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5059 {
5060 struct blame *blame = line->data;
5061 struct time *time = NULL;
5062 const char *id = NULL, *author = NULL;
5063 char text[SIZEOF_STR];
5065 if (blame->commit && *blame->commit->filename) {
5066 id = blame->commit->id;
5067 author = blame->commit->author;
5068 time = &blame->commit->time;
5069 }
5071 if (opt_date && draw_date(view, time))
5072 return TRUE;
5074 if (opt_author && draw_author(view, author))
5075 return TRUE;
5077 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5078 return TRUE;
5080 if (draw_lineno(view, lineno))
5081 return TRUE;
5083 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5084 draw_text(view, LINE_DEFAULT, text, TRUE);
5085 return TRUE;
5086 }
5088 static bool
5089 check_blame_commit(struct blame *blame, bool check_null_id)
5090 {
5091 if (!blame->commit)
5092 report("Commit data not loaded yet");
5093 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5094 report("No commit exist for the selected line");
5095 else
5096 return TRUE;
5097 return FALSE;
5098 }
5100 static void
5101 setup_blame_parent_line(struct view *view, struct blame *blame)
5102 {
5103 const char *diff_tree_argv[] = {
5104 "git", "diff-tree", "-U0", blame->commit->id,
5105 "--", blame->commit->filename, NULL
5106 };
5107 struct io io = {};
5108 int parent_lineno = -1;
5109 int blamed_lineno = -1;
5110 char *line;
5112 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5113 return;
5115 while ((line = io_get(&io, '\n', TRUE))) {
5116 if (*line == '@') {
5117 char *pos = strchr(line, '+');
5119 parent_lineno = atoi(line + 4);
5120 if (pos)
5121 blamed_lineno = atoi(pos + 1);
5123 } else if (*line == '+' && parent_lineno != -1) {
5124 if (blame->lineno == blamed_lineno - 1 &&
5125 !strcmp(blame->text, line + 1)) {
5126 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5127 break;
5128 }
5129 blamed_lineno++;
5130 }
5131 }
5133 io_done(&io);
5134 }
5136 static enum request
5137 blame_request(struct view *view, enum request request, struct line *line)
5138 {
5139 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5140 struct blame *blame = line->data;
5142 switch (request) {
5143 case REQ_VIEW_BLAME:
5144 if (check_blame_commit(blame, TRUE)) {
5145 string_copy(opt_ref, blame->commit->id);
5146 string_copy(opt_file, blame->commit->filename);
5147 if (blame->lineno)
5148 view->lineno = blame->lineno;
5149 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5150 }
5151 break;
5153 case REQ_PARENT:
5154 if (check_blame_commit(blame, TRUE) &&
5155 select_commit_parent(blame->commit->id, opt_ref,
5156 blame->commit->filename)) {
5157 string_copy(opt_file, blame->commit->filename);
5158 setup_blame_parent_line(view, blame);
5159 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5160 }
5161 break;
5163 case REQ_ENTER:
5164 if (!check_blame_commit(blame, FALSE))
5165 break;
5167 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5168 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5169 break;
5171 if (!strcmp(blame->commit->id, NULL_ID)) {
5172 struct view *diff = VIEW(REQ_VIEW_DIFF);
5173 const char *diff_index_argv[] = {
5174 "git", "diff-index", "--root", "--patch-with-stat",
5175 "-C", "-M", "HEAD", "--", view->vid, NULL
5176 };
5178 if (!blame->commit->has_previous) {
5179 diff_index_argv[1] = "diff";
5180 diff_index_argv[2] = "--no-color";
5181 diff_index_argv[6] = "--";
5182 diff_index_argv[7] = "/dev/null";
5183 }
5185 if (!prepare_update(diff, diff_index_argv, NULL)) {
5186 report("Failed to allocate diff command");
5187 break;
5188 }
5189 flags |= OPEN_PREPARED;
5190 }
5192 open_view(view, REQ_VIEW_DIFF, flags);
5193 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5194 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5195 break;
5197 default:
5198 return request;
5199 }
5201 return REQ_NONE;
5202 }
5204 static bool
5205 blame_grep(struct view *view, struct line *line)
5206 {
5207 struct blame *blame = line->data;
5208 struct blame_commit *commit = blame->commit;
5209 const char *text[] = {
5210 blame->text,
5211 commit ? commit->title : "",
5212 commit ? commit->id : "",
5213 commit && opt_author ? commit->author : "",
5214 commit ? mkdate(&commit->time, opt_date) : "",
5215 NULL
5216 };
5218 return grep_text(view, text);
5219 }
5221 static void
5222 blame_select(struct view *view, struct line *line)
5223 {
5224 struct blame *blame = line->data;
5225 struct blame_commit *commit = blame->commit;
5227 if (!commit)
5228 return;
5230 if (!strcmp(commit->id, NULL_ID))
5231 string_ncopy(ref_commit, "HEAD", 4);
5232 else
5233 string_copy_rev(ref_commit, commit->id);
5234 }
5236 static struct view_ops blame_ops = {
5237 "line",
5238 NULL,
5239 blame_open,
5240 blame_read,
5241 blame_draw,
5242 blame_request,
5243 blame_grep,
5244 blame_select,
5245 };
5247 /*
5248 * Branch backend
5249 */
5251 struct branch {
5252 const char *author; /* Author of the last commit. */
5253 struct time time; /* Date of the last activity. */
5254 const struct ref *ref; /* Name and commit ID information. */
5255 };
5257 static const struct ref branch_all;
5259 static const enum sort_field branch_sort_fields[] = {
5260 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5261 };
5262 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5264 static int
5265 branch_compare(const void *l1, const void *l2)
5266 {
5267 const struct branch *branch1 = ((const struct line *) l1)->data;
5268 const struct branch *branch2 = ((const struct line *) l2)->data;
5270 switch (get_sort_field(branch_sort_state)) {
5271 case ORDERBY_DATE:
5272 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5274 case ORDERBY_AUTHOR:
5275 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5277 case ORDERBY_NAME:
5278 default:
5279 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5280 }
5281 }
5283 static bool
5284 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5285 {
5286 struct branch *branch = line->data;
5287 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5289 if (opt_date && draw_date(view, &branch->time))
5290 return TRUE;
5292 if (opt_author && draw_author(view, branch->author))
5293 return TRUE;
5295 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5296 return TRUE;
5297 }
5299 static enum request
5300 branch_request(struct view *view, enum request request, struct line *line)
5301 {
5302 struct branch *branch = line->data;
5304 switch (request) {
5305 case REQ_REFRESH:
5306 load_refs();
5307 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5308 return REQ_NONE;
5310 case REQ_TOGGLE_SORT_FIELD:
5311 case REQ_TOGGLE_SORT_ORDER:
5312 sort_view(view, request, &branch_sort_state, branch_compare);
5313 return REQ_NONE;
5315 case REQ_ENTER:
5316 if (branch->ref == &branch_all) {
5317 const char *all_branches_argv[] = {
5318 "git", "log", "--no-color", "--pretty=raw", "--parents",
5319 "--topo-order", "--all", NULL
5320 };
5321 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5323 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5324 report("Failed to load view of all branches");
5325 return REQ_NONE;
5326 }
5327 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5328 } else {
5329 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5330 }
5331 return REQ_NONE;
5333 default:
5334 return request;
5335 }
5336 }
5338 static bool
5339 branch_read(struct view *view, char *line)
5340 {
5341 static char id[SIZEOF_REV];
5342 struct branch *reference;
5343 size_t i;
5345 if (!line)
5346 return TRUE;
5348 switch (get_line_type(line)) {
5349 case LINE_COMMIT:
5350 string_copy_rev(id, line + STRING_SIZE("commit "));
5351 return TRUE;
5353 case LINE_AUTHOR:
5354 for (i = 0, reference = NULL; i < view->lines; i++) {
5355 struct branch *branch = view->line[i].data;
5357 if (strcmp(branch->ref->id, id))
5358 continue;
5360 view->line[i].dirty = TRUE;
5361 if (reference) {
5362 branch->author = reference->author;
5363 branch->time = reference->time;
5364 continue;
5365 }
5367 parse_author_line(line + STRING_SIZE("author "),
5368 &branch->author, &branch->time);
5369 reference = branch;
5370 }
5371 return TRUE;
5373 default:
5374 return TRUE;
5375 }
5377 }
5379 static bool
5380 branch_open_visitor(void *data, const struct ref *ref)
5381 {
5382 struct view *view = data;
5383 struct branch *branch;
5385 if (ref->tag || ref->ltag || ref->remote)
5386 return TRUE;
5388 branch = calloc(1, sizeof(*branch));
5389 if (!branch)
5390 return FALSE;
5392 branch->ref = ref;
5393 return !!add_line_data(view, branch, LINE_DEFAULT);
5394 }
5396 static bool
5397 branch_open(struct view *view)
5398 {
5399 const char *branch_log[] = {
5400 "git", "log", "--no-color", "--pretty=raw",
5401 "--simplify-by-decoration", "--all", NULL
5402 };
5404 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5405 report("Failed to load branch data");
5406 return TRUE;
5407 }
5409 setup_update(view, view->id);
5410 branch_open_visitor(view, &branch_all);
5411 foreach_ref(branch_open_visitor, view);
5412 view->p_restore = TRUE;
5414 return TRUE;
5415 }
5417 static bool
5418 branch_grep(struct view *view, struct line *line)
5419 {
5420 struct branch *branch = line->data;
5421 const char *text[] = {
5422 branch->ref->name,
5423 branch->author,
5424 NULL
5425 };
5427 return grep_text(view, text);
5428 }
5430 static void
5431 branch_select(struct view *view, struct line *line)
5432 {
5433 struct branch *branch = line->data;
5435 string_copy_rev(view->ref, branch->ref->id);
5436 string_copy_rev(ref_commit, branch->ref->id);
5437 string_copy_rev(ref_head, branch->ref->id);
5438 string_copy_rev(ref_branch, branch->ref->name);
5439 }
5441 static struct view_ops branch_ops = {
5442 "branch",
5443 NULL,
5444 branch_open,
5445 branch_read,
5446 branch_draw,
5447 branch_request,
5448 branch_grep,
5449 branch_select,
5450 };
5452 /*
5453 * Status backend
5454 */
5456 struct status {
5457 char status;
5458 struct {
5459 mode_t mode;
5460 char rev[SIZEOF_REV];
5461 char name[SIZEOF_STR];
5462 } old;
5463 struct {
5464 mode_t mode;
5465 char rev[SIZEOF_REV];
5466 char name[SIZEOF_STR];
5467 } new;
5468 };
5470 static char status_onbranch[SIZEOF_STR];
5471 static struct status stage_status;
5472 static enum line_type stage_line_type;
5473 static size_t stage_chunks;
5474 static int *stage_chunk;
5476 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5478 /* This should work even for the "On branch" line. */
5479 static inline bool
5480 status_has_none(struct view *view, struct line *line)
5481 {
5482 return line < view->line + view->lines && !line[1].data;
5483 }
5485 /* Get fields from the diff line:
5486 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5487 */
5488 static inline bool
5489 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5490 {
5491 const char *old_mode = buf + 1;
5492 const char *new_mode = buf + 8;
5493 const char *old_rev = buf + 15;
5494 const char *new_rev = buf + 56;
5495 const char *status = buf + 97;
5497 if (bufsize < 98 ||
5498 old_mode[-1] != ':' ||
5499 new_mode[-1] != ' ' ||
5500 old_rev[-1] != ' ' ||
5501 new_rev[-1] != ' ' ||
5502 status[-1] != ' ')
5503 return FALSE;
5505 file->status = *status;
5507 string_copy_rev(file->old.rev, old_rev);
5508 string_copy_rev(file->new.rev, new_rev);
5510 file->old.mode = strtoul(old_mode, NULL, 8);
5511 file->new.mode = strtoul(new_mode, NULL, 8);
5513 file->old.name[0] = file->new.name[0] = 0;
5515 return TRUE;
5516 }
5518 static bool
5519 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5520 {
5521 struct status *unmerged = NULL;
5522 char *buf;
5523 struct io io = {};
5525 if (!io_run(&io, argv, opt_cdup, IO_RD))
5526 return FALSE;
5528 add_line_data(view, NULL, type);
5530 while ((buf = io_get(&io, 0, TRUE))) {
5531 struct status *file = unmerged;
5533 if (!file) {
5534 file = calloc(1, sizeof(*file));
5535 if (!file || !add_line_data(view, file, type))
5536 goto error_out;
5537 }
5539 /* Parse diff info part. */
5540 if (status) {
5541 file->status = status;
5542 if (status == 'A')
5543 string_copy(file->old.rev, NULL_ID);
5545 } else if (!file->status || file == unmerged) {
5546 if (!status_get_diff(file, buf, strlen(buf)))
5547 goto error_out;
5549 buf = io_get(&io, 0, TRUE);
5550 if (!buf)
5551 break;
5553 /* Collapse all modified entries that follow an
5554 * associated unmerged entry. */
5555 if (unmerged == file) {
5556 unmerged->status = 'U';
5557 unmerged = NULL;
5558 } else if (file->status == 'U') {
5559 unmerged = file;
5560 }
5561 }
5563 /* Grab the old name for rename/copy. */
5564 if (!*file->old.name &&
5565 (file->status == 'R' || file->status == 'C')) {
5566 string_ncopy(file->old.name, buf, strlen(buf));
5568 buf = io_get(&io, 0, TRUE);
5569 if (!buf)
5570 break;
5571 }
5573 /* git-ls-files just delivers a NUL separated list of
5574 * file names similar to the second half of the
5575 * git-diff-* output. */
5576 string_ncopy(file->new.name, buf, strlen(buf));
5577 if (!*file->old.name)
5578 string_copy(file->old.name, file->new.name);
5579 file = NULL;
5580 }
5582 if (io_error(&io)) {
5583 error_out:
5584 io_done(&io);
5585 return FALSE;
5586 }
5588 if (!view->line[view->lines - 1].data)
5589 add_line_data(view, NULL, LINE_STAT_NONE);
5591 io_done(&io);
5592 return TRUE;
5593 }
5595 /* Don't show unmerged entries in the staged section. */
5596 static const char *status_diff_index_argv[] = {
5597 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5598 "--cached", "-M", "HEAD", NULL
5599 };
5601 static const char *status_diff_files_argv[] = {
5602 "git", "diff-files", "-z", NULL
5603 };
5605 static const char *status_list_other_argv[] = {
5606 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5607 };
5609 static const char *status_list_no_head_argv[] = {
5610 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5611 };
5613 static const char *update_index_argv[] = {
5614 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5615 };
5617 /* Restore the previous line number to stay in the context or select a
5618 * line with something that can be updated. */
5619 static void
5620 status_restore(struct view *view)
5621 {
5622 if (view->p_lineno >= view->lines)
5623 view->p_lineno = view->lines - 1;
5624 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5625 view->p_lineno++;
5626 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5627 view->p_lineno--;
5629 /* If the above fails, always skip the "On branch" line. */
5630 if (view->p_lineno < view->lines)
5631 view->lineno = view->p_lineno;
5632 else
5633 view->lineno = 1;
5635 if (view->lineno < view->offset)
5636 view->offset = view->lineno;
5637 else if (view->offset + view->height <= view->lineno)
5638 view->offset = view->lineno - view->height + 1;
5640 view->p_restore = FALSE;
5641 }
5643 static void
5644 status_update_onbranch(void)
5645 {
5646 static const char *paths[][2] = {
5647 { "rebase-apply/rebasing", "Rebasing" },
5648 { "rebase-apply/applying", "Applying mailbox" },
5649 { "rebase-apply/", "Rebasing mailbox" },
5650 { "rebase-merge/interactive", "Interactive rebase" },
5651 { "rebase-merge/", "Rebase merge" },
5652 { "MERGE_HEAD", "Merging" },
5653 { "BISECT_LOG", "Bisecting" },
5654 { "HEAD", "On branch" },
5655 };
5656 char buf[SIZEOF_STR];
5657 struct stat stat;
5658 int i;
5660 if (is_initial_commit()) {
5661 string_copy(status_onbranch, "Initial commit");
5662 return;
5663 }
5665 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5666 char *head = opt_head;
5668 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5669 lstat(buf, &stat) < 0)
5670 continue;
5672 if (!*opt_head) {
5673 struct io io = {};
5675 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5676 io_read_buf(&io, buf, sizeof(buf))) {
5677 head = buf;
5678 if (!prefixcmp(head, "refs/heads/"))
5679 head += STRING_SIZE("refs/heads/");
5680 }
5681 }
5683 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5684 string_copy(status_onbranch, opt_head);
5685 return;
5686 }
5688 string_copy(status_onbranch, "Not currently on any branch");
5689 }
5691 /* First parse staged info using git-diff-index(1), then parse unstaged
5692 * info using git-diff-files(1), and finally untracked files using
5693 * git-ls-files(1). */
5694 static bool
5695 status_open(struct view *view)
5696 {
5697 reset_view(view);
5699 add_line_data(view, NULL, LINE_STAT_HEAD);
5700 status_update_onbranch();
5702 io_run_bg(update_index_argv);
5704 if (is_initial_commit()) {
5705 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5706 return FALSE;
5707 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5708 return FALSE;
5709 }
5711 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5712 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5713 return FALSE;
5715 /* Restore the exact position or use the specialized restore
5716 * mode? */
5717 if (!view->p_restore)
5718 status_restore(view);
5719 return TRUE;
5720 }
5722 static bool
5723 status_draw(struct view *view, struct line *line, unsigned int lineno)
5724 {
5725 struct status *status = line->data;
5726 enum line_type type;
5727 const char *text;
5729 if (!status) {
5730 switch (line->type) {
5731 case LINE_STAT_STAGED:
5732 type = LINE_STAT_SECTION;
5733 text = "Changes to be committed:";
5734 break;
5736 case LINE_STAT_UNSTAGED:
5737 type = LINE_STAT_SECTION;
5738 text = "Changed but not updated:";
5739 break;
5741 case LINE_STAT_UNTRACKED:
5742 type = LINE_STAT_SECTION;
5743 text = "Untracked files:";
5744 break;
5746 case LINE_STAT_NONE:
5747 type = LINE_DEFAULT;
5748 text = " (no files)";
5749 break;
5751 case LINE_STAT_HEAD:
5752 type = LINE_STAT_HEAD;
5753 text = status_onbranch;
5754 break;
5756 default:
5757 return FALSE;
5758 }
5759 } else {
5760 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5762 buf[0] = status->status;
5763 if (draw_text(view, line->type, buf, TRUE))
5764 return TRUE;
5765 type = LINE_DEFAULT;
5766 text = status->new.name;
5767 }
5769 draw_text(view, type, text, TRUE);
5770 return TRUE;
5771 }
5773 static enum request
5774 status_load_error(struct view *view, struct view *stage, const char *path)
5775 {
5776 if (displayed_views() == 2 || display[current_view] != view)
5777 maximize_view(view);
5778 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5779 return REQ_NONE;
5780 }
5782 static enum request
5783 status_enter(struct view *view, struct line *line)
5784 {
5785 struct status *status = line->data;
5786 const char *oldpath = status ? status->old.name : NULL;
5787 /* Diffs for unmerged entries are empty when passing the new
5788 * path, so leave it empty. */
5789 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5790 const char *info;
5791 enum open_flags split;
5792 struct view *stage = VIEW(REQ_VIEW_STAGE);
5794 if (line->type == LINE_STAT_NONE ||
5795 (!status && line[1].type == LINE_STAT_NONE)) {
5796 report("No file to diff");
5797 return REQ_NONE;
5798 }
5800 switch (line->type) {
5801 case LINE_STAT_STAGED:
5802 if (is_initial_commit()) {
5803 const char *no_head_diff_argv[] = {
5804 "git", "diff", "--no-color", "--patch-with-stat",
5805 "--", "/dev/null", newpath, NULL
5806 };
5808 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5809 return status_load_error(view, stage, newpath);
5810 } else {
5811 const char *index_show_argv[] = {
5812 "git", "diff-index", "--root", "--patch-with-stat",
5813 "-C", "-M", "--cached", "HEAD", "--",
5814 oldpath, newpath, NULL
5815 };
5817 if (!prepare_update(stage, index_show_argv, opt_cdup))
5818 return status_load_error(view, stage, newpath);
5819 }
5821 if (status)
5822 info = "Staged changes to %s";
5823 else
5824 info = "Staged changes";
5825 break;
5827 case LINE_STAT_UNSTAGED:
5828 {
5829 const char *files_show_argv[] = {
5830 "git", "diff-files", "--root", "--patch-with-stat",
5831 "-C", "-M", "--", oldpath, newpath, NULL
5832 };
5834 if (!prepare_update(stage, files_show_argv, opt_cdup))
5835 return status_load_error(view, stage, newpath);
5836 if (status)
5837 info = "Unstaged changes to %s";
5838 else
5839 info = "Unstaged changes";
5840 break;
5841 }
5842 case LINE_STAT_UNTRACKED:
5843 if (!newpath) {
5844 report("No file to show");
5845 return REQ_NONE;
5846 }
5848 if (!suffixcmp(status->new.name, -1, "/")) {
5849 report("Cannot display a directory");
5850 return REQ_NONE;
5851 }
5853 if (!prepare_update_file(stage, newpath))
5854 return status_load_error(view, stage, newpath);
5855 info = "Untracked file %s";
5856 break;
5858 case LINE_STAT_HEAD:
5859 return REQ_NONE;
5861 default:
5862 die("line type %d not handled in switch", line->type);
5863 }
5865 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5866 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5867 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5868 if (status) {
5869 stage_status = *status;
5870 } else {
5871 memset(&stage_status, 0, sizeof(stage_status));
5872 }
5874 stage_line_type = line->type;
5875 stage_chunks = 0;
5876 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5877 }
5879 return REQ_NONE;
5880 }
5882 static bool
5883 status_exists(struct status *status, enum line_type type)
5884 {
5885 struct view *view = VIEW(REQ_VIEW_STATUS);
5886 unsigned long lineno;
5888 for (lineno = 0; lineno < view->lines; lineno++) {
5889 struct line *line = &view->line[lineno];
5890 struct status *pos = line->data;
5892 if (line->type != type)
5893 continue;
5894 if (!pos && (!status || !status->status) && line[1].data) {
5895 select_view_line(view, lineno);
5896 return TRUE;
5897 }
5898 if (pos && !strcmp(status->new.name, pos->new.name)) {
5899 select_view_line(view, lineno);
5900 return TRUE;
5901 }
5902 }
5904 return FALSE;
5905 }
5908 static bool
5909 status_update_prepare(struct io *io, enum line_type type)
5910 {
5911 const char *staged_argv[] = {
5912 "git", "update-index", "-z", "--index-info", NULL
5913 };
5914 const char *others_argv[] = {
5915 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5916 };
5918 switch (type) {
5919 case LINE_STAT_STAGED:
5920 return io_run(io, staged_argv, opt_cdup, IO_WR);
5922 case LINE_STAT_UNSTAGED:
5923 case LINE_STAT_UNTRACKED:
5924 return io_run(io, others_argv, opt_cdup, IO_WR);
5926 default:
5927 die("line type %d not handled in switch", type);
5928 return FALSE;
5929 }
5930 }
5932 static bool
5933 status_update_write(struct io *io, struct status *status, enum line_type type)
5934 {
5935 char buf[SIZEOF_STR];
5936 size_t bufsize = 0;
5938 switch (type) {
5939 case LINE_STAT_STAGED:
5940 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5941 status->old.mode,
5942 status->old.rev,
5943 status->old.name, 0))
5944 return FALSE;
5945 break;
5947 case LINE_STAT_UNSTAGED:
5948 case LINE_STAT_UNTRACKED:
5949 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5950 return FALSE;
5951 break;
5953 default:
5954 die("line type %d not handled in switch", type);
5955 }
5957 return io_write(io, buf, bufsize);
5958 }
5960 static bool
5961 status_update_file(struct status *status, enum line_type type)
5962 {
5963 struct io io = {};
5964 bool result;
5966 if (!status_update_prepare(&io, type))
5967 return FALSE;
5969 result = status_update_write(&io, status, type);
5970 return io_done(&io) && result;
5971 }
5973 static bool
5974 status_update_files(struct view *view, struct line *line)
5975 {
5976 char buf[sizeof(view->ref)];
5977 struct io io = {};
5978 bool result = TRUE;
5979 struct line *pos = view->line + view->lines;
5980 int files = 0;
5981 int file, done;
5982 int cursor_y = -1, cursor_x = -1;
5984 if (!status_update_prepare(&io, line->type))
5985 return FALSE;
5987 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5988 files++;
5990 string_copy(buf, view->ref);
5991 getsyx(cursor_y, cursor_x);
5992 for (file = 0, done = 5; result && file < files; line++, file++) {
5993 int almost_done = file * 100 / files;
5995 if (almost_done > done) {
5996 done = almost_done;
5997 string_format(view->ref, "updating file %u of %u (%d%% done)",
5998 file, files, done);
5999 update_view_title(view);
6000 setsyx(cursor_y, cursor_x);
6001 doupdate();
6002 }
6003 result = status_update_write(&io, line->data, line->type);
6004 }
6005 string_copy(view->ref, buf);
6007 return io_done(&io) && result;
6008 }
6010 static bool
6011 status_update(struct view *view)
6012 {
6013 struct line *line = &view->line[view->lineno];
6015 assert(view->lines);
6017 if (!line->data) {
6018 /* This should work even for the "On branch" line. */
6019 if (line < view->line + view->lines && !line[1].data) {
6020 report("Nothing to update");
6021 return FALSE;
6022 }
6024 if (!status_update_files(view, line + 1)) {
6025 report("Failed to update file status");
6026 return FALSE;
6027 }
6029 } else if (!status_update_file(line->data, line->type)) {
6030 report("Failed to update file status");
6031 return FALSE;
6032 }
6034 return TRUE;
6035 }
6037 static bool
6038 status_revert(struct status *status, enum line_type type, bool has_none)
6039 {
6040 if (!status || type != LINE_STAT_UNSTAGED) {
6041 if (type == LINE_STAT_STAGED) {
6042 report("Cannot revert changes to staged files");
6043 } else if (type == LINE_STAT_UNTRACKED) {
6044 report("Cannot revert changes to untracked files");
6045 } else if (has_none) {
6046 report("Nothing to revert");
6047 } else {
6048 report("Cannot revert changes to multiple files");
6049 }
6051 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6052 char mode[10] = "100644";
6053 const char *reset_argv[] = {
6054 "git", "update-index", "--cacheinfo", mode,
6055 status->old.rev, status->old.name, NULL
6056 };
6057 const char *checkout_argv[] = {
6058 "git", "checkout", "--", status->old.name, NULL
6059 };
6061 if (status->status == 'U') {
6062 string_format(mode, "%5o", status->old.mode);
6064 if (status->old.mode == 0 && status->new.mode == 0) {
6065 reset_argv[2] = "--force-remove";
6066 reset_argv[3] = status->old.name;
6067 reset_argv[4] = NULL;
6068 }
6070 if (!io_run_fg(reset_argv, opt_cdup))
6071 return FALSE;
6072 if (status->old.mode == 0 && status->new.mode == 0)
6073 return TRUE;
6074 }
6076 return io_run_fg(checkout_argv, opt_cdup);
6077 }
6079 return FALSE;
6080 }
6082 static enum request
6083 status_request(struct view *view, enum request request, struct line *line)
6084 {
6085 struct status *status = line->data;
6087 switch (request) {
6088 case REQ_STATUS_UPDATE:
6089 if (!status_update(view))
6090 return REQ_NONE;
6091 break;
6093 case REQ_STATUS_REVERT:
6094 if (!status_revert(status, line->type, status_has_none(view, line)))
6095 return REQ_NONE;
6096 break;
6098 case REQ_STATUS_MERGE:
6099 if (!status || status->status != 'U') {
6100 report("Merging only possible for files with unmerged status ('U').");
6101 return REQ_NONE;
6102 }
6103 open_mergetool(status->new.name);
6104 break;
6106 case REQ_EDIT:
6107 if (!status)
6108 return request;
6109 if (status->status == 'D') {
6110 report("File has been deleted.");
6111 return REQ_NONE;
6112 }
6114 open_editor(status->new.name);
6115 break;
6117 case REQ_VIEW_BLAME:
6118 if (status)
6119 opt_ref[0] = 0;
6120 return request;
6122 case REQ_ENTER:
6123 /* After returning the status view has been split to
6124 * show the stage view. No further reloading is
6125 * necessary. */
6126 return status_enter(view, line);
6128 case REQ_REFRESH:
6129 /* Simply reload the view. */
6130 break;
6132 default:
6133 return request;
6134 }
6136 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6138 return REQ_NONE;
6139 }
6141 static void
6142 status_select(struct view *view, struct line *line)
6143 {
6144 struct status *status = line->data;
6145 char file[SIZEOF_STR] = "all files";
6146 const char *text;
6147 const char *key;
6149 if (status && !string_format(file, "'%s'", status->new.name))
6150 return;
6152 if (!status && line[1].type == LINE_STAT_NONE)
6153 line++;
6155 switch (line->type) {
6156 case LINE_STAT_STAGED:
6157 text = "Press %s to unstage %s for commit";
6158 break;
6160 case LINE_STAT_UNSTAGED:
6161 text = "Press %s to stage %s for commit";
6162 break;
6164 case LINE_STAT_UNTRACKED:
6165 text = "Press %s to stage %s for addition";
6166 break;
6168 case LINE_STAT_HEAD:
6169 case LINE_STAT_NONE:
6170 text = "Nothing to update";
6171 break;
6173 default:
6174 die("line type %d not handled in switch", line->type);
6175 }
6177 if (status && status->status == 'U') {
6178 text = "Press %s to resolve conflict in %s";
6179 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6181 } else {
6182 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6183 }
6185 string_format(view->ref, text, key, file);
6186 if (status)
6187 string_copy(opt_file, status->new.name);
6188 }
6190 static bool
6191 status_grep(struct view *view, struct line *line)
6192 {
6193 struct status *status = line->data;
6195 if (status) {
6196 const char buf[2] = { status->status, 0 };
6197 const char *text[] = { status->new.name, buf, NULL };
6199 return grep_text(view, text);
6200 }
6202 return FALSE;
6203 }
6205 static struct view_ops status_ops = {
6206 "file",
6207 NULL,
6208 status_open,
6209 NULL,
6210 status_draw,
6211 status_request,
6212 status_grep,
6213 status_select,
6214 };
6217 static bool
6218 stage_diff_write(struct io *io, struct line *line, struct line *end)
6219 {
6220 while (line < end) {
6221 if (!io_write(io, line->data, strlen(line->data)) ||
6222 !io_write(io, "\n", 1))
6223 return FALSE;
6224 line++;
6225 if (line->type == LINE_DIFF_CHUNK ||
6226 line->type == LINE_DIFF_HEADER)
6227 break;
6228 }
6230 return TRUE;
6231 }
6233 static struct line *
6234 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6235 {
6236 for (; view->line < line; line--)
6237 if (line->type == type)
6238 return line;
6240 return NULL;
6241 }
6243 static bool
6244 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6245 {
6246 const char *apply_argv[SIZEOF_ARG] = {
6247 "git", "apply", "--whitespace=nowarn", NULL
6248 };
6249 struct line *diff_hdr;
6250 struct io io = {};
6251 int argc = 3;
6253 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6254 if (!diff_hdr)
6255 return FALSE;
6257 if (!revert)
6258 apply_argv[argc++] = "--cached";
6259 if (revert || stage_line_type == LINE_STAT_STAGED)
6260 apply_argv[argc++] = "-R";
6261 apply_argv[argc++] = "-";
6262 apply_argv[argc++] = NULL;
6263 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6264 return FALSE;
6266 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6267 !stage_diff_write(&io, chunk, view->line + view->lines))
6268 chunk = NULL;
6270 io_done(&io);
6271 io_run_bg(update_index_argv);
6273 return chunk ? TRUE : FALSE;
6274 }
6276 static bool
6277 stage_update(struct view *view, struct line *line)
6278 {
6279 struct line *chunk = NULL;
6281 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6282 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6284 if (chunk) {
6285 if (!stage_apply_chunk(view, chunk, FALSE)) {
6286 report("Failed to apply chunk");
6287 return FALSE;
6288 }
6290 } else if (!stage_status.status) {
6291 view = VIEW(REQ_VIEW_STATUS);
6293 for (line = view->line; line < view->line + view->lines; line++)
6294 if (line->type == stage_line_type)
6295 break;
6297 if (!status_update_files(view, line + 1)) {
6298 report("Failed to update files");
6299 return FALSE;
6300 }
6302 } else if (!status_update_file(&stage_status, stage_line_type)) {
6303 report("Failed to update file");
6304 return FALSE;
6305 }
6307 return TRUE;
6308 }
6310 static bool
6311 stage_revert(struct view *view, struct line *line)
6312 {
6313 struct line *chunk = NULL;
6315 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6316 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6318 if (chunk) {
6319 if (!prompt_yesno("Are you sure you want to revert changes?"))
6320 return FALSE;
6322 if (!stage_apply_chunk(view, chunk, TRUE)) {
6323 report("Failed to revert chunk");
6324 return FALSE;
6325 }
6326 return TRUE;
6328 } else {
6329 return status_revert(stage_status.status ? &stage_status : NULL,
6330 stage_line_type, FALSE);
6331 }
6332 }
6335 static void
6336 stage_next(struct view *view, struct line *line)
6337 {
6338 int i;
6340 if (!stage_chunks) {
6341 for (line = view->line; line < view->line + view->lines; line++) {
6342 if (line->type != LINE_DIFF_CHUNK)
6343 continue;
6345 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6346 report("Allocation failure");
6347 return;
6348 }
6350 stage_chunk[stage_chunks++] = line - view->line;
6351 }
6352 }
6354 for (i = 0; i < stage_chunks; i++) {
6355 if (stage_chunk[i] > view->lineno) {
6356 do_scroll_view(view, stage_chunk[i] - view->lineno);
6357 report("Chunk %d of %d", i + 1, stage_chunks);
6358 return;
6359 }
6360 }
6362 report("No next chunk found");
6363 }
6365 static enum request
6366 stage_request(struct view *view, enum request request, struct line *line)
6367 {
6368 switch (request) {
6369 case REQ_STATUS_UPDATE:
6370 if (!stage_update(view, line))
6371 return REQ_NONE;
6372 break;
6374 case REQ_STATUS_REVERT:
6375 if (!stage_revert(view, line))
6376 return REQ_NONE;
6377 break;
6379 case REQ_STAGE_NEXT:
6380 if (stage_line_type == LINE_STAT_UNTRACKED) {
6381 report("File is untracked; press %s to add",
6382 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6383 return REQ_NONE;
6384 }
6385 stage_next(view, line);
6386 return REQ_NONE;
6388 case REQ_EDIT:
6389 if (!stage_status.new.name[0])
6390 return request;
6391 if (stage_status.status == 'D') {
6392 report("File has been deleted.");
6393 return REQ_NONE;
6394 }
6396 open_editor(stage_status.new.name);
6397 break;
6399 case REQ_REFRESH:
6400 /* Reload everything ... */
6401 break;
6403 case REQ_VIEW_BLAME:
6404 if (stage_status.new.name[0]) {
6405 string_copy(opt_file, stage_status.new.name);
6406 opt_ref[0] = 0;
6407 }
6408 return request;
6410 case REQ_ENTER:
6411 return pager_request(view, request, line);
6413 default:
6414 return request;
6415 }
6417 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6418 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6420 /* Check whether the staged entry still exists, and close the
6421 * stage view if it doesn't. */
6422 if (!status_exists(&stage_status, stage_line_type)) {
6423 status_restore(VIEW(REQ_VIEW_STATUS));
6424 return REQ_VIEW_CLOSE;
6425 }
6427 if (stage_line_type == LINE_STAT_UNTRACKED) {
6428 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6429 report("Cannot display a directory");
6430 return REQ_NONE;
6431 }
6433 if (!prepare_update_file(view, stage_status.new.name)) {
6434 report("Failed to open file: %s", strerror(errno));
6435 return REQ_NONE;
6436 }
6437 }
6438 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6440 return REQ_NONE;
6441 }
6443 static struct view_ops stage_ops = {
6444 "line",
6445 NULL,
6446 NULL,
6447 pager_read,
6448 pager_draw,
6449 stage_request,
6450 pager_grep,
6451 pager_select,
6452 };
6455 /*
6456 * Revision graph
6457 */
6459 struct commit {
6460 char id[SIZEOF_REV]; /* SHA1 ID. */
6461 char title[128]; /* First line of the commit message. */
6462 const char *author; /* Author of the commit. */
6463 struct time time; /* Date from the author ident. */
6464 struct ref_list *refs; /* Repository references. */
6465 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6466 size_t graph_size; /* The width of the graph array. */
6467 bool has_parents; /* Rewritten --parents seen. */
6468 };
6470 /* Size of rev graph with no "padding" columns */
6471 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6473 struct rev_graph {
6474 struct rev_graph *prev, *next, *parents;
6475 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6476 size_t size;
6477 struct commit *commit;
6478 size_t pos;
6479 unsigned int boundary:1;
6480 };
6482 /* Parents of the commit being visualized. */
6483 static struct rev_graph graph_parents[4];
6485 /* The current stack of revisions on the graph. */
6486 static struct rev_graph graph_stacks[4] = {
6487 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6488 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6489 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6490 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6491 };
6493 static inline bool
6494 graph_parent_is_merge(struct rev_graph *graph)
6495 {
6496 return graph->parents->size > 1;
6497 }
6499 static inline void
6500 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6501 {
6502 struct commit *commit = graph->commit;
6504 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6505 commit->graph[commit->graph_size++] = symbol;
6506 }
6508 static void
6509 clear_rev_graph(struct rev_graph *graph)
6510 {
6511 graph->boundary = 0;
6512 graph->size = graph->pos = 0;
6513 graph->commit = NULL;
6514 memset(graph->parents, 0, sizeof(*graph->parents));
6515 }
6517 static void
6518 done_rev_graph(struct rev_graph *graph)
6519 {
6520 if (graph_parent_is_merge(graph) &&
6521 graph->pos < graph->size - 1 &&
6522 graph->next->size == graph->size + graph->parents->size - 1) {
6523 size_t i = graph->pos + graph->parents->size - 1;
6525 graph->commit->graph_size = i * 2;
6526 while (i < graph->next->size - 1) {
6527 append_to_rev_graph(graph, ' ');
6528 append_to_rev_graph(graph, '\\');
6529 i++;
6530 }
6531 }
6533 clear_rev_graph(graph);
6534 }
6536 static void
6537 push_rev_graph(struct rev_graph *graph, const char *parent)
6538 {
6539 int i;
6541 /* "Collapse" duplicate parents lines.
6542 *
6543 * FIXME: This needs to also update update the drawn graph but
6544 * for now it just serves as a method for pruning graph lines. */
6545 for (i = 0; i < graph->size; i++)
6546 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6547 return;
6549 if (graph->size < SIZEOF_REVITEMS) {
6550 string_copy_rev(graph->rev[graph->size++], parent);
6551 }
6552 }
6554 static chtype
6555 get_rev_graph_symbol(struct rev_graph *graph)
6556 {
6557 chtype symbol;
6559 if (graph->boundary)
6560 symbol = REVGRAPH_BOUND;
6561 else if (graph->parents->size == 0)
6562 symbol = REVGRAPH_INIT;
6563 else if (graph_parent_is_merge(graph))
6564 symbol = REVGRAPH_MERGE;
6565 else if (graph->pos >= graph->size)
6566 symbol = REVGRAPH_BRANCH;
6567 else
6568 symbol = REVGRAPH_COMMIT;
6570 return symbol;
6571 }
6573 static void
6574 draw_rev_graph(struct rev_graph *graph)
6575 {
6576 struct rev_filler {
6577 chtype separator, line;
6578 };
6579 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6580 static struct rev_filler fillers[] = {
6581 { ' ', '|' },
6582 { '`', '.' },
6583 { '\'', ' ' },
6584 { '/', ' ' },
6585 };
6586 chtype symbol = get_rev_graph_symbol(graph);
6587 struct rev_filler *filler;
6588 size_t i;
6590 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6591 filler = &fillers[DEFAULT];
6593 for (i = 0; i < graph->pos; i++) {
6594 append_to_rev_graph(graph, filler->line);
6595 if (graph_parent_is_merge(graph->prev) &&
6596 graph->prev->pos == i)
6597 filler = &fillers[RSHARP];
6599 append_to_rev_graph(graph, filler->separator);
6600 }
6602 /* Place the symbol for this revision. */
6603 append_to_rev_graph(graph, symbol);
6605 if (graph->prev->size > graph->size)
6606 filler = &fillers[RDIAG];
6607 else
6608 filler = &fillers[DEFAULT];
6610 i++;
6612 for (; i < graph->size; i++) {
6613 append_to_rev_graph(graph, filler->separator);
6614 append_to_rev_graph(graph, filler->line);
6615 if (graph_parent_is_merge(graph->prev) &&
6616 i < graph->prev->pos + graph->parents->size)
6617 filler = &fillers[RSHARP];
6618 if (graph->prev->size > graph->size)
6619 filler = &fillers[LDIAG];
6620 }
6622 if (graph->prev->size > graph->size) {
6623 append_to_rev_graph(graph, filler->separator);
6624 if (filler->line != ' ')
6625 append_to_rev_graph(graph, filler->line);
6626 }
6627 }
6629 /* Prepare the next rev graph */
6630 static void
6631 prepare_rev_graph(struct rev_graph *graph)
6632 {
6633 size_t i;
6635 /* First, traverse all lines of revisions up to the active one. */
6636 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6637 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6638 break;
6640 push_rev_graph(graph->next, graph->rev[graph->pos]);
6641 }
6643 /* Interleave the new revision parent(s). */
6644 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6645 push_rev_graph(graph->next, graph->parents->rev[i]);
6647 /* Lastly, put any remaining revisions. */
6648 for (i = graph->pos + 1; i < graph->size; i++)
6649 push_rev_graph(graph->next, graph->rev[i]);
6650 }
6652 static void
6653 update_rev_graph(struct view *view, struct rev_graph *graph)
6654 {
6655 /* If this is the finalizing update ... */
6656 if (graph->commit)
6657 prepare_rev_graph(graph);
6659 /* Graph visualization needs a one rev look-ahead,
6660 * so the first update doesn't visualize anything. */
6661 if (!graph->prev->commit)
6662 return;
6664 if (view->lines > 2)
6665 view->line[view->lines - 3].dirty = 1;
6666 if (view->lines > 1)
6667 view->line[view->lines - 2].dirty = 1;
6668 draw_rev_graph(graph->prev);
6669 done_rev_graph(graph->prev->prev);
6670 }
6673 /*
6674 * Main view backend
6675 */
6677 static const char *main_argv[SIZEOF_ARG] = {
6678 "git", "log", "--no-color", "--pretty=raw", "--parents",
6679 "--topo-order", "%(head)", NULL
6680 };
6682 static bool
6683 main_draw(struct view *view, struct line *line, unsigned int lineno)
6684 {
6685 struct commit *commit = line->data;
6687 if (!commit->author)
6688 return FALSE;
6690 if (opt_date && draw_date(view, &commit->time))
6691 return TRUE;
6693 if (opt_author && draw_author(view, commit->author))
6694 return TRUE;
6696 if (opt_rev_graph && commit->graph_size &&
6697 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6698 return TRUE;
6700 if (opt_show_refs && commit->refs) {
6701 size_t i;
6703 for (i = 0; i < commit->refs->size; i++) {
6704 struct ref *ref = commit->refs->refs[i];
6705 enum line_type type;
6707 if (ref->head)
6708 type = LINE_MAIN_HEAD;
6709 else if (ref->ltag)
6710 type = LINE_MAIN_LOCAL_TAG;
6711 else if (ref->tag)
6712 type = LINE_MAIN_TAG;
6713 else if (ref->tracked)
6714 type = LINE_MAIN_TRACKED;
6715 else if (ref->remote)
6716 type = LINE_MAIN_REMOTE;
6717 else
6718 type = LINE_MAIN_REF;
6720 if (draw_text(view, type, "[", TRUE) ||
6721 draw_text(view, type, ref->name, TRUE) ||
6722 draw_text(view, type, "]", TRUE))
6723 return TRUE;
6725 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6726 return TRUE;
6727 }
6728 }
6730 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6731 return TRUE;
6732 }
6734 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6735 static bool
6736 main_read(struct view *view, char *line)
6737 {
6738 static struct rev_graph *graph = graph_stacks;
6739 enum line_type type;
6740 struct commit *commit;
6742 if (!line) {
6743 int i;
6745 if (!view->lines && !view->parent)
6746 die("No revisions match the given arguments.");
6747 if (view->lines > 0) {
6748 commit = view->line[view->lines - 1].data;
6749 view->line[view->lines - 1].dirty = 1;
6750 if (!commit->author) {
6751 view->lines--;
6752 free(commit);
6753 graph->commit = NULL;
6754 }
6755 }
6756 update_rev_graph(view, graph);
6758 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6759 clear_rev_graph(&graph_stacks[i]);
6760 return TRUE;
6761 }
6763 type = get_line_type(line);
6764 if (type == LINE_COMMIT) {
6765 commit = calloc(1, sizeof(struct commit));
6766 if (!commit)
6767 return FALSE;
6769 line += STRING_SIZE("commit ");
6770 if (*line == '-') {
6771 graph->boundary = 1;
6772 line++;
6773 }
6775 string_copy_rev(commit->id, line);
6776 commit->refs = get_ref_list(commit->id);
6777 graph->commit = commit;
6778 add_line_data(view, commit, LINE_MAIN_COMMIT);
6780 while ((line = strchr(line, ' '))) {
6781 line++;
6782 push_rev_graph(graph->parents, line);
6783 commit->has_parents = TRUE;
6784 }
6785 return TRUE;
6786 }
6788 if (!view->lines)
6789 return TRUE;
6790 commit = view->line[view->lines - 1].data;
6792 switch (type) {
6793 case LINE_PARENT:
6794 if (commit->has_parents)
6795 break;
6796 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6797 break;
6799 case LINE_AUTHOR:
6800 parse_author_line(line + STRING_SIZE("author "),
6801 &commit->author, &commit->time);
6802 update_rev_graph(view, graph);
6803 graph = graph->next;
6804 break;
6806 default:
6807 /* Fill in the commit title if it has not already been set. */
6808 if (commit->title[0])
6809 break;
6811 /* Require titles to start with a non-space character at the
6812 * offset used by git log. */
6813 if (strncmp(line, " ", 4))
6814 break;
6815 line += 4;
6816 /* Well, if the title starts with a whitespace character,
6817 * try to be forgiving. Otherwise we end up with no title. */
6818 while (isspace(*line))
6819 line++;
6820 if (*line == '\0')
6821 break;
6822 /* FIXME: More graceful handling of titles; append "..." to
6823 * shortened titles, etc. */
6825 string_expand(commit->title, sizeof(commit->title), line, 1);
6826 view->line[view->lines - 1].dirty = 1;
6827 }
6829 return TRUE;
6830 }
6832 static enum request
6833 main_request(struct view *view, enum request request, struct line *line)
6834 {
6835 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6837 switch (request) {
6838 case REQ_ENTER:
6839 open_view(view, REQ_VIEW_DIFF, flags);
6840 break;
6841 case REQ_REFRESH:
6842 load_refs();
6843 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6844 break;
6845 default:
6846 return request;
6847 }
6849 return REQ_NONE;
6850 }
6852 static bool
6853 grep_refs(struct ref_list *list, regex_t *regex)
6854 {
6855 regmatch_t pmatch;
6856 size_t i;
6858 if (!opt_show_refs || !list)
6859 return FALSE;
6861 for (i = 0; i < list->size; i++) {
6862 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6863 return TRUE;
6864 }
6866 return FALSE;
6867 }
6869 static bool
6870 main_grep(struct view *view, struct line *line)
6871 {
6872 struct commit *commit = line->data;
6873 const char *text[] = {
6874 commit->title,
6875 opt_author ? commit->author : "",
6876 mkdate(&commit->time, opt_date),
6877 NULL
6878 };
6880 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6881 }
6883 static void
6884 main_select(struct view *view, struct line *line)
6885 {
6886 struct commit *commit = line->data;
6888 string_copy_rev(view->ref, commit->id);
6889 string_copy_rev(ref_commit, view->ref);
6890 }
6892 static struct view_ops main_ops = {
6893 "commit",
6894 main_argv,
6895 NULL,
6896 main_read,
6897 main_draw,
6898 main_request,
6899 main_grep,
6900 main_select,
6901 };
6904 /*
6905 * Status management
6906 */
6908 /* Whether or not the curses interface has been initialized. */
6909 static bool cursed = FALSE;
6911 /* Terminal hacks and workarounds. */
6912 static bool use_scroll_redrawwin;
6913 static bool use_scroll_status_wclear;
6915 /* The status window is used for polling keystrokes. */
6916 static WINDOW *status_win;
6918 /* Reading from the prompt? */
6919 static bool input_mode = FALSE;
6921 static bool status_empty = FALSE;
6923 /* Update status and title window. */
6924 static void
6925 report(const char *msg, ...)
6926 {
6927 struct view *view = display[current_view];
6929 if (input_mode)
6930 return;
6932 if (!view) {
6933 char buf[SIZEOF_STR];
6934 va_list args;
6936 va_start(args, msg);
6937 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6938 buf[sizeof(buf) - 1] = 0;
6939 buf[sizeof(buf) - 2] = '.';
6940 buf[sizeof(buf) - 3] = '.';
6941 buf[sizeof(buf) - 4] = '.';
6942 }
6943 va_end(args);
6944 die("%s", buf);
6945 }
6947 if (!status_empty || *msg) {
6948 va_list args;
6950 va_start(args, msg);
6952 wmove(status_win, 0, 0);
6953 if (view->has_scrolled && use_scroll_status_wclear)
6954 wclear(status_win);
6955 if (*msg) {
6956 vwprintw(status_win, msg, args);
6957 status_empty = FALSE;
6958 } else {
6959 status_empty = TRUE;
6960 }
6961 wclrtoeol(status_win);
6962 wnoutrefresh(status_win);
6964 va_end(args);
6965 }
6967 update_view_title(view);
6968 }
6970 static void
6971 init_display(void)
6972 {
6973 const char *term;
6974 int x, y;
6976 /* Initialize the curses library */
6977 if (isatty(STDIN_FILENO)) {
6978 cursed = !!initscr();
6979 opt_tty = stdin;
6980 } else {
6981 /* Leave stdin and stdout alone when acting as a pager. */
6982 opt_tty = fopen("/dev/tty", "r+");
6983 if (!opt_tty)
6984 die("Failed to open /dev/tty");
6985 cursed = !!newterm(NULL, opt_tty, opt_tty);
6986 }
6988 if (!cursed)
6989 die("Failed to initialize curses");
6991 nonl(); /* Disable conversion and detect newlines from input. */
6992 cbreak(); /* Take input chars one at a time, no wait for \n */
6993 noecho(); /* Don't echo input */
6994 leaveok(stdscr, FALSE);
6996 if (has_colors())
6997 init_colors();
6999 getmaxyx(stdscr, y, x);
7000 status_win = newwin(1, 0, y - 1, 0);
7001 if (!status_win)
7002 die("Failed to create status window");
7004 /* Enable keyboard mapping */
7005 keypad(status_win, TRUE);
7006 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7008 TABSIZE = opt_tab_size;
7010 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7011 if (term && !strcmp(term, "gnome-terminal")) {
7012 /* In the gnome-terminal-emulator, the message from
7013 * scrolling up one line when impossible followed by
7014 * scrolling down one line causes corruption of the
7015 * status line. This is fixed by calling wclear. */
7016 use_scroll_status_wclear = TRUE;
7017 use_scroll_redrawwin = FALSE;
7019 } else if (term && !strcmp(term, "xrvt-xpm")) {
7020 /* No problems with full optimizations in xrvt-(unicode)
7021 * and aterm. */
7022 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7024 } else {
7025 /* When scrolling in (u)xterm the last line in the
7026 * scrolling direction will update slowly. */
7027 use_scroll_redrawwin = TRUE;
7028 use_scroll_status_wclear = FALSE;
7029 }
7030 }
7032 static int
7033 get_input(int prompt_position)
7034 {
7035 struct view *view;
7036 int i, key, cursor_y, cursor_x;
7037 bool loading = FALSE;
7039 if (prompt_position)
7040 input_mode = TRUE;
7042 while (TRUE) {
7043 foreach_view (view, i) {
7044 update_view(view);
7045 if (view_is_displayed(view) && view->has_scrolled &&
7046 use_scroll_redrawwin)
7047 redrawwin(view->win);
7048 view->has_scrolled = FALSE;
7049 if (view->pipe)
7050 loading = TRUE;
7051 }
7053 /* Update the cursor position. */
7054 if (prompt_position) {
7055 getbegyx(status_win, cursor_y, cursor_x);
7056 cursor_x = prompt_position;
7057 } else {
7058 view = display[current_view];
7059 getbegyx(view->win, cursor_y, cursor_x);
7060 cursor_x = view->width - 1;
7061 cursor_y += view->lineno - view->offset;
7062 }
7063 setsyx(cursor_y, cursor_x);
7065 /* Refresh, accept single keystroke of input */
7066 doupdate();
7067 nodelay(status_win, loading);
7068 key = wgetch(status_win);
7070 /* wgetch() with nodelay() enabled returns ERR when
7071 * there's no input. */
7072 if (key == ERR) {
7074 } else if (key == KEY_RESIZE) {
7075 int height, width;
7077 getmaxyx(stdscr, height, width);
7079 wresize(status_win, 1, width);
7080 mvwin(status_win, height - 1, 0);
7081 wnoutrefresh(status_win);
7082 resize_display();
7083 redraw_display(TRUE);
7085 } else {
7086 input_mode = FALSE;
7087 return key;
7088 }
7089 }
7090 }
7092 static char *
7093 prompt_input(const char *prompt, input_handler handler, void *data)
7094 {
7095 enum input_status status = INPUT_OK;
7096 static char buf[SIZEOF_STR];
7097 size_t pos = 0;
7099 buf[pos] = 0;
7101 while (status == INPUT_OK || status == INPUT_SKIP) {
7102 int key;
7104 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7105 wclrtoeol(status_win);
7107 key = get_input(pos + 1);
7108 switch (key) {
7109 case KEY_RETURN:
7110 case KEY_ENTER:
7111 case '\n':
7112 status = pos ? INPUT_STOP : INPUT_CANCEL;
7113 break;
7115 case KEY_BACKSPACE:
7116 if (pos > 0)
7117 buf[--pos] = 0;
7118 else
7119 status = INPUT_CANCEL;
7120 break;
7122 case KEY_ESC:
7123 status = INPUT_CANCEL;
7124 break;
7126 default:
7127 if (pos >= sizeof(buf)) {
7128 report("Input string too long");
7129 return NULL;
7130 }
7132 status = handler(data, buf, key);
7133 if (status == INPUT_OK)
7134 buf[pos++] = (char) key;
7135 }
7136 }
7138 /* Clear the status window */
7139 status_empty = FALSE;
7140 report("");
7142 if (status == INPUT_CANCEL)
7143 return NULL;
7145 buf[pos++] = 0;
7147 return buf;
7148 }
7150 static enum input_status
7151 prompt_yesno_handler(void *data, char *buf, int c)
7152 {
7153 if (c == 'y' || c == 'Y')
7154 return INPUT_STOP;
7155 if (c == 'n' || c == 'N')
7156 return INPUT_CANCEL;
7157 return INPUT_SKIP;
7158 }
7160 static bool
7161 prompt_yesno(const char *prompt)
7162 {
7163 char prompt2[SIZEOF_STR];
7165 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7166 return FALSE;
7168 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7169 }
7171 static enum input_status
7172 read_prompt_handler(void *data, char *buf, int c)
7173 {
7174 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7175 }
7177 static char *
7178 read_prompt(const char *prompt)
7179 {
7180 return prompt_input(prompt, read_prompt_handler, NULL);
7181 }
7183 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7184 {
7185 enum input_status status = INPUT_OK;
7186 int size = 0;
7188 while (items[size].text)
7189 size++;
7191 while (status == INPUT_OK) {
7192 const struct menu_item *item = &items[*selected];
7193 int key;
7194 int i;
7196 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7197 prompt, *selected + 1, size);
7198 if (item->hotkey)
7199 wprintw(status_win, "[%c] ", (char) item->hotkey);
7200 wprintw(status_win, "%s", item->text);
7201 wclrtoeol(status_win);
7203 key = get_input(COLS - 1);
7204 switch (key) {
7205 case KEY_RETURN:
7206 case KEY_ENTER:
7207 case '\n':
7208 status = INPUT_STOP;
7209 break;
7211 case KEY_LEFT:
7212 case KEY_UP:
7213 *selected = *selected - 1;
7214 if (*selected < 0)
7215 *selected = size - 1;
7216 break;
7218 case KEY_RIGHT:
7219 case KEY_DOWN:
7220 *selected = (*selected + 1) % size;
7221 break;
7223 case KEY_ESC:
7224 status = INPUT_CANCEL;
7225 break;
7227 default:
7228 for (i = 0; items[i].text; i++)
7229 if (items[i].hotkey == key) {
7230 *selected = i;
7231 status = INPUT_STOP;
7232 break;
7233 }
7234 }
7235 }
7237 /* Clear the status window */
7238 status_empty = FALSE;
7239 report("");
7241 return status != INPUT_CANCEL;
7242 }
7244 /*
7245 * Repository properties
7246 */
7248 static struct ref **refs = NULL;
7249 static size_t refs_size = 0;
7250 static struct ref *refs_head = NULL;
7252 static struct ref_list **ref_lists = NULL;
7253 static size_t ref_lists_size = 0;
7255 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7256 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7257 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7259 static int
7260 compare_refs(const void *ref1_, const void *ref2_)
7261 {
7262 const struct ref *ref1 = *(const struct ref **)ref1_;
7263 const struct ref *ref2 = *(const struct ref **)ref2_;
7265 if (ref1->tag != ref2->tag)
7266 return ref2->tag - ref1->tag;
7267 if (ref1->ltag != ref2->ltag)
7268 return ref2->ltag - ref2->ltag;
7269 if (ref1->head != ref2->head)
7270 return ref2->head - ref1->head;
7271 if (ref1->tracked != ref2->tracked)
7272 return ref2->tracked - ref1->tracked;
7273 if (ref1->remote != ref2->remote)
7274 return ref2->remote - ref1->remote;
7275 return strcmp(ref1->name, ref2->name);
7276 }
7278 static void
7279 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7280 {
7281 size_t i;
7283 for (i = 0; i < refs_size; i++)
7284 if (!visitor(data, refs[i]))
7285 break;
7286 }
7288 static struct ref *
7289 get_ref_head()
7290 {
7291 return refs_head;
7292 }
7294 static struct ref_list *
7295 get_ref_list(const char *id)
7296 {
7297 struct ref_list *list;
7298 size_t i;
7300 for (i = 0; i < ref_lists_size; i++)
7301 if (!strcmp(id, ref_lists[i]->id))
7302 return ref_lists[i];
7304 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7305 return NULL;
7306 list = calloc(1, sizeof(*list));
7307 if (!list)
7308 return NULL;
7310 for (i = 0; i < refs_size; i++) {
7311 if (!strcmp(id, refs[i]->id) &&
7312 realloc_refs_list(&list->refs, list->size, 1))
7313 list->refs[list->size++] = refs[i];
7314 }
7316 if (!list->refs) {
7317 free(list);
7318 return NULL;
7319 }
7321 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7322 ref_lists[ref_lists_size++] = list;
7323 return list;
7324 }
7326 static int
7327 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7328 {
7329 struct ref *ref = NULL;
7330 bool tag = FALSE;
7331 bool ltag = FALSE;
7332 bool remote = FALSE;
7333 bool tracked = FALSE;
7334 bool head = FALSE;
7335 int from = 0, to = refs_size - 1;
7337 if (!prefixcmp(name, "refs/tags/")) {
7338 if (!suffixcmp(name, namelen, "^{}")) {
7339 namelen -= 3;
7340 name[namelen] = 0;
7341 } else {
7342 ltag = TRUE;
7343 }
7345 tag = TRUE;
7346 namelen -= STRING_SIZE("refs/tags/");
7347 name += STRING_SIZE("refs/tags/");
7349 } else if (!prefixcmp(name, "refs/remotes/")) {
7350 remote = TRUE;
7351 namelen -= STRING_SIZE("refs/remotes/");
7352 name += STRING_SIZE("refs/remotes/");
7353 tracked = !strcmp(opt_remote, name);
7355 } else if (!prefixcmp(name, "refs/heads/")) {
7356 namelen -= STRING_SIZE("refs/heads/");
7357 name += STRING_SIZE("refs/heads/");
7358 if (!strncmp(opt_head, name, namelen))
7359 return OK;
7361 } else if (!strcmp(name, "HEAD")) {
7362 head = TRUE;
7363 if (*opt_head) {
7364 namelen = strlen(opt_head);
7365 name = opt_head;
7366 }
7367 }
7369 /* If we are reloading or it's an annotated tag, replace the
7370 * previous SHA1 with the resolved commit id; relies on the fact
7371 * git-ls-remote lists the commit id of an annotated tag right
7372 * before the commit id it points to. */
7373 while (from <= to) {
7374 size_t pos = (to + from) / 2;
7375 int cmp = strcmp(name, refs[pos]->name);
7377 if (!cmp) {
7378 ref = refs[pos];
7379 break;
7380 }
7382 if (cmp < 0)
7383 to = pos - 1;
7384 else
7385 from = pos + 1;
7386 }
7388 if (!ref) {
7389 if (!realloc_refs(&refs, refs_size, 1))
7390 return ERR;
7391 ref = calloc(1, sizeof(*ref) + namelen);
7392 if (!ref)
7393 return ERR;
7394 memmove(refs + from + 1, refs + from,
7395 (refs_size - from) * sizeof(*refs));
7396 refs[from] = ref;
7397 strncpy(ref->name, name, namelen);
7398 refs_size++;
7399 }
7401 ref->head = head;
7402 ref->tag = tag;
7403 ref->ltag = ltag;
7404 ref->remote = remote;
7405 ref->tracked = tracked;
7406 string_copy_rev(ref->id, id);
7408 if (head)
7409 refs_head = ref;
7410 return OK;
7411 }
7413 static int
7414 load_refs(void)
7415 {
7416 const char *head_argv[] = {
7417 "git", "symbolic-ref", "HEAD", NULL
7418 };
7419 static const char *ls_remote_argv[SIZEOF_ARG] = {
7420 "git", "ls-remote", opt_git_dir, NULL
7421 };
7422 static bool init = FALSE;
7423 size_t i;
7425 if (!init) {
7426 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7427 die("TIG_LS_REMOTE contains too many arguments");
7428 init = TRUE;
7429 }
7431 if (!*opt_git_dir)
7432 return OK;
7434 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7435 !prefixcmp(opt_head, "refs/heads/")) {
7436 char *offset = opt_head + STRING_SIZE("refs/heads/");
7438 memmove(opt_head, offset, strlen(offset) + 1);
7439 }
7441 refs_head = NULL;
7442 for (i = 0; i < refs_size; i++)
7443 refs[i]->id[0] = 0;
7445 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7446 return ERR;
7448 /* Update the ref lists to reflect changes. */
7449 for (i = 0; i < ref_lists_size; i++) {
7450 struct ref_list *list = ref_lists[i];
7451 size_t old, new;
7453 for (old = new = 0; old < list->size; old++)
7454 if (!strcmp(list->id, list->refs[old]->id))
7455 list->refs[new++] = list->refs[old];
7456 list->size = new;
7457 }
7459 return OK;
7460 }
7462 static void
7463 set_remote_branch(const char *name, const char *value, size_t valuelen)
7464 {
7465 if (!strcmp(name, ".remote")) {
7466 string_ncopy(opt_remote, value, valuelen);
7468 } else if (*opt_remote && !strcmp(name, ".merge")) {
7469 size_t from = strlen(opt_remote);
7471 if (!prefixcmp(value, "refs/heads/"))
7472 value += STRING_SIZE("refs/heads/");
7474 if (!string_format_from(opt_remote, &from, "/%s", value))
7475 opt_remote[0] = 0;
7476 }
7477 }
7479 static void
7480 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7481 {
7482 const char *argv[SIZEOF_ARG] = { name, "=" };
7483 int argc = 1 + (cmd == option_set_command);
7484 int error = ERR;
7486 if (!argv_from_string(argv, &argc, value))
7487 config_msg = "Too many option arguments";
7488 else
7489 error = cmd(argc, argv);
7491 if (error == ERR)
7492 warn("Option 'tig.%s': %s", name, config_msg);
7493 }
7495 static bool
7496 set_environment_variable(const char *name, const char *value)
7497 {
7498 size_t len = strlen(name) + 1 + strlen(value) + 1;
7499 char *env = malloc(len);
7501 if (env &&
7502 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7503 putenv(env) == 0)
7504 return TRUE;
7505 free(env);
7506 return FALSE;
7507 }
7509 static void
7510 set_work_tree(const char *value)
7511 {
7512 char cwd[SIZEOF_STR];
7514 if (!getcwd(cwd, sizeof(cwd)))
7515 die("Failed to get cwd path: %s", strerror(errno));
7516 if (chdir(opt_git_dir) < 0)
7517 die("Failed to chdir(%s): %s", strerror(errno));
7518 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7519 die("Failed to get git path: %s", strerror(errno));
7520 if (chdir(cwd) < 0)
7521 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7522 if (chdir(value) < 0)
7523 die("Failed to chdir(%s): %s", value, strerror(errno));
7524 if (!getcwd(cwd, sizeof(cwd)))
7525 die("Failed to get cwd path: %s", strerror(errno));
7526 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7527 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7528 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7529 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7530 opt_is_inside_work_tree = TRUE;
7531 }
7533 static int
7534 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7535 {
7536 if (!strcmp(name, "i18n.commitencoding"))
7537 string_ncopy(opt_encoding, value, valuelen);
7539 else if (!strcmp(name, "core.editor"))
7540 string_ncopy(opt_editor, value, valuelen);
7542 else if (!strcmp(name, "core.worktree"))
7543 set_work_tree(value);
7545 else if (!prefixcmp(name, "tig.color."))
7546 set_repo_config_option(name + 10, value, option_color_command);
7548 else if (!prefixcmp(name, "tig.bind."))
7549 set_repo_config_option(name + 9, value, option_bind_command);
7551 else if (!prefixcmp(name, "tig."))
7552 set_repo_config_option(name + 4, value, option_set_command);
7554 else if (*opt_head && !prefixcmp(name, "branch.") &&
7555 !strncmp(name + 7, opt_head, strlen(opt_head)))
7556 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7558 return OK;
7559 }
7561 static int
7562 load_git_config(void)
7563 {
7564 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7566 return io_run_load(config_list_argv, "=", read_repo_config_option);
7567 }
7569 static int
7570 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7571 {
7572 if (!opt_git_dir[0]) {
7573 string_ncopy(opt_git_dir, name, namelen);
7575 } else if (opt_is_inside_work_tree == -1) {
7576 /* This can be 3 different values depending on the
7577 * version of git being used. If git-rev-parse does not
7578 * understand --is-inside-work-tree it will simply echo
7579 * the option else either "true" or "false" is printed.
7580 * Default to true for the unknown case. */
7581 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7583 } else if (*name == '.') {
7584 string_ncopy(opt_cdup, name, namelen);
7586 } else {
7587 string_ncopy(opt_prefix, name, namelen);
7588 }
7590 return OK;
7591 }
7593 static int
7594 load_repo_info(void)
7595 {
7596 const char *rev_parse_argv[] = {
7597 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7598 "--show-cdup", "--show-prefix", NULL
7599 };
7601 return io_run_load(rev_parse_argv, "=", read_repo_info);
7602 }
7605 /*
7606 * Main
7607 */
7609 static const char usage[] =
7610 "tig " TIG_VERSION " (" __DATE__ ")\n"
7611 "\n"
7612 "Usage: tig [options] [revs] [--] [paths]\n"
7613 " or: tig show [options] [revs] [--] [paths]\n"
7614 " or: tig blame [rev] path\n"
7615 " or: tig status\n"
7616 " or: tig < [git command output]\n"
7617 "\n"
7618 "Options:\n"
7619 " -v, --version Show version and exit\n"
7620 " -h, --help Show help message and exit";
7622 static void __NORETURN
7623 quit(int sig)
7624 {
7625 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7626 if (cursed)
7627 endwin();
7628 exit(0);
7629 }
7631 static void __NORETURN
7632 die(const char *err, ...)
7633 {
7634 va_list args;
7636 endwin();
7638 va_start(args, err);
7639 fputs("tig: ", stderr);
7640 vfprintf(stderr, err, args);
7641 fputs("\n", stderr);
7642 va_end(args);
7644 exit(1);
7645 }
7647 static void
7648 warn(const char *msg, ...)
7649 {
7650 va_list args;
7652 va_start(args, msg);
7653 fputs("tig warning: ", stderr);
7654 vfprintf(stderr, msg, args);
7655 fputs("\n", stderr);
7656 va_end(args);
7657 }
7659 static enum request
7660 parse_options(int argc, const char *argv[])
7661 {
7662 enum request request = REQ_VIEW_MAIN;
7663 const char *subcommand;
7664 bool seen_dashdash = FALSE;
7665 /* XXX: This is vulnerable to the user overriding options
7666 * required for the main view parser. */
7667 const char *custom_argv[SIZEOF_ARG] = {
7668 "git", "log", "--no-color", "--pretty=raw", "--parents",
7669 "--topo-order", NULL
7670 };
7671 int i, j = 6;
7673 if (!isatty(STDIN_FILENO)) {
7674 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7675 return REQ_VIEW_PAGER;
7676 }
7678 if (argc <= 1)
7679 return REQ_NONE;
7681 subcommand = argv[1];
7682 if (!strcmp(subcommand, "status")) {
7683 if (argc > 2)
7684 warn("ignoring arguments after `%s'", subcommand);
7685 return REQ_VIEW_STATUS;
7687 } else if (!strcmp(subcommand, "blame")) {
7688 if (argc <= 2 || argc > 4)
7689 die("invalid number of options to blame\n\n%s", usage);
7691 i = 2;
7692 if (argc == 4) {
7693 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7694 i++;
7695 }
7697 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7698 return REQ_VIEW_BLAME;
7700 } else if (!strcmp(subcommand, "show")) {
7701 request = REQ_VIEW_DIFF;
7703 } else {
7704 subcommand = NULL;
7705 }
7707 if (subcommand) {
7708 custom_argv[1] = subcommand;
7709 j = 2;
7710 }
7712 for (i = 1 + !!subcommand; i < argc; i++) {
7713 const char *opt = argv[i];
7715 if (seen_dashdash || !strcmp(opt, "--")) {
7716 seen_dashdash = TRUE;
7718 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7719 printf("tig version %s\n", TIG_VERSION);
7720 quit(0);
7722 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7723 printf("%s\n", usage);
7724 quit(0);
7725 }
7727 custom_argv[j++] = opt;
7728 if (j >= ARRAY_SIZE(custom_argv))
7729 die("command too long");
7730 }
7732 if (!prepare_update(VIEW(request), custom_argv, NULL))
7733 die("Failed to format arguments");
7735 return request;
7736 }
7738 int
7739 main(int argc, const char *argv[])
7740 {
7741 const char *codeset = "UTF-8";
7742 enum request request = parse_options(argc, argv);
7743 struct view *view;
7744 size_t i;
7746 signal(SIGINT, quit);
7747 signal(SIGPIPE, SIG_IGN);
7749 if (setlocale(LC_ALL, "")) {
7750 codeset = nl_langinfo(CODESET);
7751 }
7753 if (load_repo_info() == ERR)
7754 die("Failed to load repo info.");
7756 if (load_options() == ERR)
7757 die("Failed to load user config.");
7759 if (load_git_config() == ERR)
7760 die("Failed to load repo config.");
7762 /* Require a git repository unless when running in pager mode. */
7763 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7764 die("Not a git repository");
7766 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7767 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7768 if (opt_iconv_in == ICONV_NONE)
7769 die("Failed to initialize character set conversion");
7770 }
7772 if (codeset && strcmp(codeset, "UTF-8")) {
7773 opt_iconv_out = iconv_open(codeset, "UTF-8");
7774 if (opt_iconv_out == ICONV_NONE)
7775 die("Failed to initialize character set conversion");
7776 }
7778 if (load_refs() == ERR)
7779 die("Failed to load refs.");
7781 foreach_view (view, i)
7782 if (!argv_from_env(view->ops->argv, view->cmd_env))
7783 die("Too many arguments in the `%s` environment variable",
7784 view->cmd_env);
7786 init_display();
7788 if (request != REQ_NONE)
7789 open_view(NULL, request, OPEN_PREPARED);
7790 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7792 while (view_driver(display[current_view], request)) {
7793 int key = get_input(0);
7795 view = display[current_view];
7796 request = get_keybinding(view->keymap, key);
7798 /* Some low-level request handling. This keeps access to
7799 * status_win restricted. */
7800 switch (request) {
7801 case REQ_PROMPT:
7802 {
7803 char *cmd = read_prompt(":");
7805 if (cmd && isdigit(*cmd)) {
7806 int lineno = view->lineno + 1;
7808 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7809 select_view_line(view, lineno - 1);
7810 report("");
7811 } else {
7812 report("Unable to parse '%s' as a line number", cmd);
7813 }
7815 } else if (cmd) {
7816 struct view *next = VIEW(REQ_VIEW_PAGER);
7817 const char *argv[SIZEOF_ARG] = { "git" };
7818 int argc = 1;
7820 /* When running random commands, initially show the
7821 * command in the title. However, it maybe later be
7822 * overwritten if a commit line is selected. */
7823 string_ncopy(next->ref, cmd, strlen(cmd));
7825 if (!argv_from_string(argv, &argc, cmd)) {
7826 report("Too many arguments");
7827 } else if (!prepare_update(next, argv, NULL)) {
7828 report("Failed to format command");
7829 } else {
7830 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7831 }
7832 }
7834 request = REQ_NONE;
7835 break;
7836 }
7837 case REQ_SEARCH:
7838 case REQ_SEARCH_BACK:
7839 {
7840 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7841 char *search = read_prompt(prompt);
7843 if (search)
7844 string_ncopy(opt_search, search, strlen(search));
7845 else if (*opt_search)
7846 request = request == REQ_SEARCH ?
7847 REQ_FIND_NEXT :
7848 REQ_FIND_PREV;
7849 else
7850 request = REQ_NONE;
7851 break;
7852 }
7853 default:
7854 break;
7855 }
7856 }
7858 quit(0);
7860 return 0;
7861 }