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 for (argc = 0; argv[argc]; argc++)
680 free((void *) argv[argc]);
681 argv[0] = NULL;
682 }
684 static bool
685 argv_copy(const char *dst[], const char *src[])
686 {
687 int argc;
689 for (argc = 0; src[argc]; argc++)
690 if (!(dst[argc] = strdup(src[argc])))
691 return FALSE;
692 return TRUE;
693 }
696 /*
697 * Executing external commands.
698 */
700 enum io_type {
701 IO_FD, /* File descriptor based IO. */
702 IO_BG, /* Execute command in the background. */
703 IO_FG, /* Execute command with same std{in,out,err}. */
704 IO_RD, /* Read only fork+exec IO. */
705 IO_WR, /* Write only fork+exec IO. */
706 IO_AP, /* Append fork+exec output to file. */
707 };
709 struct io {
710 int pipe; /* Pipe end for reading or writing. */
711 pid_t pid; /* PID of spawned process. */
712 int error; /* Error status. */
713 char *buf; /* Read buffer. */
714 size_t bufalloc; /* Allocated buffer size. */
715 size_t bufsize; /* Buffer content size. */
716 char *bufpos; /* Current buffer position. */
717 unsigned int eof:1; /* Has end of file been reached. */
718 };
720 #define IO_INIT(fd) { fd }
722 static void
723 io_init(struct io *io)
724 {
725 memset(io, 0, sizeof(*io));
726 io->pipe = -1;
727 }
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
731 {
732 char name[SIZEOF_STR] = "";
733 bool fits;
734 va_list args;
736 io_init(io);
738 va_start(args, fmt);
739 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740 va_end(args);
742 if (!fits) {
743 io->error = ENAMETOOLONG;
744 return FALSE;
745 }
746 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747 if (io->pipe == -1)
748 io->error = errno;
749 return io->pipe != -1;
750 }
752 static bool
753 io_kill(struct io *io)
754 {
755 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
756 }
758 static bool
759 io_done(struct io *io)
760 {
761 pid_t pid = io->pid;
763 if (io->pipe != -1)
764 close(io->pipe);
765 free(io->buf);
766 io_init(io);
768 while (pid > 0) {
769 int status;
770 pid_t waiting = waitpid(pid, &status, 0);
772 if (waiting < 0) {
773 if (errno == EINTR)
774 continue;
775 io->error = errno;
776 return FALSE;
777 }
779 return waiting == pid &&
780 !WIFSIGNALED(status) &&
781 WIFEXITED(status) &&
782 !WEXITSTATUS(status);
783 }
785 return TRUE;
786 }
788 static bool
789 io_start(struct io *io, enum io_type type, const char *dir, const char *argv[])
790 {
791 int pipefds[2] = { -1, -1 };
793 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
794 io->error = errno;
795 return FALSE;
796 } else if (type == IO_AP) {
797 pipefds[1] = io->pipe;
798 }
800 if ((io->pid = fork())) {
801 if (io->pid == -1)
802 io->error = errno;
803 if (pipefds[!(type == IO_WR)] != -1)
804 close(pipefds[!(type == IO_WR)]);
805 if (io->pid != -1) {
806 io->pipe = pipefds[!!(type == IO_WR)];
807 return TRUE;
808 }
810 } else {
811 if (type != IO_FG) {
812 int devnull = open("/dev/null", O_RDWR);
813 int readfd = type == IO_WR ? pipefds[0] : devnull;
814 int writefd = (type == IO_RD || type == IO_AP)
815 ? pipefds[1] : devnull;
817 dup2(readfd, STDIN_FILENO);
818 dup2(writefd, STDOUT_FILENO);
819 dup2(devnull, STDERR_FILENO);
821 close(devnull);
822 if (pipefds[0] != -1)
823 close(pipefds[0]);
824 if (pipefds[1] != -1)
825 close(pipefds[1]);
826 }
828 if (dir && *dir && chdir(dir) == -1)
829 exit(errno);
831 execvp(argv[0], (char *const*) argv);
832 exit(errno);
833 }
835 if (pipefds[!!(type == IO_WR)] != -1)
836 close(pipefds[!!(type == IO_WR)]);
837 return FALSE;
838 }
840 static bool
841 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
842 {
843 io_init(io);
844 return io_start(io, type, dir, argv);
845 }
847 static bool
848 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
849 {
850 struct io io = IO_INIT(fd);
852 return io_start(&io, type, dir, argv) && io_done(&io);
853 }
855 static bool
856 io_run_bg(const char **argv)
857 {
858 return io_complete(IO_BG, argv, NULL, -1);
859 }
861 static bool
862 io_run_fg(const char **argv, const char *dir)
863 {
864 return io_complete(IO_FG, argv, dir, -1);
865 }
867 static bool
868 io_run_append(const char **argv, int fd)
869 {
870 return io_complete(IO_AP, argv, NULL, -1);
871 }
873 static bool
874 io_eof(struct io *io)
875 {
876 return io->eof;
877 }
879 static int
880 io_error(struct io *io)
881 {
882 return io->error;
883 }
885 static char *
886 io_strerror(struct io *io)
887 {
888 return strerror(io->error);
889 }
891 static bool
892 io_can_read(struct io *io)
893 {
894 struct timeval tv = { 0, 500 };
895 fd_set fds;
897 FD_ZERO(&fds);
898 FD_SET(io->pipe, &fds);
900 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
901 }
903 static ssize_t
904 io_read(struct io *io, void *buf, size_t bufsize)
905 {
906 do {
907 ssize_t readsize = read(io->pipe, buf, bufsize);
909 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
910 continue;
911 else if (readsize == -1)
912 io->error = errno;
913 else if (readsize == 0)
914 io->eof = 1;
915 return readsize;
916 } while (1);
917 }
919 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
921 static char *
922 io_get(struct io *io, int c, bool can_read)
923 {
924 char *eol;
925 ssize_t readsize;
927 while (TRUE) {
928 if (io->bufsize > 0) {
929 eol = memchr(io->bufpos, c, io->bufsize);
930 if (eol) {
931 char *line = io->bufpos;
933 *eol = 0;
934 io->bufpos = eol + 1;
935 io->bufsize -= io->bufpos - line;
936 return line;
937 }
938 }
940 if (io_eof(io)) {
941 if (io->bufsize) {
942 io->bufpos[io->bufsize] = 0;
943 io->bufsize = 0;
944 return io->bufpos;
945 }
946 return NULL;
947 }
949 if (!can_read)
950 return NULL;
952 if (io->bufsize > 0 && io->bufpos > io->buf)
953 memmove(io->buf, io->bufpos, io->bufsize);
955 if (io->bufalloc == io->bufsize) {
956 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
957 return NULL;
958 io->bufalloc += BUFSIZ;
959 }
961 io->bufpos = io->buf;
962 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
963 if (io_error(io))
964 return NULL;
965 io->bufsize += readsize;
966 }
967 }
969 static bool
970 io_write(struct io *io, const void *buf, size_t bufsize)
971 {
972 size_t written = 0;
974 while (!io_error(io) && written < bufsize) {
975 ssize_t size;
977 size = write(io->pipe, buf + written, bufsize - written);
978 if (size < 0 && (errno == EAGAIN || errno == EINTR))
979 continue;
980 else if (size == -1)
981 io->error = errno;
982 else
983 written += size;
984 }
986 return written == bufsize;
987 }
989 static bool
990 io_read_buf(struct io *io, char buf[], size_t bufsize)
991 {
992 char *result = io_get(io, '\n', TRUE);
994 if (result) {
995 result = chomp_string(result);
996 string_ncopy_do(buf, bufsize, result, strlen(result));
997 }
999 return io_done(io) && result;
1000 }
1002 static bool
1003 io_run_buf(const char **argv, char buf[], size_t bufsize)
1004 {
1005 struct io io = IO_INIT(-1);
1007 return io_start(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1008 }
1010 static int
1011 io_load(struct io *io, const char *separators,
1012 int (*read_property)(char *, size_t, char *, size_t))
1013 {
1014 char *name;
1015 int state = OK;
1017 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1018 char *value;
1019 size_t namelen;
1020 size_t valuelen;
1022 name = chomp_string(name);
1023 namelen = strcspn(name, separators);
1025 if (name[namelen]) {
1026 name[namelen] = 0;
1027 value = chomp_string(name + namelen + 1);
1028 valuelen = strlen(value);
1030 } else {
1031 value = "";
1032 valuelen = 0;
1033 }
1035 state = read_property(name, namelen, value, valuelen);
1036 }
1038 if (state != ERR && io_error(io))
1039 state = ERR;
1040 io_done(io);
1042 return state;
1043 }
1045 static int
1046 io_run_load(const char **argv, const char *separators,
1047 int (*read_property)(char *, size_t, char *, size_t))
1048 {
1049 struct io io = IO_INIT(-1);
1051 if (!io_start(&io, IO_RD, NULL, argv))
1052 return ERR;
1053 return io_load(&io, separators, read_property);
1054 }
1057 /*
1058 * User requests
1059 */
1061 #define REQ_INFO \
1062 /* XXX: Keep the view request first and in sync with views[]. */ \
1063 REQ_GROUP("View switching") \
1064 REQ_(VIEW_MAIN, "Show main view"), \
1065 REQ_(VIEW_DIFF, "Show diff view"), \
1066 REQ_(VIEW_LOG, "Show log view"), \
1067 REQ_(VIEW_TREE, "Show tree view"), \
1068 REQ_(VIEW_BLOB, "Show blob view"), \
1069 REQ_(VIEW_BLAME, "Show blame view"), \
1070 REQ_(VIEW_BRANCH, "Show branch view"), \
1071 REQ_(VIEW_HELP, "Show help page"), \
1072 REQ_(VIEW_PAGER, "Show pager view"), \
1073 REQ_(VIEW_STATUS, "Show status view"), \
1074 REQ_(VIEW_STAGE, "Show stage view"), \
1075 \
1076 REQ_GROUP("View manipulation") \
1077 REQ_(ENTER, "Enter current line and scroll"), \
1078 REQ_(NEXT, "Move to next"), \
1079 REQ_(PREVIOUS, "Move to previous"), \
1080 REQ_(PARENT, "Move to parent"), \
1081 REQ_(VIEW_NEXT, "Move focus to next view"), \
1082 REQ_(REFRESH, "Reload and refresh"), \
1083 REQ_(MAXIMIZE, "Maximize the current view"), \
1084 REQ_(VIEW_CLOSE, "Close the current view"), \
1085 REQ_(QUIT, "Close all views and quit"), \
1086 \
1087 REQ_GROUP("View specific requests") \
1088 REQ_(STATUS_UPDATE, "Update file status"), \
1089 REQ_(STATUS_REVERT, "Revert file changes"), \
1090 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1091 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1092 \
1093 REQ_GROUP("Cursor navigation") \
1094 REQ_(MOVE_UP, "Move cursor one line up"), \
1095 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1096 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1097 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1098 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1099 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1100 \
1101 REQ_GROUP("Scrolling") \
1102 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1103 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1104 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1105 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1106 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1107 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1108 \
1109 REQ_GROUP("Searching") \
1110 REQ_(SEARCH, "Search the view"), \
1111 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1112 REQ_(FIND_NEXT, "Find next search match"), \
1113 REQ_(FIND_PREV, "Find previous search match"), \
1114 \
1115 REQ_GROUP("Option manipulation") \
1116 REQ_(OPTIONS, "Open option menu"), \
1117 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1118 REQ_(TOGGLE_DATE, "Toggle date display"), \
1119 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1120 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1121 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1122 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1123 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1124 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1125 \
1126 REQ_GROUP("Misc") \
1127 REQ_(PROMPT, "Bring up the prompt"), \
1128 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1129 REQ_(SHOW_VERSION, "Show version information"), \
1130 REQ_(STOP_LOADING, "Stop all loading views"), \
1131 REQ_(EDIT, "Open in editor"), \
1132 REQ_(NONE, "Do nothing")
1135 /* User action requests. */
1136 enum request {
1137 #define REQ_GROUP(help)
1138 #define REQ_(req, help) REQ_##req
1140 /* Offset all requests to avoid conflicts with ncurses getch values. */
1141 REQ_UNKNOWN = KEY_MAX + 1,
1142 REQ_OFFSET,
1143 REQ_INFO
1145 #undef REQ_GROUP
1146 #undef REQ_
1147 };
1149 struct request_info {
1150 enum request request;
1151 const char *name;
1152 int namelen;
1153 const char *help;
1154 };
1156 static const struct request_info req_info[] = {
1157 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1158 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1159 REQ_INFO
1160 #undef REQ_GROUP
1161 #undef REQ_
1162 };
1164 static enum request
1165 get_request(const char *name)
1166 {
1167 int namelen = strlen(name);
1168 int i;
1170 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1171 if (enum_equals(req_info[i], name, namelen))
1172 return req_info[i].request;
1174 return REQ_UNKNOWN;
1175 }
1178 /*
1179 * Options
1180 */
1182 /* Option and state variables. */
1183 static enum date opt_date = DATE_DEFAULT;
1184 static enum author opt_author = AUTHOR_DEFAULT;
1185 static bool opt_line_number = FALSE;
1186 static bool opt_line_graphics = TRUE;
1187 static bool opt_rev_graph = FALSE;
1188 static bool opt_show_refs = TRUE;
1189 static int opt_num_interval = 5;
1190 static double opt_hscroll = 0.50;
1191 static double opt_scale_split_view = 2.0 / 3.0;
1192 static int opt_tab_size = 8;
1193 static int opt_author_cols = AUTHOR_COLS;
1194 static char opt_path[SIZEOF_STR] = "";
1195 static char opt_file[SIZEOF_STR] = "";
1196 static char opt_ref[SIZEOF_REF] = "";
1197 static char opt_head[SIZEOF_REF] = "";
1198 static char opt_remote[SIZEOF_REF] = "";
1199 static char opt_encoding[20] = "UTF-8";
1200 static iconv_t opt_iconv_in = ICONV_NONE;
1201 static iconv_t opt_iconv_out = ICONV_NONE;
1202 static char opt_search[SIZEOF_STR] = "";
1203 static char opt_cdup[SIZEOF_STR] = "";
1204 static char opt_prefix[SIZEOF_STR] = "";
1205 static char opt_git_dir[SIZEOF_STR] = "";
1206 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1207 static char opt_editor[SIZEOF_STR] = "";
1208 static FILE *opt_tty = NULL;
1210 #define is_initial_commit() (!get_ref_head())
1211 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1214 /*
1215 * Line-oriented content detection.
1216 */
1218 #define LINE_INFO \
1219 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1220 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1221 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1222 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1223 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1224 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1225 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1226 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1227 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1228 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1229 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1230 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1231 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1232 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1233 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1234 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1235 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1236 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1237 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1238 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1239 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1240 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1241 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1242 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1243 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1244 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1245 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1246 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1247 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1249 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1250 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1251 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1252 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1253 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1255 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1256 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1257 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1258 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1259 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1260 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1261 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1263 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1264 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1265 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1266 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1267 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1268 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1269 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1271 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1272 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1274 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1276 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1277 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1279 enum line_type {
1280 #define LINE(type, line, fg, bg, attr) \
1281 LINE_##type
1282 LINE_INFO,
1283 LINE_NONE
1284 #undef LINE
1285 };
1287 struct line_info {
1288 const char *name; /* Option name. */
1289 int namelen; /* Size of option name. */
1290 const char *line; /* The start of line to match. */
1291 int linelen; /* Size of string to match. */
1292 int fg, bg, attr; /* Color and text attributes for the lines. */
1293 };
1295 static struct line_info line_info[] = {
1296 #define LINE(type, line, fg, bg, attr) \
1297 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1298 LINE_INFO
1299 #undef LINE
1300 };
1302 static enum line_type
1303 get_line_type(const char *line)
1304 {
1305 int linelen = strlen(line);
1306 enum line_type type;
1308 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1309 /* Case insensitive search matches Signed-off-by lines better. */
1310 if (linelen >= line_info[type].linelen &&
1311 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1312 return type;
1314 return LINE_DEFAULT;
1315 }
1317 static inline int
1318 get_line_attr(enum line_type type)
1319 {
1320 assert(type < ARRAY_SIZE(line_info));
1321 return COLOR_PAIR(type) | line_info[type].attr;
1322 }
1324 static struct line_info *
1325 get_line_info(const char *name)
1326 {
1327 size_t namelen = strlen(name);
1328 enum line_type type;
1330 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1331 if (enum_equals(line_info[type], name, namelen))
1332 return &line_info[type];
1334 return NULL;
1335 }
1337 static void
1338 init_colors(void)
1339 {
1340 int default_bg = line_info[LINE_DEFAULT].bg;
1341 int default_fg = line_info[LINE_DEFAULT].fg;
1342 enum line_type type;
1344 start_color();
1346 if (assume_default_colors(default_fg, default_bg) == ERR) {
1347 default_bg = COLOR_BLACK;
1348 default_fg = COLOR_WHITE;
1349 }
1351 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1352 struct line_info *info = &line_info[type];
1353 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1354 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1356 init_pair(type, fg, bg);
1357 }
1358 }
1360 struct line {
1361 enum line_type type;
1363 /* State flags */
1364 unsigned int selected:1;
1365 unsigned int dirty:1;
1366 unsigned int cleareol:1;
1367 unsigned int other:16;
1369 void *data; /* User data */
1370 };
1373 /*
1374 * Keys
1375 */
1377 struct keybinding {
1378 int alias;
1379 enum request request;
1380 };
1382 static struct keybinding default_keybindings[] = {
1383 /* View switching */
1384 { 'm', REQ_VIEW_MAIN },
1385 { 'd', REQ_VIEW_DIFF },
1386 { 'l', REQ_VIEW_LOG },
1387 { 't', REQ_VIEW_TREE },
1388 { 'f', REQ_VIEW_BLOB },
1389 { 'B', REQ_VIEW_BLAME },
1390 { 'H', REQ_VIEW_BRANCH },
1391 { 'p', REQ_VIEW_PAGER },
1392 { 'h', REQ_VIEW_HELP },
1393 { 'S', REQ_VIEW_STATUS },
1394 { 'c', REQ_VIEW_STAGE },
1396 /* View manipulation */
1397 { 'q', REQ_VIEW_CLOSE },
1398 { KEY_TAB, REQ_VIEW_NEXT },
1399 { KEY_RETURN, REQ_ENTER },
1400 { KEY_UP, REQ_PREVIOUS },
1401 { KEY_DOWN, REQ_NEXT },
1402 { 'R', REQ_REFRESH },
1403 { KEY_F(5), REQ_REFRESH },
1404 { 'O', REQ_MAXIMIZE },
1406 /* Cursor navigation */
1407 { 'k', REQ_MOVE_UP },
1408 { 'j', REQ_MOVE_DOWN },
1409 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1410 { KEY_END, REQ_MOVE_LAST_LINE },
1411 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1412 { ' ', REQ_MOVE_PAGE_DOWN },
1413 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1414 { 'b', REQ_MOVE_PAGE_UP },
1415 { '-', REQ_MOVE_PAGE_UP },
1417 /* Scrolling */
1418 { KEY_LEFT, REQ_SCROLL_LEFT },
1419 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1420 { KEY_IC, REQ_SCROLL_LINE_UP },
1421 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1422 { 'w', REQ_SCROLL_PAGE_UP },
1423 { 's', REQ_SCROLL_PAGE_DOWN },
1425 /* Searching */
1426 { '/', REQ_SEARCH },
1427 { '?', REQ_SEARCH_BACK },
1428 { 'n', REQ_FIND_NEXT },
1429 { 'N', REQ_FIND_PREV },
1431 /* Misc */
1432 { 'Q', REQ_QUIT },
1433 { 'z', REQ_STOP_LOADING },
1434 { 'v', REQ_SHOW_VERSION },
1435 { 'r', REQ_SCREEN_REDRAW },
1436 { 'o', REQ_OPTIONS },
1437 { '.', REQ_TOGGLE_LINENO },
1438 { 'D', REQ_TOGGLE_DATE },
1439 { 'A', REQ_TOGGLE_AUTHOR },
1440 { 'g', REQ_TOGGLE_REV_GRAPH },
1441 { 'F', REQ_TOGGLE_REFS },
1442 { 'I', REQ_TOGGLE_SORT_ORDER },
1443 { 'i', REQ_TOGGLE_SORT_FIELD },
1444 { ':', REQ_PROMPT },
1445 { 'u', REQ_STATUS_UPDATE },
1446 { '!', REQ_STATUS_REVERT },
1447 { 'M', REQ_STATUS_MERGE },
1448 { '@', REQ_STAGE_NEXT },
1449 { ',', REQ_PARENT },
1450 { 'e', REQ_EDIT },
1451 };
1453 #define KEYMAP_INFO \
1454 KEYMAP_(GENERIC), \
1455 KEYMAP_(MAIN), \
1456 KEYMAP_(DIFF), \
1457 KEYMAP_(LOG), \
1458 KEYMAP_(TREE), \
1459 KEYMAP_(BLOB), \
1460 KEYMAP_(BLAME), \
1461 KEYMAP_(BRANCH), \
1462 KEYMAP_(PAGER), \
1463 KEYMAP_(HELP), \
1464 KEYMAP_(STATUS), \
1465 KEYMAP_(STAGE)
1467 enum keymap {
1468 #define KEYMAP_(name) KEYMAP_##name
1469 KEYMAP_INFO
1470 #undef KEYMAP_
1471 };
1473 static const struct enum_map keymap_table[] = {
1474 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1475 KEYMAP_INFO
1476 #undef KEYMAP_
1477 };
1479 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1481 struct keybinding_table {
1482 struct keybinding *data;
1483 size_t size;
1484 };
1486 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1488 static void
1489 add_keybinding(enum keymap keymap, enum request request, int key)
1490 {
1491 struct keybinding_table *table = &keybindings[keymap];
1492 size_t i;
1494 for (i = 0; i < keybindings[keymap].size; i++) {
1495 if (keybindings[keymap].data[i].alias == key) {
1496 keybindings[keymap].data[i].request = request;
1497 return;
1498 }
1499 }
1501 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1502 if (!table->data)
1503 die("Failed to allocate keybinding");
1504 table->data[table->size].alias = key;
1505 table->data[table->size++].request = request;
1507 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1508 int i;
1510 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1511 if (default_keybindings[i].alias == key)
1512 default_keybindings[i].request = REQ_NONE;
1513 }
1514 }
1516 /* Looks for a key binding first in the given map, then in the generic map, and
1517 * lastly in the default keybindings. */
1518 static enum request
1519 get_keybinding(enum keymap keymap, int key)
1520 {
1521 size_t i;
1523 for (i = 0; i < keybindings[keymap].size; i++)
1524 if (keybindings[keymap].data[i].alias == key)
1525 return keybindings[keymap].data[i].request;
1527 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1528 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1529 return keybindings[KEYMAP_GENERIC].data[i].request;
1531 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1532 if (default_keybindings[i].alias == key)
1533 return default_keybindings[i].request;
1535 return (enum request) key;
1536 }
1539 struct key {
1540 const char *name;
1541 int value;
1542 };
1544 static const struct key key_table[] = {
1545 { "Enter", KEY_RETURN },
1546 { "Space", ' ' },
1547 { "Backspace", KEY_BACKSPACE },
1548 { "Tab", KEY_TAB },
1549 { "Escape", KEY_ESC },
1550 { "Left", KEY_LEFT },
1551 { "Right", KEY_RIGHT },
1552 { "Up", KEY_UP },
1553 { "Down", KEY_DOWN },
1554 { "Insert", KEY_IC },
1555 { "Delete", KEY_DC },
1556 { "Hash", '#' },
1557 { "Home", KEY_HOME },
1558 { "End", KEY_END },
1559 { "PageUp", KEY_PPAGE },
1560 { "PageDown", KEY_NPAGE },
1561 { "F1", KEY_F(1) },
1562 { "F2", KEY_F(2) },
1563 { "F3", KEY_F(3) },
1564 { "F4", KEY_F(4) },
1565 { "F5", KEY_F(5) },
1566 { "F6", KEY_F(6) },
1567 { "F7", KEY_F(7) },
1568 { "F8", KEY_F(8) },
1569 { "F9", KEY_F(9) },
1570 { "F10", KEY_F(10) },
1571 { "F11", KEY_F(11) },
1572 { "F12", KEY_F(12) },
1573 };
1575 static int
1576 get_key_value(const char *name)
1577 {
1578 int i;
1580 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1581 if (!strcasecmp(key_table[i].name, name))
1582 return key_table[i].value;
1584 if (strlen(name) == 1 && isprint(*name))
1585 return (int) *name;
1587 return ERR;
1588 }
1590 static const char *
1591 get_key_name(int key_value)
1592 {
1593 static char key_char[] = "'X'";
1594 const char *seq = NULL;
1595 int key;
1597 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1598 if (key_table[key].value == key_value)
1599 seq = key_table[key].name;
1601 if (seq == NULL &&
1602 key_value < 127 &&
1603 isprint(key_value)) {
1604 key_char[1] = (char) key_value;
1605 seq = key_char;
1606 }
1608 return seq ? seq : "(no key)";
1609 }
1611 static bool
1612 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1613 {
1614 const char *sep = *pos > 0 ? ", " : "";
1615 const char *keyname = get_key_name(keybinding->alias);
1617 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1618 }
1620 static bool
1621 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1622 enum keymap keymap, bool all)
1623 {
1624 int i;
1626 for (i = 0; i < keybindings[keymap].size; i++) {
1627 if (keybindings[keymap].data[i].request == request) {
1628 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1629 return FALSE;
1630 if (!all)
1631 break;
1632 }
1633 }
1635 return TRUE;
1636 }
1638 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1640 static const char *
1641 get_keys(enum keymap keymap, enum request request, bool all)
1642 {
1643 static char buf[BUFSIZ];
1644 size_t pos = 0;
1645 int i;
1647 buf[pos] = 0;
1649 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1650 return "Too many keybindings!";
1651 if (pos > 0 && !all)
1652 return buf;
1654 if (keymap != KEYMAP_GENERIC) {
1655 /* Only the generic keymap includes the default keybindings when
1656 * listing all keys. */
1657 if (all)
1658 return buf;
1660 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1661 return "Too many keybindings!";
1662 if (pos)
1663 return buf;
1664 }
1666 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1667 if (default_keybindings[i].request == request) {
1668 if (!append_key(buf, &pos, &default_keybindings[i]))
1669 return "Too many keybindings!";
1670 if (!all)
1671 return buf;
1672 }
1673 }
1675 return buf;
1676 }
1678 struct run_request {
1679 enum keymap keymap;
1680 int key;
1681 const char *argv[SIZEOF_ARG];
1682 };
1684 static struct run_request *run_request;
1685 static size_t run_requests;
1687 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1689 static enum request
1690 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1691 {
1692 struct run_request *req;
1694 if (argc >= ARRAY_SIZE(req->argv) - 1)
1695 return REQ_NONE;
1697 if (!realloc_run_requests(&run_request, run_requests, 1))
1698 return REQ_NONE;
1700 req = &run_request[run_requests];
1701 req->keymap = keymap;
1702 req->key = key;
1703 req->argv[0] = NULL;
1705 if (!argv_copy(req->argv, argv))
1706 return REQ_NONE;
1708 return REQ_NONE + ++run_requests;
1709 }
1711 static struct run_request *
1712 get_run_request(enum request request)
1713 {
1714 if (request <= REQ_NONE)
1715 return NULL;
1716 return &run_request[request - REQ_NONE - 1];
1717 }
1719 static void
1720 add_builtin_run_requests(void)
1721 {
1722 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1723 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1724 const char *commit[] = { "git", "commit", NULL };
1725 const char *gc[] = { "git", "gc", NULL };
1726 struct {
1727 enum keymap keymap;
1728 int key;
1729 int argc;
1730 const char **argv;
1731 } reqs[] = {
1732 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1733 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1734 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1735 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1736 };
1737 int i;
1739 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1740 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1742 if (req != reqs[i].key)
1743 continue;
1744 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1745 if (req != REQ_NONE)
1746 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1747 }
1748 }
1750 /*
1751 * User config file handling.
1752 */
1754 static int config_lineno;
1755 static bool config_errors;
1756 static const char *config_msg;
1758 static const struct enum_map color_map[] = {
1759 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1760 COLOR_MAP(DEFAULT),
1761 COLOR_MAP(BLACK),
1762 COLOR_MAP(BLUE),
1763 COLOR_MAP(CYAN),
1764 COLOR_MAP(GREEN),
1765 COLOR_MAP(MAGENTA),
1766 COLOR_MAP(RED),
1767 COLOR_MAP(WHITE),
1768 COLOR_MAP(YELLOW),
1769 };
1771 static const struct enum_map attr_map[] = {
1772 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1773 ATTR_MAP(NORMAL),
1774 ATTR_MAP(BLINK),
1775 ATTR_MAP(BOLD),
1776 ATTR_MAP(DIM),
1777 ATTR_MAP(REVERSE),
1778 ATTR_MAP(STANDOUT),
1779 ATTR_MAP(UNDERLINE),
1780 };
1782 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1784 static int parse_step(double *opt, const char *arg)
1785 {
1786 *opt = atoi(arg);
1787 if (!strchr(arg, '%'))
1788 return OK;
1790 /* "Shift down" so 100% and 1 does not conflict. */
1791 *opt = (*opt - 1) / 100;
1792 if (*opt >= 1.0) {
1793 *opt = 0.99;
1794 config_msg = "Step value larger than 100%";
1795 return ERR;
1796 }
1797 if (*opt < 0.0) {
1798 *opt = 1;
1799 config_msg = "Invalid step value";
1800 return ERR;
1801 }
1802 return OK;
1803 }
1805 static int
1806 parse_int(int *opt, const char *arg, int min, int max)
1807 {
1808 int value = atoi(arg);
1810 if (min <= value && value <= max) {
1811 *opt = value;
1812 return OK;
1813 }
1815 config_msg = "Integer value out of bound";
1816 return ERR;
1817 }
1819 static bool
1820 set_color(int *color, const char *name)
1821 {
1822 if (map_enum(color, color_map, name))
1823 return TRUE;
1824 if (!prefixcmp(name, "color"))
1825 return parse_int(color, name + 5, 0, 255) == OK;
1826 return FALSE;
1827 }
1829 /* Wants: object fgcolor bgcolor [attribute] */
1830 static int
1831 option_color_command(int argc, const char *argv[])
1832 {
1833 struct line_info *info;
1835 if (argc < 3) {
1836 config_msg = "Wrong number of arguments given to color command";
1837 return ERR;
1838 }
1840 info = get_line_info(argv[0]);
1841 if (!info) {
1842 static const struct enum_map obsolete[] = {
1843 ENUM_MAP("main-delim", LINE_DELIMITER),
1844 ENUM_MAP("main-date", LINE_DATE),
1845 ENUM_MAP("main-author", LINE_AUTHOR),
1846 };
1847 int index;
1849 if (!map_enum(&index, obsolete, argv[0])) {
1850 config_msg = "Unknown color name";
1851 return ERR;
1852 }
1853 info = &line_info[index];
1854 }
1856 if (!set_color(&info->fg, argv[1]) ||
1857 !set_color(&info->bg, argv[2])) {
1858 config_msg = "Unknown color";
1859 return ERR;
1860 }
1862 info->attr = 0;
1863 while (argc-- > 3) {
1864 int attr;
1866 if (!set_attribute(&attr, argv[argc])) {
1867 config_msg = "Unknown attribute";
1868 return ERR;
1869 }
1870 info->attr |= attr;
1871 }
1873 return OK;
1874 }
1876 static int parse_bool(bool *opt, const char *arg)
1877 {
1878 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1879 ? TRUE : FALSE;
1880 return OK;
1881 }
1883 static int parse_enum_do(unsigned int *opt, const char *arg,
1884 const struct enum_map *map, size_t map_size)
1885 {
1886 bool is_true;
1888 assert(map_size > 1);
1890 if (map_enum_do(map, map_size, (int *) opt, arg))
1891 return OK;
1893 if (parse_bool(&is_true, arg) != OK)
1894 return ERR;
1896 *opt = is_true ? map[1].value : map[0].value;
1897 return OK;
1898 }
1900 #define parse_enum(opt, arg, map) \
1901 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1903 static int
1904 parse_string(char *opt, const char *arg, size_t optsize)
1905 {
1906 int arglen = strlen(arg);
1908 switch (arg[0]) {
1909 case '\"':
1910 case '\'':
1911 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1912 config_msg = "Unmatched quotation";
1913 return ERR;
1914 }
1915 arg += 1; arglen -= 2;
1916 default:
1917 string_ncopy_do(opt, optsize, arg, arglen);
1918 return OK;
1919 }
1920 }
1922 /* Wants: name = value */
1923 static int
1924 option_set_command(int argc, const char *argv[])
1925 {
1926 if (argc != 3) {
1927 config_msg = "Wrong number of arguments given to set command";
1928 return ERR;
1929 }
1931 if (strcmp(argv[1], "=")) {
1932 config_msg = "No value assigned";
1933 return ERR;
1934 }
1936 if (!strcmp(argv[0], "show-author"))
1937 return parse_enum(&opt_author, argv[2], author_map);
1939 if (!strcmp(argv[0], "show-date"))
1940 return parse_enum(&opt_date, argv[2], date_map);
1942 if (!strcmp(argv[0], "show-rev-graph"))
1943 return parse_bool(&opt_rev_graph, argv[2]);
1945 if (!strcmp(argv[0], "show-refs"))
1946 return parse_bool(&opt_show_refs, argv[2]);
1948 if (!strcmp(argv[0], "show-line-numbers"))
1949 return parse_bool(&opt_line_number, argv[2]);
1951 if (!strcmp(argv[0], "line-graphics"))
1952 return parse_bool(&opt_line_graphics, argv[2]);
1954 if (!strcmp(argv[0], "line-number-interval"))
1955 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1957 if (!strcmp(argv[0], "author-width"))
1958 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1960 if (!strcmp(argv[0], "horizontal-scroll"))
1961 return parse_step(&opt_hscroll, argv[2]);
1963 if (!strcmp(argv[0], "split-view-height"))
1964 return parse_step(&opt_scale_split_view, argv[2]);
1966 if (!strcmp(argv[0], "tab-size"))
1967 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1969 if (!strcmp(argv[0], "commit-encoding"))
1970 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1972 config_msg = "Unknown variable name";
1973 return ERR;
1974 }
1976 /* Wants: mode request key */
1977 static int
1978 option_bind_command(int argc, const char *argv[])
1979 {
1980 enum request request;
1981 int keymap = -1;
1982 int key;
1984 if (argc < 3) {
1985 config_msg = "Wrong number of arguments given to bind command";
1986 return ERR;
1987 }
1989 if (!set_keymap(&keymap, argv[0])) {
1990 config_msg = "Unknown key map";
1991 return ERR;
1992 }
1994 key = get_key_value(argv[1]);
1995 if (key == ERR) {
1996 config_msg = "Unknown key";
1997 return ERR;
1998 }
2000 request = get_request(argv[2]);
2001 if (request == REQ_UNKNOWN) {
2002 static const struct enum_map obsolete[] = {
2003 ENUM_MAP("cherry-pick", REQ_NONE),
2004 ENUM_MAP("screen-resize", REQ_NONE),
2005 ENUM_MAP("tree-parent", REQ_PARENT),
2006 };
2007 int alias;
2009 if (map_enum(&alias, obsolete, argv[2])) {
2010 if (alias != REQ_NONE)
2011 add_keybinding(keymap, alias, key);
2012 config_msg = "Obsolete request name";
2013 return ERR;
2014 }
2015 }
2016 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2017 request = add_run_request(keymap, key, argc - 2, argv + 2);
2018 if (request == REQ_UNKNOWN) {
2019 config_msg = "Unknown request name";
2020 return ERR;
2021 }
2023 add_keybinding(keymap, request, key);
2025 return OK;
2026 }
2028 static int
2029 set_option(const char *opt, char *value)
2030 {
2031 const char *argv[SIZEOF_ARG];
2032 int argc = 0;
2034 if (!argv_from_string(argv, &argc, value)) {
2035 config_msg = "Too many option arguments";
2036 return ERR;
2037 }
2039 if (!strcmp(opt, "color"))
2040 return option_color_command(argc, argv);
2042 if (!strcmp(opt, "set"))
2043 return option_set_command(argc, argv);
2045 if (!strcmp(opt, "bind"))
2046 return option_bind_command(argc, argv);
2048 config_msg = "Unknown option command";
2049 return ERR;
2050 }
2052 static int
2053 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2054 {
2055 int status = OK;
2057 config_lineno++;
2058 config_msg = "Internal error";
2060 /* Check for comment markers, since read_properties() will
2061 * only ensure opt and value are split at first " \t". */
2062 optlen = strcspn(opt, "#");
2063 if (optlen == 0)
2064 return OK;
2066 if (opt[optlen] != 0) {
2067 config_msg = "No option value";
2068 status = ERR;
2070 } else {
2071 /* Look for comment endings in the value. */
2072 size_t len = strcspn(value, "#");
2074 if (len < valuelen) {
2075 valuelen = len;
2076 value[valuelen] = 0;
2077 }
2079 status = set_option(opt, value);
2080 }
2082 if (status == ERR) {
2083 warn("Error on line %d, near '%.*s': %s",
2084 config_lineno, (int) optlen, opt, config_msg);
2085 config_errors = TRUE;
2086 }
2088 /* Always keep going if errors are encountered. */
2089 return OK;
2090 }
2092 static void
2093 load_option_file(const char *path)
2094 {
2095 struct io io = {};
2097 /* It's OK that the file doesn't exist. */
2098 if (!io_open(&io, "%s", path))
2099 return;
2101 config_lineno = 0;
2102 config_errors = FALSE;
2104 if (io_load(&io, " \t", read_option) == ERR ||
2105 config_errors == TRUE)
2106 warn("Errors while loading %s.", path);
2107 }
2109 static int
2110 load_options(void)
2111 {
2112 const char *home = getenv("HOME");
2113 const char *tigrc_user = getenv("TIGRC_USER");
2114 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2115 char buf[SIZEOF_STR];
2117 if (!tigrc_system)
2118 tigrc_system = SYSCONFDIR "/tigrc";
2119 load_option_file(tigrc_system);
2121 if (!tigrc_user) {
2122 if (!home || !string_format(buf, "%s/.tigrc", home))
2123 return ERR;
2124 tigrc_user = buf;
2125 }
2126 load_option_file(tigrc_user);
2128 /* Add _after_ loading config files to avoid adding run requests
2129 * that conflict with keybindings. */
2130 add_builtin_run_requests();
2132 return OK;
2133 }
2136 /*
2137 * The viewer
2138 */
2140 struct view;
2141 struct view_ops;
2143 /* The display array of active views and the index of the current view. */
2144 static struct view *display[2];
2145 static unsigned int current_view;
2147 #define foreach_displayed_view(view, i) \
2148 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2150 #define displayed_views() (display[1] != NULL ? 2 : 1)
2152 /* Current head and commit ID */
2153 static char ref_blob[SIZEOF_REF] = "";
2154 static char ref_commit[SIZEOF_REF] = "HEAD";
2155 static char ref_head[SIZEOF_REF] = "HEAD";
2156 static char ref_branch[SIZEOF_REF] = "";
2158 enum view_type {
2159 VIEW_MAIN,
2160 VIEW_DIFF,
2161 VIEW_LOG,
2162 VIEW_TREE,
2163 VIEW_BLOB,
2164 VIEW_BLAME,
2165 VIEW_BRANCH,
2166 VIEW_HELP,
2167 VIEW_PAGER,
2168 VIEW_STATUS,
2169 VIEW_STAGE,
2170 };
2172 struct view {
2173 enum view_type type; /* View type */
2174 const char *name; /* View name */
2175 const char *cmd_env; /* Command line set via environment */
2176 const char *id; /* Points to either of ref_{head,commit,blob} */
2178 struct view_ops *ops; /* View operations */
2180 enum keymap keymap; /* What keymap does this view have */
2181 bool git_dir; /* Whether the view requires a git directory. */
2183 char ref[SIZEOF_REF]; /* Hovered commit reference */
2184 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2186 int height, width; /* The width and height of the main window */
2187 WINDOW *win; /* The main window */
2188 WINDOW *title; /* The title window living below the main window */
2190 /* Navigation */
2191 unsigned long offset; /* Offset of the window top */
2192 unsigned long yoffset; /* Offset from the window side. */
2193 unsigned long lineno; /* Current line number */
2194 unsigned long p_offset; /* Previous offset of the window top */
2195 unsigned long p_yoffset;/* Previous offset from the window side */
2196 unsigned long p_lineno; /* Previous current line number */
2197 bool p_restore; /* Should the previous position be restored. */
2199 /* Searching */
2200 char grep[SIZEOF_STR]; /* Search string */
2201 regex_t *regex; /* Pre-compiled regexp */
2203 /* If non-NULL, points to the view that opened this view. If this view
2204 * is closed tig will switch back to the parent view. */
2205 struct view *parent;
2206 struct view *prev;
2208 /* Buffering */
2209 size_t lines; /* Total number of lines */
2210 struct line *line; /* Line index */
2211 unsigned int digits; /* Number of digits in the lines member. */
2213 /* Drawing */
2214 struct line *curline; /* Line currently being drawn. */
2215 enum line_type curtype; /* Attribute currently used for drawing. */
2216 unsigned long col; /* Column when drawing. */
2217 bool has_scrolled; /* View was scrolled. */
2219 /* Loading */
2220 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
2221 const char *dir; /* Directory from which to execute. */
2222 struct io io;
2223 struct io *pipe;
2224 time_t start_time;
2225 time_t update_secs;
2226 };
2228 struct view_ops {
2229 /* What type of content being displayed. Used in the title bar. */
2230 const char *type;
2231 /* Default command arguments. */
2232 const char **argv;
2233 /* Open and reads in all view content. */
2234 bool (*open)(struct view *view);
2235 /* Read one line; updates view->line. */
2236 bool (*read)(struct view *view, char *data);
2237 /* Draw one line; @lineno must be < view->height. */
2238 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2239 /* Depending on view handle a special requests. */
2240 enum request (*request)(struct view *view, enum request request, struct line *line);
2241 /* Search for regexp in a line. */
2242 bool (*grep)(struct view *view, struct line *line);
2243 /* Select line */
2244 void (*select)(struct view *view, struct line *line);
2245 /* Prepare view for loading */
2246 bool (*prepare)(struct view *view);
2247 };
2249 static struct view_ops blame_ops;
2250 static struct view_ops blob_ops;
2251 static struct view_ops diff_ops;
2252 static struct view_ops help_ops;
2253 static struct view_ops log_ops;
2254 static struct view_ops main_ops;
2255 static struct view_ops pager_ops;
2256 static struct view_ops stage_ops;
2257 static struct view_ops status_ops;
2258 static struct view_ops tree_ops;
2259 static struct view_ops branch_ops;
2261 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2262 { type, name, #env, ref, ops, map, git }
2264 #define VIEW_(id, name, ops, git, ref) \
2265 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2267 static struct view views[] = {
2268 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2269 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2270 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2271 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2272 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2273 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2274 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2275 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2276 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2277 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2278 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2279 };
2281 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2283 #define foreach_view(view, i) \
2284 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2286 #define view_is_displayed(view) \
2287 (view == display[0] || view == display[1])
2289 static enum request
2290 view_request(struct view *view, enum request request)
2291 {
2292 if (!view || !view->lines)
2293 return request;
2294 return view->ops->request(view, request, &view->line[view->lineno]);
2295 }
2298 /*
2299 * View drawing.
2300 */
2302 static inline void
2303 set_view_attr(struct view *view, enum line_type type)
2304 {
2305 if (!view->curline->selected && view->curtype != type) {
2306 (void) wattrset(view->win, get_line_attr(type));
2307 wchgat(view->win, -1, 0, type, NULL);
2308 view->curtype = type;
2309 }
2310 }
2312 static int
2313 draw_chars(struct view *view, enum line_type type, const char *string,
2314 int max_len, bool use_tilde)
2315 {
2316 static char out_buffer[BUFSIZ * 2];
2317 int len = 0;
2318 int col = 0;
2319 int trimmed = FALSE;
2320 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2322 if (max_len <= 0)
2323 return 0;
2325 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2327 set_view_attr(view, type);
2328 if (len > 0) {
2329 if (opt_iconv_out != ICONV_NONE) {
2330 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2331 size_t inlen = len + 1;
2333 char *outbuf = out_buffer;
2334 size_t outlen = sizeof(out_buffer);
2336 size_t ret;
2338 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2339 if (ret != (size_t) -1) {
2340 string = out_buffer;
2341 len = sizeof(out_buffer) - outlen;
2342 }
2343 }
2345 waddnstr(view->win, string, len);
2346 }
2347 if (trimmed && use_tilde) {
2348 set_view_attr(view, LINE_DELIMITER);
2349 waddch(view->win, '~');
2350 col++;
2351 }
2353 return col;
2354 }
2356 static int
2357 draw_space(struct view *view, enum line_type type, int max, int spaces)
2358 {
2359 static char space[] = " ";
2360 int col = 0;
2362 spaces = MIN(max, spaces);
2364 while (spaces > 0) {
2365 int len = MIN(spaces, sizeof(space) - 1);
2367 col += draw_chars(view, type, space, len, FALSE);
2368 spaces -= len;
2369 }
2371 return col;
2372 }
2374 static bool
2375 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2376 {
2377 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2378 return view->width + view->yoffset <= view->col;
2379 }
2381 static bool
2382 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2383 {
2384 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2385 int max = view->width + view->yoffset - view->col;
2386 int i;
2388 if (max < size)
2389 size = max;
2391 set_view_attr(view, type);
2392 /* Using waddch() instead of waddnstr() ensures that
2393 * they'll be rendered correctly for the cursor line. */
2394 for (i = skip; i < size; i++)
2395 waddch(view->win, graphic[i]);
2397 view->col += size;
2398 if (size < max && skip <= size)
2399 waddch(view->win, ' ');
2400 view->col++;
2402 return view->width + view->yoffset <= view->col;
2403 }
2405 static bool
2406 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2407 {
2408 int max = MIN(view->width + view->yoffset - view->col, len);
2409 int col;
2411 if (text)
2412 col = draw_chars(view, type, text, max - 1, trim);
2413 else
2414 col = draw_space(view, type, max - 1, max - 1);
2416 view->col += col;
2417 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2418 return view->width + view->yoffset <= view->col;
2419 }
2421 static bool
2422 draw_date(struct view *view, struct time *time)
2423 {
2424 const char *date = mkdate(time, opt_date);
2425 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2427 return draw_field(view, LINE_DATE, date, cols, FALSE);
2428 }
2430 static bool
2431 draw_author(struct view *view, const char *author)
2432 {
2433 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2434 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2436 if (abbreviate && author)
2437 author = get_author_initials(author);
2439 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2440 }
2442 static bool
2443 draw_mode(struct view *view, mode_t mode)
2444 {
2445 const char *str;
2447 if (S_ISDIR(mode))
2448 str = "drwxr-xr-x";
2449 else if (S_ISLNK(mode))
2450 str = "lrwxrwxrwx";
2451 else if (S_ISGITLINK(mode))
2452 str = "m---------";
2453 else if (S_ISREG(mode) && mode & S_IXUSR)
2454 str = "-rwxr-xr-x";
2455 else if (S_ISREG(mode))
2456 str = "-rw-r--r--";
2457 else
2458 str = "----------";
2460 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2461 }
2463 static bool
2464 draw_lineno(struct view *view, unsigned int lineno)
2465 {
2466 char number[10];
2467 int digits3 = view->digits < 3 ? 3 : view->digits;
2468 int max = MIN(view->width + view->yoffset - view->col, digits3);
2469 char *text = NULL;
2470 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2472 lineno += view->offset + 1;
2473 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2474 static char fmt[] = "%1ld";
2476 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2477 if (string_format(number, fmt, lineno))
2478 text = number;
2479 }
2480 if (text)
2481 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2482 else
2483 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2484 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2485 }
2487 static bool
2488 draw_view_line(struct view *view, unsigned int lineno)
2489 {
2490 struct line *line;
2491 bool selected = (view->offset + lineno == view->lineno);
2493 assert(view_is_displayed(view));
2495 if (view->offset + lineno >= view->lines)
2496 return FALSE;
2498 line = &view->line[view->offset + lineno];
2500 wmove(view->win, lineno, 0);
2501 if (line->cleareol)
2502 wclrtoeol(view->win);
2503 view->col = 0;
2504 view->curline = line;
2505 view->curtype = LINE_NONE;
2506 line->selected = FALSE;
2507 line->dirty = line->cleareol = 0;
2509 if (selected) {
2510 set_view_attr(view, LINE_CURSOR);
2511 line->selected = TRUE;
2512 view->ops->select(view, line);
2513 }
2515 return view->ops->draw(view, line, lineno);
2516 }
2518 static void
2519 redraw_view_dirty(struct view *view)
2520 {
2521 bool dirty = FALSE;
2522 int lineno;
2524 for (lineno = 0; lineno < view->height; lineno++) {
2525 if (view->offset + lineno >= view->lines)
2526 break;
2527 if (!view->line[view->offset + lineno].dirty)
2528 continue;
2529 dirty = TRUE;
2530 if (!draw_view_line(view, lineno))
2531 break;
2532 }
2534 if (!dirty)
2535 return;
2536 wnoutrefresh(view->win);
2537 }
2539 static void
2540 redraw_view_from(struct view *view, int lineno)
2541 {
2542 assert(0 <= lineno && lineno < view->height);
2544 for (; lineno < view->height; lineno++) {
2545 if (!draw_view_line(view, lineno))
2546 break;
2547 }
2549 wnoutrefresh(view->win);
2550 }
2552 static void
2553 redraw_view(struct view *view)
2554 {
2555 werase(view->win);
2556 redraw_view_from(view, 0);
2557 }
2560 static void
2561 update_view_title(struct view *view)
2562 {
2563 char buf[SIZEOF_STR];
2564 char state[SIZEOF_STR];
2565 size_t bufpos = 0, statelen = 0;
2567 assert(view_is_displayed(view));
2569 if (view->type != VIEW_STATUS && view->lines) {
2570 unsigned int view_lines = view->offset + view->height;
2571 unsigned int lines = view->lines
2572 ? MIN(view_lines, view->lines) * 100 / view->lines
2573 : 0;
2575 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2576 view->ops->type,
2577 view->lineno + 1,
2578 view->lines,
2579 lines);
2581 }
2583 if (view->pipe) {
2584 time_t secs = time(NULL) - view->start_time;
2586 /* Three git seconds are a long time ... */
2587 if (secs > 2)
2588 string_format_from(state, &statelen, " loading %lds", secs);
2589 }
2591 string_format_from(buf, &bufpos, "[%s]", view->name);
2592 if (*view->ref && bufpos < view->width) {
2593 size_t refsize = strlen(view->ref);
2594 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2596 if (minsize < view->width)
2597 refsize = view->width - minsize + 7;
2598 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2599 }
2601 if (statelen && bufpos < view->width) {
2602 string_format_from(buf, &bufpos, "%s", state);
2603 }
2605 if (view == display[current_view])
2606 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2607 else
2608 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2610 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2611 wclrtoeol(view->title);
2612 wnoutrefresh(view->title);
2613 }
2615 static int
2616 apply_step(double step, int value)
2617 {
2618 if (step >= 1)
2619 return (int) step;
2620 value *= step + 0.01;
2621 return value ? value : 1;
2622 }
2624 static void
2625 resize_display(void)
2626 {
2627 int offset, i;
2628 struct view *base = display[0];
2629 struct view *view = display[1] ? display[1] : display[0];
2631 /* Setup window dimensions */
2633 getmaxyx(stdscr, base->height, base->width);
2635 /* Make room for the status window. */
2636 base->height -= 1;
2638 if (view != base) {
2639 /* Horizontal split. */
2640 view->width = base->width;
2641 view->height = apply_step(opt_scale_split_view, base->height);
2642 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2643 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2644 base->height -= view->height;
2646 /* Make room for the title bar. */
2647 view->height -= 1;
2648 }
2650 /* Make room for the title bar. */
2651 base->height -= 1;
2653 offset = 0;
2655 foreach_displayed_view (view, i) {
2656 if (!view->win) {
2657 view->win = newwin(view->height, 0, offset, 0);
2658 if (!view->win)
2659 die("Failed to create %s view", view->name);
2661 scrollok(view->win, FALSE);
2663 view->title = newwin(1, 0, offset + view->height, 0);
2664 if (!view->title)
2665 die("Failed to create title window");
2667 } else {
2668 wresize(view->win, view->height, view->width);
2669 mvwin(view->win, offset, 0);
2670 mvwin(view->title, offset + view->height, 0);
2671 }
2673 offset += view->height + 1;
2674 }
2675 }
2677 static void
2678 redraw_display(bool clear)
2679 {
2680 struct view *view;
2681 int i;
2683 foreach_displayed_view (view, i) {
2684 if (clear)
2685 wclear(view->win);
2686 redraw_view(view);
2687 update_view_title(view);
2688 }
2689 }
2692 /*
2693 * Option management
2694 */
2696 static void
2697 toggle_enum_option_do(unsigned int *opt, const char *help,
2698 const struct enum_map *map, size_t size)
2699 {
2700 *opt = (*opt + 1) % size;
2701 redraw_display(FALSE);
2702 report("Displaying %s %s", enum_name(map[*opt]), help);
2703 }
2705 #define toggle_enum_option(opt, help, map) \
2706 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2708 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2709 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2711 static void
2712 toggle_view_option(bool *option, const char *help)
2713 {
2714 *option = !*option;
2715 redraw_display(FALSE);
2716 report("%sabling %s", *option ? "En" : "Dis", help);
2717 }
2719 static void
2720 open_option_menu(void)
2721 {
2722 const struct menu_item menu[] = {
2723 { '.', "line numbers", &opt_line_number },
2724 { 'D', "date display", &opt_date },
2725 { 'A', "author display", &opt_author },
2726 { 'g', "revision graph display", &opt_rev_graph },
2727 { 'F', "reference display", &opt_show_refs },
2728 { 0 }
2729 };
2730 int selected = 0;
2732 if (prompt_menu("Toggle option", menu, &selected)) {
2733 if (menu[selected].data == &opt_date)
2734 toggle_date();
2735 else if (menu[selected].data == &opt_author)
2736 toggle_author();
2737 else
2738 toggle_view_option(menu[selected].data, menu[selected].text);
2739 }
2740 }
2742 static void
2743 maximize_view(struct view *view)
2744 {
2745 memset(display, 0, sizeof(display));
2746 current_view = 0;
2747 display[current_view] = view;
2748 resize_display();
2749 redraw_display(FALSE);
2750 report("");
2751 }
2754 /*
2755 * Navigation
2756 */
2758 static bool
2759 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2760 {
2761 if (lineno >= view->lines)
2762 lineno = view->lines > 0 ? view->lines - 1 : 0;
2764 if (offset > lineno || offset + view->height <= lineno) {
2765 unsigned long half = view->height / 2;
2767 if (lineno > half)
2768 offset = lineno - half;
2769 else
2770 offset = 0;
2771 }
2773 if (offset != view->offset || lineno != view->lineno) {
2774 view->offset = offset;
2775 view->lineno = lineno;
2776 return TRUE;
2777 }
2779 return FALSE;
2780 }
2782 /* Scrolling backend */
2783 static void
2784 do_scroll_view(struct view *view, int lines)
2785 {
2786 bool redraw_current_line = FALSE;
2788 /* The rendering expects the new offset. */
2789 view->offset += lines;
2791 assert(0 <= view->offset && view->offset < view->lines);
2792 assert(lines);
2794 /* Move current line into the view. */
2795 if (view->lineno < view->offset) {
2796 view->lineno = view->offset;
2797 redraw_current_line = TRUE;
2798 } else if (view->lineno >= view->offset + view->height) {
2799 view->lineno = view->offset + view->height - 1;
2800 redraw_current_line = TRUE;
2801 }
2803 assert(view->offset <= view->lineno && view->lineno < view->lines);
2805 /* Redraw the whole screen if scrolling is pointless. */
2806 if (view->height < ABS(lines)) {
2807 redraw_view(view);
2809 } else {
2810 int line = lines > 0 ? view->height - lines : 0;
2811 int end = line + ABS(lines);
2813 scrollok(view->win, TRUE);
2814 wscrl(view->win, lines);
2815 scrollok(view->win, FALSE);
2817 while (line < end && draw_view_line(view, line))
2818 line++;
2820 if (redraw_current_line)
2821 draw_view_line(view, view->lineno - view->offset);
2822 wnoutrefresh(view->win);
2823 }
2825 view->has_scrolled = TRUE;
2826 report("");
2827 }
2829 /* Scroll frontend */
2830 static void
2831 scroll_view(struct view *view, enum request request)
2832 {
2833 int lines = 1;
2835 assert(view_is_displayed(view));
2837 switch (request) {
2838 case REQ_SCROLL_LEFT:
2839 if (view->yoffset == 0) {
2840 report("Cannot scroll beyond the first column");
2841 return;
2842 }
2843 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2844 view->yoffset = 0;
2845 else
2846 view->yoffset -= apply_step(opt_hscroll, view->width);
2847 redraw_view_from(view, 0);
2848 report("");
2849 return;
2850 case REQ_SCROLL_RIGHT:
2851 view->yoffset += apply_step(opt_hscroll, view->width);
2852 redraw_view(view);
2853 report("");
2854 return;
2855 case REQ_SCROLL_PAGE_DOWN:
2856 lines = view->height;
2857 case REQ_SCROLL_LINE_DOWN:
2858 if (view->offset + lines > view->lines)
2859 lines = view->lines - view->offset;
2861 if (lines == 0 || view->offset + view->height >= view->lines) {
2862 report("Cannot scroll beyond the last line");
2863 return;
2864 }
2865 break;
2867 case REQ_SCROLL_PAGE_UP:
2868 lines = view->height;
2869 case REQ_SCROLL_LINE_UP:
2870 if (lines > view->offset)
2871 lines = view->offset;
2873 if (lines == 0) {
2874 report("Cannot scroll beyond the first line");
2875 return;
2876 }
2878 lines = -lines;
2879 break;
2881 default:
2882 die("request %d not handled in switch", request);
2883 }
2885 do_scroll_view(view, lines);
2886 }
2888 /* Cursor moving */
2889 static void
2890 move_view(struct view *view, enum request request)
2891 {
2892 int scroll_steps = 0;
2893 int steps;
2895 switch (request) {
2896 case REQ_MOVE_FIRST_LINE:
2897 steps = -view->lineno;
2898 break;
2900 case REQ_MOVE_LAST_LINE:
2901 steps = view->lines - view->lineno - 1;
2902 break;
2904 case REQ_MOVE_PAGE_UP:
2905 steps = view->height > view->lineno
2906 ? -view->lineno : -view->height;
2907 break;
2909 case REQ_MOVE_PAGE_DOWN:
2910 steps = view->lineno + view->height >= view->lines
2911 ? view->lines - view->lineno - 1 : view->height;
2912 break;
2914 case REQ_MOVE_UP:
2915 steps = -1;
2916 break;
2918 case REQ_MOVE_DOWN:
2919 steps = 1;
2920 break;
2922 default:
2923 die("request %d not handled in switch", request);
2924 }
2926 if (steps <= 0 && view->lineno == 0) {
2927 report("Cannot move beyond the first line");
2928 return;
2930 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2931 report("Cannot move beyond the last line");
2932 return;
2933 }
2935 /* Move the current line */
2936 view->lineno += steps;
2937 assert(0 <= view->lineno && view->lineno < view->lines);
2939 /* Check whether the view needs to be scrolled */
2940 if (view->lineno < view->offset ||
2941 view->lineno >= view->offset + view->height) {
2942 scroll_steps = steps;
2943 if (steps < 0 && -steps > view->offset) {
2944 scroll_steps = -view->offset;
2946 } else if (steps > 0) {
2947 if (view->lineno == view->lines - 1 &&
2948 view->lines > view->height) {
2949 scroll_steps = view->lines - view->offset - 1;
2950 if (scroll_steps >= view->height)
2951 scroll_steps -= view->height - 1;
2952 }
2953 }
2954 }
2956 if (!view_is_displayed(view)) {
2957 view->offset += scroll_steps;
2958 assert(0 <= view->offset && view->offset < view->lines);
2959 view->ops->select(view, &view->line[view->lineno]);
2960 return;
2961 }
2963 /* Repaint the old "current" line if we be scrolling */
2964 if (ABS(steps) < view->height)
2965 draw_view_line(view, view->lineno - steps - view->offset);
2967 if (scroll_steps) {
2968 do_scroll_view(view, scroll_steps);
2969 return;
2970 }
2972 /* Draw the current line */
2973 draw_view_line(view, view->lineno - view->offset);
2975 wnoutrefresh(view->win);
2976 report("");
2977 }
2980 /*
2981 * Searching
2982 */
2984 static void search_view(struct view *view, enum request request);
2986 static bool
2987 grep_text(struct view *view, const char *text[])
2988 {
2989 regmatch_t pmatch;
2990 size_t i;
2992 for (i = 0; text[i]; i++)
2993 if (*text[i] &&
2994 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2995 return TRUE;
2996 return FALSE;
2997 }
2999 static void
3000 select_view_line(struct view *view, unsigned long lineno)
3001 {
3002 unsigned long old_lineno = view->lineno;
3003 unsigned long old_offset = view->offset;
3005 if (goto_view_line(view, view->offset, lineno)) {
3006 if (view_is_displayed(view)) {
3007 if (old_offset != view->offset) {
3008 redraw_view(view);
3009 } else {
3010 draw_view_line(view, old_lineno - view->offset);
3011 draw_view_line(view, view->lineno - view->offset);
3012 wnoutrefresh(view->win);
3013 }
3014 } else {
3015 view->ops->select(view, &view->line[view->lineno]);
3016 }
3017 }
3018 }
3020 static void
3021 find_next(struct view *view, enum request request)
3022 {
3023 unsigned long lineno = view->lineno;
3024 int direction;
3026 if (!*view->grep) {
3027 if (!*opt_search)
3028 report("No previous search");
3029 else
3030 search_view(view, request);
3031 return;
3032 }
3034 switch (request) {
3035 case REQ_SEARCH:
3036 case REQ_FIND_NEXT:
3037 direction = 1;
3038 break;
3040 case REQ_SEARCH_BACK:
3041 case REQ_FIND_PREV:
3042 direction = -1;
3043 break;
3045 default:
3046 return;
3047 }
3049 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3050 lineno += direction;
3052 /* Note, lineno is unsigned long so will wrap around in which case it
3053 * will become bigger than view->lines. */
3054 for (; lineno < view->lines; lineno += direction) {
3055 if (view->ops->grep(view, &view->line[lineno])) {
3056 select_view_line(view, lineno);
3057 report("Line %ld matches '%s'", lineno + 1, view->grep);
3058 return;
3059 }
3060 }
3062 report("No match found for '%s'", view->grep);
3063 }
3065 static void
3066 search_view(struct view *view, enum request request)
3067 {
3068 int regex_err;
3070 if (view->regex) {
3071 regfree(view->regex);
3072 *view->grep = 0;
3073 } else {
3074 view->regex = calloc(1, sizeof(*view->regex));
3075 if (!view->regex)
3076 return;
3077 }
3079 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3080 if (regex_err != 0) {
3081 char buf[SIZEOF_STR] = "unknown error";
3083 regerror(regex_err, view->regex, buf, sizeof(buf));
3084 report("Search failed: %s", buf);
3085 return;
3086 }
3088 string_copy(view->grep, opt_search);
3090 find_next(view, request);
3091 }
3093 /*
3094 * Incremental updating
3095 */
3097 static void
3098 reset_view(struct view *view)
3099 {
3100 int i;
3102 for (i = 0; i < view->lines; i++)
3103 free(view->line[i].data);
3104 free(view->line);
3106 view->p_offset = view->offset;
3107 view->p_yoffset = view->yoffset;
3108 view->p_lineno = view->lineno;
3110 view->line = NULL;
3111 view->offset = 0;
3112 view->yoffset = 0;
3113 view->lines = 0;
3114 view->lineno = 0;
3115 view->vid[0] = 0;
3116 view->update_secs = 0;
3117 }
3119 static const char *
3120 format_arg(const char *name)
3121 {
3122 static struct {
3123 const char *name;
3124 size_t namelen;
3125 const char *value;
3126 const char *value_if_empty;
3127 } vars[] = {
3128 #define FORMAT_VAR(name, value, value_if_empty) \
3129 { name, STRING_SIZE(name), value, value_if_empty }
3130 FORMAT_VAR("%(directory)", opt_path, ""),
3131 FORMAT_VAR("%(file)", opt_file, ""),
3132 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3133 FORMAT_VAR("%(head)", ref_head, ""),
3134 FORMAT_VAR("%(commit)", ref_commit, ""),
3135 FORMAT_VAR("%(blob)", ref_blob, ""),
3136 FORMAT_VAR("%(branch)", ref_branch, ""),
3137 };
3138 int i;
3140 for (i = 0; i < ARRAY_SIZE(vars); i++)
3141 if (!strncmp(name, vars[i].name, vars[i].namelen))
3142 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3144 report("Unknown replacement: `%s`", name);
3145 return NULL;
3146 }
3148 static bool
3149 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3150 {
3151 char buf[SIZEOF_STR];
3152 int argc;
3154 argv_free(dst_argv);
3156 for (argc = 0; src_argv[argc]; argc++) {
3157 const char *arg = src_argv[argc];
3158 size_t bufpos = 0;
3160 while (arg) {
3161 char *next = strstr(arg, "%(");
3162 int len = next - arg;
3163 const char *value;
3165 if (!next || !replace) {
3166 len = strlen(arg);
3167 value = "";
3169 } else {
3170 value = format_arg(next);
3172 if (!value) {
3173 return FALSE;
3174 }
3175 }
3177 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3178 return FALSE;
3180 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3181 }
3183 dst_argv[argc] = strdup(buf);
3184 if (!dst_argv[argc])
3185 break;
3186 }
3188 dst_argv[argc] = NULL;
3190 return src_argv[argc] == NULL;
3191 }
3193 static bool
3194 restore_view_position(struct view *view)
3195 {
3196 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3197 return FALSE;
3199 /* Changing the view position cancels the restoring. */
3200 /* FIXME: Changing back to the first line is not detected. */
3201 if (view->offset != 0 || view->lineno != 0) {
3202 view->p_restore = FALSE;
3203 return FALSE;
3204 }
3206 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3207 view_is_displayed(view))
3208 werase(view->win);
3210 view->yoffset = view->p_yoffset;
3211 view->p_restore = FALSE;
3213 return TRUE;
3214 }
3216 static void
3217 end_update(struct view *view, bool force)
3218 {
3219 if (!view->pipe)
3220 return;
3221 while (!view->ops->read(view, NULL))
3222 if (!force)
3223 return;
3224 if (force)
3225 io_kill(view->pipe);
3226 io_done(view->pipe);
3227 view->pipe = NULL;
3228 }
3230 static void
3231 setup_update(struct view *view, const char *vid)
3232 {
3233 reset_view(view);
3234 string_copy_rev(view->vid, vid);
3235 view->pipe = &view->io;
3236 view->start_time = time(NULL);
3237 }
3239 static bool
3240 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3241 {
3242 io_init(&view->io);
3243 view->dir = dir;
3244 return format_argv(view->argv, argv, replace);
3245 }
3247 static bool
3248 prepare_update(struct view *view, const char *argv[], const char *dir)
3249 {
3250 if (view->pipe)
3251 end_update(view, TRUE);
3252 return prepare_io(view, dir, argv, FALSE);
3253 }
3255 static bool
3256 start_update(struct view *view, const char **argv, const char *dir)
3257 {
3258 if (view->pipe)
3259 io_done(view->pipe);
3260 return prepare_io(view, dir, argv, FALSE) &&
3261 io_start(&view->io, IO_RD, dir, view->argv);
3262 }
3264 static bool
3265 prepare_update_file(struct view *view, const char *name)
3266 {
3267 if (view->pipe)
3268 end_update(view, TRUE);
3269 argv_free(view->argv);
3270 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3271 }
3273 static bool
3274 begin_update(struct view *view, bool refresh)
3275 {
3276 if (view->pipe)
3277 end_update(view, TRUE);
3279 if (!refresh) {
3280 if (view->ops->prepare) {
3281 if (!view->ops->prepare(view))
3282 return FALSE;
3283 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3284 return FALSE;
3285 }
3287 /* Put the current ref_* value to the view title ref
3288 * member. This is needed by the blob view. Most other
3289 * views sets it automatically after loading because the
3290 * first line is a commit line. */
3291 string_copy_rev(view->ref, view->id);
3292 }
3294 if (view->argv[0] && !io_start(&view->io, IO_RD, view->dir, view->argv))
3295 return FALSE;
3297 setup_update(view, view->id);
3299 return TRUE;
3300 }
3302 static bool
3303 update_view(struct view *view)
3304 {
3305 char out_buffer[BUFSIZ * 2];
3306 char *line;
3307 /* Clear the view and redraw everything since the tree sorting
3308 * might have rearranged things. */
3309 bool redraw = view->lines == 0;
3310 bool can_read = TRUE;
3312 if (!view->pipe)
3313 return TRUE;
3315 if (!io_can_read(view->pipe)) {
3316 if (view->lines == 0 && view_is_displayed(view)) {
3317 time_t secs = time(NULL) - view->start_time;
3319 if (secs > 1 && secs > view->update_secs) {
3320 if (view->update_secs == 0)
3321 redraw_view(view);
3322 update_view_title(view);
3323 view->update_secs = secs;
3324 }
3325 }
3326 return TRUE;
3327 }
3329 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3330 if (opt_iconv_in != ICONV_NONE) {
3331 ICONV_CONST char *inbuf = line;
3332 size_t inlen = strlen(line) + 1;
3334 char *outbuf = out_buffer;
3335 size_t outlen = sizeof(out_buffer);
3337 size_t ret;
3339 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3340 if (ret != (size_t) -1)
3341 line = out_buffer;
3342 }
3344 if (!view->ops->read(view, line)) {
3345 report("Allocation failure");
3346 end_update(view, TRUE);
3347 return FALSE;
3348 }
3349 }
3351 {
3352 unsigned long lines = view->lines;
3353 int digits;
3355 for (digits = 0; lines; digits++)
3356 lines /= 10;
3358 /* Keep the displayed view in sync with line number scaling. */
3359 if (digits != view->digits) {
3360 view->digits = digits;
3361 if (opt_line_number || view->type == VIEW_BLAME)
3362 redraw = TRUE;
3363 }
3364 }
3366 if (io_error(view->pipe)) {
3367 report("Failed to read: %s", io_strerror(view->pipe));
3368 end_update(view, TRUE);
3370 } else if (io_eof(view->pipe)) {
3371 if (view_is_displayed(view))
3372 report("");
3373 end_update(view, FALSE);
3374 }
3376 if (restore_view_position(view))
3377 redraw = TRUE;
3379 if (!view_is_displayed(view))
3380 return TRUE;
3382 if (redraw)
3383 redraw_view_from(view, 0);
3384 else
3385 redraw_view_dirty(view);
3387 /* Update the title _after_ the redraw so that if the redraw picks up a
3388 * commit reference in view->ref it'll be available here. */
3389 update_view_title(view);
3390 return TRUE;
3391 }
3393 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3395 static struct line *
3396 add_line_data(struct view *view, void *data, enum line_type type)
3397 {
3398 struct line *line;
3400 if (!realloc_lines(&view->line, view->lines, 1))
3401 return NULL;
3403 line = &view->line[view->lines++];
3404 memset(line, 0, sizeof(*line));
3405 line->type = type;
3406 line->data = data;
3407 line->dirty = 1;
3409 return line;
3410 }
3412 static struct line *
3413 add_line_text(struct view *view, const char *text, enum line_type type)
3414 {
3415 char *data = text ? strdup(text) : NULL;
3417 return data ? add_line_data(view, data, type) : NULL;
3418 }
3420 static struct line *
3421 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3422 {
3423 char buf[SIZEOF_STR];
3424 va_list args;
3426 va_start(args, fmt);
3427 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3428 buf[0] = 0;
3429 va_end(args);
3431 return buf[0] ? add_line_text(view, buf, type) : NULL;
3432 }
3434 /*
3435 * View opening
3436 */
3438 enum open_flags {
3439 OPEN_DEFAULT = 0, /* Use default view switching. */
3440 OPEN_SPLIT = 1, /* Split current view. */
3441 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3442 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3443 OPEN_PREPARED = 32, /* Open already prepared command. */
3444 };
3446 static void
3447 open_view(struct view *prev, enum request request, enum open_flags flags)
3448 {
3449 bool split = !!(flags & OPEN_SPLIT);
3450 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3451 bool nomaximize = !!(flags & OPEN_REFRESH);
3452 struct view *view = VIEW(request);
3453 int nviews = displayed_views();
3454 struct view *base_view = display[0];
3456 if (view == prev && nviews == 1 && !reload) {
3457 report("Already in %s view", view->name);
3458 return;
3459 }
3461 if (view->git_dir && !opt_git_dir[0]) {
3462 report("The %s view is disabled in pager view", view->name);
3463 return;
3464 }
3466 if (split) {
3467 display[1] = view;
3468 current_view = 1;
3469 view->parent = prev;
3470 } else if (!nomaximize) {
3471 /* Maximize the current view. */
3472 memset(display, 0, sizeof(display));
3473 current_view = 0;
3474 display[current_view] = view;
3475 }
3477 /* No prev signals that this is the first loaded view. */
3478 if (prev && view != prev) {
3479 view->prev = prev;
3480 }
3482 /* Resize the view when switching between split- and full-screen,
3483 * or when switching between two different full-screen views. */
3484 if (nviews != displayed_views() ||
3485 (nviews == 1 && base_view != display[0]))
3486 resize_display();
3488 if (view->ops->open) {
3489 if (view->pipe)
3490 end_update(view, TRUE);
3491 if (!view->ops->open(view)) {
3492 report("Failed to load %s view", view->name);
3493 return;
3494 }
3495 restore_view_position(view);
3497 } else if ((reload || strcmp(view->vid, view->id)) &&
3498 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3499 report("Failed to load %s view", view->name);
3500 return;
3501 }
3503 if (split && prev->lineno - prev->offset >= prev->height) {
3504 /* Take the title line into account. */
3505 int lines = prev->lineno - prev->offset - prev->height + 1;
3507 /* Scroll the view that was split if the current line is
3508 * outside the new limited view. */
3509 do_scroll_view(prev, lines);
3510 }
3512 if (prev && view != prev && split && view_is_displayed(prev)) {
3513 /* "Blur" the previous view. */
3514 update_view_title(prev);
3515 }
3517 if (view->pipe && view->lines == 0) {
3518 /* Clear the old view and let the incremental updating refill
3519 * the screen. */
3520 werase(view->win);
3521 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3522 report("");
3523 } else if (view_is_displayed(view)) {
3524 redraw_view(view);
3525 report("");
3526 }
3527 }
3529 static void
3530 open_external_viewer(const char *argv[], const char *dir)
3531 {
3532 def_prog_mode(); /* save current tty modes */
3533 endwin(); /* restore original tty modes */
3534 io_run_fg(argv, dir);
3535 fprintf(stderr, "Press Enter to continue");
3536 getc(opt_tty);
3537 reset_prog_mode();
3538 redraw_display(TRUE);
3539 }
3541 static void
3542 open_mergetool(const char *file)
3543 {
3544 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3546 open_external_viewer(mergetool_argv, opt_cdup);
3547 }
3549 static void
3550 open_editor(const char *file)
3551 {
3552 const char *editor_argv[] = { "vi", file, NULL };
3553 const char *editor;
3555 editor = getenv("GIT_EDITOR");
3556 if (!editor && *opt_editor)
3557 editor = opt_editor;
3558 if (!editor)
3559 editor = getenv("VISUAL");
3560 if (!editor)
3561 editor = getenv("EDITOR");
3562 if (!editor)
3563 editor = "vi";
3565 editor_argv[0] = editor;
3566 open_external_viewer(editor_argv, opt_cdup);
3567 }
3569 static void
3570 open_run_request(enum request request)
3571 {
3572 struct run_request *req = get_run_request(request);
3573 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3575 if (!req) {
3576 report("Unknown run request");
3577 return;
3578 }
3580 if (format_argv(argv, req->argv, TRUE))
3581 open_external_viewer(argv, NULL);
3582 argv_free(argv);
3583 }
3585 /*
3586 * User request switch noodle
3587 */
3589 static int
3590 view_driver(struct view *view, enum request request)
3591 {
3592 int i;
3594 if (request == REQ_NONE)
3595 return TRUE;
3597 if (request > REQ_NONE) {
3598 open_run_request(request);
3599 view_request(view, REQ_REFRESH);
3600 return TRUE;
3601 }
3603 request = view_request(view, request);
3604 if (request == REQ_NONE)
3605 return TRUE;
3607 switch (request) {
3608 case REQ_MOVE_UP:
3609 case REQ_MOVE_DOWN:
3610 case REQ_MOVE_PAGE_UP:
3611 case REQ_MOVE_PAGE_DOWN:
3612 case REQ_MOVE_FIRST_LINE:
3613 case REQ_MOVE_LAST_LINE:
3614 move_view(view, request);
3615 break;
3617 case REQ_SCROLL_LEFT:
3618 case REQ_SCROLL_RIGHT:
3619 case REQ_SCROLL_LINE_DOWN:
3620 case REQ_SCROLL_LINE_UP:
3621 case REQ_SCROLL_PAGE_DOWN:
3622 case REQ_SCROLL_PAGE_UP:
3623 scroll_view(view, request);
3624 break;
3626 case REQ_VIEW_BLAME:
3627 if (!opt_file[0]) {
3628 report("No file chosen, press %s to open tree view",
3629 get_key(view->keymap, REQ_VIEW_TREE));
3630 break;
3631 }
3632 open_view(view, request, OPEN_DEFAULT);
3633 break;
3635 case REQ_VIEW_BLOB:
3636 if (!ref_blob[0]) {
3637 report("No file chosen, press %s to open tree view",
3638 get_key(view->keymap, REQ_VIEW_TREE));
3639 break;
3640 }
3641 open_view(view, request, OPEN_DEFAULT);
3642 break;
3644 case REQ_VIEW_PAGER:
3645 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3646 report("No pager content, press %s to run command from prompt",
3647 get_key(view->keymap, REQ_PROMPT));
3648 break;
3649 }
3650 open_view(view, request, OPEN_DEFAULT);
3651 break;
3653 case REQ_VIEW_STAGE:
3654 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3655 report("No stage content, press %s to open the status view and choose file",
3656 get_key(view->keymap, REQ_VIEW_STATUS));
3657 break;
3658 }
3659 open_view(view, request, OPEN_DEFAULT);
3660 break;
3662 case REQ_VIEW_STATUS:
3663 if (opt_is_inside_work_tree == FALSE) {
3664 report("The status view requires a working tree");
3665 break;
3666 }
3667 open_view(view, request, OPEN_DEFAULT);
3668 break;
3670 case REQ_VIEW_MAIN:
3671 case REQ_VIEW_DIFF:
3672 case REQ_VIEW_LOG:
3673 case REQ_VIEW_TREE:
3674 case REQ_VIEW_HELP:
3675 case REQ_VIEW_BRANCH:
3676 open_view(view, request, OPEN_DEFAULT);
3677 break;
3679 case REQ_NEXT:
3680 case REQ_PREVIOUS:
3681 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3683 if (view->parent) {
3684 int line;
3686 view = view->parent;
3687 line = view->lineno;
3688 move_view(view, request);
3689 if (view_is_displayed(view))
3690 update_view_title(view);
3691 if (line != view->lineno)
3692 view_request(view, REQ_ENTER);
3693 } else {
3694 move_view(view, request);
3695 }
3696 break;
3698 case REQ_VIEW_NEXT:
3699 {
3700 int nviews = displayed_views();
3701 int next_view = (current_view + 1) % nviews;
3703 if (next_view == current_view) {
3704 report("Only one view is displayed");
3705 break;
3706 }
3708 current_view = next_view;
3709 /* Blur out the title of the previous view. */
3710 update_view_title(view);
3711 report("");
3712 break;
3713 }
3714 case REQ_REFRESH:
3715 report("Refreshing is not yet supported for the %s view", view->name);
3716 break;
3718 case REQ_MAXIMIZE:
3719 if (displayed_views() == 2)
3720 maximize_view(view);
3721 break;
3723 case REQ_OPTIONS:
3724 open_option_menu();
3725 break;
3727 case REQ_TOGGLE_LINENO:
3728 toggle_view_option(&opt_line_number, "line numbers");
3729 break;
3731 case REQ_TOGGLE_DATE:
3732 toggle_date();
3733 break;
3735 case REQ_TOGGLE_AUTHOR:
3736 toggle_author();
3737 break;
3739 case REQ_TOGGLE_REV_GRAPH:
3740 toggle_view_option(&opt_rev_graph, "revision graph display");
3741 break;
3743 case REQ_TOGGLE_REFS:
3744 toggle_view_option(&opt_show_refs, "reference display");
3745 break;
3747 case REQ_TOGGLE_SORT_FIELD:
3748 case REQ_TOGGLE_SORT_ORDER:
3749 report("Sorting is not yet supported for the %s view", view->name);
3750 break;
3752 case REQ_SEARCH:
3753 case REQ_SEARCH_BACK:
3754 search_view(view, request);
3755 break;
3757 case REQ_FIND_NEXT:
3758 case REQ_FIND_PREV:
3759 find_next(view, request);
3760 break;
3762 case REQ_STOP_LOADING:
3763 foreach_view(view, i) {
3764 if (view->pipe)
3765 report("Stopped loading the %s view", view->name),
3766 end_update(view, TRUE);
3767 }
3768 break;
3770 case REQ_SHOW_VERSION:
3771 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3772 return TRUE;
3774 case REQ_SCREEN_REDRAW:
3775 redraw_display(TRUE);
3776 break;
3778 case REQ_EDIT:
3779 report("Nothing to edit");
3780 break;
3782 case REQ_ENTER:
3783 report("Nothing to enter");
3784 break;
3786 case REQ_VIEW_CLOSE:
3787 /* XXX: Mark closed views by letting view->prev point to the
3788 * view itself. Parents to closed view should never be
3789 * followed. */
3790 if (view->prev && view->prev != view) {
3791 maximize_view(view->prev);
3792 view->prev = view;
3793 break;
3794 }
3795 /* Fall-through */
3796 case REQ_QUIT:
3797 return FALSE;
3799 default:
3800 report("Unknown key, press %s for help",
3801 get_key(view->keymap, REQ_VIEW_HELP));
3802 return TRUE;
3803 }
3805 return TRUE;
3806 }
3809 /*
3810 * View backend utilities
3811 */
3813 enum sort_field {
3814 ORDERBY_NAME,
3815 ORDERBY_DATE,
3816 ORDERBY_AUTHOR,
3817 };
3819 struct sort_state {
3820 const enum sort_field *fields;
3821 size_t size, current;
3822 bool reverse;
3823 };
3825 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3826 #define get_sort_field(state) ((state).fields[(state).current])
3827 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3829 static void
3830 sort_view(struct view *view, enum request request, struct sort_state *state,
3831 int (*compare)(const void *, const void *))
3832 {
3833 switch (request) {
3834 case REQ_TOGGLE_SORT_FIELD:
3835 state->current = (state->current + 1) % state->size;
3836 break;
3838 case REQ_TOGGLE_SORT_ORDER:
3839 state->reverse = !state->reverse;
3840 break;
3841 default:
3842 die("Not a sort request");
3843 }
3845 qsort(view->line, view->lines, sizeof(*view->line), compare);
3846 redraw_view(view);
3847 }
3849 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3851 /* Small author cache to reduce memory consumption. It uses binary
3852 * search to lookup or find place to position new entries. No entries
3853 * are ever freed. */
3854 static const char *
3855 get_author(const char *name)
3856 {
3857 static const char **authors;
3858 static size_t authors_size;
3859 int from = 0, to = authors_size - 1;
3861 while (from <= to) {
3862 size_t pos = (to + from) / 2;
3863 int cmp = strcmp(name, authors[pos]);
3865 if (!cmp)
3866 return authors[pos];
3868 if (cmp < 0)
3869 to = pos - 1;
3870 else
3871 from = pos + 1;
3872 }
3874 if (!realloc_authors(&authors, authors_size, 1))
3875 return NULL;
3876 name = strdup(name);
3877 if (!name)
3878 return NULL;
3880 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3881 authors[from] = name;
3882 authors_size++;
3884 return name;
3885 }
3887 static void
3888 parse_timesec(struct time *time, const char *sec)
3889 {
3890 time->sec = (time_t) atol(sec);
3891 }
3893 static void
3894 parse_timezone(struct time *time, const char *zone)
3895 {
3896 long tz;
3898 tz = ('0' - zone[1]) * 60 * 60 * 10;
3899 tz += ('0' - zone[2]) * 60 * 60;
3900 tz += ('0' - zone[3]) * 60 * 10;
3901 tz += ('0' - zone[4]) * 60;
3903 if (zone[0] == '-')
3904 tz = -tz;
3906 time->tz = tz;
3907 time->sec -= tz;
3908 }
3910 /* Parse author lines where the name may be empty:
3911 * author <email@address.tld> 1138474660 +0100
3912 */
3913 static void
3914 parse_author_line(char *ident, const char **author, struct time *time)
3915 {
3916 char *nameend = strchr(ident, '<');
3917 char *emailend = strchr(ident, '>');
3919 if (nameend && emailend)
3920 *nameend = *emailend = 0;
3921 ident = chomp_string(ident);
3922 if (!*ident) {
3923 if (nameend)
3924 ident = chomp_string(nameend + 1);
3925 if (!*ident)
3926 ident = "Unknown";
3927 }
3929 *author = get_author(ident);
3931 /* Parse epoch and timezone */
3932 if (emailend && emailend[1] == ' ') {
3933 char *secs = emailend + 2;
3934 char *zone = strchr(secs, ' ');
3936 parse_timesec(time, secs);
3938 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3939 parse_timezone(time, zone + 1);
3940 }
3941 }
3943 static bool
3944 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3945 {
3946 char rev[SIZEOF_REV];
3947 const char *revlist_argv[] = {
3948 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3949 };
3950 struct menu_item *items;
3951 char text[SIZEOF_STR];
3952 bool ok = TRUE;
3953 int i;
3955 items = calloc(*parents + 1, sizeof(*items));
3956 if (!items)
3957 return FALSE;
3959 for (i = 0; i < *parents; i++) {
3960 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3961 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3962 !(items[i].text = strdup(text))) {
3963 ok = FALSE;
3964 break;
3965 }
3966 }
3968 if (ok) {
3969 *parents = 0;
3970 ok = prompt_menu("Select parent", items, parents);
3971 }
3972 for (i = 0; items[i].text; i++)
3973 free((char *) items[i].text);
3974 free(items);
3975 return ok;
3976 }
3978 static bool
3979 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3980 {
3981 char buf[SIZEOF_STR * 4];
3982 const char *revlist_argv[] = {
3983 "git", "log", "--no-color", "-1",
3984 "--pretty=format:%P", id, "--", path, NULL
3985 };
3986 int parents;
3988 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3989 (parents = strlen(buf) / 40) < 0) {
3990 report("Failed to get parent information");
3991 return FALSE;
3993 } else if (parents == 0) {
3994 if (path)
3995 report("Path '%s' does not exist in the parent", path);
3996 else
3997 report("The selected commit has no parents");
3998 return FALSE;
3999 }
4001 if (parents == 1)
4002 parents = 0;
4003 else if (!open_commit_parent_menu(buf, &parents))
4004 return FALSE;
4006 string_copy_rev(rev, &buf[41 * parents]);
4007 return TRUE;
4008 }
4010 /*
4011 * Pager backend
4012 */
4014 static bool
4015 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4016 {
4017 char text[SIZEOF_STR];
4019 if (opt_line_number && draw_lineno(view, lineno))
4020 return TRUE;
4022 string_expand(text, sizeof(text), line->data, opt_tab_size);
4023 draw_text(view, line->type, text, TRUE);
4024 return TRUE;
4025 }
4027 static bool
4028 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4029 {
4030 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4031 char ref[SIZEOF_STR];
4033 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4034 return TRUE;
4036 /* This is the only fatal call, since it can "corrupt" the buffer. */
4037 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4038 return FALSE;
4040 return TRUE;
4041 }
4043 static void
4044 add_pager_refs(struct view *view, struct line *line)
4045 {
4046 char buf[SIZEOF_STR];
4047 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4048 struct ref_list *list;
4049 size_t bufpos = 0, i;
4050 const char *sep = "Refs: ";
4051 bool is_tag = FALSE;
4053 assert(line->type == LINE_COMMIT);
4055 list = get_ref_list(commit_id);
4056 if (!list) {
4057 if (view->type == VIEW_DIFF)
4058 goto try_add_describe_ref;
4059 return;
4060 }
4062 for (i = 0; i < list->size; i++) {
4063 struct ref *ref = list->refs[i];
4064 const char *fmt = ref->tag ? "%s[%s]" :
4065 ref->remote ? "%s<%s>" : "%s%s";
4067 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4068 return;
4069 sep = ", ";
4070 if (ref->tag)
4071 is_tag = TRUE;
4072 }
4074 if (!is_tag && view->type == VIEW_DIFF) {
4075 try_add_describe_ref:
4076 /* Add <tag>-g<commit_id> "fake" reference. */
4077 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4078 return;
4079 }
4081 if (bufpos == 0)
4082 return;
4084 add_line_text(view, buf, LINE_PP_REFS);
4085 }
4087 static bool
4088 pager_read(struct view *view, char *data)
4089 {
4090 struct line *line;
4092 if (!data)
4093 return TRUE;
4095 line = add_line_text(view, data, get_line_type(data));
4096 if (!line)
4097 return FALSE;
4099 if (line->type == LINE_COMMIT &&
4100 (view->type == VIEW_DIFF ||
4101 view->type == VIEW_LOG))
4102 add_pager_refs(view, line);
4104 return TRUE;
4105 }
4107 static enum request
4108 pager_request(struct view *view, enum request request, struct line *line)
4109 {
4110 int split = 0;
4112 if (request != REQ_ENTER)
4113 return request;
4115 if (line->type == LINE_COMMIT &&
4116 (view->type == VIEW_LOG ||
4117 view->type == VIEW_PAGER)) {
4118 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4119 split = 1;
4120 }
4122 /* Always scroll the view even if it was split. That way
4123 * you can use Enter to scroll through the log view and
4124 * split open each commit diff. */
4125 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4127 /* FIXME: A minor workaround. Scrolling the view will call report("")
4128 * but if we are scrolling a non-current view this won't properly
4129 * update the view title. */
4130 if (split)
4131 update_view_title(view);
4133 return REQ_NONE;
4134 }
4136 static bool
4137 pager_grep(struct view *view, struct line *line)
4138 {
4139 const char *text[] = { line->data, NULL };
4141 return grep_text(view, text);
4142 }
4144 static void
4145 pager_select(struct view *view, struct line *line)
4146 {
4147 if (line->type == LINE_COMMIT) {
4148 char *text = (char *)line->data + STRING_SIZE("commit ");
4150 if (view->type != VIEW_PAGER)
4151 string_copy_rev(view->ref, text);
4152 string_copy_rev(ref_commit, text);
4153 }
4154 }
4156 static struct view_ops pager_ops = {
4157 "line",
4158 NULL,
4159 NULL,
4160 pager_read,
4161 pager_draw,
4162 pager_request,
4163 pager_grep,
4164 pager_select,
4165 };
4167 static const char *log_argv[SIZEOF_ARG] = {
4168 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4169 };
4171 static enum request
4172 log_request(struct view *view, enum request request, struct line *line)
4173 {
4174 switch (request) {
4175 case REQ_REFRESH:
4176 load_refs();
4177 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4178 return REQ_NONE;
4179 default:
4180 return pager_request(view, request, line);
4181 }
4182 }
4184 static struct view_ops log_ops = {
4185 "line",
4186 log_argv,
4187 NULL,
4188 pager_read,
4189 pager_draw,
4190 log_request,
4191 pager_grep,
4192 pager_select,
4193 };
4195 static const char *diff_argv[SIZEOF_ARG] = {
4196 "git", "show", "--pretty=fuller", "--no-color", "--root",
4197 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4198 };
4200 static struct view_ops diff_ops = {
4201 "line",
4202 diff_argv,
4203 NULL,
4204 pager_read,
4205 pager_draw,
4206 pager_request,
4207 pager_grep,
4208 pager_select,
4209 };
4211 /*
4212 * Help backend
4213 */
4215 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4217 static bool
4218 help_open_keymap_title(struct view *view, enum keymap keymap)
4219 {
4220 struct line *line;
4222 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4223 help_keymap_hidden[keymap] ? '+' : '-',
4224 enum_name(keymap_table[keymap]));
4225 if (line)
4226 line->other = keymap;
4228 return help_keymap_hidden[keymap];
4229 }
4231 static void
4232 help_open_keymap(struct view *view, enum keymap keymap)
4233 {
4234 const char *group = NULL;
4235 char buf[SIZEOF_STR];
4236 size_t bufpos;
4237 bool add_title = TRUE;
4238 int i;
4240 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4241 const char *key = NULL;
4243 if (req_info[i].request == REQ_NONE)
4244 continue;
4246 if (!req_info[i].request) {
4247 group = req_info[i].help;
4248 continue;
4249 }
4251 key = get_keys(keymap, req_info[i].request, TRUE);
4252 if (!key || !*key)
4253 continue;
4255 if (add_title && help_open_keymap_title(view, keymap))
4256 return;
4257 add_title = FALSE;
4259 if (group) {
4260 add_line_text(view, group, LINE_HELP_GROUP);
4261 group = NULL;
4262 }
4264 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4265 enum_name(req_info[i]), req_info[i].help);
4266 }
4268 group = "External commands:";
4270 for (i = 0; i < run_requests; i++) {
4271 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4272 const char *key;
4273 int argc;
4275 if (!req || req->keymap != keymap)
4276 continue;
4278 key = get_key_name(req->key);
4279 if (!*key)
4280 key = "(no key defined)";
4282 if (add_title && help_open_keymap_title(view, keymap))
4283 return;
4284 if (group) {
4285 add_line_text(view, group, LINE_HELP_GROUP);
4286 group = NULL;
4287 }
4289 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4290 if (!string_format_from(buf, &bufpos, "%s%s",
4291 argc ? " " : "", req->argv[argc]))
4292 return;
4294 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4295 }
4296 }
4298 static bool
4299 help_open(struct view *view)
4300 {
4301 enum keymap keymap;
4303 reset_view(view);
4304 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4305 add_line_text(view, "", LINE_DEFAULT);
4307 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4308 help_open_keymap(view, keymap);
4310 return TRUE;
4311 }
4313 static enum request
4314 help_request(struct view *view, enum request request, struct line *line)
4315 {
4316 switch (request) {
4317 case REQ_ENTER:
4318 if (line->type == LINE_HELP_KEYMAP) {
4319 help_keymap_hidden[line->other] =
4320 !help_keymap_hidden[line->other];
4321 view->p_restore = TRUE;
4322 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4323 }
4325 return REQ_NONE;
4326 default:
4327 return pager_request(view, request, line);
4328 }
4329 }
4331 static struct view_ops help_ops = {
4332 "line",
4333 NULL,
4334 help_open,
4335 NULL,
4336 pager_draw,
4337 help_request,
4338 pager_grep,
4339 pager_select,
4340 };
4343 /*
4344 * Tree backend
4345 */
4347 struct tree_stack_entry {
4348 struct tree_stack_entry *prev; /* Entry below this in the stack */
4349 unsigned long lineno; /* Line number to restore */
4350 char *name; /* Position of name in opt_path */
4351 };
4353 /* The top of the path stack. */
4354 static struct tree_stack_entry *tree_stack = NULL;
4355 unsigned long tree_lineno = 0;
4357 static void
4358 pop_tree_stack_entry(void)
4359 {
4360 struct tree_stack_entry *entry = tree_stack;
4362 tree_lineno = entry->lineno;
4363 entry->name[0] = 0;
4364 tree_stack = entry->prev;
4365 free(entry);
4366 }
4368 static void
4369 push_tree_stack_entry(const char *name, unsigned long lineno)
4370 {
4371 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4372 size_t pathlen = strlen(opt_path);
4374 if (!entry)
4375 return;
4377 entry->prev = tree_stack;
4378 entry->name = opt_path + pathlen;
4379 tree_stack = entry;
4381 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4382 pop_tree_stack_entry();
4383 return;
4384 }
4386 /* Move the current line to the first tree entry. */
4387 tree_lineno = 1;
4388 entry->lineno = lineno;
4389 }
4391 /* Parse output from git-ls-tree(1):
4392 *
4393 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4394 */
4396 #define SIZEOF_TREE_ATTR \
4397 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4399 #define SIZEOF_TREE_MODE \
4400 STRING_SIZE("100644 ")
4402 #define TREE_ID_OFFSET \
4403 STRING_SIZE("100644 blob ")
4405 struct tree_entry {
4406 char id[SIZEOF_REV];
4407 mode_t mode;
4408 struct time time; /* Date from the author ident. */
4409 const char *author; /* Author of the commit. */
4410 char name[1];
4411 };
4413 static const char *
4414 tree_path(const struct line *line)
4415 {
4416 return ((struct tree_entry *) line->data)->name;
4417 }
4419 static int
4420 tree_compare_entry(const struct line *line1, const struct line *line2)
4421 {
4422 if (line1->type != line2->type)
4423 return line1->type == LINE_TREE_DIR ? -1 : 1;
4424 return strcmp(tree_path(line1), tree_path(line2));
4425 }
4427 static const enum sort_field tree_sort_fields[] = {
4428 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4429 };
4430 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4432 static int
4433 tree_compare(const void *l1, const void *l2)
4434 {
4435 const struct line *line1 = (const struct line *) l1;
4436 const struct line *line2 = (const struct line *) l2;
4437 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4438 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4440 if (line1->type == LINE_TREE_HEAD)
4441 return -1;
4442 if (line2->type == LINE_TREE_HEAD)
4443 return 1;
4445 switch (get_sort_field(tree_sort_state)) {
4446 case ORDERBY_DATE:
4447 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4449 case ORDERBY_AUTHOR:
4450 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4452 case ORDERBY_NAME:
4453 default:
4454 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4455 }
4456 }
4459 static struct line *
4460 tree_entry(struct view *view, enum line_type type, const char *path,
4461 const char *mode, const char *id)
4462 {
4463 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4464 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4466 if (!entry || !line) {
4467 free(entry);
4468 return NULL;
4469 }
4471 strncpy(entry->name, path, strlen(path));
4472 if (mode)
4473 entry->mode = strtoul(mode, NULL, 8);
4474 if (id)
4475 string_copy_rev(entry->id, id);
4477 return line;
4478 }
4480 static bool
4481 tree_read_date(struct view *view, char *text, bool *read_date)
4482 {
4483 static const char *author_name;
4484 static struct time author_time;
4486 if (!text && *read_date) {
4487 *read_date = FALSE;
4488 return TRUE;
4490 } else if (!text) {
4491 char *path = *opt_path ? opt_path : ".";
4492 /* Find next entry to process */
4493 const char *log_file[] = {
4494 "git", "log", "--no-color", "--pretty=raw",
4495 "--cc", "--raw", view->id, "--", path, NULL
4496 };
4498 if (!view->lines) {
4499 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4500 report("Tree is empty");
4501 return TRUE;
4502 }
4504 if (!start_update(view, log_file, opt_cdup)) {
4505 report("Failed to load tree data");
4506 return TRUE;
4507 }
4509 *read_date = TRUE;
4510 return FALSE;
4512 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4513 parse_author_line(text + STRING_SIZE("author "),
4514 &author_name, &author_time);
4516 } else if (*text == ':') {
4517 char *pos;
4518 size_t annotated = 1;
4519 size_t i;
4521 pos = strchr(text, '\t');
4522 if (!pos)
4523 return TRUE;
4524 text = pos + 1;
4525 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4526 text += strlen(opt_path);
4527 pos = strchr(text, '/');
4528 if (pos)
4529 *pos = 0;
4531 for (i = 1; i < view->lines; i++) {
4532 struct line *line = &view->line[i];
4533 struct tree_entry *entry = line->data;
4535 annotated += !!entry->author;
4536 if (entry->author || strcmp(entry->name, text))
4537 continue;
4539 entry->author = author_name;
4540 entry->time = author_time;
4541 line->dirty = 1;
4542 break;
4543 }
4545 if (annotated == view->lines)
4546 io_kill(view->pipe);
4547 }
4548 return TRUE;
4549 }
4551 static bool
4552 tree_read(struct view *view, char *text)
4553 {
4554 static bool read_date = FALSE;
4555 struct tree_entry *data;
4556 struct line *entry, *line;
4557 enum line_type type;
4558 size_t textlen = text ? strlen(text) : 0;
4559 char *path = text + SIZEOF_TREE_ATTR;
4561 if (read_date || !text)
4562 return tree_read_date(view, text, &read_date);
4564 if (textlen <= SIZEOF_TREE_ATTR)
4565 return FALSE;
4566 if (view->lines == 0 &&
4567 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4568 return FALSE;
4570 /* Strip the path part ... */
4571 if (*opt_path) {
4572 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4573 size_t striplen = strlen(opt_path);
4575 if (pathlen > striplen)
4576 memmove(path, path + striplen,
4577 pathlen - striplen + 1);
4579 /* Insert "link" to parent directory. */
4580 if (view->lines == 1 &&
4581 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4582 return FALSE;
4583 }
4585 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4586 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4587 if (!entry)
4588 return FALSE;
4589 data = entry->data;
4591 /* Skip "Directory ..." and ".." line. */
4592 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4593 if (tree_compare_entry(line, entry) <= 0)
4594 continue;
4596 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4598 line->data = data;
4599 line->type = type;
4600 for (; line <= entry; line++)
4601 line->dirty = line->cleareol = 1;
4602 return TRUE;
4603 }
4605 if (tree_lineno > view->lineno) {
4606 view->lineno = tree_lineno;
4607 tree_lineno = 0;
4608 }
4610 return TRUE;
4611 }
4613 static bool
4614 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4615 {
4616 struct tree_entry *entry = line->data;
4618 if (line->type == LINE_TREE_HEAD) {
4619 if (draw_text(view, line->type, "Directory path /", TRUE))
4620 return TRUE;
4621 } else {
4622 if (draw_mode(view, entry->mode))
4623 return TRUE;
4625 if (opt_author && draw_author(view, entry->author))
4626 return TRUE;
4628 if (opt_date && draw_date(view, &entry->time))
4629 return TRUE;
4630 }
4631 if (draw_text(view, line->type, entry->name, TRUE))
4632 return TRUE;
4633 return TRUE;
4634 }
4636 static void
4637 open_blob_editor(const char *id)
4638 {
4639 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4640 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4641 int fd = mkstemp(file);
4643 if (fd == -1)
4644 report("Failed to create temporary file");
4645 else if (!io_run_append(blob_argv, fd))
4646 report("Failed to save blob data to file");
4647 else
4648 open_editor(file);
4649 if (fd != -1)
4650 unlink(file);
4651 }
4653 static enum request
4654 tree_request(struct view *view, enum request request, struct line *line)
4655 {
4656 enum open_flags flags;
4657 struct tree_entry *entry = line->data;
4659 switch (request) {
4660 case REQ_VIEW_BLAME:
4661 if (line->type != LINE_TREE_FILE) {
4662 report("Blame only supported for files");
4663 return REQ_NONE;
4664 }
4666 string_copy(opt_ref, view->vid);
4667 return request;
4669 case REQ_EDIT:
4670 if (line->type != LINE_TREE_FILE) {
4671 report("Edit only supported for files");
4672 } else if (!is_head_commit(view->vid)) {
4673 open_blob_editor(entry->id);
4674 } else {
4675 open_editor(opt_file);
4676 }
4677 return REQ_NONE;
4679 case REQ_TOGGLE_SORT_FIELD:
4680 case REQ_TOGGLE_SORT_ORDER:
4681 sort_view(view, request, &tree_sort_state, tree_compare);
4682 return REQ_NONE;
4684 case REQ_PARENT:
4685 if (!*opt_path) {
4686 /* quit view if at top of tree */
4687 return REQ_VIEW_CLOSE;
4688 }
4689 /* fake 'cd ..' */
4690 line = &view->line[1];
4691 break;
4693 case REQ_ENTER:
4694 break;
4696 default:
4697 return request;
4698 }
4700 /* Cleanup the stack if the tree view is at a different tree. */
4701 while (!*opt_path && tree_stack)
4702 pop_tree_stack_entry();
4704 switch (line->type) {
4705 case LINE_TREE_DIR:
4706 /* Depending on whether it is a subdirectory or parent link
4707 * mangle the path buffer. */
4708 if (line == &view->line[1] && *opt_path) {
4709 pop_tree_stack_entry();
4711 } else {
4712 const char *basename = tree_path(line);
4714 push_tree_stack_entry(basename, view->lineno);
4715 }
4717 /* Trees and subtrees share the same ID, so they are not not
4718 * unique like blobs. */
4719 flags = OPEN_RELOAD;
4720 request = REQ_VIEW_TREE;
4721 break;
4723 case LINE_TREE_FILE:
4724 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4725 request = REQ_VIEW_BLOB;
4726 break;
4728 default:
4729 return REQ_NONE;
4730 }
4732 open_view(view, request, flags);
4733 if (request == REQ_VIEW_TREE)
4734 view->lineno = tree_lineno;
4736 return REQ_NONE;
4737 }
4739 static bool
4740 tree_grep(struct view *view, struct line *line)
4741 {
4742 struct tree_entry *entry = line->data;
4743 const char *text[] = {
4744 entry->name,
4745 opt_author ? entry->author : "",
4746 mkdate(&entry->time, opt_date),
4747 NULL
4748 };
4750 return grep_text(view, text);
4751 }
4753 static void
4754 tree_select(struct view *view, struct line *line)
4755 {
4756 struct tree_entry *entry = line->data;
4758 if (line->type == LINE_TREE_FILE) {
4759 string_copy_rev(ref_blob, entry->id);
4760 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4762 } else if (line->type != LINE_TREE_DIR) {
4763 return;
4764 }
4766 string_copy_rev(view->ref, entry->id);
4767 }
4769 static bool
4770 tree_prepare(struct view *view)
4771 {
4772 if (view->lines == 0 && opt_prefix[0]) {
4773 char *pos = opt_prefix;
4775 while (pos && *pos) {
4776 char *end = strchr(pos, '/');
4778 if (end)
4779 *end = 0;
4780 push_tree_stack_entry(pos, 0);
4781 pos = end;
4782 if (end) {
4783 *end = '/';
4784 pos++;
4785 }
4786 }
4788 } else if (strcmp(view->vid, view->id)) {
4789 opt_path[0] = 0;
4790 }
4792 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4793 }
4795 static const char *tree_argv[SIZEOF_ARG] = {
4796 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4797 };
4799 static struct view_ops tree_ops = {
4800 "file",
4801 tree_argv,
4802 NULL,
4803 tree_read,
4804 tree_draw,
4805 tree_request,
4806 tree_grep,
4807 tree_select,
4808 tree_prepare,
4809 };
4811 static bool
4812 blob_read(struct view *view, char *line)
4813 {
4814 if (!line)
4815 return TRUE;
4816 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4817 }
4819 static enum request
4820 blob_request(struct view *view, enum request request, struct line *line)
4821 {
4822 switch (request) {
4823 case REQ_EDIT:
4824 open_blob_editor(view->vid);
4825 return REQ_NONE;
4826 default:
4827 return pager_request(view, request, line);
4828 }
4829 }
4831 static const char *blob_argv[SIZEOF_ARG] = {
4832 "git", "cat-file", "blob", "%(blob)", NULL
4833 };
4835 static struct view_ops blob_ops = {
4836 "line",
4837 blob_argv,
4838 NULL,
4839 blob_read,
4840 pager_draw,
4841 blob_request,
4842 pager_grep,
4843 pager_select,
4844 };
4846 /*
4847 * Blame backend
4848 *
4849 * Loading the blame view is a two phase job:
4850 *
4851 * 1. File content is read either using opt_file from the
4852 * filesystem or using git-cat-file.
4853 * 2. Then blame information is incrementally added by
4854 * reading output from git-blame.
4855 */
4857 struct blame_commit {
4858 char id[SIZEOF_REV]; /* SHA1 ID. */
4859 char title[128]; /* First line of the commit message. */
4860 const char *author; /* Author of the commit. */
4861 struct time time; /* Date from the author ident. */
4862 char filename[128]; /* Name of file. */
4863 bool has_previous; /* Was a "previous" line detected. */
4864 };
4866 struct blame {
4867 struct blame_commit *commit;
4868 unsigned long lineno;
4869 char text[1];
4870 };
4872 static bool
4873 blame_open(struct view *view)
4874 {
4875 char path[SIZEOF_STR];
4877 if (!view->prev && *opt_prefix) {
4878 string_copy(path, opt_file);
4879 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4880 return FALSE;
4881 }
4883 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4884 const char *blame_cat_file_argv[] = {
4885 "git", "cat-file", "blob", path, NULL
4886 };
4888 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4889 !start_update(view, blame_cat_file_argv, opt_cdup))
4890 return FALSE;
4891 }
4893 setup_update(view, opt_file);
4894 string_format(view->ref, "%s ...", opt_file);
4896 return TRUE;
4897 }
4899 static struct blame_commit *
4900 get_blame_commit(struct view *view, const char *id)
4901 {
4902 size_t i;
4904 for (i = 0; i < view->lines; i++) {
4905 struct blame *blame = view->line[i].data;
4907 if (!blame->commit)
4908 continue;
4910 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4911 return blame->commit;
4912 }
4914 {
4915 struct blame_commit *commit = calloc(1, sizeof(*commit));
4917 if (commit)
4918 string_ncopy(commit->id, id, SIZEOF_REV);
4919 return commit;
4920 }
4921 }
4923 static bool
4924 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4925 {
4926 const char *pos = *posref;
4928 *posref = NULL;
4929 pos = strchr(pos + 1, ' ');
4930 if (!pos || !isdigit(pos[1]))
4931 return FALSE;
4932 *number = atoi(pos + 1);
4933 if (*number < min || *number > max)
4934 return FALSE;
4936 *posref = pos;
4937 return TRUE;
4938 }
4940 static struct blame_commit *
4941 parse_blame_commit(struct view *view, const char *text, int *blamed)
4942 {
4943 struct blame_commit *commit;
4944 struct blame *blame;
4945 const char *pos = text + SIZEOF_REV - 2;
4946 size_t orig_lineno = 0;
4947 size_t lineno;
4948 size_t group;
4950 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4951 return NULL;
4953 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4954 !parse_number(&pos, &lineno, 1, view->lines) ||
4955 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4956 return NULL;
4958 commit = get_blame_commit(view, text);
4959 if (!commit)
4960 return NULL;
4962 *blamed += group;
4963 while (group--) {
4964 struct line *line = &view->line[lineno + group - 1];
4966 blame = line->data;
4967 blame->commit = commit;
4968 blame->lineno = orig_lineno + group - 1;
4969 line->dirty = 1;
4970 }
4972 return commit;
4973 }
4975 static bool
4976 blame_read_file(struct view *view, const char *line, bool *read_file)
4977 {
4978 if (!line) {
4979 const char *blame_argv[] = {
4980 "git", "blame", "--incremental",
4981 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4982 };
4984 if (view->lines == 0 && !view->prev)
4985 die("No blame exist for %s", view->vid);
4987 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4988 report("Failed to load blame data");
4989 return TRUE;
4990 }
4992 *read_file = FALSE;
4993 return FALSE;
4995 } else {
4996 size_t linelen = strlen(line);
4997 struct blame *blame = malloc(sizeof(*blame) + linelen);
4999 if (!blame)
5000 return FALSE;
5002 blame->commit = NULL;
5003 strncpy(blame->text, line, linelen);
5004 blame->text[linelen] = 0;
5005 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5006 }
5007 }
5009 static bool
5010 match_blame_header(const char *name, char **line)
5011 {
5012 size_t namelen = strlen(name);
5013 bool matched = !strncmp(name, *line, namelen);
5015 if (matched)
5016 *line += namelen;
5018 return matched;
5019 }
5021 static bool
5022 blame_read(struct view *view, char *line)
5023 {
5024 static struct blame_commit *commit = NULL;
5025 static int blamed = 0;
5026 static bool read_file = TRUE;
5028 if (read_file)
5029 return blame_read_file(view, line, &read_file);
5031 if (!line) {
5032 /* Reset all! */
5033 commit = NULL;
5034 blamed = 0;
5035 read_file = TRUE;
5036 string_format(view->ref, "%s", view->vid);
5037 if (view_is_displayed(view)) {
5038 update_view_title(view);
5039 redraw_view_from(view, 0);
5040 }
5041 return TRUE;
5042 }
5044 if (!commit) {
5045 commit = parse_blame_commit(view, line, &blamed);
5046 string_format(view->ref, "%s %2d%%", view->vid,
5047 view->lines ? blamed * 100 / view->lines : 0);
5049 } else if (match_blame_header("author ", &line)) {
5050 commit->author = get_author(line);
5052 } else if (match_blame_header("author-time ", &line)) {
5053 parse_timesec(&commit->time, line);
5055 } else if (match_blame_header("author-tz ", &line)) {
5056 parse_timezone(&commit->time, line);
5058 } else if (match_blame_header("summary ", &line)) {
5059 string_ncopy(commit->title, line, strlen(line));
5061 } else if (match_blame_header("previous ", &line)) {
5062 commit->has_previous = TRUE;
5064 } else if (match_blame_header("filename ", &line)) {
5065 string_ncopy(commit->filename, line, strlen(line));
5066 commit = NULL;
5067 }
5069 return TRUE;
5070 }
5072 static bool
5073 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5074 {
5075 struct blame *blame = line->data;
5076 struct time *time = NULL;
5077 const char *id = NULL, *author = NULL;
5078 char text[SIZEOF_STR];
5080 if (blame->commit && *blame->commit->filename) {
5081 id = blame->commit->id;
5082 author = blame->commit->author;
5083 time = &blame->commit->time;
5084 }
5086 if (opt_date && draw_date(view, time))
5087 return TRUE;
5089 if (opt_author && draw_author(view, author))
5090 return TRUE;
5092 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5093 return TRUE;
5095 if (draw_lineno(view, lineno))
5096 return TRUE;
5098 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5099 draw_text(view, LINE_DEFAULT, text, TRUE);
5100 return TRUE;
5101 }
5103 static bool
5104 check_blame_commit(struct blame *blame, bool check_null_id)
5105 {
5106 if (!blame->commit)
5107 report("Commit data not loaded yet");
5108 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5109 report("No commit exist for the selected line");
5110 else
5111 return TRUE;
5112 return FALSE;
5113 }
5115 static void
5116 setup_blame_parent_line(struct view *view, struct blame *blame)
5117 {
5118 const char *diff_tree_argv[] = {
5119 "git", "diff-tree", "-U0", blame->commit->id,
5120 "--", blame->commit->filename, NULL
5121 };
5122 struct io io = {};
5123 int parent_lineno = -1;
5124 int blamed_lineno = -1;
5125 char *line;
5127 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5128 return;
5130 while ((line = io_get(&io, '\n', TRUE))) {
5131 if (*line == '@') {
5132 char *pos = strchr(line, '+');
5134 parent_lineno = atoi(line + 4);
5135 if (pos)
5136 blamed_lineno = atoi(pos + 1);
5138 } else if (*line == '+' && parent_lineno != -1) {
5139 if (blame->lineno == blamed_lineno - 1 &&
5140 !strcmp(blame->text, line + 1)) {
5141 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5142 break;
5143 }
5144 blamed_lineno++;
5145 }
5146 }
5148 io_done(&io);
5149 }
5151 static enum request
5152 blame_request(struct view *view, enum request request, struct line *line)
5153 {
5154 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5155 struct blame *blame = line->data;
5157 switch (request) {
5158 case REQ_VIEW_BLAME:
5159 if (check_blame_commit(blame, TRUE)) {
5160 string_copy(opt_ref, blame->commit->id);
5161 string_copy(opt_file, blame->commit->filename);
5162 if (blame->lineno)
5163 view->lineno = blame->lineno;
5164 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5165 }
5166 break;
5168 case REQ_PARENT:
5169 if (check_blame_commit(blame, TRUE) &&
5170 select_commit_parent(blame->commit->id, opt_ref,
5171 blame->commit->filename)) {
5172 string_copy(opt_file, blame->commit->filename);
5173 setup_blame_parent_line(view, blame);
5174 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5175 }
5176 break;
5178 case REQ_ENTER:
5179 if (!check_blame_commit(blame, FALSE))
5180 break;
5182 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5183 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5184 break;
5186 if (!strcmp(blame->commit->id, NULL_ID)) {
5187 struct view *diff = VIEW(REQ_VIEW_DIFF);
5188 const char *diff_index_argv[] = {
5189 "git", "diff-index", "--root", "--patch-with-stat",
5190 "-C", "-M", "HEAD", "--", view->vid, NULL
5191 };
5193 if (!blame->commit->has_previous) {
5194 diff_index_argv[1] = "diff";
5195 diff_index_argv[2] = "--no-color";
5196 diff_index_argv[6] = "--";
5197 diff_index_argv[7] = "/dev/null";
5198 }
5200 if (!prepare_update(diff, diff_index_argv, NULL)) {
5201 report("Failed to allocate diff command");
5202 break;
5203 }
5204 flags |= OPEN_PREPARED;
5205 }
5207 open_view(view, REQ_VIEW_DIFF, flags);
5208 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5209 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5210 break;
5212 default:
5213 return request;
5214 }
5216 return REQ_NONE;
5217 }
5219 static bool
5220 blame_grep(struct view *view, struct line *line)
5221 {
5222 struct blame *blame = line->data;
5223 struct blame_commit *commit = blame->commit;
5224 const char *text[] = {
5225 blame->text,
5226 commit ? commit->title : "",
5227 commit ? commit->id : "",
5228 commit && opt_author ? commit->author : "",
5229 commit ? mkdate(&commit->time, opt_date) : "",
5230 NULL
5231 };
5233 return grep_text(view, text);
5234 }
5236 static void
5237 blame_select(struct view *view, struct line *line)
5238 {
5239 struct blame *blame = line->data;
5240 struct blame_commit *commit = blame->commit;
5242 if (!commit)
5243 return;
5245 if (!strcmp(commit->id, NULL_ID))
5246 string_ncopy(ref_commit, "HEAD", 4);
5247 else
5248 string_copy_rev(ref_commit, commit->id);
5249 }
5251 static struct view_ops blame_ops = {
5252 "line",
5253 NULL,
5254 blame_open,
5255 blame_read,
5256 blame_draw,
5257 blame_request,
5258 blame_grep,
5259 blame_select,
5260 };
5262 /*
5263 * Branch backend
5264 */
5266 struct branch {
5267 const char *author; /* Author of the last commit. */
5268 struct time time; /* Date of the last activity. */
5269 const struct ref *ref; /* Name and commit ID information. */
5270 };
5272 static const struct ref branch_all;
5274 static const enum sort_field branch_sort_fields[] = {
5275 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5276 };
5277 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5279 static int
5280 branch_compare(const void *l1, const void *l2)
5281 {
5282 const struct branch *branch1 = ((const struct line *) l1)->data;
5283 const struct branch *branch2 = ((const struct line *) l2)->data;
5285 switch (get_sort_field(branch_sort_state)) {
5286 case ORDERBY_DATE:
5287 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5289 case ORDERBY_AUTHOR:
5290 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5292 case ORDERBY_NAME:
5293 default:
5294 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5295 }
5296 }
5298 static bool
5299 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5300 {
5301 struct branch *branch = line->data;
5302 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5304 if (opt_date && draw_date(view, &branch->time))
5305 return TRUE;
5307 if (opt_author && draw_author(view, branch->author))
5308 return TRUE;
5310 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5311 return TRUE;
5312 }
5314 static enum request
5315 branch_request(struct view *view, enum request request, struct line *line)
5316 {
5317 struct branch *branch = line->data;
5319 switch (request) {
5320 case REQ_REFRESH:
5321 load_refs();
5322 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5323 return REQ_NONE;
5325 case REQ_TOGGLE_SORT_FIELD:
5326 case REQ_TOGGLE_SORT_ORDER:
5327 sort_view(view, request, &branch_sort_state, branch_compare);
5328 return REQ_NONE;
5330 case REQ_ENTER:
5331 if (branch->ref == &branch_all) {
5332 const char *all_branches_argv[] = {
5333 "git", "log", "--no-color", "--pretty=raw", "--parents",
5334 "--topo-order", "--all", NULL
5335 };
5336 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5338 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5339 report("Failed to load view of all branches");
5340 return REQ_NONE;
5341 }
5342 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5343 } else {
5344 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5345 }
5346 return REQ_NONE;
5348 default:
5349 return request;
5350 }
5351 }
5353 static bool
5354 branch_read(struct view *view, char *line)
5355 {
5356 static char id[SIZEOF_REV];
5357 struct branch *reference;
5358 size_t i;
5360 if (!line)
5361 return TRUE;
5363 switch (get_line_type(line)) {
5364 case LINE_COMMIT:
5365 string_copy_rev(id, line + STRING_SIZE("commit "));
5366 return TRUE;
5368 case LINE_AUTHOR:
5369 for (i = 0, reference = NULL; i < view->lines; i++) {
5370 struct branch *branch = view->line[i].data;
5372 if (strcmp(branch->ref->id, id))
5373 continue;
5375 view->line[i].dirty = TRUE;
5376 if (reference) {
5377 branch->author = reference->author;
5378 branch->time = reference->time;
5379 continue;
5380 }
5382 parse_author_line(line + STRING_SIZE("author "),
5383 &branch->author, &branch->time);
5384 reference = branch;
5385 }
5386 return TRUE;
5388 default:
5389 return TRUE;
5390 }
5392 }
5394 static bool
5395 branch_open_visitor(void *data, const struct ref *ref)
5396 {
5397 struct view *view = data;
5398 struct branch *branch;
5400 if (ref->tag || ref->ltag || ref->remote)
5401 return TRUE;
5403 branch = calloc(1, sizeof(*branch));
5404 if (!branch)
5405 return FALSE;
5407 branch->ref = ref;
5408 return !!add_line_data(view, branch, LINE_DEFAULT);
5409 }
5411 static bool
5412 branch_open(struct view *view)
5413 {
5414 const char *branch_log[] = {
5415 "git", "log", "--no-color", "--pretty=raw",
5416 "--simplify-by-decoration", "--all", NULL
5417 };
5419 if (!start_update(view, branch_log, NULL)) {
5420 report("Failed to load branch data");
5421 return TRUE;
5422 }
5424 setup_update(view, view->id);
5425 branch_open_visitor(view, &branch_all);
5426 foreach_ref(branch_open_visitor, view);
5427 view->p_restore = TRUE;
5429 return TRUE;
5430 }
5432 static bool
5433 branch_grep(struct view *view, struct line *line)
5434 {
5435 struct branch *branch = line->data;
5436 const char *text[] = {
5437 branch->ref->name,
5438 branch->author,
5439 NULL
5440 };
5442 return grep_text(view, text);
5443 }
5445 static void
5446 branch_select(struct view *view, struct line *line)
5447 {
5448 struct branch *branch = line->data;
5450 string_copy_rev(view->ref, branch->ref->id);
5451 string_copy_rev(ref_commit, branch->ref->id);
5452 string_copy_rev(ref_head, branch->ref->id);
5453 string_copy_rev(ref_branch, branch->ref->name);
5454 }
5456 static struct view_ops branch_ops = {
5457 "branch",
5458 NULL,
5459 branch_open,
5460 branch_read,
5461 branch_draw,
5462 branch_request,
5463 branch_grep,
5464 branch_select,
5465 };
5467 /*
5468 * Status backend
5469 */
5471 struct status {
5472 char status;
5473 struct {
5474 mode_t mode;
5475 char rev[SIZEOF_REV];
5476 char name[SIZEOF_STR];
5477 } old;
5478 struct {
5479 mode_t mode;
5480 char rev[SIZEOF_REV];
5481 char name[SIZEOF_STR];
5482 } new;
5483 };
5485 static char status_onbranch[SIZEOF_STR];
5486 static struct status stage_status;
5487 static enum line_type stage_line_type;
5488 static size_t stage_chunks;
5489 static int *stage_chunk;
5491 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5493 /* This should work even for the "On branch" line. */
5494 static inline bool
5495 status_has_none(struct view *view, struct line *line)
5496 {
5497 return line < view->line + view->lines && !line[1].data;
5498 }
5500 /* Get fields from the diff line:
5501 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5502 */
5503 static inline bool
5504 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5505 {
5506 const char *old_mode = buf + 1;
5507 const char *new_mode = buf + 8;
5508 const char *old_rev = buf + 15;
5509 const char *new_rev = buf + 56;
5510 const char *status = buf + 97;
5512 if (bufsize < 98 ||
5513 old_mode[-1] != ':' ||
5514 new_mode[-1] != ' ' ||
5515 old_rev[-1] != ' ' ||
5516 new_rev[-1] != ' ' ||
5517 status[-1] != ' ')
5518 return FALSE;
5520 file->status = *status;
5522 string_copy_rev(file->old.rev, old_rev);
5523 string_copy_rev(file->new.rev, new_rev);
5525 file->old.mode = strtoul(old_mode, NULL, 8);
5526 file->new.mode = strtoul(new_mode, NULL, 8);
5528 file->old.name[0] = file->new.name[0] = 0;
5530 return TRUE;
5531 }
5533 static bool
5534 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5535 {
5536 struct status *unmerged = NULL;
5537 char *buf;
5538 struct io io = {};
5540 if (!io_run(&io, argv, opt_cdup, IO_RD))
5541 return FALSE;
5543 add_line_data(view, NULL, type);
5545 while ((buf = io_get(&io, 0, TRUE))) {
5546 struct status *file = unmerged;
5548 if (!file) {
5549 file = calloc(1, sizeof(*file));
5550 if (!file || !add_line_data(view, file, type))
5551 goto error_out;
5552 }
5554 /* Parse diff info part. */
5555 if (status) {
5556 file->status = status;
5557 if (status == 'A')
5558 string_copy(file->old.rev, NULL_ID);
5560 } else if (!file->status || file == unmerged) {
5561 if (!status_get_diff(file, buf, strlen(buf)))
5562 goto error_out;
5564 buf = io_get(&io, 0, TRUE);
5565 if (!buf)
5566 break;
5568 /* Collapse all modified entries that follow an
5569 * associated unmerged entry. */
5570 if (unmerged == file) {
5571 unmerged->status = 'U';
5572 unmerged = NULL;
5573 } else if (file->status == 'U') {
5574 unmerged = file;
5575 }
5576 }
5578 /* Grab the old name for rename/copy. */
5579 if (!*file->old.name &&
5580 (file->status == 'R' || file->status == 'C')) {
5581 string_ncopy(file->old.name, buf, strlen(buf));
5583 buf = io_get(&io, 0, TRUE);
5584 if (!buf)
5585 break;
5586 }
5588 /* git-ls-files just delivers a NUL separated list of
5589 * file names similar to the second half of the
5590 * git-diff-* output. */
5591 string_ncopy(file->new.name, buf, strlen(buf));
5592 if (!*file->old.name)
5593 string_copy(file->old.name, file->new.name);
5594 file = NULL;
5595 }
5597 if (io_error(&io)) {
5598 error_out:
5599 io_done(&io);
5600 return FALSE;
5601 }
5603 if (!view->line[view->lines - 1].data)
5604 add_line_data(view, NULL, LINE_STAT_NONE);
5606 io_done(&io);
5607 return TRUE;
5608 }
5610 /* Don't show unmerged entries in the staged section. */
5611 static const char *status_diff_index_argv[] = {
5612 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5613 "--cached", "-M", "HEAD", NULL
5614 };
5616 static const char *status_diff_files_argv[] = {
5617 "git", "diff-files", "-z", NULL
5618 };
5620 static const char *status_list_other_argv[] = {
5621 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5622 };
5624 static const char *status_list_no_head_argv[] = {
5625 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5626 };
5628 static const char *update_index_argv[] = {
5629 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5630 };
5632 /* Restore the previous line number to stay in the context or select a
5633 * line with something that can be updated. */
5634 static void
5635 status_restore(struct view *view)
5636 {
5637 if (view->p_lineno >= view->lines)
5638 view->p_lineno = view->lines - 1;
5639 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5640 view->p_lineno++;
5641 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5642 view->p_lineno--;
5644 /* If the above fails, always skip the "On branch" line. */
5645 if (view->p_lineno < view->lines)
5646 view->lineno = view->p_lineno;
5647 else
5648 view->lineno = 1;
5650 if (view->lineno < view->offset)
5651 view->offset = view->lineno;
5652 else if (view->offset + view->height <= view->lineno)
5653 view->offset = view->lineno - view->height + 1;
5655 view->p_restore = FALSE;
5656 }
5658 static void
5659 status_update_onbranch(void)
5660 {
5661 static const char *paths[][2] = {
5662 { "rebase-apply/rebasing", "Rebasing" },
5663 { "rebase-apply/applying", "Applying mailbox" },
5664 { "rebase-apply/", "Rebasing mailbox" },
5665 { "rebase-merge/interactive", "Interactive rebase" },
5666 { "rebase-merge/", "Rebase merge" },
5667 { "MERGE_HEAD", "Merging" },
5668 { "BISECT_LOG", "Bisecting" },
5669 { "HEAD", "On branch" },
5670 };
5671 char buf[SIZEOF_STR];
5672 struct stat stat;
5673 int i;
5675 if (is_initial_commit()) {
5676 string_copy(status_onbranch, "Initial commit");
5677 return;
5678 }
5680 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5681 char *head = opt_head;
5683 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5684 lstat(buf, &stat) < 0)
5685 continue;
5687 if (!*opt_head) {
5688 struct io io = {};
5690 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5691 io_read_buf(&io, buf, sizeof(buf))) {
5692 head = buf;
5693 if (!prefixcmp(head, "refs/heads/"))
5694 head += STRING_SIZE("refs/heads/");
5695 }
5696 }
5698 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5699 string_copy(status_onbranch, opt_head);
5700 return;
5701 }
5703 string_copy(status_onbranch, "Not currently on any branch");
5704 }
5706 /* First parse staged info using git-diff-index(1), then parse unstaged
5707 * info using git-diff-files(1), and finally untracked files using
5708 * git-ls-files(1). */
5709 static bool
5710 status_open(struct view *view)
5711 {
5712 reset_view(view);
5714 add_line_data(view, NULL, LINE_STAT_HEAD);
5715 status_update_onbranch();
5717 io_run_bg(update_index_argv);
5719 if (is_initial_commit()) {
5720 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5721 return FALSE;
5722 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5723 return FALSE;
5724 }
5726 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5727 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5728 return FALSE;
5730 /* Restore the exact position or use the specialized restore
5731 * mode? */
5732 if (!view->p_restore)
5733 status_restore(view);
5734 return TRUE;
5735 }
5737 static bool
5738 status_draw(struct view *view, struct line *line, unsigned int lineno)
5739 {
5740 struct status *status = line->data;
5741 enum line_type type;
5742 const char *text;
5744 if (!status) {
5745 switch (line->type) {
5746 case LINE_STAT_STAGED:
5747 type = LINE_STAT_SECTION;
5748 text = "Changes to be committed:";
5749 break;
5751 case LINE_STAT_UNSTAGED:
5752 type = LINE_STAT_SECTION;
5753 text = "Changed but not updated:";
5754 break;
5756 case LINE_STAT_UNTRACKED:
5757 type = LINE_STAT_SECTION;
5758 text = "Untracked files:";
5759 break;
5761 case LINE_STAT_NONE:
5762 type = LINE_DEFAULT;
5763 text = " (no files)";
5764 break;
5766 case LINE_STAT_HEAD:
5767 type = LINE_STAT_HEAD;
5768 text = status_onbranch;
5769 break;
5771 default:
5772 return FALSE;
5773 }
5774 } else {
5775 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5777 buf[0] = status->status;
5778 if (draw_text(view, line->type, buf, TRUE))
5779 return TRUE;
5780 type = LINE_DEFAULT;
5781 text = status->new.name;
5782 }
5784 draw_text(view, type, text, TRUE);
5785 return TRUE;
5786 }
5788 static enum request
5789 status_load_error(struct view *view, struct view *stage, const char *path)
5790 {
5791 if (displayed_views() == 2 || display[current_view] != view)
5792 maximize_view(view);
5793 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5794 return REQ_NONE;
5795 }
5797 static enum request
5798 status_enter(struct view *view, struct line *line)
5799 {
5800 struct status *status = line->data;
5801 const char *oldpath = status ? status->old.name : NULL;
5802 /* Diffs for unmerged entries are empty when passing the new
5803 * path, so leave it empty. */
5804 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5805 const char *info;
5806 enum open_flags split;
5807 struct view *stage = VIEW(REQ_VIEW_STAGE);
5809 if (line->type == LINE_STAT_NONE ||
5810 (!status && line[1].type == LINE_STAT_NONE)) {
5811 report("No file to diff");
5812 return REQ_NONE;
5813 }
5815 switch (line->type) {
5816 case LINE_STAT_STAGED:
5817 if (is_initial_commit()) {
5818 const char *no_head_diff_argv[] = {
5819 "git", "diff", "--no-color", "--patch-with-stat",
5820 "--", "/dev/null", newpath, NULL
5821 };
5823 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5824 return status_load_error(view, stage, newpath);
5825 } else {
5826 const char *index_show_argv[] = {
5827 "git", "diff-index", "--root", "--patch-with-stat",
5828 "-C", "-M", "--cached", "HEAD", "--",
5829 oldpath, newpath, NULL
5830 };
5832 if (!prepare_update(stage, index_show_argv, opt_cdup))
5833 return status_load_error(view, stage, newpath);
5834 }
5836 if (status)
5837 info = "Staged changes to %s";
5838 else
5839 info = "Staged changes";
5840 break;
5842 case LINE_STAT_UNSTAGED:
5843 {
5844 const char *files_show_argv[] = {
5845 "git", "diff-files", "--root", "--patch-with-stat",
5846 "-C", "-M", "--", oldpath, newpath, NULL
5847 };
5849 if (!prepare_update(stage, files_show_argv, opt_cdup))
5850 return status_load_error(view, stage, newpath);
5851 if (status)
5852 info = "Unstaged changes to %s";
5853 else
5854 info = "Unstaged changes";
5855 break;
5856 }
5857 case LINE_STAT_UNTRACKED:
5858 if (!newpath) {
5859 report("No file to show");
5860 return REQ_NONE;
5861 }
5863 if (!suffixcmp(status->new.name, -1, "/")) {
5864 report("Cannot display a directory");
5865 return REQ_NONE;
5866 }
5868 if (!prepare_update_file(stage, newpath))
5869 return status_load_error(view, stage, newpath);
5870 info = "Untracked file %s";
5871 break;
5873 case LINE_STAT_HEAD:
5874 return REQ_NONE;
5876 default:
5877 die("line type %d not handled in switch", line->type);
5878 }
5880 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5881 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5882 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5883 if (status) {
5884 stage_status = *status;
5885 } else {
5886 memset(&stage_status, 0, sizeof(stage_status));
5887 }
5889 stage_line_type = line->type;
5890 stage_chunks = 0;
5891 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5892 }
5894 return REQ_NONE;
5895 }
5897 static bool
5898 status_exists(struct status *status, enum line_type type)
5899 {
5900 struct view *view = VIEW(REQ_VIEW_STATUS);
5901 unsigned long lineno;
5903 for (lineno = 0; lineno < view->lines; lineno++) {
5904 struct line *line = &view->line[lineno];
5905 struct status *pos = line->data;
5907 if (line->type != type)
5908 continue;
5909 if (!pos && (!status || !status->status) && line[1].data) {
5910 select_view_line(view, lineno);
5911 return TRUE;
5912 }
5913 if (pos && !strcmp(status->new.name, pos->new.name)) {
5914 select_view_line(view, lineno);
5915 return TRUE;
5916 }
5917 }
5919 return FALSE;
5920 }
5923 static bool
5924 status_update_prepare(struct io *io, enum line_type type)
5925 {
5926 const char *staged_argv[] = {
5927 "git", "update-index", "-z", "--index-info", NULL
5928 };
5929 const char *others_argv[] = {
5930 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5931 };
5933 switch (type) {
5934 case LINE_STAT_STAGED:
5935 return io_run(io, staged_argv, opt_cdup, IO_WR);
5937 case LINE_STAT_UNSTAGED:
5938 case LINE_STAT_UNTRACKED:
5939 return io_run(io, others_argv, opt_cdup, IO_WR);
5941 default:
5942 die("line type %d not handled in switch", type);
5943 return FALSE;
5944 }
5945 }
5947 static bool
5948 status_update_write(struct io *io, struct status *status, enum line_type type)
5949 {
5950 char buf[SIZEOF_STR];
5951 size_t bufsize = 0;
5953 switch (type) {
5954 case LINE_STAT_STAGED:
5955 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5956 status->old.mode,
5957 status->old.rev,
5958 status->old.name, 0))
5959 return FALSE;
5960 break;
5962 case LINE_STAT_UNSTAGED:
5963 case LINE_STAT_UNTRACKED:
5964 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5965 return FALSE;
5966 break;
5968 default:
5969 die("line type %d not handled in switch", type);
5970 }
5972 return io_write(io, buf, bufsize);
5973 }
5975 static bool
5976 status_update_file(struct status *status, enum line_type type)
5977 {
5978 struct io io = {};
5979 bool result;
5981 if (!status_update_prepare(&io, type))
5982 return FALSE;
5984 result = status_update_write(&io, status, type);
5985 return io_done(&io) && result;
5986 }
5988 static bool
5989 status_update_files(struct view *view, struct line *line)
5990 {
5991 char buf[sizeof(view->ref)];
5992 struct io io = {};
5993 bool result = TRUE;
5994 struct line *pos = view->line + view->lines;
5995 int files = 0;
5996 int file, done;
5997 int cursor_y = -1, cursor_x = -1;
5999 if (!status_update_prepare(&io, line->type))
6000 return FALSE;
6002 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6003 files++;
6005 string_copy(buf, view->ref);
6006 getsyx(cursor_y, cursor_x);
6007 for (file = 0, done = 5; result && file < files; line++, file++) {
6008 int almost_done = file * 100 / files;
6010 if (almost_done > done) {
6011 done = almost_done;
6012 string_format(view->ref, "updating file %u of %u (%d%% done)",
6013 file, files, done);
6014 update_view_title(view);
6015 setsyx(cursor_y, cursor_x);
6016 doupdate();
6017 }
6018 result = status_update_write(&io, line->data, line->type);
6019 }
6020 string_copy(view->ref, buf);
6022 return io_done(&io) && result;
6023 }
6025 static bool
6026 status_update(struct view *view)
6027 {
6028 struct line *line = &view->line[view->lineno];
6030 assert(view->lines);
6032 if (!line->data) {
6033 /* This should work even for the "On branch" line. */
6034 if (line < view->line + view->lines && !line[1].data) {
6035 report("Nothing to update");
6036 return FALSE;
6037 }
6039 if (!status_update_files(view, line + 1)) {
6040 report("Failed to update file status");
6041 return FALSE;
6042 }
6044 } else if (!status_update_file(line->data, line->type)) {
6045 report("Failed to update file status");
6046 return FALSE;
6047 }
6049 return TRUE;
6050 }
6052 static bool
6053 status_revert(struct status *status, enum line_type type, bool has_none)
6054 {
6055 if (!status || type != LINE_STAT_UNSTAGED) {
6056 if (type == LINE_STAT_STAGED) {
6057 report("Cannot revert changes to staged files");
6058 } else if (type == LINE_STAT_UNTRACKED) {
6059 report("Cannot revert changes to untracked files");
6060 } else if (has_none) {
6061 report("Nothing to revert");
6062 } else {
6063 report("Cannot revert changes to multiple files");
6064 }
6066 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6067 char mode[10] = "100644";
6068 const char *reset_argv[] = {
6069 "git", "update-index", "--cacheinfo", mode,
6070 status->old.rev, status->old.name, NULL
6071 };
6072 const char *checkout_argv[] = {
6073 "git", "checkout", "--", status->old.name, NULL
6074 };
6076 if (status->status == 'U') {
6077 string_format(mode, "%5o", status->old.mode);
6079 if (status->old.mode == 0 && status->new.mode == 0) {
6080 reset_argv[2] = "--force-remove";
6081 reset_argv[3] = status->old.name;
6082 reset_argv[4] = NULL;
6083 }
6085 if (!io_run_fg(reset_argv, opt_cdup))
6086 return FALSE;
6087 if (status->old.mode == 0 && status->new.mode == 0)
6088 return TRUE;
6089 }
6091 return io_run_fg(checkout_argv, opt_cdup);
6092 }
6094 return FALSE;
6095 }
6097 static enum request
6098 status_request(struct view *view, enum request request, struct line *line)
6099 {
6100 struct status *status = line->data;
6102 switch (request) {
6103 case REQ_STATUS_UPDATE:
6104 if (!status_update(view))
6105 return REQ_NONE;
6106 break;
6108 case REQ_STATUS_REVERT:
6109 if (!status_revert(status, line->type, status_has_none(view, line)))
6110 return REQ_NONE;
6111 break;
6113 case REQ_STATUS_MERGE:
6114 if (!status || status->status != 'U') {
6115 report("Merging only possible for files with unmerged status ('U').");
6116 return REQ_NONE;
6117 }
6118 open_mergetool(status->new.name);
6119 break;
6121 case REQ_EDIT:
6122 if (!status)
6123 return request;
6124 if (status->status == 'D') {
6125 report("File has been deleted.");
6126 return REQ_NONE;
6127 }
6129 open_editor(status->new.name);
6130 break;
6132 case REQ_VIEW_BLAME:
6133 if (status)
6134 opt_ref[0] = 0;
6135 return request;
6137 case REQ_ENTER:
6138 /* After returning the status view has been split to
6139 * show the stage view. No further reloading is
6140 * necessary. */
6141 return status_enter(view, line);
6143 case REQ_REFRESH:
6144 /* Simply reload the view. */
6145 break;
6147 default:
6148 return request;
6149 }
6151 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6153 return REQ_NONE;
6154 }
6156 static void
6157 status_select(struct view *view, struct line *line)
6158 {
6159 struct status *status = line->data;
6160 char file[SIZEOF_STR] = "all files";
6161 const char *text;
6162 const char *key;
6164 if (status && !string_format(file, "'%s'", status->new.name))
6165 return;
6167 if (!status && line[1].type == LINE_STAT_NONE)
6168 line++;
6170 switch (line->type) {
6171 case LINE_STAT_STAGED:
6172 text = "Press %s to unstage %s for commit";
6173 break;
6175 case LINE_STAT_UNSTAGED:
6176 text = "Press %s to stage %s for commit";
6177 break;
6179 case LINE_STAT_UNTRACKED:
6180 text = "Press %s to stage %s for addition";
6181 break;
6183 case LINE_STAT_HEAD:
6184 case LINE_STAT_NONE:
6185 text = "Nothing to update";
6186 break;
6188 default:
6189 die("line type %d not handled in switch", line->type);
6190 }
6192 if (status && status->status == 'U') {
6193 text = "Press %s to resolve conflict in %s";
6194 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6196 } else {
6197 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6198 }
6200 string_format(view->ref, text, key, file);
6201 if (status)
6202 string_copy(opt_file, status->new.name);
6203 }
6205 static bool
6206 status_grep(struct view *view, struct line *line)
6207 {
6208 struct status *status = line->data;
6210 if (status) {
6211 const char buf[2] = { status->status, 0 };
6212 const char *text[] = { status->new.name, buf, NULL };
6214 return grep_text(view, text);
6215 }
6217 return FALSE;
6218 }
6220 static struct view_ops status_ops = {
6221 "file",
6222 NULL,
6223 status_open,
6224 NULL,
6225 status_draw,
6226 status_request,
6227 status_grep,
6228 status_select,
6229 };
6232 static bool
6233 stage_diff_write(struct io *io, struct line *line, struct line *end)
6234 {
6235 while (line < end) {
6236 if (!io_write(io, line->data, strlen(line->data)) ||
6237 !io_write(io, "\n", 1))
6238 return FALSE;
6239 line++;
6240 if (line->type == LINE_DIFF_CHUNK ||
6241 line->type == LINE_DIFF_HEADER)
6242 break;
6243 }
6245 return TRUE;
6246 }
6248 static struct line *
6249 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6250 {
6251 for (; view->line < line; line--)
6252 if (line->type == type)
6253 return line;
6255 return NULL;
6256 }
6258 static bool
6259 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6260 {
6261 const char *apply_argv[SIZEOF_ARG] = {
6262 "git", "apply", "--whitespace=nowarn", NULL
6263 };
6264 struct line *diff_hdr;
6265 struct io io = {};
6266 int argc = 3;
6268 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6269 if (!diff_hdr)
6270 return FALSE;
6272 if (!revert)
6273 apply_argv[argc++] = "--cached";
6274 if (revert || stage_line_type == LINE_STAT_STAGED)
6275 apply_argv[argc++] = "-R";
6276 apply_argv[argc++] = "-";
6277 apply_argv[argc++] = NULL;
6278 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6279 return FALSE;
6281 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6282 !stage_diff_write(&io, chunk, view->line + view->lines))
6283 chunk = NULL;
6285 io_done(&io);
6286 io_run_bg(update_index_argv);
6288 return chunk ? TRUE : FALSE;
6289 }
6291 static bool
6292 stage_update(struct view *view, struct line *line)
6293 {
6294 struct line *chunk = NULL;
6296 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6297 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6299 if (chunk) {
6300 if (!stage_apply_chunk(view, chunk, FALSE)) {
6301 report("Failed to apply chunk");
6302 return FALSE;
6303 }
6305 } else if (!stage_status.status) {
6306 view = VIEW(REQ_VIEW_STATUS);
6308 for (line = view->line; line < view->line + view->lines; line++)
6309 if (line->type == stage_line_type)
6310 break;
6312 if (!status_update_files(view, line + 1)) {
6313 report("Failed to update files");
6314 return FALSE;
6315 }
6317 } else if (!status_update_file(&stage_status, stage_line_type)) {
6318 report("Failed to update file");
6319 return FALSE;
6320 }
6322 return TRUE;
6323 }
6325 static bool
6326 stage_revert(struct view *view, struct line *line)
6327 {
6328 struct line *chunk = NULL;
6330 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6331 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6333 if (chunk) {
6334 if (!prompt_yesno("Are you sure you want to revert changes?"))
6335 return FALSE;
6337 if (!stage_apply_chunk(view, chunk, TRUE)) {
6338 report("Failed to revert chunk");
6339 return FALSE;
6340 }
6341 return TRUE;
6343 } else {
6344 return status_revert(stage_status.status ? &stage_status : NULL,
6345 stage_line_type, FALSE);
6346 }
6347 }
6350 static void
6351 stage_next(struct view *view, struct line *line)
6352 {
6353 int i;
6355 if (!stage_chunks) {
6356 for (line = view->line; line < view->line + view->lines; line++) {
6357 if (line->type != LINE_DIFF_CHUNK)
6358 continue;
6360 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6361 report("Allocation failure");
6362 return;
6363 }
6365 stage_chunk[stage_chunks++] = line - view->line;
6366 }
6367 }
6369 for (i = 0; i < stage_chunks; i++) {
6370 if (stage_chunk[i] > view->lineno) {
6371 do_scroll_view(view, stage_chunk[i] - view->lineno);
6372 report("Chunk %d of %d", i + 1, stage_chunks);
6373 return;
6374 }
6375 }
6377 report("No next chunk found");
6378 }
6380 static enum request
6381 stage_request(struct view *view, enum request request, struct line *line)
6382 {
6383 switch (request) {
6384 case REQ_STATUS_UPDATE:
6385 if (!stage_update(view, line))
6386 return REQ_NONE;
6387 break;
6389 case REQ_STATUS_REVERT:
6390 if (!stage_revert(view, line))
6391 return REQ_NONE;
6392 break;
6394 case REQ_STAGE_NEXT:
6395 if (stage_line_type == LINE_STAT_UNTRACKED) {
6396 report("File is untracked; press %s to add",
6397 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6398 return REQ_NONE;
6399 }
6400 stage_next(view, line);
6401 return REQ_NONE;
6403 case REQ_EDIT:
6404 if (!stage_status.new.name[0])
6405 return request;
6406 if (stage_status.status == 'D') {
6407 report("File has been deleted.");
6408 return REQ_NONE;
6409 }
6411 open_editor(stage_status.new.name);
6412 break;
6414 case REQ_REFRESH:
6415 /* Reload everything ... */
6416 break;
6418 case REQ_VIEW_BLAME:
6419 if (stage_status.new.name[0]) {
6420 string_copy(opt_file, stage_status.new.name);
6421 opt_ref[0] = 0;
6422 }
6423 return request;
6425 case REQ_ENTER:
6426 return pager_request(view, request, line);
6428 default:
6429 return request;
6430 }
6432 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6433 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6435 /* Check whether the staged entry still exists, and close the
6436 * stage view if it doesn't. */
6437 if (!status_exists(&stage_status, stage_line_type)) {
6438 status_restore(VIEW(REQ_VIEW_STATUS));
6439 return REQ_VIEW_CLOSE;
6440 }
6442 if (stage_line_type == LINE_STAT_UNTRACKED) {
6443 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6444 report("Cannot display a directory");
6445 return REQ_NONE;
6446 }
6448 if (!prepare_update_file(view, stage_status.new.name)) {
6449 report("Failed to open file: %s", strerror(errno));
6450 return REQ_NONE;
6451 }
6452 }
6453 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6455 return REQ_NONE;
6456 }
6458 static struct view_ops stage_ops = {
6459 "line",
6460 NULL,
6461 NULL,
6462 pager_read,
6463 pager_draw,
6464 stage_request,
6465 pager_grep,
6466 pager_select,
6467 };
6470 /*
6471 * Revision graph
6472 */
6474 struct commit {
6475 char id[SIZEOF_REV]; /* SHA1 ID. */
6476 char title[128]; /* First line of the commit message. */
6477 const char *author; /* Author of the commit. */
6478 struct time time; /* Date from the author ident. */
6479 struct ref_list *refs; /* Repository references. */
6480 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6481 size_t graph_size; /* The width of the graph array. */
6482 bool has_parents; /* Rewritten --parents seen. */
6483 };
6485 /* Size of rev graph with no "padding" columns */
6486 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6488 struct rev_graph {
6489 struct rev_graph *prev, *next, *parents;
6490 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6491 size_t size;
6492 struct commit *commit;
6493 size_t pos;
6494 unsigned int boundary:1;
6495 };
6497 /* Parents of the commit being visualized. */
6498 static struct rev_graph graph_parents[4];
6500 /* The current stack of revisions on the graph. */
6501 static struct rev_graph graph_stacks[4] = {
6502 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6503 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6504 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6505 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6506 };
6508 static inline bool
6509 graph_parent_is_merge(struct rev_graph *graph)
6510 {
6511 return graph->parents->size > 1;
6512 }
6514 static inline void
6515 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6516 {
6517 struct commit *commit = graph->commit;
6519 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6520 commit->graph[commit->graph_size++] = symbol;
6521 }
6523 static void
6524 clear_rev_graph(struct rev_graph *graph)
6525 {
6526 graph->boundary = 0;
6527 graph->size = graph->pos = 0;
6528 graph->commit = NULL;
6529 memset(graph->parents, 0, sizeof(*graph->parents));
6530 }
6532 static void
6533 done_rev_graph(struct rev_graph *graph)
6534 {
6535 if (graph_parent_is_merge(graph) &&
6536 graph->pos < graph->size - 1 &&
6537 graph->next->size == graph->size + graph->parents->size - 1) {
6538 size_t i = graph->pos + graph->parents->size - 1;
6540 graph->commit->graph_size = i * 2;
6541 while (i < graph->next->size - 1) {
6542 append_to_rev_graph(graph, ' ');
6543 append_to_rev_graph(graph, '\\');
6544 i++;
6545 }
6546 }
6548 clear_rev_graph(graph);
6549 }
6551 static void
6552 push_rev_graph(struct rev_graph *graph, const char *parent)
6553 {
6554 int i;
6556 /* "Collapse" duplicate parents lines.
6557 *
6558 * FIXME: This needs to also update update the drawn graph but
6559 * for now it just serves as a method for pruning graph lines. */
6560 for (i = 0; i < graph->size; i++)
6561 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6562 return;
6564 if (graph->size < SIZEOF_REVITEMS) {
6565 string_copy_rev(graph->rev[graph->size++], parent);
6566 }
6567 }
6569 static chtype
6570 get_rev_graph_symbol(struct rev_graph *graph)
6571 {
6572 chtype symbol;
6574 if (graph->boundary)
6575 symbol = REVGRAPH_BOUND;
6576 else if (graph->parents->size == 0)
6577 symbol = REVGRAPH_INIT;
6578 else if (graph_parent_is_merge(graph))
6579 symbol = REVGRAPH_MERGE;
6580 else if (graph->pos >= graph->size)
6581 symbol = REVGRAPH_BRANCH;
6582 else
6583 symbol = REVGRAPH_COMMIT;
6585 return symbol;
6586 }
6588 static void
6589 draw_rev_graph(struct rev_graph *graph)
6590 {
6591 struct rev_filler {
6592 chtype separator, line;
6593 };
6594 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6595 static struct rev_filler fillers[] = {
6596 { ' ', '|' },
6597 { '`', '.' },
6598 { '\'', ' ' },
6599 { '/', ' ' },
6600 };
6601 chtype symbol = get_rev_graph_symbol(graph);
6602 struct rev_filler *filler;
6603 size_t i;
6605 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6606 filler = &fillers[DEFAULT];
6608 for (i = 0; i < graph->pos; i++) {
6609 append_to_rev_graph(graph, filler->line);
6610 if (graph_parent_is_merge(graph->prev) &&
6611 graph->prev->pos == i)
6612 filler = &fillers[RSHARP];
6614 append_to_rev_graph(graph, filler->separator);
6615 }
6617 /* Place the symbol for this revision. */
6618 append_to_rev_graph(graph, symbol);
6620 if (graph->prev->size > graph->size)
6621 filler = &fillers[RDIAG];
6622 else
6623 filler = &fillers[DEFAULT];
6625 i++;
6627 for (; i < graph->size; i++) {
6628 append_to_rev_graph(graph, filler->separator);
6629 append_to_rev_graph(graph, filler->line);
6630 if (graph_parent_is_merge(graph->prev) &&
6631 i < graph->prev->pos + graph->parents->size)
6632 filler = &fillers[RSHARP];
6633 if (graph->prev->size > graph->size)
6634 filler = &fillers[LDIAG];
6635 }
6637 if (graph->prev->size > graph->size) {
6638 append_to_rev_graph(graph, filler->separator);
6639 if (filler->line != ' ')
6640 append_to_rev_graph(graph, filler->line);
6641 }
6642 }
6644 /* Prepare the next rev graph */
6645 static void
6646 prepare_rev_graph(struct rev_graph *graph)
6647 {
6648 size_t i;
6650 /* First, traverse all lines of revisions up to the active one. */
6651 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6652 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6653 break;
6655 push_rev_graph(graph->next, graph->rev[graph->pos]);
6656 }
6658 /* Interleave the new revision parent(s). */
6659 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6660 push_rev_graph(graph->next, graph->parents->rev[i]);
6662 /* Lastly, put any remaining revisions. */
6663 for (i = graph->pos + 1; i < graph->size; i++)
6664 push_rev_graph(graph->next, graph->rev[i]);
6665 }
6667 static void
6668 update_rev_graph(struct view *view, struct rev_graph *graph)
6669 {
6670 /* If this is the finalizing update ... */
6671 if (graph->commit)
6672 prepare_rev_graph(graph);
6674 /* Graph visualization needs a one rev look-ahead,
6675 * so the first update doesn't visualize anything. */
6676 if (!graph->prev->commit)
6677 return;
6679 if (view->lines > 2)
6680 view->line[view->lines - 3].dirty = 1;
6681 if (view->lines > 1)
6682 view->line[view->lines - 2].dirty = 1;
6683 draw_rev_graph(graph->prev);
6684 done_rev_graph(graph->prev->prev);
6685 }
6688 /*
6689 * Main view backend
6690 */
6692 static const char *main_argv[SIZEOF_ARG] = {
6693 "git", "log", "--no-color", "--pretty=raw", "--parents",
6694 "--topo-order", "%(head)", NULL
6695 };
6697 static bool
6698 main_draw(struct view *view, struct line *line, unsigned int lineno)
6699 {
6700 struct commit *commit = line->data;
6702 if (!commit->author)
6703 return FALSE;
6705 if (opt_date && draw_date(view, &commit->time))
6706 return TRUE;
6708 if (opt_author && draw_author(view, commit->author))
6709 return TRUE;
6711 if (opt_rev_graph && commit->graph_size &&
6712 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6713 return TRUE;
6715 if (opt_show_refs && commit->refs) {
6716 size_t i;
6718 for (i = 0; i < commit->refs->size; i++) {
6719 struct ref *ref = commit->refs->refs[i];
6720 enum line_type type;
6722 if (ref->head)
6723 type = LINE_MAIN_HEAD;
6724 else if (ref->ltag)
6725 type = LINE_MAIN_LOCAL_TAG;
6726 else if (ref->tag)
6727 type = LINE_MAIN_TAG;
6728 else if (ref->tracked)
6729 type = LINE_MAIN_TRACKED;
6730 else if (ref->remote)
6731 type = LINE_MAIN_REMOTE;
6732 else
6733 type = LINE_MAIN_REF;
6735 if (draw_text(view, type, "[", TRUE) ||
6736 draw_text(view, type, ref->name, TRUE) ||
6737 draw_text(view, type, "]", TRUE))
6738 return TRUE;
6740 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6741 return TRUE;
6742 }
6743 }
6745 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6746 return TRUE;
6747 }
6749 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6750 static bool
6751 main_read(struct view *view, char *line)
6752 {
6753 static struct rev_graph *graph = graph_stacks;
6754 enum line_type type;
6755 struct commit *commit;
6757 if (!line) {
6758 int i;
6760 if (!view->lines && !view->prev)
6761 die("No revisions match the given arguments.");
6762 if (view->lines > 0) {
6763 commit = view->line[view->lines - 1].data;
6764 view->line[view->lines - 1].dirty = 1;
6765 if (!commit->author) {
6766 view->lines--;
6767 free(commit);
6768 graph->commit = NULL;
6769 }
6770 }
6771 update_rev_graph(view, graph);
6773 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6774 clear_rev_graph(&graph_stacks[i]);
6775 return TRUE;
6776 }
6778 type = get_line_type(line);
6779 if (type == LINE_COMMIT) {
6780 commit = calloc(1, sizeof(struct commit));
6781 if (!commit)
6782 return FALSE;
6784 line += STRING_SIZE("commit ");
6785 if (*line == '-') {
6786 graph->boundary = 1;
6787 line++;
6788 }
6790 string_copy_rev(commit->id, line);
6791 commit->refs = get_ref_list(commit->id);
6792 graph->commit = commit;
6793 add_line_data(view, commit, LINE_MAIN_COMMIT);
6795 while ((line = strchr(line, ' '))) {
6796 line++;
6797 push_rev_graph(graph->parents, line);
6798 commit->has_parents = TRUE;
6799 }
6800 return TRUE;
6801 }
6803 if (!view->lines)
6804 return TRUE;
6805 commit = view->line[view->lines - 1].data;
6807 switch (type) {
6808 case LINE_PARENT:
6809 if (commit->has_parents)
6810 break;
6811 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6812 break;
6814 case LINE_AUTHOR:
6815 parse_author_line(line + STRING_SIZE("author "),
6816 &commit->author, &commit->time);
6817 update_rev_graph(view, graph);
6818 graph = graph->next;
6819 break;
6821 default:
6822 /* Fill in the commit title if it has not already been set. */
6823 if (commit->title[0])
6824 break;
6826 /* Require titles to start with a non-space character at the
6827 * offset used by git log. */
6828 if (strncmp(line, " ", 4))
6829 break;
6830 line += 4;
6831 /* Well, if the title starts with a whitespace character,
6832 * try to be forgiving. Otherwise we end up with no title. */
6833 while (isspace(*line))
6834 line++;
6835 if (*line == '\0')
6836 break;
6837 /* FIXME: More graceful handling of titles; append "..." to
6838 * shortened titles, etc. */
6840 string_expand(commit->title, sizeof(commit->title), line, 1);
6841 view->line[view->lines - 1].dirty = 1;
6842 }
6844 return TRUE;
6845 }
6847 static enum request
6848 main_request(struct view *view, enum request request, struct line *line)
6849 {
6850 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6852 switch (request) {
6853 case REQ_ENTER:
6854 open_view(view, REQ_VIEW_DIFF, flags);
6855 break;
6856 case REQ_REFRESH:
6857 load_refs();
6858 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6859 break;
6860 default:
6861 return request;
6862 }
6864 return REQ_NONE;
6865 }
6867 static bool
6868 grep_refs(struct ref_list *list, regex_t *regex)
6869 {
6870 regmatch_t pmatch;
6871 size_t i;
6873 if (!opt_show_refs || !list)
6874 return FALSE;
6876 for (i = 0; i < list->size; i++) {
6877 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6878 return TRUE;
6879 }
6881 return FALSE;
6882 }
6884 static bool
6885 main_grep(struct view *view, struct line *line)
6886 {
6887 struct commit *commit = line->data;
6888 const char *text[] = {
6889 commit->title,
6890 opt_author ? commit->author : "",
6891 mkdate(&commit->time, opt_date),
6892 NULL
6893 };
6895 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6896 }
6898 static void
6899 main_select(struct view *view, struct line *line)
6900 {
6901 struct commit *commit = line->data;
6903 string_copy_rev(view->ref, commit->id);
6904 string_copy_rev(ref_commit, view->ref);
6905 }
6907 static struct view_ops main_ops = {
6908 "commit",
6909 main_argv,
6910 NULL,
6911 main_read,
6912 main_draw,
6913 main_request,
6914 main_grep,
6915 main_select,
6916 };
6919 /*
6920 * Status management
6921 */
6923 /* Whether or not the curses interface has been initialized. */
6924 static bool cursed = FALSE;
6926 /* Terminal hacks and workarounds. */
6927 static bool use_scroll_redrawwin;
6928 static bool use_scroll_status_wclear;
6930 /* The status window is used for polling keystrokes. */
6931 static WINDOW *status_win;
6933 /* Reading from the prompt? */
6934 static bool input_mode = FALSE;
6936 static bool status_empty = FALSE;
6938 /* Update status and title window. */
6939 static void
6940 report(const char *msg, ...)
6941 {
6942 struct view *view = display[current_view];
6944 if (input_mode)
6945 return;
6947 if (!view) {
6948 char buf[SIZEOF_STR];
6949 va_list args;
6951 va_start(args, msg);
6952 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6953 buf[sizeof(buf) - 1] = 0;
6954 buf[sizeof(buf) - 2] = '.';
6955 buf[sizeof(buf) - 3] = '.';
6956 buf[sizeof(buf) - 4] = '.';
6957 }
6958 va_end(args);
6959 die("%s", buf);
6960 }
6962 if (!status_empty || *msg) {
6963 va_list args;
6965 va_start(args, msg);
6967 wmove(status_win, 0, 0);
6968 if (view->has_scrolled && use_scroll_status_wclear)
6969 wclear(status_win);
6970 if (*msg) {
6971 vwprintw(status_win, msg, args);
6972 status_empty = FALSE;
6973 } else {
6974 status_empty = TRUE;
6975 }
6976 wclrtoeol(status_win);
6977 wnoutrefresh(status_win);
6979 va_end(args);
6980 }
6982 update_view_title(view);
6983 }
6985 static void
6986 init_display(void)
6987 {
6988 const char *term;
6989 int x, y;
6991 /* Initialize the curses library */
6992 if (isatty(STDIN_FILENO)) {
6993 cursed = !!initscr();
6994 opt_tty = stdin;
6995 } else {
6996 /* Leave stdin and stdout alone when acting as a pager. */
6997 opt_tty = fopen("/dev/tty", "r+");
6998 if (!opt_tty)
6999 die("Failed to open /dev/tty");
7000 cursed = !!newterm(NULL, opt_tty, opt_tty);
7001 }
7003 if (!cursed)
7004 die("Failed to initialize curses");
7006 nonl(); /* Disable conversion and detect newlines from input. */
7007 cbreak(); /* Take input chars one at a time, no wait for \n */
7008 noecho(); /* Don't echo input */
7009 leaveok(stdscr, FALSE);
7011 if (has_colors())
7012 init_colors();
7014 getmaxyx(stdscr, y, x);
7015 status_win = newwin(1, 0, y - 1, 0);
7016 if (!status_win)
7017 die("Failed to create status window");
7019 /* Enable keyboard mapping */
7020 keypad(status_win, TRUE);
7021 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7023 TABSIZE = opt_tab_size;
7025 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7026 if (term && !strcmp(term, "gnome-terminal")) {
7027 /* In the gnome-terminal-emulator, the message from
7028 * scrolling up one line when impossible followed by
7029 * scrolling down one line causes corruption of the
7030 * status line. This is fixed by calling wclear. */
7031 use_scroll_status_wclear = TRUE;
7032 use_scroll_redrawwin = FALSE;
7034 } else if (term && !strcmp(term, "xrvt-xpm")) {
7035 /* No problems with full optimizations in xrvt-(unicode)
7036 * and aterm. */
7037 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7039 } else {
7040 /* When scrolling in (u)xterm the last line in the
7041 * scrolling direction will update slowly. */
7042 use_scroll_redrawwin = TRUE;
7043 use_scroll_status_wclear = FALSE;
7044 }
7045 }
7047 static int
7048 get_input(int prompt_position)
7049 {
7050 struct view *view;
7051 int i, key, cursor_y, cursor_x;
7052 bool loading = FALSE;
7054 if (prompt_position)
7055 input_mode = TRUE;
7057 while (TRUE) {
7058 foreach_view (view, i) {
7059 update_view(view);
7060 if (view_is_displayed(view) && view->has_scrolled &&
7061 use_scroll_redrawwin)
7062 redrawwin(view->win);
7063 view->has_scrolled = FALSE;
7064 if (view->pipe)
7065 loading = TRUE;
7066 }
7068 /* Update the cursor position. */
7069 if (prompt_position) {
7070 getbegyx(status_win, cursor_y, cursor_x);
7071 cursor_x = prompt_position;
7072 } else {
7073 view = display[current_view];
7074 getbegyx(view->win, cursor_y, cursor_x);
7075 cursor_x = view->width - 1;
7076 cursor_y += view->lineno - view->offset;
7077 }
7078 setsyx(cursor_y, cursor_x);
7080 /* Refresh, accept single keystroke of input */
7081 doupdate();
7082 nodelay(status_win, loading);
7083 key = wgetch(status_win);
7085 /* wgetch() with nodelay() enabled returns ERR when
7086 * there's no input. */
7087 if (key == ERR) {
7089 } else if (key == KEY_RESIZE) {
7090 int height, width;
7092 getmaxyx(stdscr, height, width);
7094 wresize(status_win, 1, width);
7095 mvwin(status_win, height - 1, 0);
7096 wnoutrefresh(status_win);
7097 resize_display();
7098 redraw_display(TRUE);
7100 } else {
7101 input_mode = FALSE;
7102 return key;
7103 }
7104 }
7105 }
7107 static char *
7108 prompt_input(const char *prompt, input_handler handler, void *data)
7109 {
7110 enum input_status status = INPUT_OK;
7111 static char buf[SIZEOF_STR];
7112 size_t pos = 0;
7114 buf[pos] = 0;
7116 while (status == INPUT_OK || status == INPUT_SKIP) {
7117 int key;
7119 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7120 wclrtoeol(status_win);
7122 key = get_input(pos + 1);
7123 switch (key) {
7124 case KEY_RETURN:
7125 case KEY_ENTER:
7126 case '\n':
7127 status = pos ? INPUT_STOP : INPUT_CANCEL;
7128 break;
7130 case KEY_BACKSPACE:
7131 if (pos > 0)
7132 buf[--pos] = 0;
7133 else
7134 status = INPUT_CANCEL;
7135 break;
7137 case KEY_ESC:
7138 status = INPUT_CANCEL;
7139 break;
7141 default:
7142 if (pos >= sizeof(buf)) {
7143 report("Input string too long");
7144 return NULL;
7145 }
7147 status = handler(data, buf, key);
7148 if (status == INPUT_OK)
7149 buf[pos++] = (char) key;
7150 }
7151 }
7153 /* Clear the status window */
7154 status_empty = FALSE;
7155 report("");
7157 if (status == INPUT_CANCEL)
7158 return NULL;
7160 buf[pos++] = 0;
7162 return buf;
7163 }
7165 static enum input_status
7166 prompt_yesno_handler(void *data, char *buf, int c)
7167 {
7168 if (c == 'y' || c == 'Y')
7169 return INPUT_STOP;
7170 if (c == 'n' || c == 'N')
7171 return INPUT_CANCEL;
7172 return INPUT_SKIP;
7173 }
7175 static bool
7176 prompt_yesno(const char *prompt)
7177 {
7178 char prompt2[SIZEOF_STR];
7180 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7181 return FALSE;
7183 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7184 }
7186 static enum input_status
7187 read_prompt_handler(void *data, char *buf, int c)
7188 {
7189 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7190 }
7192 static char *
7193 read_prompt(const char *prompt)
7194 {
7195 return prompt_input(prompt, read_prompt_handler, NULL);
7196 }
7198 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7199 {
7200 enum input_status status = INPUT_OK;
7201 int size = 0;
7203 while (items[size].text)
7204 size++;
7206 while (status == INPUT_OK) {
7207 const struct menu_item *item = &items[*selected];
7208 int key;
7209 int i;
7211 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7212 prompt, *selected + 1, size);
7213 if (item->hotkey)
7214 wprintw(status_win, "[%c] ", (char) item->hotkey);
7215 wprintw(status_win, "%s", item->text);
7216 wclrtoeol(status_win);
7218 key = get_input(COLS - 1);
7219 switch (key) {
7220 case KEY_RETURN:
7221 case KEY_ENTER:
7222 case '\n':
7223 status = INPUT_STOP;
7224 break;
7226 case KEY_LEFT:
7227 case KEY_UP:
7228 *selected = *selected - 1;
7229 if (*selected < 0)
7230 *selected = size - 1;
7231 break;
7233 case KEY_RIGHT:
7234 case KEY_DOWN:
7235 *selected = (*selected + 1) % size;
7236 break;
7238 case KEY_ESC:
7239 status = INPUT_CANCEL;
7240 break;
7242 default:
7243 for (i = 0; items[i].text; i++)
7244 if (items[i].hotkey == key) {
7245 *selected = i;
7246 status = INPUT_STOP;
7247 break;
7248 }
7249 }
7250 }
7252 /* Clear the status window */
7253 status_empty = FALSE;
7254 report("");
7256 return status != INPUT_CANCEL;
7257 }
7259 /*
7260 * Repository properties
7261 */
7263 static struct ref **refs = NULL;
7264 static size_t refs_size = 0;
7265 static struct ref *refs_head = NULL;
7267 static struct ref_list **ref_lists = NULL;
7268 static size_t ref_lists_size = 0;
7270 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7271 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7272 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7274 static int
7275 compare_refs(const void *ref1_, const void *ref2_)
7276 {
7277 const struct ref *ref1 = *(const struct ref **)ref1_;
7278 const struct ref *ref2 = *(const struct ref **)ref2_;
7280 if (ref1->tag != ref2->tag)
7281 return ref2->tag - ref1->tag;
7282 if (ref1->ltag != ref2->ltag)
7283 return ref2->ltag - ref2->ltag;
7284 if (ref1->head != ref2->head)
7285 return ref2->head - ref1->head;
7286 if (ref1->tracked != ref2->tracked)
7287 return ref2->tracked - ref1->tracked;
7288 if (ref1->remote != ref2->remote)
7289 return ref2->remote - ref1->remote;
7290 return strcmp(ref1->name, ref2->name);
7291 }
7293 static void
7294 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7295 {
7296 size_t i;
7298 for (i = 0; i < refs_size; i++)
7299 if (!visitor(data, refs[i]))
7300 break;
7301 }
7303 static struct ref *
7304 get_ref_head()
7305 {
7306 return refs_head;
7307 }
7309 static struct ref_list *
7310 get_ref_list(const char *id)
7311 {
7312 struct ref_list *list;
7313 size_t i;
7315 for (i = 0; i < ref_lists_size; i++)
7316 if (!strcmp(id, ref_lists[i]->id))
7317 return ref_lists[i];
7319 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7320 return NULL;
7321 list = calloc(1, sizeof(*list));
7322 if (!list)
7323 return NULL;
7325 for (i = 0; i < refs_size; i++) {
7326 if (!strcmp(id, refs[i]->id) &&
7327 realloc_refs_list(&list->refs, list->size, 1))
7328 list->refs[list->size++] = refs[i];
7329 }
7331 if (!list->refs) {
7332 free(list);
7333 return NULL;
7334 }
7336 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7337 ref_lists[ref_lists_size++] = list;
7338 return list;
7339 }
7341 static int
7342 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7343 {
7344 struct ref *ref = NULL;
7345 bool tag = FALSE;
7346 bool ltag = FALSE;
7347 bool remote = FALSE;
7348 bool tracked = FALSE;
7349 bool head = FALSE;
7350 int from = 0, to = refs_size - 1;
7352 if (!prefixcmp(name, "refs/tags/")) {
7353 if (!suffixcmp(name, namelen, "^{}")) {
7354 namelen -= 3;
7355 name[namelen] = 0;
7356 } else {
7357 ltag = TRUE;
7358 }
7360 tag = TRUE;
7361 namelen -= STRING_SIZE("refs/tags/");
7362 name += STRING_SIZE("refs/tags/");
7364 } else if (!prefixcmp(name, "refs/remotes/")) {
7365 remote = TRUE;
7366 namelen -= STRING_SIZE("refs/remotes/");
7367 name += STRING_SIZE("refs/remotes/");
7368 tracked = !strcmp(opt_remote, name);
7370 } else if (!prefixcmp(name, "refs/heads/")) {
7371 namelen -= STRING_SIZE("refs/heads/");
7372 name += STRING_SIZE("refs/heads/");
7373 if (!strncmp(opt_head, name, namelen))
7374 return OK;
7376 } else if (!strcmp(name, "HEAD")) {
7377 head = TRUE;
7378 if (*opt_head) {
7379 namelen = strlen(opt_head);
7380 name = opt_head;
7381 }
7382 }
7384 /* If we are reloading or it's an annotated tag, replace the
7385 * previous SHA1 with the resolved commit id; relies on the fact
7386 * git-ls-remote lists the commit id of an annotated tag right
7387 * before the commit id it points to. */
7388 while (from <= to) {
7389 size_t pos = (to + from) / 2;
7390 int cmp = strcmp(name, refs[pos]->name);
7392 if (!cmp) {
7393 ref = refs[pos];
7394 break;
7395 }
7397 if (cmp < 0)
7398 to = pos - 1;
7399 else
7400 from = pos + 1;
7401 }
7403 if (!ref) {
7404 if (!realloc_refs(&refs, refs_size, 1))
7405 return ERR;
7406 ref = calloc(1, sizeof(*ref) + namelen);
7407 if (!ref)
7408 return ERR;
7409 memmove(refs + from + 1, refs + from,
7410 (refs_size - from) * sizeof(*refs));
7411 refs[from] = ref;
7412 strncpy(ref->name, name, namelen);
7413 refs_size++;
7414 }
7416 ref->head = head;
7417 ref->tag = tag;
7418 ref->ltag = ltag;
7419 ref->remote = remote;
7420 ref->tracked = tracked;
7421 string_copy_rev(ref->id, id);
7423 if (head)
7424 refs_head = ref;
7425 return OK;
7426 }
7428 static int
7429 load_refs(void)
7430 {
7431 const char *head_argv[] = {
7432 "git", "symbolic-ref", "HEAD", NULL
7433 };
7434 static const char *ls_remote_argv[SIZEOF_ARG] = {
7435 "git", "ls-remote", opt_git_dir, NULL
7436 };
7437 static bool init = FALSE;
7438 size_t i;
7440 if (!init) {
7441 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7442 die("TIG_LS_REMOTE contains too many arguments");
7443 init = TRUE;
7444 }
7446 if (!*opt_git_dir)
7447 return OK;
7449 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7450 !prefixcmp(opt_head, "refs/heads/")) {
7451 char *offset = opt_head + STRING_SIZE("refs/heads/");
7453 memmove(opt_head, offset, strlen(offset) + 1);
7454 }
7456 refs_head = NULL;
7457 for (i = 0; i < refs_size; i++)
7458 refs[i]->id[0] = 0;
7460 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7461 return ERR;
7463 /* Update the ref lists to reflect changes. */
7464 for (i = 0; i < ref_lists_size; i++) {
7465 struct ref_list *list = ref_lists[i];
7466 size_t old, new;
7468 for (old = new = 0; old < list->size; old++)
7469 if (!strcmp(list->id, list->refs[old]->id))
7470 list->refs[new++] = list->refs[old];
7471 list->size = new;
7472 }
7474 return OK;
7475 }
7477 static void
7478 set_remote_branch(const char *name, const char *value, size_t valuelen)
7479 {
7480 if (!strcmp(name, ".remote")) {
7481 string_ncopy(opt_remote, value, valuelen);
7483 } else if (*opt_remote && !strcmp(name, ".merge")) {
7484 size_t from = strlen(opt_remote);
7486 if (!prefixcmp(value, "refs/heads/"))
7487 value += STRING_SIZE("refs/heads/");
7489 if (!string_format_from(opt_remote, &from, "/%s", value))
7490 opt_remote[0] = 0;
7491 }
7492 }
7494 static void
7495 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7496 {
7497 const char *argv[SIZEOF_ARG] = { name, "=" };
7498 int argc = 1 + (cmd == option_set_command);
7499 int error = ERR;
7501 if (!argv_from_string(argv, &argc, value))
7502 config_msg = "Too many option arguments";
7503 else
7504 error = cmd(argc, argv);
7506 if (error == ERR)
7507 warn("Option 'tig.%s': %s", name, config_msg);
7508 }
7510 static bool
7511 set_environment_variable(const char *name, const char *value)
7512 {
7513 size_t len = strlen(name) + 1 + strlen(value) + 1;
7514 char *env = malloc(len);
7516 if (env &&
7517 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7518 putenv(env) == 0)
7519 return TRUE;
7520 free(env);
7521 return FALSE;
7522 }
7524 static void
7525 set_work_tree(const char *value)
7526 {
7527 char cwd[SIZEOF_STR];
7529 if (!getcwd(cwd, sizeof(cwd)))
7530 die("Failed to get cwd path: %s", strerror(errno));
7531 if (chdir(opt_git_dir) < 0)
7532 die("Failed to chdir(%s): %s", strerror(errno));
7533 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7534 die("Failed to get git path: %s", strerror(errno));
7535 if (chdir(cwd) < 0)
7536 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7537 if (chdir(value) < 0)
7538 die("Failed to chdir(%s): %s", value, strerror(errno));
7539 if (!getcwd(cwd, sizeof(cwd)))
7540 die("Failed to get cwd path: %s", strerror(errno));
7541 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7542 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7543 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7544 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7545 opt_is_inside_work_tree = TRUE;
7546 }
7548 static int
7549 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7550 {
7551 if (!strcmp(name, "i18n.commitencoding"))
7552 string_ncopy(opt_encoding, value, valuelen);
7554 else if (!strcmp(name, "core.editor"))
7555 string_ncopy(opt_editor, value, valuelen);
7557 else if (!strcmp(name, "core.worktree"))
7558 set_work_tree(value);
7560 else if (!prefixcmp(name, "tig.color."))
7561 set_repo_config_option(name + 10, value, option_color_command);
7563 else if (!prefixcmp(name, "tig.bind."))
7564 set_repo_config_option(name + 9, value, option_bind_command);
7566 else if (!prefixcmp(name, "tig."))
7567 set_repo_config_option(name + 4, value, option_set_command);
7569 else if (*opt_head && !prefixcmp(name, "branch.") &&
7570 !strncmp(name + 7, opt_head, strlen(opt_head)))
7571 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7573 return OK;
7574 }
7576 static int
7577 load_git_config(void)
7578 {
7579 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7581 return io_run_load(config_list_argv, "=", read_repo_config_option);
7582 }
7584 static int
7585 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7586 {
7587 if (!opt_git_dir[0]) {
7588 string_ncopy(opt_git_dir, name, namelen);
7590 } else if (opt_is_inside_work_tree == -1) {
7591 /* This can be 3 different values depending on the
7592 * version of git being used. If git-rev-parse does not
7593 * understand --is-inside-work-tree it will simply echo
7594 * the option else either "true" or "false" is printed.
7595 * Default to true for the unknown case. */
7596 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7598 } else if (*name == '.') {
7599 string_ncopy(opt_cdup, name, namelen);
7601 } else {
7602 string_ncopy(opt_prefix, name, namelen);
7603 }
7605 return OK;
7606 }
7608 static int
7609 load_repo_info(void)
7610 {
7611 const char *rev_parse_argv[] = {
7612 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7613 "--show-cdup", "--show-prefix", NULL
7614 };
7616 return io_run_load(rev_parse_argv, "=", read_repo_info);
7617 }
7620 /*
7621 * Main
7622 */
7624 static const char usage[] =
7625 "tig " TIG_VERSION " (" __DATE__ ")\n"
7626 "\n"
7627 "Usage: tig [options] [revs] [--] [paths]\n"
7628 " or: tig show [options] [revs] [--] [paths]\n"
7629 " or: tig blame [rev] path\n"
7630 " or: tig status\n"
7631 " or: tig < [git command output]\n"
7632 "\n"
7633 "Options:\n"
7634 " -v, --version Show version and exit\n"
7635 " -h, --help Show help message and exit";
7637 static void __NORETURN
7638 quit(int sig)
7639 {
7640 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7641 if (cursed)
7642 endwin();
7643 exit(0);
7644 }
7646 static void __NORETURN
7647 die(const char *err, ...)
7648 {
7649 va_list args;
7651 endwin();
7653 va_start(args, err);
7654 fputs("tig: ", stderr);
7655 vfprintf(stderr, err, args);
7656 fputs("\n", stderr);
7657 va_end(args);
7659 exit(1);
7660 }
7662 static void
7663 warn(const char *msg, ...)
7664 {
7665 va_list args;
7667 va_start(args, msg);
7668 fputs("tig warning: ", stderr);
7669 vfprintf(stderr, msg, args);
7670 fputs("\n", stderr);
7671 va_end(args);
7672 }
7674 static enum request
7675 parse_options(int argc, const char *argv[])
7676 {
7677 enum request request = REQ_VIEW_MAIN;
7678 const char *subcommand;
7679 bool seen_dashdash = FALSE;
7680 /* XXX: This is vulnerable to the user overriding options
7681 * required for the main view parser. */
7682 const char *custom_argv[SIZEOF_ARG] = {
7683 "git", "log", "--no-color", "--pretty=raw", "--parents",
7684 "--topo-order", NULL
7685 };
7686 int i, j = 6;
7688 if (!isatty(STDIN_FILENO)) {
7689 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7690 return REQ_VIEW_PAGER;
7691 }
7693 if (argc <= 1)
7694 return REQ_NONE;
7696 subcommand = argv[1];
7697 if (!strcmp(subcommand, "status")) {
7698 if (argc > 2)
7699 warn("ignoring arguments after `%s'", subcommand);
7700 return REQ_VIEW_STATUS;
7702 } else if (!strcmp(subcommand, "blame")) {
7703 if (argc <= 2 || argc > 4)
7704 die("invalid number of options to blame\n\n%s", usage);
7706 i = 2;
7707 if (argc == 4) {
7708 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7709 i++;
7710 }
7712 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7713 return REQ_VIEW_BLAME;
7715 } else if (!strcmp(subcommand, "show")) {
7716 request = REQ_VIEW_DIFF;
7718 } else {
7719 subcommand = NULL;
7720 }
7722 if (subcommand) {
7723 custom_argv[1] = subcommand;
7724 j = 2;
7725 }
7727 for (i = 1 + !!subcommand; i < argc; i++) {
7728 const char *opt = argv[i];
7730 if (seen_dashdash || !strcmp(opt, "--")) {
7731 seen_dashdash = TRUE;
7733 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7734 printf("tig version %s\n", TIG_VERSION);
7735 quit(0);
7737 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7738 printf("%s\n", usage);
7739 quit(0);
7740 }
7742 custom_argv[j++] = opt;
7743 if (j >= ARRAY_SIZE(custom_argv))
7744 die("command too long");
7745 }
7747 if (!prepare_update(VIEW(request), custom_argv, NULL))
7748 die("Failed to format arguments");
7750 return request;
7751 }
7753 int
7754 main(int argc, const char *argv[])
7755 {
7756 const char *codeset = "UTF-8";
7757 enum request request = parse_options(argc, argv);
7758 struct view *view;
7759 size_t i;
7761 signal(SIGINT, quit);
7762 signal(SIGPIPE, SIG_IGN);
7764 if (setlocale(LC_ALL, "")) {
7765 codeset = nl_langinfo(CODESET);
7766 }
7768 if (load_repo_info() == ERR)
7769 die("Failed to load repo info.");
7771 if (load_options() == ERR)
7772 die("Failed to load user config.");
7774 if (load_git_config() == ERR)
7775 die("Failed to load repo config.");
7777 /* Require a git repository unless when running in pager mode. */
7778 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7779 die("Not a git repository");
7781 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7782 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7783 if (opt_iconv_in == ICONV_NONE)
7784 die("Failed to initialize character set conversion");
7785 }
7787 if (codeset && strcmp(codeset, "UTF-8")) {
7788 opt_iconv_out = iconv_open(codeset, "UTF-8");
7789 if (opt_iconv_out == ICONV_NONE)
7790 die("Failed to initialize character set conversion");
7791 }
7793 if (load_refs() == ERR)
7794 die("Failed to load refs.");
7796 foreach_view (view, i)
7797 if (!argv_from_env(view->ops->argv, view->cmd_env))
7798 die("Too many arguments in the `%s` environment variable",
7799 view->cmd_env);
7801 init_display();
7803 if (request != REQ_NONE)
7804 open_view(NULL, request, OPEN_PREPARED);
7805 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7807 while (view_driver(display[current_view], request)) {
7808 int key = get_input(0);
7810 view = display[current_view];
7811 request = get_keybinding(view->keymap, key);
7813 /* Some low-level request handling. This keeps access to
7814 * status_win restricted. */
7815 switch (request) {
7816 case REQ_NONE:
7817 report("Unknown key, press %s for help",
7818 get_key(view->keymap, REQ_VIEW_HELP));
7819 break;
7820 case REQ_PROMPT:
7821 {
7822 char *cmd = read_prompt(":");
7824 if (cmd && isdigit(*cmd)) {
7825 int lineno = view->lineno + 1;
7827 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7828 select_view_line(view, lineno - 1);
7829 report("");
7830 } else {
7831 report("Unable to parse '%s' as a line number", cmd);
7832 }
7834 } else if (cmd) {
7835 struct view *next = VIEW(REQ_VIEW_PAGER);
7836 const char *argv[SIZEOF_ARG] = { "git" };
7837 int argc = 1;
7839 /* When running random commands, initially show the
7840 * command in the title. However, it maybe later be
7841 * overwritten if a commit line is selected. */
7842 string_ncopy(next->ref, cmd, strlen(cmd));
7844 if (!argv_from_string(argv, &argc, cmd)) {
7845 report("Too many arguments");
7846 } else if (!prepare_update(next, argv, NULL)) {
7847 report("Failed to format command");
7848 } else {
7849 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7850 }
7851 }
7853 request = REQ_NONE;
7854 break;
7855 }
7856 case REQ_SEARCH:
7857 case REQ_SEARCH_BACK:
7858 {
7859 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7860 char *search = read_prompt(prompt);
7862 if (search)
7863 string_ncopy(opt_search, search, strlen(search));
7864 else if (*opt_search)
7865 request = request == REQ_SEARCH ?
7866 REQ_FIND_NEXT :
7867 REQ_FIND_PREV;
7868 else
7869 request = REQ_NONE;
7870 break;
7871 }
7872 default:
7873 break;
7874 }
7875 }
7877 quit(0);
7879 return 0;
7880 }