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 if (branch->ref == &branch_all) {
5339 const char *all_branches_argv[] = {
5340 "git", "log", "--no-color", "--pretty=raw", "--parents",
5341 "--topo-order", "--all", NULL
5342 };
5343 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5345 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5346 report("Failed to load view of all branches");
5347 return REQ_NONE;
5348 }
5349 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5350 } else {
5351 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5352 }
5353 return REQ_NONE;
5355 default:
5356 return request;
5357 }
5358 }
5360 static bool
5361 branch_read(struct view *view, char *line)
5362 {
5363 static char id[SIZEOF_REV];
5364 struct branch *reference;
5365 size_t i;
5367 if (!line)
5368 return TRUE;
5370 switch (get_line_type(line)) {
5371 case LINE_COMMIT:
5372 string_copy_rev(id, line + STRING_SIZE("commit "));
5373 return TRUE;
5375 case LINE_AUTHOR:
5376 for (i = 0, reference = NULL; i < view->lines; i++) {
5377 struct branch *branch = view->line[i].data;
5379 if (strcmp(branch->ref->id, id))
5380 continue;
5382 view->line[i].dirty = TRUE;
5383 if (reference) {
5384 branch->author = reference->author;
5385 branch->time = reference->time;
5386 continue;
5387 }
5389 parse_author_line(line + STRING_SIZE("author "),
5390 &branch->author, &branch->time);
5391 reference = branch;
5392 }
5393 return TRUE;
5395 default:
5396 return TRUE;
5397 }
5399 }
5401 static bool
5402 branch_open_visitor(void *data, const struct ref *ref)
5403 {
5404 struct view *view = data;
5405 struct branch *branch;
5407 if (ref->tag || ref->ltag || ref->remote)
5408 return TRUE;
5410 branch = calloc(1, sizeof(*branch));
5411 if (!branch)
5412 return FALSE;
5414 branch->ref = ref;
5415 return !!add_line_data(view, branch, LINE_DEFAULT);
5416 }
5418 static bool
5419 branch_open(struct view *view)
5420 {
5421 const char *branch_log[] = {
5422 "git", "log", "--no-color", "--pretty=raw",
5423 "--simplify-by-decoration", "--all", NULL
5424 };
5426 if (!start_update(view, branch_log, NULL)) {
5427 report("Failed to load branch data");
5428 return TRUE;
5429 }
5431 setup_update(view, view->id);
5432 branch_open_visitor(view, &branch_all);
5433 foreach_ref(branch_open_visitor, view);
5434 view->p_restore = TRUE;
5436 return TRUE;
5437 }
5439 static bool
5440 branch_grep(struct view *view, struct line *line)
5441 {
5442 struct branch *branch = line->data;
5443 const char *text[] = {
5444 branch->ref->name,
5445 branch->author,
5446 NULL
5447 };
5449 return grep_text(view, text);
5450 }
5452 static void
5453 branch_select(struct view *view, struct line *line)
5454 {
5455 struct branch *branch = line->data;
5457 string_copy_rev(view->ref, branch->ref->id);
5458 string_copy_rev(ref_commit, branch->ref->id);
5459 string_copy_rev(ref_head, branch->ref->id);
5460 string_copy_rev(ref_branch, branch->ref->name);
5461 }
5463 static struct view_ops branch_ops = {
5464 "branch",
5465 NULL,
5466 branch_open,
5467 branch_read,
5468 branch_draw,
5469 branch_request,
5470 branch_grep,
5471 branch_select,
5472 };
5474 /*
5475 * Status backend
5476 */
5478 struct status {
5479 char status;
5480 struct {
5481 mode_t mode;
5482 char rev[SIZEOF_REV];
5483 char name[SIZEOF_STR];
5484 } old;
5485 struct {
5486 mode_t mode;
5487 char rev[SIZEOF_REV];
5488 char name[SIZEOF_STR];
5489 } new;
5490 };
5492 static char status_onbranch[SIZEOF_STR];
5493 static struct status stage_status;
5494 static enum line_type stage_line_type;
5495 static size_t stage_chunks;
5496 static int *stage_chunk;
5498 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5500 /* This should work even for the "On branch" line. */
5501 static inline bool
5502 status_has_none(struct view *view, struct line *line)
5503 {
5504 return line < view->line + view->lines && !line[1].data;
5505 }
5507 /* Get fields from the diff line:
5508 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5509 */
5510 static inline bool
5511 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5512 {
5513 const char *old_mode = buf + 1;
5514 const char *new_mode = buf + 8;
5515 const char *old_rev = buf + 15;
5516 const char *new_rev = buf + 56;
5517 const char *status = buf + 97;
5519 if (bufsize < 98 ||
5520 old_mode[-1] != ':' ||
5521 new_mode[-1] != ' ' ||
5522 old_rev[-1] != ' ' ||
5523 new_rev[-1] != ' ' ||
5524 status[-1] != ' ')
5525 return FALSE;
5527 file->status = *status;
5529 string_copy_rev(file->old.rev, old_rev);
5530 string_copy_rev(file->new.rev, new_rev);
5532 file->old.mode = strtoul(old_mode, NULL, 8);
5533 file->new.mode = strtoul(new_mode, NULL, 8);
5535 file->old.name[0] = file->new.name[0] = 0;
5537 return TRUE;
5538 }
5540 static bool
5541 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5542 {
5543 struct status *unmerged = NULL;
5544 char *buf;
5545 struct io io;
5547 if (!io_run(&io, IO_RD, opt_cdup, argv))
5548 return FALSE;
5550 add_line_data(view, NULL, type);
5552 while ((buf = io_get(&io, 0, TRUE))) {
5553 struct status *file = unmerged;
5555 if (!file) {
5556 file = calloc(1, sizeof(*file));
5557 if (!file || !add_line_data(view, file, type))
5558 goto error_out;
5559 }
5561 /* Parse diff info part. */
5562 if (status) {
5563 file->status = status;
5564 if (status == 'A')
5565 string_copy(file->old.rev, NULL_ID);
5567 } else if (!file->status || file == unmerged) {
5568 if (!status_get_diff(file, buf, strlen(buf)))
5569 goto error_out;
5571 buf = io_get(&io, 0, TRUE);
5572 if (!buf)
5573 break;
5575 /* Collapse all modified entries that follow an
5576 * associated unmerged entry. */
5577 if (unmerged == file) {
5578 unmerged->status = 'U';
5579 unmerged = NULL;
5580 } else if (file->status == 'U') {
5581 unmerged = file;
5582 }
5583 }
5585 /* Grab the old name for rename/copy. */
5586 if (!*file->old.name &&
5587 (file->status == 'R' || file->status == 'C')) {
5588 string_ncopy(file->old.name, buf, strlen(buf));
5590 buf = io_get(&io, 0, TRUE);
5591 if (!buf)
5592 break;
5593 }
5595 /* git-ls-files just delivers a NUL separated list of
5596 * file names similar to the second half of the
5597 * git-diff-* output. */
5598 string_ncopy(file->new.name, buf, strlen(buf));
5599 if (!*file->old.name)
5600 string_copy(file->old.name, file->new.name);
5601 file = NULL;
5602 }
5604 if (io_error(&io)) {
5605 error_out:
5606 io_done(&io);
5607 return FALSE;
5608 }
5610 if (!view->line[view->lines - 1].data)
5611 add_line_data(view, NULL, LINE_STAT_NONE);
5613 io_done(&io);
5614 return TRUE;
5615 }
5617 /* Don't show unmerged entries in the staged section. */
5618 static const char *status_diff_index_argv[] = {
5619 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5620 "--cached", "-M", "HEAD", NULL
5621 };
5623 static const char *status_diff_files_argv[] = {
5624 "git", "diff-files", "-z", NULL
5625 };
5627 static const char *status_list_other_argv[] = {
5628 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5629 };
5631 static const char *status_list_no_head_argv[] = {
5632 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5633 };
5635 static const char *update_index_argv[] = {
5636 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5637 };
5639 /* Restore the previous line number to stay in the context or select a
5640 * line with something that can be updated. */
5641 static void
5642 status_restore(struct view *view)
5643 {
5644 if (view->p_lineno >= view->lines)
5645 view->p_lineno = view->lines - 1;
5646 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5647 view->p_lineno++;
5648 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5649 view->p_lineno--;
5651 /* If the above fails, always skip the "On branch" line. */
5652 if (view->p_lineno < view->lines)
5653 view->lineno = view->p_lineno;
5654 else
5655 view->lineno = 1;
5657 if (view->lineno < view->offset)
5658 view->offset = view->lineno;
5659 else if (view->offset + view->height <= view->lineno)
5660 view->offset = view->lineno - view->height + 1;
5662 view->p_restore = FALSE;
5663 }
5665 static void
5666 status_update_onbranch(void)
5667 {
5668 static const char *paths[][2] = {
5669 { "rebase-apply/rebasing", "Rebasing" },
5670 { "rebase-apply/applying", "Applying mailbox" },
5671 { "rebase-apply/", "Rebasing mailbox" },
5672 { "rebase-merge/interactive", "Interactive rebase" },
5673 { "rebase-merge/", "Rebase merge" },
5674 { "MERGE_HEAD", "Merging" },
5675 { "BISECT_LOG", "Bisecting" },
5676 { "HEAD", "On branch" },
5677 };
5678 char buf[SIZEOF_STR];
5679 struct stat stat;
5680 int i;
5682 if (is_initial_commit()) {
5683 string_copy(status_onbranch, "Initial commit");
5684 return;
5685 }
5687 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5688 char *head = opt_head;
5690 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5691 lstat(buf, &stat) < 0)
5692 continue;
5694 if (!*opt_head) {
5695 struct io io;
5697 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5698 io_read_buf(&io, buf, sizeof(buf))) {
5699 head = buf;
5700 if (!prefixcmp(head, "refs/heads/"))
5701 head += STRING_SIZE("refs/heads/");
5702 }
5703 }
5705 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5706 string_copy(status_onbranch, opt_head);
5707 return;
5708 }
5710 string_copy(status_onbranch, "Not currently on any branch");
5711 }
5713 /* First parse staged info using git-diff-index(1), then parse unstaged
5714 * info using git-diff-files(1), and finally untracked files using
5715 * git-ls-files(1). */
5716 static bool
5717 status_open(struct view *view)
5718 {
5719 reset_view(view);
5721 add_line_data(view, NULL, LINE_STAT_HEAD);
5722 status_update_onbranch();
5724 io_run_bg(update_index_argv);
5726 if (is_initial_commit()) {
5727 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5728 return FALSE;
5729 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5730 return FALSE;
5731 }
5733 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5734 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5735 return FALSE;
5737 /* Restore the exact position or use the specialized restore
5738 * mode? */
5739 if (!view->p_restore)
5740 status_restore(view);
5741 return TRUE;
5742 }
5744 static bool
5745 status_draw(struct view *view, struct line *line, unsigned int lineno)
5746 {
5747 struct status *status = line->data;
5748 enum line_type type;
5749 const char *text;
5751 if (!status) {
5752 switch (line->type) {
5753 case LINE_STAT_STAGED:
5754 type = LINE_STAT_SECTION;
5755 text = "Changes to be committed:";
5756 break;
5758 case LINE_STAT_UNSTAGED:
5759 type = LINE_STAT_SECTION;
5760 text = "Changed but not updated:";
5761 break;
5763 case LINE_STAT_UNTRACKED:
5764 type = LINE_STAT_SECTION;
5765 text = "Untracked files:";
5766 break;
5768 case LINE_STAT_NONE:
5769 type = LINE_DEFAULT;
5770 text = " (no files)";
5771 break;
5773 case LINE_STAT_HEAD:
5774 type = LINE_STAT_HEAD;
5775 text = status_onbranch;
5776 break;
5778 default:
5779 return FALSE;
5780 }
5781 } else {
5782 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5784 buf[0] = status->status;
5785 if (draw_text(view, line->type, buf, TRUE))
5786 return TRUE;
5787 type = LINE_DEFAULT;
5788 text = status->new.name;
5789 }
5791 draw_text(view, type, text, TRUE);
5792 return TRUE;
5793 }
5795 static enum request
5796 status_load_error(struct view *view, struct view *stage, const char *path)
5797 {
5798 if (displayed_views() == 2 || display[current_view] != view)
5799 maximize_view(view);
5800 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5801 return REQ_NONE;
5802 }
5804 static enum request
5805 status_enter(struct view *view, struct line *line)
5806 {
5807 struct status *status = line->data;
5808 const char *oldpath = status ? status->old.name : NULL;
5809 /* Diffs for unmerged entries are empty when passing the new
5810 * path, so leave it empty. */
5811 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5812 const char *info;
5813 enum open_flags split;
5814 struct view *stage = VIEW(REQ_VIEW_STAGE);
5816 if (line->type == LINE_STAT_NONE ||
5817 (!status && line[1].type == LINE_STAT_NONE)) {
5818 report("No file to diff");
5819 return REQ_NONE;
5820 }
5822 switch (line->type) {
5823 case LINE_STAT_STAGED:
5824 if (is_initial_commit()) {
5825 const char *no_head_diff_argv[] = {
5826 "git", "diff", "--no-color", "--patch-with-stat",
5827 "--", "/dev/null", newpath, NULL
5828 };
5830 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5831 return status_load_error(view, stage, newpath);
5832 } else {
5833 const char *index_show_argv[] = {
5834 "git", "diff-index", "--root", "--patch-with-stat",
5835 "-C", "-M", "--cached", "HEAD", "--",
5836 oldpath, newpath, NULL
5837 };
5839 if (!prepare_update(stage, index_show_argv, opt_cdup))
5840 return status_load_error(view, stage, newpath);
5841 }
5843 if (status)
5844 info = "Staged changes to %s";
5845 else
5846 info = "Staged changes";
5847 break;
5849 case LINE_STAT_UNSTAGED:
5850 {
5851 const char *files_show_argv[] = {
5852 "git", "diff-files", "--root", "--patch-with-stat",
5853 "-C", "-M", "--", oldpath, newpath, NULL
5854 };
5856 if (!prepare_update(stage, files_show_argv, opt_cdup))
5857 return status_load_error(view, stage, newpath);
5858 if (status)
5859 info = "Unstaged changes to %s";
5860 else
5861 info = "Unstaged changes";
5862 break;
5863 }
5864 case LINE_STAT_UNTRACKED:
5865 if (!newpath) {
5866 report("No file to show");
5867 return REQ_NONE;
5868 }
5870 if (!suffixcmp(status->new.name, -1, "/")) {
5871 report("Cannot display a directory");
5872 return REQ_NONE;
5873 }
5875 if (!prepare_update_file(stage, newpath))
5876 return status_load_error(view, stage, newpath);
5877 info = "Untracked file %s";
5878 break;
5880 case LINE_STAT_HEAD:
5881 return REQ_NONE;
5883 default:
5884 die("line type %d not handled in switch", line->type);
5885 }
5887 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5888 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5889 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5890 if (status) {
5891 stage_status = *status;
5892 } else {
5893 memset(&stage_status, 0, sizeof(stage_status));
5894 }
5896 stage_line_type = line->type;
5897 stage_chunks = 0;
5898 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5899 }
5901 return REQ_NONE;
5902 }
5904 static bool
5905 status_exists(struct status *status, enum line_type type)
5906 {
5907 struct view *view = VIEW(REQ_VIEW_STATUS);
5908 unsigned long lineno;
5910 for (lineno = 0; lineno < view->lines; lineno++) {
5911 struct line *line = &view->line[lineno];
5912 struct status *pos = line->data;
5914 if (line->type != type)
5915 continue;
5916 if (!pos && (!status || !status->status) && line[1].data) {
5917 select_view_line(view, lineno);
5918 return TRUE;
5919 }
5920 if (pos && !strcmp(status->new.name, pos->new.name)) {
5921 select_view_line(view, lineno);
5922 return TRUE;
5923 }
5924 }
5926 return FALSE;
5927 }
5930 static bool
5931 status_update_prepare(struct io *io, enum line_type type)
5932 {
5933 const char *staged_argv[] = {
5934 "git", "update-index", "-z", "--index-info", NULL
5935 };
5936 const char *others_argv[] = {
5937 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5938 };
5940 switch (type) {
5941 case LINE_STAT_STAGED:
5942 return io_run(io, IO_WR, opt_cdup, staged_argv);
5944 case LINE_STAT_UNSTAGED:
5945 case LINE_STAT_UNTRACKED:
5946 return io_run(io, IO_WR, opt_cdup, others_argv);
5948 default:
5949 die("line type %d not handled in switch", type);
5950 return FALSE;
5951 }
5952 }
5954 static bool
5955 status_update_write(struct io *io, struct status *status, enum line_type type)
5956 {
5957 char buf[SIZEOF_STR];
5958 size_t bufsize = 0;
5960 switch (type) {
5961 case LINE_STAT_STAGED:
5962 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5963 status->old.mode,
5964 status->old.rev,
5965 status->old.name, 0))
5966 return FALSE;
5967 break;
5969 case LINE_STAT_UNSTAGED:
5970 case LINE_STAT_UNTRACKED:
5971 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5972 return FALSE;
5973 break;
5975 default:
5976 die("line type %d not handled in switch", type);
5977 }
5979 return io_write(io, buf, bufsize);
5980 }
5982 static bool
5983 status_update_file(struct status *status, enum line_type type)
5984 {
5985 struct io io;
5986 bool result;
5988 if (!status_update_prepare(&io, type))
5989 return FALSE;
5991 result = status_update_write(&io, status, type);
5992 return io_done(&io) && result;
5993 }
5995 static bool
5996 status_update_files(struct view *view, struct line *line)
5997 {
5998 char buf[sizeof(view->ref)];
5999 struct io io;
6000 bool result = TRUE;
6001 struct line *pos = view->line + view->lines;
6002 int files = 0;
6003 int file, done;
6004 int cursor_y = -1, cursor_x = -1;
6006 if (!status_update_prepare(&io, line->type))
6007 return FALSE;
6009 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6010 files++;
6012 string_copy(buf, view->ref);
6013 getsyx(cursor_y, cursor_x);
6014 for (file = 0, done = 5; result && file < files; line++, file++) {
6015 int almost_done = file * 100 / files;
6017 if (almost_done > done) {
6018 done = almost_done;
6019 string_format(view->ref, "updating file %u of %u (%d%% done)",
6020 file, files, done);
6021 update_view_title(view);
6022 setsyx(cursor_y, cursor_x);
6023 doupdate();
6024 }
6025 result = status_update_write(&io, line->data, line->type);
6026 }
6027 string_copy(view->ref, buf);
6029 return io_done(&io) && result;
6030 }
6032 static bool
6033 status_update(struct view *view)
6034 {
6035 struct line *line = &view->line[view->lineno];
6037 assert(view->lines);
6039 if (!line->data) {
6040 /* This should work even for the "On branch" line. */
6041 if (line < view->line + view->lines && !line[1].data) {
6042 report("Nothing to update");
6043 return FALSE;
6044 }
6046 if (!status_update_files(view, line + 1)) {
6047 report("Failed to update file status");
6048 return FALSE;
6049 }
6051 } else if (!status_update_file(line->data, line->type)) {
6052 report("Failed to update file status");
6053 return FALSE;
6054 }
6056 return TRUE;
6057 }
6059 static bool
6060 status_revert(struct status *status, enum line_type type, bool has_none)
6061 {
6062 if (!status || type != LINE_STAT_UNSTAGED) {
6063 if (type == LINE_STAT_STAGED) {
6064 report("Cannot revert changes to staged files");
6065 } else if (type == LINE_STAT_UNTRACKED) {
6066 report("Cannot revert changes to untracked files");
6067 } else if (has_none) {
6068 report("Nothing to revert");
6069 } else {
6070 report("Cannot revert changes to multiple files");
6071 }
6073 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6074 char mode[10] = "100644";
6075 const char *reset_argv[] = {
6076 "git", "update-index", "--cacheinfo", mode,
6077 status->old.rev, status->old.name, NULL
6078 };
6079 const char *checkout_argv[] = {
6080 "git", "checkout", "--", status->old.name, NULL
6081 };
6083 if (status->status == 'U') {
6084 string_format(mode, "%5o", status->old.mode);
6086 if (status->old.mode == 0 && status->new.mode == 0) {
6087 reset_argv[2] = "--force-remove";
6088 reset_argv[3] = status->old.name;
6089 reset_argv[4] = NULL;
6090 }
6092 if (!io_run_fg(reset_argv, opt_cdup))
6093 return FALSE;
6094 if (status->old.mode == 0 && status->new.mode == 0)
6095 return TRUE;
6096 }
6098 return io_run_fg(checkout_argv, opt_cdup);
6099 }
6101 return FALSE;
6102 }
6104 static enum request
6105 status_request(struct view *view, enum request request, struct line *line)
6106 {
6107 struct status *status = line->data;
6109 switch (request) {
6110 case REQ_STATUS_UPDATE:
6111 if (!status_update(view))
6112 return REQ_NONE;
6113 break;
6115 case REQ_STATUS_REVERT:
6116 if (!status_revert(status, line->type, status_has_none(view, line)))
6117 return REQ_NONE;
6118 break;
6120 case REQ_STATUS_MERGE:
6121 if (!status || status->status != 'U') {
6122 report("Merging only possible for files with unmerged status ('U').");
6123 return REQ_NONE;
6124 }
6125 open_mergetool(status->new.name);
6126 break;
6128 case REQ_EDIT:
6129 if (!status)
6130 return request;
6131 if (status->status == 'D') {
6132 report("File has been deleted.");
6133 return REQ_NONE;
6134 }
6136 open_editor(status->new.name);
6137 break;
6139 case REQ_VIEW_BLAME:
6140 if (status)
6141 opt_ref[0] = 0;
6142 return request;
6144 case REQ_ENTER:
6145 /* After returning the status view has been split to
6146 * show the stage view. No further reloading is
6147 * necessary. */
6148 return status_enter(view, line);
6150 case REQ_REFRESH:
6151 /* Simply reload the view. */
6152 break;
6154 default:
6155 return request;
6156 }
6158 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6160 return REQ_NONE;
6161 }
6163 static void
6164 status_select(struct view *view, struct line *line)
6165 {
6166 struct status *status = line->data;
6167 char file[SIZEOF_STR] = "all files";
6168 const char *text;
6169 const char *key;
6171 if (status && !string_format(file, "'%s'", status->new.name))
6172 return;
6174 if (!status && line[1].type == LINE_STAT_NONE)
6175 line++;
6177 switch (line->type) {
6178 case LINE_STAT_STAGED:
6179 text = "Press %s to unstage %s for commit";
6180 break;
6182 case LINE_STAT_UNSTAGED:
6183 text = "Press %s to stage %s for commit";
6184 break;
6186 case LINE_STAT_UNTRACKED:
6187 text = "Press %s to stage %s for addition";
6188 break;
6190 case LINE_STAT_HEAD:
6191 case LINE_STAT_NONE:
6192 text = "Nothing to update";
6193 break;
6195 default:
6196 die("line type %d not handled in switch", line->type);
6197 }
6199 if (status && status->status == 'U') {
6200 text = "Press %s to resolve conflict in %s";
6201 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6203 } else {
6204 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6205 }
6207 string_format(view->ref, text, key, file);
6208 if (status)
6209 string_copy(opt_file, status->new.name);
6210 }
6212 static bool
6213 status_grep(struct view *view, struct line *line)
6214 {
6215 struct status *status = line->data;
6217 if (status) {
6218 const char buf[2] = { status->status, 0 };
6219 const char *text[] = { status->new.name, buf, NULL };
6221 return grep_text(view, text);
6222 }
6224 return FALSE;
6225 }
6227 static struct view_ops status_ops = {
6228 "file",
6229 NULL,
6230 status_open,
6231 NULL,
6232 status_draw,
6233 status_request,
6234 status_grep,
6235 status_select,
6236 };
6239 static bool
6240 stage_diff_write(struct io *io, struct line *line, struct line *end)
6241 {
6242 while (line < end) {
6243 if (!io_write(io, line->data, strlen(line->data)) ||
6244 !io_write(io, "\n", 1))
6245 return FALSE;
6246 line++;
6247 if (line->type == LINE_DIFF_CHUNK ||
6248 line->type == LINE_DIFF_HEADER)
6249 break;
6250 }
6252 return TRUE;
6253 }
6255 static struct line *
6256 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6257 {
6258 for (; view->line < line; line--)
6259 if (line->type == type)
6260 return line;
6262 return NULL;
6263 }
6265 static bool
6266 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6267 {
6268 const char *apply_argv[SIZEOF_ARG] = {
6269 "git", "apply", "--whitespace=nowarn", NULL
6270 };
6271 struct line *diff_hdr;
6272 struct io io;
6273 int argc = 3;
6275 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6276 if (!diff_hdr)
6277 return FALSE;
6279 if (!revert)
6280 apply_argv[argc++] = "--cached";
6281 if (revert || stage_line_type == LINE_STAT_STAGED)
6282 apply_argv[argc++] = "-R";
6283 apply_argv[argc++] = "-";
6284 apply_argv[argc++] = NULL;
6285 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6286 return FALSE;
6288 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6289 !stage_diff_write(&io, chunk, view->line + view->lines))
6290 chunk = NULL;
6292 io_done(&io);
6293 io_run_bg(update_index_argv);
6295 return chunk ? TRUE : FALSE;
6296 }
6298 static bool
6299 stage_update(struct view *view, struct line *line)
6300 {
6301 struct line *chunk = NULL;
6303 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6304 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6306 if (chunk) {
6307 if (!stage_apply_chunk(view, chunk, FALSE)) {
6308 report("Failed to apply chunk");
6309 return FALSE;
6310 }
6312 } else if (!stage_status.status) {
6313 view = VIEW(REQ_VIEW_STATUS);
6315 for (line = view->line; line < view->line + view->lines; line++)
6316 if (line->type == stage_line_type)
6317 break;
6319 if (!status_update_files(view, line + 1)) {
6320 report("Failed to update files");
6321 return FALSE;
6322 }
6324 } else if (!status_update_file(&stage_status, stage_line_type)) {
6325 report("Failed to update file");
6326 return FALSE;
6327 }
6329 return TRUE;
6330 }
6332 static bool
6333 stage_revert(struct view *view, struct line *line)
6334 {
6335 struct line *chunk = NULL;
6337 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6338 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6340 if (chunk) {
6341 if (!prompt_yesno("Are you sure you want to revert changes?"))
6342 return FALSE;
6344 if (!stage_apply_chunk(view, chunk, TRUE)) {
6345 report("Failed to revert chunk");
6346 return FALSE;
6347 }
6348 return TRUE;
6350 } else {
6351 return status_revert(stage_status.status ? &stage_status : NULL,
6352 stage_line_type, FALSE);
6353 }
6354 }
6357 static void
6358 stage_next(struct view *view, struct line *line)
6359 {
6360 int i;
6362 if (!stage_chunks) {
6363 for (line = view->line; line < view->line + view->lines; line++) {
6364 if (line->type != LINE_DIFF_CHUNK)
6365 continue;
6367 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6368 report("Allocation failure");
6369 return;
6370 }
6372 stage_chunk[stage_chunks++] = line - view->line;
6373 }
6374 }
6376 for (i = 0; i < stage_chunks; i++) {
6377 if (stage_chunk[i] > view->lineno) {
6378 do_scroll_view(view, stage_chunk[i] - view->lineno);
6379 report("Chunk %d of %d", i + 1, stage_chunks);
6380 return;
6381 }
6382 }
6384 report("No next chunk found");
6385 }
6387 static enum request
6388 stage_request(struct view *view, enum request request, struct line *line)
6389 {
6390 switch (request) {
6391 case REQ_STATUS_UPDATE:
6392 if (!stage_update(view, line))
6393 return REQ_NONE;
6394 break;
6396 case REQ_STATUS_REVERT:
6397 if (!stage_revert(view, line))
6398 return REQ_NONE;
6399 break;
6401 case REQ_STAGE_NEXT:
6402 if (stage_line_type == LINE_STAT_UNTRACKED) {
6403 report("File is untracked; press %s to add",
6404 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6405 return REQ_NONE;
6406 }
6407 stage_next(view, line);
6408 return REQ_NONE;
6410 case REQ_EDIT:
6411 if (!stage_status.new.name[0])
6412 return request;
6413 if (stage_status.status == 'D') {
6414 report("File has been deleted.");
6415 return REQ_NONE;
6416 }
6418 open_editor(stage_status.new.name);
6419 break;
6421 case REQ_REFRESH:
6422 /* Reload everything ... */
6423 break;
6425 case REQ_VIEW_BLAME:
6426 if (stage_status.new.name[0]) {
6427 string_copy(opt_file, stage_status.new.name);
6428 opt_ref[0] = 0;
6429 }
6430 return request;
6432 case REQ_ENTER:
6433 return pager_request(view, request, line);
6435 default:
6436 return request;
6437 }
6439 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6440 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6442 /* Check whether the staged entry still exists, and close the
6443 * stage view if it doesn't. */
6444 if (!status_exists(&stage_status, stage_line_type)) {
6445 status_restore(VIEW(REQ_VIEW_STATUS));
6446 return REQ_VIEW_CLOSE;
6447 }
6449 if (stage_line_type == LINE_STAT_UNTRACKED) {
6450 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6451 report("Cannot display a directory");
6452 return REQ_NONE;
6453 }
6455 if (!prepare_update_file(view, stage_status.new.name)) {
6456 report("Failed to open file: %s", strerror(errno));
6457 return REQ_NONE;
6458 }
6459 }
6460 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6462 return REQ_NONE;
6463 }
6465 static struct view_ops stage_ops = {
6466 "line",
6467 NULL,
6468 NULL,
6469 pager_read,
6470 pager_draw,
6471 stage_request,
6472 pager_grep,
6473 pager_select,
6474 };
6477 /*
6478 * Revision graph
6479 */
6481 struct commit {
6482 char id[SIZEOF_REV]; /* SHA1 ID. */
6483 char title[128]; /* First line of the commit message. */
6484 const char *author; /* Author of the commit. */
6485 struct time time; /* Date from the author ident. */
6486 struct ref_list *refs; /* Repository references. */
6487 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6488 size_t graph_size; /* The width of the graph array. */
6489 bool has_parents; /* Rewritten --parents seen. */
6490 };
6492 /* Size of rev graph with no "padding" columns */
6493 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6495 struct rev_graph {
6496 struct rev_graph *prev, *next, *parents;
6497 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6498 size_t size;
6499 struct commit *commit;
6500 size_t pos;
6501 unsigned int boundary:1;
6502 };
6504 /* Parents of the commit being visualized. */
6505 static struct rev_graph graph_parents[4];
6507 /* The current stack of revisions on the graph. */
6508 static struct rev_graph graph_stacks[4] = {
6509 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6510 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6511 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6512 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6513 };
6515 static inline bool
6516 graph_parent_is_merge(struct rev_graph *graph)
6517 {
6518 return graph->parents->size > 1;
6519 }
6521 static inline void
6522 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6523 {
6524 struct commit *commit = graph->commit;
6526 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6527 commit->graph[commit->graph_size++] = symbol;
6528 }
6530 static void
6531 clear_rev_graph(struct rev_graph *graph)
6532 {
6533 graph->boundary = 0;
6534 graph->size = graph->pos = 0;
6535 graph->commit = NULL;
6536 memset(graph->parents, 0, sizeof(*graph->parents));
6537 }
6539 static void
6540 done_rev_graph(struct rev_graph *graph)
6541 {
6542 if (graph_parent_is_merge(graph) &&
6543 graph->pos < graph->size - 1 &&
6544 graph->next->size == graph->size + graph->parents->size - 1) {
6545 size_t i = graph->pos + graph->parents->size - 1;
6547 graph->commit->graph_size = i * 2;
6548 while (i < graph->next->size - 1) {
6549 append_to_rev_graph(graph, ' ');
6550 append_to_rev_graph(graph, '\\');
6551 i++;
6552 }
6553 }
6555 clear_rev_graph(graph);
6556 }
6558 static void
6559 push_rev_graph(struct rev_graph *graph, const char *parent)
6560 {
6561 int i;
6563 /* "Collapse" duplicate parents lines.
6564 *
6565 * FIXME: This needs to also update update the drawn graph but
6566 * for now it just serves as a method for pruning graph lines. */
6567 for (i = 0; i < graph->size; i++)
6568 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6569 return;
6571 if (graph->size < SIZEOF_REVITEMS) {
6572 string_copy_rev(graph->rev[graph->size++], parent);
6573 }
6574 }
6576 static chtype
6577 get_rev_graph_symbol(struct rev_graph *graph)
6578 {
6579 chtype symbol;
6581 if (graph->boundary)
6582 symbol = REVGRAPH_BOUND;
6583 else if (graph->parents->size == 0)
6584 symbol = REVGRAPH_INIT;
6585 else if (graph_parent_is_merge(graph))
6586 symbol = REVGRAPH_MERGE;
6587 else if (graph->pos >= graph->size)
6588 symbol = REVGRAPH_BRANCH;
6589 else
6590 symbol = REVGRAPH_COMMIT;
6592 return symbol;
6593 }
6595 static void
6596 draw_rev_graph(struct rev_graph *graph)
6597 {
6598 struct rev_filler {
6599 chtype separator, line;
6600 };
6601 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6602 static struct rev_filler fillers[] = {
6603 { ' ', '|' },
6604 { '`', '.' },
6605 { '\'', ' ' },
6606 { '/', ' ' },
6607 };
6608 chtype symbol = get_rev_graph_symbol(graph);
6609 struct rev_filler *filler;
6610 size_t i;
6612 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6613 filler = &fillers[DEFAULT];
6615 for (i = 0; i < graph->pos; i++) {
6616 append_to_rev_graph(graph, filler->line);
6617 if (graph_parent_is_merge(graph->prev) &&
6618 graph->prev->pos == i)
6619 filler = &fillers[RSHARP];
6621 append_to_rev_graph(graph, filler->separator);
6622 }
6624 /* Place the symbol for this revision. */
6625 append_to_rev_graph(graph, symbol);
6627 if (graph->prev->size > graph->size)
6628 filler = &fillers[RDIAG];
6629 else
6630 filler = &fillers[DEFAULT];
6632 i++;
6634 for (; i < graph->size; i++) {
6635 append_to_rev_graph(graph, filler->separator);
6636 append_to_rev_graph(graph, filler->line);
6637 if (graph_parent_is_merge(graph->prev) &&
6638 i < graph->prev->pos + graph->parents->size)
6639 filler = &fillers[RSHARP];
6640 if (graph->prev->size > graph->size)
6641 filler = &fillers[LDIAG];
6642 }
6644 if (graph->prev->size > graph->size) {
6645 append_to_rev_graph(graph, filler->separator);
6646 if (filler->line != ' ')
6647 append_to_rev_graph(graph, filler->line);
6648 }
6649 }
6651 /* Prepare the next rev graph */
6652 static void
6653 prepare_rev_graph(struct rev_graph *graph)
6654 {
6655 size_t i;
6657 /* First, traverse all lines of revisions up to the active one. */
6658 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6659 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6660 break;
6662 push_rev_graph(graph->next, graph->rev[graph->pos]);
6663 }
6665 /* Interleave the new revision parent(s). */
6666 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6667 push_rev_graph(graph->next, graph->parents->rev[i]);
6669 /* Lastly, put any remaining revisions. */
6670 for (i = graph->pos + 1; i < graph->size; i++)
6671 push_rev_graph(graph->next, graph->rev[i]);
6672 }
6674 static void
6675 update_rev_graph(struct view *view, struct rev_graph *graph)
6676 {
6677 /* If this is the finalizing update ... */
6678 if (graph->commit)
6679 prepare_rev_graph(graph);
6681 /* Graph visualization needs a one rev look-ahead,
6682 * so the first update doesn't visualize anything. */
6683 if (!graph->prev->commit)
6684 return;
6686 if (view->lines > 2)
6687 view->line[view->lines - 3].dirty = 1;
6688 if (view->lines > 1)
6689 view->line[view->lines - 2].dirty = 1;
6690 draw_rev_graph(graph->prev);
6691 done_rev_graph(graph->prev->prev);
6692 }
6695 /*
6696 * Main view backend
6697 */
6699 static const char *main_argv[SIZEOF_ARG] = {
6700 "git", "log", "--no-color", "--pretty=raw", "--parents",
6701 "--topo-order", "%(head)", NULL
6702 };
6704 static bool
6705 main_draw(struct view *view, struct line *line, unsigned int lineno)
6706 {
6707 struct commit *commit = line->data;
6709 if (!commit->author)
6710 return FALSE;
6712 if (opt_date && draw_date(view, &commit->time))
6713 return TRUE;
6715 if (opt_author && draw_author(view, commit->author))
6716 return TRUE;
6718 if (opt_rev_graph && commit->graph_size &&
6719 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6720 return TRUE;
6722 if (opt_show_refs && commit->refs) {
6723 size_t i;
6725 for (i = 0; i < commit->refs->size; i++) {
6726 struct ref *ref = commit->refs->refs[i];
6727 enum line_type type;
6729 if (ref->head)
6730 type = LINE_MAIN_HEAD;
6731 else if (ref->ltag)
6732 type = LINE_MAIN_LOCAL_TAG;
6733 else if (ref->tag)
6734 type = LINE_MAIN_TAG;
6735 else if (ref->tracked)
6736 type = LINE_MAIN_TRACKED;
6737 else if (ref->remote)
6738 type = LINE_MAIN_REMOTE;
6739 else
6740 type = LINE_MAIN_REF;
6742 if (draw_text(view, type, "[", TRUE) ||
6743 draw_text(view, type, ref->name, TRUE) ||
6744 draw_text(view, type, "]", TRUE))
6745 return TRUE;
6747 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6748 return TRUE;
6749 }
6750 }
6752 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6753 return TRUE;
6754 }
6756 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6757 static bool
6758 main_read(struct view *view, char *line)
6759 {
6760 static struct rev_graph *graph = graph_stacks;
6761 enum line_type type;
6762 struct commit *commit;
6764 if (!line) {
6765 int i;
6767 if (!view->lines && !view->prev)
6768 die("No revisions match the given arguments.");
6769 if (view->lines > 0) {
6770 commit = view->line[view->lines - 1].data;
6771 view->line[view->lines - 1].dirty = 1;
6772 if (!commit->author) {
6773 view->lines--;
6774 free(commit);
6775 graph->commit = NULL;
6776 }
6777 }
6778 update_rev_graph(view, graph);
6780 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6781 clear_rev_graph(&graph_stacks[i]);
6782 return TRUE;
6783 }
6785 type = get_line_type(line);
6786 if (type == LINE_COMMIT) {
6787 commit = calloc(1, sizeof(struct commit));
6788 if (!commit)
6789 return FALSE;
6791 line += STRING_SIZE("commit ");
6792 if (*line == '-') {
6793 graph->boundary = 1;
6794 line++;
6795 }
6797 string_copy_rev(commit->id, line);
6798 commit->refs = get_ref_list(commit->id);
6799 graph->commit = commit;
6800 add_line_data(view, commit, LINE_MAIN_COMMIT);
6802 while ((line = strchr(line, ' '))) {
6803 line++;
6804 push_rev_graph(graph->parents, line);
6805 commit->has_parents = TRUE;
6806 }
6807 return TRUE;
6808 }
6810 if (!view->lines)
6811 return TRUE;
6812 commit = view->line[view->lines - 1].data;
6814 switch (type) {
6815 case LINE_PARENT:
6816 if (commit->has_parents)
6817 break;
6818 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6819 break;
6821 case LINE_AUTHOR:
6822 parse_author_line(line + STRING_SIZE("author "),
6823 &commit->author, &commit->time);
6824 update_rev_graph(view, graph);
6825 graph = graph->next;
6826 break;
6828 default:
6829 /* Fill in the commit title if it has not already been set. */
6830 if (commit->title[0])
6831 break;
6833 /* Require titles to start with a non-space character at the
6834 * offset used by git log. */
6835 if (strncmp(line, " ", 4))
6836 break;
6837 line += 4;
6838 /* Well, if the title starts with a whitespace character,
6839 * try to be forgiving. Otherwise we end up with no title. */
6840 while (isspace(*line))
6841 line++;
6842 if (*line == '\0')
6843 break;
6844 /* FIXME: More graceful handling of titles; append "..." to
6845 * shortened titles, etc. */
6847 string_expand(commit->title, sizeof(commit->title), line, 1);
6848 view->line[view->lines - 1].dirty = 1;
6849 }
6851 return TRUE;
6852 }
6854 static enum request
6855 main_request(struct view *view, enum request request, struct line *line)
6856 {
6857 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6859 switch (request) {
6860 case REQ_ENTER:
6861 open_view(view, REQ_VIEW_DIFF, flags);
6862 break;
6863 case REQ_REFRESH:
6864 load_refs();
6865 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6866 break;
6867 default:
6868 return request;
6869 }
6871 return REQ_NONE;
6872 }
6874 static bool
6875 grep_refs(struct ref_list *list, regex_t *regex)
6876 {
6877 regmatch_t pmatch;
6878 size_t i;
6880 if (!opt_show_refs || !list)
6881 return FALSE;
6883 for (i = 0; i < list->size; i++) {
6884 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6885 return TRUE;
6886 }
6888 return FALSE;
6889 }
6891 static bool
6892 main_grep(struct view *view, struct line *line)
6893 {
6894 struct commit *commit = line->data;
6895 const char *text[] = {
6896 commit->title,
6897 opt_author ? commit->author : "",
6898 mkdate(&commit->time, opt_date),
6899 NULL
6900 };
6902 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6903 }
6905 static void
6906 main_select(struct view *view, struct line *line)
6907 {
6908 struct commit *commit = line->data;
6910 string_copy_rev(view->ref, commit->id);
6911 string_copy_rev(ref_commit, view->ref);
6912 }
6914 static struct view_ops main_ops = {
6915 "commit",
6916 main_argv,
6917 NULL,
6918 main_read,
6919 main_draw,
6920 main_request,
6921 main_grep,
6922 main_select,
6923 };
6926 /*
6927 * Status management
6928 */
6930 /* Whether or not the curses interface has been initialized. */
6931 static bool cursed = FALSE;
6933 /* Terminal hacks and workarounds. */
6934 static bool use_scroll_redrawwin;
6935 static bool use_scroll_status_wclear;
6937 /* The status window is used for polling keystrokes. */
6938 static WINDOW *status_win;
6940 /* Reading from the prompt? */
6941 static bool input_mode = FALSE;
6943 static bool status_empty = FALSE;
6945 /* Update status and title window. */
6946 static void
6947 report(const char *msg, ...)
6948 {
6949 struct view *view = display[current_view];
6951 if (input_mode)
6952 return;
6954 if (!view) {
6955 char buf[SIZEOF_STR];
6956 va_list args;
6958 va_start(args, msg);
6959 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6960 buf[sizeof(buf) - 1] = 0;
6961 buf[sizeof(buf) - 2] = '.';
6962 buf[sizeof(buf) - 3] = '.';
6963 buf[sizeof(buf) - 4] = '.';
6964 }
6965 va_end(args);
6966 die("%s", buf);
6967 }
6969 if (!status_empty || *msg) {
6970 va_list args;
6972 va_start(args, msg);
6974 wmove(status_win, 0, 0);
6975 if (view->has_scrolled && use_scroll_status_wclear)
6976 wclear(status_win);
6977 if (*msg) {
6978 vwprintw(status_win, msg, args);
6979 status_empty = FALSE;
6980 } else {
6981 status_empty = TRUE;
6982 }
6983 wclrtoeol(status_win);
6984 wnoutrefresh(status_win);
6986 va_end(args);
6987 }
6989 update_view_title(view);
6990 }
6992 static void
6993 init_display(void)
6994 {
6995 const char *term;
6996 int x, y;
6998 /* Initialize the curses library */
6999 if (isatty(STDIN_FILENO)) {
7000 cursed = !!initscr();
7001 opt_tty = stdin;
7002 } else {
7003 /* Leave stdin and stdout alone when acting as a pager. */
7004 opt_tty = fopen("/dev/tty", "r+");
7005 if (!opt_tty)
7006 die("Failed to open /dev/tty");
7007 cursed = !!newterm(NULL, opt_tty, opt_tty);
7008 }
7010 if (!cursed)
7011 die("Failed to initialize curses");
7013 nonl(); /* Disable conversion and detect newlines from input. */
7014 cbreak(); /* Take input chars one at a time, no wait for \n */
7015 noecho(); /* Don't echo input */
7016 leaveok(stdscr, FALSE);
7018 if (has_colors())
7019 init_colors();
7021 getmaxyx(stdscr, y, x);
7022 status_win = newwin(1, 0, y - 1, 0);
7023 if (!status_win)
7024 die("Failed to create status window");
7026 /* Enable keyboard mapping */
7027 keypad(status_win, TRUE);
7028 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7030 TABSIZE = opt_tab_size;
7032 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7033 if (term && !strcmp(term, "gnome-terminal")) {
7034 /* In the gnome-terminal-emulator, the message from
7035 * scrolling up one line when impossible followed by
7036 * scrolling down one line causes corruption of the
7037 * status line. This is fixed by calling wclear. */
7038 use_scroll_status_wclear = TRUE;
7039 use_scroll_redrawwin = FALSE;
7041 } else if (term && !strcmp(term, "xrvt-xpm")) {
7042 /* No problems with full optimizations in xrvt-(unicode)
7043 * and aterm. */
7044 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7046 } else {
7047 /* When scrolling in (u)xterm the last line in the
7048 * scrolling direction will update slowly. */
7049 use_scroll_redrawwin = TRUE;
7050 use_scroll_status_wclear = FALSE;
7051 }
7052 }
7054 static int
7055 get_input(int prompt_position)
7056 {
7057 struct view *view;
7058 int i, key, cursor_y, cursor_x;
7059 bool loading = FALSE;
7061 if (prompt_position)
7062 input_mode = TRUE;
7064 while (TRUE) {
7065 foreach_view (view, i) {
7066 update_view(view);
7067 if (view_is_displayed(view) && view->has_scrolled &&
7068 use_scroll_redrawwin)
7069 redrawwin(view->win);
7070 view->has_scrolled = FALSE;
7071 if (view->pipe)
7072 loading = TRUE;
7073 }
7075 /* Update the cursor position. */
7076 if (prompt_position) {
7077 getbegyx(status_win, cursor_y, cursor_x);
7078 cursor_x = prompt_position;
7079 } else {
7080 view = display[current_view];
7081 getbegyx(view->win, cursor_y, cursor_x);
7082 cursor_x = view->width - 1;
7083 cursor_y += view->lineno - view->offset;
7084 }
7085 setsyx(cursor_y, cursor_x);
7087 /* Refresh, accept single keystroke of input */
7088 doupdate();
7089 nodelay(status_win, loading);
7090 key = wgetch(status_win);
7092 /* wgetch() with nodelay() enabled returns ERR when
7093 * there's no input. */
7094 if (key == ERR) {
7096 } else if (key == KEY_RESIZE) {
7097 int height, width;
7099 getmaxyx(stdscr, height, width);
7101 wresize(status_win, 1, width);
7102 mvwin(status_win, height - 1, 0);
7103 wnoutrefresh(status_win);
7104 resize_display();
7105 redraw_display(TRUE);
7107 } else {
7108 input_mode = FALSE;
7109 return key;
7110 }
7111 }
7112 }
7114 static char *
7115 prompt_input(const char *prompt, input_handler handler, void *data)
7116 {
7117 enum input_status status = INPUT_OK;
7118 static char buf[SIZEOF_STR];
7119 size_t pos = 0;
7121 buf[pos] = 0;
7123 while (status == INPUT_OK || status == INPUT_SKIP) {
7124 int key;
7126 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7127 wclrtoeol(status_win);
7129 key = get_input(pos + 1);
7130 switch (key) {
7131 case KEY_RETURN:
7132 case KEY_ENTER:
7133 case '\n':
7134 status = pos ? INPUT_STOP : INPUT_CANCEL;
7135 break;
7137 case KEY_BACKSPACE:
7138 if (pos > 0)
7139 buf[--pos] = 0;
7140 else
7141 status = INPUT_CANCEL;
7142 break;
7144 case KEY_ESC:
7145 status = INPUT_CANCEL;
7146 break;
7148 default:
7149 if (pos >= sizeof(buf)) {
7150 report("Input string too long");
7151 return NULL;
7152 }
7154 status = handler(data, buf, key);
7155 if (status == INPUT_OK)
7156 buf[pos++] = (char) key;
7157 }
7158 }
7160 /* Clear the status window */
7161 status_empty = FALSE;
7162 report("");
7164 if (status == INPUT_CANCEL)
7165 return NULL;
7167 buf[pos++] = 0;
7169 return buf;
7170 }
7172 static enum input_status
7173 prompt_yesno_handler(void *data, char *buf, int c)
7174 {
7175 if (c == 'y' || c == 'Y')
7176 return INPUT_STOP;
7177 if (c == 'n' || c == 'N')
7178 return INPUT_CANCEL;
7179 return INPUT_SKIP;
7180 }
7182 static bool
7183 prompt_yesno(const char *prompt)
7184 {
7185 char prompt2[SIZEOF_STR];
7187 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7188 return FALSE;
7190 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7191 }
7193 static enum input_status
7194 read_prompt_handler(void *data, char *buf, int c)
7195 {
7196 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7197 }
7199 static char *
7200 read_prompt(const char *prompt)
7201 {
7202 return prompt_input(prompt, read_prompt_handler, NULL);
7203 }
7205 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7206 {
7207 enum input_status status = INPUT_OK;
7208 int size = 0;
7210 while (items[size].text)
7211 size++;
7213 while (status == INPUT_OK) {
7214 const struct menu_item *item = &items[*selected];
7215 int key;
7216 int i;
7218 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7219 prompt, *selected + 1, size);
7220 if (item->hotkey)
7221 wprintw(status_win, "[%c] ", (char) item->hotkey);
7222 wprintw(status_win, "%s", item->text);
7223 wclrtoeol(status_win);
7225 key = get_input(COLS - 1);
7226 switch (key) {
7227 case KEY_RETURN:
7228 case KEY_ENTER:
7229 case '\n':
7230 status = INPUT_STOP;
7231 break;
7233 case KEY_LEFT:
7234 case KEY_UP:
7235 *selected = *selected - 1;
7236 if (*selected < 0)
7237 *selected = size - 1;
7238 break;
7240 case KEY_RIGHT:
7241 case KEY_DOWN:
7242 *selected = (*selected + 1) % size;
7243 break;
7245 case KEY_ESC:
7246 status = INPUT_CANCEL;
7247 break;
7249 default:
7250 for (i = 0; items[i].text; i++)
7251 if (items[i].hotkey == key) {
7252 *selected = i;
7253 status = INPUT_STOP;
7254 break;
7255 }
7256 }
7257 }
7259 /* Clear the status window */
7260 status_empty = FALSE;
7261 report("");
7263 return status != INPUT_CANCEL;
7264 }
7266 /*
7267 * Repository properties
7268 */
7270 static struct ref **refs = NULL;
7271 static size_t refs_size = 0;
7272 static struct ref *refs_head = NULL;
7274 static struct ref_list **ref_lists = NULL;
7275 static size_t ref_lists_size = 0;
7277 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7278 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7279 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7281 static int
7282 compare_refs(const void *ref1_, const void *ref2_)
7283 {
7284 const struct ref *ref1 = *(const struct ref **)ref1_;
7285 const struct ref *ref2 = *(const struct ref **)ref2_;
7287 if (ref1->tag != ref2->tag)
7288 return ref2->tag - ref1->tag;
7289 if (ref1->ltag != ref2->ltag)
7290 return ref2->ltag - ref2->ltag;
7291 if (ref1->head != ref2->head)
7292 return ref2->head - ref1->head;
7293 if (ref1->tracked != ref2->tracked)
7294 return ref2->tracked - ref1->tracked;
7295 if (ref1->remote != ref2->remote)
7296 return ref2->remote - ref1->remote;
7297 return strcmp(ref1->name, ref2->name);
7298 }
7300 static void
7301 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7302 {
7303 size_t i;
7305 for (i = 0; i < refs_size; i++)
7306 if (!visitor(data, refs[i]))
7307 break;
7308 }
7310 static struct ref *
7311 get_ref_head()
7312 {
7313 return refs_head;
7314 }
7316 static struct ref_list *
7317 get_ref_list(const char *id)
7318 {
7319 struct ref_list *list;
7320 size_t i;
7322 for (i = 0; i < ref_lists_size; i++)
7323 if (!strcmp(id, ref_lists[i]->id))
7324 return ref_lists[i];
7326 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7327 return NULL;
7328 list = calloc(1, sizeof(*list));
7329 if (!list)
7330 return NULL;
7332 for (i = 0; i < refs_size; i++) {
7333 if (!strcmp(id, refs[i]->id) &&
7334 realloc_refs_list(&list->refs, list->size, 1))
7335 list->refs[list->size++] = refs[i];
7336 }
7338 if (!list->refs) {
7339 free(list);
7340 return NULL;
7341 }
7343 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7344 ref_lists[ref_lists_size++] = list;
7345 return list;
7346 }
7348 static int
7349 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7350 {
7351 struct ref *ref = NULL;
7352 bool tag = FALSE;
7353 bool ltag = FALSE;
7354 bool remote = FALSE;
7355 bool tracked = FALSE;
7356 bool head = FALSE;
7357 int from = 0, to = refs_size - 1;
7359 if (!prefixcmp(name, "refs/tags/")) {
7360 if (!suffixcmp(name, namelen, "^{}")) {
7361 namelen -= 3;
7362 name[namelen] = 0;
7363 } else {
7364 ltag = TRUE;
7365 }
7367 tag = TRUE;
7368 namelen -= STRING_SIZE("refs/tags/");
7369 name += STRING_SIZE("refs/tags/");
7371 } else if (!prefixcmp(name, "refs/remotes/")) {
7372 remote = TRUE;
7373 namelen -= STRING_SIZE("refs/remotes/");
7374 name += STRING_SIZE("refs/remotes/");
7375 tracked = !strcmp(opt_remote, name);
7377 } else if (!prefixcmp(name, "refs/heads/")) {
7378 namelen -= STRING_SIZE("refs/heads/");
7379 name += STRING_SIZE("refs/heads/");
7380 if (!strncmp(opt_head, name, namelen))
7381 return OK;
7383 } else if (!strcmp(name, "HEAD")) {
7384 head = TRUE;
7385 if (*opt_head) {
7386 namelen = strlen(opt_head);
7387 name = opt_head;
7388 }
7389 }
7391 /* If we are reloading or it's an annotated tag, replace the
7392 * previous SHA1 with the resolved commit id; relies on the fact
7393 * git-ls-remote lists the commit id of an annotated tag right
7394 * before the commit id it points to. */
7395 while (from <= to) {
7396 size_t pos = (to + from) / 2;
7397 int cmp = strcmp(name, refs[pos]->name);
7399 if (!cmp) {
7400 ref = refs[pos];
7401 break;
7402 }
7404 if (cmp < 0)
7405 to = pos - 1;
7406 else
7407 from = pos + 1;
7408 }
7410 if (!ref) {
7411 if (!realloc_refs(&refs, refs_size, 1))
7412 return ERR;
7413 ref = calloc(1, sizeof(*ref) + namelen);
7414 if (!ref)
7415 return ERR;
7416 memmove(refs + from + 1, refs + from,
7417 (refs_size - from) * sizeof(*refs));
7418 refs[from] = ref;
7419 strncpy(ref->name, name, namelen);
7420 refs_size++;
7421 }
7423 ref->head = head;
7424 ref->tag = tag;
7425 ref->ltag = ltag;
7426 ref->remote = remote;
7427 ref->tracked = tracked;
7428 string_copy_rev(ref->id, id);
7430 if (head)
7431 refs_head = ref;
7432 return OK;
7433 }
7435 static int
7436 load_refs(void)
7437 {
7438 const char *head_argv[] = {
7439 "git", "symbolic-ref", "HEAD", NULL
7440 };
7441 static const char *ls_remote_argv[SIZEOF_ARG] = {
7442 "git", "ls-remote", opt_git_dir, NULL
7443 };
7444 static bool init = FALSE;
7445 size_t i;
7447 if (!init) {
7448 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7449 die("TIG_LS_REMOTE contains too many arguments");
7450 init = TRUE;
7451 }
7453 if (!*opt_git_dir)
7454 return OK;
7456 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7457 !prefixcmp(opt_head, "refs/heads/")) {
7458 char *offset = opt_head + STRING_SIZE("refs/heads/");
7460 memmove(opt_head, offset, strlen(offset) + 1);
7461 }
7463 refs_head = NULL;
7464 for (i = 0; i < refs_size; i++)
7465 refs[i]->id[0] = 0;
7467 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7468 return ERR;
7470 /* Update the ref lists to reflect changes. */
7471 for (i = 0; i < ref_lists_size; i++) {
7472 struct ref_list *list = ref_lists[i];
7473 size_t old, new;
7475 for (old = new = 0; old < list->size; old++)
7476 if (!strcmp(list->id, list->refs[old]->id))
7477 list->refs[new++] = list->refs[old];
7478 list->size = new;
7479 }
7481 return OK;
7482 }
7484 static void
7485 set_remote_branch(const char *name, const char *value, size_t valuelen)
7486 {
7487 if (!strcmp(name, ".remote")) {
7488 string_ncopy(opt_remote, value, valuelen);
7490 } else if (*opt_remote && !strcmp(name, ".merge")) {
7491 size_t from = strlen(opt_remote);
7493 if (!prefixcmp(value, "refs/heads/"))
7494 value += STRING_SIZE("refs/heads/");
7496 if (!string_format_from(opt_remote, &from, "/%s", value))
7497 opt_remote[0] = 0;
7498 }
7499 }
7501 static void
7502 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7503 {
7504 const char *argv[SIZEOF_ARG] = { name, "=" };
7505 int argc = 1 + (cmd == option_set_command);
7506 int error = ERR;
7508 if (!argv_from_string(argv, &argc, value))
7509 config_msg = "Too many option arguments";
7510 else
7511 error = cmd(argc, argv);
7513 if (error == ERR)
7514 warn("Option 'tig.%s': %s", name, config_msg);
7515 }
7517 static bool
7518 set_environment_variable(const char *name, const char *value)
7519 {
7520 size_t len = strlen(name) + 1 + strlen(value) + 1;
7521 char *env = malloc(len);
7523 if (env &&
7524 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7525 putenv(env) == 0)
7526 return TRUE;
7527 free(env);
7528 return FALSE;
7529 }
7531 static void
7532 set_work_tree(const char *value)
7533 {
7534 char cwd[SIZEOF_STR];
7536 if (!getcwd(cwd, sizeof(cwd)))
7537 die("Failed to get cwd path: %s", strerror(errno));
7538 if (chdir(opt_git_dir) < 0)
7539 die("Failed to chdir(%s): %s", strerror(errno));
7540 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7541 die("Failed to get git path: %s", strerror(errno));
7542 if (chdir(cwd) < 0)
7543 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7544 if (chdir(value) < 0)
7545 die("Failed to chdir(%s): %s", value, strerror(errno));
7546 if (!getcwd(cwd, sizeof(cwd)))
7547 die("Failed to get cwd path: %s", strerror(errno));
7548 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7549 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7550 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7551 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7552 opt_is_inside_work_tree = TRUE;
7553 }
7555 static int
7556 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7557 {
7558 if (!strcmp(name, "i18n.commitencoding"))
7559 string_ncopy(opt_encoding, value, valuelen);
7561 else if (!strcmp(name, "core.editor"))
7562 string_ncopy(opt_editor, value, valuelen);
7564 else if (!strcmp(name, "core.worktree"))
7565 set_work_tree(value);
7567 else if (!prefixcmp(name, "tig.color."))
7568 set_repo_config_option(name + 10, value, option_color_command);
7570 else if (!prefixcmp(name, "tig.bind."))
7571 set_repo_config_option(name + 9, value, option_bind_command);
7573 else if (!prefixcmp(name, "tig."))
7574 set_repo_config_option(name + 4, value, option_set_command);
7576 else if (*opt_head && !prefixcmp(name, "branch.") &&
7577 !strncmp(name + 7, opt_head, strlen(opt_head)))
7578 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7580 return OK;
7581 }
7583 static int
7584 load_git_config(void)
7585 {
7586 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7588 return io_run_load(config_list_argv, "=", read_repo_config_option);
7589 }
7591 static int
7592 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7593 {
7594 if (!opt_git_dir[0]) {
7595 string_ncopy(opt_git_dir, name, namelen);
7597 } else if (opt_is_inside_work_tree == -1) {
7598 /* This can be 3 different values depending on the
7599 * version of git being used. If git-rev-parse does not
7600 * understand --is-inside-work-tree it will simply echo
7601 * the option else either "true" or "false" is printed.
7602 * Default to true for the unknown case. */
7603 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7605 } else if (*name == '.') {
7606 string_ncopy(opt_cdup, name, namelen);
7608 } else {
7609 string_ncopy(opt_prefix, name, namelen);
7610 }
7612 return OK;
7613 }
7615 static int
7616 load_repo_info(void)
7617 {
7618 const char *rev_parse_argv[] = {
7619 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7620 "--show-cdup", "--show-prefix", NULL
7621 };
7623 return io_run_load(rev_parse_argv, "=", read_repo_info);
7624 }
7627 /*
7628 * Main
7629 */
7631 static const char usage[] =
7632 "tig " TIG_VERSION " (" __DATE__ ")\n"
7633 "\n"
7634 "Usage: tig [options] [revs] [--] [paths]\n"
7635 " or: tig show [options] [revs] [--] [paths]\n"
7636 " or: tig blame [rev] path\n"
7637 " or: tig status\n"
7638 " or: tig < [git command output]\n"
7639 "\n"
7640 "Options:\n"
7641 " -v, --version Show version and exit\n"
7642 " -h, --help Show help message and exit";
7644 static void __NORETURN
7645 quit(int sig)
7646 {
7647 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7648 if (cursed)
7649 endwin();
7650 exit(0);
7651 }
7653 static void __NORETURN
7654 die(const char *err, ...)
7655 {
7656 va_list args;
7658 endwin();
7660 va_start(args, err);
7661 fputs("tig: ", stderr);
7662 vfprintf(stderr, err, args);
7663 fputs("\n", stderr);
7664 va_end(args);
7666 exit(1);
7667 }
7669 static void
7670 warn(const char *msg, ...)
7671 {
7672 va_list args;
7674 va_start(args, msg);
7675 fputs("tig warning: ", stderr);
7676 vfprintf(stderr, msg, args);
7677 fputs("\n", stderr);
7678 va_end(args);
7679 }
7681 static enum request
7682 parse_options(int argc, const char *argv[])
7683 {
7684 enum request request = REQ_VIEW_MAIN;
7685 const char *subcommand;
7686 bool seen_dashdash = FALSE;
7687 /* XXX: This is vulnerable to the user overriding options
7688 * required for the main view parser. */
7689 const char *custom_argv[SIZEOF_ARG] = {
7690 "git", "log", "--no-color", "--pretty=raw", "--parents",
7691 "--topo-order", NULL
7692 };
7693 int i, j = 6;
7695 if (!isatty(STDIN_FILENO)) {
7696 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7697 return REQ_VIEW_PAGER;
7698 }
7700 if (argc <= 1)
7701 return REQ_NONE;
7703 subcommand = argv[1];
7704 if (!strcmp(subcommand, "status")) {
7705 if (argc > 2)
7706 warn("ignoring arguments after `%s'", subcommand);
7707 return REQ_VIEW_STATUS;
7709 } else if (!strcmp(subcommand, "blame")) {
7710 if (argc <= 2 || argc > 4)
7711 die("invalid number of options to blame\n\n%s", usage);
7713 i = 2;
7714 if (argc == 4) {
7715 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7716 i++;
7717 }
7719 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7720 return REQ_VIEW_BLAME;
7722 } else if (!strcmp(subcommand, "show")) {
7723 request = REQ_VIEW_DIFF;
7725 } else {
7726 subcommand = NULL;
7727 }
7729 if (subcommand) {
7730 custom_argv[1] = subcommand;
7731 j = 2;
7732 }
7734 for (i = 1 + !!subcommand; i < argc; i++) {
7735 const char *opt = argv[i];
7737 if (seen_dashdash || !strcmp(opt, "--")) {
7738 seen_dashdash = TRUE;
7740 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7741 printf("tig version %s\n", TIG_VERSION);
7742 quit(0);
7744 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7745 printf("%s\n", usage);
7746 quit(0);
7747 }
7749 custom_argv[j++] = opt;
7750 if (j >= ARRAY_SIZE(custom_argv))
7751 die("command too long");
7752 }
7754 if (!prepare_update(VIEW(request), custom_argv, NULL))
7755 die("Failed to format arguments");
7757 return request;
7758 }
7760 int
7761 main(int argc, const char *argv[])
7762 {
7763 const char *codeset = "UTF-8";
7764 enum request request = parse_options(argc, argv);
7765 struct view *view;
7766 size_t i;
7768 signal(SIGINT, quit);
7769 signal(SIGPIPE, SIG_IGN);
7771 if (setlocale(LC_ALL, "")) {
7772 codeset = nl_langinfo(CODESET);
7773 }
7775 if (load_repo_info() == ERR)
7776 die("Failed to load repo info.");
7778 if (load_options() == ERR)
7779 die("Failed to load user config.");
7781 if (load_git_config() == ERR)
7782 die("Failed to load repo config.");
7784 /* Require a git repository unless when running in pager mode. */
7785 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7786 die("Not a git repository");
7788 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7789 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7790 if (opt_iconv_in == ICONV_NONE)
7791 die("Failed to initialize character set conversion");
7792 }
7794 if (codeset && strcmp(codeset, "UTF-8")) {
7795 opt_iconv_out = iconv_open(codeset, "UTF-8");
7796 if (opt_iconv_out == ICONV_NONE)
7797 die("Failed to initialize character set conversion");
7798 }
7800 if (load_refs() == ERR)
7801 die("Failed to load refs.");
7803 foreach_view (view, i)
7804 if (!argv_from_env(view->ops->argv, view->cmd_env))
7805 die("Too many arguments in the `%s` environment variable",
7806 view->cmd_env);
7808 init_display();
7810 if (request != REQ_NONE)
7811 open_view(NULL, request, OPEN_PREPARED);
7812 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7814 while (view_driver(display[current_view], request)) {
7815 int key = get_input(0);
7817 view = display[current_view];
7818 request = get_keybinding(view->keymap, key);
7820 /* Some low-level request handling. This keeps access to
7821 * status_win restricted. */
7822 switch (request) {
7823 case REQ_NONE:
7824 report("Unknown key, press %s for help",
7825 get_key(view->keymap, REQ_VIEW_HELP));
7826 break;
7827 case REQ_PROMPT:
7828 {
7829 char *cmd = read_prompt(":");
7831 if (cmd && isdigit(*cmd)) {
7832 int lineno = view->lineno + 1;
7834 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7835 select_view_line(view, lineno - 1);
7836 report("");
7837 } else {
7838 report("Unable to parse '%s' as a line number", cmd);
7839 }
7841 } else if (cmd) {
7842 struct view *next = VIEW(REQ_VIEW_PAGER);
7843 const char *argv[SIZEOF_ARG] = { "git" };
7844 int argc = 1;
7846 /* When running random commands, initially show the
7847 * command in the title. However, it maybe later be
7848 * overwritten if a commit line is selected. */
7849 string_ncopy(next->ref, cmd, strlen(cmd));
7851 if (!argv_from_string(argv, &argc, cmd)) {
7852 report("Too many arguments");
7853 } else if (!prepare_update(next, argv, NULL)) {
7854 report("Failed to format command");
7855 } else {
7856 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7857 }
7858 }
7860 request = REQ_NONE;
7861 break;
7862 }
7863 case REQ_SEARCH:
7864 case REQ_SEARCH_BACK:
7865 {
7866 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7867 char *search = read_prompt(prompt);
7869 if (search)
7870 string_ncopy(opt_search, search, strlen(search));
7871 else if (*opt_search)
7872 request = request == REQ_SEARCH ?
7873 REQ_FIND_NEXT :
7874 REQ_FIND_PREV;
7875 else
7876 request = REQ_NONE;
7877 break;
7878 }
7879 default:
7880 break;
7881 }
7882 }
7884 quit(0);
7886 return 0;
7887 }