3e4d369063259d67589aabae0c8a8a47d871148a
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 }
233 static char *
234 chomp_string(char *name)
235 {
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
246 }
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
250 {
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
262 }
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
272 {
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
287 }
289 return 0;
290 }
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
305 {
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
313 }
315 buf[bufpos] = 0;
316 return buf;
317 }
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
323 {
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
331 }
333 return FALSE;
334 }
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
344 {
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 }
352 /*
353 * Unicode / UTF-8 handling
354 *
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358 */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
362 {
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
382 }
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
394 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
399 {
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
403 }
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
408 {
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
447 }
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
452 }
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
458 *
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
462 {
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
491 }
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
499 }
500 break;
501 }
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
506 }
508 return string - *start;
509 }
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
529 };
531 struct time {
532 time_t sec;
533 int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
537 {
538 return t1->sec - t2->sec;
539 }
541 static const char *
542 mkdate(const struct time *time, enum date date)
543 {
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
552 };
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
577 }
578 }
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
583 }
584 else {
585 gmtime_r(&time->sec, &tm);
586 }
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
674 static void
675 argv_free(const char *argv[])
676 {
677 int argc;
679 if (!argv)
680 return;
681 for (argc = 0; argv[argc]; argc++)
682 free((void *) argv[argc]);
683 argv[0] = NULL;
684 }
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
690 {
691 int argc = 0;
693 while (*argv && (*argv)[argc])
694 argc++;
696 if (!argv_realloc(argv, argc, 2))
697 return FALSE;
699 (*argv)[argc++] = strdup(arg);
700 (*argv)[argc] = NULL;
701 return TRUE;
702 }
704 static bool
705 argv_copy(const char ***dst, const char *src[])
706 {
707 int argc;
709 for (argc = 0; src[argc]; argc++)
710 if (!argv_append(dst, src[argc]))
711 return FALSE;
712 return TRUE;
713 }
716 /*
717 * Executing external commands.
718 */
720 enum io_type {
721 IO_FD, /* File descriptor based IO. */
722 IO_BG, /* Execute command in the background. */
723 IO_FG, /* Execute command with same std{in,out,err}. */
724 IO_RD, /* Read only fork+exec IO. */
725 IO_WR, /* Write only fork+exec IO. */
726 IO_AP, /* Append fork+exec output to file. */
727 };
729 struct io {
730 int pipe; /* Pipe end for reading or writing. */
731 pid_t pid; /* PID of spawned process. */
732 int error; /* Error status. */
733 char *buf; /* Read buffer. */
734 size_t bufalloc; /* Allocated buffer size. */
735 size_t bufsize; /* Buffer content size. */
736 char *bufpos; /* Current buffer position. */
737 unsigned int eof:1; /* Has end of file been reached. */
738 };
740 static void
741 io_init(struct io *io)
742 {
743 memset(io, 0, sizeof(*io));
744 io->pipe = -1;
745 }
747 static bool
748 io_open(struct io *io, const char *fmt, ...)
749 {
750 char name[SIZEOF_STR] = "";
751 bool fits;
752 va_list args;
754 io_init(io);
756 va_start(args, fmt);
757 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
758 va_end(args);
760 if (!fits) {
761 io->error = ENAMETOOLONG;
762 return FALSE;
763 }
764 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
765 if (io->pipe == -1)
766 io->error = errno;
767 return io->pipe != -1;
768 }
770 static bool
771 io_kill(struct io *io)
772 {
773 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
774 }
776 static bool
777 io_done(struct io *io)
778 {
779 pid_t pid = io->pid;
781 if (io->pipe != -1)
782 close(io->pipe);
783 free(io->buf);
784 io_init(io);
786 while (pid > 0) {
787 int status;
788 pid_t waiting = waitpid(pid, &status, 0);
790 if (waiting < 0) {
791 if (errno == EINTR)
792 continue;
793 io->error = errno;
794 return FALSE;
795 }
797 return waiting == pid &&
798 !WIFSIGNALED(status) &&
799 WIFEXITED(status) &&
800 !WEXITSTATUS(status);
801 }
803 return TRUE;
804 }
806 static bool
807 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
808 {
809 int pipefds[2] = { -1, -1 };
810 va_list args;
812 io_init(io);
814 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
815 io->error = errno;
816 return FALSE;
817 } else if (type == IO_AP) {
818 va_start(args, argv);
819 pipefds[1] = va_arg(args, int);
820 va_end(args);
821 }
823 if ((io->pid = fork())) {
824 if (io->pid == -1)
825 io->error = errno;
826 if (pipefds[!(type == IO_WR)] != -1)
827 close(pipefds[!(type == IO_WR)]);
828 if (io->pid != -1) {
829 io->pipe = pipefds[!!(type == IO_WR)];
830 return TRUE;
831 }
833 } else {
834 if (type != IO_FG) {
835 int devnull = open("/dev/null", O_RDWR);
836 int readfd = type == IO_WR ? pipefds[0] : devnull;
837 int writefd = (type == IO_RD || type == IO_AP)
838 ? pipefds[1] : devnull;
840 dup2(readfd, STDIN_FILENO);
841 dup2(writefd, STDOUT_FILENO);
842 dup2(devnull, STDERR_FILENO);
844 close(devnull);
845 if (pipefds[0] != -1)
846 close(pipefds[0]);
847 if (pipefds[1] != -1)
848 close(pipefds[1]);
849 }
851 if (dir && *dir && chdir(dir) == -1)
852 exit(errno);
854 execvp(argv[0], (char *const*) argv);
855 exit(errno);
856 }
858 if (pipefds[!!(type == IO_WR)] != -1)
859 close(pipefds[!!(type == IO_WR)]);
860 return FALSE;
861 }
863 static bool
864 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
865 {
866 struct io io;
868 return io_run(&io, type, dir, argv, fd) && io_done(&io);
869 }
871 static bool
872 io_run_bg(const char **argv)
873 {
874 return io_complete(IO_BG, argv, NULL, -1);
875 }
877 static bool
878 io_run_fg(const char **argv, const char *dir)
879 {
880 return io_complete(IO_FG, argv, dir, -1);
881 }
883 static bool
884 io_run_append(const char **argv, int fd)
885 {
886 return io_complete(IO_AP, argv, NULL, fd);
887 }
889 static bool
890 io_eof(struct io *io)
891 {
892 return io->eof;
893 }
895 static int
896 io_error(struct io *io)
897 {
898 return io->error;
899 }
901 static char *
902 io_strerror(struct io *io)
903 {
904 return strerror(io->error);
905 }
907 static bool
908 io_can_read(struct io *io)
909 {
910 struct timeval tv = { 0, 500 };
911 fd_set fds;
913 FD_ZERO(&fds);
914 FD_SET(io->pipe, &fds);
916 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
917 }
919 static ssize_t
920 io_read(struct io *io, void *buf, size_t bufsize)
921 {
922 do {
923 ssize_t readsize = read(io->pipe, buf, bufsize);
925 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
926 continue;
927 else if (readsize == -1)
928 io->error = errno;
929 else if (readsize == 0)
930 io->eof = 1;
931 return readsize;
932 } while (1);
933 }
935 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
937 static char *
938 io_get(struct io *io, int c, bool can_read)
939 {
940 char *eol;
941 ssize_t readsize;
943 while (TRUE) {
944 if (io->bufsize > 0) {
945 eol = memchr(io->bufpos, c, io->bufsize);
946 if (eol) {
947 char *line = io->bufpos;
949 *eol = 0;
950 io->bufpos = eol + 1;
951 io->bufsize -= io->bufpos - line;
952 return line;
953 }
954 }
956 if (io_eof(io)) {
957 if (io->bufsize) {
958 io->bufpos[io->bufsize] = 0;
959 io->bufsize = 0;
960 return io->bufpos;
961 }
962 return NULL;
963 }
965 if (!can_read)
966 return NULL;
968 if (io->bufsize > 0 && io->bufpos > io->buf)
969 memmove(io->buf, io->bufpos, io->bufsize);
971 if (io->bufalloc == io->bufsize) {
972 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
973 return NULL;
974 io->bufalloc += BUFSIZ;
975 }
977 io->bufpos = io->buf;
978 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
979 if (io_error(io))
980 return NULL;
981 io->bufsize += readsize;
982 }
983 }
985 static bool
986 io_write(struct io *io, const void *buf, size_t bufsize)
987 {
988 size_t written = 0;
990 while (!io_error(io) && written < bufsize) {
991 ssize_t size;
993 size = write(io->pipe, buf + written, bufsize - written);
994 if (size < 0 && (errno == EAGAIN || errno == EINTR))
995 continue;
996 else if (size == -1)
997 io->error = errno;
998 else
999 written += size;
1000 }
1002 return written == bufsize;
1003 }
1005 static bool
1006 io_read_buf(struct io *io, char buf[], size_t bufsize)
1007 {
1008 char *result = io_get(io, '\n', TRUE);
1010 if (result) {
1011 result = chomp_string(result);
1012 string_ncopy_do(buf, bufsize, result, strlen(result));
1013 }
1015 return io_done(io) && result;
1016 }
1018 static bool
1019 io_run_buf(const char **argv, char buf[], size_t bufsize)
1020 {
1021 struct io io;
1023 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1024 }
1026 static int
1027 io_load(struct io *io, const char *separators,
1028 int (*read_property)(char *, size_t, char *, size_t))
1029 {
1030 char *name;
1031 int state = OK;
1033 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1034 char *value;
1035 size_t namelen;
1036 size_t valuelen;
1038 name = chomp_string(name);
1039 namelen = strcspn(name, separators);
1041 if (name[namelen]) {
1042 name[namelen] = 0;
1043 value = chomp_string(name + namelen + 1);
1044 valuelen = strlen(value);
1046 } else {
1047 value = "";
1048 valuelen = 0;
1049 }
1051 state = read_property(name, namelen, value, valuelen);
1052 }
1054 if (state != ERR && io_error(io))
1055 state = ERR;
1056 io_done(io);
1058 return state;
1059 }
1061 static int
1062 io_run_load(const char **argv, const char *separators,
1063 int (*read_property)(char *, size_t, char *, size_t))
1064 {
1065 struct io io;
1067 if (!io_run(&io, IO_RD, NULL, argv))
1068 return ERR;
1069 return io_load(&io, separators, read_property);
1070 }
1073 /*
1074 * User requests
1075 */
1077 #define REQ_INFO \
1078 /* XXX: Keep the view request first and in sync with views[]. */ \
1079 REQ_GROUP("View switching") \
1080 REQ_(VIEW_MAIN, "Show main view"), \
1081 REQ_(VIEW_DIFF, "Show diff view"), \
1082 REQ_(VIEW_LOG, "Show log view"), \
1083 REQ_(VIEW_TREE, "Show tree view"), \
1084 REQ_(VIEW_BLOB, "Show blob view"), \
1085 REQ_(VIEW_BLAME, "Show blame view"), \
1086 REQ_(VIEW_BRANCH, "Show branch view"), \
1087 REQ_(VIEW_HELP, "Show help page"), \
1088 REQ_(VIEW_PAGER, "Show pager view"), \
1089 REQ_(VIEW_STATUS, "Show status view"), \
1090 REQ_(VIEW_STAGE, "Show stage view"), \
1091 \
1092 REQ_GROUP("View manipulation") \
1093 REQ_(ENTER, "Enter current line and scroll"), \
1094 REQ_(NEXT, "Move to next"), \
1095 REQ_(PREVIOUS, "Move to previous"), \
1096 REQ_(PARENT, "Move to parent"), \
1097 REQ_(VIEW_NEXT, "Move focus to next view"), \
1098 REQ_(REFRESH, "Reload and refresh"), \
1099 REQ_(MAXIMIZE, "Maximize the current view"), \
1100 REQ_(VIEW_CLOSE, "Close the current view"), \
1101 REQ_(QUIT, "Close all views and quit"), \
1102 \
1103 REQ_GROUP("View specific requests") \
1104 REQ_(STATUS_UPDATE, "Update file status"), \
1105 REQ_(STATUS_REVERT, "Revert file changes"), \
1106 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1107 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1108 \
1109 REQ_GROUP("Cursor navigation") \
1110 REQ_(MOVE_UP, "Move cursor one line up"), \
1111 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1112 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1113 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1114 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1115 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1116 \
1117 REQ_GROUP("Scrolling") \
1118 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1119 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1120 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1121 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1122 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1123 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1124 \
1125 REQ_GROUP("Searching") \
1126 REQ_(SEARCH, "Search the view"), \
1127 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1128 REQ_(FIND_NEXT, "Find next search match"), \
1129 REQ_(FIND_PREV, "Find previous search match"), \
1130 \
1131 REQ_GROUP("Option manipulation") \
1132 REQ_(OPTIONS, "Open option menu"), \
1133 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1134 REQ_(TOGGLE_DATE, "Toggle date display"), \
1135 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1136 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1137 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1138 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1139 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1140 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1141 \
1142 REQ_GROUP("Misc") \
1143 REQ_(PROMPT, "Bring up the prompt"), \
1144 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1145 REQ_(SHOW_VERSION, "Show version information"), \
1146 REQ_(STOP_LOADING, "Stop all loading views"), \
1147 REQ_(EDIT, "Open in editor"), \
1148 REQ_(NONE, "Do nothing")
1151 /* User action requests. */
1152 enum request {
1153 #define REQ_GROUP(help)
1154 #define REQ_(req, help) REQ_##req
1156 /* Offset all requests to avoid conflicts with ncurses getch values. */
1157 REQ_UNKNOWN = KEY_MAX + 1,
1158 REQ_OFFSET,
1159 REQ_INFO
1161 #undef REQ_GROUP
1162 #undef REQ_
1163 };
1165 struct request_info {
1166 enum request request;
1167 const char *name;
1168 int namelen;
1169 const char *help;
1170 };
1172 static const struct request_info req_info[] = {
1173 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1174 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1175 REQ_INFO
1176 #undef REQ_GROUP
1177 #undef REQ_
1178 };
1180 static enum request
1181 get_request(const char *name)
1182 {
1183 int namelen = strlen(name);
1184 int i;
1186 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1187 if (enum_equals(req_info[i], name, namelen))
1188 return req_info[i].request;
1190 return REQ_UNKNOWN;
1191 }
1194 /*
1195 * Options
1196 */
1198 /* Option and state variables. */
1199 static enum date opt_date = DATE_DEFAULT;
1200 static enum author opt_author = AUTHOR_DEFAULT;
1201 static bool opt_line_number = FALSE;
1202 static bool opt_line_graphics = TRUE;
1203 static bool opt_rev_graph = FALSE;
1204 static bool opt_show_refs = TRUE;
1205 static int opt_num_interval = 5;
1206 static double opt_hscroll = 0.50;
1207 static double opt_scale_split_view = 2.0 / 3.0;
1208 static int opt_tab_size = 8;
1209 static int opt_author_cols = AUTHOR_COLS;
1210 static char opt_path[SIZEOF_STR] = "";
1211 static char opt_file[SIZEOF_STR] = "";
1212 static char opt_ref[SIZEOF_REF] = "";
1213 static char opt_head[SIZEOF_REF] = "";
1214 static char opt_remote[SIZEOF_REF] = "";
1215 static char opt_encoding[20] = "UTF-8";
1216 static iconv_t opt_iconv_in = ICONV_NONE;
1217 static iconv_t opt_iconv_out = ICONV_NONE;
1218 static char opt_search[SIZEOF_STR] = "";
1219 static char opt_cdup[SIZEOF_STR] = "";
1220 static char opt_prefix[SIZEOF_STR] = "";
1221 static char opt_git_dir[SIZEOF_STR] = "";
1222 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1223 static char opt_editor[SIZEOF_STR] = "";
1224 static FILE *opt_tty = NULL;
1226 #define is_initial_commit() (!get_ref_head())
1227 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1230 /*
1231 * Line-oriented content detection.
1232 */
1234 #define LINE_INFO \
1235 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1236 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1237 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1238 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1239 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1240 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1241 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1242 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1243 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1244 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1245 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1246 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1247 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1249 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1250 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1256 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1257 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1258 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1259 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1260 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1261 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1266 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1267 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1268 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1269 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1271 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1272 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1273 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1274 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1275 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1276 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1277 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1279 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1280 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1281 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1282 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1283 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1284 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1285 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1286 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1287 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1288 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1289 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1290 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1292 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1293 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1295 enum line_type {
1296 #define LINE(type, line, fg, bg, attr) \
1297 LINE_##type
1298 LINE_INFO,
1299 LINE_NONE
1300 #undef LINE
1301 };
1303 struct line_info {
1304 const char *name; /* Option name. */
1305 int namelen; /* Size of option name. */
1306 const char *line; /* The start of line to match. */
1307 int linelen; /* Size of string to match. */
1308 int fg, bg, attr; /* Color and text attributes for the lines. */
1309 };
1311 static struct line_info line_info[] = {
1312 #define LINE(type, line, fg, bg, attr) \
1313 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1314 LINE_INFO
1315 #undef LINE
1316 };
1318 static enum line_type
1319 get_line_type(const char *line)
1320 {
1321 int linelen = strlen(line);
1322 enum line_type type;
1324 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1325 /* Case insensitive search matches Signed-off-by lines better. */
1326 if (linelen >= line_info[type].linelen &&
1327 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1328 return type;
1330 return LINE_DEFAULT;
1331 }
1333 static inline int
1334 get_line_attr(enum line_type type)
1335 {
1336 assert(type < ARRAY_SIZE(line_info));
1337 return COLOR_PAIR(type) | line_info[type].attr;
1338 }
1340 static struct line_info *
1341 get_line_info(const char *name)
1342 {
1343 size_t namelen = strlen(name);
1344 enum line_type type;
1346 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1347 if (enum_equals(line_info[type], name, namelen))
1348 return &line_info[type];
1350 return NULL;
1351 }
1353 static void
1354 init_colors(void)
1355 {
1356 int default_bg = line_info[LINE_DEFAULT].bg;
1357 int default_fg = line_info[LINE_DEFAULT].fg;
1358 enum line_type type;
1360 start_color();
1362 if (assume_default_colors(default_fg, default_bg) == ERR) {
1363 default_bg = COLOR_BLACK;
1364 default_fg = COLOR_WHITE;
1365 }
1367 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1368 struct line_info *info = &line_info[type];
1369 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1370 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1372 init_pair(type, fg, bg);
1373 }
1374 }
1376 struct line {
1377 enum line_type type;
1379 /* State flags */
1380 unsigned int selected:1;
1381 unsigned int dirty:1;
1382 unsigned int cleareol:1;
1383 unsigned int other:16;
1385 void *data; /* User data */
1386 };
1389 /*
1390 * Keys
1391 */
1393 struct keybinding {
1394 int alias;
1395 enum request request;
1396 };
1398 static struct keybinding default_keybindings[] = {
1399 /* View switching */
1400 { 'm', REQ_VIEW_MAIN },
1401 { 'd', REQ_VIEW_DIFF },
1402 { 'l', REQ_VIEW_LOG },
1403 { 't', REQ_VIEW_TREE },
1404 { 'f', REQ_VIEW_BLOB },
1405 { 'B', REQ_VIEW_BLAME },
1406 { 'H', REQ_VIEW_BRANCH },
1407 { 'p', REQ_VIEW_PAGER },
1408 { 'h', REQ_VIEW_HELP },
1409 { 'S', REQ_VIEW_STATUS },
1410 { 'c', REQ_VIEW_STAGE },
1412 /* View manipulation */
1413 { 'q', REQ_VIEW_CLOSE },
1414 { KEY_TAB, REQ_VIEW_NEXT },
1415 { KEY_RETURN, REQ_ENTER },
1416 { KEY_UP, REQ_PREVIOUS },
1417 { KEY_DOWN, REQ_NEXT },
1418 { 'R', REQ_REFRESH },
1419 { KEY_F(5), REQ_REFRESH },
1420 { 'O', REQ_MAXIMIZE },
1422 /* Cursor navigation */
1423 { 'k', REQ_MOVE_UP },
1424 { 'j', REQ_MOVE_DOWN },
1425 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1426 { KEY_END, REQ_MOVE_LAST_LINE },
1427 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1428 { ' ', REQ_MOVE_PAGE_DOWN },
1429 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1430 { 'b', REQ_MOVE_PAGE_UP },
1431 { '-', REQ_MOVE_PAGE_UP },
1433 /* Scrolling */
1434 { KEY_LEFT, REQ_SCROLL_LEFT },
1435 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1436 { KEY_IC, REQ_SCROLL_LINE_UP },
1437 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1438 { 'w', REQ_SCROLL_PAGE_UP },
1439 { 's', REQ_SCROLL_PAGE_DOWN },
1441 /* Searching */
1442 { '/', REQ_SEARCH },
1443 { '?', REQ_SEARCH_BACK },
1444 { 'n', REQ_FIND_NEXT },
1445 { 'N', REQ_FIND_PREV },
1447 /* Misc */
1448 { 'Q', REQ_QUIT },
1449 { 'z', REQ_STOP_LOADING },
1450 { 'v', REQ_SHOW_VERSION },
1451 { 'r', REQ_SCREEN_REDRAW },
1452 { 'o', REQ_OPTIONS },
1453 { '.', REQ_TOGGLE_LINENO },
1454 { 'D', REQ_TOGGLE_DATE },
1455 { 'A', REQ_TOGGLE_AUTHOR },
1456 { 'g', REQ_TOGGLE_REV_GRAPH },
1457 { 'F', REQ_TOGGLE_REFS },
1458 { 'I', REQ_TOGGLE_SORT_ORDER },
1459 { 'i', REQ_TOGGLE_SORT_FIELD },
1460 { ':', REQ_PROMPT },
1461 { 'u', REQ_STATUS_UPDATE },
1462 { '!', REQ_STATUS_REVERT },
1463 { 'M', REQ_STATUS_MERGE },
1464 { '@', REQ_STAGE_NEXT },
1465 { ',', REQ_PARENT },
1466 { 'e', REQ_EDIT },
1467 };
1469 #define KEYMAP_INFO \
1470 KEYMAP_(GENERIC), \
1471 KEYMAP_(MAIN), \
1472 KEYMAP_(DIFF), \
1473 KEYMAP_(LOG), \
1474 KEYMAP_(TREE), \
1475 KEYMAP_(BLOB), \
1476 KEYMAP_(BLAME), \
1477 KEYMAP_(BRANCH), \
1478 KEYMAP_(PAGER), \
1479 KEYMAP_(HELP), \
1480 KEYMAP_(STATUS), \
1481 KEYMAP_(STAGE)
1483 enum keymap {
1484 #define KEYMAP_(name) KEYMAP_##name
1485 KEYMAP_INFO
1486 #undef KEYMAP_
1487 };
1489 static const struct enum_map keymap_table[] = {
1490 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1491 KEYMAP_INFO
1492 #undef KEYMAP_
1493 };
1495 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1497 struct keybinding_table {
1498 struct keybinding *data;
1499 size_t size;
1500 };
1502 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1504 static void
1505 add_keybinding(enum keymap keymap, enum request request, int key)
1506 {
1507 struct keybinding_table *table = &keybindings[keymap];
1508 size_t i;
1510 for (i = 0; i < keybindings[keymap].size; i++) {
1511 if (keybindings[keymap].data[i].alias == key) {
1512 keybindings[keymap].data[i].request = request;
1513 return;
1514 }
1515 }
1517 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1518 if (!table->data)
1519 die("Failed to allocate keybinding");
1520 table->data[table->size].alias = key;
1521 table->data[table->size++].request = request;
1523 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1524 int i;
1526 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1527 if (default_keybindings[i].alias == key)
1528 default_keybindings[i].request = REQ_NONE;
1529 }
1530 }
1532 /* Looks for a key binding first in the given map, then in the generic map, and
1533 * lastly in the default keybindings. */
1534 static enum request
1535 get_keybinding(enum keymap keymap, int key)
1536 {
1537 size_t i;
1539 for (i = 0; i < keybindings[keymap].size; i++)
1540 if (keybindings[keymap].data[i].alias == key)
1541 return keybindings[keymap].data[i].request;
1543 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1544 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1545 return keybindings[KEYMAP_GENERIC].data[i].request;
1547 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1548 if (default_keybindings[i].alias == key)
1549 return default_keybindings[i].request;
1551 return (enum request) key;
1552 }
1555 struct key {
1556 const char *name;
1557 int value;
1558 };
1560 static const struct key key_table[] = {
1561 { "Enter", KEY_RETURN },
1562 { "Space", ' ' },
1563 { "Backspace", KEY_BACKSPACE },
1564 { "Tab", KEY_TAB },
1565 { "Escape", KEY_ESC },
1566 { "Left", KEY_LEFT },
1567 { "Right", KEY_RIGHT },
1568 { "Up", KEY_UP },
1569 { "Down", KEY_DOWN },
1570 { "Insert", KEY_IC },
1571 { "Delete", KEY_DC },
1572 { "Hash", '#' },
1573 { "Home", KEY_HOME },
1574 { "End", KEY_END },
1575 { "PageUp", KEY_PPAGE },
1576 { "PageDown", KEY_NPAGE },
1577 { "F1", KEY_F(1) },
1578 { "F2", KEY_F(2) },
1579 { "F3", KEY_F(3) },
1580 { "F4", KEY_F(4) },
1581 { "F5", KEY_F(5) },
1582 { "F6", KEY_F(6) },
1583 { "F7", KEY_F(7) },
1584 { "F8", KEY_F(8) },
1585 { "F9", KEY_F(9) },
1586 { "F10", KEY_F(10) },
1587 { "F11", KEY_F(11) },
1588 { "F12", KEY_F(12) },
1589 };
1591 static int
1592 get_key_value(const char *name)
1593 {
1594 int i;
1596 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1597 if (!strcasecmp(key_table[i].name, name))
1598 return key_table[i].value;
1600 if (strlen(name) == 1 && isprint(*name))
1601 return (int) *name;
1603 return ERR;
1604 }
1606 static const char *
1607 get_key_name(int key_value)
1608 {
1609 static char key_char[] = "'X'";
1610 const char *seq = NULL;
1611 int key;
1613 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1614 if (key_table[key].value == key_value)
1615 seq = key_table[key].name;
1617 if (seq == NULL &&
1618 key_value < 127 &&
1619 isprint(key_value)) {
1620 key_char[1] = (char) key_value;
1621 seq = key_char;
1622 }
1624 return seq ? seq : "(no key)";
1625 }
1627 static bool
1628 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1629 {
1630 const char *sep = *pos > 0 ? ", " : "";
1631 const char *keyname = get_key_name(keybinding->alias);
1633 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1634 }
1636 static bool
1637 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1638 enum keymap keymap, bool all)
1639 {
1640 int i;
1642 for (i = 0; i < keybindings[keymap].size; i++) {
1643 if (keybindings[keymap].data[i].request == request) {
1644 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1645 return FALSE;
1646 if (!all)
1647 break;
1648 }
1649 }
1651 return TRUE;
1652 }
1654 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1656 static const char *
1657 get_keys(enum keymap keymap, enum request request, bool all)
1658 {
1659 static char buf[BUFSIZ];
1660 size_t pos = 0;
1661 int i;
1663 buf[pos] = 0;
1665 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1666 return "Too many keybindings!";
1667 if (pos > 0 && !all)
1668 return buf;
1670 if (keymap != KEYMAP_GENERIC) {
1671 /* Only the generic keymap includes the default keybindings when
1672 * listing all keys. */
1673 if (all)
1674 return buf;
1676 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1677 return "Too many keybindings!";
1678 if (pos)
1679 return buf;
1680 }
1682 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1683 if (default_keybindings[i].request == request) {
1684 if (!append_key(buf, &pos, &default_keybindings[i]))
1685 return "Too many keybindings!";
1686 if (!all)
1687 return buf;
1688 }
1689 }
1691 return buf;
1692 }
1694 struct run_request {
1695 enum keymap keymap;
1696 int key;
1697 const char **argv;
1698 };
1700 static struct run_request *run_request;
1701 static size_t run_requests;
1703 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1705 static enum request
1706 add_run_request(enum keymap keymap, int key, const char **argv)
1707 {
1708 struct run_request *req;
1710 if (!realloc_run_requests(&run_request, run_requests, 1))
1711 return REQ_NONE;
1713 req = &run_request[run_requests];
1714 req->keymap = keymap;
1715 req->key = key;
1716 req->argv = NULL;
1718 if (!argv_copy(&req->argv, argv))
1719 return REQ_NONE;
1721 return REQ_NONE + ++run_requests;
1722 }
1724 static struct run_request *
1725 get_run_request(enum request request)
1726 {
1727 if (request <= REQ_NONE)
1728 return NULL;
1729 return &run_request[request - REQ_NONE - 1];
1730 }
1732 static void
1733 add_builtin_run_requests(void)
1734 {
1735 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1736 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1737 const char *commit[] = { "git", "commit", NULL };
1738 const char *gc[] = { "git", "gc", NULL };
1739 struct run_request reqs[] = {
1740 { KEYMAP_MAIN, 'C', cherry_pick },
1741 { KEYMAP_STATUS, 'C', commit },
1742 { KEYMAP_BRANCH, 'C', checkout },
1743 { KEYMAP_GENERIC, 'G', gc },
1744 };
1745 int i;
1747 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1748 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1750 if (req != reqs[i].key)
1751 continue;
1752 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1753 if (req != REQ_NONE)
1754 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1755 }
1756 }
1758 /*
1759 * User config file handling.
1760 */
1762 static int config_lineno;
1763 static bool config_errors;
1764 static const char *config_msg;
1766 static const struct enum_map color_map[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1768 COLOR_MAP(DEFAULT),
1769 COLOR_MAP(BLACK),
1770 COLOR_MAP(BLUE),
1771 COLOR_MAP(CYAN),
1772 COLOR_MAP(GREEN),
1773 COLOR_MAP(MAGENTA),
1774 COLOR_MAP(RED),
1775 COLOR_MAP(WHITE),
1776 COLOR_MAP(YELLOW),
1777 };
1779 static const struct enum_map attr_map[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1781 ATTR_MAP(NORMAL),
1782 ATTR_MAP(BLINK),
1783 ATTR_MAP(BOLD),
1784 ATTR_MAP(DIM),
1785 ATTR_MAP(REVERSE),
1786 ATTR_MAP(STANDOUT),
1787 ATTR_MAP(UNDERLINE),
1788 };
1790 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt, const char *arg)
1793 {
1794 *opt = atoi(arg);
1795 if (!strchr(arg, '%'))
1796 return OK;
1798 /* "Shift down" so 100% and 1 does not conflict. */
1799 *opt = (*opt - 1) / 100;
1800 if (*opt >= 1.0) {
1801 *opt = 0.99;
1802 config_msg = "Step value larger than 100%";
1803 return ERR;
1804 }
1805 if (*opt < 0.0) {
1806 *opt = 1;
1807 config_msg = "Invalid step value";
1808 return ERR;
1809 }
1810 return OK;
1811 }
1813 static int
1814 parse_int(int *opt, const char *arg, int min, int max)
1815 {
1816 int value = atoi(arg);
1818 if (min <= value && value <= max) {
1819 *opt = value;
1820 return OK;
1821 }
1823 config_msg = "Integer value out of bound";
1824 return ERR;
1825 }
1827 static bool
1828 set_color(int *color, const char *name)
1829 {
1830 if (map_enum(color, color_map, name))
1831 return TRUE;
1832 if (!prefixcmp(name, "color"))
1833 return parse_int(color, name + 5, 0, 255) == OK;
1834 return FALSE;
1835 }
1837 /* Wants: object fgcolor bgcolor [attribute] */
1838 static int
1839 option_color_command(int argc, const char *argv[])
1840 {
1841 struct line_info *info;
1843 if (argc < 3) {
1844 config_msg = "Wrong number of arguments given to color command";
1845 return ERR;
1846 }
1848 info = get_line_info(argv[0]);
1849 if (!info) {
1850 static const struct enum_map obsolete[] = {
1851 ENUM_MAP("main-delim", LINE_DELIMITER),
1852 ENUM_MAP("main-date", LINE_DATE),
1853 ENUM_MAP("main-author", LINE_AUTHOR),
1854 };
1855 int index;
1857 if (!map_enum(&index, obsolete, argv[0])) {
1858 config_msg = "Unknown color name";
1859 return ERR;
1860 }
1861 info = &line_info[index];
1862 }
1864 if (!set_color(&info->fg, argv[1]) ||
1865 !set_color(&info->bg, argv[2])) {
1866 config_msg = "Unknown color";
1867 return ERR;
1868 }
1870 info->attr = 0;
1871 while (argc-- > 3) {
1872 int attr;
1874 if (!set_attribute(&attr, argv[argc])) {
1875 config_msg = "Unknown attribute";
1876 return ERR;
1877 }
1878 info->attr |= attr;
1879 }
1881 return OK;
1882 }
1884 static int parse_bool(bool *opt, const char *arg)
1885 {
1886 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1887 ? TRUE : FALSE;
1888 return OK;
1889 }
1891 static int parse_enum_do(unsigned int *opt, const char *arg,
1892 const struct enum_map *map, size_t map_size)
1893 {
1894 bool is_true;
1896 assert(map_size > 1);
1898 if (map_enum_do(map, map_size, (int *) opt, arg))
1899 return OK;
1901 if (parse_bool(&is_true, arg) != OK)
1902 return ERR;
1904 *opt = is_true ? map[1].value : map[0].value;
1905 return OK;
1906 }
1908 #define parse_enum(opt, arg, map) \
1909 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1911 static int
1912 parse_string(char *opt, const char *arg, size_t optsize)
1913 {
1914 int arglen = strlen(arg);
1916 switch (arg[0]) {
1917 case '\"':
1918 case '\'':
1919 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1920 config_msg = "Unmatched quotation";
1921 return ERR;
1922 }
1923 arg += 1; arglen -= 2;
1924 default:
1925 string_ncopy_do(opt, optsize, arg, arglen);
1926 return OK;
1927 }
1928 }
1930 /* Wants: name = value */
1931 static int
1932 option_set_command(int argc, const char *argv[])
1933 {
1934 if (argc != 3) {
1935 config_msg = "Wrong number of arguments given to set command";
1936 return ERR;
1937 }
1939 if (strcmp(argv[1], "=")) {
1940 config_msg = "No value assigned";
1941 return ERR;
1942 }
1944 if (!strcmp(argv[0], "show-author"))
1945 return parse_enum(&opt_author, argv[2], author_map);
1947 if (!strcmp(argv[0], "show-date"))
1948 return parse_enum(&opt_date, argv[2], date_map);
1950 if (!strcmp(argv[0], "show-rev-graph"))
1951 return parse_bool(&opt_rev_graph, argv[2]);
1953 if (!strcmp(argv[0], "show-refs"))
1954 return parse_bool(&opt_show_refs, argv[2]);
1956 if (!strcmp(argv[0], "show-line-numbers"))
1957 return parse_bool(&opt_line_number, argv[2]);
1959 if (!strcmp(argv[0], "line-graphics"))
1960 return parse_bool(&opt_line_graphics, argv[2]);
1962 if (!strcmp(argv[0], "line-number-interval"))
1963 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1965 if (!strcmp(argv[0], "author-width"))
1966 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1968 if (!strcmp(argv[0], "horizontal-scroll"))
1969 return parse_step(&opt_hscroll, argv[2]);
1971 if (!strcmp(argv[0], "split-view-height"))
1972 return parse_step(&opt_scale_split_view, argv[2]);
1974 if (!strcmp(argv[0], "tab-size"))
1975 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1977 if (!strcmp(argv[0], "commit-encoding"))
1978 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1980 config_msg = "Unknown variable name";
1981 return ERR;
1982 }
1984 /* Wants: mode request key */
1985 static int
1986 option_bind_command(int argc, const char *argv[])
1987 {
1988 enum request request;
1989 int keymap = -1;
1990 int key;
1992 if (argc < 3) {
1993 config_msg = "Wrong number of arguments given to bind command";
1994 return ERR;
1995 }
1997 if (!set_keymap(&keymap, argv[0])) {
1998 config_msg = "Unknown key map";
1999 return ERR;
2000 }
2002 key = get_key_value(argv[1]);
2003 if (key == ERR) {
2004 config_msg = "Unknown key";
2005 return ERR;
2006 }
2008 request = get_request(argv[2]);
2009 if (request == REQ_UNKNOWN) {
2010 static const struct enum_map obsolete[] = {
2011 ENUM_MAP("cherry-pick", REQ_NONE),
2012 ENUM_MAP("screen-resize", REQ_NONE),
2013 ENUM_MAP("tree-parent", REQ_PARENT),
2014 };
2015 int alias;
2017 if (map_enum(&alias, obsolete, argv[2])) {
2018 if (alias != REQ_NONE)
2019 add_keybinding(keymap, alias, key);
2020 config_msg = "Obsolete request name";
2021 return ERR;
2022 }
2023 }
2024 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2025 request = add_run_request(keymap, key, argv + 2);
2026 if (request == REQ_UNKNOWN) {
2027 config_msg = "Unknown request name";
2028 return ERR;
2029 }
2031 add_keybinding(keymap, request, key);
2033 return OK;
2034 }
2036 static int
2037 set_option(const char *opt, char *value)
2038 {
2039 const char *argv[SIZEOF_ARG];
2040 int argc = 0;
2042 if (!argv_from_string(argv, &argc, value)) {
2043 config_msg = "Too many option arguments";
2044 return ERR;
2045 }
2047 if (!strcmp(opt, "color"))
2048 return option_color_command(argc, argv);
2050 if (!strcmp(opt, "set"))
2051 return option_set_command(argc, argv);
2053 if (!strcmp(opt, "bind"))
2054 return option_bind_command(argc, argv);
2056 config_msg = "Unknown option command";
2057 return ERR;
2058 }
2060 static int
2061 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2062 {
2063 int status = OK;
2065 config_lineno++;
2066 config_msg = "Internal error";
2068 /* Check for comment markers, since read_properties() will
2069 * only ensure opt and value are split at first " \t". */
2070 optlen = strcspn(opt, "#");
2071 if (optlen == 0)
2072 return OK;
2074 if (opt[optlen] != 0) {
2075 config_msg = "No option value";
2076 status = ERR;
2078 } else {
2079 /* Look for comment endings in the value. */
2080 size_t len = strcspn(value, "#");
2082 if (len < valuelen) {
2083 valuelen = len;
2084 value[valuelen] = 0;
2085 }
2087 status = set_option(opt, value);
2088 }
2090 if (status == ERR) {
2091 warn("Error on line %d, near '%.*s': %s",
2092 config_lineno, (int) optlen, opt, config_msg);
2093 config_errors = TRUE;
2094 }
2096 /* Always keep going if errors are encountered. */
2097 return OK;
2098 }
2100 static void
2101 load_option_file(const char *path)
2102 {
2103 struct io io;
2105 /* It's OK that the file doesn't exist. */
2106 if (!io_open(&io, "%s", path))
2107 return;
2109 config_lineno = 0;
2110 config_errors = FALSE;
2112 if (io_load(&io, " \t", read_option) == ERR ||
2113 config_errors == TRUE)
2114 warn("Errors while loading %s.", path);
2115 }
2117 static int
2118 load_options(void)
2119 {
2120 const char *home = getenv("HOME");
2121 const char *tigrc_user = getenv("TIGRC_USER");
2122 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2123 char buf[SIZEOF_STR];
2125 if (!tigrc_system)
2126 tigrc_system = SYSCONFDIR "/tigrc";
2127 load_option_file(tigrc_system);
2129 if (!tigrc_user) {
2130 if (!home || !string_format(buf, "%s/.tigrc", home))
2131 return ERR;
2132 tigrc_user = buf;
2133 }
2134 load_option_file(tigrc_user);
2136 /* Add _after_ loading config files to avoid adding run requests
2137 * that conflict with keybindings. */
2138 add_builtin_run_requests();
2140 return OK;
2141 }
2144 /*
2145 * The viewer
2146 */
2148 struct view;
2149 struct view_ops;
2151 /* The display array of active views and the index of the current view. */
2152 static struct view *display[2];
2153 static unsigned int current_view;
2155 #define foreach_displayed_view(view, i) \
2156 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2158 #define displayed_views() (display[1] != NULL ? 2 : 1)
2160 /* Current head and commit ID */
2161 static char ref_blob[SIZEOF_REF] = "";
2162 static char ref_commit[SIZEOF_REF] = "HEAD";
2163 static char ref_head[SIZEOF_REF] = "HEAD";
2164 static char ref_branch[SIZEOF_REF] = "";
2166 enum view_type {
2167 VIEW_MAIN,
2168 VIEW_DIFF,
2169 VIEW_LOG,
2170 VIEW_TREE,
2171 VIEW_BLOB,
2172 VIEW_BLAME,
2173 VIEW_BRANCH,
2174 VIEW_HELP,
2175 VIEW_PAGER,
2176 VIEW_STATUS,
2177 VIEW_STAGE,
2178 };
2180 struct view {
2181 enum view_type type; /* View type */
2182 const char *name; /* View name */
2183 const char *cmd_env; /* Command line set via environment */
2184 const char *id; /* Points to either of ref_{head,commit,blob} */
2186 struct view_ops *ops; /* View operations */
2188 enum keymap keymap; /* What keymap does this view have */
2189 bool git_dir; /* Whether the view requires a git directory. */
2191 char ref[SIZEOF_REF]; /* Hovered commit reference */
2192 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2194 int height, width; /* The width and height of the main window */
2195 WINDOW *win; /* The main window */
2196 WINDOW *title; /* The title window living below the main window */
2198 /* Navigation */
2199 unsigned long offset; /* Offset of the window top */
2200 unsigned long yoffset; /* Offset from the window side. */
2201 unsigned long lineno; /* Current line number */
2202 unsigned long p_offset; /* Previous offset of the window top */
2203 unsigned long p_yoffset;/* Previous offset from the window side */
2204 unsigned long p_lineno; /* Previous current line number */
2205 bool p_restore; /* Should the previous position be restored. */
2207 /* Searching */
2208 char grep[SIZEOF_STR]; /* Search string */
2209 regex_t *regex; /* Pre-compiled regexp */
2211 /* If non-NULL, points to the view that opened this view. If this view
2212 * is closed tig will switch back to the parent view. */
2213 struct view *parent;
2214 struct view *prev;
2216 /* Buffering */
2217 size_t lines; /* Total number of lines */
2218 struct line *line; /* Line index */
2219 unsigned int digits; /* Number of digits in the lines member. */
2221 /* Drawing */
2222 struct line *curline; /* Line currently being drawn. */
2223 enum line_type curtype; /* Attribute currently used for drawing. */
2224 unsigned long col; /* Column when drawing. */
2225 bool has_scrolled; /* View was scrolled. */
2227 /* Loading */
2228 const char **argv; /* Shell command arguments. */
2229 const char *dir; /* Directory from which to execute. */
2230 struct io io;
2231 struct io *pipe;
2232 time_t start_time;
2233 time_t update_secs;
2234 };
2236 struct view_ops {
2237 /* What type of content being displayed. Used in the title bar. */
2238 const char *type;
2239 /* Default command arguments. */
2240 const char **argv;
2241 /* Open and reads in all view content. */
2242 bool (*open)(struct view *view);
2243 /* Read one line; updates view->line. */
2244 bool (*read)(struct view *view, char *data);
2245 /* Draw one line; @lineno must be < view->height. */
2246 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2247 /* Depending on view handle a special requests. */
2248 enum request (*request)(struct view *view, enum request request, struct line *line);
2249 /* Search for regexp in a line. */
2250 bool (*grep)(struct view *view, struct line *line);
2251 /* Select line */
2252 void (*select)(struct view *view, struct line *line);
2253 /* Prepare view for loading */
2254 bool (*prepare)(struct view *view);
2255 };
2257 static struct view_ops blame_ops;
2258 static struct view_ops blob_ops;
2259 static struct view_ops diff_ops;
2260 static struct view_ops help_ops;
2261 static struct view_ops log_ops;
2262 static struct view_ops main_ops;
2263 static struct view_ops pager_ops;
2264 static struct view_ops stage_ops;
2265 static struct view_ops status_ops;
2266 static struct view_ops tree_ops;
2267 static struct view_ops branch_ops;
2269 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2270 { type, name, #env, ref, ops, map, git }
2272 #define VIEW_(id, name, ops, git, ref) \
2273 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2275 static struct view views[] = {
2276 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2277 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2278 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2279 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2280 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2281 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2282 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2283 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2284 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2285 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2286 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2287 };
2289 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2291 #define foreach_view(view, i) \
2292 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2294 #define view_is_displayed(view) \
2295 (view == display[0] || view == display[1])
2297 static enum request
2298 view_request(struct view *view, enum request request)
2299 {
2300 if (!view || !view->lines)
2301 return request;
2302 return view->ops->request(view, request, &view->line[view->lineno]);
2303 }
2306 /*
2307 * View drawing.
2308 */
2310 static inline void
2311 set_view_attr(struct view *view, enum line_type type)
2312 {
2313 if (!view->curline->selected && view->curtype != type) {
2314 (void) wattrset(view->win, get_line_attr(type));
2315 wchgat(view->win, -1, 0, type, NULL);
2316 view->curtype = type;
2317 }
2318 }
2320 static int
2321 draw_chars(struct view *view, enum line_type type, const char *string,
2322 int max_len, bool use_tilde)
2323 {
2324 static char out_buffer[BUFSIZ * 2];
2325 int len = 0;
2326 int col = 0;
2327 int trimmed = FALSE;
2328 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2330 if (max_len <= 0)
2331 return 0;
2333 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2335 set_view_attr(view, type);
2336 if (len > 0) {
2337 if (opt_iconv_out != ICONV_NONE) {
2338 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2339 size_t inlen = len + 1;
2341 char *outbuf = out_buffer;
2342 size_t outlen = sizeof(out_buffer);
2344 size_t ret;
2346 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2347 if (ret != (size_t) -1) {
2348 string = out_buffer;
2349 len = sizeof(out_buffer) - outlen;
2350 }
2351 }
2353 waddnstr(view->win, string, len);
2354 }
2355 if (trimmed && use_tilde) {
2356 set_view_attr(view, LINE_DELIMITER);
2357 waddch(view->win, '~');
2358 col++;
2359 }
2361 return col;
2362 }
2364 static int
2365 draw_space(struct view *view, enum line_type type, int max, int spaces)
2366 {
2367 static char space[] = " ";
2368 int col = 0;
2370 spaces = MIN(max, spaces);
2372 while (spaces > 0) {
2373 int len = MIN(spaces, sizeof(space) - 1);
2375 col += draw_chars(view, type, space, len, FALSE);
2376 spaces -= len;
2377 }
2379 return col;
2380 }
2382 static bool
2383 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2384 {
2385 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2386 return view->width + view->yoffset <= view->col;
2387 }
2389 static bool
2390 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2391 {
2392 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2393 int max = view->width + view->yoffset - view->col;
2394 int i;
2396 if (max < size)
2397 size = max;
2399 set_view_attr(view, type);
2400 /* Using waddch() instead of waddnstr() ensures that
2401 * they'll be rendered correctly for the cursor line. */
2402 for (i = skip; i < size; i++)
2403 waddch(view->win, graphic[i]);
2405 view->col += size;
2406 if (size < max && skip <= size)
2407 waddch(view->win, ' ');
2408 view->col++;
2410 return view->width + view->yoffset <= view->col;
2411 }
2413 static bool
2414 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2415 {
2416 int max = MIN(view->width + view->yoffset - view->col, len);
2417 int col;
2419 if (text)
2420 col = draw_chars(view, type, text, max - 1, trim);
2421 else
2422 col = draw_space(view, type, max - 1, max - 1);
2424 view->col += col;
2425 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2426 return view->width + view->yoffset <= view->col;
2427 }
2429 static bool
2430 draw_date(struct view *view, struct time *time)
2431 {
2432 const char *date = mkdate(time, opt_date);
2433 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2435 return draw_field(view, LINE_DATE, date, cols, FALSE);
2436 }
2438 static bool
2439 draw_author(struct view *view, const char *author)
2440 {
2441 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2442 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2444 if (abbreviate && author)
2445 author = get_author_initials(author);
2447 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2448 }
2450 static bool
2451 draw_mode(struct view *view, mode_t mode)
2452 {
2453 const char *str;
2455 if (S_ISDIR(mode))
2456 str = "drwxr-xr-x";
2457 else if (S_ISLNK(mode))
2458 str = "lrwxrwxrwx";
2459 else if (S_ISGITLINK(mode))
2460 str = "m---------";
2461 else if (S_ISREG(mode) && mode & S_IXUSR)
2462 str = "-rwxr-xr-x";
2463 else if (S_ISREG(mode))
2464 str = "-rw-r--r--";
2465 else
2466 str = "----------";
2468 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2469 }
2471 static bool
2472 draw_lineno(struct view *view, unsigned int lineno)
2473 {
2474 char number[10];
2475 int digits3 = view->digits < 3 ? 3 : view->digits;
2476 int max = MIN(view->width + view->yoffset - view->col, digits3);
2477 char *text = NULL;
2478 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2480 lineno += view->offset + 1;
2481 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2482 static char fmt[] = "%1ld";
2484 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2485 if (string_format(number, fmt, lineno))
2486 text = number;
2487 }
2488 if (text)
2489 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2490 else
2491 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2492 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2493 }
2495 static bool
2496 draw_view_line(struct view *view, unsigned int lineno)
2497 {
2498 struct line *line;
2499 bool selected = (view->offset + lineno == view->lineno);
2501 assert(view_is_displayed(view));
2503 if (view->offset + lineno >= view->lines)
2504 return FALSE;
2506 line = &view->line[view->offset + lineno];
2508 wmove(view->win, lineno, 0);
2509 if (line->cleareol)
2510 wclrtoeol(view->win);
2511 view->col = 0;
2512 view->curline = line;
2513 view->curtype = LINE_NONE;
2514 line->selected = FALSE;
2515 line->dirty = line->cleareol = 0;
2517 if (selected) {
2518 set_view_attr(view, LINE_CURSOR);
2519 line->selected = TRUE;
2520 view->ops->select(view, line);
2521 }
2523 return view->ops->draw(view, line, lineno);
2524 }
2526 static void
2527 redraw_view_dirty(struct view *view)
2528 {
2529 bool dirty = FALSE;
2530 int lineno;
2532 for (lineno = 0; lineno < view->height; lineno++) {
2533 if (view->offset + lineno >= view->lines)
2534 break;
2535 if (!view->line[view->offset + lineno].dirty)
2536 continue;
2537 dirty = TRUE;
2538 if (!draw_view_line(view, lineno))
2539 break;
2540 }
2542 if (!dirty)
2543 return;
2544 wnoutrefresh(view->win);
2545 }
2547 static void
2548 redraw_view_from(struct view *view, int lineno)
2549 {
2550 assert(0 <= lineno && lineno < view->height);
2552 for (; lineno < view->height; lineno++) {
2553 if (!draw_view_line(view, lineno))
2554 break;
2555 }
2557 wnoutrefresh(view->win);
2558 }
2560 static void
2561 redraw_view(struct view *view)
2562 {
2563 werase(view->win);
2564 redraw_view_from(view, 0);
2565 }
2568 static void
2569 update_view_title(struct view *view)
2570 {
2571 char buf[SIZEOF_STR];
2572 char state[SIZEOF_STR];
2573 size_t bufpos = 0, statelen = 0;
2575 assert(view_is_displayed(view));
2577 if (view->type != VIEW_STATUS && view->lines) {
2578 unsigned int view_lines = view->offset + view->height;
2579 unsigned int lines = view->lines
2580 ? MIN(view_lines, view->lines) * 100 / view->lines
2581 : 0;
2583 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2584 view->ops->type,
2585 view->lineno + 1,
2586 view->lines,
2587 lines);
2589 }
2591 if (view->pipe) {
2592 time_t secs = time(NULL) - view->start_time;
2594 /* Three git seconds are a long time ... */
2595 if (secs > 2)
2596 string_format_from(state, &statelen, " loading %lds", secs);
2597 }
2599 string_format_from(buf, &bufpos, "[%s]", view->name);
2600 if (*view->ref && bufpos < view->width) {
2601 size_t refsize = strlen(view->ref);
2602 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2604 if (minsize < view->width)
2605 refsize = view->width - minsize + 7;
2606 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2607 }
2609 if (statelen && bufpos < view->width) {
2610 string_format_from(buf, &bufpos, "%s", state);
2611 }
2613 if (view == display[current_view])
2614 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2615 else
2616 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2618 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2619 wclrtoeol(view->title);
2620 wnoutrefresh(view->title);
2621 }
2623 static int
2624 apply_step(double step, int value)
2625 {
2626 if (step >= 1)
2627 return (int) step;
2628 value *= step + 0.01;
2629 return value ? value : 1;
2630 }
2632 static void
2633 resize_display(void)
2634 {
2635 int offset, i;
2636 struct view *base = display[0];
2637 struct view *view = display[1] ? display[1] : display[0];
2639 /* Setup window dimensions */
2641 getmaxyx(stdscr, base->height, base->width);
2643 /* Make room for the status window. */
2644 base->height -= 1;
2646 if (view != base) {
2647 /* Horizontal split. */
2648 view->width = base->width;
2649 view->height = apply_step(opt_scale_split_view, base->height);
2650 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2651 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2652 base->height -= view->height;
2654 /* Make room for the title bar. */
2655 view->height -= 1;
2656 }
2658 /* Make room for the title bar. */
2659 base->height -= 1;
2661 offset = 0;
2663 foreach_displayed_view (view, i) {
2664 if (!view->win) {
2665 view->win = newwin(view->height, 0, offset, 0);
2666 if (!view->win)
2667 die("Failed to create %s view", view->name);
2669 scrollok(view->win, FALSE);
2671 view->title = newwin(1, 0, offset + view->height, 0);
2672 if (!view->title)
2673 die("Failed to create title window");
2675 } else {
2676 wresize(view->win, view->height, view->width);
2677 mvwin(view->win, offset, 0);
2678 mvwin(view->title, offset + view->height, 0);
2679 }
2681 offset += view->height + 1;
2682 }
2683 }
2685 static void
2686 redraw_display(bool clear)
2687 {
2688 struct view *view;
2689 int i;
2691 foreach_displayed_view (view, i) {
2692 if (clear)
2693 wclear(view->win);
2694 redraw_view(view);
2695 update_view_title(view);
2696 }
2697 }
2700 /*
2701 * Option management
2702 */
2704 static void
2705 toggle_enum_option_do(unsigned int *opt, const char *help,
2706 const struct enum_map *map, size_t size)
2707 {
2708 *opt = (*opt + 1) % size;
2709 redraw_display(FALSE);
2710 report("Displaying %s %s", enum_name(map[*opt]), help);
2711 }
2713 #define toggle_enum_option(opt, help, map) \
2714 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2716 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2717 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2719 static void
2720 toggle_view_option(bool *option, const char *help)
2721 {
2722 *option = !*option;
2723 redraw_display(FALSE);
2724 report("%sabling %s", *option ? "En" : "Dis", help);
2725 }
2727 static void
2728 open_option_menu(void)
2729 {
2730 const struct menu_item menu[] = {
2731 { '.', "line numbers", &opt_line_number },
2732 { 'D', "date display", &opt_date },
2733 { 'A', "author display", &opt_author },
2734 { 'g', "revision graph display", &opt_rev_graph },
2735 { 'F', "reference display", &opt_show_refs },
2736 { 0 }
2737 };
2738 int selected = 0;
2740 if (prompt_menu("Toggle option", menu, &selected)) {
2741 if (menu[selected].data == &opt_date)
2742 toggle_date();
2743 else if (menu[selected].data == &opt_author)
2744 toggle_author();
2745 else
2746 toggle_view_option(menu[selected].data, menu[selected].text);
2747 }
2748 }
2750 static void
2751 maximize_view(struct view *view)
2752 {
2753 memset(display, 0, sizeof(display));
2754 current_view = 0;
2755 display[current_view] = view;
2756 resize_display();
2757 redraw_display(FALSE);
2758 report("");
2759 }
2762 /*
2763 * Navigation
2764 */
2766 static bool
2767 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2768 {
2769 if (lineno >= view->lines)
2770 lineno = view->lines > 0 ? view->lines - 1 : 0;
2772 if (offset > lineno || offset + view->height <= lineno) {
2773 unsigned long half = view->height / 2;
2775 if (lineno > half)
2776 offset = lineno - half;
2777 else
2778 offset = 0;
2779 }
2781 if (offset != view->offset || lineno != view->lineno) {
2782 view->offset = offset;
2783 view->lineno = lineno;
2784 return TRUE;
2785 }
2787 return FALSE;
2788 }
2790 /* Scrolling backend */
2791 static void
2792 do_scroll_view(struct view *view, int lines)
2793 {
2794 bool redraw_current_line = FALSE;
2796 /* The rendering expects the new offset. */
2797 view->offset += lines;
2799 assert(0 <= view->offset && view->offset < view->lines);
2800 assert(lines);
2802 /* Move current line into the view. */
2803 if (view->lineno < view->offset) {
2804 view->lineno = view->offset;
2805 redraw_current_line = TRUE;
2806 } else if (view->lineno >= view->offset + view->height) {
2807 view->lineno = view->offset + view->height - 1;
2808 redraw_current_line = TRUE;
2809 }
2811 assert(view->offset <= view->lineno && view->lineno < view->lines);
2813 /* Redraw the whole screen if scrolling is pointless. */
2814 if (view->height < ABS(lines)) {
2815 redraw_view(view);
2817 } else {
2818 int line = lines > 0 ? view->height - lines : 0;
2819 int end = line + ABS(lines);
2821 scrollok(view->win, TRUE);
2822 wscrl(view->win, lines);
2823 scrollok(view->win, FALSE);
2825 while (line < end && draw_view_line(view, line))
2826 line++;
2828 if (redraw_current_line)
2829 draw_view_line(view, view->lineno - view->offset);
2830 wnoutrefresh(view->win);
2831 }
2833 view->has_scrolled = TRUE;
2834 report("");
2835 }
2837 /* Scroll frontend */
2838 static void
2839 scroll_view(struct view *view, enum request request)
2840 {
2841 int lines = 1;
2843 assert(view_is_displayed(view));
2845 switch (request) {
2846 case REQ_SCROLL_LEFT:
2847 if (view->yoffset == 0) {
2848 report("Cannot scroll beyond the first column");
2849 return;
2850 }
2851 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2852 view->yoffset = 0;
2853 else
2854 view->yoffset -= apply_step(opt_hscroll, view->width);
2855 redraw_view_from(view, 0);
2856 report("");
2857 return;
2858 case REQ_SCROLL_RIGHT:
2859 view->yoffset += apply_step(opt_hscroll, view->width);
2860 redraw_view(view);
2861 report("");
2862 return;
2863 case REQ_SCROLL_PAGE_DOWN:
2864 lines = view->height;
2865 case REQ_SCROLL_LINE_DOWN:
2866 if (view->offset + lines > view->lines)
2867 lines = view->lines - view->offset;
2869 if (lines == 0 || view->offset + view->height >= view->lines) {
2870 report("Cannot scroll beyond the last line");
2871 return;
2872 }
2873 break;
2875 case REQ_SCROLL_PAGE_UP:
2876 lines = view->height;
2877 case REQ_SCROLL_LINE_UP:
2878 if (lines > view->offset)
2879 lines = view->offset;
2881 if (lines == 0) {
2882 report("Cannot scroll beyond the first line");
2883 return;
2884 }
2886 lines = -lines;
2887 break;
2889 default:
2890 die("request %d not handled in switch", request);
2891 }
2893 do_scroll_view(view, lines);
2894 }
2896 /* Cursor moving */
2897 static void
2898 move_view(struct view *view, enum request request)
2899 {
2900 int scroll_steps = 0;
2901 int steps;
2903 switch (request) {
2904 case REQ_MOVE_FIRST_LINE:
2905 steps = -view->lineno;
2906 break;
2908 case REQ_MOVE_LAST_LINE:
2909 steps = view->lines - view->lineno - 1;
2910 break;
2912 case REQ_MOVE_PAGE_UP:
2913 steps = view->height > view->lineno
2914 ? -view->lineno : -view->height;
2915 break;
2917 case REQ_MOVE_PAGE_DOWN:
2918 steps = view->lineno + view->height >= view->lines
2919 ? view->lines - view->lineno - 1 : view->height;
2920 break;
2922 case REQ_MOVE_UP:
2923 steps = -1;
2924 break;
2926 case REQ_MOVE_DOWN:
2927 steps = 1;
2928 break;
2930 default:
2931 die("request %d not handled in switch", request);
2932 }
2934 if (steps <= 0 && view->lineno == 0) {
2935 report("Cannot move beyond the first line");
2936 return;
2938 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2939 report("Cannot move beyond the last line");
2940 return;
2941 }
2943 /* Move the current line */
2944 view->lineno += steps;
2945 assert(0 <= view->lineno && view->lineno < view->lines);
2947 /* Check whether the view needs to be scrolled */
2948 if (view->lineno < view->offset ||
2949 view->lineno >= view->offset + view->height) {
2950 scroll_steps = steps;
2951 if (steps < 0 && -steps > view->offset) {
2952 scroll_steps = -view->offset;
2954 } else if (steps > 0) {
2955 if (view->lineno == view->lines - 1 &&
2956 view->lines > view->height) {
2957 scroll_steps = view->lines - view->offset - 1;
2958 if (scroll_steps >= view->height)
2959 scroll_steps -= view->height - 1;
2960 }
2961 }
2962 }
2964 if (!view_is_displayed(view)) {
2965 view->offset += scroll_steps;
2966 assert(0 <= view->offset && view->offset < view->lines);
2967 view->ops->select(view, &view->line[view->lineno]);
2968 return;
2969 }
2971 /* Repaint the old "current" line if we be scrolling */
2972 if (ABS(steps) < view->height)
2973 draw_view_line(view, view->lineno - steps - view->offset);
2975 if (scroll_steps) {
2976 do_scroll_view(view, scroll_steps);
2977 return;
2978 }
2980 /* Draw the current line */
2981 draw_view_line(view, view->lineno - view->offset);
2983 wnoutrefresh(view->win);
2984 report("");
2985 }
2988 /*
2989 * Searching
2990 */
2992 static void search_view(struct view *view, enum request request);
2994 static bool
2995 grep_text(struct view *view, const char *text[])
2996 {
2997 regmatch_t pmatch;
2998 size_t i;
3000 for (i = 0; text[i]; i++)
3001 if (*text[i] &&
3002 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3003 return TRUE;
3004 return FALSE;
3005 }
3007 static void
3008 select_view_line(struct view *view, unsigned long lineno)
3009 {
3010 unsigned long old_lineno = view->lineno;
3011 unsigned long old_offset = view->offset;
3013 if (goto_view_line(view, view->offset, lineno)) {
3014 if (view_is_displayed(view)) {
3015 if (old_offset != view->offset) {
3016 redraw_view(view);
3017 } else {
3018 draw_view_line(view, old_lineno - view->offset);
3019 draw_view_line(view, view->lineno - view->offset);
3020 wnoutrefresh(view->win);
3021 }
3022 } else {
3023 view->ops->select(view, &view->line[view->lineno]);
3024 }
3025 }
3026 }
3028 static void
3029 find_next(struct view *view, enum request request)
3030 {
3031 unsigned long lineno = view->lineno;
3032 int direction;
3034 if (!*view->grep) {
3035 if (!*opt_search)
3036 report("No previous search");
3037 else
3038 search_view(view, request);
3039 return;
3040 }
3042 switch (request) {
3043 case REQ_SEARCH:
3044 case REQ_FIND_NEXT:
3045 direction = 1;
3046 break;
3048 case REQ_SEARCH_BACK:
3049 case REQ_FIND_PREV:
3050 direction = -1;
3051 break;
3053 default:
3054 return;
3055 }
3057 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3058 lineno += direction;
3060 /* Note, lineno is unsigned long so will wrap around in which case it
3061 * will become bigger than view->lines. */
3062 for (; lineno < view->lines; lineno += direction) {
3063 if (view->ops->grep(view, &view->line[lineno])) {
3064 select_view_line(view, lineno);
3065 report("Line %ld matches '%s'", lineno + 1, view->grep);
3066 return;
3067 }
3068 }
3070 report("No match found for '%s'", view->grep);
3071 }
3073 static void
3074 search_view(struct view *view, enum request request)
3075 {
3076 int regex_err;
3078 if (view->regex) {
3079 regfree(view->regex);
3080 *view->grep = 0;
3081 } else {
3082 view->regex = calloc(1, sizeof(*view->regex));
3083 if (!view->regex)
3084 return;
3085 }
3087 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3088 if (regex_err != 0) {
3089 char buf[SIZEOF_STR] = "unknown error";
3091 regerror(regex_err, view->regex, buf, sizeof(buf));
3092 report("Search failed: %s", buf);
3093 return;
3094 }
3096 string_copy(view->grep, opt_search);
3098 find_next(view, request);
3099 }
3101 /*
3102 * Incremental updating
3103 */
3105 static void
3106 reset_view(struct view *view)
3107 {
3108 int i;
3110 for (i = 0; i < view->lines; i++)
3111 free(view->line[i].data);
3112 free(view->line);
3114 view->p_offset = view->offset;
3115 view->p_yoffset = view->yoffset;
3116 view->p_lineno = view->lineno;
3118 view->line = NULL;
3119 view->offset = 0;
3120 view->yoffset = 0;
3121 view->lines = 0;
3122 view->lineno = 0;
3123 view->vid[0] = 0;
3124 view->update_secs = 0;
3125 }
3127 static const char *
3128 format_arg(const char *name)
3129 {
3130 static struct {
3131 const char *name;
3132 size_t namelen;
3133 const char *value;
3134 const char *value_if_empty;
3135 } vars[] = {
3136 #define FORMAT_VAR(name, value, value_if_empty) \
3137 { name, STRING_SIZE(name), value, value_if_empty }
3138 FORMAT_VAR("%(directory)", opt_path, ""),
3139 FORMAT_VAR("%(file)", opt_file, ""),
3140 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3141 FORMAT_VAR("%(head)", ref_head, ""),
3142 FORMAT_VAR("%(commit)", ref_commit, ""),
3143 FORMAT_VAR("%(blob)", ref_blob, ""),
3144 FORMAT_VAR("%(branch)", ref_branch, ""),
3145 };
3146 int i;
3148 for (i = 0; i < ARRAY_SIZE(vars); i++)
3149 if (!strncmp(name, vars[i].name, vars[i].namelen))
3150 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3152 report("Unknown replacement: `%s`", name);
3153 return NULL;
3154 }
3156 static bool
3157 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3158 {
3159 char buf[SIZEOF_STR];
3160 int argc;
3162 argv_free(*dst_argv);
3164 for (argc = 0; src_argv[argc]; argc++) {
3165 const char *arg = src_argv[argc];
3166 size_t bufpos = 0;
3168 while (arg) {
3169 char *next = strstr(arg, "%(");
3170 int len = next - arg;
3171 const char *value;
3173 if (!next || !replace) {
3174 len = strlen(arg);
3175 value = "";
3177 } else {
3178 value = format_arg(next);
3180 if (!value) {
3181 return FALSE;
3182 }
3183 }
3185 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3186 return FALSE;
3188 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3189 }
3191 if (!argv_append(dst_argv, buf))
3192 break;
3193 }
3195 return src_argv[argc] == NULL;
3196 }
3198 static bool
3199 restore_view_position(struct view *view)
3200 {
3201 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3202 return FALSE;
3204 /* Changing the view position cancels the restoring. */
3205 /* FIXME: Changing back to the first line is not detected. */
3206 if (view->offset != 0 || view->lineno != 0) {
3207 view->p_restore = FALSE;
3208 return FALSE;
3209 }
3211 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3212 view_is_displayed(view))
3213 werase(view->win);
3215 view->yoffset = view->p_yoffset;
3216 view->p_restore = FALSE;
3218 return TRUE;
3219 }
3221 static void
3222 end_update(struct view *view, bool force)
3223 {
3224 if (!view->pipe)
3225 return;
3226 while (!view->ops->read(view, NULL))
3227 if (!force)
3228 return;
3229 if (force)
3230 io_kill(view->pipe);
3231 io_done(view->pipe);
3232 view->pipe = NULL;
3233 }
3235 static void
3236 setup_update(struct view *view, const char *vid)
3237 {
3238 reset_view(view);
3239 string_copy_rev(view->vid, vid);
3240 view->pipe = &view->io;
3241 view->start_time = time(NULL);
3242 }
3244 static bool
3245 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3246 {
3247 view->dir = dir;
3248 return format_argv(&view->argv, argv, replace);
3249 }
3251 static bool
3252 prepare_update(struct view *view, const char *argv[], const char *dir)
3253 {
3254 if (view->pipe)
3255 end_update(view, TRUE);
3256 return prepare_io(view, dir, argv, FALSE);
3257 }
3259 static bool
3260 start_update(struct view *view, const char **argv, const char *dir)
3261 {
3262 if (view->pipe)
3263 io_done(view->pipe);
3264 return prepare_io(view, dir, argv, FALSE) &&
3265 io_run(&view->io, IO_RD, dir, view->argv);
3266 }
3268 static bool
3269 prepare_update_file(struct view *view, const char *name)
3270 {
3271 if (view->pipe)
3272 end_update(view, TRUE);
3273 argv_free(view->argv);
3274 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3275 }
3277 static bool
3278 begin_update(struct view *view, bool refresh)
3279 {
3280 if (view->pipe)
3281 end_update(view, TRUE);
3283 if (!refresh) {
3284 if (view->ops->prepare) {
3285 if (!view->ops->prepare(view))
3286 return FALSE;
3287 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3288 return FALSE;
3289 }
3291 /* Put the current ref_* value to the view title ref
3292 * member. This is needed by the blob view. Most other
3293 * views sets it automatically after loading because the
3294 * first line is a commit line. */
3295 string_copy_rev(view->ref, view->id);
3296 }
3298 if (view->argv && view->argv[0] &&
3299 !io_run(&view->io, IO_RD, view->dir, view->argv))
3300 return FALSE;
3302 setup_update(view, view->id);
3304 return TRUE;
3305 }
3307 static bool
3308 update_view(struct view *view)
3309 {
3310 char out_buffer[BUFSIZ * 2];
3311 char *line;
3312 /* Clear the view and redraw everything since the tree sorting
3313 * might have rearranged things. */
3314 bool redraw = view->lines == 0;
3315 bool can_read = TRUE;
3317 if (!view->pipe)
3318 return TRUE;
3320 if (!io_can_read(view->pipe)) {
3321 if (view->lines == 0 && view_is_displayed(view)) {
3322 time_t secs = time(NULL) - view->start_time;
3324 if (secs > 1 && secs > view->update_secs) {
3325 if (view->update_secs == 0)
3326 redraw_view(view);
3327 update_view_title(view);
3328 view->update_secs = secs;
3329 }
3330 }
3331 return TRUE;
3332 }
3334 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3335 if (opt_iconv_in != ICONV_NONE) {
3336 ICONV_CONST char *inbuf = line;
3337 size_t inlen = strlen(line) + 1;
3339 char *outbuf = out_buffer;
3340 size_t outlen = sizeof(out_buffer);
3342 size_t ret;
3344 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3345 if (ret != (size_t) -1)
3346 line = out_buffer;
3347 }
3349 if (!view->ops->read(view, line)) {
3350 report("Allocation failure");
3351 end_update(view, TRUE);
3352 return FALSE;
3353 }
3354 }
3356 {
3357 unsigned long lines = view->lines;
3358 int digits;
3360 for (digits = 0; lines; digits++)
3361 lines /= 10;
3363 /* Keep the displayed view in sync with line number scaling. */
3364 if (digits != view->digits) {
3365 view->digits = digits;
3366 if (opt_line_number || view->type == VIEW_BLAME)
3367 redraw = TRUE;
3368 }
3369 }
3371 if (io_error(view->pipe)) {
3372 report("Failed to read: %s", io_strerror(view->pipe));
3373 end_update(view, TRUE);
3375 } else if (io_eof(view->pipe)) {
3376 if (view_is_displayed(view))
3377 report("");
3378 end_update(view, FALSE);
3379 }
3381 if (restore_view_position(view))
3382 redraw = TRUE;
3384 if (!view_is_displayed(view))
3385 return TRUE;
3387 if (redraw)
3388 redraw_view_from(view, 0);
3389 else
3390 redraw_view_dirty(view);
3392 /* Update the title _after_ the redraw so that if the redraw picks up a
3393 * commit reference in view->ref it'll be available here. */
3394 update_view_title(view);
3395 return TRUE;
3396 }
3398 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3400 static struct line *
3401 add_line_data(struct view *view, void *data, enum line_type type)
3402 {
3403 struct line *line;
3405 if (!realloc_lines(&view->line, view->lines, 1))
3406 return NULL;
3408 line = &view->line[view->lines++];
3409 memset(line, 0, sizeof(*line));
3410 line->type = type;
3411 line->data = data;
3412 line->dirty = 1;
3414 return line;
3415 }
3417 static struct line *
3418 add_line_text(struct view *view, const char *text, enum line_type type)
3419 {
3420 char *data = text ? strdup(text) : NULL;
3422 return data ? add_line_data(view, data, type) : NULL;
3423 }
3425 static struct line *
3426 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3427 {
3428 char buf[SIZEOF_STR];
3429 va_list args;
3431 va_start(args, fmt);
3432 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3433 buf[0] = 0;
3434 va_end(args);
3436 return buf[0] ? add_line_text(view, buf, type) : NULL;
3437 }
3439 /*
3440 * View opening
3441 */
3443 enum open_flags {
3444 OPEN_DEFAULT = 0, /* Use default view switching. */
3445 OPEN_SPLIT = 1, /* Split current view. */
3446 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3447 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3448 OPEN_PREPARED = 32, /* Open already prepared command. */
3449 };
3451 static void
3452 open_view(struct view *prev, enum request request, enum open_flags flags)
3453 {
3454 bool split = !!(flags & OPEN_SPLIT);
3455 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3456 bool nomaximize = !!(flags & OPEN_REFRESH);
3457 struct view *view = VIEW(request);
3458 int nviews = displayed_views();
3459 struct view *base_view = display[0];
3461 if (view == prev && nviews == 1 && !reload) {
3462 report("Already in %s view", view->name);
3463 return;
3464 }
3466 if (view->git_dir && !opt_git_dir[0]) {
3467 report("The %s view is disabled in pager view", view->name);
3468 return;
3469 }
3471 if (split) {
3472 display[1] = view;
3473 current_view = 1;
3474 view->parent = prev;
3475 } else if (!nomaximize) {
3476 /* Maximize the current view. */
3477 memset(display, 0, sizeof(display));
3478 current_view = 0;
3479 display[current_view] = view;
3480 }
3482 /* No prev signals that this is the first loaded view. */
3483 if (prev && view != prev) {
3484 view->prev = prev;
3485 }
3487 /* Resize the view when switching between split- and full-screen,
3488 * or when switching between two different full-screen views. */
3489 if (nviews != displayed_views() ||
3490 (nviews == 1 && base_view != display[0]))
3491 resize_display();
3493 if (view->ops->open) {
3494 if (view->pipe)
3495 end_update(view, TRUE);
3496 if (!view->ops->open(view)) {
3497 report("Failed to load %s view", view->name);
3498 return;
3499 }
3500 restore_view_position(view);
3502 } else if ((reload || strcmp(view->vid, view->id)) &&
3503 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3504 report("Failed to load %s view", view->name);
3505 return;
3506 }
3508 if (split && prev->lineno - prev->offset >= prev->height) {
3509 /* Take the title line into account. */
3510 int lines = prev->lineno - prev->offset - prev->height + 1;
3512 /* Scroll the view that was split if the current line is
3513 * outside the new limited view. */
3514 do_scroll_view(prev, lines);
3515 }
3517 if (prev && view != prev && split && view_is_displayed(prev)) {
3518 /* "Blur" the previous view. */
3519 update_view_title(prev);
3520 }
3522 if (view->pipe && view->lines == 0) {
3523 /* Clear the old view and let the incremental updating refill
3524 * the screen. */
3525 werase(view->win);
3526 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3527 report("");
3528 } else if (view_is_displayed(view)) {
3529 redraw_view(view);
3530 report("");
3531 }
3532 }
3534 static void
3535 open_external_viewer(const char *argv[], const char *dir)
3536 {
3537 def_prog_mode(); /* save current tty modes */
3538 endwin(); /* restore original tty modes */
3539 io_run_fg(argv, dir);
3540 fprintf(stderr, "Press Enter to continue");
3541 getc(opt_tty);
3542 reset_prog_mode();
3543 redraw_display(TRUE);
3544 }
3546 static void
3547 open_mergetool(const char *file)
3548 {
3549 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3551 open_external_viewer(mergetool_argv, opt_cdup);
3552 }
3554 static void
3555 open_editor(const char *file)
3556 {
3557 const char *editor_argv[] = { "vi", file, NULL };
3558 const char *editor;
3560 editor = getenv("GIT_EDITOR");
3561 if (!editor && *opt_editor)
3562 editor = opt_editor;
3563 if (!editor)
3564 editor = getenv("VISUAL");
3565 if (!editor)
3566 editor = getenv("EDITOR");
3567 if (!editor)
3568 editor = "vi";
3570 editor_argv[0] = editor;
3571 open_external_viewer(editor_argv, opt_cdup);
3572 }
3574 static void
3575 open_run_request(enum request request)
3576 {
3577 struct run_request *req = get_run_request(request);
3578 const char **argv = NULL;
3580 if (!req) {
3581 report("Unknown run request");
3582 return;
3583 }
3585 if (format_argv(&argv, req->argv, TRUE))
3586 open_external_viewer(argv, NULL);
3587 if (argv)
3588 argv_free(argv);
3589 free(argv);
3590 }
3592 /*
3593 * User request switch noodle
3594 */
3596 static int
3597 view_driver(struct view *view, enum request request)
3598 {
3599 int i;
3601 if (request == REQ_NONE)
3602 return TRUE;
3604 if (request > REQ_NONE) {
3605 open_run_request(request);
3606 view_request(view, REQ_REFRESH);
3607 return TRUE;
3608 }
3610 request = view_request(view, request);
3611 if (request == REQ_NONE)
3612 return TRUE;
3614 switch (request) {
3615 case REQ_MOVE_UP:
3616 case REQ_MOVE_DOWN:
3617 case REQ_MOVE_PAGE_UP:
3618 case REQ_MOVE_PAGE_DOWN:
3619 case REQ_MOVE_FIRST_LINE:
3620 case REQ_MOVE_LAST_LINE:
3621 move_view(view, request);
3622 break;
3624 case REQ_SCROLL_LEFT:
3625 case REQ_SCROLL_RIGHT:
3626 case REQ_SCROLL_LINE_DOWN:
3627 case REQ_SCROLL_LINE_UP:
3628 case REQ_SCROLL_PAGE_DOWN:
3629 case REQ_SCROLL_PAGE_UP:
3630 scroll_view(view, request);
3631 break;
3633 case REQ_VIEW_BLAME:
3634 if (!opt_file[0]) {
3635 report("No file chosen, press %s to open tree view",
3636 get_key(view->keymap, REQ_VIEW_TREE));
3637 break;
3638 }
3639 open_view(view, request, OPEN_DEFAULT);
3640 break;
3642 case REQ_VIEW_BLOB:
3643 if (!ref_blob[0]) {
3644 report("No file chosen, press %s to open tree view",
3645 get_key(view->keymap, REQ_VIEW_TREE));
3646 break;
3647 }
3648 open_view(view, request, OPEN_DEFAULT);
3649 break;
3651 case REQ_VIEW_PAGER:
3652 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3653 report("No pager content, press %s to run command from prompt",
3654 get_key(view->keymap, REQ_PROMPT));
3655 break;
3656 }
3657 open_view(view, request, OPEN_DEFAULT);
3658 break;
3660 case REQ_VIEW_STAGE:
3661 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3662 report("No stage content, press %s to open the status view and choose file",
3663 get_key(view->keymap, REQ_VIEW_STATUS));
3664 break;
3665 }
3666 open_view(view, request, OPEN_DEFAULT);
3667 break;
3669 case REQ_VIEW_STATUS:
3670 if (opt_is_inside_work_tree == FALSE) {
3671 report("The status view requires a working tree");
3672 break;
3673 }
3674 open_view(view, request, OPEN_DEFAULT);
3675 break;
3677 case REQ_VIEW_MAIN:
3678 case REQ_VIEW_DIFF:
3679 case REQ_VIEW_LOG:
3680 case REQ_VIEW_TREE:
3681 case REQ_VIEW_HELP:
3682 case REQ_VIEW_BRANCH:
3683 open_view(view, request, OPEN_DEFAULT);
3684 break;
3686 case REQ_NEXT:
3687 case REQ_PREVIOUS:
3688 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3690 if (view->parent) {
3691 int line;
3693 view = view->parent;
3694 line = view->lineno;
3695 move_view(view, request);
3696 if (view_is_displayed(view))
3697 update_view_title(view);
3698 if (line != view->lineno)
3699 view_request(view, REQ_ENTER);
3700 } else {
3701 move_view(view, request);
3702 }
3703 break;
3705 case REQ_VIEW_NEXT:
3706 {
3707 int nviews = displayed_views();
3708 int next_view = (current_view + 1) % nviews;
3710 if (next_view == current_view) {
3711 report("Only one view is displayed");
3712 break;
3713 }
3715 current_view = next_view;
3716 /* Blur out the title of the previous view. */
3717 update_view_title(view);
3718 report("");
3719 break;
3720 }
3721 case REQ_REFRESH:
3722 report("Refreshing is not yet supported for the %s view", view->name);
3723 break;
3725 case REQ_MAXIMIZE:
3726 if (displayed_views() == 2)
3727 maximize_view(view);
3728 break;
3730 case REQ_OPTIONS:
3731 open_option_menu();
3732 break;
3734 case REQ_TOGGLE_LINENO:
3735 toggle_view_option(&opt_line_number, "line numbers");
3736 break;
3738 case REQ_TOGGLE_DATE:
3739 toggle_date();
3740 break;
3742 case REQ_TOGGLE_AUTHOR:
3743 toggle_author();
3744 break;
3746 case REQ_TOGGLE_REV_GRAPH:
3747 toggle_view_option(&opt_rev_graph, "revision graph display");
3748 break;
3750 case REQ_TOGGLE_REFS:
3751 toggle_view_option(&opt_show_refs, "reference display");
3752 break;
3754 case REQ_TOGGLE_SORT_FIELD:
3755 case REQ_TOGGLE_SORT_ORDER:
3756 report("Sorting is not yet supported for the %s view", view->name);
3757 break;
3759 case REQ_SEARCH:
3760 case REQ_SEARCH_BACK:
3761 search_view(view, request);
3762 break;
3764 case REQ_FIND_NEXT:
3765 case REQ_FIND_PREV:
3766 find_next(view, request);
3767 break;
3769 case REQ_STOP_LOADING:
3770 foreach_view(view, i) {
3771 if (view->pipe)
3772 report("Stopped loading the %s view", view->name),
3773 end_update(view, TRUE);
3774 }
3775 break;
3777 case REQ_SHOW_VERSION:
3778 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3779 return TRUE;
3781 case REQ_SCREEN_REDRAW:
3782 redraw_display(TRUE);
3783 break;
3785 case REQ_EDIT:
3786 report("Nothing to edit");
3787 break;
3789 case REQ_ENTER:
3790 report("Nothing to enter");
3791 break;
3793 case REQ_VIEW_CLOSE:
3794 /* XXX: Mark closed views by letting view->prev point to the
3795 * view itself. Parents to closed view should never be
3796 * followed. */
3797 if (view->prev && view->prev != view) {
3798 maximize_view(view->prev);
3799 view->prev = view;
3800 break;
3801 }
3802 /* Fall-through */
3803 case REQ_QUIT:
3804 return FALSE;
3806 default:
3807 report("Unknown key, press %s for help",
3808 get_key(view->keymap, REQ_VIEW_HELP));
3809 return TRUE;
3810 }
3812 return TRUE;
3813 }
3816 /*
3817 * View backend utilities
3818 */
3820 enum sort_field {
3821 ORDERBY_NAME,
3822 ORDERBY_DATE,
3823 ORDERBY_AUTHOR,
3824 };
3826 struct sort_state {
3827 const enum sort_field *fields;
3828 size_t size, current;
3829 bool reverse;
3830 };
3832 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3833 #define get_sort_field(state) ((state).fields[(state).current])
3834 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3836 static void
3837 sort_view(struct view *view, enum request request, struct sort_state *state,
3838 int (*compare)(const void *, const void *))
3839 {
3840 switch (request) {
3841 case REQ_TOGGLE_SORT_FIELD:
3842 state->current = (state->current + 1) % state->size;
3843 break;
3845 case REQ_TOGGLE_SORT_ORDER:
3846 state->reverse = !state->reverse;
3847 break;
3848 default:
3849 die("Not a sort request");
3850 }
3852 qsort(view->line, view->lines, sizeof(*view->line), compare);
3853 redraw_view(view);
3854 }
3856 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3858 /* Small author cache to reduce memory consumption. It uses binary
3859 * search to lookup or find place to position new entries. No entries
3860 * are ever freed. */
3861 static const char *
3862 get_author(const char *name)
3863 {
3864 static const char **authors;
3865 static size_t authors_size;
3866 int from = 0, to = authors_size - 1;
3868 while (from <= to) {
3869 size_t pos = (to + from) / 2;
3870 int cmp = strcmp(name, authors[pos]);
3872 if (!cmp)
3873 return authors[pos];
3875 if (cmp < 0)
3876 to = pos - 1;
3877 else
3878 from = pos + 1;
3879 }
3881 if (!realloc_authors(&authors, authors_size, 1))
3882 return NULL;
3883 name = strdup(name);
3884 if (!name)
3885 return NULL;
3887 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3888 authors[from] = name;
3889 authors_size++;
3891 return name;
3892 }
3894 static void
3895 parse_timesec(struct time *time, const char *sec)
3896 {
3897 time->sec = (time_t) atol(sec);
3898 }
3900 static void
3901 parse_timezone(struct time *time, const char *zone)
3902 {
3903 long tz;
3905 tz = ('0' - zone[1]) * 60 * 60 * 10;
3906 tz += ('0' - zone[2]) * 60 * 60;
3907 tz += ('0' - zone[3]) * 60 * 10;
3908 tz += ('0' - zone[4]) * 60;
3910 if (zone[0] == '-')
3911 tz = -tz;
3913 time->tz = tz;
3914 time->sec -= tz;
3915 }
3917 /* Parse author lines where the name may be empty:
3918 * author <email@address.tld> 1138474660 +0100
3919 */
3920 static void
3921 parse_author_line(char *ident, const char **author, struct time *time)
3922 {
3923 char *nameend = strchr(ident, '<');
3924 char *emailend = strchr(ident, '>');
3926 if (nameend && emailend)
3927 *nameend = *emailend = 0;
3928 ident = chomp_string(ident);
3929 if (!*ident) {
3930 if (nameend)
3931 ident = chomp_string(nameend + 1);
3932 if (!*ident)
3933 ident = "Unknown";
3934 }
3936 *author = get_author(ident);
3938 /* Parse epoch and timezone */
3939 if (emailend && emailend[1] == ' ') {
3940 char *secs = emailend + 2;
3941 char *zone = strchr(secs, ' ');
3943 parse_timesec(time, secs);
3945 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3946 parse_timezone(time, zone + 1);
3947 }
3948 }
3950 static bool
3951 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3952 {
3953 char rev[SIZEOF_REV];
3954 const char *revlist_argv[] = {
3955 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3956 };
3957 struct menu_item *items;
3958 char text[SIZEOF_STR];
3959 bool ok = TRUE;
3960 int i;
3962 items = calloc(*parents + 1, sizeof(*items));
3963 if (!items)
3964 return FALSE;
3966 for (i = 0; i < *parents; i++) {
3967 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3968 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3969 !(items[i].text = strdup(text))) {
3970 ok = FALSE;
3971 break;
3972 }
3973 }
3975 if (ok) {
3976 *parents = 0;
3977 ok = prompt_menu("Select parent", items, parents);
3978 }
3979 for (i = 0; items[i].text; i++)
3980 free((char *) items[i].text);
3981 free(items);
3982 return ok;
3983 }
3985 static bool
3986 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3987 {
3988 char buf[SIZEOF_STR * 4];
3989 const char *revlist_argv[] = {
3990 "git", "log", "--no-color", "-1",
3991 "--pretty=format:%P", id, "--", path, NULL
3992 };
3993 int parents;
3995 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3996 (parents = strlen(buf) / 40) < 0) {
3997 report("Failed to get parent information");
3998 return FALSE;
4000 } else if (parents == 0) {
4001 if (path)
4002 report("Path '%s' does not exist in the parent", path);
4003 else
4004 report("The selected commit has no parents");
4005 return FALSE;
4006 }
4008 if (parents == 1)
4009 parents = 0;
4010 else if (!open_commit_parent_menu(buf, &parents))
4011 return FALSE;
4013 string_copy_rev(rev, &buf[41 * parents]);
4014 return TRUE;
4015 }
4017 /*
4018 * Pager backend
4019 */
4021 static bool
4022 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4023 {
4024 char text[SIZEOF_STR];
4026 if (opt_line_number && draw_lineno(view, lineno))
4027 return TRUE;
4029 string_expand(text, sizeof(text), line->data, opt_tab_size);
4030 draw_text(view, line->type, text, TRUE);
4031 return TRUE;
4032 }
4034 static bool
4035 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4036 {
4037 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4038 char ref[SIZEOF_STR];
4040 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4041 return TRUE;
4043 /* This is the only fatal call, since it can "corrupt" the buffer. */
4044 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4045 return FALSE;
4047 return TRUE;
4048 }
4050 static void
4051 add_pager_refs(struct view *view, struct line *line)
4052 {
4053 char buf[SIZEOF_STR];
4054 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4055 struct ref_list *list;
4056 size_t bufpos = 0, i;
4057 const char *sep = "Refs: ";
4058 bool is_tag = FALSE;
4060 assert(line->type == LINE_COMMIT);
4062 list = get_ref_list(commit_id);
4063 if (!list) {
4064 if (view->type == VIEW_DIFF)
4065 goto try_add_describe_ref;
4066 return;
4067 }
4069 for (i = 0; i < list->size; i++) {
4070 struct ref *ref = list->refs[i];
4071 const char *fmt = ref->tag ? "%s[%s]" :
4072 ref->remote ? "%s<%s>" : "%s%s";
4074 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4075 return;
4076 sep = ", ";
4077 if (ref->tag)
4078 is_tag = TRUE;
4079 }
4081 if (!is_tag && view->type == VIEW_DIFF) {
4082 try_add_describe_ref:
4083 /* Add <tag>-g<commit_id> "fake" reference. */
4084 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4085 return;
4086 }
4088 if (bufpos == 0)
4089 return;
4091 add_line_text(view, buf, LINE_PP_REFS);
4092 }
4094 static bool
4095 pager_read(struct view *view, char *data)
4096 {
4097 struct line *line;
4099 if (!data)
4100 return TRUE;
4102 line = add_line_text(view, data, get_line_type(data));
4103 if (!line)
4104 return FALSE;
4106 if (line->type == LINE_COMMIT &&
4107 (view->type == VIEW_DIFF ||
4108 view->type == VIEW_LOG))
4109 add_pager_refs(view, line);
4111 return TRUE;
4112 }
4114 static enum request
4115 pager_request(struct view *view, enum request request, struct line *line)
4116 {
4117 int split = 0;
4119 if (request != REQ_ENTER)
4120 return request;
4122 if (line->type == LINE_COMMIT &&
4123 (view->type == VIEW_LOG ||
4124 view->type == VIEW_PAGER)) {
4125 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4126 split = 1;
4127 }
4129 /* Always scroll the view even if it was split. That way
4130 * you can use Enter to scroll through the log view and
4131 * split open each commit diff. */
4132 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4134 /* FIXME: A minor workaround. Scrolling the view will call report("")
4135 * but if we are scrolling a non-current view this won't properly
4136 * update the view title. */
4137 if (split)
4138 update_view_title(view);
4140 return REQ_NONE;
4141 }
4143 static bool
4144 pager_grep(struct view *view, struct line *line)
4145 {
4146 const char *text[] = { line->data, NULL };
4148 return grep_text(view, text);
4149 }
4151 static void
4152 pager_select(struct view *view, struct line *line)
4153 {
4154 if (line->type == LINE_COMMIT) {
4155 char *text = (char *)line->data + STRING_SIZE("commit ");
4157 if (view->type != VIEW_PAGER)
4158 string_copy_rev(view->ref, text);
4159 string_copy_rev(ref_commit, text);
4160 }
4161 }
4163 static struct view_ops pager_ops = {
4164 "line",
4165 NULL,
4166 NULL,
4167 pager_read,
4168 pager_draw,
4169 pager_request,
4170 pager_grep,
4171 pager_select,
4172 };
4174 static const char *log_argv[SIZEOF_ARG] = {
4175 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4176 };
4178 static enum request
4179 log_request(struct view *view, enum request request, struct line *line)
4180 {
4181 switch (request) {
4182 case REQ_REFRESH:
4183 load_refs();
4184 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4185 return REQ_NONE;
4186 default:
4187 return pager_request(view, request, line);
4188 }
4189 }
4191 static struct view_ops log_ops = {
4192 "line",
4193 log_argv,
4194 NULL,
4195 pager_read,
4196 pager_draw,
4197 log_request,
4198 pager_grep,
4199 pager_select,
4200 };
4202 static const char *diff_argv[SIZEOF_ARG] = {
4203 "git", "show", "--pretty=fuller", "--no-color", "--root",
4204 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4205 };
4207 static struct view_ops diff_ops = {
4208 "line",
4209 diff_argv,
4210 NULL,
4211 pager_read,
4212 pager_draw,
4213 pager_request,
4214 pager_grep,
4215 pager_select,
4216 };
4218 /*
4219 * Help backend
4220 */
4222 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4224 static bool
4225 help_open_keymap_title(struct view *view, enum keymap keymap)
4226 {
4227 struct line *line;
4229 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4230 help_keymap_hidden[keymap] ? '+' : '-',
4231 enum_name(keymap_table[keymap]));
4232 if (line)
4233 line->other = keymap;
4235 return help_keymap_hidden[keymap];
4236 }
4238 static void
4239 help_open_keymap(struct view *view, enum keymap keymap)
4240 {
4241 const char *group = NULL;
4242 char buf[SIZEOF_STR];
4243 size_t bufpos;
4244 bool add_title = TRUE;
4245 int i;
4247 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4248 const char *key = NULL;
4250 if (req_info[i].request == REQ_NONE)
4251 continue;
4253 if (!req_info[i].request) {
4254 group = req_info[i].help;
4255 continue;
4256 }
4258 key = get_keys(keymap, req_info[i].request, TRUE);
4259 if (!key || !*key)
4260 continue;
4262 if (add_title && help_open_keymap_title(view, keymap))
4263 return;
4264 add_title = FALSE;
4266 if (group) {
4267 add_line_text(view, group, LINE_HELP_GROUP);
4268 group = NULL;
4269 }
4271 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4272 enum_name(req_info[i]), req_info[i].help);
4273 }
4275 group = "External commands:";
4277 for (i = 0; i < run_requests; i++) {
4278 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4279 const char *key;
4280 int argc;
4282 if (!req || req->keymap != keymap)
4283 continue;
4285 key = get_key_name(req->key);
4286 if (!*key)
4287 key = "(no key defined)";
4289 if (add_title && help_open_keymap_title(view, keymap))
4290 return;
4291 if (group) {
4292 add_line_text(view, group, LINE_HELP_GROUP);
4293 group = NULL;
4294 }
4296 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4297 if (!string_format_from(buf, &bufpos, "%s%s",
4298 argc ? " " : "", req->argv[argc]))
4299 return;
4301 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4302 }
4303 }
4305 static bool
4306 help_open(struct view *view)
4307 {
4308 enum keymap keymap;
4310 reset_view(view);
4311 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4312 add_line_text(view, "", LINE_DEFAULT);
4314 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4315 help_open_keymap(view, keymap);
4317 return TRUE;
4318 }
4320 static enum request
4321 help_request(struct view *view, enum request request, struct line *line)
4322 {
4323 switch (request) {
4324 case REQ_ENTER:
4325 if (line->type == LINE_HELP_KEYMAP) {
4326 help_keymap_hidden[line->other] =
4327 !help_keymap_hidden[line->other];
4328 view->p_restore = TRUE;
4329 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4330 }
4332 return REQ_NONE;
4333 default:
4334 return pager_request(view, request, line);
4335 }
4336 }
4338 static struct view_ops help_ops = {
4339 "line",
4340 NULL,
4341 help_open,
4342 NULL,
4343 pager_draw,
4344 help_request,
4345 pager_grep,
4346 pager_select,
4347 };
4350 /*
4351 * Tree backend
4352 */
4354 struct tree_stack_entry {
4355 struct tree_stack_entry *prev; /* Entry below this in the stack */
4356 unsigned long lineno; /* Line number to restore */
4357 char *name; /* Position of name in opt_path */
4358 };
4360 /* The top of the path stack. */
4361 static struct tree_stack_entry *tree_stack = NULL;
4362 unsigned long tree_lineno = 0;
4364 static void
4365 pop_tree_stack_entry(void)
4366 {
4367 struct tree_stack_entry *entry = tree_stack;
4369 tree_lineno = entry->lineno;
4370 entry->name[0] = 0;
4371 tree_stack = entry->prev;
4372 free(entry);
4373 }
4375 static void
4376 push_tree_stack_entry(const char *name, unsigned long lineno)
4377 {
4378 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4379 size_t pathlen = strlen(opt_path);
4381 if (!entry)
4382 return;
4384 entry->prev = tree_stack;
4385 entry->name = opt_path + pathlen;
4386 tree_stack = entry;
4388 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4389 pop_tree_stack_entry();
4390 return;
4391 }
4393 /* Move the current line to the first tree entry. */
4394 tree_lineno = 1;
4395 entry->lineno = lineno;
4396 }
4398 /* Parse output from git-ls-tree(1):
4399 *
4400 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4401 */
4403 #define SIZEOF_TREE_ATTR \
4404 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4406 #define SIZEOF_TREE_MODE \
4407 STRING_SIZE("100644 ")
4409 #define TREE_ID_OFFSET \
4410 STRING_SIZE("100644 blob ")
4412 struct tree_entry {
4413 char id[SIZEOF_REV];
4414 mode_t mode;
4415 struct time time; /* Date from the author ident. */
4416 const char *author; /* Author of the commit. */
4417 char name[1];
4418 };
4420 static const char *
4421 tree_path(const struct line *line)
4422 {
4423 return ((struct tree_entry *) line->data)->name;
4424 }
4426 static int
4427 tree_compare_entry(const struct line *line1, const struct line *line2)
4428 {
4429 if (line1->type != line2->type)
4430 return line1->type == LINE_TREE_DIR ? -1 : 1;
4431 return strcmp(tree_path(line1), tree_path(line2));
4432 }
4434 static const enum sort_field tree_sort_fields[] = {
4435 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4436 };
4437 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4439 static int
4440 tree_compare(const void *l1, const void *l2)
4441 {
4442 const struct line *line1 = (const struct line *) l1;
4443 const struct line *line2 = (const struct line *) l2;
4444 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4445 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4447 if (line1->type == LINE_TREE_HEAD)
4448 return -1;
4449 if (line2->type == LINE_TREE_HEAD)
4450 return 1;
4452 switch (get_sort_field(tree_sort_state)) {
4453 case ORDERBY_DATE:
4454 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4456 case ORDERBY_AUTHOR:
4457 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4459 case ORDERBY_NAME:
4460 default:
4461 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4462 }
4463 }
4466 static struct line *
4467 tree_entry(struct view *view, enum line_type type, const char *path,
4468 const char *mode, const char *id)
4469 {
4470 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4471 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4473 if (!entry || !line) {
4474 free(entry);
4475 return NULL;
4476 }
4478 strncpy(entry->name, path, strlen(path));
4479 if (mode)
4480 entry->mode = strtoul(mode, NULL, 8);
4481 if (id)
4482 string_copy_rev(entry->id, id);
4484 return line;
4485 }
4487 static bool
4488 tree_read_date(struct view *view, char *text, bool *read_date)
4489 {
4490 static const char *author_name;
4491 static struct time author_time;
4493 if (!text && *read_date) {
4494 *read_date = FALSE;
4495 return TRUE;
4497 } else if (!text) {
4498 char *path = *opt_path ? opt_path : ".";
4499 /* Find next entry to process */
4500 const char *log_file[] = {
4501 "git", "log", "--no-color", "--pretty=raw",
4502 "--cc", "--raw", view->id, "--", path, NULL
4503 };
4505 if (!view->lines) {
4506 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4507 report("Tree is empty");
4508 return TRUE;
4509 }
4511 if (!start_update(view, log_file, opt_cdup)) {
4512 report("Failed to load tree data");
4513 return TRUE;
4514 }
4516 *read_date = TRUE;
4517 return FALSE;
4519 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4520 parse_author_line(text + STRING_SIZE("author "),
4521 &author_name, &author_time);
4523 } else if (*text == ':') {
4524 char *pos;
4525 size_t annotated = 1;
4526 size_t i;
4528 pos = strchr(text, '\t');
4529 if (!pos)
4530 return TRUE;
4531 text = pos + 1;
4532 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4533 text += strlen(opt_path);
4534 pos = strchr(text, '/');
4535 if (pos)
4536 *pos = 0;
4538 for (i = 1; i < view->lines; i++) {
4539 struct line *line = &view->line[i];
4540 struct tree_entry *entry = line->data;
4542 annotated += !!entry->author;
4543 if (entry->author || strcmp(entry->name, text))
4544 continue;
4546 entry->author = author_name;
4547 entry->time = author_time;
4548 line->dirty = 1;
4549 break;
4550 }
4552 if (annotated == view->lines)
4553 io_kill(view->pipe);
4554 }
4555 return TRUE;
4556 }
4558 static bool
4559 tree_read(struct view *view, char *text)
4560 {
4561 static bool read_date = FALSE;
4562 struct tree_entry *data;
4563 struct line *entry, *line;
4564 enum line_type type;
4565 size_t textlen = text ? strlen(text) : 0;
4566 char *path = text + SIZEOF_TREE_ATTR;
4568 if (read_date || !text)
4569 return tree_read_date(view, text, &read_date);
4571 if (textlen <= SIZEOF_TREE_ATTR)
4572 return FALSE;
4573 if (view->lines == 0 &&
4574 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4575 return FALSE;
4577 /* Strip the path part ... */
4578 if (*opt_path) {
4579 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4580 size_t striplen = strlen(opt_path);
4582 if (pathlen > striplen)
4583 memmove(path, path + striplen,
4584 pathlen - striplen + 1);
4586 /* Insert "link" to parent directory. */
4587 if (view->lines == 1 &&
4588 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4589 return FALSE;
4590 }
4592 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4593 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4594 if (!entry)
4595 return FALSE;
4596 data = entry->data;
4598 /* Skip "Directory ..." and ".." line. */
4599 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4600 if (tree_compare_entry(line, entry) <= 0)
4601 continue;
4603 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4605 line->data = data;
4606 line->type = type;
4607 for (; line <= entry; line++)
4608 line->dirty = line->cleareol = 1;
4609 return TRUE;
4610 }
4612 if (tree_lineno > view->lineno) {
4613 view->lineno = tree_lineno;
4614 tree_lineno = 0;
4615 }
4617 return TRUE;
4618 }
4620 static bool
4621 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4622 {
4623 struct tree_entry *entry = line->data;
4625 if (line->type == LINE_TREE_HEAD) {
4626 if (draw_text(view, line->type, "Directory path /", TRUE))
4627 return TRUE;
4628 } else {
4629 if (draw_mode(view, entry->mode))
4630 return TRUE;
4632 if (opt_author && draw_author(view, entry->author))
4633 return TRUE;
4635 if (opt_date && draw_date(view, &entry->time))
4636 return TRUE;
4637 }
4638 if (draw_text(view, line->type, entry->name, TRUE))
4639 return TRUE;
4640 return TRUE;
4641 }
4643 static void
4644 open_blob_editor(const char *id)
4645 {
4646 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4647 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4648 int fd = mkstemp(file);
4650 if (fd == -1)
4651 report("Failed to create temporary file");
4652 else if (!io_run_append(blob_argv, fd))
4653 report("Failed to save blob data to file");
4654 else
4655 open_editor(file);
4656 if (fd != -1)
4657 unlink(file);
4658 }
4660 static enum request
4661 tree_request(struct view *view, enum request request, struct line *line)
4662 {
4663 enum open_flags flags;
4664 struct tree_entry *entry = line->data;
4666 switch (request) {
4667 case REQ_VIEW_BLAME:
4668 if (line->type != LINE_TREE_FILE) {
4669 report("Blame only supported for files");
4670 return REQ_NONE;
4671 }
4673 string_copy(opt_ref, view->vid);
4674 return request;
4676 case REQ_EDIT:
4677 if (line->type != LINE_TREE_FILE) {
4678 report("Edit only supported for files");
4679 } else if (!is_head_commit(view->vid)) {
4680 open_blob_editor(entry->id);
4681 } else {
4682 open_editor(opt_file);
4683 }
4684 return REQ_NONE;
4686 case REQ_TOGGLE_SORT_FIELD:
4687 case REQ_TOGGLE_SORT_ORDER:
4688 sort_view(view, request, &tree_sort_state, tree_compare);
4689 return REQ_NONE;
4691 case REQ_PARENT:
4692 if (!*opt_path) {
4693 /* quit view if at top of tree */
4694 return REQ_VIEW_CLOSE;
4695 }
4696 /* fake 'cd ..' */
4697 line = &view->line[1];
4698 break;
4700 case REQ_ENTER:
4701 break;
4703 default:
4704 return request;
4705 }
4707 /* Cleanup the stack if the tree view is at a different tree. */
4708 while (!*opt_path && tree_stack)
4709 pop_tree_stack_entry();
4711 switch (line->type) {
4712 case LINE_TREE_DIR:
4713 /* Depending on whether it is a subdirectory or parent link
4714 * mangle the path buffer. */
4715 if (line == &view->line[1] && *opt_path) {
4716 pop_tree_stack_entry();
4718 } else {
4719 const char *basename = tree_path(line);
4721 push_tree_stack_entry(basename, view->lineno);
4722 }
4724 /* Trees and subtrees share the same ID, so they are not not
4725 * unique like blobs. */
4726 flags = OPEN_RELOAD;
4727 request = REQ_VIEW_TREE;
4728 break;
4730 case LINE_TREE_FILE:
4731 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4732 request = REQ_VIEW_BLOB;
4733 break;
4735 default:
4736 return REQ_NONE;
4737 }
4739 open_view(view, request, flags);
4740 if (request == REQ_VIEW_TREE)
4741 view->lineno = tree_lineno;
4743 return REQ_NONE;
4744 }
4746 static bool
4747 tree_grep(struct view *view, struct line *line)
4748 {
4749 struct tree_entry *entry = line->data;
4750 const char *text[] = {
4751 entry->name,
4752 opt_author ? entry->author : "",
4753 mkdate(&entry->time, opt_date),
4754 NULL
4755 };
4757 return grep_text(view, text);
4758 }
4760 static void
4761 tree_select(struct view *view, struct line *line)
4762 {
4763 struct tree_entry *entry = line->data;
4765 if (line->type == LINE_TREE_FILE) {
4766 string_copy_rev(ref_blob, entry->id);
4767 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4769 } else if (line->type != LINE_TREE_DIR) {
4770 return;
4771 }
4773 string_copy_rev(view->ref, entry->id);
4774 }
4776 static bool
4777 tree_prepare(struct view *view)
4778 {
4779 if (view->lines == 0 && opt_prefix[0]) {
4780 char *pos = opt_prefix;
4782 while (pos && *pos) {
4783 char *end = strchr(pos, '/');
4785 if (end)
4786 *end = 0;
4787 push_tree_stack_entry(pos, 0);
4788 pos = end;
4789 if (end) {
4790 *end = '/';
4791 pos++;
4792 }
4793 }
4795 } else if (strcmp(view->vid, view->id)) {
4796 opt_path[0] = 0;
4797 }
4799 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4800 }
4802 static const char *tree_argv[SIZEOF_ARG] = {
4803 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4804 };
4806 static struct view_ops tree_ops = {
4807 "file",
4808 tree_argv,
4809 NULL,
4810 tree_read,
4811 tree_draw,
4812 tree_request,
4813 tree_grep,
4814 tree_select,
4815 tree_prepare,
4816 };
4818 static bool
4819 blob_read(struct view *view, char *line)
4820 {
4821 if (!line)
4822 return TRUE;
4823 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4824 }
4826 static enum request
4827 blob_request(struct view *view, enum request request, struct line *line)
4828 {
4829 switch (request) {
4830 case REQ_EDIT:
4831 open_blob_editor(view->vid);
4832 return REQ_NONE;
4833 default:
4834 return pager_request(view, request, line);
4835 }
4836 }
4838 static const char *blob_argv[SIZEOF_ARG] = {
4839 "git", "cat-file", "blob", "%(blob)", NULL
4840 };
4842 static struct view_ops blob_ops = {
4843 "line",
4844 blob_argv,
4845 NULL,
4846 blob_read,
4847 pager_draw,
4848 blob_request,
4849 pager_grep,
4850 pager_select,
4851 };
4853 /*
4854 * Blame backend
4855 *
4856 * Loading the blame view is a two phase job:
4857 *
4858 * 1. File content is read either using opt_file from the
4859 * filesystem or using git-cat-file.
4860 * 2. Then blame information is incrementally added by
4861 * reading output from git-blame.
4862 */
4864 struct blame_commit {
4865 char id[SIZEOF_REV]; /* SHA1 ID. */
4866 char title[128]; /* First line of the commit message. */
4867 const char *author; /* Author of the commit. */
4868 struct time time; /* Date from the author ident. */
4869 char filename[128]; /* Name of file. */
4870 bool has_previous; /* Was a "previous" line detected. */
4871 };
4873 struct blame {
4874 struct blame_commit *commit;
4875 unsigned long lineno;
4876 char text[1];
4877 };
4879 static bool
4880 blame_open(struct view *view)
4881 {
4882 char path[SIZEOF_STR];
4884 if (!view->prev && *opt_prefix) {
4885 string_copy(path, opt_file);
4886 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4887 return FALSE;
4888 }
4890 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4891 const char *blame_cat_file_argv[] = {
4892 "git", "cat-file", "blob", path, NULL
4893 };
4895 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4896 !start_update(view, blame_cat_file_argv, opt_cdup))
4897 return FALSE;
4898 }
4900 setup_update(view, opt_file);
4901 string_format(view->ref, "%s ...", opt_file);
4903 return TRUE;
4904 }
4906 static struct blame_commit *
4907 get_blame_commit(struct view *view, const char *id)
4908 {
4909 size_t i;
4911 for (i = 0; i < view->lines; i++) {
4912 struct blame *blame = view->line[i].data;
4914 if (!blame->commit)
4915 continue;
4917 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4918 return blame->commit;
4919 }
4921 {
4922 struct blame_commit *commit = calloc(1, sizeof(*commit));
4924 if (commit)
4925 string_ncopy(commit->id, id, SIZEOF_REV);
4926 return commit;
4927 }
4928 }
4930 static bool
4931 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4932 {
4933 const char *pos = *posref;
4935 *posref = NULL;
4936 pos = strchr(pos + 1, ' ');
4937 if (!pos || !isdigit(pos[1]))
4938 return FALSE;
4939 *number = atoi(pos + 1);
4940 if (*number < min || *number > max)
4941 return FALSE;
4943 *posref = pos;
4944 return TRUE;
4945 }
4947 static struct blame_commit *
4948 parse_blame_commit(struct view *view, const char *text, int *blamed)
4949 {
4950 struct blame_commit *commit;
4951 struct blame *blame;
4952 const char *pos = text + SIZEOF_REV - 2;
4953 size_t orig_lineno = 0;
4954 size_t lineno;
4955 size_t group;
4957 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4958 return NULL;
4960 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4961 !parse_number(&pos, &lineno, 1, view->lines) ||
4962 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4963 return NULL;
4965 commit = get_blame_commit(view, text);
4966 if (!commit)
4967 return NULL;
4969 *blamed += group;
4970 while (group--) {
4971 struct line *line = &view->line[lineno + group - 1];
4973 blame = line->data;
4974 blame->commit = commit;
4975 blame->lineno = orig_lineno + group - 1;
4976 line->dirty = 1;
4977 }
4979 return commit;
4980 }
4982 static bool
4983 blame_read_file(struct view *view, const char *line, bool *read_file)
4984 {
4985 if (!line) {
4986 const char *blame_argv[] = {
4987 "git", "blame", "--incremental",
4988 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4989 };
4991 if (view->lines == 0 && !view->prev)
4992 die("No blame exist for %s", view->vid);
4994 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4995 report("Failed to load blame data");
4996 return TRUE;
4997 }
4999 *read_file = FALSE;
5000 return FALSE;
5002 } else {
5003 size_t linelen = strlen(line);
5004 struct blame *blame = malloc(sizeof(*blame) + linelen);
5006 if (!blame)
5007 return FALSE;
5009 blame->commit = NULL;
5010 strncpy(blame->text, line, linelen);
5011 blame->text[linelen] = 0;
5012 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5013 }
5014 }
5016 static bool
5017 match_blame_header(const char *name, char **line)
5018 {
5019 size_t namelen = strlen(name);
5020 bool matched = !strncmp(name, *line, namelen);
5022 if (matched)
5023 *line += namelen;
5025 return matched;
5026 }
5028 static bool
5029 blame_read(struct view *view, char *line)
5030 {
5031 static struct blame_commit *commit = NULL;
5032 static int blamed = 0;
5033 static bool read_file = TRUE;
5035 if (read_file)
5036 return blame_read_file(view, line, &read_file);
5038 if (!line) {
5039 /* Reset all! */
5040 commit = NULL;
5041 blamed = 0;
5042 read_file = TRUE;
5043 string_format(view->ref, "%s", view->vid);
5044 if (view_is_displayed(view)) {
5045 update_view_title(view);
5046 redraw_view_from(view, 0);
5047 }
5048 return TRUE;
5049 }
5051 if (!commit) {
5052 commit = parse_blame_commit(view, line, &blamed);
5053 string_format(view->ref, "%s %2d%%", view->vid,
5054 view->lines ? blamed * 100 / view->lines : 0);
5056 } else if (match_blame_header("author ", &line)) {
5057 commit->author = get_author(line);
5059 } else if (match_blame_header("author-time ", &line)) {
5060 parse_timesec(&commit->time, line);
5062 } else if (match_blame_header("author-tz ", &line)) {
5063 parse_timezone(&commit->time, line);
5065 } else if (match_blame_header("summary ", &line)) {
5066 string_ncopy(commit->title, line, strlen(line));
5068 } else if (match_blame_header("previous ", &line)) {
5069 commit->has_previous = TRUE;
5071 } else if (match_blame_header("filename ", &line)) {
5072 string_ncopy(commit->filename, line, strlen(line));
5073 commit = NULL;
5074 }
5076 return TRUE;
5077 }
5079 static bool
5080 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5081 {
5082 struct blame *blame = line->data;
5083 struct time *time = NULL;
5084 const char *id = NULL, *author = NULL;
5085 char text[SIZEOF_STR];
5087 if (blame->commit && *blame->commit->filename) {
5088 id = blame->commit->id;
5089 author = blame->commit->author;
5090 time = &blame->commit->time;
5091 }
5093 if (opt_date && draw_date(view, time))
5094 return TRUE;
5096 if (opt_author && draw_author(view, author))
5097 return TRUE;
5099 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5100 return TRUE;
5102 if (draw_lineno(view, lineno))
5103 return TRUE;
5105 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5106 draw_text(view, LINE_DEFAULT, text, TRUE);
5107 return TRUE;
5108 }
5110 static bool
5111 check_blame_commit(struct blame *blame, bool check_null_id)
5112 {
5113 if (!blame->commit)
5114 report("Commit data not loaded yet");
5115 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5116 report("No commit exist for the selected line");
5117 else
5118 return TRUE;
5119 return FALSE;
5120 }
5122 static void
5123 setup_blame_parent_line(struct view *view, struct blame *blame)
5124 {
5125 const char *diff_tree_argv[] = {
5126 "git", "diff-tree", "-U0", blame->commit->id,
5127 "--", blame->commit->filename, NULL
5128 };
5129 struct io io;
5130 int parent_lineno = -1;
5131 int blamed_lineno = -1;
5132 char *line;
5134 if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5135 return;
5137 while ((line = io_get(&io, '\n', TRUE))) {
5138 if (*line == '@') {
5139 char *pos = strchr(line, '+');
5141 parent_lineno = atoi(line + 4);
5142 if (pos)
5143 blamed_lineno = atoi(pos + 1);
5145 } else if (*line == '+' && parent_lineno != -1) {
5146 if (blame->lineno == blamed_lineno - 1 &&
5147 !strcmp(blame->text, line + 1)) {
5148 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5149 break;
5150 }
5151 blamed_lineno++;
5152 }
5153 }
5155 io_done(&io);
5156 }
5158 static enum request
5159 blame_request(struct view *view, enum request request, struct line *line)
5160 {
5161 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5162 struct blame *blame = line->data;
5164 switch (request) {
5165 case REQ_VIEW_BLAME:
5166 if (check_blame_commit(blame, TRUE)) {
5167 string_copy(opt_ref, blame->commit->id);
5168 string_copy(opt_file, blame->commit->filename);
5169 if (blame->lineno)
5170 view->lineno = blame->lineno;
5171 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5172 }
5173 break;
5175 case REQ_PARENT:
5176 if (check_blame_commit(blame, TRUE) &&
5177 select_commit_parent(blame->commit->id, opt_ref,
5178 blame->commit->filename)) {
5179 string_copy(opt_file, blame->commit->filename);
5180 setup_blame_parent_line(view, blame);
5181 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5182 }
5183 break;
5185 case REQ_ENTER:
5186 if (!check_blame_commit(blame, FALSE))
5187 break;
5189 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5190 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5191 break;
5193 if (!strcmp(blame->commit->id, NULL_ID)) {
5194 struct view *diff = VIEW(REQ_VIEW_DIFF);
5195 const char *diff_index_argv[] = {
5196 "git", "diff-index", "--root", "--patch-with-stat",
5197 "-C", "-M", "HEAD", "--", view->vid, NULL
5198 };
5200 if (!blame->commit->has_previous) {
5201 diff_index_argv[1] = "diff";
5202 diff_index_argv[2] = "--no-color";
5203 diff_index_argv[6] = "--";
5204 diff_index_argv[7] = "/dev/null";
5205 }
5207 if (!prepare_update(diff, diff_index_argv, NULL)) {
5208 report("Failed to allocate diff command");
5209 break;
5210 }
5211 flags |= OPEN_PREPARED;
5212 }
5214 open_view(view, REQ_VIEW_DIFF, flags);
5215 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5216 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5217 break;
5219 default:
5220 return request;
5221 }
5223 return REQ_NONE;
5224 }
5226 static bool
5227 blame_grep(struct view *view, struct line *line)
5228 {
5229 struct blame *blame = line->data;
5230 struct blame_commit *commit = blame->commit;
5231 const char *text[] = {
5232 blame->text,
5233 commit ? commit->title : "",
5234 commit ? commit->id : "",
5235 commit && opt_author ? commit->author : "",
5236 commit ? mkdate(&commit->time, opt_date) : "",
5237 NULL
5238 };
5240 return grep_text(view, text);
5241 }
5243 static void
5244 blame_select(struct view *view, struct line *line)
5245 {
5246 struct blame *blame = line->data;
5247 struct blame_commit *commit = blame->commit;
5249 if (!commit)
5250 return;
5252 if (!strcmp(commit->id, NULL_ID))
5253 string_ncopy(ref_commit, "HEAD", 4);
5254 else
5255 string_copy_rev(ref_commit, commit->id);
5256 }
5258 static struct view_ops blame_ops = {
5259 "line",
5260 NULL,
5261 blame_open,
5262 blame_read,
5263 blame_draw,
5264 blame_request,
5265 blame_grep,
5266 blame_select,
5267 };
5269 /*
5270 * Branch backend
5271 */
5273 struct branch {
5274 const char *author; /* Author of the last commit. */
5275 struct time time; /* Date of the last activity. */
5276 const struct ref *ref; /* Name and commit ID information. */
5277 };
5279 static const struct ref branch_all;
5281 static const enum sort_field branch_sort_fields[] = {
5282 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5283 };
5284 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5286 static int
5287 branch_compare(const void *l1, const void *l2)
5288 {
5289 const struct branch *branch1 = ((const struct line *) l1)->data;
5290 const struct branch *branch2 = ((const struct line *) l2)->data;
5292 switch (get_sort_field(branch_sort_state)) {
5293 case ORDERBY_DATE:
5294 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5296 case ORDERBY_AUTHOR:
5297 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5299 case ORDERBY_NAME:
5300 default:
5301 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5302 }
5303 }
5305 static bool
5306 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5307 {
5308 struct branch *branch = line->data;
5309 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5311 if (opt_date && draw_date(view, &branch->time))
5312 return TRUE;
5314 if (opt_author && draw_author(view, branch->author))
5315 return TRUE;
5317 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5318 return TRUE;
5319 }
5321 static enum request
5322 branch_request(struct view *view, enum request request, struct line *line)
5323 {
5324 struct branch *branch = line->data;
5326 switch (request) {
5327 case REQ_REFRESH:
5328 load_refs();
5329 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5330 return REQ_NONE;
5332 case REQ_TOGGLE_SORT_FIELD:
5333 case REQ_TOGGLE_SORT_ORDER:
5334 sort_view(view, request, &branch_sort_state, branch_compare);
5335 return REQ_NONE;
5337 case REQ_ENTER:
5338 {
5339 const struct ref *ref = branch->ref;
5340 const char *all_branches_argv[] = {
5341 "git", "log", "--no-color", "--pretty=raw", "--parents",
5342 "--topo-order",
5343 ref == &branch_all ? "--all" : ref->name, NULL
5344 };
5345 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5347 if (!prepare_update(main_view, all_branches_argv, NULL))
5348 report("Failed to load view of all branches");
5349 else
5350 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5351 return REQ_NONE;
5352 }
5353 default:
5354 return request;
5355 }
5356 }
5358 static bool
5359 branch_read(struct view *view, char *line)
5360 {
5361 static char id[SIZEOF_REV];
5362 struct branch *reference;
5363 size_t i;
5365 if (!line)
5366 return TRUE;
5368 switch (get_line_type(line)) {
5369 case LINE_COMMIT:
5370 string_copy_rev(id, line + STRING_SIZE("commit "));
5371 return TRUE;
5373 case LINE_AUTHOR:
5374 for (i = 0, reference = NULL; i < view->lines; i++) {
5375 struct branch *branch = view->line[i].data;
5377 if (strcmp(branch->ref->id, id))
5378 continue;
5380 view->line[i].dirty = TRUE;
5381 if (reference) {
5382 branch->author = reference->author;
5383 branch->time = reference->time;
5384 continue;
5385 }
5387 parse_author_line(line + STRING_SIZE("author "),
5388 &branch->author, &branch->time);
5389 reference = branch;
5390 }
5391 return TRUE;
5393 default:
5394 return TRUE;
5395 }
5397 }
5399 static bool
5400 branch_open_visitor(void *data, const struct ref *ref)
5401 {
5402 struct view *view = data;
5403 struct branch *branch;
5405 if (ref->tag || ref->ltag || ref->remote)
5406 return TRUE;
5408 branch = calloc(1, sizeof(*branch));
5409 if (!branch)
5410 return FALSE;
5412 branch->ref = ref;
5413 return !!add_line_data(view, branch, LINE_DEFAULT);
5414 }
5416 static bool
5417 branch_open(struct view *view)
5418 {
5419 const char *branch_log[] = {
5420 "git", "log", "--no-color", "--pretty=raw",
5421 "--simplify-by-decoration", "--all", NULL
5422 };
5424 if (!start_update(view, branch_log, NULL)) {
5425 report("Failed to load branch data");
5426 return TRUE;
5427 }
5429 setup_update(view, view->id);
5430 branch_open_visitor(view, &branch_all);
5431 foreach_ref(branch_open_visitor, view);
5432 view->p_restore = TRUE;
5434 return TRUE;
5435 }
5437 static bool
5438 branch_grep(struct view *view, struct line *line)
5439 {
5440 struct branch *branch = line->data;
5441 const char *text[] = {
5442 branch->ref->name,
5443 branch->author,
5444 NULL
5445 };
5447 return grep_text(view, text);
5448 }
5450 static void
5451 branch_select(struct view *view, struct line *line)
5452 {
5453 struct branch *branch = line->data;
5455 string_copy_rev(view->ref, branch->ref->id);
5456 string_copy_rev(ref_commit, branch->ref->id);
5457 string_copy_rev(ref_head, branch->ref->id);
5458 string_copy_rev(ref_branch, branch->ref->name);
5459 }
5461 static struct view_ops branch_ops = {
5462 "branch",
5463 NULL,
5464 branch_open,
5465 branch_read,
5466 branch_draw,
5467 branch_request,
5468 branch_grep,
5469 branch_select,
5470 };
5472 /*
5473 * Status backend
5474 */
5476 struct status {
5477 char status;
5478 struct {
5479 mode_t mode;
5480 char rev[SIZEOF_REV];
5481 char name[SIZEOF_STR];
5482 } old;
5483 struct {
5484 mode_t mode;
5485 char rev[SIZEOF_REV];
5486 char name[SIZEOF_STR];
5487 } new;
5488 };
5490 static char status_onbranch[SIZEOF_STR];
5491 static struct status stage_status;
5492 static enum line_type stage_line_type;
5493 static size_t stage_chunks;
5494 static int *stage_chunk;
5496 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5498 /* This should work even for the "On branch" line. */
5499 static inline bool
5500 status_has_none(struct view *view, struct line *line)
5501 {
5502 return line < view->line + view->lines && !line[1].data;
5503 }
5505 /* Get fields from the diff line:
5506 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5507 */
5508 static inline bool
5509 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5510 {
5511 const char *old_mode = buf + 1;
5512 const char *new_mode = buf + 8;
5513 const char *old_rev = buf + 15;
5514 const char *new_rev = buf + 56;
5515 const char *status = buf + 97;
5517 if (bufsize < 98 ||
5518 old_mode[-1] != ':' ||
5519 new_mode[-1] != ' ' ||
5520 old_rev[-1] != ' ' ||
5521 new_rev[-1] != ' ' ||
5522 status[-1] != ' ')
5523 return FALSE;
5525 file->status = *status;
5527 string_copy_rev(file->old.rev, old_rev);
5528 string_copy_rev(file->new.rev, new_rev);
5530 file->old.mode = strtoul(old_mode, NULL, 8);
5531 file->new.mode = strtoul(new_mode, NULL, 8);
5533 file->old.name[0] = file->new.name[0] = 0;
5535 return TRUE;
5536 }
5538 static bool
5539 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5540 {
5541 struct status *unmerged = NULL;
5542 char *buf;
5543 struct io io;
5545 if (!io_run(&io, IO_RD, opt_cdup, argv))
5546 return FALSE;
5548 add_line_data(view, NULL, type);
5550 while ((buf = io_get(&io, 0, TRUE))) {
5551 struct status *file = unmerged;
5553 if (!file) {
5554 file = calloc(1, sizeof(*file));
5555 if (!file || !add_line_data(view, file, type))
5556 goto error_out;
5557 }
5559 /* Parse diff info part. */
5560 if (status) {
5561 file->status = status;
5562 if (status == 'A')
5563 string_copy(file->old.rev, NULL_ID);
5565 } else if (!file->status || file == unmerged) {
5566 if (!status_get_diff(file, buf, strlen(buf)))
5567 goto error_out;
5569 buf = io_get(&io, 0, TRUE);
5570 if (!buf)
5571 break;
5573 /* Collapse all modified entries that follow an
5574 * associated unmerged entry. */
5575 if (unmerged == file) {
5576 unmerged->status = 'U';
5577 unmerged = NULL;
5578 } else if (file->status == 'U') {
5579 unmerged = file;
5580 }
5581 }
5583 /* Grab the old name for rename/copy. */
5584 if (!*file->old.name &&
5585 (file->status == 'R' || file->status == 'C')) {
5586 string_ncopy(file->old.name, buf, strlen(buf));
5588 buf = io_get(&io, 0, TRUE);
5589 if (!buf)
5590 break;
5591 }
5593 /* git-ls-files just delivers a NUL separated list of
5594 * file names similar to the second half of the
5595 * git-diff-* output. */
5596 string_ncopy(file->new.name, buf, strlen(buf));
5597 if (!*file->old.name)
5598 string_copy(file->old.name, file->new.name);
5599 file = NULL;
5600 }
5602 if (io_error(&io)) {
5603 error_out:
5604 io_done(&io);
5605 return FALSE;
5606 }
5608 if (!view->line[view->lines - 1].data)
5609 add_line_data(view, NULL, LINE_STAT_NONE);
5611 io_done(&io);
5612 return TRUE;
5613 }
5615 /* Don't show unmerged entries in the staged section. */
5616 static const char *status_diff_index_argv[] = {
5617 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5618 "--cached", "-M", "HEAD", NULL
5619 };
5621 static const char *status_diff_files_argv[] = {
5622 "git", "diff-files", "-z", NULL
5623 };
5625 static const char *status_list_other_argv[] = {
5626 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5627 };
5629 static const char *status_list_no_head_argv[] = {
5630 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5631 };
5633 static const char *update_index_argv[] = {
5634 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5635 };
5637 /* Restore the previous line number to stay in the context or select a
5638 * line with something that can be updated. */
5639 static void
5640 status_restore(struct view *view)
5641 {
5642 if (view->p_lineno >= view->lines)
5643 view->p_lineno = view->lines - 1;
5644 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5645 view->p_lineno++;
5646 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5647 view->p_lineno--;
5649 /* If the above fails, always skip the "On branch" line. */
5650 if (view->p_lineno < view->lines)
5651 view->lineno = view->p_lineno;
5652 else
5653 view->lineno = 1;
5655 if (view->lineno < view->offset)
5656 view->offset = view->lineno;
5657 else if (view->offset + view->height <= view->lineno)
5658 view->offset = view->lineno - view->height + 1;
5660 view->p_restore = FALSE;
5661 }
5663 static void
5664 status_update_onbranch(void)
5665 {
5666 static const char *paths[][2] = {
5667 { "rebase-apply/rebasing", "Rebasing" },
5668 { "rebase-apply/applying", "Applying mailbox" },
5669 { "rebase-apply/", "Rebasing mailbox" },
5670 { "rebase-merge/interactive", "Interactive rebase" },
5671 { "rebase-merge/", "Rebase merge" },
5672 { "MERGE_HEAD", "Merging" },
5673 { "BISECT_LOG", "Bisecting" },
5674 { "HEAD", "On branch" },
5675 };
5676 char buf[SIZEOF_STR];
5677 struct stat stat;
5678 int i;
5680 if (is_initial_commit()) {
5681 string_copy(status_onbranch, "Initial commit");
5682 return;
5683 }
5685 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5686 char *head = opt_head;
5688 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5689 lstat(buf, &stat) < 0)
5690 continue;
5692 if (!*opt_head) {
5693 struct io io;
5695 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5696 io_read_buf(&io, buf, sizeof(buf))) {
5697 head = buf;
5698 if (!prefixcmp(head, "refs/heads/"))
5699 head += STRING_SIZE("refs/heads/");
5700 }
5701 }
5703 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5704 string_copy(status_onbranch, opt_head);
5705 return;
5706 }
5708 string_copy(status_onbranch, "Not currently on any branch");
5709 }
5711 /* First parse staged info using git-diff-index(1), then parse unstaged
5712 * info using git-diff-files(1), and finally untracked files using
5713 * git-ls-files(1). */
5714 static bool
5715 status_open(struct view *view)
5716 {
5717 reset_view(view);
5719 add_line_data(view, NULL, LINE_STAT_HEAD);
5720 status_update_onbranch();
5722 io_run_bg(update_index_argv);
5724 if (is_initial_commit()) {
5725 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5726 return FALSE;
5727 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5728 return FALSE;
5729 }
5731 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5732 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5733 return FALSE;
5735 /* Restore the exact position or use the specialized restore
5736 * mode? */
5737 if (!view->p_restore)
5738 status_restore(view);
5739 return TRUE;
5740 }
5742 static bool
5743 status_draw(struct view *view, struct line *line, unsigned int lineno)
5744 {
5745 struct status *status = line->data;
5746 enum line_type type;
5747 const char *text;
5749 if (!status) {
5750 switch (line->type) {
5751 case LINE_STAT_STAGED:
5752 type = LINE_STAT_SECTION;
5753 text = "Changes to be committed:";
5754 break;
5756 case LINE_STAT_UNSTAGED:
5757 type = LINE_STAT_SECTION;
5758 text = "Changed but not updated:";
5759 break;
5761 case LINE_STAT_UNTRACKED:
5762 type = LINE_STAT_SECTION;
5763 text = "Untracked files:";
5764 break;
5766 case LINE_STAT_NONE:
5767 type = LINE_DEFAULT;
5768 text = " (no files)";
5769 break;
5771 case LINE_STAT_HEAD:
5772 type = LINE_STAT_HEAD;
5773 text = status_onbranch;
5774 break;
5776 default:
5777 return FALSE;
5778 }
5779 } else {
5780 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5782 buf[0] = status->status;
5783 if (draw_text(view, line->type, buf, TRUE))
5784 return TRUE;
5785 type = LINE_DEFAULT;
5786 text = status->new.name;
5787 }
5789 draw_text(view, type, text, TRUE);
5790 return TRUE;
5791 }
5793 static enum request
5794 status_load_error(struct view *view, struct view *stage, const char *path)
5795 {
5796 if (displayed_views() == 2 || display[current_view] != view)
5797 maximize_view(view);
5798 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5799 return REQ_NONE;
5800 }
5802 static enum request
5803 status_enter(struct view *view, struct line *line)
5804 {
5805 struct status *status = line->data;
5806 const char *oldpath = status ? status->old.name : NULL;
5807 /* Diffs for unmerged entries are empty when passing the new
5808 * path, so leave it empty. */
5809 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5810 const char *info;
5811 enum open_flags split;
5812 struct view *stage = VIEW(REQ_VIEW_STAGE);
5814 if (line->type == LINE_STAT_NONE ||
5815 (!status && line[1].type == LINE_STAT_NONE)) {
5816 report("No file to diff");
5817 return REQ_NONE;
5818 }
5820 switch (line->type) {
5821 case LINE_STAT_STAGED:
5822 if (is_initial_commit()) {
5823 const char *no_head_diff_argv[] = {
5824 "git", "diff", "--no-color", "--patch-with-stat",
5825 "--", "/dev/null", newpath, NULL
5826 };
5828 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5829 return status_load_error(view, stage, newpath);
5830 } else {
5831 const char *index_show_argv[] = {
5832 "git", "diff-index", "--root", "--patch-with-stat",
5833 "-C", "-M", "--cached", "HEAD", "--",
5834 oldpath, newpath, NULL
5835 };
5837 if (!prepare_update(stage, index_show_argv, opt_cdup))
5838 return status_load_error(view, stage, newpath);
5839 }
5841 if (status)
5842 info = "Staged changes to %s";
5843 else
5844 info = "Staged changes";
5845 break;
5847 case LINE_STAT_UNSTAGED:
5848 {
5849 const char *files_show_argv[] = {
5850 "git", "diff-files", "--root", "--patch-with-stat",
5851 "-C", "-M", "--", oldpath, newpath, NULL
5852 };
5854 if (!prepare_update(stage, files_show_argv, opt_cdup))
5855 return status_load_error(view, stage, newpath);
5856 if (status)
5857 info = "Unstaged changes to %s";
5858 else
5859 info = "Unstaged changes";
5860 break;
5861 }
5862 case LINE_STAT_UNTRACKED:
5863 if (!newpath) {
5864 report("No file to show");
5865 return REQ_NONE;
5866 }
5868 if (!suffixcmp(status->new.name, -1, "/")) {
5869 report("Cannot display a directory");
5870 return REQ_NONE;
5871 }
5873 if (!prepare_update_file(stage, newpath))
5874 return status_load_error(view, stage, newpath);
5875 info = "Untracked file %s";
5876 break;
5878 case LINE_STAT_HEAD:
5879 return REQ_NONE;
5881 default:
5882 die("line type %d not handled in switch", line->type);
5883 }
5885 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5886 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5887 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5888 if (status) {
5889 stage_status = *status;
5890 } else {
5891 memset(&stage_status, 0, sizeof(stage_status));
5892 }
5894 stage_line_type = line->type;
5895 stage_chunks = 0;
5896 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5897 }
5899 return REQ_NONE;
5900 }
5902 static bool
5903 status_exists(struct status *status, enum line_type type)
5904 {
5905 struct view *view = VIEW(REQ_VIEW_STATUS);
5906 unsigned long lineno;
5908 for (lineno = 0; lineno < view->lines; lineno++) {
5909 struct line *line = &view->line[lineno];
5910 struct status *pos = line->data;
5912 if (line->type != type)
5913 continue;
5914 if (!pos && (!status || !status->status) && line[1].data) {
5915 select_view_line(view, lineno);
5916 return TRUE;
5917 }
5918 if (pos && !strcmp(status->new.name, pos->new.name)) {
5919 select_view_line(view, lineno);
5920 return TRUE;
5921 }
5922 }
5924 return FALSE;
5925 }
5928 static bool
5929 status_update_prepare(struct io *io, enum line_type type)
5930 {
5931 const char *staged_argv[] = {
5932 "git", "update-index", "-z", "--index-info", NULL
5933 };
5934 const char *others_argv[] = {
5935 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5936 };
5938 switch (type) {
5939 case LINE_STAT_STAGED:
5940 return io_run(io, IO_WR, opt_cdup, staged_argv);
5942 case LINE_STAT_UNSTAGED:
5943 case LINE_STAT_UNTRACKED:
5944 return io_run(io, IO_WR, opt_cdup, others_argv);
5946 default:
5947 die("line type %d not handled in switch", type);
5948 return FALSE;
5949 }
5950 }
5952 static bool
5953 status_update_write(struct io *io, struct status *status, enum line_type type)
5954 {
5955 char buf[SIZEOF_STR];
5956 size_t bufsize = 0;
5958 switch (type) {
5959 case LINE_STAT_STAGED:
5960 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5961 status->old.mode,
5962 status->old.rev,
5963 status->old.name, 0))
5964 return FALSE;
5965 break;
5967 case LINE_STAT_UNSTAGED:
5968 case LINE_STAT_UNTRACKED:
5969 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5970 return FALSE;
5971 break;
5973 default:
5974 die("line type %d not handled in switch", type);
5975 }
5977 return io_write(io, buf, bufsize);
5978 }
5980 static bool
5981 status_update_file(struct status *status, enum line_type type)
5982 {
5983 struct io io;
5984 bool result;
5986 if (!status_update_prepare(&io, type))
5987 return FALSE;
5989 result = status_update_write(&io, status, type);
5990 return io_done(&io) && result;
5991 }
5993 static bool
5994 status_update_files(struct view *view, struct line *line)
5995 {
5996 char buf[sizeof(view->ref)];
5997 struct io io;
5998 bool result = TRUE;
5999 struct line *pos = view->line + view->lines;
6000 int files = 0;
6001 int file, done;
6002 int cursor_y = -1, cursor_x = -1;
6004 if (!status_update_prepare(&io, line->type))
6005 return FALSE;
6007 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6008 files++;
6010 string_copy(buf, view->ref);
6011 getsyx(cursor_y, cursor_x);
6012 for (file = 0, done = 5; result && file < files; line++, file++) {
6013 int almost_done = file * 100 / files;
6015 if (almost_done > done) {
6016 done = almost_done;
6017 string_format(view->ref, "updating file %u of %u (%d%% done)",
6018 file, files, done);
6019 update_view_title(view);
6020 setsyx(cursor_y, cursor_x);
6021 doupdate();
6022 }
6023 result = status_update_write(&io, line->data, line->type);
6024 }
6025 string_copy(view->ref, buf);
6027 return io_done(&io) && result;
6028 }
6030 static bool
6031 status_update(struct view *view)
6032 {
6033 struct line *line = &view->line[view->lineno];
6035 assert(view->lines);
6037 if (!line->data) {
6038 /* This should work even for the "On branch" line. */
6039 if (line < view->line + view->lines && !line[1].data) {
6040 report("Nothing to update");
6041 return FALSE;
6042 }
6044 if (!status_update_files(view, line + 1)) {
6045 report("Failed to update file status");
6046 return FALSE;
6047 }
6049 } else if (!status_update_file(line->data, line->type)) {
6050 report("Failed to update file status");
6051 return FALSE;
6052 }
6054 return TRUE;
6055 }
6057 static bool
6058 status_revert(struct status *status, enum line_type type, bool has_none)
6059 {
6060 if (!status || type != LINE_STAT_UNSTAGED) {
6061 if (type == LINE_STAT_STAGED) {
6062 report("Cannot revert changes to staged files");
6063 } else if (type == LINE_STAT_UNTRACKED) {
6064 report("Cannot revert changes to untracked files");
6065 } else if (has_none) {
6066 report("Nothing to revert");
6067 } else {
6068 report("Cannot revert changes to multiple files");
6069 }
6071 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6072 char mode[10] = "100644";
6073 const char *reset_argv[] = {
6074 "git", "update-index", "--cacheinfo", mode,
6075 status->old.rev, status->old.name, NULL
6076 };
6077 const char *checkout_argv[] = {
6078 "git", "checkout", "--", status->old.name, NULL
6079 };
6081 if (status->status == 'U') {
6082 string_format(mode, "%5o", status->old.mode);
6084 if (status->old.mode == 0 && status->new.mode == 0) {
6085 reset_argv[2] = "--force-remove";
6086 reset_argv[3] = status->old.name;
6087 reset_argv[4] = NULL;
6088 }
6090 if (!io_run_fg(reset_argv, opt_cdup))
6091 return FALSE;
6092 if (status->old.mode == 0 && status->new.mode == 0)
6093 return TRUE;
6094 }
6096 return io_run_fg(checkout_argv, opt_cdup);
6097 }
6099 return FALSE;
6100 }
6102 static enum request
6103 status_request(struct view *view, enum request request, struct line *line)
6104 {
6105 struct status *status = line->data;
6107 switch (request) {
6108 case REQ_STATUS_UPDATE:
6109 if (!status_update(view))
6110 return REQ_NONE;
6111 break;
6113 case REQ_STATUS_REVERT:
6114 if (!status_revert(status, line->type, status_has_none(view, line)))
6115 return REQ_NONE;
6116 break;
6118 case REQ_STATUS_MERGE:
6119 if (!status || status->status != 'U') {
6120 report("Merging only possible for files with unmerged status ('U').");
6121 return REQ_NONE;
6122 }
6123 open_mergetool(status->new.name);
6124 break;
6126 case REQ_EDIT:
6127 if (!status)
6128 return request;
6129 if (status->status == 'D') {
6130 report("File has been deleted.");
6131 return REQ_NONE;
6132 }
6134 open_editor(status->new.name);
6135 break;
6137 case REQ_VIEW_BLAME:
6138 if (status)
6139 opt_ref[0] = 0;
6140 return request;
6142 case REQ_ENTER:
6143 /* After returning the status view has been split to
6144 * show the stage view. No further reloading is
6145 * necessary. */
6146 return status_enter(view, line);
6148 case REQ_REFRESH:
6149 /* Simply reload the view. */
6150 break;
6152 default:
6153 return request;
6154 }
6156 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6158 return REQ_NONE;
6159 }
6161 static void
6162 status_select(struct view *view, struct line *line)
6163 {
6164 struct status *status = line->data;
6165 char file[SIZEOF_STR] = "all files";
6166 const char *text;
6167 const char *key;
6169 if (status && !string_format(file, "'%s'", status->new.name))
6170 return;
6172 if (!status && line[1].type == LINE_STAT_NONE)
6173 line++;
6175 switch (line->type) {
6176 case LINE_STAT_STAGED:
6177 text = "Press %s to unstage %s for commit";
6178 break;
6180 case LINE_STAT_UNSTAGED:
6181 text = "Press %s to stage %s for commit";
6182 break;
6184 case LINE_STAT_UNTRACKED:
6185 text = "Press %s to stage %s for addition";
6186 break;
6188 case LINE_STAT_HEAD:
6189 case LINE_STAT_NONE:
6190 text = "Nothing to update";
6191 break;
6193 default:
6194 die("line type %d not handled in switch", line->type);
6195 }
6197 if (status && status->status == 'U') {
6198 text = "Press %s to resolve conflict in %s";
6199 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6201 } else {
6202 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6203 }
6205 string_format(view->ref, text, key, file);
6206 if (status)
6207 string_copy(opt_file, status->new.name);
6208 }
6210 static bool
6211 status_grep(struct view *view, struct line *line)
6212 {
6213 struct status *status = line->data;
6215 if (status) {
6216 const char buf[2] = { status->status, 0 };
6217 const char *text[] = { status->new.name, buf, NULL };
6219 return grep_text(view, text);
6220 }
6222 return FALSE;
6223 }
6225 static struct view_ops status_ops = {
6226 "file",
6227 NULL,
6228 status_open,
6229 NULL,
6230 status_draw,
6231 status_request,
6232 status_grep,
6233 status_select,
6234 };
6237 static bool
6238 stage_diff_write(struct io *io, struct line *line, struct line *end)
6239 {
6240 while (line < end) {
6241 if (!io_write(io, line->data, strlen(line->data)) ||
6242 !io_write(io, "\n", 1))
6243 return FALSE;
6244 line++;
6245 if (line->type == LINE_DIFF_CHUNK ||
6246 line->type == LINE_DIFF_HEADER)
6247 break;
6248 }
6250 return TRUE;
6251 }
6253 static struct line *
6254 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6255 {
6256 for (; view->line < line; line--)
6257 if (line->type == type)
6258 return line;
6260 return NULL;
6261 }
6263 static bool
6264 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6265 {
6266 const char *apply_argv[SIZEOF_ARG] = {
6267 "git", "apply", "--whitespace=nowarn", NULL
6268 };
6269 struct line *diff_hdr;
6270 struct io io;
6271 int argc = 3;
6273 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6274 if (!diff_hdr)
6275 return FALSE;
6277 if (!revert)
6278 apply_argv[argc++] = "--cached";
6279 if (revert || stage_line_type == LINE_STAT_STAGED)
6280 apply_argv[argc++] = "-R";
6281 apply_argv[argc++] = "-";
6282 apply_argv[argc++] = NULL;
6283 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6284 return FALSE;
6286 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6287 !stage_diff_write(&io, chunk, view->line + view->lines))
6288 chunk = NULL;
6290 io_done(&io);
6291 io_run_bg(update_index_argv);
6293 return chunk ? TRUE : FALSE;
6294 }
6296 static bool
6297 stage_update(struct view *view, struct line *line)
6298 {
6299 struct line *chunk = NULL;
6301 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6302 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6304 if (chunk) {
6305 if (!stage_apply_chunk(view, chunk, FALSE)) {
6306 report("Failed to apply chunk");
6307 return FALSE;
6308 }
6310 } else if (!stage_status.status) {
6311 view = VIEW(REQ_VIEW_STATUS);
6313 for (line = view->line; line < view->line + view->lines; line++)
6314 if (line->type == stage_line_type)
6315 break;
6317 if (!status_update_files(view, line + 1)) {
6318 report("Failed to update files");
6319 return FALSE;
6320 }
6322 } else if (!status_update_file(&stage_status, stage_line_type)) {
6323 report("Failed to update file");
6324 return FALSE;
6325 }
6327 return TRUE;
6328 }
6330 static bool
6331 stage_revert(struct view *view, struct line *line)
6332 {
6333 struct line *chunk = NULL;
6335 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6336 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6338 if (chunk) {
6339 if (!prompt_yesno("Are you sure you want to revert changes?"))
6340 return FALSE;
6342 if (!stage_apply_chunk(view, chunk, TRUE)) {
6343 report("Failed to revert chunk");
6344 return FALSE;
6345 }
6346 return TRUE;
6348 } else {
6349 return status_revert(stage_status.status ? &stage_status : NULL,
6350 stage_line_type, FALSE);
6351 }
6352 }
6355 static void
6356 stage_next(struct view *view, struct line *line)
6357 {
6358 int i;
6360 if (!stage_chunks) {
6361 for (line = view->line; line < view->line + view->lines; line++) {
6362 if (line->type != LINE_DIFF_CHUNK)
6363 continue;
6365 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6366 report("Allocation failure");
6367 return;
6368 }
6370 stage_chunk[stage_chunks++] = line - view->line;
6371 }
6372 }
6374 for (i = 0; i < stage_chunks; i++) {
6375 if (stage_chunk[i] > view->lineno) {
6376 do_scroll_view(view, stage_chunk[i] - view->lineno);
6377 report("Chunk %d of %d", i + 1, stage_chunks);
6378 return;
6379 }
6380 }
6382 report("No next chunk found");
6383 }
6385 static enum request
6386 stage_request(struct view *view, enum request request, struct line *line)
6387 {
6388 switch (request) {
6389 case REQ_STATUS_UPDATE:
6390 if (!stage_update(view, line))
6391 return REQ_NONE;
6392 break;
6394 case REQ_STATUS_REVERT:
6395 if (!stage_revert(view, line))
6396 return REQ_NONE;
6397 break;
6399 case REQ_STAGE_NEXT:
6400 if (stage_line_type == LINE_STAT_UNTRACKED) {
6401 report("File is untracked; press %s to add",
6402 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6403 return REQ_NONE;
6404 }
6405 stage_next(view, line);
6406 return REQ_NONE;
6408 case REQ_EDIT:
6409 if (!stage_status.new.name[0])
6410 return request;
6411 if (stage_status.status == 'D') {
6412 report("File has been deleted.");
6413 return REQ_NONE;
6414 }
6416 open_editor(stage_status.new.name);
6417 break;
6419 case REQ_REFRESH:
6420 /* Reload everything ... */
6421 break;
6423 case REQ_VIEW_BLAME:
6424 if (stage_status.new.name[0]) {
6425 string_copy(opt_file, stage_status.new.name);
6426 opt_ref[0] = 0;
6427 }
6428 return request;
6430 case REQ_ENTER:
6431 return pager_request(view, request, line);
6433 default:
6434 return request;
6435 }
6437 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6438 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6440 /* Check whether the staged entry still exists, and close the
6441 * stage view if it doesn't. */
6442 if (!status_exists(&stage_status, stage_line_type)) {
6443 status_restore(VIEW(REQ_VIEW_STATUS));
6444 return REQ_VIEW_CLOSE;
6445 }
6447 if (stage_line_type == LINE_STAT_UNTRACKED) {
6448 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6449 report("Cannot display a directory");
6450 return REQ_NONE;
6451 }
6453 if (!prepare_update_file(view, stage_status.new.name)) {
6454 report("Failed to open file: %s", strerror(errno));
6455 return REQ_NONE;
6456 }
6457 }
6458 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6460 return REQ_NONE;
6461 }
6463 static struct view_ops stage_ops = {
6464 "line",
6465 NULL,
6466 NULL,
6467 pager_read,
6468 pager_draw,
6469 stage_request,
6470 pager_grep,
6471 pager_select,
6472 };
6475 /*
6476 * Revision graph
6477 */
6479 struct commit {
6480 char id[SIZEOF_REV]; /* SHA1 ID. */
6481 char title[128]; /* First line of the commit message. */
6482 const char *author; /* Author of the commit. */
6483 struct time time; /* Date from the author ident. */
6484 struct ref_list *refs; /* Repository references. */
6485 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6486 size_t graph_size; /* The width of the graph array. */
6487 bool has_parents; /* Rewritten --parents seen. */
6488 };
6490 /* Size of rev graph with no "padding" columns */
6491 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6493 struct rev_graph {
6494 struct rev_graph *prev, *next, *parents;
6495 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6496 size_t size;
6497 struct commit *commit;
6498 size_t pos;
6499 unsigned int boundary:1;
6500 };
6502 /* Parents of the commit being visualized. */
6503 static struct rev_graph graph_parents[4];
6505 /* The current stack of revisions on the graph. */
6506 static struct rev_graph graph_stacks[4] = {
6507 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6508 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6509 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6510 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6511 };
6513 static inline bool
6514 graph_parent_is_merge(struct rev_graph *graph)
6515 {
6516 return graph->parents->size > 1;
6517 }
6519 static inline void
6520 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6521 {
6522 struct commit *commit = graph->commit;
6524 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6525 commit->graph[commit->graph_size++] = symbol;
6526 }
6528 static void
6529 clear_rev_graph(struct rev_graph *graph)
6530 {
6531 graph->boundary = 0;
6532 graph->size = graph->pos = 0;
6533 graph->commit = NULL;
6534 memset(graph->parents, 0, sizeof(*graph->parents));
6535 }
6537 static void
6538 done_rev_graph(struct rev_graph *graph)
6539 {
6540 if (graph_parent_is_merge(graph) &&
6541 graph->pos < graph->size - 1 &&
6542 graph->next->size == graph->size + graph->parents->size - 1) {
6543 size_t i = graph->pos + graph->parents->size - 1;
6545 graph->commit->graph_size = i * 2;
6546 while (i < graph->next->size - 1) {
6547 append_to_rev_graph(graph, ' ');
6548 append_to_rev_graph(graph, '\\');
6549 i++;
6550 }
6551 }
6553 clear_rev_graph(graph);
6554 }
6556 static void
6557 push_rev_graph(struct rev_graph *graph, const char *parent)
6558 {
6559 int i;
6561 /* "Collapse" duplicate parents lines.
6562 *
6563 * FIXME: This needs to also update update the drawn graph but
6564 * for now it just serves as a method for pruning graph lines. */
6565 for (i = 0; i < graph->size; i++)
6566 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6567 return;
6569 if (graph->size < SIZEOF_REVITEMS) {
6570 string_copy_rev(graph->rev[graph->size++], parent);
6571 }
6572 }
6574 static chtype
6575 get_rev_graph_symbol(struct rev_graph *graph)
6576 {
6577 chtype symbol;
6579 if (graph->boundary)
6580 symbol = REVGRAPH_BOUND;
6581 else if (graph->parents->size == 0)
6582 symbol = REVGRAPH_INIT;
6583 else if (graph_parent_is_merge(graph))
6584 symbol = REVGRAPH_MERGE;
6585 else if (graph->pos >= graph->size)
6586 symbol = REVGRAPH_BRANCH;
6587 else
6588 symbol = REVGRAPH_COMMIT;
6590 return symbol;
6591 }
6593 static void
6594 draw_rev_graph(struct rev_graph *graph)
6595 {
6596 struct rev_filler {
6597 chtype separator, line;
6598 };
6599 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6600 static struct rev_filler fillers[] = {
6601 { ' ', '|' },
6602 { '`', '.' },
6603 { '\'', ' ' },
6604 { '/', ' ' },
6605 };
6606 chtype symbol = get_rev_graph_symbol(graph);
6607 struct rev_filler *filler;
6608 size_t i;
6610 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6611 filler = &fillers[DEFAULT];
6613 for (i = 0; i < graph->pos; i++) {
6614 append_to_rev_graph(graph, filler->line);
6615 if (graph_parent_is_merge(graph->prev) &&
6616 graph->prev->pos == i)
6617 filler = &fillers[RSHARP];
6619 append_to_rev_graph(graph, filler->separator);
6620 }
6622 /* Place the symbol for this revision. */
6623 append_to_rev_graph(graph, symbol);
6625 if (graph->prev->size > graph->size)
6626 filler = &fillers[RDIAG];
6627 else
6628 filler = &fillers[DEFAULT];
6630 i++;
6632 for (; i < graph->size; i++) {
6633 append_to_rev_graph(graph, filler->separator);
6634 append_to_rev_graph(graph, filler->line);
6635 if (graph_parent_is_merge(graph->prev) &&
6636 i < graph->prev->pos + graph->parents->size)
6637 filler = &fillers[RSHARP];
6638 if (graph->prev->size > graph->size)
6639 filler = &fillers[LDIAG];
6640 }
6642 if (graph->prev->size > graph->size) {
6643 append_to_rev_graph(graph, filler->separator);
6644 if (filler->line != ' ')
6645 append_to_rev_graph(graph, filler->line);
6646 }
6647 }
6649 /* Prepare the next rev graph */
6650 static void
6651 prepare_rev_graph(struct rev_graph *graph)
6652 {
6653 size_t i;
6655 /* First, traverse all lines of revisions up to the active one. */
6656 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6657 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6658 break;
6660 push_rev_graph(graph->next, graph->rev[graph->pos]);
6661 }
6663 /* Interleave the new revision parent(s). */
6664 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6665 push_rev_graph(graph->next, graph->parents->rev[i]);
6667 /* Lastly, put any remaining revisions. */
6668 for (i = graph->pos + 1; i < graph->size; i++)
6669 push_rev_graph(graph->next, graph->rev[i]);
6670 }
6672 static void
6673 update_rev_graph(struct view *view, struct rev_graph *graph)
6674 {
6675 /* If this is the finalizing update ... */
6676 if (graph->commit)
6677 prepare_rev_graph(graph);
6679 /* Graph visualization needs a one rev look-ahead,
6680 * so the first update doesn't visualize anything. */
6681 if (!graph->prev->commit)
6682 return;
6684 if (view->lines > 2)
6685 view->line[view->lines - 3].dirty = 1;
6686 if (view->lines > 1)
6687 view->line[view->lines - 2].dirty = 1;
6688 draw_rev_graph(graph->prev);
6689 done_rev_graph(graph->prev->prev);
6690 }
6693 /*
6694 * Main view backend
6695 */
6697 static const char *main_argv[SIZEOF_ARG] = {
6698 "git", "log", "--no-color", "--pretty=raw", "--parents",
6699 "--topo-order", "%(head)", NULL
6700 };
6702 static bool
6703 main_draw(struct view *view, struct line *line, unsigned int lineno)
6704 {
6705 struct commit *commit = line->data;
6707 if (!commit->author)
6708 return FALSE;
6710 if (opt_date && draw_date(view, &commit->time))
6711 return TRUE;
6713 if (opt_author && draw_author(view, commit->author))
6714 return TRUE;
6716 if (opt_rev_graph && commit->graph_size &&
6717 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6718 return TRUE;
6720 if (opt_show_refs && commit->refs) {
6721 size_t i;
6723 for (i = 0; i < commit->refs->size; i++) {
6724 struct ref *ref = commit->refs->refs[i];
6725 enum line_type type;
6727 if (ref->head)
6728 type = LINE_MAIN_HEAD;
6729 else if (ref->ltag)
6730 type = LINE_MAIN_LOCAL_TAG;
6731 else if (ref->tag)
6732 type = LINE_MAIN_TAG;
6733 else if (ref->tracked)
6734 type = LINE_MAIN_TRACKED;
6735 else if (ref->remote)
6736 type = LINE_MAIN_REMOTE;
6737 else
6738 type = LINE_MAIN_REF;
6740 if (draw_text(view, type, "[", TRUE) ||
6741 draw_text(view, type, ref->name, TRUE) ||
6742 draw_text(view, type, "]", TRUE))
6743 return TRUE;
6745 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6746 return TRUE;
6747 }
6748 }
6750 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6751 return TRUE;
6752 }
6754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6755 static bool
6756 main_read(struct view *view, char *line)
6757 {
6758 static struct rev_graph *graph = graph_stacks;
6759 enum line_type type;
6760 struct commit *commit;
6762 if (!line) {
6763 int i;
6765 if (!view->lines && !view->prev)
6766 die("No revisions match the given arguments.");
6767 if (view->lines > 0) {
6768 commit = view->line[view->lines - 1].data;
6769 view->line[view->lines - 1].dirty = 1;
6770 if (!commit->author) {
6771 view->lines--;
6772 free(commit);
6773 graph->commit = NULL;
6774 }
6775 }
6776 update_rev_graph(view, graph);
6778 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6779 clear_rev_graph(&graph_stacks[i]);
6780 return TRUE;
6781 }
6783 type = get_line_type(line);
6784 if (type == LINE_COMMIT) {
6785 commit = calloc(1, sizeof(struct commit));
6786 if (!commit)
6787 return FALSE;
6789 line += STRING_SIZE("commit ");
6790 if (*line == '-') {
6791 graph->boundary = 1;
6792 line++;
6793 }
6795 string_copy_rev(commit->id, line);
6796 commit->refs = get_ref_list(commit->id);
6797 graph->commit = commit;
6798 add_line_data(view, commit, LINE_MAIN_COMMIT);
6800 while ((line = strchr(line, ' '))) {
6801 line++;
6802 push_rev_graph(graph->parents, line);
6803 commit->has_parents = TRUE;
6804 }
6805 return TRUE;
6806 }
6808 if (!view->lines)
6809 return TRUE;
6810 commit = view->line[view->lines - 1].data;
6812 switch (type) {
6813 case LINE_PARENT:
6814 if (commit->has_parents)
6815 break;
6816 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6817 break;
6819 case LINE_AUTHOR:
6820 parse_author_line(line + STRING_SIZE("author "),
6821 &commit->author, &commit->time);
6822 update_rev_graph(view, graph);
6823 graph = graph->next;
6824 break;
6826 default:
6827 /* Fill in the commit title if it has not already been set. */
6828 if (commit->title[0])
6829 break;
6831 /* Require titles to start with a non-space character at the
6832 * offset used by git log. */
6833 if (strncmp(line, " ", 4))
6834 break;
6835 line += 4;
6836 /* Well, if the title starts with a whitespace character,
6837 * try to be forgiving. Otherwise we end up with no title. */
6838 while (isspace(*line))
6839 line++;
6840 if (*line == '\0')
6841 break;
6842 /* FIXME: More graceful handling of titles; append "..." to
6843 * shortened titles, etc. */
6845 string_expand(commit->title, sizeof(commit->title), line, 1);
6846 view->line[view->lines - 1].dirty = 1;
6847 }
6849 return TRUE;
6850 }
6852 static enum request
6853 main_request(struct view *view, enum request request, struct line *line)
6854 {
6855 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6857 switch (request) {
6858 case REQ_ENTER:
6859 open_view(view, REQ_VIEW_DIFF, flags);
6860 break;
6861 case REQ_REFRESH:
6862 load_refs();
6863 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6864 break;
6865 default:
6866 return request;
6867 }
6869 return REQ_NONE;
6870 }
6872 static bool
6873 grep_refs(struct ref_list *list, regex_t *regex)
6874 {
6875 regmatch_t pmatch;
6876 size_t i;
6878 if (!opt_show_refs || !list)
6879 return FALSE;
6881 for (i = 0; i < list->size; i++) {
6882 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6883 return TRUE;
6884 }
6886 return FALSE;
6887 }
6889 static bool
6890 main_grep(struct view *view, struct line *line)
6891 {
6892 struct commit *commit = line->data;
6893 const char *text[] = {
6894 commit->title,
6895 opt_author ? commit->author : "",
6896 mkdate(&commit->time, opt_date),
6897 NULL
6898 };
6900 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6901 }
6903 static void
6904 main_select(struct view *view, struct line *line)
6905 {
6906 struct commit *commit = line->data;
6908 string_copy_rev(view->ref, commit->id);
6909 string_copy_rev(ref_commit, view->ref);
6910 }
6912 static struct view_ops main_ops = {
6913 "commit",
6914 main_argv,
6915 NULL,
6916 main_read,
6917 main_draw,
6918 main_request,
6919 main_grep,
6920 main_select,
6921 };
6924 /*
6925 * Status management
6926 */
6928 /* Whether or not the curses interface has been initialized. */
6929 static bool cursed = FALSE;
6931 /* Terminal hacks and workarounds. */
6932 static bool use_scroll_redrawwin;
6933 static bool use_scroll_status_wclear;
6935 /* The status window is used for polling keystrokes. */
6936 static WINDOW *status_win;
6938 /* Reading from the prompt? */
6939 static bool input_mode = FALSE;
6941 static bool status_empty = FALSE;
6943 /* Update status and title window. */
6944 static void
6945 report(const char *msg, ...)
6946 {
6947 struct view *view = display[current_view];
6949 if (input_mode)
6950 return;
6952 if (!view) {
6953 char buf[SIZEOF_STR];
6954 va_list args;
6956 va_start(args, msg);
6957 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6958 buf[sizeof(buf) - 1] = 0;
6959 buf[sizeof(buf) - 2] = '.';
6960 buf[sizeof(buf) - 3] = '.';
6961 buf[sizeof(buf) - 4] = '.';
6962 }
6963 va_end(args);
6964 die("%s", buf);
6965 }
6967 if (!status_empty || *msg) {
6968 va_list args;
6970 va_start(args, msg);
6972 wmove(status_win, 0, 0);
6973 if (view->has_scrolled && use_scroll_status_wclear)
6974 wclear(status_win);
6975 if (*msg) {
6976 vwprintw(status_win, msg, args);
6977 status_empty = FALSE;
6978 } else {
6979 status_empty = TRUE;
6980 }
6981 wclrtoeol(status_win);
6982 wnoutrefresh(status_win);
6984 va_end(args);
6985 }
6987 update_view_title(view);
6988 }
6990 static void
6991 init_display(void)
6992 {
6993 const char *term;
6994 int x, y;
6996 /* Initialize the curses library */
6997 if (isatty(STDIN_FILENO)) {
6998 cursed = !!initscr();
6999 opt_tty = stdin;
7000 } else {
7001 /* Leave stdin and stdout alone when acting as a pager. */
7002 opt_tty = fopen("/dev/tty", "r+");
7003 if (!opt_tty)
7004 die("Failed to open /dev/tty");
7005 cursed = !!newterm(NULL, opt_tty, opt_tty);
7006 }
7008 if (!cursed)
7009 die("Failed to initialize curses");
7011 nonl(); /* Disable conversion and detect newlines from input. */
7012 cbreak(); /* Take input chars one at a time, no wait for \n */
7013 noecho(); /* Don't echo input */
7014 leaveok(stdscr, FALSE);
7016 if (has_colors())
7017 init_colors();
7019 getmaxyx(stdscr, y, x);
7020 status_win = newwin(1, 0, y - 1, 0);
7021 if (!status_win)
7022 die("Failed to create status window");
7024 /* Enable keyboard mapping */
7025 keypad(status_win, TRUE);
7026 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7028 TABSIZE = opt_tab_size;
7030 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7031 if (term && !strcmp(term, "gnome-terminal")) {
7032 /* In the gnome-terminal-emulator, the message from
7033 * scrolling up one line when impossible followed by
7034 * scrolling down one line causes corruption of the
7035 * status line. This is fixed by calling wclear. */
7036 use_scroll_status_wclear = TRUE;
7037 use_scroll_redrawwin = FALSE;
7039 } else if (term && !strcmp(term, "xrvt-xpm")) {
7040 /* No problems with full optimizations in xrvt-(unicode)
7041 * and aterm. */
7042 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7044 } else {
7045 /* When scrolling in (u)xterm the last line in the
7046 * scrolling direction will update slowly. */
7047 use_scroll_redrawwin = TRUE;
7048 use_scroll_status_wclear = FALSE;
7049 }
7050 }
7052 static int
7053 get_input(int prompt_position)
7054 {
7055 struct view *view;
7056 int i, key, cursor_y, cursor_x;
7057 bool loading = FALSE;
7059 if (prompt_position)
7060 input_mode = TRUE;
7062 while (TRUE) {
7063 foreach_view (view, i) {
7064 update_view(view);
7065 if (view_is_displayed(view) && view->has_scrolled &&
7066 use_scroll_redrawwin)
7067 redrawwin(view->win);
7068 view->has_scrolled = FALSE;
7069 if (view->pipe)
7070 loading = TRUE;
7071 }
7073 /* Update the cursor position. */
7074 if (prompt_position) {
7075 getbegyx(status_win, cursor_y, cursor_x);
7076 cursor_x = prompt_position;
7077 } else {
7078 view = display[current_view];
7079 getbegyx(view->win, cursor_y, cursor_x);
7080 cursor_x = view->width - 1;
7081 cursor_y += view->lineno - view->offset;
7082 }
7083 setsyx(cursor_y, cursor_x);
7085 /* Refresh, accept single keystroke of input */
7086 doupdate();
7087 nodelay(status_win, loading);
7088 key = wgetch(status_win);
7090 /* wgetch() with nodelay() enabled returns ERR when
7091 * there's no input. */
7092 if (key == ERR) {
7094 } else if (key == KEY_RESIZE) {
7095 int height, width;
7097 getmaxyx(stdscr, height, width);
7099 wresize(status_win, 1, width);
7100 mvwin(status_win, height - 1, 0);
7101 wnoutrefresh(status_win);
7102 resize_display();
7103 redraw_display(TRUE);
7105 } else {
7106 input_mode = FALSE;
7107 return key;
7108 }
7109 }
7110 }
7112 static char *
7113 prompt_input(const char *prompt, input_handler handler, void *data)
7114 {
7115 enum input_status status = INPUT_OK;
7116 static char buf[SIZEOF_STR];
7117 size_t pos = 0;
7119 buf[pos] = 0;
7121 while (status == INPUT_OK || status == INPUT_SKIP) {
7122 int key;
7124 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7125 wclrtoeol(status_win);
7127 key = get_input(pos + 1);
7128 switch (key) {
7129 case KEY_RETURN:
7130 case KEY_ENTER:
7131 case '\n':
7132 status = pos ? INPUT_STOP : INPUT_CANCEL;
7133 break;
7135 case KEY_BACKSPACE:
7136 if (pos > 0)
7137 buf[--pos] = 0;
7138 else
7139 status = INPUT_CANCEL;
7140 break;
7142 case KEY_ESC:
7143 status = INPUT_CANCEL;
7144 break;
7146 default:
7147 if (pos >= sizeof(buf)) {
7148 report("Input string too long");
7149 return NULL;
7150 }
7152 status = handler(data, buf, key);
7153 if (status == INPUT_OK)
7154 buf[pos++] = (char) key;
7155 }
7156 }
7158 /* Clear the status window */
7159 status_empty = FALSE;
7160 report("");
7162 if (status == INPUT_CANCEL)
7163 return NULL;
7165 buf[pos++] = 0;
7167 return buf;
7168 }
7170 static enum input_status
7171 prompt_yesno_handler(void *data, char *buf, int c)
7172 {
7173 if (c == 'y' || c == 'Y')
7174 return INPUT_STOP;
7175 if (c == 'n' || c == 'N')
7176 return INPUT_CANCEL;
7177 return INPUT_SKIP;
7178 }
7180 static bool
7181 prompt_yesno(const char *prompt)
7182 {
7183 char prompt2[SIZEOF_STR];
7185 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7186 return FALSE;
7188 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7189 }
7191 static enum input_status
7192 read_prompt_handler(void *data, char *buf, int c)
7193 {
7194 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7195 }
7197 static char *
7198 read_prompt(const char *prompt)
7199 {
7200 return prompt_input(prompt, read_prompt_handler, NULL);
7201 }
7203 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7204 {
7205 enum input_status status = INPUT_OK;
7206 int size = 0;
7208 while (items[size].text)
7209 size++;
7211 while (status == INPUT_OK) {
7212 const struct menu_item *item = &items[*selected];
7213 int key;
7214 int i;
7216 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7217 prompt, *selected + 1, size);
7218 if (item->hotkey)
7219 wprintw(status_win, "[%c] ", (char) item->hotkey);
7220 wprintw(status_win, "%s", item->text);
7221 wclrtoeol(status_win);
7223 key = get_input(COLS - 1);
7224 switch (key) {
7225 case KEY_RETURN:
7226 case KEY_ENTER:
7227 case '\n':
7228 status = INPUT_STOP;
7229 break;
7231 case KEY_LEFT:
7232 case KEY_UP:
7233 *selected = *selected - 1;
7234 if (*selected < 0)
7235 *selected = size - 1;
7236 break;
7238 case KEY_RIGHT:
7239 case KEY_DOWN:
7240 *selected = (*selected + 1) % size;
7241 break;
7243 case KEY_ESC:
7244 status = INPUT_CANCEL;
7245 break;
7247 default:
7248 for (i = 0; items[i].text; i++)
7249 if (items[i].hotkey == key) {
7250 *selected = i;
7251 status = INPUT_STOP;
7252 break;
7253 }
7254 }
7255 }
7257 /* Clear the status window */
7258 status_empty = FALSE;
7259 report("");
7261 return status != INPUT_CANCEL;
7262 }
7264 /*
7265 * Repository properties
7266 */
7268 static struct ref **refs = NULL;
7269 static size_t refs_size = 0;
7270 static struct ref *refs_head = NULL;
7272 static struct ref_list **ref_lists = NULL;
7273 static size_t ref_lists_size = 0;
7275 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7276 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7277 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7279 static int
7280 compare_refs(const void *ref1_, const void *ref2_)
7281 {
7282 const struct ref *ref1 = *(const struct ref **)ref1_;
7283 const struct ref *ref2 = *(const struct ref **)ref2_;
7285 if (ref1->tag != ref2->tag)
7286 return ref2->tag - ref1->tag;
7287 if (ref1->ltag != ref2->ltag)
7288 return ref2->ltag - ref2->ltag;
7289 if (ref1->head != ref2->head)
7290 return ref2->head - ref1->head;
7291 if (ref1->tracked != ref2->tracked)
7292 return ref2->tracked - ref1->tracked;
7293 if (ref1->remote != ref2->remote)
7294 return ref2->remote - ref1->remote;
7295 return strcmp(ref1->name, ref2->name);
7296 }
7298 static void
7299 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7300 {
7301 size_t i;
7303 for (i = 0; i < refs_size; i++)
7304 if (!visitor(data, refs[i]))
7305 break;
7306 }
7308 static struct ref *
7309 get_ref_head()
7310 {
7311 return refs_head;
7312 }
7314 static struct ref_list *
7315 get_ref_list(const char *id)
7316 {
7317 struct ref_list *list;
7318 size_t i;
7320 for (i = 0; i < ref_lists_size; i++)
7321 if (!strcmp(id, ref_lists[i]->id))
7322 return ref_lists[i];
7324 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7325 return NULL;
7326 list = calloc(1, sizeof(*list));
7327 if (!list)
7328 return NULL;
7330 for (i = 0; i < refs_size; i++) {
7331 if (!strcmp(id, refs[i]->id) &&
7332 realloc_refs_list(&list->refs, list->size, 1))
7333 list->refs[list->size++] = refs[i];
7334 }
7336 if (!list->refs) {
7337 free(list);
7338 return NULL;
7339 }
7341 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7342 ref_lists[ref_lists_size++] = list;
7343 return list;
7344 }
7346 static int
7347 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7348 {
7349 struct ref *ref = NULL;
7350 bool tag = FALSE;
7351 bool ltag = FALSE;
7352 bool remote = FALSE;
7353 bool tracked = FALSE;
7354 bool head = FALSE;
7355 int from = 0, to = refs_size - 1;
7357 if (!prefixcmp(name, "refs/tags/")) {
7358 if (!suffixcmp(name, namelen, "^{}")) {
7359 namelen -= 3;
7360 name[namelen] = 0;
7361 } else {
7362 ltag = TRUE;
7363 }
7365 tag = TRUE;
7366 namelen -= STRING_SIZE("refs/tags/");
7367 name += STRING_SIZE("refs/tags/");
7369 } else if (!prefixcmp(name, "refs/remotes/")) {
7370 remote = TRUE;
7371 namelen -= STRING_SIZE("refs/remotes/");
7372 name += STRING_SIZE("refs/remotes/");
7373 tracked = !strcmp(opt_remote, name);
7375 } else if (!prefixcmp(name, "refs/heads/")) {
7376 namelen -= STRING_SIZE("refs/heads/");
7377 name += STRING_SIZE("refs/heads/");
7378 if (!strncmp(opt_head, name, namelen))
7379 return OK;
7381 } else if (!strcmp(name, "HEAD")) {
7382 head = TRUE;
7383 if (*opt_head) {
7384 namelen = strlen(opt_head);
7385 name = opt_head;
7386 }
7387 }
7389 /* If we are reloading or it's an annotated tag, replace the
7390 * previous SHA1 with the resolved commit id; relies on the fact
7391 * git-ls-remote lists the commit id of an annotated tag right
7392 * before the commit id it points to. */
7393 while (from <= to) {
7394 size_t pos = (to + from) / 2;
7395 int cmp = strcmp(name, refs[pos]->name);
7397 if (!cmp) {
7398 ref = refs[pos];
7399 break;
7400 }
7402 if (cmp < 0)
7403 to = pos - 1;
7404 else
7405 from = pos + 1;
7406 }
7408 if (!ref) {
7409 if (!realloc_refs(&refs, refs_size, 1))
7410 return ERR;
7411 ref = calloc(1, sizeof(*ref) + namelen);
7412 if (!ref)
7413 return ERR;
7414 memmove(refs + from + 1, refs + from,
7415 (refs_size - from) * sizeof(*refs));
7416 refs[from] = ref;
7417 strncpy(ref->name, name, namelen);
7418 refs_size++;
7419 }
7421 ref->head = head;
7422 ref->tag = tag;
7423 ref->ltag = ltag;
7424 ref->remote = remote;
7425 ref->tracked = tracked;
7426 string_copy_rev(ref->id, id);
7428 if (head)
7429 refs_head = ref;
7430 return OK;
7431 }
7433 static int
7434 load_refs(void)
7435 {
7436 const char *head_argv[] = {
7437 "git", "symbolic-ref", "HEAD", NULL
7438 };
7439 static const char *ls_remote_argv[SIZEOF_ARG] = {
7440 "git", "ls-remote", opt_git_dir, NULL
7441 };
7442 static bool init = FALSE;
7443 size_t i;
7445 if (!init) {
7446 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7447 die("TIG_LS_REMOTE contains too many arguments");
7448 init = TRUE;
7449 }
7451 if (!*opt_git_dir)
7452 return OK;
7454 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7455 !prefixcmp(opt_head, "refs/heads/")) {
7456 char *offset = opt_head + STRING_SIZE("refs/heads/");
7458 memmove(opt_head, offset, strlen(offset) + 1);
7459 }
7461 refs_head = NULL;
7462 for (i = 0; i < refs_size; i++)
7463 refs[i]->id[0] = 0;
7465 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7466 return ERR;
7468 /* Update the ref lists to reflect changes. */
7469 for (i = 0; i < ref_lists_size; i++) {
7470 struct ref_list *list = ref_lists[i];
7471 size_t old, new;
7473 for (old = new = 0; old < list->size; old++)
7474 if (!strcmp(list->id, list->refs[old]->id))
7475 list->refs[new++] = list->refs[old];
7476 list->size = new;
7477 }
7479 return OK;
7480 }
7482 static void
7483 set_remote_branch(const char *name, const char *value, size_t valuelen)
7484 {
7485 if (!strcmp(name, ".remote")) {
7486 string_ncopy(opt_remote, value, valuelen);
7488 } else if (*opt_remote && !strcmp(name, ".merge")) {
7489 size_t from = strlen(opt_remote);
7491 if (!prefixcmp(value, "refs/heads/"))
7492 value += STRING_SIZE("refs/heads/");
7494 if (!string_format_from(opt_remote, &from, "/%s", value))
7495 opt_remote[0] = 0;
7496 }
7497 }
7499 static void
7500 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7501 {
7502 const char *argv[SIZEOF_ARG] = { name, "=" };
7503 int argc = 1 + (cmd == option_set_command);
7504 int error = ERR;
7506 if (!argv_from_string(argv, &argc, value))
7507 config_msg = "Too many option arguments";
7508 else
7509 error = cmd(argc, argv);
7511 if (error == ERR)
7512 warn("Option 'tig.%s': %s", name, config_msg);
7513 }
7515 static bool
7516 set_environment_variable(const char *name, const char *value)
7517 {
7518 size_t len = strlen(name) + 1 + strlen(value) + 1;
7519 char *env = malloc(len);
7521 if (env &&
7522 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7523 putenv(env) == 0)
7524 return TRUE;
7525 free(env);
7526 return FALSE;
7527 }
7529 static void
7530 set_work_tree(const char *value)
7531 {
7532 char cwd[SIZEOF_STR];
7534 if (!getcwd(cwd, sizeof(cwd)))
7535 die("Failed to get cwd path: %s", strerror(errno));
7536 if (chdir(opt_git_dir) < 0)
7537 die("Failed to chdir(%s): %s", strerror(errno));
7538 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7539 die("Failed to get git path: %s", strerror(errno));
7540 if (chdir(cwd) < 0)
7541 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7542 if (chdir(value) < 0)
7543 die("Failed to chdir(%s): %s", value, strerror(errno));
7544 if (!getcwd(cwd, sizeof(cwd)))
7545 die("Failed to get cwd path: %s", strerror(errno));
7546 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7547 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7548 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7549 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7550 opt_is_inside_work_tree = TRUE;
7551 }
7553 static int
7554 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7555 {
7556 if (!strcmp(name, "i18n.commitencoding"))
7557 string_ncopy(opt_encoding, value, valuelen);
7559 else if (!strcmp(name, "core.editor"))
7560 string_ncopy(opt_editor, value, valuelen);
7562 else if (!strcmp(name, "core.worktree"))
7563 set_work_tree(value);
7565 else if (!prefixcmp(name, "tig.color."))
7566 set_repo_config_option(name + 10, value, option_color_command);
7568 else if (!prefixcmp(name, "tig.bind."))
7569 set_repo_config_option(name + 9, value, option_bind_command);
7571 else if (!prefixcmp(name, "tig."))
7572 set_repo_config_option(name + 4, value, option_set_command);
7574 else if (*opt_head && !prefixcmp(name, "branch.") &&
7575 !strncmp(name + 7, opt_head, strlen(opt_head)))
7576 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7578 return OK;
7579 }
7581 static int
7582 load_git_config(void)
7583 {
7584 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7586 return io_run_load(config_list_argv, "=", read_repo_config_option);
7587 }
7589 static int
7590 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7591 {
7592 if (!opt_git_dir[0]) {
7593 string_ncopy(opt_git_dir, name, namelen);
7595 } else if (opt_is_inside_work_tree == -1) {
7596 /* This can be 3 different values depending on the
7597 * version of git being used. If git-rev-parse does not
7598 * understand --is-inside-work-tree it will simply echo
7599 * the option else either "true" or "false" is printed.
7600 * Default to true for the unknown case. */
7601 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7603 } else if (*name == '.') {
7604 string_ncopy(opt_cdup, name, namelen);
7606 } else {
7607 string_ncopy(opt_prefix, name, namelen);
7608 }
7610 return OK;
7611 }
7613 static int
7614 load_repo_info(void)
7615 {
7616 const char *rev_parse_argv[] = {
7617 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7618 "--show-cdup", "--show-prefix", NULL
7619 };
7621 return io_run_load(rev_parse_argv, "=", read_repo_info);
7622 }
7625 /*
7626 * Main
7627 */
7629 static const char usage[] =
7630 "tig " TIG_VERSION " (" __DATE__ ")\n"
7631 "\n"
7632 "Usage: tig [options] [revs] [--] [paths]\n"
7633 " or: tig show [options] [revs] [--] [paths]\n"
7634 " or: tig blame [rev] path\n"
7635 " or: tig status\n"
7636 " or: tig < [git command output]\n"
7637 "\n"
7638 "Options:\n"
7639 " -v, --version Show version and exit\n"
7640 " -h, --help Show help message and exit";
7642 static void __NORETURN
7643 quit(int sig)
7644 {
7645 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7646 if (cursed)
7647 endwin();
7648 exit(0);
7649 }
7651 static void __NORETURN
7652 die(const char *err, ...)
7653 {
7654 va_list args;
7656 endwin();
7658 va_start(args, err);
7659 fputs("tig: ", stderr);
7660 vfprintf(stderr, err, args);
7661 fputs("\n", stderr);
7662 va_end(args);
7664 exit(1);
7665 }
7667 static void
7668 warn(const char *msg, ...)
7669 {
7670 va_list args;
7672 va_start(args, msg);
7673 fputs("tig warning: ", stderr);
7674 vfprintf(stderr, msg, args);
7675 fputs("\n", stderr);
7676 va_end(args);
7677 }
7679 static enum request
7680 parse_options(int argc, const char *argv[])
7681 {
7682 enum request request = REQ_VIEW_MAIN;
7683 const char *subcommand;
7684 bool seen_dashdash = FALSE;
7685 /* XXX: This is vulnerable to the user overriding options
7686 * required for the main view parser. */
7687 const char *custom_argv[SIZEOF_ARG] = {
7688 "git", "log", "--no-color", "--pretty=raw", "--parents",
7689 "--topo-order", NULL
7690 };
7691 int i, j = 6;
7693 if (!isatty(STDIN_FILENO)) {
7694 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7695 return REQ_VIEW_PAGER;
7696 }
7698 if (argc <= 1)
7699 return REQ_NONE;
7701 subcommand = argv[1];
7702 if (!strcmp(subcommand, "status")) {
7703 if (argc > 2)
7704 warn("ignoring arguments after `%s'", subcommand);
7705 return REQ_VIEW_STATUS;
7707 } else if (!strcmp(subcommand, "blame")) {
7708 if (argc <= 2 || argc > 4)
7709 die("invalid number of options to blame\n\n%s", usage);
7711 i = 2;
7712 if (argc == 4) {
7713 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7714 i++;
7715 }
7717 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7718 return REQ_VIEW_BLAME;
7720 } else if (!strcmp(subcommand, "show")) {
7721 request = REQ_VIEW_DIFF;
7723 } else {
7724 subcommand = NULL;
7725 }
7727 if (subcommand) {
7728 custom_argv[1] = subcommand;
7729 j = 2;
7730 }
7732 for (i = 1 + !!subcommand; i < argc; i++) {
7733 const char *opt = argv[i];
7735 if (seen_dashdash || !strcmp(opt, "--")) {
7736 seen_dashdash = TRUE;
7738 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7739 printf("tig version %s\n", TIG_VERSION);
7740 quit(0);
7742 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7743 printf("%s\n", usage);
7744 quit(0);
7745 }
7747 custom_argv[j++] = opt;
7748 if (j >= ARRAY_SIZE(custom_argv))
7749 die("command too long");
7750 }
7752 if (!prepare_update(VIEW(request), custom_argv, NULL))
7753 die("Failed to format arguments");
7755 return request;
7756 }
7758 int
7759 main(int argc, const char *argv[])
7760 {
7761 const char *codeset = "UTF-8";
7762 enum request request = parse_options(argc, argv);
7763 struct view *view;
7764 size_t i;
7766 signal(SIGINT, quit);
7767 signal(SIGPIPE, SIG_IGN);
7769 if (setlocale(LC_ALL, "")) {
7770 codeset = nl_langinfo(CODESET);
7771 }
7773 if (load_repo_info() == ERR)
7774 die("Failed to load repo info.");
7776 if (load_options() == ERR)
7777 die("Failed to load user config.");
7779 if (load_git_config() == ERR)
7780 die("Failed to load repo config.");
7782 /* Require a git repository unless when running in pager mode. */
7783 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7784 die("Not a git repository");
7786 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7787 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7788 if (opt_iconv_in == ICONV_NONE)
7789 die("Failed to initialize character set conversion");
7790 }
7792 if (codeset && strcmp(codeset, "UTF-8")) {
7793 opt_iconv_out = iconv_open(codeset, "UTF-8");
7794 if (opt_iconv_out == ICONV_NONE)
7795 die("Failed to initialize character set conversion");
7796 }
7798 if (load_refs() == ERR)
7799 die("Failed to load refs.");
7801 foreach_view (view, i)
7802 if (!argv_from_env(view->ops->argv, view->cmd_env))
7803 die("Too many arguments in the `%s` environment variable",
7804 view->cmd_env);
7806 init_display();
7808 if (request != REQ_NONE)
7809 open_view(NULL, request, OPEN_PREPARED);
7810 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7812 while (view_driver(display[current_view], request)) {
7813 int key = get_input(0);
7815 view = display[current_view];
7816 request = get_keybinding(view->keymap, key);
7818 /* Some low-level request handling. This keeps access to
7819 * status_win restricted. */
7820 switch (request) {
7821 case REQ_NONE:
7822 report("Unknown key, press %s for help",
7823 get_key(view->keymap, REQ_VIEW_HELP));
7824 break;
7825 case REQ_PROMPT:
7826 {
7827 char *cmd = read_prompt(":");
7829 if (cmd && isdigit(*cmd)) {
7830 int lineno = view->lineno + 1;
7832 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7833 select_view_line(view, lineno - 1);
7834 report("");
7835 } else {
7836 report("Unable to parse '%s' as a line number", cmd);
7837 }
7839 } else if (cmd) {
7840 struct view *next = VIEW(REQ_VIEW_PAGER);
7841 const char *argv[SIZEOF_ARG] = { "git" };
7842 int argc = 1;
7844 /* When running random commands, initially show the
7845 * command in the title. However, it maybe later be
7846 * overwritten if a commit line is selected. */
7847 string_ncopy(next->ref, cmd, strlen(cmd));
7849 if (!argv_from_string(argv, &argc, cmd)) {
7850 report("Too many arguments");
7851 } else if (!prepare_update(next, argv, NULL)) {
7852 report("Failed to format command");
7853 } else {
7854 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7855 }
7856 }
7858 request = REQ_NONE;
7859 break;
7860 }
7861 case REQ_SEARCH:
7862 case REQ_SEARCH_BACK:
7863 {
7864 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7865 char *search = read_prompt(prompt);
7867 if (search)
7868 string_ncopy(opt_search, search, strlen(search));
7869 else if (*opt_search)
7870 request = request == REQ_SEARCH ?
7871 REQ_FIND_NEXT :
7872 REQ_FIND_PREV;
7873 else
7874 request = REQ_NONE;
7875 break;
7876 }
7877 default:
7878 break;
7879 }
7880 }
7882 quit(0);
7884 return 0;
7885 }