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 foreach_view(view, i) {
3741 if (view->pipe)
3742 report("Stopped loading the %s view", view->name),
3743 end_update(view, TRUE);
3744 }
3745 break;
3747 case REQ_SHOW_VERSION:
3748 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3749 return TRUE;
3751 case REQ_SCREEN_REDRAW:
3752 redraw_display(TRUE);
3753 break;
3755 case REQ_EDIT:
3756 report("Nothing to edit");
3757 break;
3759 case REQ_ENTER:
3760 report("Nothing to enter");
3761 break;
3763 case REQ_VIEW_CLOSE:
3764 /* XXX: Mark closed views by letting view->parent point to the
3765 * view itself. Parents to closed view should never be
3766 * followed. */
3767 if (view->parent &&
3768 view->parent->parent != view->parent) {
3769 maximize_view(view->parent);
3770 view->parent = view;
3771 break;
3772 }
3773 /* Fall-through */
3774 case REQ_QUIT:
3775 return FALSE;
3777 default:
3778 report("Unknown key, press %s for help",
3779 get_key(view->keymap, REQ_VIEW_HELP));
3780 return TRUE;
3781 }
3783 return TRUE;
3784 }
3787 /*
3788 * View backend utilities
3789 */
3791 enum sort_field {
3792 ORDERBY_NAME,
3793 ORDERBY_DATE,
3794 ORDERBY_AUTHOR,
3795 };
3797 struct sort_state {
3798 const enum sort_field *fields;
3799 size_t size, current;
3800 bool reverse;
3801 };
3803 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3804 #define get_sort_field(state) ((state).fields[(state).current])
3805 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3807 static void
3808 sort_view(struct view *view, enum request request, struct sort_state *state,
3809 int (*compare)(const void *, const void *))
3810 {
3811 switch (request) {
3812 case REQ_TOGGLE_SORT_FIELD:
3813 state->current = (state->current + 1) % state->size;
3814 break;
3816 case REQ_TOGGLE_SORT_ORDER:
3817 state->reverse = !state->reverse;
3818 break;
3819 default:
3820 die("Not a sort request");
3821 }
3823 qsort(view->line, view->lines, sizeof(*view->line), compare);
3824 redraw_view(view);
3825 }
3827 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3829 /* Small author cache to reduce memory consumption. It uses binary
3830 * search to lookup or find place to position new entries. No entries
3831 * are ever freed. */
3832 static const char *
3833 get_author(const char *name)
3834 {
3835 static const char **authors;
3836 static size_t authors_size;
3837 int from = 0, to = authors_size - 1;
3839 while (from <= to) {
3840 size_t pos = (to + from) / 2;
3841 int cmp = strcmp(name, authors[pos]);
3843 if (!cmp)
3844 return authors[pos];
3846 if (cmp < 0)
3847 to = pos - 1;
3848 else
3849 from = pos + 1;
3850 }
3852 if (!realloc_authors(&authors, authors_size, 1))
3853 return NULL;
3854 name = strdup(name);
3855 if (!name)
3856 return NULL;
3858 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3859 authors[from] = name;
3860 authors_size++;
3862 return name;
3863 }
3865 static void
3866 parse_timesec(struct time *time, const char *sec)
3867 {
3868 time->sec = (time_t) atol(sec);
3869 }
3871 static void
3872 parse_timezone(struct time *time, const char *zone)
3873 {
3874 long tz;
3876 tz = ('0' - zone[1]) * 60 * 60 * 10;
3877 tz += ('0' - zone[2]) * 60 * 60;
3878 tz += ('0' - zone[3]) * 60;
3879 tz += ('0' - zone[4]);
3881 if (zone[0] == '-')
3882 tz = -tz;
3884 time->tz = tz;
3885 time->sec -= tz;
3886 }
3888 /* Parse author lines where the name may be empty:
3889 * author <email@address.tld> 1138474660 +0100
3890 */
3891 static void
3892 parse_author_line(char *ident, const char **author, struct time *time)
3893 {
3894 char *nameend = strchr(ident, '<');
3895 char *emailend = strchr(ident, '>');
3897 if (nameend && emailend)
3898 *nameend = *emailend = 0;
3899 ident = chomp_string(ident);
3900 if (!*ident) {
3901 if (nameend)
3902 ident = chomp_string(nameend + 1);
3903 if (!*ident)
3904 ident = "Unknown";
3905 }
3907 *author = get_author(ident);
3909 /* Parse epoch and timezone */
3910 if (emailend && emailend[1] == ' ') {
3911 char *secs = emailend + 2;
3912 char *zone = strchr(secs, ' ');
3914 parse_timesec(time, secs);
3916 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3917 parse_timezone(time, zone + 1);
3918 }
3919 }
3921 static bool
3922 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3923 {
3924 char rev[SIZEOF_REV];
3925 const char *revlist_argv[] = {
3926 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3927 };
3928 struct menu_item *items;
3929 char text[SIZEOF_STR];
3930 bool ok = TRUE;
3931 int i;
3933 items = calloc(*parents + 1, sizeof(*items));
3934 if (!items)
3935 return FALSE;
3937 for (i = 0; i < *parents; i++) {
3938 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3939 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3940 !(items[i].text = strdup(text))) {
3941 ok = FALSE;
3942 break;
3943 }
3944 }
3946 if (ok) {
3947 *parents = 0;
3948 ok = prompt_menu("Select parent", items, parents);
3949 }
3950 for (i = 0; items[i].text; i++)
3951 free((char *) items[i].text);
3952 free(items);
3953 return ok;
3954 }
3956 static bool
3957 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3958 {
3959 char buf[SIZEOF_STR * 4];
3960 const char *revlist_argv[] = {
3961 "git", "log", "--no-color", "-1",
3962 "--pretty=format:%P", id, "--", path, NULL
3963 };
3964 int parents;
3966 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3967 (parents = strlen(buf) / 40) < 0) {
3968 report("Failed to get parent information");
3969 return FALSE;
3971 } else if (parents == 0) {
3972 if (path)
3973 report("Path '%s' does not exist in the parent", path);
3974 else
3975 report("The selected commit has no parents");
3976 return FALSE;
3977 }
3979 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3980 return FALSE;
3982 string_copy_rev(rev, &buf[41 * parents]);
3983 return TRUE;
3984 }
3986 /*
3987 * Pager backend
3988 */
3990 static bool
3991 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3992 {
3993 char text[SIZEOF_STR];
3995 if (opt_line_number && draw_lineno(view, lineno))
3996 return TRUE;
3998 string_expand(text, sizeof(text), line->data, opt_tab_size);
3999 draw_text(view, line->type, text, TRUE);
4000 return TRUE;
4001 }
4003 static bool
4004 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4005 {
4006 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4007 char ref[SIZEOF_STR];
4009 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4010 return TRUE;
4012 /* This is the only fatal call, since it can "corrupt" the buffer. */
4013 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4014 return FALSE;
4016 return TRUE;
4017 }
4019 static void
4020 add_pager_refs(struct view *view, struct line *line)
4021 {
4022 char buf[SIZEOF_STR];
4023 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4024 struct ref_list *list;
4025 size_t bufpos = 0, i;
4026 const char *sep = "Refs: ";
4027 bool is_tag = FALSE;
4029 assert(line->type == LINE_COMMIT);
4031 list = get_ref_list(commit_id);
4032 if (!list) {
4033 if (view == VIEW(REQ_VIEW_DIFF))
4034 goto try_add_describe_ref;
4035 return;
4036 }
4038 for (i = 0; i < list->size; i++) {
4039 struct ref *ref = list->refs[i];
4040 const char *fmt = ref->tag ? "%s[%s]" :
4041 ref->remote ? "%s<%s>" : "%s%s";
4043 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4044 return;
4045 sep = ", ";
4046 if (ref->tag)
4047 is_tag = TRUE;
4048 }
4050 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4051 try_add_describe_ref:
4052 /* Add <tag>-g<commit_id> "fake" reference. */
4053 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4054 return;
4055 }
4057 if (bufpos == 0)
4058 return;
4060 add_line_text(view, buf, LINE_PP_REFS);
4061 }
4063 static bool
4064 pager_read(struct view *view, char *data)
4065 {
4066 struct line *line;
4068 if (!data)
4069 return TRUE;
4071 line = add_line_text(view, data, get_line_type(data));
4072 if (!line)
4073 return FALSE;
4075 if (line->type == LINE_COMMIT &&
4076 (view == VIEW(REQ_VIEW_DIFF) ||
4077 view == VIEW(REQ_VIEW_LOG)))
4078 add_pager_refs(view, line);
4080 return TRUE;
4081 }
4083 static enum request
4084 pager_request(struct view *view, enum request request, struct line *line)
4085 {
4086 int split = 0;
4088 if (request != REQ_ENTER)
4089 return request;
4091 if (line->type == LINE_COMMIT &&
4092 (view == VIEW(REQ_VIEW_LOG) ||
4093 view == VIEW(REQ_VIEW_PAGER))) {
4094 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4095 split = 1;
4096 }
4098 /* Always scroll the view even if it was split. That way
4099 * you can use Enter to scroll through the log view and
4100 * split open each commit diff. */
4101 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4103 /* FIXME: A minor workaround. Scrolling the view will call report("")
4104 * but if we are scrolling a non-current view this won't properly
4105 * update the view title. */
4106 if (split)
4107 update_view_title(view);
4109 return REQ_NONE;
4110 }
4112 static bool
4113 pager_grep(struct view *view, struct line *line)
4114 {
4115 const char *text[] = { line->data, NULL };
4117 return grep_text(view, text);
4118 }
4120 static void
4121 pager_select(struct view *view, struct line *line)
4122 {
4123 if (line->type == LINE_COMMIT) {
4124 char *text = (char *)line->data + STRING_SIZE("commit ");
4126 if (view != VIEW(REQ_VIEW_PAGER))
4127 string_copy_rev(view->ref, text);
4128 string_copy_rev(ref_commit, text);
4129 }
4130 }
4132 static struct view_ops pager_ops = {
4133 "line",
4134 NULL,
4135 NULL,
4136 pager_read,
4137 pager_draw,
4138 pager_request,
4139 pager_grep,
4140 pager_select,
4141 };
4143 static const char *log_argv[SIZEOF_ARG] = {
4144 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4145 };
4147 static enum request
4148 log_request(struct view *view, enum request request, struct line *line)
4149 {
4150 switch (request) {
4151 case REQ_REFRESH:
4152 load_refs();
4153 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4154 return REQ_NONE;
4155 default:
4156 return pager_request(view, request, line);
4157 }
4158 }
4160 static struct view_ops log_ops = {
4161 "line",
4162 log_argv,
4163 NULL,
4164 pager_read,
4165 pager_draw,
4166 log_request,
4167 pager_grep,
4168 pager_select,
4169 };
4171 static const char *diff_argv[SIZEOF_ARG] = {
4172 "git", "show", "--pretty=fuller", "--no-color", "--root",
4173 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4174 };
4176 static struct view_ops diff_ops = {
4177 "line",
4178 diff_argv,
4179 NULL,
4180 pager_read,
4181 pager_draw,
4182 pager_request,
4183 pager_grep,
4184 pager_select,
4185 };
4187 /*
4188 * Help backend
4189 */
4191 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4193 static bool
4194 help_open_keymap_title(struct view *view, enum keymap keymap)
4195 {
4196 struct line *line;
4198 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4199 help_keymap_hidden[keymap] ? '+' : '-',
4200 enum_name(keymap_table[keymap]));
4201 if (line)
4202 line->other = keymap;
4204 return help_keymap_hidden[keymap];
4205 }
4207 static void
4208 help_open_keymap(struct view *view, enum keymap keymap)
4209 {
4210 const char *group = NULL;
4211 char buf[SIZEOF_STR];
4212 size_t bufpos;
4213 bool add_title = TRUE;
4214 int i;
4216 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4217 const char *key = NULL;
4219 if (req_info[i].request == REQ_NONE)
4220 continue;
4222 if (!req_info[i].request) {
4223 group = req_info[i].help;
4224 continue;
4225 }
4227 key = get_keys(keymap, req_info[i].request, TRUE);
4228 if (!key || !*key)
4229 continue;
4231 if (add_title && help_open_keymap_title(view, keymap))
4232 return;
4233 add_title = FALSE;
4235 if (group) {
4236 add_line_text(view, group, LINE_HELP_GROUP);
4237 group = NULL;
4238 }
4240 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4241 enum_name(req_info[i]), req_info[i].help);
4242 }
4244 group = "External commands:";
4246 for (i = 0; i < run_requests; i++) {
4247 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4248 const char *key;
4249 int argc;
4251 if (!req || req->keymap != keymap)
4252 continue;
4254 key = get_key_name(req->key);
4255 if (!*key)
4256 key = "(no key defined)";
4258 if (add_title && help_open_keymap_title(view, keymap))
4259 return;
4260 if (group) {
4261 add_line_text(view, group, LINE_HELP_GROUP);
4262 group = NULL;
4263 }
4265 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4266 if (!string_format_from(buf, &bufpos, "%s%s",
4267 argc ? " " : "", req->argv[argc]))
4268 return;
4270 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4271 }
4272 }
4274 static bool
4275 help_open(struct view *view)
4276 {
4277 enum keymap keymap;
4279 reset_view(view);
4280 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4281 add_line_text(view, "", LINE_DEFAULT);
4283 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4284 help_open_keymap(view, keymap);
4286 return TRUE;
4287 }
4289 static enum request
4290 help_request(struct view *view, enum request request, struct line *line)
4291 {
4292 switch (request) {
4293 case REQ_ENTER:
4294 if (line->type == LINE_HELP_KEYMAP) {
4295 help_keymap_hidden[line->other] =
4296 !help_keymap_hidden[line->other];
4297 view->p_restore = TRUE;
4298 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4299 }
4301 return REQ_NONE;
4302 default:
4303 return pager_request(view, request, line);
4304 }
4305 }
4307 static struct view_ops help_ops = {
4308 "line",
4309 NULL,
4310 help_open,
4311 NULL,
4312 pager_draw,
4313 help_request,
4314 pager_grep,
4315 pager_select,
4316 };
4319 /*
4320 * Tree backend
4321 */
4323 struct tree_stack_entry {
4324 struct tree_stack_entry *prev; /* Entry below this in the stack */
4325 unsigned long lineno; /* Line number to restore */
4326 char *name; /* Position of name in opt_path */
4327 };
4329 /* The top of the path stack. */
4330 static struct tree_stack_entry *tree_stack = NULL;
4331 unsigned long tree_lineno = 0;
4333 static void
4334 pop_tree_stack_entry(void)
4335 {
4336 struct tree_stack_entry *entry = tree_stack;
4338 tree_lineno = entry->lineno;
4339 entry->name[0] = 0;
4340 tree_stack = entry->prev;
4341 free(entry);
4342 }
4344 static void
4345 push_tree_stack_entry(const char *name, unsigned long lineno)
4346 {
4347 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4348 size_t pathlen = strlen(opt_path);
4350 if (!entry)
4351 return;
4353 entry->prev = tree_stack;
4354 entry->name = opt_path + pathlen;
4355 tree_stack = entry;
4357 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4358 pop_tree_stack_entry();
4359 return;
4360 }
4362 /* Move the current line to the first tree entry. */
4363 tree_lineno = 1;
4364 entry->lineno = lineno;
4365 }
4367 /* Parse output from git-ls-tree(1):
4368 *
4369 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4370 */
4372 #define SIZEOF_TREE_ATTR \
4373 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4375 #define SIZEOF_TREE_MODE \
4376 STRING_SIZE("100644 ")
4378 #define TREE_ID_OFFSET \
4379 STRING_SIZE("100644 blob ")
4381 struct tree_entry {
4382 char id[SIZEOF_REV];
4383 mode_t mode;
4384 struct time time; /* Date from the author ident. */
4385 const char *author; /* Author of the commit. */
4386 char name[1];
4387 };
4389 static const char *
4390 tree_path(const struct line *line)
4391 {
4392 return ((struct tree_entry *) line->data)->name;
4393 }
4395 static int
4396 tree_compare_entry(const struct line *line1, const struct line *line2)
4397 {
4398 if (line1->type != line2->type)
4399 return line1->type == LINE_TREE_DIR ? -1 : 1;
4400 return strcmp(tree_path(line1), tree_path(line2));
4401 }
4403 static const enum sort_field tree_sort_fields[] = {
4404 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4405 };
4406 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4408 static int
4409 tree_compare(const void *l1, const void *l2)
4410 {
4411 const struct line *line1 = (const struct line *) l1;
4412 const struct line *line2 = (const struct line *) l2;
4413 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4414 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4416 if (line1->type == LINE_TREE_HEAD)
4417 return -1;
4418 if (line2->type == LINE_TREE_HEAD)
4419 return 1;
4421 switch (get_sort_field(tree_sort_state)) {
4422 case ORDERBY_DATE:
4423 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4425 case ORDERBY_AUTHOR:
4426 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4428 case ORDERBY_NAME:
4429 default:
4430 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4431 }
4432 }
4435 static struct line *
4436 tree_entry(struct view *view, enum line_type type, const char *path,
4437 const char *mode, const char *id)
4438 {
4439 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4440 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4442 if (!entry || !line) {
4443 free(entry);
4444 return NULL;
4445 }
4447 strncpy(entry->name, path, strlen(path));
4448 if (mode)
4449 entry->mode = strtoul(mode, NULL, 8);
4450 if (id)
4451 string_copy_rev(entry->id, id);
4453 return line;
4454 }
4456 static bool
4457 tree_read_date(struct view *view, char *text, bool *read_date)
4458 {
4459 static const char *author_name;
4460 static struct time author_time;
4462 if (!text && *read_date) {
4463 *read_date = FALSE;
4464 return TRUE;
4466 } else if (!text) {
4467 char *path = *opt_path ? opt_path : ".";
4468 /* Find next entry to process */
4469 const char *log_file[] = {
4470 "git", "log", "--no-color", "--pretty=raw",
4471 "--cc", "--raw", view->id, "--", path, NULL
4472 };
4473 struct io io = {};
4475 if (!view->lines) {
4476 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4477 report("Tree is empty");
4478 return TRUE;
4479 }
4481 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4482 report("Failed to load tree data");
4483 return TRUE;
4484 }
4486 io_done(view->pipe);
4487 view->io = io;
4488 *read_date = TRUE;
4489 return FALSE;
4491 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4492 parse_author_line(text + STRING_SIZE("author "),
4493 &author_name, &author_time);
4495 } else if (*text == ':') {
4496 char *pos;
4497 size_t annotated = 1;
4498 size_t i;
4500 pos = strchr(text, '\t');
4501 if (!pos)
4502 return TRUE;
4503 text = pos + 1;
4504 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4505 text += strlen(opt_path);
4506 pos = strchr(text, '/');
4507 if (pos)
4508 *pos = 0;
4510 for (i = 1; i < view->lines; i++) {
4511 struct line *line = &view->line[i];
4512 struct tree_entry *entry = line->data;
4514 annotated += !!entry->author;
4515 if (entry->author || strcmp(entry->name, text))
4516 continue;
4518 entry->author = author_name;
4519 entry->time = author_time;
4520 line->dirty = 1;
4521 break;
4522 }
4524 if (annotated == view->lines)
4525 io_kill(view->pipe);
4526 }
4527 return TRUE;
4528 }
4530 static bool
4531 tree_read(struct view *view, char *text)
4532 {
4533 static bool read_date = FALSE;
4534 struct tree_entry *data;
4535 struct line *entry, *line;
4536 enum line_type type;
4537 size_t textlen = text ? strlen(text) : 0;
4538 char *path = text + SIZEOF_TREE_ATTR;
4540 if (read_date || !text)
4541 return tree_read_date(view, text, &read_date);
4543 if (textlen <= SIZEOF_TREE_ATTR)
4544 return FALSE;
4545 if (view->lines == 0 &&
4546 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4547 return FALSE;
4549 /* Strip the path part ... */
4550 if (*opt_path) {
4551 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4552 size_t striplen = strlen(opt_path);
4554 if (pathlen > striplen)
4555 memmove(path, path + striplen,
4556 pathlen - striplen + 1);
4558 /* Insert "link" to parent directory. */
4559 if (view->lines == 1 &&
4560 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4561 return FALSE;
4562 }
4564 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4565 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4566 if (!entry)
4567 return FALSE;
4568 data = entry->data;
4570 /* Skip "Directory ..." and ".." line. */
4571 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4572 if (tree_compare_entry(line, entry) <= 0)
4573 continue;
4575 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4577 line->data = data;
4578 line->type = type;
4579 for (; line <= entry; line++)
4580 line->dirty = line->cleareol = 1;
4581 return TRUE;
4582 }
4584 if (tree_lineno > view->lineno) {
4585 view->lineno = tree_lineno;
4586 tree_lineno = 0;
4587 }
4589 return TRUE;
4590 }
4592 static bool
4593 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4594 {
4595 struct tree_entry *entry = line->data;
4597 if (line->type == LINE_TREE_HEAD) {
4598 if (draw_text(view, line->type, "Directory path /", TRUE))
4599 return TRUE;
4600 } else {
4601 if (draw_mode(view, entry->mode))
4602 return TRUE;
4604 if (opt_author && draw_author(view, entry->author))
4605 return TRUE;
4607 if (opt_date && draw_date(view, &entry->time))
4608 return TRUE;
4609 }
4610 if (draw_text(view, line->type, entry->name, TRUE))
4611 return TRUE;
4612 return TRUE;
4613 }
4615 static void
4616 open_blob_editor()
4617 {
4618 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4619 int fd = mkstemp(file);
4621 if (fd == -1)
4622 report("Failed to create temporary file");
4623 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4624 report("Failed to save blob data to file");
4625 else
4626 open_editor(file);
4627 if (fd != -1)
4628 unlink(file);
4629 }
4631 static enum request
4632 tree_request(struct view *view, enum request request, struct line *line)
4633 {
4634 enum open_flags flags;
4636 switch (request) {
4637 case REQ_VIEW_BLAME:
4638 if (line->type != LINE_TREE_FILE) {
4639 report("Blame only supported for files");
4640 return REQ_NONE;
4641 }
4643 string_copy(opt_ref, view->vid);
4644 return request;
4646 case REQ_EDIT:
4647 if (line->type != LINE_TREE_FILE) {
4648 report("Edit only supported for files");
4649 } else if (!is_head_commit(view->vid)) {
4650 open_blob_editor();
4651 } else {
4652 open_editor(opt_file);
4653 }
4654 return REQ_NONE;
4656 case REQ_TOGGLE_SORT_FIELD:
4657 case REQ_TOGGLE_SORT_ORDER:
4658 sort_view(view, request, &tree_sort_state, tree_compare);
4659 return REQ_NONE;
4661 case REQ_PARENT:
4662 if (!*opt_path) {
4663 /* quit view if at top of tree */
4664 return REQ_VIEW_CLOSE;
4665 }
4666 /* fake 'cd ..' */
4667 line = &view->line[1];
4668 break;
4670 case REQ_ENTER:
4671 break;
4673 default:
4674 return request;
4675 }
4677 /* Cleanup the stack if the tree view is at a different tree. */
4678 while (!*opt_path && tree_stack)
4679 pop_tree_stack_entry();
4681 switch (line->type) {
4682 case LINE_TREE_DIR:
4683 /* Depending on whether it is a subdirectory or parent link
4684 * mangle the path buffer. */
4685 if (line == &view->line[1] && *opt_path) {
4686 pop_tree_stack_entry();
4688 } else {
4689 const char *basename = tree_path(line);
4691 push_tree_stack_entry(basename, view->lineno);
4692 }
4694 /* Trees and subtrees share the same ID, so they are not not
4695 * unique like blobs. */
4696 flags = OPEN_RELOAD;
4697 request = REQ_VIEW_TREE;
4698 break;
4700 case LINE_TREE_FILE:
4701 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4702 request = REQ_VIEW_BLOB;
4703 break;
4705 default:
4706 return REQ_NONE;
4707 }
4709 open_view(view, request, flags);
4710 if (request == REQ_VIEW_TREE)
4711 view->lineno = tree_lineno;
4713 return REQ_NONE;
4714 }
4716 static bool
4717 tree_grep(struct view *view, struct line *line)
4718 {
4719 struct tree_entry *entry = line->data;
4720 const char *text[] = {
4721 entry->name,
4722 opt_author ? entry->author : "",
4723 mkdate(&entry->time, opt_date),
4724 NULL
4725 };
4727 return grep_text(view, text);
4728 }
4730 static void
4731 tree_select(struct view *view, struct line *line)
4732 {
4733 struct tree_entry *entry = line->data;
4735 if (line->type == LINE_TREE_FILE) {
4736 string_copy_rev(ref_blob, entry->id);
4737 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4739 } else if (line->type != LINE_TREE_DIR) {
4740 return;
4741 }
4743 string_copy_rev(view->ref, entry->id);
4744 }
4746 static bool
4747 tree_prepare(struct view *view)
4748 {
4749 if (view->lines == 0 && opt_prefix[0]) {
4750 char *pos = opt_prefix;
4752 while (pos && *pos) {
4753 char *end = strchr(pos, '/');
4755 if (end)
4756 *end = 0;
4757 push_tree_stack_entry(pos, 0);
4758 pos = end;
4759 if (end) {
4760 *end = '/';
4761 pos++;
4762 }
4763 }
4765 } else if (strcmp(view->vid, view->id)) {
4766 opt_path[0] = 0;
4767 }
4769 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4770 }
4772 static const char *tree_argv[SIZEOF_ARG] = {
4773 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4774 };
4776 static struct view_ops tree_ops = {
4777 "file",
4778 tree_argv,
4779 NULL,
4780 tree_read,
4781 tree_draw,
4782 tree_request,
4783 tree_grep,
4784 tree_select,
4785 tree_prepare,
4786 };
4788 static bool
4789 blob_read(struct view *view, char *line)
4790 {
4791 if (!line)
4792 return TRUE;
4793 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4794 }
4796 static enum request
4797 blob_request(struct view *view, enum request request, struct line *line)
4798 {
4799 switch (request) {
4800 case REQ_EDIT:
4801 open_blob_editor();
4802 return REQ_NONE;
4803 default:
4804 return pager_request(view, request, line);
4805 }
4806 }
4808 static const char *blob_argv[SIZEOF_ARG] = {
4809 "git", "cat-file", "blob", "%(blob)", NULL
4810 };
4812 static struct view_ops blob_ops = {
4813 "line",
4814 blob_argv,
4815 NULL,
4816 blob_read,
4817 pager_draw,
4818 blob_request,
4819 pager_grep,
4820 pager_select,
4821 };
4823 /*
4824 * Blame backend
4825 *
4826 * Loading the blame view is a two phase job:
4827 *
4828 * 1. File content is read either using opt_file from the
4829 * filesystem or using git-cat-file.
4830 * 2. Then blame information is incrementally added by
4831 * reading output from git-blame.
4832 */
4834 static const char *blame_head_argv[] = {
4835 "git", "blame", "--incremental", "--", "%(file)", NULL
4836 };
4838 static const char *blame_ref_argv[] = {
4839 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4840 };
4842 static const char *blame_cat_file_argv[] = {
4843 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4844 };
4846 struct blame_commit {
4847 char id[SIZEOF_REV]; /* SHA1 ID. */
4848 char title[128]; /* First line of the commit message. */
4849 const char *author; /* Author of the commit. */
4850 struct time time; /* Date from the author ident. */
4851 char filename[128]; /* Name of file. */
4852 bool has_previous; /* Was a "previous" line detected. */
4853 };
4855 struct blame {
4856 struct blame_commit *commit;
4857 unsigned long lineno;
4858 char text[1];
4859 };
4861 static bool
4862 blame_open(struct view *view)
4863 {
4864 char path[SIZEOF_STR];
4866 if (!view->parent && *opt_prefix) {
4867 string_copy(path, opt_file);
4868 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4869 return FALSE;
4870 }
4872 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4873 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4874 return FALSE;
4875 }
4877 setup_update(view, opt_file);
4878 string_format(view->ref, "%s ...", opt_file);
4880 return TRUE;
4881 }
4883 static struct blame_commit *
4884 get_blame_commit(struct view *view, const char *id)
4885 {
4886 size_t i;
4888 for (i = 0; i < view->lines; i++) {
4889 struct blame *blame = view->line[i].data;
4891 if (!blame->commit)
4892 continue;
4894 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4895 return blame->commit;
4896 }
4898 {
4899 struct blame_commit *commit = calloc(1, sizeof(*commit));
4901 if (commit)
4902 string_ncopy(commit->id, id, SIZEOF_REV);
4903 return commit;
4904 }
4905 }
4907 static bool
4908 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4909 {
4910 const char *pos = *posref;
4912 *posref = NULL;
4913 pos = strchr(pos + 1, ' ');
4914 if (!pos || !isdigit(pos[1]))
4915 return FALSE;
4916 *number = atoi(pos + 1);
4917 if (*number < min || *number > max)
4918 return FALSE;
4920 *posref = pos;
4921 return TRUE;
4922 }
4924 static struct blame_commit *
4925 parse_blame_commit(struct view *view, const char *text, int *blamed)
4926 {
4927 struct blame_commit *commit;
4928 struct blame *blame;
4929 const char *pos = text + SIZEOF_REV - 2;
4930 size_t orig_lineno = 0;
4931 size_t lineno;
4932 size_t group;
4934 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4935 return NULL;
4937 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4938 !parse_number(&pos, &lineno, 1, view->lines) ||
4939 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4940 return NULL;
4942 commit = get_blame_commit(view, text);
4943 if (!commit)
4944 return NULL;
4946 *blamed += group;
4947 while (group--) {
4948 struct line *line = &view->line[lineno + group - 1];
4950 blame = line->data;
4951 blame->commit = commit;
4952 blame->lineno = orig_lineno + group - 1;
4953 line->dirty = 1;
4954 }
4956 return commit;
4957 }
4959 static bool
4960 blame_read_file(struct view *view, const char *line, bool *read_file)
4961 {
4962 if (!line) {
4963 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4964 struct io io = {};
4966 if (view->lines == 0 && !view->parent)
4967 die("No blame exist for %s", view->vid);
4969 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4970 report("Failed to load blame data");
4971 return TRUE;
4972 }
4974 io_done(view->pipe);
4975 view->io = io;
4976 *read_file = FALSE;
4977 return FALSE;
4979 } else {
4980 size_t linelen = strlen(line);
4981 struct blame *blame = malloc(sizeof(*blame) + linelen);
4983 if (!blame)
4984 return FALSE;
4986 blame->commit = NULL;
4987 strncpy(blame->text, line, linelen);
4988 blame->text[linelen] = 0;
4989 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4990 }
4991 }
4993 static bool
4994 match_blame_header(const char *name, char **line)
4995 {
4996 size_t namelen = strlen(name);
4997 bool matched = !strncmp(name, *line, namelen);
4999 if (matched)
5000 *line += namelen;
5002 return matched;
5003 }
5005 static bool
5006 blame_read(struct view *view, char *line)
5007 {
5008 static struct blame_commit *commit = NULL;
5009 static int blamed = 0;
5010 static bool read_file = TRUE;
5012 if (read_file)
5013 return blame_read_file(view, line, &read_file);
5015 if (!line) {
5016 /* Reset all! */
5017 commit = NULL;
5018 blamed = 0;
5019 read_file = TRUE;
5020 string_format(view->ref, "%s", view->vid);
5021 if (view_is_displayed(view)) {
5022 update_view_title(view);
5023 redraw_view_from(view, 0);
5024 }
5025 return TRUE;
5026 }
5028 if (!commit) {
5029 commit = parse_blame_commit(view, line, &blamed);
5030 string_format(view->ref, "%s %2d%%", view->vid,
5031 view->lines ? blamed * 100 / view->lines : 0);
5033 } else if (match_blame_header("author ", &line)) {
5034 commit->author = get_author(line);
5036 } else if (match_blame_header("author-time ", &line)) {
5037 parse_timesec(&commit->time, line);
5039 } else if (match_blame_header("author-tz ", &line)) {
5040 parse_timezone(&commit->time, line);
5042 } else if (match_blame_header("summary ", &line)) {
5043 string_ncopy(commit->title, line, strlen(line));
5045 } else if (match_blame_header("previous ", &line)) {
5046 commit->has_previous = TRUE;
5048 } else if (match_blame_header("filename ", &line)) {
5049 string_ncopy(commit->filename, line, strlen(line));
5050 commit = NULL;
5051 }
5053 return TRUE;
5054 }
5056 static bool
5057 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5058 {
5059 struct blame *blame = line->data;
5060 struct time *time = NULL;
5061 const char *id = NULL, *author = NULL;
5062 char text[SIZEOF_STR];
5064 if (blame->commit && *blame->commit->filename) {
5065 id = blame->commit->id;
5066 author = blame->commit->author;
5067 time = &blame->commit->time;
5068 }
5070 if (opt_date && draw_date(view, time))
5071 return TRUE;
5073 if (opt_author && draw_author(view, author))
5074 return TRUE;
5076 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5077 return TRUE;
5079 if (draw_lineno(view, lineno))
5080 return TRUE;
5082 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5083 draw_text(view, LINE_DEFAULT, text, TRUE);
5084 return TRUE;
5085 }
5087 static bool
5088 check_blame_commit(struct blame *blame, bool check_null_id)
5089 {
5090 if (!blame->commit)
5091 report("Commit data not loaded yet");
5092 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5093 report("No commit exist for the selected line");
5094 else
5095 return TRUE;
5096 return FALSE;
5097 }
5099 static void
5100 setup_blame_parent_line(struct view *view, struct blame *blame)
5101 {
5102 const char *diff_tree_argv[] = {
5103 "git", "diff-tree", "-U0", blame->commit->id,
5104 "--", blame->commit->filename, NULL
5105 };
5106 struct io io = {};
5107 int parent_lineno = -1;
5108 int blamed_lineno = -1;
5109 char *line;
5111 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5112 return;
5114 while ((line = io_get(&io, '\n', TRUE))) {
5115 if (*line == '@') {
5116 char *pos = strchr(line, '+');
5118 parent_lineno = atoi(line + 4);
5119 if (pos)
5120 blamed_lineno = atoi(pos + 1);
5122 } else if (*line == '+' && parent_lineno != -1) {
5123 if (blame->lineno == blamed_lineno - 1 &&
5124 !strcmp(blame->text, line + 1)) {
5125 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5126 break;
5127 }
5128 blamed_lineno++;
5129 }
5130 }
5132 io_done(&io);
5133 }
5135 static enum request
5136 blame_request(struct view *view, enum request request, struct line *line)
5137 {
5138 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5139 struct blame *blame = line->data;
5141 switch (request) {
5142 case REQ_VIEW_BLAME:
5143 if (check_blame_commit(blame, TRUE)) {
5144 string_copy(opt_ref, blame->commit->id);
5145 string_copy(opt_file, blame->commit->filename);
5146 if (blame->lineno)
5147 view->lineno = blame->lineno;
5148 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5149 }
5150 break;
5152 case REQ_PARENT:
5153 if (check_blame_commit(blame, TRUE) &&
5154 select_commit_parent(blame->commit->id, opt_ref,
5155 blame->commit->filename)) {
5156 string_copy(opt_file, blame->commit->filename);
5157 setup_blame_parent_line(view, blame);
5158 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5159 }
5160 break;
5162 case REQ_ENTER:
5163 if (!check_blame_commit(blame, FALSE))
5164 break;
5166 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5167 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5168 break;
5170 if (!strcmp(blame->commit->id, NULL_ID)) {
5171 struct view *diff = VIEW(REQ_VIEW_DIFF);
5172 const char *diff_index_argv[] = {
5173 "git", "diff-index", "--root", "--patch-with-stat",
5174 "-C", "-M", "HEAD", "--", view->vid, NULL
5175 };
5177 if (!blame->commit->has_previous) {
5178 diff_index_argv[1] = "diff";
5179 diff_index_argv[2] = "--no-color";
5180 diff_index_argv[6] = "--";
5181 diff_index_argv[7] = "/dev/null";
5182 }
5184 if (!prepare_update(diff, diff_index_argv, NULL)) {
5185 report("Failed to allocate diff command");
5186 break;
5187 }
5188 flags |= OPEN_PREPARED;
5189 }
5191 open_view(view, REQ_VIEW_DIFF, flags);
5192 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5193 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5194 break;
5196 default:
5197 return request;
5198 }
5200 return REQ_NONE;
5201 }
5203 static bool
5204 blame_grep(struct view *view, struct line *line)
5205 {
5206 struct blame *blame = line->data;
5207 struct blame_commit *commit = blame->commit;
5208 const char *text[] = {
5209 blame->text,
5210 commit ? commit->title : "",
5211 commit ? commit->id : "",
5212 commit && opt_author ? commit->author : "",
5213 commit ? mkdate(&commit->time, opt_date) : "",
5214 NULL
5215 };
5217 return grep_text(view, text);
5218 }
5220 static void
5221 blame_select(struct view *view, struct line *line)
5222 {
5223 struct blame *blame = line->data;
5224 struct blame_commit *commit = blame->commit;
5226 if (!commit)
5227 return;
5229 if (!strcmp(commit->id, NULL_ID))
5230 string_ncopy(ref_commit, "HEAD", 4);
5231 else
5232 string_copy_rev(ref_commit, commit->id);
5233 }
5235 static struct view_ops blame_ops = {
5236 "line",
5237 NULL,
5238 blame_open,
5239 blame_read,
5240 blame_draw,
5241 blame_request,
5242 blame_grep,
5243 blame_select,
5244 };
5246 /*
5247 * Branch backend
5248 */
5250 struct branch {
5251 const char *author; /* Author of the last commit. */
5252 struct time time; /* Date of the last activity. */
5253 const struct ref *ref; /* Name and commit ID information. */
5254 };
5256 static const struct ref branch_all;
5258 static const enum sort_field branch_sort_fields[] = {
5259 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5260 };
5261 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5263 static int
5264 branch_compare(const void *l1, const void *l2)
5265 {
5266 const struct branch *branch1 = ((const struct line *) l1)->data;
5267 const struct branch *branch2 = ((const struct line *) l2)->data;
5269 switch (get_sort_field(branch_sort_state)) {
5270 case ORDERBY_DATE:
5271 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5273 case ORDERBY_AUTHOR:
5274 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5276 case ORDERBY_NAME:
5277 default:
5278 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5279 }
5280 }
5282 static bool
5283 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5284 {
5285 struct branch *branch = line->data;
5286 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5288 if (opt_date && draw_date(view, &branch->time))
5289 return TRUE;
5291 if (opt_author && draw_author(view, branch->author))
5292 return TRUE;
5294 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5295 return TRUE;
5296 }
5298 static enum request
5299 branch_request(struct view *view, enum request request, struct line *line)
5300 {
5301 struct branch *branch = line->data;
5303 switch (request) {
5304 case REQ_REFRESH:
5305 load_refs();
5306 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5307 return REQ_NONE;
5309 case REQ_TOGGLE_SORT_FIELD:
5310 case REQ_TOGGLE_SORT_ORDER:
5311 sort_view(view, request, &branch_sort_state, branch_compare);
5312 return REQ_NONE;
5314 case REQ_ENTER:
5315 if (branch->ref == &branch_all) {
5316 const char *all_branches_argv[] = {
5317 "git", "log", "--no-color", "--pretty=raw", "--parents",
5318 "--topo-order", "--all", NULL
5319 };
5320 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5322 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5323 report("Failed to load view of all branches");
5324 return REQ_NONE;
5325 }
5326 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5327 } else {
5328 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5329 }
5330 return REQ_NONE;
5332 default:
5333 return request;
5334 }
5335 }
5337 static bool
5338 branch_read(struct view *view, char *line)
5339 {
5340 static char id[SIZEOF_REV];
5341 struct branch *reference;
5342 size_t i;
5344 if (!line)
5345 return TRUE;
5347 switch (get_line_type(line)) {
5348 case LINE_COMMIT:
5349 string_copy_rev(id, line + STRING_SIZE("commit "));
5350 return TRUE;
5352 case LINE_AUTHOR:
5353 for (i = 0, reference = NULL; i < view->lines; i++) {
5354 struct branch *branch = view->line[i].data;
5356 if (strcmp(branch->ref->id, id))
5357 continue;
5359 view->line[i].dirty = TRUE;
5360 if (reference) {
5361 branch->author = reference->author;
5362 branch->time = reference->time;
5363 continue;
5364 }
5366 parse_author_line(line + STRING_SIZE("author "),
5367 &branch->author, &branch->time);
5368 reference = branch;
5369 }
5370 return TRUE;
5372 default:
5373 return TRUE;
5374 }
5376 }
5378 static bool
5379 branch_open_visitor(void *data, const struct ref *ref)
5380 {
5381 struct view *view = data;
5382 struct branch *branch;
5384 if (ref->tag || ref->ltag || ref->remote)
5385 return TRUE;
5387 branch = calloc(1, sizeof(*branch));
5388 if (!branch)
5389 return FALSE;
5391 branch->ref = ref;
5392 return !!add_line_data(view, branch, LINE_DEFAULT);
5393 }
5395 static bool
5396 branch_open(struct view *view)
5397 {
5398 const char *branch_log[] = {
5399 "git", "log", "--no-color", "--pretty=raw",
5400 "--simplify-by-decoration", "--all", NULL
5401 };
5403 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5404 report("Failed to load branch data");
5405 return TRUE;
5406 }
5408 setup_update(view, view->id);
5409 branch_open_visitor(view, &branch_all);
5410 foreach_ref(branch_open_visitor, view);
5411 view->p_restore = TRUE;
5413 return TRUE;
5414 }
5416 static bool
5417 branch_grep(struct view *view, struct line *line)
5418 {
5419 struct branch *branch = line->data;
5420 const char *text[] = {
5421 branch->ref->name,
5422 branch->author,
5423 NULL
5424 };
5426 return grep_text(view, text);
5427 }
5429 static void
5430 branch_select(struct view *view, struct line *line)
5431 {
5432 struct branch *branch = line->data;
5434 string_copy_rev(view->ref, branch->ref->id);
5435 string_copy_rev(ref_commit, branch->ref->id);
5436 string_copy_rev(ref_head, branch->ref->id);
5437 string_copy_rev(ref_branch, branch->ref->name);
5438 }
5440 static struct view_ops branch_ops = {
5441 "branch",
5442 NULL,
5443 branch_open,
5444 branch_read,
5445 branch_draw,
5446 branch_request,
5447 branch_grep,
5448 branch_select,
5449 };
5451 /*
5452 * Status backend
5453 */
5455 struct status {
5456 char status;
5457 struct {
5458 mode_t mode;
5459 char rev[SIZEOF_REV];
5460 char name[SIZEOF_STR];
5461 } old;
5462 struct {
5463 mode_t mode;
5464 char rev[SIZEOF_REV];
5465 char name[SIZEOF_STR];
5466 } new;
5467 };
5469 static char status_onbranch[SIZEOF_STR];
5470 static struct status stage_status;
5471 static enum line_type stage_line_type;
5472 static size_t stage_chunks;
5473 static int *stage_chunk;
5475 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5477 /* This should work even for the "On branch" line. */
5478 static inline bool
5479 status_has_none(struct view *view, struct line *line)
5480 {
5481 return line < view->line + view->lines && !line[1].data;
5482 }
5484 /* Get fields from the diff line:
5485 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5486 */
5487 static inline bool
5488 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5489 {
5490 const char *old_mode = buf + 1;
5491 const char *new_mode = buf + 8;
5492 const char *old_rev = buf + 15;
5493 const char *new_rev = buf + 56;
5494 const char *status = buf + 97;
5496 if (bufsize < 98 ||
5497 old_mode[-1] != ':' ||
5498 new_mode[-1] != ' ' ||
5499 old_rev[-1] != ' ' ||
5500 new_rev[-1] != ' ' ||
5501 status[-1] != ' ')
5502 return FALSE;
5504 file->status = *status;
5506 string_copy_rev(file->old.rev, old_rev);
5507 string_copy_rev(file->new.rev, new_rev);
5509 file->old.mode = strtoul(old_mode, NULL, 8);
5510 file->new.mode = strtoul(new_mode, NULL, 8);
5512 file->old.name[0] = file->new.name[0] = 0;
5514 return TRUE;
5515 }
5517 static bool
5518 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5519 {
5520 struct status *unmerged = NULL;
5521 char *buf;
5522 struct io io = {};
5524 if (!io_run(&io, argv, opt_cdup, IO_RD))
5525 return FALSE;
5527 add_line_data(view, NULL, type);
5529 while ((buf = io_get(&io, 0, TRUE))) {
5530 struct status *file = unmerged;
5532 if (!file) {
5533 file = calloc(1, sizeof(*file));
5534 if (!file || !add_line_data(view, file, type))
5535 goto error_out;
5536 }
5538 /* Parse diff info part. */
5539 if (status) {
5540 file->status = status;
5541 if (status == 'A')
5542 string_copy(file->old.rev, NULL_ID);
5544 } else if (!file->status || file == unmerged) {
5545 if (!status_get_diff(file, buf, strlen(buf)))
5546 goto error_out;
5548 buf = io_get(&io, 0, TRUE);
5549 if (!buf)
5550 break;
5552 /* Collapse all modified entries that follow an
5553 * associated unmerged entry. */
5554 if (unmerged == file) {
5555 unmerged->status = 'U';
5556 unmerged = NULL;
5557 } else if (file->status == 'U') {
5558 unmerged = file;
5559 }
5560 }
5562 /* Grab the old name for rename/copy. */
5563 if (!*file->old.name &&
5564 (file->status == 'R' || file->status == 'C')) {
5565 string_ncopy(file->old.name, buf, strlen(buf));
5567 buf = io_get(&io, 0, TRUE);
5568 if (!buf)
5569 break;
5570 }
5572 /* git-ls-files just delivers a NUL separated list of
5573 * file names similar to the second half of the
5574 * git-diff-* output. */
5575 string_ncopy(file->new.name, buf, strlen(buf));
5576 if (!*file->old.name)
5577 string_copy(file->old.name, file->new.name);
5578 file = NULL;
5579 }
5581 if (io_error(&io)) {
5582 error_out:
5583 io_done(&io);
5584 return FALSE;
5585 }
5587 if (!view->line[view->lines - 1].data)
5588 add_line_data(view, NULL, LINE_STAT_NONE);
5590 io_done(&io);
5591 return TRUE;
5592 }
5594 /* Don't show unmerged entries in the staged section. */
5595 static const char *status_diff_index_argv[] = {
5596 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5597 "--cached", "-M", "HEAD", NULL
5598 };
5600 static const char *status_diff_files_argv[] = {
5601 "git", "diff-files", "-z", NULL
5602 };
5604 static const char *status_list_other_argv[] = {
5605 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5606 };
5608 static const char *status_list_no_head_argv[] = {
5609 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5610 };
5612 static const char *update_index_argv[] = {
5613 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5614 };
5616 /* Restore the previous line number to stay in the context or select a
5617 * line with something that can be updated. */
5618 static void
5619 status_restore(struct view *view)
5620 {
5621 if (view->p_lineno >= view->lines)
5622 view->p_lineno = view->lines - 1;
5623 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5624 view->p_lineno++;
5625 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5626 view->p_lineno--;
5628 /* If the above fails, always skip the "On branch" line. */
5629 if (view->p_lineno < view->lines)
5630 view->lineno = view->p_lineno;
5631 else
5632 view->lineno = 1;
5634 if (view->lineno < view->offset)
5635 view->offset = view->lineno;
5636 else if (view->offset + view->height <= view->lineno)
5637 view->offset = view->lineno - view->height + 1;
5639 view->p_restore = FALSE;
5640 }
5642 static void
5643 status_update_onbranch(void)
5644 {
5645 static const char *paths[][2] = {
5646 { "rebase-apply/rebasing", "Rebasing" },
5647 { "rebase-apply/applying", "Applying mailbox" },
5648 { "rebase-apply/", "Rebasing mailbox" },
5649 { "rebase-merge/interactive", "Interactive rebase" },
5650 { "rebase-merge/", "Rebase merge" },
5651 { "MERGE_HEAD", "Merging" },
5652 { "BISECT_LOG", "Bisecting" },
5653 { "HEAD", "On branch" },
5654 };
5655 char buf[SIZEOF_STR];
5656 struct stat stat;
5657 int i;
5659 if (is_initial_commit()) {
5660 string_copy(status_onbranch, "Initial commit");
5661 return;
5662 }
5664 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5665 char *head = opt_head;
5667 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5668 lstat(buf, &stat) < 0)
5669 continue;
5671 if (!*opt_head) {
5672 struct io io = {};
5674 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5675 io_read_buf(&io, buf, sizeof(buf))) {
5676 head = buf;
5677 if (!prefixcmp(head, "refs/heads/"))
5678 head += STRING_SIZE("refs/heads/");
5679 }
5680 }
5682 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5683 string_copy(status_onbranch, opt_head);
5684 return;
5685 }
5687 string_copy(status_onbranch, "Not currently on any branch");
5688 }
5690 /* First parse staged info using git-diff-index(1), then parse unstaged
5691 * info using git-diff-files(1), and finally untracked files using
5692 * git-ls-files(1). */
5693 static bool
5694 status_open(struct view *view)
5695 {
5696 reset_view(view);
5698 add_line_data(view, NULL, LINE_STAT_HEAD);
5699 status_update_onbranch();
5701 io_run_bg(update_index_argv);
5703 if (is_initial_commit()) {
5704 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5705 return FALSE;
5706 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5707 return FALSE;
5708 }
5710 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5711 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5712 return FALSE;
5714 /* Restore the exact position or use the specialized restore
5715 * mode? */
5716 if (!view->p_restore)
5717 status_restore(view);
5718 return TRUE;
5719 }
5721 static bool
5722 status_draw(struct view *view, struct line *line, unsigned int lineno)
5723 {
5724 struct status *status = line->data;
5725 enum line_type type;
5726 const char *text;
5728 if (!status) {
5729 switch (line->type) {
5730 case LINE_STAT_STAGED:
5731 type = LINE_STAT_SECTION;
5732 text = "Changes to be committed:";
5733 break;
5735 case LINE_STAT_UNSTAGED:
5736 type = LINE_STAT_SECTION;
5737 text = "Changed but not updated:";
5738 break;
5740 case LINE_STAT_UNTRACKED:
5741 type = LINE_STAT_SECTION;
5742 text = "Untracked files:";
5743 break;
5745 case LINE_STAT_NONE:
5746 type = LINE_DEFAULT;
5747 text = " (no files)";
5748 break;
5750 case LINE_STAT_HEAD:
5751 type = LINE_STAT_HEAD;
5752 text = status_onbranch;
5753 break;
5755 default:
5756 return FALSE;
5757 }
5758 } else {
5759 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5761 buf[0] = status->status;
5762 if (draw_text(view, line->type, buf, TRUE))
5763 return TRUE;
5764 type = LINE_DEFAULT;
5765 text = status->new.name;
5766 }
5768 draw_text(view, type, text, TRUE);
5769 return TRUE;
5770 }
5772 static enum request
5773 status_load_error(struct view *view, struct view *stage, const char *path)
5774 {
5775 if (displayed_views() == 2 || display[current_view] != view)
5776 maximize_view(view);
5777 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5778 return REQ_NONE;
5779 }
5781 static enum request
5782 status_enter(struct view *view, struct line *line)
5783 {
5784 struct status *status = line->data;
5785 const char *oldpath = status ? status->old.name : NULL;
5786 /* Diffs for unmerged entries are empty when passing the new
5787 * path, so leave it empty. */
5788 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5789 const char *info;
5790 enum open_flags split;
5791 struct view *stage = VIEW(REQ_VIEW_STAGE);
5793 if (line->type == LINE_STAT_NONE ||
5794 (!status && line[1].type == LINE_STAT_NONE)) {
5795 report("No file to diff");
5796 return REQ_NONE;
5797 }
5799 switch (line->type) {
5800 case LINE_STAT_STAGED:
5801 if (is_initial_commit()) {
5802 const char *no_head_diff_argv[] = {
5803 "git", "diff", "--no-color", "--patch-with-stat",
5804 "--", "/dev/null", newpath, NULL
5805 };
5807 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5808 return status_load_error(view, stage, newpath);
5809 } else {
5810 const char *index_show_argv[] = {
5811 "git", "diff-index", "--root", "--patch-with-stat",
5812 "-C", "-M", "--cached", "HEAD", "--",
5813 oldpath, newpath, NULL
5814 };
5816 if (!prepare_update(stage, index_show_argv, opt_cdup))
5817 return status_load_error(view, stage, newpath);
5818 }
5820 if (status)
5821 info = "Staged changes to %s";
5822 else
5823 info = "Staged changes";
5824 break;
5826 case LINE_STAT_UNSTAGED:
5827 {
5828 const char *files_show_argv[] = {
5829 "git", "diff-files", "--root", "--patch-with-stat",
5830 "-C", "-M", "--", oldpath, newpath, NULL
5831 };
5833 if (!prepare_update(stage, files_show_argv, opt_cdup))
5834 return status_load_error(view, stage, newpath);
5835 if (status)
5836 info = "Unstaged changes to %s";
5837 else
5838 info = "Unstaged changes";
5839 break;
5840 }
5841 case LINE_STAT_UNTRACKED:
5842 if (!newpath) {
5843 report("No file to show");
5844 return REQ_NONE;
5845 }
5847 if (!suffixcmp(status->new.name, -1, "/")) {
5848 report("Cannot display a directory");
5849 return REQ_NONE;
5850 }
5852 if (!prepare_update_file(stage, newpath))
5853 return status_load_error(view, stage, newpath);
5854 info = "Untracked file %s";
5855 break;
5857 case LINE_STAT_HEAD:
5858 return REQ_NONE;
5860 default:
5861 die("line type %d not handled in switch", line->type);
5862 }
5864 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5865 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5866 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5867 if (status) {
5868 stage_status = *status;
5869 } else {
5870 memset(&stage_status, 0, sizeof(stage_status));
5871 }
5873 stage_line_type = line->type;
5874 stage_chunks = 0;
5875 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5876 }
5878 return REQ_NONE;
5879 }
5881 static bool
5882 status_exists(struct status *status, enum line_type type)
5883 {
5884 struct view *view = VIEW(REQ_VIEW_STATUS);
5885 unsigned long lineno;
5887 for (lineno = 0; lineno < view->lines; lineno++) {
5888 struct line *line = &view->line[lineno];
5889 struct status *pos = line->data;
5891 if (line->type != type)
5892 continue;
5893 if (!pos && (!status || !status->status) && line[1].data) {
5894 select_view_line(view, lineno);
5895 return TRUE;
5896 }
5897 if (pos && !strcmp(status->new.name, pos->new.name)) {
5898 select_view_line(view, lineno);
5899 return TRUE;
5900 }
5901 }
5903 return FALSE;
5904 }
5907 static bool
5908 status_update_prepare(struct io *io, enum line_type type)
5909 {
5910 const char *staged_argv[] = {
5911 "git", "update-index", "-z", "--index-info", NULL
5912 };
5913 const char *others_argv[] = {
5914 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5915 };
5917 switch (type) {
5918 case LINE_STAT_STAGED:
5919 return io_run(io, staged_argv, opt_cdup, IO_WR);
5921 case LINE_STAT_UNSTAGED:
5922 case LINE_STAT_UNTRACKED:
5923 return io_run(io, others_argv, opt_cdup, IO_WR);
5925 default:
5926 die("line type %d not handled in switch", type);
5927 return FALSE;
5928 }
5929 }
5931 static bool
5932 status_update_write(struct io *io, struct status *status, enum line_type type)
5933 {
5934 char buf[SIZEOF_STR];
5935 size_t bufsize = 0;
5937 switch (type) {
5938 case LINE_STAT_STAGED:
5939 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5940 status->old.mode,
5941 status->old.rev,
5942 status->old.name, 0))
5943 return FALSE;
5944 break;
5946 case LINE_STAT_UNSTAGED:
5947 case LINE_STAT_UNTRACKED:
5948 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5949 return FALSE;
5950 break;
5952 default:
5953 die("line type %d not handled in switch", type);
5954 }
5956 return io_write(io, buf, bufsize);
5957 }
5959 static bool
5960 status_update_file(struct status *status, enum line_type type)
5961 {
5962 struct io io = {};
5963 bool result;
5965 if (!status_update_prepare(&io, type))
5966 return FALSE;
5968 result = status_update_write(&io, status, type);
5969 return io_done(&io) && result;
5970 }
5972 static bool
5973 status_update_files(struct view *view, struct line *line)
5974 {
5975 char buf[sizeof(view->ref)];
5976 struct io io = {};
5977 bool result = TRUE;
5978 struct line *pos = view->line + view->lines;
5979 int files = 0;
5980 int file, done;
5981 int cursor_y = -1, cursor_x = -1;
5983 if (!status_update_prepare(&io, line->type))
5984 return FALSE;
5986 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5987 files++;
5989 string_copy(buf, view->ref);
5990 getsyx(cursor_y, cursor_x);
5991 for (file = 0, done = 5; result && file < files; line++, file++) {
5992 int almost_done = file * 100 / files;
5994 if (almost_done > done) {
5995 done = almost_done;
5996 string_format(view->ref, "updating file %u of %u (%d%% done)",
5997 file, files, done);
5998 update_view_title(view);
5999 setsyx(cursor_y, cursor_x);
6000 doupdate();
6001 }
6002 result = status_update_write(&io, line->data, line->type);
6003 }
6004 string_copy(view->ref, buf);
6006 return io_done(&io) && result;
6007 }
6009 static bool
6010 status_update(struct view *view)
6011 {
6012 struct line *line = &view->line[view->lineno];
6014 assert(view->lines);
6016 if (!line->data) {
6017 /* This should work even for the "On branch" line. */
6018 if (line < view->line + view->lines && !line[1].data) {
6019 report("Nothing to update");
6020 return FALSE;
6021 }
6023 if (!status_update_files(view, line + 1)) {
6024 report("Failed to update file status");
6025 return FALSE;
6026 }
6028 } else if (!status_update_file(line->data, line->type)) {
6029 report("Failed to update file status");
6030 return FALSE;
6031 }
6033 return TRUE;
6034 }
6036 static bool
6037 status_revert(struct status *status, enum line_type type, bool has_none)
6038 {
6039 if (!status || type != LINE_STAT_UNSTAGED) {
6040 if (type == LINE_STAT_STAGED) {
6041 report("Cannot revert changes to staged files");
6042 } else if (type == LINE_STAT_UNTRACKED) {
6043 report("Cannot revert changes to untracked files");
6044 } else if (has_none) {
6045 report("Nothing to revert");
6046 } else {
6047 report("Cannot revert changes to multiple files");
6048 }
6050 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6051 char mode[10] = "100644";
6052 const char *reset_argv[] = {
6053 "git", "update-index", "--cacheinfo", mode,
6054 status->old.rev, status->old.name, NULL
6055 };
6056 const char *checkout_argv[] = {
6057 "git", "checkout", "--", status->old.name, NULL
6058 };
6060 if (status->status == 'U') {
6061 string_format(mode, "%5o", status->old.mode);
6063 if (status->old.mode == 0 && status->new.mode == 0) {
6064 reset_argv[2] = "--force-remove";
6065 reset_argv[3] = status->old.name;
6066 reset_argv[4] = NULL;
6067 }
6069 if (!io_run_fg(reset_argv, opt_cdup))
6070 return FALSE;
6071 if (status->old.mode == 0 && status->new.mode == 0)
6072 return TRUE;
6073 }
6075 return io_run_fg(checkout_argv, opt_cdup);
6076 }
6078 return FALSE;
6079 }
6081 static enum request
6082 status_request(struct view *view, enum request request, struct line *line)
6083 {
6084 struct status *status = line->data;
6086 switch (request) {
6087 case REQ_STATUS_UPDATE:
6088 if (!status_update(view))
6089 return REQ_NONE;
6090 break;
6092 case REQ_STATUS_REVERT:
6093 if (!status_revert(status, line->type, status_has_none(view, line)))
6094 return REQ_NONE;
6095 break;
6097 case REQ_STATUS_MERGE:
6098 if (!status || status->status != 'U') {
6099 report("Merging only possible for files with unmerged status ('U').");
6100 return REQ_NONE;
6101 }
6102 open_mergetool(status->new.name);
6103 break;
6105 case REQ_EDIT:
6106 if (!status)
6107 return request;
6108 if (status->status == 'D') {
6109 report("File has been deleted.");
6110 return REQ_NONE;
6111 }
6113 open_editor(status->new.name);
6114 break;
6116 case REQ_VIEW_BLAME:
6117 if (status)
6118 opt_ref[0] = 0;
6119 return request;
6121 case REQ_ENTER:
6122 /* After returning the status view has been split to
6123 * show the stage view. No further reloading is
6124 * necessary. */
6125 return status_enter(view, line);
6127 case REQ_REFRESH:
6128 /* Simply reload the view. */
6129 break;
6131 default:
6132 return request;
6133 }
6135 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6137 return REQ_NONE;
6138 }
6140 static void
6141 status_select(struct view *view, struct line *line)
6142 {
6143 struct status *status = line->data;
6144 char file[SIZEOF_STR] = "all files";
6145 const char *text;
6146 const char *key;
6148 if (status && !string_format(file, "'%s'", status->new.name))
6149 return;
6151 if (!status && line[1].type == LINE_STAT_NONE)
6152 line++;
6154 switch (line->type) {
6155 case LINE_STAT_STAGED:
6156 text = "Press %s to unstage %s for commit";
6157 break;
6159 case LINE_STAT_UNSTAGED:
6160 text = "Press %s to stage %s for commit";
6161 break;
6163 case LINE_STAT_UNTRACKED:
6164 text = "Press %s to stage %s for addition";
6165 break;
6167 case LINE_STAT_HEAD:
6168 case LINE_STAT_NONE:
6169 text = "Nothing to update";
6170 break;
6172 default:
6173 die("line type %d not handled in switch", line->type);
6174 }
6176 if (status && status->status == 'U') {
6177 text = "Press %s to resolve conflict in %s";
6178 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6180 } else {
6181 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6182 }
6184 string_format(view->ref, text, key, file);
6185 if (status)
6186 string_copy(opt_file, status->new.name);
6187 }
6189 static bool
6190 status_grep(struct view *view, struct line *line)
6191 {
6192 struct status *status = line->data;
6194 if (status) {
6195 const char buf[2] = { status->status, 0 };
6196 const char *text[] = { status->new.name, buf, NULL };
6198 return grep_text(view, text);
6199 }
6201 return FALSE;
6202 }
6204 static struct view_ops status_ops = {
6205 "file",
6206 NULL,
6207 status_open,
6208 NULL,
6209 status_draw,
6210 status_request,
6211 status_grep,
6212 status_select,
6213 };
6216 static bool
6217 stage_diff_write(struct io *io, struct line *line, struct line *end)
6218 {
6219 while (line < end) {
6220 if (!io_write(io, line->data, strlen(line->data)) ||
6221 !io_write(io, "\n", 1))
6222 return FALSE;
6223 line++;
6224 if (line->type == LINE_DIFF_CHUNK ||
6225 line->type == LINE_DIFF_HEADER)
6226 break;
6227 }
6229 return TRUE;
6230 }
6232 static struct line *
6233 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6234 {
6235 for (; view->line < line; line--)
6236 if (line->type == type)
6237 return line;
6239 return NULL;
6240 }
6242 static bool
6243 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6244 {
6245 const char *apply_argv[SIZEOF_ARG] = {
6246 "git", "apply", "--whitespace=nowarn", NULL
6247 };
6248 struct line *diff_hdr;
6249 struct io io = {};
6250 int argc = 3;
6252 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6253 if (!diff_hdr)
6254 return FALSE;
6256 if (!revert)
6257 apply_argv[argc++] = "--cached";
6258 if (revert || stage_line_type == LINE_STAT_STAGED)
6259 apply_argv[argc++] = "-R";
6260 apply_argv[argc++] = "-";
6261 apply_argv[argc++] = NULL;
6262 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6263 return FALSE;
6265 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6266 !stage_diff_write(&io, chunk, view->line + view->lines))
6267 chunk = NULL;
6269 io_done(&io);
6270 io_run_bg(update_index_argv);
6272 return chunk ? TRUE : FALSE;
6273 }
6275 static bool
6276 stage_update(struct view *view, struct line *line)
6277 {
6278 struct line *chunk = NULL;
6280 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6281 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6283 if (chunk) {
6284 if (!stage_apply_chunk(view, chunk, FALSE)) {
6285 report("Failed to apply chunk");
6286 return FALSE;
6287 }
6289 } else if (!stage_status.status) {
6290 view = VIEW(REQ_VIEW_STATUS);
6292 for (line = view->line; line < view->line + view->lines; line++)
6293 if (line->type == stage_line_type)
6294 break;
6296 if (!status_update_files(view, line + 1)) {
6297 report("Failed to update files");
6298 return FALSE;
6299 }
6301 } else if (!status_update_file(&stage_status, stage_line_type)) {
6302 report("Failed to update file");
6303 return FALSE;
6304 }
6306 return TRUE;
6307 }
6309 static bool
6310 stage_revert(struct view *view, struct line *line)
6311 {
6312 struct line *chunk = NULL;
6314 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6315 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6317 if (chunk) {
6318 if (!prompt_yesno("Are you sure you want to revert changes?"))
6319 return FALSE;
6321 if (!stage_apply_chunk(view, chunk, TRUE)) {
6322 report("Failed to revert chunk");
6323 return FALSE;
6324 }
6325 return TRUE;
6327 } else {
6328 return status_revert(stage_status.status ? &stage_status : NULL,
6329 stage_line_type, FALSE);
6330 }
6331 }
6334 static void
6335 stage_next(struct view *view, struct line *line)
6336 {
6337 int i;
6339 if (!stage_chunks) {
6340 for (line = view->line; line < view->line + view->lines; line++) {
6341 if (line->type != LINE_DIFF_CHUNK)
6342 continue;
6344 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6345 report("Allocation failure");
6346 return;
6347 }
6349 stage_chunk[stage_chunks++] = line - view->line;
6350 }
6351 }
6353 for (i = 0; i < stage_chunks; i++) {
6354 if (stage_chunk[i] > view->lineno) {
6355 do_scroll_view(view, stage_chunk[i] - view->lineno);
6356 report("Chunk %d of %d", i + 1, stage_chunks);
6357 return;
6358 }
6359 }
6361 report("No next chunk found");
6362 }
6364 static enum request
6365 stage_request(struct view *view, enum request request, struct line *line)
6366 {
6367 switch (request) {
6368 case REQ_STATUS_UPDATE:
6369 if (!stage_update(view, line))
6370 return REQ_NONE;
6371 break;
6373 case REQ_STATUS_REVERT:
6374 if (!stage_revert(view, line))
6375 return REQ_NONE;
6376 break;
6378 case REQ_STAGE_NEXT:
6379 if (stage_line_type == LINE_STAT_UNTRACKED) {
6380 report("File is untracked; press %s to add",
6381 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6382 return REQ_NONE;
6383 }
6384 stage_next(view, line);
6385 return REQ_NONE;
6387 case REQ_EDIT:
6388 if (!stage_status.new.name[0])
6389 return request;
6390 if (stage_status.status == 'D') {
6391 report("File has been deleted.");
6392 return REQ_NONE;
6393 }
6395 open_editor(stage_status.new.name);
6396 break;
6398 case REQ_REFRESH:
6399 /* Reload everything ... */
6400 break;
6402 case REQ_VIEW_BLAME:
6403 if (stage_status.new.name[0]) {
6404 string_copy(opt_file, stage_status.new.name);
6405 opt_ref[0] = 0;
6406 }
6407 return request;
6409 case REQ_ENTER:
6410 return pager_request(view, request, line);
6412 default:
6413 return request;
6414 }
6416 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6417 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6419 /* Check whether the staged entry still exists, and close the
6420 * stage view if it doesn't. */
6421 if (!status_exists(&stage_status, stage_line_type)) {
6422 status_restore(VIEW(REQ_VIEW_STATUS));
6423 return REQ_VIEW_CLOSE;
6424 }
6426 if (stage_line_type == LINE_STAT_UNTRACKED) {
6427 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6428 report("Cannot display a directory");
6429 return REQ_NONE;
6430 }
6432 if (!prepare_update_file(view, stage_status.new.name)) {
6433 report("Failed to open file: %s", strerror(errno));
6434 return REQ_NONE;
6435 }
6436 }
6437 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6439 return REQ_NONE;
6440 }
6442 static struct view_ops stage_ops = {
6443 "line",
6444 NULL,
6445 NULL,
6446 pager_read,
6447 pager_draw,
6448 stage_request,
6449 pager_grep,
6450 pager_select,
6451 };
6454 /*
6455 * Revision graph
6456 */
6458 struct commit {
6459 char id[SIZEOF_REV]; /* SHA1 ID. */
6460 char title[128]; /* First line of the commit message. */
6461 const char *author; /* Author of the commit. */
6462 struct time time; /* Date from the author ident. */
6463 struct ref_list *refs; /* Repository references. */
6464 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6465 size_t graph_size; /* The width of the graph array. */
6466 bool has_parents; /* Rewritten --parents seen. */
6467 };
6469 /* Size of rev graph with no "padding" columns */
6470 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6472 struct rev_graph {
6473 struct rev_graph *prev, *next, *parents;
6474 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6475 size_t size;
6476 struct commit *commit;
6477 size_t pos;
6478 unsigned int boundary:1;
6479 };
6481 /* Parents of the commit being visualized. */
6482 static struct rev_graph graph_parents[4];
6484 /* The current stack of revisions on the graph. */
6485 static struct rev_graph graph_stacks[4] = {
6486 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6487 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6488 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6489 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6490 };
6492 static inline bool
6493 graph_parent_is_merge(struct rev_graph *graph)
6494 {
6495 return graph->parents->size > 1;
6496 }
6498 static inline void
6499 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6500 {
6501 struct commit *commit = graph->commit;
6503 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6504 commit->graph[commit->graph_size++] = symbol;
6505 }
6507 static void
6508 clear_rev_graph(struct rev_graph *graph)
6509 {
6510 graph->boundary = 0;
6511 graph->size = graph->pos = 0;
6512 graph->commit = NULL;
6513 memset(graph->parents, 0, sizeof(*graph->parents));
6514 }
6516 static void
6517 done_rev_graph(struct rev_graph *graph)
6518 {
6519 if (graph_parent_is_merge(graph) &&
6520 graph->pos < graph->size - 1 &&
6521 graph->next->size == graph->size + graph->parents->size - 1) {
6522 size_t i = graph->pos + graph->parents->size - 1;
6524 graph->commit->graph_size = i * 2;
6525 while (i < graph->next->size - 1) {
6526 append_to_rev_graph(graph, ' ');
6527 append_to_rev_graph(graph, '\\');
6528 i++;
6529 }
6530 }
6532 clear_rev_graph(graph);
6533 }
6535 static void
6536 push_rev_graph(struct rev_graph *graph, const char *parent)
6537 {
6538 int i;
6540 /* "Collapse" duplicate parents lines.
6541 *
6542 * FIXME: This needs to also update update the drawn graph but
6543 * for now it just serves as a method for pruning graph lines. */
6544 for (i = 0; i < graph->size; i++)
6545 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6546 return;
6548 if (graph->size < SIZEOF_REVITEMS) {
6549 string_copy_rev(graph->rev[graph->size++], parent);
6550 }
6551 }
6553 static chtype
6554 get_rev_graph_symbol(struct rev_graph *graph)
6555 {
6556 chtype symbol;
6558 if (graph->boundary)
6559 symbol = REVGRAPH_BOUND;
6560 else if (graph->parents->size == 0)
6561 symbol = REVGRAPH_INIT;
6562 else if (graph_parent_is_merge(graph))
6563 symbol = REVGRAPH_MERGE;
6564 else if (graph->pos >= graph->size)
6565 symbol = REVGRAPH_BRANCH;
6566 else
6567 symbol = REVGRAPH_COMMIT;
6569 return symbol;
6570 }
6572 static void
6573 draw_rev_graph(struct rev_graph *graph)
6574 {
6575 struct rev_filler {
6576 chtype separator, line;
6577 };
6578 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6579 static struct rev_filler fillers[] = {
6580 { ' ', '|' },
6581 { '`', '.' },
6582 { '\'', ' ' },
6583 { '/', ' ' },
6584 };
6585 chtype symbol = get_rev_graph_symbol(graph);
6586 struct rev_filler *filler;
6587 size_t i;
6589 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6590 filler = &fillers[DEFAULT];
6592 for (i = 0; i < graph->pos; i++) {
6593 append_to_rev_graph(graph, filler->line);
6594 if (graph_parent_is_merge(graph->prev) &&
6595 graph->prev->pos == i)
6596 filler = &fillers[RSHARP];
6598 append_to_rev_graph(graph, filler->separator);
6599 }
6601 /* Place the symbol for this revision. */
6602 append_to_rev_graph(graph, symbol);
6604 if (graph->prev->size > graph->size)
6605 filler = &fillers[RDIAG];
6606 else
6607 filler = &fillers[DEFAULT];
6609 i++;
6611 for (; i < graph->size; i++) {
6612 append_to_rev_graph(graph, filler->separator);
6613 append_to_rev_graph(graph, filler->line);
6614 if (graph_parent_is_merge(graph->prev) &&
6615 i < graph->prev->pos + graph->parents->size)
6616 filler = &fillers[RSHARP];
6617 if (graph->prev->size > graph->size)
6618 filler = &fillers[LDIAG];
6619 }
6621 if (graph->prev->size > graph->size) {
6622 append_to_rev_graph(graph, filler->separator);
6623 if (filler->line != ' ')
6624 append_to_rev_graph(graph, filler->line);
6625 }
6626 }
6628 /* Prepare the next rev graph */
6629 static void
6630 prepare_rev_graph(struct rev_graph *graph)
6631 {
6632 size_t i;
6634 /* First, traverse all lines of revisions up to the active one. */
6635 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6636 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6637 break;
6639 push_rev_graph(graph->next, graph->rev[graph->pos]);
6640 }
6642 /* Interleave the new revision parent(s). */
6643 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6644 push_rev_graph(graph->next, graph->parents->rev[i]);
6646 /* Lastly, put any remaining revisions. */
6647 for (i = graph->pos + 1; i < graph->size; i++)
6648 push_rev_graph(graph->next, graph->rev[i]);
6649 }
6651 static void
6652 update_rev_graph(struct view *view, struct rev_graph *graph)
6653 {
6654 /* If this is the finalizing update ... */
6655 if (graph->commit)
6656 prepare_rev_graph(graph);
6658 /* Graph visualization needs a one rev look-ahead,
6659 * so the first update doesn't visualize anything. */
6660 if (!graph->prev->commit)
6661 return;
6663 if (view->lines > 2)
6664 view->line[view->lines - 3].dirty = 1;
6665 if (view->lines > 1)
6666 view->line[view->lines - 2].dirty = 1;
6667 draw_rev_graph(graph->prev);
6668 done_rev_graph(graph->prev->prev);
6669 }
6672 /*
6673 * Main view backend
6674 */
6676 static const char *main_argv[SIZEOF_ARG] = {
6677 "git", "log", "--no-color", "--pretty=raw", "--parents",
6678 "--topo-order", "%(head)", NULL
6679 };
6681 static bool
6682 main_draw(struct view *view, struct line *line, unsigned int lineno)
6683 {
6684 struct commit *commit = line->data;
6686 if (!commit->author)
6687 return FALSE;
6689 if (opt_date && draw_date(view, &commit->time))
6690 return TRUE;
6692 if (opt_author && draw_author(view, commit->author))
6693 return TRUE;
6695 if (opt_rev_graph && commit->graph_size &&
6696 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6697 return TRUE;
6699 if (opt_show_refs && commit->refs) {
6700 size_t i;
6702 for (i = 0; i < commit->refs->size; i++) {
6703 struct ref *ref = commit->refs->refs[i];
6704 enum line_type type;
6706 if (ref->head)
6707 type = LINE_MAIN_HEAD;
6708 else if (ref->ltag)
6709 type = LINE_MAIN_LOCAL_TAG;
6710 else if (ref->tag)
6711 type = LINE_MAIN_TAG;
6712 else if (ref->tracked)
6713 type = LINE_MAIN_TRACKED;
6714 else if (ref->remote)
6715 type = LINE_MAIN_REMOTE;
6716 else
6717 type = LINE_MAIN_REF;
6719 if (draw_text(view, type, "[", TRUE) ||
6720 draw_text(view, type, ref->name, TRUE) ||
6721 draw_text(view, type, "]", TRUE))
6722 return TRUE;
6724 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6725 return TRUE;
6726 }
6727 }
6729 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6730 return TRUE;
6731 }
6733 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6734 static bool
6735 main_read(struct view *view, char *line)
6736 {
6737 static struct rev_graph *graph = graph_stacks;
6738 enum line_type type;
6739 struct commit *commit;
6741 if (!line) {
6742 int i;
6744 if (!view->lines && !view->parent)
6745 die("No revisions match the given arguments.");
6746 if (view->lines > 0) {
6747 commit = view->line[view->lines - 1].data;
6748 view->line[view->lines - 1].dirty = 1;
6749 if (!commit->author) {
6750 view->lines--;
6751 free(commit);
6752 graph->commit = NULL;
6753 }
6754 }
6755 update_rev_graph(view, graph);
6757 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6758 clear_rev_graph(&graph_stacks[i]);
6759 return TRUE;
6760 }
6762 type = get_line_type(line);
6763 if (type == LINE_COMMIT) {
6764 commit = calloc(1, sizeof(struct commit));
6765 if (!commit)
6766 return FALSE;
6768 line += STRING_SIZE("commit ");
6769 if (*line == '-') {
6770 graph->boundary = 1;
6771 line++;
6772 }
6774 string_copy_rev(commit->id, line);
6775 commit->refs = get_ref_list(commit->id);
6776 graph->commit = commit;
6777 add_line_data(view, commit, LINE_MAIN_COMMIT);
6779 while ((line = strchr(line, ' '))) {
6780 line++;
6781 push_rev_graph(graph->parents, line);
6782 commit->has_parents = TRUE;
6783 }
6784 return TRUE;
6785 }
6787 if (!view->lines)
6788 return TRUE;
6789 commit = view->line[view->lines - 1].data;
6791 switch (type) {
6792 case LINE_PARENT:
6793 if (commit->has_parents)
6794 break;
6795 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6796 break;
6798 case LINE_AUTHOR:
6799 parse_author_line(line + STRING_SIZE("author "),
6800 &commit->author, &commit->time);
6801 update_rev_graph(view, graph);
6802 graph = graph->next;
6803 break;
6805 default:
6806 /* Fill in the commit title if it has not already been set. */
6807 if (commit->title[0])
6808 break;
6810 /* Require titles to start with a non-space character at the
6811 * offset used by git log. */
6812 if (strncmp(line, " ", 4))
6813 break;
6814 line += 4;
6815 /* Well, if the title starts with a whitespace character,
6816 * try to be forgiving. Otherwise we end up with no title. */
6817 while (isspace(*line))
6818 line++;
6819 if (*line == '\0')
6820 break;
6821 /* FIXME: More graceful handling of titles; append "..." to
6822 * shortened titles, etc. */
6824 string_expand(commit->title, sizeof(commit->title), line, 1);
6825 view->line[view->lines - 1].dirty = 1;
6826 }
6828 return TRUE;
6829 }
6831 static enum request
6832 main_request(struct view *view, enum request request, struct line *line)
6833 {
6834 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6836 switch (request) {
6837 case REQ_ENTER:
6838 open_view(view, REQ_VIEW_DIFF, flags);
6839 break;
6840 case REQ_REFRESH:
6841 load_refs();
6842 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6843 break;
6844 default:
6845 return request;
6846 }
6848 return REQ_NONE;
6849 }
6851 static bool
6852 grep_refs(struct ref_list *list, regex_t *regex)
6853 {
6854 regmatch_t pmatch;
6855 size_t i;
6857 if (!opt_show_refs || !list)
6858 return FALSE;
6860 for (i = 0; i < list->size; i++) {
6861 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6862 return TRUE;
6863 }
6865 return FALSE;
6866 }
6868 static bool
6869 main_grep(struct view *view, struct line *line)
6870 {
6871 struct commit *commit = line->data;
6872 const char *text[] = {
6873 commit->title,
6874 opt_author ? commit->author : "",
6875 mkdate(&commit->time, opt_date),
6876 NULL
6877 };
6879 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6880 }
6882 static void
6883 main_select(struct view *view, struct line *line)
6884 {
6885 struct commit *commit = line->data;
6887 string_copy_rev(view->ref, commit->id);
6888 string_copy_rev(ref_commit, view->ref);
6889 }
6891 static struct view_ops main_ops = {
6892 "commit",
6893 main_argv,
6894 NULL,
6895 main_read,
6896 main_draw,
6897 main_request,
6898 main_grep,
6899 main_select,
6900 };
6903 /*
6904 * Status management
6905 */
6907 /* Whether or not the curses interface has been initialized. */
6908 static bool cursed = FALSE;
6910 /* Terminal hacks and workarounds. */
6911 static bool use_scroll_redrawwin;
6912 static bool use_scroll_status_wclear;
6914 /* The status window is used for polling keystrokes. */
6915 static WINDOW *status_win;
6917 /* Reading from the prompt? */
6918 static bool input_mode = FALSE;
6920 static bool status_empty = FALSE;
6922 /* Update status and title window. */
6923 static void
6924 report(const char *msg, ...)
6925 {
6926 struct view *view = display[current_view];
6928 if (input_mode)
6929 return;
6931 if (!view) {
6932 char buf[SIZEOF_STR];
6933 va_list args;
6935 va_start(args, msg);
6936 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6937 buf[sizeof(buf) - 1] = 0;
6938 buf[sizeof(buf) - 2] = '.';
6939 buf[sizeof(buf) - 3] = '.';
6940 buf[sizeof(buf) - 4] = '.';
6941 }
6942 va_end(args);
6943 die("%s", buf);
6944 }
6946 if (!status_empty || *msg) {
6947 va_list args;
6949 va_start(args, msg);
6951 wmove(status_win, 0, 0);
6952 if (view->has_scrolled && use_scroll_status_wclear)
6953 wclear(status_win);
6954 if (*msg) {
6955 vwprintw(status_win, msg, args);
6956 status_empty = FALSE;
6957 } else {
6958 status_empty = TRUE;
6959 }
6960 wclrtoeol(status_win);
6961 wnoutrefresh(status_win);
6963 va_end(args);
6964 }
6966 update_view_title(view);
6967 }
6969 static void
6970 init_display(void)
6971 {
6972 const char *term;
6973 int x, y;
6975 /* Initialize the curses library */
6976 if (isatty(STDIN_FILENO)) {
6977 cursed = !!initscr();
6978 opt_tty = stdin;
6979 } else {
6980 /* Leave stdin and stdout alone when acting as a pager. */
6981 opt_tty = fopen("/dev/tty", "r+");
6982 if (!opt_tty)
6983 die("Failed to open /dev/tty");
6984 cursed = !!newterm(NULL, opt_tty, opt_tty);
6985 }
6987 if (!cursed)
6988 die("Failed to initialize curses");
6990 nonl(); /* Disable conversion and detect newlines from input. */
6991 cbreak(); /* Take input chars one at a time, no wait for \n */
6992 noecho(); /* Don't echo input */
6993 leaveok(stdscr, FALSE);
6995 if (has_colors())
6996 init_colors();
6998 getmaxyx(stdscr, y, x);
6999 status_win = newwin(1, 0, y - 1, 0);
7000 if (!status_win)
7001 die("Failed to create status window");
7003 /* Enable keyboard mapping */
7004 keypad(status_win, TRUE);
7005 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7007 TABSIZE = opt_tab_size;
7009 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7010 if (term && !strcmp(term, "gnome-terminal")) {
7011 /* In the gnome-terminal-emulator, the message from
7012 * scrolling up one line when impossible followed by
7013 * scrolling down one line causes corruption of the
7014 * status line. This is fixed by calling wclear. */
7015 use_scroll_status_wclear = TRUE;
7016 use_scroll_redrawwin = FALSE;
7018 } else if (term && !strcmp(term, "xrvt-xpm")) {
7019 /* No problems with full optimizations in xrvt-(unicode)
7020 * and aterm. */
7021 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7023 } else {
7024 /* When scrolling in (u)xterm the last line in the
7025 * scrolling direction will update slowly. */
7026 use_scroll_redrawwin = TRUE;
7027 use_scroll_status_wclear = FALSE;
7028 }
7029 }
7031 static int
7032 get_input(int prompt_position)
7033 {
7034 struct view *view;
7035 int i, key, cursor_y, cursor_x;
7036 bool loading = FALSE;
7038 if (prompt_position)
7039 input_mode = TRUE;
7041 while (TRUE) {
7042 foreach_view (view, i) {
7043 update_view(view);
7044 if (view_is_displayed(view) && view->has_scrolled &&
7045 use_scroll_redrawwin)
7046 redrawwin(view->win);
7047 view->has_scrolled = FALSE;
7048 if (view->pipe)
7049 loading = TRUE;
7050 }
7052 /* Update the cursor position. */
7053 if (prompt_position) {
7054 getbegyx(status_win, cursor_y, cursor_x);
7055 cursor_x = prompt_position;
7056 } else {
7057 view = display[current_view];
7058 getbegyx(view->win, cursor_y, cursor_x);
7059 cursor_x = view->width - 1;
7060 cursor_y += view->lineno - view->offset;
7061 }
7062 setsyx(cursor_y, cursor_x);
7064 /* Refresh, accept single keystroke of input */
7065 doupdate();
7066 nodelay(status_win, loading);
7067 key = wgetch(status_win);
7069 /* wgetch() with nodelay() enabled returns ERR when
7070 * there's no input. */
7071 if (key == ERR) {
7073 } else if (key == KEY_RESIZE) {
7074 int height, width;
7076 getmaxyx(stdscr, height, width);
7078 wresize(status_win, 1, width);
7079 mvwin(status_win, height - 1, 0);
7080 wnoutrefresh(status_win);
7081 resize_display();
7082 redraw_display(TRUE);
7084 } else {
7085 input_mode = FALSE;
7086 return key;
7087 }
7088 }
7089 }
7091 static char *
7092 prompt_input(const char *prompt, input_handler handler, void *data)
7093 {
7094 enum input_status status = INPUT_OK;
7095 static char buf[SIZEOF_STR];
7096 size_t pos = 0;
7098 buf[pos] = 0;
7100 while (status == INPUT_OK || status == INPUT_SKIP) {
7101 int key;
7103 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7104 wclrtoeol(status_win);
7106 key = get_input(pos + 1);
7107 switch (key) {
7108 case KEY_RETURN:
7109 case KEY_ENTER:
7110 case '\n':
7111 status = pos ? INPUT_STOP : INPUT_CANCEL;
7112 break;
7114 case KEY_BACKSPACE:
7115 if (pos > 0)
7116 buf[--pos] = 0;
7117 else
7118 status = INPUT_CANCEL;
7119 break;
7121 case KEY_ESC:
7122 status = INPUT_CANCEL;
7123 break;
7125 default:
7126 if (pos >= sizeof(buf)) {
7127 report("Input string too long");
7128 return NULL;
7129 }
7131 status = handler(data, buf, key);
7132 if (status == INPUT_OK)
7133 buf[pos++] = (char) key;
7134 }
7135 }
7137 /* Clear the status window */
7138 status_empty = FALSE;
7139 report("");
7141 if (status == INPUT_CANCEL)
7142 return NULL;
7144 buf[pos++] = 0;
7146 return buf;
7147 }
7149 static enum input_status
7150 prompt_yesno_handler(void *data, char *buf, int c)
7151 {
7152 if (c == 'y' || c == 'Y')
7153 return INPUT_STOP;
7154 if (c == 'n' || c == 'N')
7155 return INPUT_CANCEL;
7156 return INPUT_SKIP;
7157 }
7159 static bool
7160 prompt_yesno(const char *prompt)
7161 {
7162 char prompt2[SIZEOF_STR];
7164 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7165 return FALSE;
7167 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7168 }
7170 static enum input_status
7171 read_prompt_handler(void *data, char *buf, int c)
7172 {
7173 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7174 }
7176 static char *
7177 read_prompt(const char *prompt)
7178 {
7179 return prompt_input(prompt, read_prompt_handler, NULL);
7180 }
7182 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7183 {
7184 enum input_status status = INPUT_OK;
7185 int size = 0;
7187 while (items[size].text)
7188 size++;
7190 while (status == INPUT_OK) {
7191 const struct menu_item *item = &items[*selected];
7192 int key;
7193 int i;
7195 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7196 prompt, *selected + 1, size);
7197 if (item->hotkey)
7198 wprintw(status_win, "[%c] ", (char) item->hotkey);
7199 wprintw(status_win, "%s", item->text);
7200 wclrtoeol(status_win);
7202 key = get_input(COLS - 1);
7203 switch (key) {
7204 case KEY_RETURN:
7205 case KEY_ENTER:
7206 case '\n':
7207 status = INPUT_STOP;
7208 break;
7210 case KEY_LEFT:
7211 case KEY_UP:
7212 *selected = *selected - 1;
7213 if (*selected < 0)
7214 *selected = size - 1;
7215 break;
7217 case KEY_RIGHT:
7218 case KEY_DOWN:
7219 *selected = (*selected + 1) % size;
7220 break;
7222 case KEY_ESC:
7223 status = INPUT_CANCEL;
7224 break;
7226 default:
7227 for (i = 0; items[i].text; i++)
7228 if (items[i].hotkey == key) {
7229 *selected = i;
7230 status = INPUT_STOP;
7231 break;
7232 }
7233 }
7234 }
7236 /* Clear the status window */
7237 status_empty = FALSE;
7238 report("");
7240 return status != INPUT_CANCEL;
7241 }
7243 /*
7244 * Repository properties
7245 */
7247 static struct ref **refs = NULL;
7248 static size_t refs_size = 0;
7249 static struct ref *refs_head = NULL;
7251 static struct ref_list **ref_lists = NULL;
7252 static size_t ref_lists_size = 0;
7254 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7255 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7256 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7258 static int
7259 compare_refs(const void *ref1_, const void *ref2_)
7260 {
7261 const struct ref *ref1 = *(const struct ref **)ref1_;
7262 const struct ref *ref2 = *(const struct ref **)ref2_;
7264 if (ref1->tag != ref2->tag)
7265 return ref2->tag - ref1->tag;
7266 if (ref1->ltag != ref2->ltag)
7267 return ref2->ltag - ref2->ltag;
7268 if (ref1->head != ref2->head)
7269 return ref2->head - ref1->head;
7270 if (ref1->tracked != ref2->tracked)
7271 return ref2->tracked - ref1->tracked;
7272 if (ref1->remote != ref2->remote)
7273 return ref2->remote - ref1->remote;
7274 return strcmp(ref1->name, ref2->name);
7275 }
7277 static void
7278 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7279 {
7280 size_t i;
7282 for (i = 0; i < refs_size; i++)
7283 if (!visitor(data, refs[i]))
7284 break;
7285 }
7287 static struct ref *
7288 get_ref_head()
7289 {
7290 return refs_head;
7291 }
7293 static struct ref_list *
7294 get_ref_list(const char *id)
7295 {
7296 struct ref_list *list;
7297 size_t i;
7299 for (i = 0; i < ref_lists_size; i++)
7300 if (!strcmp(id, ref_lists[i]->id))
7301 return ref_lists[i];
7303 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7304 return NULL;
7305 list = calloc(1, sizeof(*list));
7306 if (!list)
7307 return NULL;
7309 for (i = 0; i < refs_size; i++) {
7310 if (!strcmp(id, refs[i]->id) &&
7311 realloc_refs_list(&list->refs, list->size, 1))
7312 list->refs[list->size++] = refs[i];
7313 }
7315 if (!list->refs) {
7316 free(list);
7317 return NULL;
7318 }
7320 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7321 ref_lists[ref_lists_size++] = list;
7322 return list;
7323 }
7325 static int
7326 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7327 {
7328 struct ref *ref = NULL;
7329 bool tag = FALSE;
7330 bool ltag = FALSE;
7331 bool remote = FALSE;
7332 bool tracked = FALSE;
7333 bool head = FALSE;
7334 int from = 0, to = refs_size - 1;
7336 if (!prefixcmp(name, "refs/tags/")) {
7337 if (!suffixcmp(name, namelen, "^{}")) {
7338 namelen -= 3;
7339 name[namelen] = 0;
7340 } else {
7341 ltag = TRUE;
7342 }
7344 tag = TRUE;
7345 namelen -= STRING_SIZE("refs/tags/");
7346 name += STRING_SIZE("refs/tags/");
7348 } else if (!prefixcmp(name, "refs/remotes/")) {
7349 remote = TRUE;
7350 namelen -= STRING_SIZE("refs/remotes/");
7351 name += STRING_SIZE("refs/remotes/");
7352 tracked = !strcmp(opt_remote, name);
7354 } else if (!prefixcmp(name, "refs/heads/")) {
7355 namelen -= STRING_SIZE("refs/heads/");
7356 name += STRING_SIZE("refs/heads/");
7357 if (!strncmp(opt_head, name, namelen))
7358 return OK;
7360 } else if (!strcmp(name, "HEAD")) {
7361 head = TRUE;
7362 if (*opt_head) {
7363 namelen = strlen(opt_head);
7364 name = opt_head;
7365 }
7366 }
7368 /* If we are reloading or it's an annotated tag, replace the
7369 * previous SHA1 with the resolved commit id; relies on the fact
7370 * git-ls-remote lists the commit id of an annotated tag right
7371 * before the commit id it points to. */
7372 while (from <= to) {
7373 size_t pos = (to + from) / 2;
7374 int cmp = strcmp(name, refs[pos]->name);
7376 if (!cmp) {
7377 ref = refs[pos];
7378 break;
7379 }
7381 if (cmp < 0)
7382 to = pos - 1;
7383 else
7384 from = pos + 1;
7385 }
7387 if (!ref) {
7388 if (!realloc_refs(&refs, refs_size, 1))
7389 return ERR;
7390 ref = calloc(1, sizeof(*ref) + namelen);
7391 if (!ref)
7392 return ERR;
7393 memmove(refs + from + 1, refs + from,
7394 (refs_size - from) * sizeof(*refs));
7395 refs[from] = ref;
7396 strncpy(ref->name, name, namelen);
7397 refs_size++;
7398 }
7400 ref->head = head;
7401 ref->tag = tag;
7402 ref->ltag = ltag;
7403 ref->remote = remote;
7404 ref->tracked = tracked;
7405 string_copy_rev(ref->id, id);
7407 if (head)
7408 refs_head = ref;
7409 return OK;
7410 }
7412 static int
7413 load_refs(void)
7414 {
7415 const char *head_argv[] = {
7416 "git", "symbolic-ref", "HEAD", NULL
7417 };
7418 static const char *ls_remote_argv[SIZEOF_ARG] = {
7419 "git", "ls-remote", opt_git_dir, NULL
7420 };
7421 static bool init = FALSE;
7422 size_t i;
7424 if (!init) {
7425 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7426 die("TIG_LS_REMOTE contains too many arguments");
7427 init = TRUE;
7428 }
7430 if (!*opt_git_dir)
7431 return OK;
7433 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7434 !prefixcmp(opt_head, "refs/heads/")) {
7435 char *offset = opt_head + STRING_SIZE("refs/heads/");
7437 memmove(opt_head, offset, strlen(offset) + 1);
7438 }
7440 refs_head = NULL;
7441 for (i = 0; i < refs_size; i++)
7442 refs[i]->id[0] = 0;
7444 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7445 return ERR;
7447 /* Update the ref lists to reflect changes. */
7448 for (i = 0; i < ref_lists_size; i++) {
7449 struct ref_list *list = ref_lists[i];
7450 size_t old, new;
7452 for (old = new = 0; old < list->size; old++)
7453 if (!strcmp(list->id, list->refs[old]->id))
7454 list->refs[new++] = list->refs[old];
7455 list->size = new;
7456 }
7458 return OK;
7459 }
7461 static void
7462 set_remote_branch(const char *name, const char *value, size_t valuelen)
7463 {
7464 if (!strcmp(name, ".remote")) {
7465 string_ncopy(opt_remote, value, valuelen);
7467 } else if (*opt_remote && !strcmp(name, ".merge")) {
7468 size_t from = strlen(opt_remote);
7470 if (!prefixcmp(value, "refs/heads/"))
7471 value += STRING_SIZE("refs/heads/");
7473 if (!string_format_from(opt_remote, &from, "/%s", value))
7474 opt_remote[0] = 0;
7475 }
7476 }
7478 static void
7479 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7480 {
7481 const char *argv[SIZEOF_ARG] = { name, "=" };
7482 int argc = 1 + (cmd == option_set_command);
7483 int error = ERR;
7485 if (!argv_from_string(argv, &argc, value))
7486 config_msg = "Too many option arguments";
7487 else
7488 error = cmd(argc, argv);
7490 if (error == ERR)
7491 warn("Option 'tig.%s': %s", name, config_msg);
7492 }
7494 static bool
7495 set_environment_variable(const char *name, const char *value)
7496 {
7497 size_t len = strlen(name) + 1 + strlen(value) + 1;
7498 char *env = malloc(len);
7500 if (env &&
7501 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7502 putenv(env) == 0)
7503 return TRUE;
7504 free(env);
7505 return FALSE;
7506 }
7508 static void
7509 set_work_tree(const char *value)
7510 {
7511 char cwd[SIZEOF_STR];
7513 if (!getcwd(cwd, sizeof(cwd)))
7514 die("Failed to get cwd path: %s", strerror(errno));
7515 if (chdir(opt_git_dir) < 0)
7516 die("Failed to chdir(%s): %s", strerror(errno));
7517 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7518 die("Failed to get git path: %s", strerror(errno));
7519 if (chdir(cwd) < 0)
7520 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7521 if (chdir(value) < 0)
7522 die("Failed to chdir(%s): %s", value, strerror(errno));
7523 if (!getcwd(cwd, sizeof(cwd)))
7524 die("Failed to get cwd path: %s", strerror(errno));
7525 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7526 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7527 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7528 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7529 opt_is_inside_work_tree = TRUE;
7530 }
7532 static int
7533 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7534 {
7535 if (!strcmp(name, "i18n.commitencoding"))
7536 string_ncopy(opt_encoding, value, valuelen);
7538 else if (!strcmp(name, "core.editor"))
7539 string_ncopy(opt_editor, value, valuelen);
7541 else if (!strcmp(name, "core.worktree"))
7542 set_work_tree(value);
7544 else if (!prefixcmp(name, "tig.color."))
7545 set_repo_config_option(name + 10, value, option_color_command);
7547 else if (!prefixcmp(name, "tig.bind."))
7548 set_repo_config_option(name + 9, value, option_bind_command);
7550 else if (!prefixcmp(name, "tig."))
7551 set_repo_config_option(name + 4, value, option_set_command);
7553 else if (*opt_head && !prefixcmp(name, "branch.") &&
7554 !strncmp(name + 7, opt_head, strlen(opt_head)))
7555 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7557 return OK;
7558 }
7560 static int
7561 load_git_config(void)
7562 {
7563 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7565 return io_run_load(config_list_argv, "=", read_repo_config_option);
7566 }
7568 static int
7569 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7570 {
7571 if (!opt_git_dir[0]) {
7572 string_ncopy(opt_git_dir, name, namelen);
7574 } else if (opt_is_inside_work_tree == -1) {
7575 /* This can be 3 different values depending on the
7576 * version of git being used. If git-rev-parse does not
7577 * understand --is-inside-work-tree it will simply echo
7578 * the option else either "true" or "false" is printed.
7579 * Default to true for the unknown case. */
7580 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7582 } else if (*name == '.') {
7583 string_ncopy(opt_cdup, name, namelen);
7585 } else {
7586 string_ncopy(opt_prefix, name, namelen);
7587 }
7589 return OK;
7590 }
7592 static int
7593 load_repo_info(void)
7594 {
7595 const char *rev_parse_argv[] = {
7596 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7597 "--show-cdup", "--show-prefix", NULL
7598 };
7600 return io_run_load(rev_parse_argv, "=", read_repo_info);
7601 }
7604 /*
7605 * Main
7606 */
7608 static const char usage[] =
7609 "tig " TIG_VERSION " (" __DATE__ ")\n"
7610 "\n"
7611 "Usage: tig [options] [revs] [--] [paths]\n"
7612 " or: tig show [options] [revs] [--] [paths]\n"
7613 " or: tig blame [rev] path\n"
7614 " or: tig status\n"
7615 " or: tig < [git command output]\n"
7616 "\n"
7617 "Options:\n"
7618 " -v, --version Show version and exit\n"
7619 " -h, --help Show help message and exit";
7621 static void __NORETURN
7622 quit(int sig)
7623 {
7624 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7625 if (cursed)
7626 endwin();
7627 exit(0);
7628 }
7630 static void __NORETURN
7631 die(const char *err, ...)
7632 {
7633 va_list args;
7635 endwin();
7637 va_start(args, err);
7638 fputs("tig: ", stderr);
7639 vfprintf(stderr, err, args);
7640 fputs("\n", stderr);
7641 va_end(args);
7643 exit(1);
7644 }
7646 static void
7647 warn(const char *msg, ...)
7648 {
7649 va_list args;
7651 va_start(args, msg);
7652 fputs("tig warning: ", stderr);
7653 vfprintf(stderr, msg, args);
7654 fputs("\n", stderr);
7655 va_end(args);
7656 }
7658 static enum request
7659 parse_options(int argc, const char *argv[])
7660 {
7661 enum request request = REQ_VIEW_MAIN;
7662 const char *subcommand;
7663 bool seen_dashdash = FALSE;
7664 /* XXX: This is vulnerable to the user overriding options
7665 * required for the main view parser. */
7666 const char *custom_argv[SIZEOF_ARG] = {
7667 "git", "log", "--no-color", "--pretty=raw", "--parents",
7668 "--topo-order", NULL
7669 };
7670 int i, j = 6;
7672 if (!isatty(STDIN_FILENO)) {
7673 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7674 return REQ_VIEW_PAGER;
7675 }
7677 if (argc <= 1)
7678 return REQ_NONE;
7680 subcommand = argv[1];
7681 if (!strcmp(subcommand, "status")) {
7682 if (argc > 2)
7683 warn("ignoring arguments after `%s'", subcommand);
7684 return REQ_VIEW_STATUS;
7686 } else if (!strcmp(subcommand, "blame")) {
7687 if (argc <= 2 || argc > 4)
7688 die("invalid number of options to blame\n\n%s", usage);
7690 i = 2;
7691 if (argc == 4) {
7692 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7693 i++;
7694 }
7696 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7697 return REQ_VIEW_BLAME;
7699 } else if (!strcmp(subcommand, "show")) {
7700 request = REQ_VIEW_DIFF;
7702 } else {
7703 subcommand = NULL;
7704 }
7706 if (subcommand) {
7707 custom_argv[1] = subcommand;
7708 j = 2;
7709 }
7711 for (i = 1 + !!subcommand; i < argc; i++) {
7712 const char *opt = argv[i];
7714 if (seen_dashdash || !strcmp(opt, "--")) {
7715 seen_dashdash = TRUE;
7717 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7718 printf("tig version %s\n", TIG_VERSION);
7719 quit(0);
7721 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7722 printf("%s\n", usage);
7723 quit(0);
7724 }
7726 custom_argv[j++] = opt;
7727 if (j >= ARRAY_SIZE(custom_argv))
7728 die("command too long");
7729 }
7731 if (!prepare_update(VIEW(request), custom_argv, NULL))
7732 die("Failed to format arguments");
7734 return request;
7735 }
7737 int
7738 main(int argc, const char *argv[])
7739 {
7740 const char *codeset = "UTF-8";
7741 enum request request = parse_options(argc, argv);
7742 struct view *view;
7743 size_t i;
7745 signal(SIGINT, quit);
7746 signal(SIGPIPE, SIG_IGN);
7748 if (setlocale(LC_ALL, "")) {
7749 codeset = nl_langinfo(CODESET);
7750 }
7752 if (load_repo_info() == ERR)
7753 die("Failed to load repo info.");
7755 if (load_options() == ERR)
7756 die("Failed to load user config.");
7758 if (load_git_config() == ERR)
7759 die("Failed to load repo config.");
7761 /* Require a git repository unless when running in pager mode. */
7762 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7763 die("Not a git repository");
7765 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7766 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7767 if (opt_iconv_in == ICONV_NONE)
7768 die("Failed to initialize character set conversion");
7769 }
7771 if (codeset && strcmp(codeset, "UTF-8")) {
7772 opt_iconv_out = iconv_open(codeset, "UTF-8");
7773 if (opt_iconv_out == ICONV_NONE)
7774 die("Failed to initialize character set conversion");
7775 }
7777 if (load_refs() == ERR)
7778 die("Failed to load refs.");
7780 foreach_view (view, i)
7781 if (!argv_from_env(view->ops->argv, view->cmd_env))
7782 die("Too many arguments in the `%s` environment variable",
7783 view->cmd_env);
7785 init_display();
7787 if (request != REQ_NONE)
7788 open_view(NULL, request, OPEN_PREPARED);
7789 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7791 while (view_driver(display[current_view], request)) {
7792 int key = get_input(0);
7794 view = display[current_view];
7795 request = get_keybinding(view->keymap, key);
7797 /* Some low-level request handling. This keeps access to
7798 * status_win restricted. */
7799 switch (request) {
7800 case REQ_PROMPT:
7801 {
7802 char *cmd = read_prompt(":");
7804 if (cmd && isdigit(*cmd)) {
7805 int lineno = view->lineno + 1;
7807 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7808 select_view_line(view, lineno - 1);
7809 report("");
7810 } else {
7811 report("Unable to parse '%s' as a line number", cmd);
7812 }
7814 } else if (cmd) {
7815 struct view *next = VIEW(REQ_VIEW_PAGER);
7816 const char *argv[SIZEOF_ARG] = { "git" };
7817 int argc = 1;
7819 /* When running random commands, initially show the
7820 * command in the title. However, it maybe later be
7821 * overwritten if a commit line is selected. */
7822 string_ncopy(next->ref, cmd, strlen(cmd));
7824 if (!argv_from_string(argv, &argc, cmd)) {
7825 report("Too many arguments");
7826 } else if (!prepare_update(next, argv, NULL)) {
7827 report("Failed to format command");
7828 } else {
7829 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7830 }
7831 }
7833 request = REQ_NONE;
7834 break;
7835 }
7836 case REQ_SEARCH:
7837 case REQ_SEARCH_BACK:
7838 {
7839 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7840 char *search = read_prompt(prompt);
7842 if (search)
7843 string_ncopy(opt_search, search, strlen(search));
7844 else if (*opt_search)
7845 request = request == REQ_SEARCH ?
7846 REQ_FIND_NEXT :
7847 REQ_FIND_PREV;
7848 else
7849 request = REQ_NONE;
7850 break;
7851 }
7852 default:
7853 break;
7854 }
7855 }
7857 quit(0);
7859 return 0;
7860 }