1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_NONE /* No replacement should be performed. */
146 };
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 enum input_status {
151 INPUT_OK,
152 INPUT_SKIP,
153 INPUT_STOP,
154 INPUT_CANCEL
155 };
157 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
159 static char *prompt_input(const char *prompt, input_handler handler, void *data);
160 static bool prompt_yesno(const char *prompt);
162 struct menu_item {
163 int hotkey;
164 const char *text;
165 void *data;
166 };
168 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
170 /*
171 * Allocation helpers ... Entering macro hell to never be seen again.
172 */
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
175 static type * \
176 name(type **mem, size_t size, size_t increase) \
177 { \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
180 type *tmp = *mem; \
181 \
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
184 if (tmp) \
185 *mem = tmp; \
186 } \
187 \
188 return tmp; \
189 }
191 /*
192 * String helpers
193 */
195 static inline void
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
197 {
198 if (srclen > dstlen - 1)
199 srclen = dstlen - 1;
201 strncpy(dst, src, srclen);
202 dst[srclen] = 0;
203 }
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
219 static void
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
221 {
222 size_t size, pos;
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
231 size += expanded;
232 } else {
233 dst[size++] = src[pos];
234 }
235 }
237 dst[size] = 0;
238 }
240 static char *
241 chomp_string(char *name)
242 {
243 int namelen;
245 while (isspace(*name))
246 name++;
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
250 name[namelen--] = 0;
252 return name;
253 }
255 static bool
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
257 {
258 va_list args;
259 size_t pos = bufpos ? *bufpos : 0;
261 va_start(args, fmt);
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 va_end(args);
265 if (bufpos)
266 *bufpos = pos;
268 return pos >= bufsize ? FALSE : TRUE;
269 }
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
277 static int
278 string_enum_compare(const char *str1, const char *str2, int len)
279 {
280 size_t i;
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
287 continue;
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
291 continue;
293 return str1[i] - str2[i];
294 }
296 return 0;
297 }
299 #define enum_equals(entry, str, len) \
300 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
302 struct enum_map {
303 const char *name;
304 int namelen;
305 int value;
306 };
308 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
310 static char *
311 enum_map_name(const char *name, size_t namelen)
312 {
313 static char buf[SIZEOF_STR];
314 int bufpos;
316 for (bufpos = 0; bufpos <= namelen; bufpos++) {
317 buf[bufpos] = tolower(name[bufpos]);
318 if (buf[bufpos] == '_')
319 buf[bufpos] = '-';
320 }
322 buf[bufpos] = 0;
323 return buf;
324 }
326 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
328 static bool
329 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
330 {
331 size_t namelen = strlen(name);
332 int i;
334 for (i = 0; i < map_size; i++)
335 if (enum_equals(map[i], name, namelen)) {
336 *value = map[i].value;
337 return TRUE;
338 }
340 return FALSE;
341 }
343 #define map_enum(attr, map, name) \
344 map_enum_do(map, ARRAY_SIZE(map), attr, name)
346 #define prefixcmp(str1, str2) \
347 strncmp(str1, str2, STRING_SIZE(str2))
349 static inline int
350 suffixcmp(const char *str, int slen, const char *suffix)
351 {
352 size_t len = slen >= 0 ? slen : strlen(str);
353 size_t suffixlen = strlen(suffix);
355 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
356 }
359 /*
360 * Unicode / UTF-8 handling
361 *
362 * NOTE: Much of the following code for dealing with Unicode is derived from
363 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
364 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
365 */
367 static inline int
368 unicode_width(unsigned long c, int tab_size)
369 {
370 if (c >= 0x1100 &&
371 (c <= 0x115f /* Hangul Jamo */
372 || c == 0x2329
373 || c == 0x232a
374 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
375 /* CJK ... Yi */
376 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
377 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
378 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
379 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
380 || (c >= 0xffe0 && c <= 0xffe6)
381 || (c >= 0x20000 && c <= 0x2fffd)
382 || (c >= 0x30000 && c <= 0x3fffd)))
383 return 2;
385 if (c == '\t')
386 return tab_size;
388 return 1;
389 }
391 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
392 * Illegal bytes are set one. */
393 static const unsigned char utf8_bytes[256] = {
394 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
396 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
397 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
398 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
399 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
400 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
401 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
402 };
404 static inline unsigned char
405 utf8_char_length(const char *string, const char *end)
406 {
407 int c = *(unsigned char *) string;
409 return utf8_bytes[c];
410 }
412 /* Decode UTF-8 multi-byte representation into a Unicode character. */
413 static inline unsigned long
414 utf8_to_unicode(const char *string, size_t length)
415 {
416 unsigned long unicode;
418 switch (length) {
419 case 1:
420 unicode = string[0];
421 break;
422 case 2:
423 unicode = (string[0] & 0x1f) << 6;
424 unicode += (string[1] & 0x3f);
425 break;
426 case 3:
427 unicode = (string[0] & 0x0f) << 12;
428 unicode += ((string[1] & 0x3f) << 6);
429 unicode += (string[2] & 0x3f);
430 break;
431 case 4:
432 unicode = (string[0] & 0x0f) << 18;
433 unicode += ((string[1] & 0x3f) << 12);
434 unicode += ((string[2] & 0x3f) << 6);
435 unicode += (string[3] & 0x3f);
436 break;
437 case 5:
438 unicode = (string[0] & 0x0f) << 24;
439 unicode += ((string[1] & 0x3f) << 18);
440 unicode += ((string[2] & 0x3f) << 12);
441 unicode += ((string[3] & 0x3f) << 6);
442 unicode += (string[4] & 0x3f);
443 break;
444 case 6:
445 unicode = (string[0] & 0x01) << 30;
446 unicode += ((string[1] & 0x3f) << 24);
447 unicode += ((string[2] & 0x3f) << 18);
448 unicode += ((string[3] & 0x3f) << 12);
449 unicode += ((string[4] & 0x3f) << 6);
450 unicode += (string[5] & 0x3f);
451 break;
452 default:
453 return 0;
454 }
456 /* Invalid characters could return the special 0xfffd value but NUL
457 * should be just as good. */
458 return unicode > 0xffff ? 0 : unicode;
459 }
461 /* Calculates how much of string can be shown within the given maximum width
462 * and sets trimmed parameter to non-zero value if all of string could not be
463 * shown. If the reserve flag is TRUE, it will reserve at least one
464 * trailing character, which can be useful when drawing a delimiter.
465 *
466 * Returns the number of bytes to output from string to satisfy max_width. */
467 static size_t
468 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
469 {
470 const char *string = *start;
471 const char *end = strchr(string, '\0');
472 unsigned char last_bytes = 0;
473 size_t last_ucwidth = 0;
475 *width = 0;
476 *trimmed = 0;
478 while (string < end) {
479 unsigned char bytes = utf8_char_length(string, end);
480 size_t ucwidth;
481 unsigned long unicode;
483 if (string + bytes > end)
484 break;
486 /* Change representation to figure out whether
487 * it is a single- or double-width character. */
489 unicode = utf8_to_unicode(string, bytes);
490 /* FIXME: Graceful handling of invalid Unicode character. */
491 if (!unicode)
492 break;
494 ucwidth = unicode_width(unicode, tab_size);
495 if (skip > 0) {
496 skip -= ucwidth <= skip ? ucwidth : skip;
497 *start += bytes;
498 }
499 *width += ucwidth;
500 if (*width > max_width) {
501 *trimmed = 1;
502 *width -= ucwidth;
503 if (reserve && *width == max_width) {
504 string -= last_bytes;
505 *width -= last_ucwidth;
506 }
507 break;
508 }
510 string += bytes;
511 last_bytes = ucwidth ? bytes : 0;
512 last_ucwidth = ucwidth;
513 }
515 return string - *start;
516 }
519 #define DATE_INFO \
520 DATE_(NO), \
521 DATE_(DEFAULT), \
522 DATE_(RELATIVE), \
523 DATE_(SHORT)
525 enum date {
526 #define DATE_(name) DATE_##name
527 DATE_INFO
528 #undef DATE_
529 };
531 static const struct enum_map date_map[] = {
532 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
533 DATE_INFO
534 #undef DATE_
535 };
537 struct time {
538 time_t sec;
539 int tz;
540 };
542 static inline int timecmp(const struct time *t1, const struct time *t2)
543 {
544 return t1->sec - t2->sec;
545 }
547 static const char *
548 mkdate(const struct time *time, enum date date)
549 {
550 static char buf[DATE_COLS + 1];
551 static const struct enum_map reldate[] = {
552 { "second", 1, 60 * 2 },
553 { "minute", 60, 60 * 60 * 2 },
554 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
555 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
556 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
557 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
558 };
559 struct tm tm;
561 if (!date || !time || !time->sec)
562 return "";
564 if (date == DATE_RELATIVE) {
565 struct timeval now;
566 time_t date = time->sec + time->tz;
567 time_t seconds;
568 int i;
570 gettimeofday(&now, NULL);
571 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
572 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
573 if (seconds >= reldate[i].value)
574 continue;
576 seconds /= reldate[i].namelen;
577 if (!string_format(buf, "%ld %s%s %s",
578 seconds, reldate[i].name,
579 seconds > 1 ? "s" : "",
580 now.tv_sec >= date ? "ago" : "ahead"))
581 break;
582 return buf;
583 }
584 }
586 gmtime_r(&time->sec, &tm);
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
675 /*
676 * Executing external commands.
677 */
679 enum io_type {
680 IO_FD, /* File descriptor based IO. */
681 IO_BG, /* Execute command in the background. */
682 IO_FG, /* Execute command with same std{in,out,err}. */
683 IO_RD, /* Read only fork+exec IO. */
684 IO_WR, /* Write only fork+exec IO. */
685 IO_AP, /* Append fork+exec output to file. */
686 };
688 struct io {
689 enum io_type type; /* The requested type of pipe. */
690 const char *dir; /* Directory from which to execute. */
691 pid_t pid; /* PID of spawned process. */
692 int pipe; /* Pipe end for reading or writing. */
693 int error; /* Error status. */
694 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
695 char *buf; /* Read buffer. */
696 size_t bufalloc; /* Allocated buffer size. */
697 size_t bufsize; /* Buffer content size. */
698 char *bufpos; /* Current buffer position. */
699 unsigned int eof:1; /* Has end of file been reached. */
700 };
702 static void
703 io_reset(struct io *io)
704 {
705 io->pipe = -1;
706 io->pid = 0;
707 io->buf = io->bufpos = NULL;
708 io->bufalloc = io->bufsize = 0;
709 io->error = 0;
710 io->eof = 0;
711 }
713 static void
714 io_init(struct io *io, const char *dir, enum io_type type)
715 {
716 io_reset(io);
717 io->type = type;
718 io->dir = dir;
719 }
721 static bool
722 io_format(struct io *io, const char *dir, enum io_type type,
723 const char *argv[], enum format_flags flags)
724 {
725 io_init(io, dir, type);
726 return format_argv(io->argv, argv, flags);
727 }
729 static bool
730 io_open(struct io *io, const char *fmt, ...)
731 {
732 char name[SIZEOF_STR] = "";
733 bool fits;
734 va_list args;
736 io_init(io, NULL, IO_FD);
738 va_start(args, fmt);
739 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
740 va_end(args);
742 if (!fits) {
743 io->error = ENAMETOOLONG;
744 return FALSE;
745 }
746 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
747 if (io->pipe == -1)
748 io->error = errno;
749 return io->pipe != -1;
750 }
752 static bool
753 io_kill(struct io *io)
754 {
755 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
756 }
758 static bool
759 io_done(struct io *io)
760 {
761 pid_t pid = io->pid;
763 if (io->pipe != -1)
764 close(io->pipe);
765 free(io->buf);
766 io_reset(io);
768 while (pid > 0) {
769 int status;
770 pid_t waiting = waitpid(pid, &status, 0);
772 if (waiting < 0) {
773 if (errno == EINTR)
774 continue;
775 io->error = errno;
776 return FALSE;
777 }
779 return waiting == pid &&
780 !WIFSIGNALED(status) &&
781 WIFEXITED(status) &&
782 !WEXITSTATUS(status);
783 }
785 return TRUE;
786 }
788 static bool
789 io_start(struct io *io)
790 {
791 int pipefds[2] = { -1, -1 };
793 if (io->type == IO_FD)
794 return TRUE;
796 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
797 io->error = errno;
798 return FALSE;
799 } else if (io->type == IO_AP) {
800 pipefds[1] = io->pipe;
801 }
803 if ((io->pid = fork())) {
804 if (io->pid == -1)
805 io->error = errno;
806 if (pipefds[!(io->type == IO_WR)] != -1)
807 close(pipefds[!(io->type == IO_WR)]);
808 if (io->pid != -1) {
809 io->pipe = pipefds[!!(io->type == IO_WR)];
810 return TRUE;
811 }
813 } else {
814 if (io->type != IO_FG) {
815 int devnull = open("/dev/null", O_RDWR);
816 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
817 int writefd = (io->type == IO_RD || io->type == IO_AP)
818 ? pipefds[1] : devnull;
820 dup2(readfd, STDIN_FILENO);
821 dup2(writefd, STDOUT_FILENO);
822 dup2(devnull, STDERR_FILENO);
824 close(devnull);
825 if (pipefds[0] != -1)
826 close(pipefds[0]);
827 if (pipefds[1] != -1)
828 close(pipefds[1]);
829 }
831 if (io->dir && *io->dir && chdir(io->dir) == -1)
832 exit(errno);
834 execvp(io->argv[0], (char *const*) io->argv);
835 exit(errno);
836 }
838 if (pipefds[!!(io->type == IO_WR)] != -1)
839 close(pipefds[!!(io->type == IO_WR)]);
840 return FALSE;
841 }
843 static bool
844 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
845 {
846 io_init(io, dir, type);
847 if (!format_argv(io->argv, argv, FORMAT_NONE))
848 return FALSE;
849 return io_start(io);
850 }
852 static int
853 io_complete(struct io *io)
854 {
855 return io_start(io) && io_done(io);
856 }
858 static int
859 io_run_bg(const char **argv)
860 {
861 struct io io = {};
863 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
864 return FALSE;
865 return io_complete(&io);
866 }
868 static bool
869 io_run_fg(const char **argv, const char *dir)
870 {
871 struct io io = {};
873 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
874 return FALSE;
875 return io_complete(&io);
876 }
878 static bool
879 io_run_append(const char **argv, enum format_flags flags, int fd)
880 {
881 struct io io = {};
883 if (!io_format(&io, NULL, IO_AP, argv, flags)) {
884 close(fd);
885 return FALSE;
886 }
888 io.pipe = fd;
889 return io_complete(&io);
890 }
892 static bool
893 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
894 {
895 return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
896 }
898 static bool
899 io_eof(struct io *io)
900 {
901 return io->eof;
902 }
904 static int
905 io_error(struct io *io)
906 {
907 return io->error;
908 }
910 static char *
911 io_strerror(struct io *io)
912 {
913 return strerror(io->error);
914 }
916 static bool
917 io_can_read(struct io *io)
918 {
919 struct timeval tv = { 0, 500 };
920 fd_set fds;
922 FD_ZERO(&fds);
923 FD_SET(io->pipe, &fds);
925 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
926 }
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
930 {
931 do {
932 ssize_t readsize = read(io->pipe, buf, bufsize);
934 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935 continue;
936 else if (readsize == -1)
937 io->error = errno;
938 else if (readsize == 0)
939 io->eof = 1;
940 return readsize;
941 } while (1);
942 }
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
948 {
949 char *eol;
950 ssize_t readsize;
952 while (TRUE) {
953 if (io->bufsize > 0) {
954 eol = memchr(io->bufpos, c, io->bufsize);
955 if (eol) {
956 char *line = io->bufpos;
958 *eol = 0;
959 io->bufpos = eol + 1;
960 io->bufsize -= io->bufpos - line;
961 return line;
962 }
963 }
965 if (io_eof(io)) {
966 if (io->bufsize) {
967 io->bufpos[io->bufsize] = 0;
968 io->bufsize = 0;
969 return io->bufpos;
970 }
971 return NULL;
972 }
974 if (!can_read)
975 return NULL;
977 if (io->bufsize > 0 && io->bufpos > io->buf)
978 memmove(io->buf, io->bufpos, io->bufsize);
980 if (io->bufalloc == io->bufsize) {
981 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982 return NULL;
983 io->bufalloc += BUFSIZ;
984 }
986 io->bufpos = io->buf;
987 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988 if (io_error(io))
989 return NULL;
990 io->bufsize += readsize;
991 }
992 }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
996 {
997 size_t written = 0;
999 while (!io_error(io) && written < bufsize) {
1000 ssize_t size;
1002 size = write(io->pipe, buf + written, bufsize - written);
1003 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004 continue;
1005 else if (size == -1)
1006 io->error = errno;
1007 else
1008 written += size;
1009 }
1011 return written == bufsize;
1012 }
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1016 {
1017 char *result = io_get(io, '\n', TRUE);
1019 if (result) {
1020 result = chomp_string(result);
1021 string_ncopy_do(buf, bufsize, result, strlen(result));
1022 }
1024 return io_done(io) && result;
1025 }
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1029 {
1030 struct io io = {};
1032 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1033 && io_read_buf(&io, buf, bufsize);
1034 }
1036 static int
1037 io_load(struct io *io, const char *separators,
1038 int (*read_property)(char *, size_t, char *, size_t))
1039 {
1040 char *name;
1041 int state = OK;
1043 if (!io_start(io))
1044 return ERR;
1046 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047 char *value;
1048 size_t namelen;
1049 size_t valuelen;
1051 name = chomp_string(name);
1052 namelen = strcspn(name, separators);
1054 if (name[namelen]) {
1055 name[namelen] = 0;
1056 value = chomp_string(name + namelen + 1);
1057 valuelen = strlen(value);
1059 } else {
1060 value = "";
1061 valuelen = 0;
1062 }
1064 state = read_property(name, namelen, value, valuelen);
1065 }
1067 if (state != ERR && io_error(io))
1068 state = ERR;
1069 io_done(io);
1071 return state;
1072 }
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076 int (*read_property)(char *, size_t, char *, size_t))
1077 {
1078 struct io io = {};
1080 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1081 ? io_load(&io, separators, read_property) : ERR;
1082 }
1085 /*
1086 * User requests
1087 */
1089 #define REQ_INFO \
1090 /* XXX: Keep the view request first and in sync with views[]. */ \
1091 REQ_GROUP("View switching") \
1092 REQ_(VIEW_MAIN, "Show main view"), \
1093 REQ_(VIEW_DIFF, "Show diff view"), \
1094 REQ_(VIEW_LOG, "Show log view"), \
1095 REQ_(VIEW_TREE, "Show tree view"), \
1096 REQ_(VIEW_BLOB, "Show blob view"), \
1097 REQ_(VIEW_BLAME, "Show blame view"), \
1098 REQ_(VIEW_BRANCH, "Show branch view"), \
1099 REQ_(VIEW_HELP, "Show help page"), \
1100 REQ_(VIEW_PAGER, "Show pager view"), \
1101 REQ_(VIEW_STATUS, "Show status view"), \
1102 REQ_(VIEW_STAGE, "Show stage view"), \
1103 \
1104 REQ_GROUP("View manipulation") \
1105 REQ_(ENTER, "Enter current line and scroll"), \
1106 REQ_(NEXT, "Move to next"), \
1107 REQ_(PREVIOUS, "Move to previous"), \
1108 REQ_(PARENT, "Move to parent"), \
1109 REQ_(VIEW_NEXT, "Move focus to next view"), \
1110 REQ_(REFRESH, "Reload and refresh"), \
1111 REQ_(MAXIMIZE, "Maximize the current view"), \
1112 REQ_(VIEW_CLOSE, "Close the current view"), \
1113 REQ_(QUIT, "Close all views and quit"), \
1114 \
1115 REQ_GROUP("View specific requests") \
1116 REQ_(STATUS_UPDATE, "Update file status"), \
1117 REQ_(STATUS_REVERT, "Revert file changes"), \
1118 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1119 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1120 \
1121 REQ_GROUP("Cursor navigation") \
1122 REQ_(MOVE_UP, "Move cursor one line up"), \
1123 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1124 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1125 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1126 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1127 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1128 \
1129 REQ_GROUP("Scrolling") \
1130 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1131 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1132 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1133 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1134 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1135 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1136 \
1137 REQ_GROUP("Searching") \
1138 REQ_(SEARCH, "Search the view"), \
1139 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1140 REQ_(FIND_NEXT, "Find next search match"), \
1141 REQ_(FIND_PREV, "Find previous search match"), \
1142 \
1143 REQ_GROUP("Option manipulation") \
1144 REQ_(OPTIONS, "Open option menu"), \
1145 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1146 REQ_(TOGGLE_DATE, "Toggle date display"), \
1147 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1149 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1150 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1151 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153 \
1154 REQ_GROUP("Misc") \
1155 REQ_(PROMPT, "Bring up the prompt"), \
1156 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1157 REQ_(SHOW_VERSION, "Show version information"), \
1158 REQ_(STOP_LOADING, "Stop all loading views"), \
1159 REQ_(EDIT, "Open in editor"), \
1160 REQ_(NONE, "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168 /* Offset all requests to avoid conflicts with ncurses getch values. */
1169 REQ_OFFSET = KEY_MAX + 1,
1170 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1174 };
1176 struct request_info {
1177 enum request request;
1178 const char *name;
1179 int namelen;
1180 const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186 REQ_INFO
1187 #undef REQ_GROUP
1188 #undef REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1193 {
1194 int namelen = strlen(name);
1195 int i;
1197 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198 if (enum_equals(req_info[i], name, namelen))
1199 return req_info[i].request;
1201 return REQ_NONE;
1202 }
1205 /*
1206 * Options
1207 */
1209 /* Option and state variables. */
1210 static enum date opt_date = DATE_DEFAULT;
1211 static enum author opt_author = AUTHOR_DEFAULT;
1212 static bool opt_line_number = FALSE;
1213 static bool opt_line_graphics = TRUE;
1214 static bool opt_rev_graph = FALSE;
1215 static bool opt_show_refs = TRUE;
1216 static int opt_num_interval = 5;
1217 static double opt_hscroll = 0.50;
1218 static double opt_scale_split_view = 2.0 / 3.0;
1219 static int opt_tab_size = 8;
1220 static int opt_author_cols = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR] = "";
1222 static char opt_file[SIZEOF_STR] = "";
1223 static char opt_ref[SIZEOF_REF] = "";
1224 static char opt_head[SIZEOF_REF] = "";
1225 static char opt_remote[SIZEOF_REF] = "";
1226 static char opt_encoding[20] = "UTF-8";
1227 static iconv_t opt_iconv_in = ICONV_NONE;
1228 static iconv_t opt_iconv_out = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR] = "";
1230 static char opt_cdup[SIZEOF_STR] = "";
1231 static char opt_prefix[SIZEOF_STR] = "";
1232 static char opt_git_dir[SIZEOF_STR] = "";
1233 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR] = "";
1235 static FILE *opt_tty = NULL;
1237 #define is_initial_commit() (!get_ref_head())
1238 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1241 /*
1242 * Line-oriented content detection.
1243 */
1245 #define LINE_INFO \
1246 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1247 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1260 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1261 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1264 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1267 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1268 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1269 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1272 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1275 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1276 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1277 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1280 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1281 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1282 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1283 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1284 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1285 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1286 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1288 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1292 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1294 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1295 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1296 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1297 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1298 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1299 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1300 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1302 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1304 enum line_type {
1305 #define LINE(type, line, fg, bg, attr) \
1306 LINE_##type
1307 LINE_INFO,
1308 LINE_NONE
1309 #undef LINE
1310 };
1312 struct line_info {
1313 const char *name; /* Option name. */
1314 int namelen; /* Size of option name. */
1315 const char *line; /* The start of line to match. */
1316 int linelen; /* Size of string to match. */
1317 int fg, bg, attr; /* Color and text attributes for the lines. */
1318 };
1320 static struct line_info line_info[] = {
1321 #define LINE(type, line, fg, bg, attr) \
1322 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1323 LINE_INFO
1324 #undef LINE
1325 };
1327 static enum line_type
1328 get_line_type(const char *line)
1329 {
1330 int linelen = strlen(line);
1331 enum line_type type;
1333 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1334 /* Case insensitive search matches Signed-off-by lines better. */
1335 if (linelen >= line_info[type].linelen &&
1336 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1337 return type;
1339 return LINE_DEFAULT;
1340 }
1342 static inline int
1343 get_line_attr(enum line_type type)
1344 {
1345 assert(type < ARRAY_SIZE(line_info));
1346 return COLOR_PAIR(type) | line_info[type].attr;
1347 }
1349 static struct line_info *
1350 get_line_info(const char *name)
1351 {
1352 size_t namelen = strlen(name);
1353 enum line_type type;
1355 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1356 if (enum_equals(line_info[type], name, namelen))
1357 return &line_info[type];
1359 return NULL;
1360 }
1362 static void
1363 init_colors(void)
1364 {
1365 int default_bg = line_info[LINE_DEFAULT].bg;
1366 int default_fg = line_info[LINE_DEFAULT].fg;
1367 enum line_type type;
1369 start_color();
1371 if (assume_default_colors(default_fg, default_bg) == ERR) {
1372 default_bg = COLOR_BLACK;
1373 default_fg = COLOR_WHITE;
1374 }
1376 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1377 struct line_info *info = &line_info[type];
1378 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1379 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1381 init_pair(type, fg, bg);
1382 }
1383 }
1385 struct line {
1386 enum line_type type;
1388 /* State flags */
1389 unsigned int selected:1;
1390 unsigned int dirty:1;
1391 unsigned int cleareol:1;
1392 unsigned int other:16;
1394 void *data; /* User data */
1395 };
1398 /*
1399 * Keys
1400 */
1402 struct keybinding {
1403 int alias;
1404 enum request request;
1405 };
1407 static const struct keybinding default_keybindings[] = {
1408 /* View switching */
1409 { 'm', REQ_VIEW_MAIN },
1410 { 'd', REQ_VIEW_DIFF },
1411 { 'l', REQ_VIEW_LOG },
1412 { 't', REQ_VIEW_TREE },
1413 { 'f', REQ_VIEW_BLOB },
1414 { 'B', REQ_VIEW_BLAME },
1415 { 'H', REQ_VIEW_BRANCH },
1416 { 'p', REQ_VIEW_PAGER },
1417 { 'h', REQ_VIEW_HELP },
1418 { 'S', REQ_VIEW_STATUS },
1419 { 'c', REQ_VIEW_STAGE },
1421 /* View manipulation */
1422 { 'q', REQ_VIEW_CLOSE },
1423 { KEY_TAB, REQ_VIEW_NEXT },
1424 { KEY_RETURN, REQ_ENTER },
1425 { KEY_UP, REQ_PREVIOUS },
1426 { KEY_DOWN, REQ_NEXT },
1427 { 'R', REQ_REFRESH },
1428 { KEY_F(5), REQ_REFRESH },
1429 { 'O', REQ_MAXIMIZE },
1431 /* Cursor navigation */
1432 { 'k', REQ_MOVE_UP },
1433 { 'j', REQ_MOVE_DOWN },
1434 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1435 { KEY_END, REQ_MOVE_LAST_LINE },
1436 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1437 { ' ', REQ_MOVE_PAGE_DOWN },
1438 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1439 { 'b', REQ_MOVE_PAGE_UP },
1440 { '-', REQ_MOVE_PAGE_UP },
1442 /* Scrolling */
1443 { KEY_LEFT, REQ_SCROLL_LEFT },
1444 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1445 { KEY_IC, REQ_SCROLL_LINE_UP },
1446 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1447 { 'w', REQ_SCROLL_PAGE_UP },
1448 { 's', REQ_SCROLL_PAGE_DOWN },
1450 /* Searching */
1451 { '/', REQ_SEARCH },
1452 { '?', REQ_SEARCH_BACK },
1453 { 'n', REQ_FIND_NEXT },
1454 { 'N', REQ_FIND_PREV },
1456 /* Misc */
1457 { 'Q', REQ_QUIT },
1458 { 'z', REQ_STOP_LOADING },
1459 { 'v', REQ_SHOW_VERSION },
1460 { 'r', REQ_SCREEN_REDRAW },
1461 { 'o', REQ_OPTIONS },
1462 { '.', REQ_TOGGLE_LINENO },
1463 { 'D', REQ_TOGGLE_DATE },
1464 { 'A', REQ_TOGGLE_AUTHOR },
1465 { 'g', REQ_TOGGLE_REV_GRAPH },
1466 { 'F', REQ_TOGGLE_REFS },
1467 { 'I', REQ_TOGGLE_SORT_ORDER },
1468 { 'i', REQ_TOGGLE_SORT_FIELD },
1469 { ':', REQ_PROMPT },
1470 { 'u', REQ_STATUS_UPDATE },
1471 { '!', REQ_STATUS_REVERT },
1472 { 'M', REQ_STATUS_MERGE },
1473 { '@', REQ_STAGE_NEXT },
1474 { ',', REQ_PARENT },
1475 { 'e', REQ_EDIT },
1476 };
1478 #define KEYMAP_INFO \
1479 KEYMAP_(GENERIC), \
1480 KEYMAP_(MAIN), \
1481 KEYMAP_(DIFF), \
1482 KEYMAP_(LOG), \
1483 KEYMAP_(TREE), \
1484 KEYMAP_(BLOB), \
1485 KEYMAP_(BLAME), \
1486 KEYMAP_(BRANCH), \
1487 KEYMAP_(PAGER), \
1488 KEYMAP_(HELP), \
1489 KEYMAP_(STATUS), \
1490 KEYMAP_(STAGE)
1492 enum keymap {
1493 #define KEYMAP_(name) KEYMAP_##name
1494 KEYMAP_INFO
1495 #undef KEYMAP_
1496 };
1498 static const struct enum_map keymap_table[] = {
1499 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1500 KEYMAP_INFO
1501 #undef KEYMAP_
1502 };
1504 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1506 struct keybinding_table {
1507 struct keybinding *data;
1508 size_t size;
1509 };
1511 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1513 static void
1514 add_keybinding(enum keymap keymap, enum request request, int key)
1515 {
1516 struct keybinding_table *table = &keybindings[keymap];
1518 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1519 if (!table->data)
1520 die("Failed to allocate keybinding");
1521 table->data[table->size].alias = key;
1522 table->data[table->size++].request = request;
1523 }
1525 /* Looks for a key binding first in the given map, then in the generic map, and
1526 * lastly in the default keybindings. */
1527 static enum request
1528 get_keybinding(enum keymap keymap, int key)
1529 {
1530 size_t i;
1532 for (i = 0; i < keybindings[keymap].size; i++)
1533 if (keybindings[keymap].data[i].alias == key)
1534 return keybindings[keymap].data[i].request;
1536 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1537 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1538 return keybindings[KEYMAP_GENERIC].data[i].request;
1540 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541 if (default_keybindings[i].alias == key)
1542 return default_keybindings[i].request;
1544 return (enum request) key;
1545 }
1548 struct key {
1549 const char *name;
1550 int value;
1551 };
1553 static const struct key key_table[] = {
1554 { "Enter", KEY_RETURN },
1555 { "Space", ' ' },
1556 { "Backspace", KEY_BACKSPACE },
1557 { "Tab", KEY_TAB },
1558 { "Escape", KEY_ESC },
1559 { "Left", KEY_LEFT },
1560 { "Right", KEY_RIGHT },
1561 { "Up", KEY_UP },
1562 { "Down", KEY_DOWN },
1563 { "Insert", KEY_IC },
1564 { "Delete", KEY_DC },
1565 { "Hash", '#' },
1566 { "Home", KEY_HOME },
1567 { "End", KEY_END },
1568 { "PageUp", KEY_PPAGE },
1569 { "PageDown", KEY_NPAGE },
1570 { "F1", KEY_F(1) },
1571 { "F2", KEY_F(2) },
1572 { "F3", KEY_F(3) },
1573 { "F4", KEY_F(4) },
1574 { "F5", KEY_F(5) },
1575 { "F6", KEY_F(6) },
1576 { "F7", KEY_F(7) },
1577 { "F8", KEY_F(8) },
1578 { "F9", KEY_F(9) },
1579 { "F10", KEY_F(10) },
1580 { "F11", KEY_F(11) },
1581 { "F12", KEY_F(12) },
1582 };
1584 static int
1585 get_key_value(const char *name)
1586 {
1587 int i;
1589 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1590 if (!strcasecmp(key_table[i].name, name))
1591 return key_table[i].value;
1593 if (strlen(name) == 1 && isprint(*name))
1594 return (int) *name;
1596 return ERR;
1597 }
1599 static const char *
1600 get_key_name(int key_value)
1601 {
1602 static char key_char[] = "'X'";
1603 const char *seq = NULL;
1604 int key;
1606 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1607 if (key_table[key].value == key_value)
1608 seq = key_table[key].name;
1610 if (seq == NULL &&
1611 key_value < 127 &&
1612 isprint(key_value)) {
1613 key_char[1] = (char) key_value;
1614 seq = key_char;
1615 }
1617 return seq ? seq : "(no key)";
1618 }
1620 static bool
1621 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1622 {
1623 const char *sep = *pos > 0 ? ", " : "";
1624 const char *keyname = get_key_name(keybinding->alias);
1626 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1627 }
1629 static bool
1630 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1631 enum keymap keymap, bool all)
1632 {
1633 int i;
1635 for (i = 0; i < keybindings[keymap].size; i++) {
1636 if (keybindings[keymap].data[i].request == request) {
1637 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1638 return FALSE;
1639 if (!all)
1640 break;
1641 }
1642 }
1644 return TRUE;
1645 }
1647 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1649 static const char *
1650 get_keys(enum keymap keymap, enum request request, bool all)
1651 {
1652 static char buf[BUFSIZ];
1653 size_t pos = 0;
1654 int i;
1656 buf[pos] = 0;
1658 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1659 return "Too many keybindings!";
1660 if (pos > 0 && !all)
1661 return buf;
1663 if (keymap != KEYMAP_GENERIC) {
1664 /* Only the generic keymap includes the default keybindings when
1665 * listing all keys. */
1666 if (all)
1667 return buf;
1669 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1670 return "Too many keybindings!";
1671 if (pos)
1672 return buf;
1673 }
1675 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1676 if (default_keybindings[i].request == request) {
1677 if (!append_key(buf, &pos, &default_keybindings[i]))
1678 return "Too many keybindings!";
1679 if (!all)
1680 return buf;
1681 }
1682 }
1684 return buf;
1685 }
1687 struct run_request {
1688 enum keymap keymap;
1689 int key;
1690 const char *argv[SIZEOF_ARG];
1691 };
1693 static struct run_request *run_request;
1694 static size_t run_requests;
1696 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1698 static enum request
1699 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1700 {
1701 struct run_request *req;
1703 if (argc >= ARRAY_SIZE(req->argv) - 1)
1704 return REQ_NONE;
1706 if (!realloc_run_requests(&run_request, run_requests, 1))
1707 return REQ_NONE;
1709 req = &run_request[run_requests];
1710 req->keymap = keymap;
1711 req->key = key;
1712 req->argv[0] = NULL;
1714 if (!format_argv(req->argv, argv, FORMAT_NONE))
1715 return REQ_NONE;
1717 return REQ_NONE + ++run_requests;
1718 }
1720 static struct run_request *
1721 get_run_request(enum request request)
1722 {
1723 if (request <= REQ_NONE)
1724 return NULL;
1725 return &run_request[request - REQ_NONE - 1];
1726 }
1728 static void
1729 add_builtin_run_requests(void)
1730 {
1731 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1732 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1733 const char *commit[] = { "git", "commit", NULL };
1734 const char *gc[] = { "git", "gc", NULL };
1735 struct {
1736 enum keymap keymap;
1737 int key;
1738 int argc;
1739 const char **argv;
1740 } reqs[] = {
1741 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1742 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1743 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1744 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1745 };
1746 int i;
1748 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1749 enum request req;
1751 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1752 if (req != REQ_NONE)
1753 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1754 }
1755 }
1757 /*
1758 * User config file handling.
1759 */
1761 static int config_lineno;
1762 static bool config_errors;
1763 static const char *config_msg;
1765 static const struct enum_map color_map[] = {
1766 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1767 COLOR_MAP(DEFAULT),
1768 COLOR_MAP(BLACK),
1769 COLOR_MAP(BLUE),
1770 COLOR_MAP(CYAN),
1771 COLOR_MAP(GREEN),
1772 COLOR_MAP(MAGENTA),
1773 COLOR_MAP(RED),
1774 COLOR_MAP(WHITE),
1775 COLOR_MAP(YELLOW),
1776 };
1778 static const struct enum_map attr_map[] = {
1779 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1780 ATTR_MAP(NORMAL),
1781 ATTR_MAP(BLINK),
1782 ATTR_MAP(BOLD),
1783 ATTR_MAP(DIM),
1784 ATTR_MAP(REVERSE),
1785 ATTR_MAP(STANDOUT),
1786 ATTR_MAP(UNDERLINE),
1787 };
1789 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1791 static int parse_step(double *opt, const char *arg)
1792 {
1793 *opt = atoi(arg);
1794 if (!strchr(arg, '%'))
1795 return OK;
1797 /* "Shift down" so 100% and 1 does not conflict. */
1798 *opt = (*opt - 1) / 100;
1799 if (*opt >= 1.0) {
1800 *opt = 0.99;
1801 config_msg = "Step value larger than 100%";
1802 return ERR;
1803 }
1804 if (*opt < 0.0) {
1805 *opt = 1;
1806 config_msg = "Invalid step value";
1807 return ERR;
1808 }
1809 return OK;
1810 }
1812 static int
1813 parse_int(int *opt, const char *arg, int min, int max)
1814 {
1815 int value = atoi(arg);
1817 if (min <= value && value <= max) {
1818 *opt = value;
1819 return OK;
1820 }
1822 config_msg = "Integer value out of bound";
1823 return ERR;
1824 }
1826 static bool
1827 set_color(int *color, const char *name)
1828 {
1829 if (map_enum(color, color_map, name))
1830 return TRUE;
1831 if (!prefixcmp(name, "color"))
1832 return parse_int(color, name + 5, 0, 255) == OK;
1833 return FALSE;
1834 }
1836 /* Wants: object fgcolor bgcolor [attribute] */
1837 static int
1838 option_color_command(int argc, const char *argv[])
1839 {
1840 struct line_info *info;
1842 if (argc < 3) {
1843 config_msg = "Wrong number of arguments given to color command";
1844 return ERR;
1845 }
1847 info = get_line_info(argv[0]);
1848 if (!info) {
1849 static const struct enum_map obsolete[] = {
1850 ENUM_MAP("main-delim", LINE_DELIMITER),
1851 ENUM_MAP("main-date", LINE_DATE),
1852 ENUM_MAP("main-author", LINE_AUTHOR),
1853 };
1854 int index;
1856 if (!map_enum(&index, obsolete, argv[0])) {
1857 config_msg = "Unknown color name";
1858 return ERR;
1859 }
1860 info = &line_info[index];
1861 }
1863 if (!set_color(&info->fg, argv[1]) ||
1864 !set_color(&info->bg, argv[2])) {
1865 config_msg = "Unknown color";
1866 return ERR;
1867 }
1869 info->attr = 0;
1870 while (argc-- > 3) {
1871 int attr;
1873 if (!set_attribute(&attr, argv[argc])) {
1874 config_msg = "Unknown attribute";
1875 return ERR;
1876 }
1877 info->attr |= attr;
1878 }
1880 return OK;
1881 }
1883 static int parse_bool(bool *opt, const char *arg)
1884 {
1885 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1886 ? TRUE : FALSE;
1887 return OK;
1888 }
1890 static int parse_enum_do(unsigned int *opt, const char *arg,
1891 const struct enum_map *map, size_t map_size)
1892 {
1893 bool is_true;
1895 assert(map_size > 1);
1897 if (map_enum_do(map, map_size, (int *) opt, arg))
1898 return OK;
1900 if (parse_bool(&is_true, arg) != OK)
1901 return ERR;
1903 *opt = is_true ? map[1].value : map[0].value;
1904 return OK;
1905 }
1907 #define parse_enum(opt, arg, map) \
1908 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1910 static int
1911 parse_string(char *opt, const char *arg, size_t optsize)
1912 {
1913 int arglen = strlen(arg);
1915 switch (arg[0]) {
1916 case '\"':
1917 case '\'':
1918 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1919 config_msg = "Unmatched quotation";
1920 return ERR;
1921 }
1922 arg += 1; arglen -= 2;
1923 default:
1924 string_ncopy_do(opt, optsize, arg, arglen);
1925 return OK;
1926 }
1927 }
1929 /* Wants: name = value */
1930 static int
1931 option_set_command(int argc, const char *argv[])
1932 {
1933 if (argc != 3) {
1934 config_msg = "Wrong number of arguments given to set command";
1935 return ERR;
1936 }
1938 if (strcmp(argv[1], "=")) {
1939 config_msg = "No value assigned";
1940 return ERR;
1941 }
1943 if (!strcmp(argv[0], "show-author"))
1944 return parse_enum(&opt_author, argv[2], author_map);
1946 if (!strcmp(argv[0], "show-date"))
1947 return parse_enum(&opt_date, argv[2], date_map);
1949 if (!strcmp(argv[0], "show-rev-graph"))
1950 return parse_bool(&opt_rev_graph, argv[2]);
1952 if (!strcmp(argv[0], "show-refs"))
1953 return parse_bool(&opt_show_refs, argv[2]);
1955 if (!strcmp(argv[0], "show-line-numbers"))
1956 return parse_bool(&opt_line_number, argv[2]);
1958 if (!strcmp(argv[0], "line-graphics"))
1959 return parse_bool(&opt_line_graphics, argv[2]);
1961 if (!strcmp(argv[0], "line-number-interval"))
1962 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1964 if (!strcmp(argv[0], "author-width"))
1965 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1967 if (!strcmp(argv[0], "horizontal-scroll"))
1968 return parse_step(&opt_hscroll, argv[2]);
1970 if (!strcmp(argv[0], "split-view-height"))
1971 return parse_step(&opt_scale_split_view, argv[2]);
1973 if (!strcmp(argv[0], "tab-size"))
1974 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1976 if (!strcmp(argv[0], "commit-encoding"))
1977 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1979 config_msg = "Unknown variable name";
1980 return ERR;
1981 }
1983 /* Wants: mode request key */
1984 static int
1985 option_bind_command(int argc, const char *argv[])
1986 {
1987 enum request request;
1988 int keymap = -1;
1989 int key;
1991 if (argc < 3) {
1992 config_msg = "Wrong number of arguments given to bind command";
1993 return ERR;
1994 }
1996 if (set_keymap(&keymap, argv[0]) == ERR) {
1997 config_msg = "Unknown key map";
1998 return ERR;
1999 }
2001 key = get_key_value(argv[1]);
2002 if (key == ERR) {
2003 config_msg = "Unknown key";
2004 return ERR;
2005 }
2007 request = get_request(argv[2]);
2008 if (request == REQ_NONE) {
2009 static const struct enum_map obsolete[] = {
2010 ENUM_MAP("cherry-pick", REQ_NONE),
2011 ENUM_MAP("screen-resize", REQ_NONE),
2012 ENUM_MAP("tree-parent", REQ_PARENT),
2013 };
2014 int alias;
2016 if (map_enum(&alias, obsolete, argv[2])) {
2017 if (alias != REQ_NONE)
2018 add_keybinding(keymap, alias, key);
2019 config_msg = "Obsolete request name";
2020 return ERR;
2021 }
2022 }
2023 if (request == REQ_NONE && *argv[2]++ == '!')
2024 request = add_run_request(keymap, key, argc - 2, argv + 2);
2025 if (request == REQ_NONE) {
2026 config_msg = "Unknown request name";
2027 return ERR;
2028 }
2030 add_keybinding(keymap, request, key);
2032 return OK;
2033 }
2035 static int
2036 set_option(const char *opt, char *value)
2037 {
2038 const char *argv[SIZEOF_ARG];
2039 int argc = 0;
2041 if (!argv_from_string(argv, &argc, value)) {
2042 config_msg = "Too many option arguments";
2043 return ERR;
2044 }
2046 if (!strcmp(opt, "color"))
2047 return option_color_command(argc, argv);
2049 if (!strcmp(opt, "set"))
2050 return option_set_command(argc, argv);
2052 if (!strcmp(opt, "bind"))
2053 return option_bind_command(argc, argv);
2055 config_msg = "Unknown option command";
2056 return ERR;
2057 }
2059 static int
2060 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2061 {
2062 int status = OK;
2064 config_lineno++;
2065 config_msg = "Internal error";
2067 /* Check for comment markers, since read_properties() will
2068 * only ensure opt and value are split at first " \t". */
2069 optlen = strcspn(opt, "#");
2070 if (optlen == 0)
2071 return OK;
2073 if (opt[optlen] != 0) {
2074 config_msg = "No option value";
2075 status = ERR;
2077 } else {
2078 /* Look for comment endings in the value. */
2079 size_t len = strcspn(value, "#");
2081 if (len < valuelen) {
2082 valuelen = len;
2083 value[valuelen] = 0;
2084 }
2086 status = set_option(opt, value);
2087 }
2089 if (status == ERR) {
2090 warn("Error on line %d, near '%.*s': %s",
2091 config_lineno, (int) optlen, opt, config_msg);
2092 config_errors = TRUE;
2093 }
2095 /* Always keep going if errors are encountered. */
2096 return OK;
2097 }
2099 static void
2100 load_option_file(const char *path)
2101 {
2102 struct io io = {};
2104 /* It's OK that the file doesn't exist. */
2105 if (!io_open(&io, "%s", path))
2106 return;
2108 config_lineno = 0;
2109 config_errors = FALSE;
2111 if (io_load(&io, " \t", read_option) == ERR ||
2112 config_errors == TRUE)
2113 warn("Errors while loading %s.", path);
2114 }
2116 static int
2117 load_options(void)
2118 {
2119 const char *home = getenv("HOME");
2120 const char *tigrc_user = getenv("TIGRC_USER");
2121 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2122 char buf[SIZEOF_STR];
2124 add_builtin_run_requests();
2126 if (!tigrc_system)
2127 tigrc_system = SYSCONFDIR "/tigrc";
2128 load_option_file(tigrc_system);
2130 if (!tigrc_user) {
2131 if (!home || !string_format(buf, "%s/.tigrc", home))
2132 return ERR;
2133 tigrc_user = buf;
2134 }
2135 load_option_file(tigrc_user);
2137 return OK;
2138 }
2141 /*
2142 * The viewer
2143 */
2145 struct view;
2146 struct view_ops;
2148 /* The display array of active views and the index of the current view. */
2149 static struct view *display[2];
2150 static unsigned int current_view;
2152 #define foreach_displayed_view(view, i) \
2153 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2155 #define displayed_views() (display[1] != NULL ? 2 : 1)
2157 /* Current head and commit ID */
2158 static char ref_blob[SIZEOF_REF] = "";
2159 static char ref_commit[SIZEOF_REF] = "HEAD";
2160 static char ref_head[SIZEOF_REF] = "HEAD";
2161 static char ref_branch[SIZEOF_REF] = "";
2163 enum view_type {
2164 VIEW_MAIN,
2165 VIEW_DIFF,
2166 VIEW_LOG,
2167 VIEW_TREE,
2168 VIEW_BLOB,
2169 VIEW_BLAME,
2170 VIEW_BRANCH,
2171 VIEW_HELP,
2172 VIEW_PAGER,
2173 VIEW_STATUS,
2174 VIEW_STAGE,
2175 };
2177 struct view {
2178 enum view_type type; /* View type */
2179 const char *name; /* View name */
2180 const char *cmd_env; /* Command line set via environment */
2181 const char *id; /* Points to either of ref_{head,commit,blob} */
2183 struct view_ops *ops; /* View operations */
2185 enum keymap keymap; /* What keymap does this view have */
2186 bool git_dir; /* Whether the view requires a git directory. */
2187 bool refresh; /* Whether the view supports refreshing. */
2189 char ref[SIZEOF_REF]; /* Hovered commit reference */
2190 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2192 int height, width; /* The width and height of the main window */
2193 WINDOW *win; /* The main window */
2194 WINDOW *title; /* The title window living below the main window */
2196 /* Navigation */
2197 unsigned long offset; /* Offset of the window top */
2198 unsigned long yoffset; /* Offset from the window side. */
2199 unsigned long lineno; /* Current line number */
2200 unsigned long p_offset; /* Previous offset of the window top */
2201 unsigned long p_yoffset;/* Previous offset from the window side */
2202 unsigned long p_lineno; /* Previous current line number */
2203 bool p_restore; /* Should the previous position be restored. */
2205 /* Searching */
2206 char grep[SIZEOF_STR]; /* Search string */
2207 regex_t *regex; /* Pre-compiled regexp */
2209 /* If non-NULL, points to the view that opened this view. If this view
2210 * is closed tig will switch back to the parent view. */
2211 struct view *parent;
2213 /* Buffering */
2214 size_t lines; /* Total number of lines */
2215 struct line *line; /* Line index */
2216 unsigned int digits; /* Number of digits in the lines member. */
2218 /* Drawing */
2219 struct line *curline; /* Line currently being drawn. */
2220 enum line_type curtype; /* Attribute currently used for drawing. */
2221 unsigned long col; /* Column when drawing. */
2222 bool has_scrolled; /* View was scrolled. */
2224 /* Loading */
2225 struct io io;
2226 struct io *pipe;
2227 time_t start_time;
2228 time_t update_secs;
2229 };
2231 struct view_ops {
2232 /* What type of content being displayed. Used in the title bar. */
2233 const char *type;
2234 /* Default command arguments. */
2235 const char **argv;
2236 /* Open and reads in all view content. */
2237 bool (*open)(struct view *view);
2238 /* Read one line; updates view->line. */
2239 bool (*read)(struct view *view, char *data);
2240 /* Draw one line; @lineno must be < view->height. */
2241 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2242 /* Depending on view handle a special requests. */
2243 enum request (*request)(struct view *view, enum request request, struct line *line);
2244 /* Search for regexp in a line. */
2245 bool (*grep)(struct view *view, struct line *line);
2246 /* Select line */
2247 void (*select)(struct view *view, struct line *line);
2248 /* Prepare view for loading */
2249 bool (*prepare)(struct view *view);
2250 };
2252 static struct view_ops blame_ops;
2253 static struct view_ops blob_ops;
2254 static struct view_ops diff_ops;
2255 static struct view_ops help_ops;
2256 static struct view_ops log_ops;
2257 static struct view_ops main_ops;
2258 static struct view_ops pager_ops;
2259 static struct view_ops stage_ops;
2260 static struct view_ops status_ops;
2261 static struct view_ops tree_ops;
2262 static struct view_ops branch_ops;
2264 #define VIEW_STR(type, name, env, ref, ops, map, git, refresh) \
2265 { type, name, #env, ref, ops, map, git, refresh }
2267 #define VIEW_(id, name, ops, git, refresh, ref) \
2268 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git, refresh)
2270 static struct view views[] = {
2271 VIEW_(MAIN, "main", &main_ops, TRUE, TRUE, ref_head),
2272 VIEW_(DIFF, "diff", &diff_ops, TRUE, FALSE, ref_commit),
2273 VIEW_(LOG, "log", &log_ops, TRUE, TRUE, ref_head),
2274 VIEW_(TREE, "tree", &tree_ops, TRUE, FALSE, ref_commit),
2275 VIEW_(BLOB, "blob", &blob_ops, TRUE, FALSE, ref_blob),
2276 VIEW_(BLAME, "blame", &blame_ops, TRUE, FALSE, ref_commit),
2277 VIEW_(BRANCH, "branch", &branch_ops, TRUE, TRUE, ref_head),
2278 VIEW_(HELP, "help", &help_ops, FALSE, FALSE, ""),
2279 VIEW_(PAGER, "pager", &pager_ops, FALSE, FALSE, "stdin"),
2280 VIEW_(STATUS, "status", &status_ops, TRUE, TRUE, ""),
2281 VIEW_(STAGE, "stage", &stage_ops, TRUE, TRUE, ""),
2282 };
2284 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2286 #define foreach_view(view, i) \
2287 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2289 #define view_is_displayed(view) \
2290 (view == display[0] || view == display[1])
2292 #define view_has_parent(view, child_type, parent_type) \
2293 (view->type == child_type && view->parent && view->parent->type == parent_type)
2295 static inline void
2296 set_view_attr(struct view *view, enum line_type type)
2297 {
2298 if (!view->curline->selected && view->curtype != type) {
2299 (void) wattrset(view->win, get_line_attr(type));
2300 wchgat(view->win, -1, 0, type, NULL);
2301 view->curtype = type;
2302 }
2303 }
2305 static int
2306 draw_chars(struct view *view, enum line_type type, const char *string,
2307 int max_len, bool use_tilde)
2308 {
2309 static char out_buffer[BUFSIZ * 2];
2310 int len = 0;
2311 int col = 0;
2312 int trimmed = FALSE;
2313 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2315 if (max_len <= 0)
2316 return 0;
2318 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2320 set_view_attr(view, type);
2321 if (len > 0) {
2322 if (opt_iconv_out != ICONV_NONE) {
2323 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2324 size_t inlen = len + 1;
2326 char *outbuf = out_buffer;
2327 size_t outlen = sizeof(out_buffer);
2329 size_t ret;
2331 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2332 if (ret != (size_t) -1) {
2333 string = out_buffer;
2334 len = sizeof(out_buffer) - outlen;
2335 }
2336 }
2338 waddnstr(view->win, string, len);
2339 }
2340 if (trimmed && use_tilde) {
2341 set_view_attr(view, LINE_DELIMITER);
2342 waddch(view->win, '~');
2343 col++;
2344 }
2346 return col;
2347 }
2349 static int
2350 draw_space(struct view *view, enum line_type type, int max, int spaces)
2351 {
2352 static char space[] = " ";
2353 int col = 0;
2355 spaces = MIN(max, spaces);
2357 while (spaces > 0) {
2358 int len = MIN(spaces, sizeof(space) - 1);
2360 col += draw_chars(view, type, space, len, FALSE);
2361 spaces -= len;
2362 }
2364 return col;
2365 }
2367 static bool
2368 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2369 {
2370 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2371 return view->width + view->yoffset <= view->col;
2372 }
2374 static bool
2375 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2376 {
2377 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2378 int max = view->width + view->yoffset - view->col;
2379 int i;
2381 if (max < size)
2382 size = max;
2384 set_view_attr(view, type);
2385 /* Using waddch() instead of waddnstr() ensures that
2386 * they'll be rendered correctly for the cursor line. */
2387 for (i = skip; i < size; i++)
2388 waddch(view->win, graphic[i]);
2390 view->col += size;
2391 if (size < max && skip <= size)
2392 waddch(view->win, ' ');
2393 view->col++;
2395 return view->width + view->yoffset <= view->col;
2396 }
2398 static bool
2399 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2400 {
2401 int max = MIN(view->width + view->yoffset - view->col, len);
2402 int col;
2404 if (text)
2405 col = draw_chars(view, type, text, max - 1, trim);
2406 else
2407 col = draw_space(view, type, max - 1, max - 1);
2409 view->col += col;
2410 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2411 return view->width + view->yoffset <= view->col;
2412 }
2414 static bool
2415 draw_date(struct view *view, struct time *time)
2416 {
2417 const char *date = mkdate(time, opt_date);
2418 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2420 return draw_field(view, LINE_DATE, date, cols, FALSE);
2421 }
2423 static bool
2424 draw_author(struct view *view, const char *author)
2425 {
2426 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2427 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2429 if (abbreviate && author)
2430 author = get_author_initials(author);
2432 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2433 }
2435 static bool
2436 draw_mode(struct view *view, mode_t mode)
2437 {
2438 const char *str;
2440 if (S_ISDIR(mode))
2441 str = "drwxr-xr-x";
2442 else if (S_ISLNK(mode))
2443 str = "lrwxrwxrwx";
2444 else if (S_ISGITLINK(mode))
2445 str = "m---------";
2446 else if (S_ISREG(mode) && mode & S_IXUSR)
2447 str = "-rwxr-xr-x";
2448 else if (S_ISREG(mode))
2449 str = "-rw-r--r--";
2450 else
2451 str = "----------";
2453 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2454 }
2456 static bool
2457 draw_lineno(struct view *view, unsigned int lineno)
2458 {
2459 char number[10];
2460 int digits3 = view->digits < 3 ? 3 : view->digits;
2461 int max = MIN(view->width + view->yoffset - view->col, digits3);
2462 char *text = NULL;
2463 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2465 lineno += view->offset + 1;
2466 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2467 static char fmt[] = "%1ld";
2469 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2470 if (string_format(number, fmt, lineno))
2471 text = number;
2472 }
2473 if (text)
2474 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2475 else
2476 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2477 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2478 }
2480 static bool
2481 draw_view_line(struct view *view, unsigned int lineno)
2482 {
2483 struct line *line;
2484 bool selected = (view->offset + lineno == view->lineno);
2486 assert(view_is_displayed(view));
2488 if (view->offset + lineno >= view->lines)
2489 return FALSE;
2491 line = &view->line[view->offset + lineno];
2493 wmove(view->win, lineno, 0);
2494 if (line->cleareol)
2495 wclrtoeol(view->win);
2496 view->col = 0;
2497 view->curline = line;
2498 view->curtype = LINE_NONE;
2499 line->selected = FALSE;
2500 line->dirty = line->cleareol = 0;
2502 if (selected) {
2503 set_view_attr(view, LINE_CURSOR);
2504 line->selected = TRUE;
2505 view->ops->select(view, line);
2506 }
2508 return view->ops->draw(view, line, lineno);
2509 }
2511 static void
2512 redraw_view_dirty(struct view *view)
2513 {
2514 bool dirty = FALSE;
2515 int lineno;
2517 for (lineno = 0; lineno < view->height; lineno++) {
2518 if (view->offset + lineno >= view->lines)
2519 break;
2520 if (!view->line[view->offset + lineno].dirty)
2521 continue;
2522 dirty = TRUE;
2523 if (!draw_view_line(view, lineno))
2524 break;
2525 }
2527 if (!dirty)
2528 return;
2529 wnoutrefresh(view->win);
2530 }
2532 static void
2533 redraw_view_from(struct view *view, int lineno)
2534 {
2535 assert(0 <= lineno && lineno < view->height);
2537 for (; lineno < view->height; lineno++) {
2538 if (!draw_view_line(view, lineno))
2539 break;
2540 }
2542 wnoutrefresh(view->win);
2543 }
2545 static void
2546 redraw_view(struct view *view)
2547 {
2548 werase(view->win);
2549 redraw_view_from(view, 0);
2550 }
2553 static void
2554 update_view_title(struct view *view)
2555 {
2556 char buf[SIZEOF_STR];
2557 char state[SIZEOF_STR];
2558 size_t bufpos = 0, statelen = 0;
2560 assert(view_is_displayed(view));
2562 if (view->type != VIEW_STATUS && view->lines) {
2563 unsigned int view_lines = view->offset + view->height;
2564 unsigned int lines = view->lines
2565 ? MIN(view_lines, view->lines) * 100 / view->lines
2566 : 0;
2568 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2569 view->ops->type,
2570 view->lineno + 1,
2571 view->lines,
2572 lines);
2574 }
2576 if (view->pipe) {
2577 time_t secs = time(NULL) - view->start_time;
2579 /* Three git seconds are a long time ... */
2580 if (secs > 2)
2581 string_format_from(state, &statelen, " loading %lds", secs);
2582 }
2584 string_format_from(buf, &bufpos, "[%s]", view->name);
2585 if (*view->ref && bufpos < view->width) {
2586 size_t refsize = strlen(view->ref);
2587 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2589 if (minsize < view->width)
2590 refsize = view->width - minsize + 7;
2591 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2592 }
2594 if (statelen && bufpos < view->width) {
2595 string_format_from(buf, &bufpos, "%s", state);
2596 }
2598 if (view == display[current_view])
2599 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2600 else
2601 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2603 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2604 wclrtoeol(view->title);
2605 wnoutrefresh(view->title);
2606 }
2608 static int
2609 apply_step(double step, int value)
2610 {
2611 if (step >= 1)
2612 return (int) step;
2613 value *= step + 0.01;
2614 return value ? value : 1;
2615 }
2617 static void
2618 resize_display(void)
2619 {
2620 int offset, i;
2621 struct view *base = display[0];
2622 struct view *view = display[1] ? display[1] : display[0];
2624 /* Setup window dimensions */
2626 getmaxyx(stdscr, base->height, base->width);
2628 /* Make room for the status window. */
2629 base->height -= 1;
2631 if (view != base) {
2632 /* Horizontal split. */
2633 view->width = base->width;
2634 view->height = apply_step(opt_scale_split_view, base->height);
2635 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2636 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2637 base->height -= view->height;
2639 /* Make room for the title bar. */
2640 view->height -= 1;
2641 }
2643 /* Make room for the title bar. */
2644 base->height -= 1;
2646 offset = 0;
2648 foreach_displayed_view (view, i) {
2649 if (!view->win) {
2650 view->win = newwin(view->height, 0, offset, 0);
2651 if (!view->win)
2652 die("Failed to create %s view", view->name);
2654 scrollok(view->win, FALSE);
2656 view->title = newwin(1, 0, offset + view->height, 0);
2657 if (!view->title)
2658 die("Failed to create title window");
2660 } else {
2661 wresize(view->win, view->height, view->width);
2662 mvwin(view->win, offset, 0);
2663 mvwin(view->title, offset + view->height, 0);
2664 }
2666 offset += view->height + 1;
2667 }
2668 }
2670 static void
2671 redraw_display(bool clear)
2672 {
2673 struct view *view;
2674 int i;
2676 foreach_displayed_view (view, i) {
2677 if (clear)
2678 wclear(view->win);
2679 redraw_view(view);
2680 update_view_title(view);
2681 }
2682 }
2684 static void
2685 toggle_enum_option_do(unsigned int *opt, const char *help,
2686 const struct enum_map *map, size_t size)
2687 {
2688 *opt = (*opt + 1) % size;
2689 redraw_display(FALSE);
2690 report("Displaying %s %s", enum_name(map[*opt]), help);
2691 }
2693 #define toggle_enum_option(opt, help, map) \
2694 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2696 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2697 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2699 static void
2700 toggle_view_option(bool *option, const char *help)
2701 {
2702 *option = !*option;
2703 redraw_display(FALSE);
2704 report("%sabling %s", *option ? "En" : "Dis", help);
2705 }
2707 static void
2708 open_option_menu(void)
2709 {
2710 const struct menu_item menu[] = {
2711 { '.', "line numbers", &opt_line_number },
2712 { 'D', "date display", &opt_date },
2713 { 'A', "author display", &opt_author },
2714 { 'g', "revision graph display", &opt_rev_graph },
2715 { 'F', "reference display", &opt_show_refs },
2716 { 0 }
2717 };
2718 int selected = 0;
2720 if (prompt_menu("Toggle option", menu, &selected)) {
2721 if (menu[selected].data == &opt_date)
2722 toggle_date();
2723 else if (menu[selected].data == &opt_author)
2724 toggle_author();
2725 else
2726 toggle_view_option(menu[selected].data, menu[selected].text);
2727 }
2728 }
2730 static void
2731 maximize_view(struct view *view)
2732 {
2733 memset(display, 0, sizeof(display));
2734 current_view = 0;
2735 display[current_view] = view;
2736 resize_display();
2737 redraw_display(FALSE);
2738 report("");
2739 }
2742 /*
2743 * Navigation
2744 */
2746 static bool
2747 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2748 {
2749 if (lineno >= view->lines)
2750 lineno = view->lines > 0 ? view->lines - 1 : 0;
2752 if (offset > lineno || offset + view->height <= lineno) {
2753 unsigned long half = view->height / 2;
2755 if (lineno > half)
2756 offset = lineno - half;
2757 else
2758 offset = 0;
2759 }
2761 if (offset != view->offset || lineno != view->lineno) {
2762 view->offset = offset;
2763 view->lineno = lineno;
2764 return TRUE;
2765 }
2767 return FALSE;
2768 }
2770 /* Scrolling backend */
2771 static void
2772 do_scroll_view(struct view *view, int lines)
2773 {
2774 bool redraw_current_line = FALSE;
2776 /* The rendering expects the new offset. */
2777 view->offset += lines;
2779 assert(0 <= view->offset && view->offset < view->lines);
2780 assert(lines);
2782 /* Move current line into the view. */
2783 if (view->lineno < view->offset) {
2784 view->lineno = view->offset;
2785 redraw_current_line = TRUE;
2786 } else if (view->lineno >= view->offset + view->height) {
2787 view->lineno = view->offset + view->height - 1;
2788 redraw_current_line = TRUE;
2789 }
2791 assert(view->offset <= view->lineno && view->lineno < view->lines);
2793 /* Redraw the whole screen if scrolling is pointless. */
2794 if (view->height < ABS(lines)) {
2795 redraw_view(view);
2797 } else {
2798 int line = lines > 0 ? view->height - lines : 0;
2799 int end = line + ABS(lines);
2801 scrollok(view->win, TRUE);
2802 wscrl(view->win, lines);
2803 scrollok(view->win, FALSE);
2805 while (line < end && draw_view_line(view, line))
2806 line++;
2808 if (redraw_current_line)
2809 draw_view_line(view, view->lineno - view->offset);
2810 wnoutrefresh(view->win);
2811 }
2813 view->has_scrolled = TRUE;
2814 report("");
2815 }
2817 /* Scroll frontend */
2818 static void
2819 scroll_view(struct view *view, enum request request)
2820 {
2821 int lines = 1;
2823 assert(view_is_displayed(view));
2825 switch (request) {
2826 case REQ_SCROLL_LEFT:
2827 if (view->yoffset == 0) {
2828 report("Cannot scroll beyond the first column");
2829 return;
2830 }
2831 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2832 view->yoffset = 0;
2833 else
2834 view->yoffset -= apply_step(opt_hscroll, view->width);
2835 redraw_view_from(view, 0);
2836 report("");
2837 return;
2838 case REQ_SCROLL_RIGHT:
2839 view->yoffset += apply_step(opt_hscroll, view->width);
2840 redraw_view(view);
2841 report("");
2842 return;
2843 case REQ_SCROLL_PAGE_DOWN:
2844 lines = view->height;
2845 case REQ_SCROLL_LINE_DOWN:
2846 if (view->offset + lines > view->lines)
2847 lines = view->lines - view->offset;
2849 if (lines == 0 || view->offset + view->height >= view->lines) {
2850 report("Cannot scroll beyond the last line");
2851 return;
2852 }
2853 break;
2855 case REQ_SCROLL_PAGE_UP:
2856 lines = view->height;
2857 case REQ_SCROLL_LINE_UP:
2858 if (lines > view->offset)
2859 lines = view->offset;
2861 if (lines == 0) {
2862 report("Cannot scroll beyond the first line");
2863 return;
2864 }
2866 lines = -lines;
2867 break;
2869 default:
2870 die("request %d not handled in switch", request);
2871 }
2873 do_scroll_view(view, lines);
2874 }
2876 /* Cursor moving */
2877 static void
2878 move_view(struct view *view, enum request request)
2879 {
2880 int scroll_steps = 0;
2881 int steps;
2883 switch (request) {
2884 case REQ_MOVE_FIRST_LINE:
2885 steps = -view->lineno;
2886 break;
2888 case REQ_MOVE_LAST_LINE:
2889 steps = view->lines - view->lineno - 1;
2890 break;
2892 case REQ_MOVE_PAGE_UP:
2893 steps = view->height > view->lineno
2894 ? -view->lineno : -view->height;
2895 break;
2897 case REQ_MOVE_PAGE_DOWN:
2898 steps = view->lineno + view->height >= view->lines
2899 ? view->lines - view->lineno - 1 : view->height;
2900 break;
2902 case REQ_MOVE_UP:
2903 steps = -1;
2904 break;
2906 case REQ_MOVE_DOWN:
2907 steps = 1;
2908 break;
2910 default:
2911 die("request %d not handled in switch", request);
2912 }
2914 if (steps <= 0 && view->lineno == 0) {
2915 report("Cannot move beyond the first line");
2916 return;
2918 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2919 report("Cannot move beyond the last line");
2920 return;
2921 }
2923 /* Move the current line */
2924 view->lineno += steps;
2925 assert(0 <= view->lineno && view->lineno < view->lines);
2927 /* Check whether the view needs to be scrolled */
2928 if (view->lineno < view->offset ||
2929 view->lineno >= view->offset + view->height) {
2930 scroll_steps = steps;
2931 if (steps < 0 && -steps > view->offset) {
2932 scroll_steps = -view->offset;
2934 } else if (steps > 0) {
2935 if (view->lineno == view->lines - 1 &&
2936 view->lines > view->height) {
2937 scroll_steps = view->lines - view->offset - 1;
2938 if (scroll_steps >= view->height)
2939 scroll_steps -= view->height - 1;
2940 }
2941 }
2942 }
2944 if (!view_is_displayed(view)) {
2945 view->offset += scroll_steps;
2946 assert(0 <= view->offset && view->offset < view->lines);
2947 view->ops->select(view, &view->line[view->lineno]);
2948 return;
2949 }
2951 /* Repaint the old "current" line if we be scrolling */
2952 if (ABS(steps) < view->height)
2953 draw_view_line(view, view->lineno - steps - view->offset);
2955 if (scroll_steps) {
2956 do_scroll_view(view, scroll_steps);
2957 return;
2958 }
2960 /* Draw the current line */
2961 draw_view_line(view, view->lineno - view->offset);
2963 wnoutrefresh(view->win);
2964 report("");
2965 }
2968 /*
2969 * Searching
2970 */
2972 static void search_view(struct view *view, enum request request);
2974 static bool
2975 grep_text(struct view *view, const char *text[])
2976 {
2977 regmatch_t pmatch;
2978 size_t i;
2980 for (i = 0; text[i]; i++)
2981 if (*text[i] &&
2982 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2983 return TRUE;
2984 return FALSE;
2985 }
2987 static void
2988 select_view_line(struct view *view, unsigned long lineno)
2989 {
2990 unsigned long old_lineno = view->lineno;
2991 unsigned long old_offset = view->offset;
2993 if (goto_view_line(view, view->offset, lineno)) {
2994 if (view_is_displayed(view)) {
2995 if (old_offset != view->offset) {
2996 redraw_view(view);
2997 } else {
2998 draw_view_line(view, old_lineno - view->offset);
2999 draw_view_line(view, view->lineno - view->offset);
3000 wnoutrefresh(view->win);
3001 }
3002 } else {
3003 view->ops->select(view, &view->line[view->lineno]);
3004 }
3005 }
3006 }
3008 static void
3009 find_next(struct view *view, enum request request)
3010 {
3011 unsigned long lineno = view->lineno;
3012 int direction;
3014 if (!*view->grep) {
3015 if (!*opt_search)
3016 report("No previous search");
3017 else
3018 search_view(view, request);
3019 return;
3020 }
3022 switch (request) {
3023 case REQ_SEARCH:
3024 case REQ_FIND_NEXT:
3025 direction = 1;
3026 break;
3028 case REQ_SEARCH_BACK:
3029 case REQ_FIND_PREV:
3030 direction = -1;
3031 break;
3033 default:
3034 return;
3035 }
3037 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3038 lineno += direction;
3040 /* Note, lineno is unsigned long so will wrap around in which case it
3041 * will become bigger than view->lines. */
3042 for (; lineno < view->lines; lineno += direction) {
3043 if (view->ops->grep(view, &view->line[lineno])) {
3044 select_view_line(view, lineno);
3045 report("Line %ld matches '%s'", lineno + 1, view->grep);
3046 return;
3047 }
3048 }
3050 report("No match found for '%s'", view->grep);
3051 }
3053 static void
3054 search_view(struct view *view, enum request request)
3055 {
3056 int regex_err;
3058 if (view->regex) {
3059 regfree(view->regex);
3060 *view->grep = 0;
3061 } else {
3062 view->regex = calloc(1, sizeof(*view->regex));
3063 if (!view->regex)
3064 return;
3065 }
3067 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3068 if (regex_err != 0) {
3069 char buf[SIZEOF_STR] = "unknown error";
3071 regerror(regex_err, view->regex, buf, sizeof(buf));
3072 report("Search failed: %s", buf);
3073 return;
3074 }
3076 string_copy(view->grep, opt_search);
3078 find_next(view, request);
3079 }
3081 /*
3082 * Incremental updating
3083 */
3085 static void
3086 reset_view(struct view *view)
3087 {
3088 int i;
3090 for (i = 0; i < view->lines; i++)
3091 free(view->line[i].data);
3092 free(view->line);
3094 view->p_offset = view->offset;
3095 view->p_yoffset = view->yoffset;
3096 view->p_lineno = view->lineno;
3098 view->line = NULL;
3099 view->offset = 0;
3100 view->yoffset = 0;
3101 view->lines = 0;
3102 view->lineno = 0;
3103 view->vid[0] = 0;
3104 view->update_secs = 0;
3105 }
3107 static void
3108 free_argv(const char *argv[])
3109 {
3110 int argc;
3112 for (argc = 0; argv[argc]; argc++)
3113 free((void *) argv[argc]);
3114 }
3116 static const char *
3117 format_arg(const char *name)
3118 {
3119 static struct {
3120 const char *name;
3121 size_t namelen;
3122 const char *value;
3123 const char *value_if_empty;
3124 } vars[] = {
3125 #define FORMAT_VAR(name, value, value_if_empty) \
3126 { name, STRING_SIZE(name), value, value_if_empty }
3127 FORMAT_VAR("%(directory)", opt_path, ""),
3128 FORMAT_VAR("%(file)", opt_file, ""),
3129 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3130 FORMAT_VAR("%(head)", ref_head, ""),
3131 FORMAT_VAR("%(commit)", ref_commit, ""),
3132 FORMAT_VAR("%(blob)", ref_blob, ""),
3133 FORMAT_VAR("%(branch)", ref_branch, ""),
3134 };
3135 int i;
3137 for (i = 0; i < ARRAY_SIZE(vars); i++)
3138 if (!strncmp(name, vars[i].name, vars[i].namelen))
3139 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3141 report("Unknown replacement: `%s`", name);
3142 return NULL;
3143 }
3145 static bool
3146 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3147 {
3148 char buf[SIZEOF_STR];
3149 int argc;
3150 bool noreplace = flags == FORMAT_NONE;
3152 free_argv(dst_argv);
3154 for (argc = 0; src_argv[argc]; argc++) {
3155 const char *arg = src_argv[argc];
3156 size_t bufpos = 0;
3158 while (arg) {
3159 char *next = strstr(arg, "%(");
3160 int len = next - arg;
3161 const char *value;
3163 if (!next || noreplace) {
3164 len = strlen(arg);
3165 value = "";
3167 } else {
3168 value = format_arg(next);
3170 if (!value) {
3171 return FALSE;
3172 }
3173 }
3175 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3176 return FALSE;
3178 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3179 }
3181 dst_argv[argc] = strdup(buf);
3182 if (!dst_argv[argc])
3183 break;
3184 }
3186 dst_argv[argc] = NULL;
3188 return src_argv[argc] == NULL;
3189 }
3191 static bool
3192 restore_view_position(struct view *view)
3193 {
3194 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3195 return FALSE;
3197 /* Changing the view position cancels the restoring. */
3198 /* FIXME: Changing back to the first line is not detected. */
3199 if (view->offset != 0 || view->lineno != 0) {
3200 view->p_restore = FALSE;
3201 return FALSE;
3202 }
3204 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3205 view_is_displayed(view))
3206 werase(view->win);
3208 view->yoffset = view->p_yoffset;
3209 view->p_restore = FALSE;
3211 return TRUE;
3212 }
3214 static void
3215 end_update(struct view *view, bool force)
3216 {
3217 if (!view->pipe)
3218 return;
3219 while (!view->ops->read(view, NULL))
3220 if (!force)
3221 return;
3222 if (force)
3223 io_kill(view->pipe);
3224 io_done(view->pipe);
3225 view->pipe = NULL;
3226 }
3228 static void
3229 setup_update(struct view *view, const char *vid)
3230 {
3231 reset_view(view);
3232 string_copy_rev(view->vid, vid);
3233 view->pipe = &view->io;
3234 view->start_time = time(NULL);
3235 }
3237 static bool
3238 prepare_update(struct view *view, const char *argv[], const char *dir)
3239 {
3240 if (view->pipe)
3241 end_update(view, TRUE);
3242 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3243 }
3245 static bool
3246 prepare_update_file(struct view *view, const char *name)
3247 {
3248 if (view->pipe)
3249 end_update(view, TRUE);
3250 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3251 }
3253 static bool
3254 begin_update(struct view *view, bool refresh)
3255 {
3256 if (view->pipe)
3257 end_update(view, TRUE);
3259 if (!refresh) {
3260 if (view->ops->prepare) {
3261 if (!view->ops->prepare(view))
3262 return FALSE;
3263 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3264 return FALSE;
3265 }
3267 /* Put the current ref_* value to the view title ref
3268 * member. This is needed by the blob view. Most other
3269 * views sets it automatically after loading because the
3270 * first line is a commit line. */
3271 string_copy_rev(view->ref, view->id);
3272 }
3274 if (!io_start(&view->io))
3275 return FALSE;
3277 setup_update(view, view->id);
3279 return TRUE;
3280 }
3282 static bool
3283 update_view(struct view *view)
3284 {
3285 char out_buffer[BUFSIZ * 2];
3286 char *line;
3287 /* Clear the view and redraw everything since the tree sorting
3288 * might have rearranged things. */
3289 bool redraw = view->lines == 0;
3290 bool can_read = TRUE;
3292 if (!view->pipe)
3293 return TRUE;
3295 if (!io_can_read(view->pipe)) {
3296 if (view->lines == 0 && view_is_displayed(view)) {
3297 time_t secs = time(NULL) - view->start_time;
3299 if (secs > 1 && secs > view->update_secs) {
3300 if (view->update_secs == 0)
3301 redraw_view(view);
3302 update_view_title(view);
3303 view->update_secs = secs;
3304 }
3305 }
3306 return TRUE;
3307 }
3309 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3310 if (opt_iconv_in != ICONV_NONE) {
3311 ICONV_CONST char *inbuf = line;
3312 size_t inlen = strlen(line) + 1;
3314 char *outbuf = out_buffer;
3315 size_t outlen = sizeof(out_buffer);
3317 size_t ret;
3319 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3320 if (ret != (size_t) -1)
3321 line = out_buffer;
3322 }
3324 if (!view->ops->read(view, line)) {
3325 report("Allocation failure");
3326 end_update(view, TRUE);
3327 return FALSE;
3328 }
3329 }
3331 {
3332 unsigned long lines = view->lines;
3333 int digits;
3335 for (digits = 0; lines; digits++)
3336 lines /= 10;
3338 /* Keep the displayed view in sync with line number scaling. */
3339 if (digits != view->digits) {
3340 view->digits = digits;
3341 if (opt_line_number || view->type == VIEW_BLAME)
3342 redraw = TRUE;
3343 }
3344 }
3346 if (io_error(view->pipe)) {
3347 report("Failed to read: %s", io_strerror(view->pipe));
3348 end_update(view, TRUE);
3350 } else if (io_eof(view->pipe)) {
3351 report("");
3352 end_update(view, FALSE);
3353 }
3355 if (restore_view_position(view))
3356 redraw = TRUE;
3358 if (!view_is_displayed(view))
3359 return TRUE;
3361 if (redraw)
3362 redraw_view_from(view, 0);
3363 else
3364 redraw_view_dirty(view);
3366 /* Update the title _after_ the redraw so that if the redraw picks up a
3367 * commit reference in view->ref it'll be available here. */
3368 update_view_title(view);
3369 return TRUE;
3370 }
3372 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3374 static struct line *
3375 add_line_data(struct view *view, void *data, enum line_type type)
3376 {
3377 struct line *line;
3379 if (!realloc_lines(&view->line, view->lines, 1))
3380 return NULL;
3382 line = &view->line[view->lines++];
3383 memset(line, 0, sizeof(*line));
3384 line->type = type;
3385 line->data = data;
3386 line->dirty = 1;
3388 return line;
3389 }
3391 static struct line *
3392 add_line_text(struct view *view, const char *text, enum line_type type)
3393 {
3394 char *data = text ? strdup(text) : NULL;
3396 return data ? add_line_data(view, data, type) : NULL;
3397 }
3399 static struct line *
3400 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3401 {
3402 char buf[SIZEOF_STR];
3403 va_list args;
3405 va_start(args, fmt);
3406 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3407 buf[0] = 0;
3408 va_end(args);
3410 return buf[0] ? add_line_text(view, buf, type) : NULL;
3411 }
3413 /*
3414 * View opening
3415 */
3417 enum open_flags {
3418 OPEN_DEFAULT = 0, /* Use default view switching. */
3419 OPEN_SPLIT = 1, /* Split current view. */
3420 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3421 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3422 OPEN_PREPARED = 32, /* Open already prepared command. */
3423 };
3425 static void
3426 open_view(struct view *prev, enum request request, enum open_flags flags)
3427 {
3428 bool split = !!(flags & OPEN_SPLIT);
3429 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3430 bool nomaximize = !!(flags & OPEN_REFRESH);
3431 struct view *view = VIEW(request);
3432 int nviews = displayed_views();
3433 struct view *base_view = display[0];
3435 if (view == prev && nviews == 1 && !reload) {
3436 report("Already in %s view", view->name);
3437 return;
3438 }
3440 if (view->git_dir && !opt_git_dir[0]) {
3441 report("The %s view is disabled in pager view", view->name);
3442 return;
3443 }
3445 if (split) {
3446 display[1] = view;
3447 current_view = 1;
3448 } else if (!nomaximize) {
3449 /* Maximize the current view. */
3450 memset(display, 0, sizeof(display));
3451 current_view = 0;
3452 display[current_view] = view;
3453 }
3455 /* No parent signals that this is the first loaded view. */
3456 if (prev && view != prev) {
3457 view->parent = prev;
3458 }
3460 /* Resize the view when switching between split- and full-screen,
3461 * or when switching between two different full-screen views. */
3462 if (nviews != displayed_views() ||
3463 (nviews == 1 && base_view != display[0]))
3464 resize_display();
3466 if (view->ops->open) {
3467 if (view->pipe)
3468 end_update(view, TRUE);
3469 if (!view->ops->open(view)) {
3470 report("Failed to load %s view", view->name);
3471 return;
3472 }
3473 restore_view_position(view);
3475 } else if ((reload || strcmp(view->vid, view->id)) &&
3476 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3477 report("Failed to load %s view", view->name);
3478 return;
3479 }
3481 if (split && prev->lineno - prev->offset >= prev->height) {
3482 /* Take the title line into account. */
3483 int lines = prev->lineno - prev->offset - prev->height + 1;
3485 /* Scroll the view that was split if the current line is
3486 * outside the new limited view. */
3487 do_scroll_view(prev, lines);
3488 }
3490 if (prev && view != prev && split && view_is_displayed(prev)) {
3491 /* "Blur" the previous view. */
3492 update_view_title(prev);
3493 }
3495 if (view->pipe && view->lines == 0) {
3496 /* Clear the old view and let the incremental updating refill
3497 * the screen. */
3498 werase(view->win);
3499 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3500 report("");
3501 } else if (view_is_displayed(view)) {
3502 redraw_view(view);
3503 report("");
3504 }
3505 }
3507 static void
3508 open_external_viewer(const char *argv[], const char *dir)
3509 {
3510 def_prog_mode(); /* save current tty modes */
3511 endwin(); /* restore original tty modes */
3512 io_run_fg(argv, dir);
3513 fprintf(stderr, "Press Enter to continue");
3514 getc(opt_tty);
3515 reset_prog_mode();
3516 redraw_display(TRUE);
3517 }
3519 static void
3520 open_mergetool(const char *file)
3521 {
3522 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3524 open_external_viewer(mergetool_argv, opt_cdup);
3525 }
3527 static void
3528 open_editor(const char *file)
3529 {
3530 const char *editor_argv[] = { "vi", file, NULL };
3531 const char *editor;
3533 editor = getenv("GIT_EDITOR");
3534 if (!editor && *opt_editor)
3535 editor = opt_editor;
3536 if (!editor)
3537 editor = getenv("VISUAL");
3538 if (!editor)
3539 editor = getenv("EDITOR");
3540 if (!editor)
3541 editor = "vi";
3543 editor_argv[0] = editor;
3544 open_external_viewer(editor_argv, opt_cdup);
3545 }
3547 static void
3548 open_run_request(enum request request)
3549 {
3550 struct run_request *req = get_run_request(request);
3551 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3553 if (!req) {
3554 report("Unknown run request");
3555 return;
3556 }
3558 if (format_argv(argv, req->argv, FORMAT_ALL))
3559 open_external_viewer(argv, NULL);
3560 free_argv(argv);
3561 }
3563 /*
3564 * User request switch noodle
3565 */
3567 static int
3568 view_driver(struct view *view, enum request request)
3569 {
3570 int i;
3572 if (request == REQ_NONE)
3573 return TRUE;
3575 if (request > REQ_NONE) {
3576 open_run_request(request);
3577 /* FIXME: When all views can refresh always do this. */
3578 if (view->refresh)
3579 request = REQ_REFRESH;
3580 else
3581 return TRUE;
3582 }
3584 if (view && view->lines) {
3585 request = view->ops->request(view, request, &view->line[view->lineno]);
3586 if (request == REQ_NONE)
3587 return TRUE;
3588 }
3590 switch (request) {
3591 case REQ_MOVE_UP:
3592 case REQ_MOVE_DOWN:
3593 case REQ_MOVE_PAGE_UP:
3594 case REQ_MOVE_PAGE_DOWN:
3595 case REQ_MOVE_FIRST_LINE:
3596 case REQ_MOVE_LAST_LINE:
3597 move_view(view, request);
3598 break;
3600 case REQ_SCROLL_LEFT:
3601 case REQ_SCROLL_RIGHT:
3602 case REQ_SCROLL_LINE_DOWN:
3603 case REQ_SCROLL_LINE_UP:
3604 case REQ_SCROLL_PAGE_DOWN:
3605 case REQ_SCROLL_PAGE_UP:
3606 scroll_view(view, request);
3607 break;
3609 case REQ_VIEW_BLAME:
3610 if (!opt_file[0]) {
3611 report("No file chosen, press %s to open tree view",
3612 get_key(view->keymap, REQ_VIEW_TREE));
3613 break;
3614 }
3615 open_view(view, request, OPEN_DEFAULT);
3616 break;
3618 case REQ_VIEW_BLOB:
3619 if (!ref_blob[0]) {
3620 report("No file chosen, press %s to open tree view",
3621 get_key(view->keymap, REQ_VIEW_TREE));
3622 break;
3623 }
3624 open_view(view, request, OPEN_DEFAULT);
3625 break;
3627 case REQ_VIEW_PAGER:
3628 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3629 report("No pager content, press %s to run command from prompt",
3630 get_key(view->keymap, REQ_PROMPT));
3631 break;
3632 }
3633 open_view(view, request, OPEN_DEFAULT);
3634 break;
3636 case REQ_VIEW_STAGE:
3637 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3638 report("No stage content, press %s to open the status view and choose file",
3639 get_key(view->keymap, REQ_VIEW_STATUS));
3640 break;
3641 }
3642 open_view(view, request, OPEN_DEFAULT);
3643 break;
3645 case REQ_VIEW_STATUS:
3646 if (opt_is_inside_work_tree == FALSE) {
3647 report("The status view requires a working tree");
3648 break;
3649 }
3650 open_view(view, request, OPEN_DEFAULT);
3651 break;
3653 case REQ_VIEW_MAIN:
3654 case REQ_VIEW_DIFF:
3655 case REQ_VIEW_LOG:
3656 case REQ_VIEW_TREE:
3657 case REQ_VIEW_HELP:
3658 case REQ_VIEW_BRANCH:
3659 open_view(view, request, OPEN_DEFAULT);
3660 break;
3662 case REQ_NEXT:
3663 case REQ_PREVIOUS:
3664 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3666 if (view_has_parent(view, VIEW_DIFF, VIEW_MAIN) ||
3667 view_has_parent(view, VIEW_DIFF, VIEW_BLAME) ||
3668 view_has_parent(view, VIEW_STAGE, VIEW_STATUS) ||
3669 view_has_parent(view, VIEW_BLOB, VIEW_TREE) ||
3670 view_has_parent(view, VIEW_MAIN, VIEW_BRANCH)) {
3671 int line;
3673 view = view->parent;
3674 line = view->lineno;
3675 move_view(view, request);
3676 if (view_is_displayed(view))
3677 update_view_title(view);
3678 if (line != view->lineno)
3679 view->ops->request(view, REQ_ENTER,
3680 &view->line[view->lineno]);
3682 } else {
3683 move_view(view, request);
3684 }
3685 break;
3687 case REQ_VIEW_NEXT:
3688 {
3689 int nviews = displayed_views();
3690 int next_view = (current_view + 1) % nviews;
3692 if (next_view == current_view) {
3693 report("Only one view is displayed");
3694 break;
3695 }
3697 current_view = next_view;
3698 /* Blur out the title of the previous view. */
3699 update_view_title(view);
3700 report("");
3701 break;
3702 }
3703 case REQ_REFRESH:
3704 report("Refreshing is not yet supported for the %s view", view->name);
3705 break;
3707 case REQ_MAXIMIZE:
3708 if (displayed_views() == 2)
3709 maximize_view(view);
3710 break;
3712 case REQ_OPTIONS:
3713 open_option_menu();
3714 break;
3716 case REQ_TOGGLE_LINENO:
3717 toggle_view_option(&opt_line_number, "line numbers");
3718 break;
3720 case REQ_TOGGLE_DATE:
3721 toggle_date();
3722 break;
3724 case REQ_TOGGLE_AUTHOR:
3725 toggle_author();
3726 break;
3728 case REQ_TOGGLE_REV_GRAPH:
3729 toggle_view_option(&opt_rev_graph, "revision graph display");
3730 break;
3732 case REQ_TOGGLE_REFS:
3733 toggle_view_option(&opt_show_refs, "reference display");
3734 break;
3736 case REQ_TOGGLE_SORT_FIELD:
3737 case REQ_TOGGLE_SORT_ORDER:
3738 report("Sorting is not yet supported for the %s view", view->name);
3739 break;
3741 case REQ_SEARCH:
3742 case REQ_SEARCH_BACK:
3743 search_view(view, request);
3744 break;
3746 case REQ_FIND_NEXT:
3747 case REQ_FIND_PREV:
3748 find_next(view, request);
3749 break;
3751 case REQ_STOP_LOADING:
3752 foreach_view(view, i) {
3753 if (view->pipe)
3754 report("Stopped loading the %s view", view->name),
3755 end_update(view, TRUE);
3756 }
3757 break;
3759 case REQ_SHOW_VERSION:
3760 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3761 return TRUE;
3763 case REQ_SCREEN_REDRAW:
3764 redraw_display(TRUE);
3765 break;
3767 case REQ_EDIT:
3768 report("Nothing to edit");
3769 break;
3771 case REQ_ENTER:
3772 report("Nothing to enter");
3773 break;
3775 case REQ_VIEW_CLOSE:
3776 /* XXX: Mark closed views by letting view->parent point to the
3777 * view itself. Parents to closed view should never be
3778 * followed. */
3779 if (view->parent &&
3780 view->parent->parent != view->parent) {
3781 maximize_view(view->parent);
3782 view->parent = view;
3783 break;
3784 }
3785 /* Fall-through */
3786 case REQ_QUIT:
3787 return FALSE;
3789 default:
3790 report("Unknown key, press %s for help",
3791 get_key(view->keymap, REQ_VIEW_HELP));
3792 return TRUE;
3793 }
3795 return TRUE;
3796 }
3799 /*
3800 * View backend utilities
3801 */
3803 enum sort_field {
3804 ORDERBY_NAME,
3805 ORDERBY_DATE,
3806 ORDERBY_AUTHOR,
3807 };
3809 struct sort_state {
3810 const enum sort_field *fields;
3811 size_t size, current;
3812 bool reverse;
3813 };
3815 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3816 #define get_sort_field(state) ((state).fields[(state).current])
3817 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3819 static void
3820 sort_view(struct view *view, enum request request, struct sort_state *state,
3821 int (*compare)(const void *, const void *))
3822 {
3823 switch (request) {
3824 case REQ_TOGGLE_SORT_FIELD:
3825 state->current = (state->current + 1) % state->size;
3826 break;
3828 case REQ_TOGGLE_SORT_ORDER:
3829 state->reverse = !state->reverse;
3830 break;
3831 default:
3832 die("Not a sort request");
3833 }
3835 qsort(view->line, view->lines, sizeof(*view->line), compare);
3836 redraw_view(view);
3837 }
3839 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3841 /* Small author cache to reduce memory consumption. It uses binary
3842 * search to lookup or find place to position new entries. No entries
3843 * are ever freed. */
3844 static const char *
3845 get_author(const char *name)
3846 {
3847 static const char **authors;
3848 static size_t authors_size;
3849 int from = 0, to = authors_size - 1;
3851 while (from <= to) {
3852 size_t pos = (to + from) / 2;
3853 int cmp = strcmp(name, authors[pos]);
3855 if (!cmp)
3856 return authors[pos];
3858 if (cmp < 0)
3859 to = pos - 1;
3860 else
3861 from = pos + 1;
3862 }
3864 if (!realloc_authors(&authors, authors_size, 1))
3865 return NULL;
3866 name = strdup(name);
3867 if (!name)
3868 return NULL;
3870 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3871 authors[from] = name;
3872 authors_size++;
3874 return name;
3875 }
3877 static void
3878 parse_timesec(struct time *time, const char *sec)
3879 {
3880 time->sec = (time_t) atol(sec);
3881 }
3883 static void
3884 parse_timezone(struct time *time, const char *zone)
3885 {
3886 long tz;
3888 tz = ('0' - zone[1]) * 60 * 60 * 10;
3889 tz += ('0' - zone[2]) * 60 * 60;
3890 tz += ('0' - zone[3]) * 60;
3891 tz += ('0' - zone[4]);
3893 if (zone[0] == '-')
3894 tz = -tz;
3896 time->tz = tz;
3897 time->sec -= tz;
3898 }
3900 /* Parse author lines where the name may be empty:
3901 * author <email@address.tld> 1138474660 +0100
3902 */
3903 static void
3904 parse_author_line(char *ident, const char **author, struct time *time)
3905 {
3906 char *nameend = strchr(ident, '<');
3907 char *emailend = strchr(ident, '>');
3909 if (nameend && emailend)
3910 *nameend = *emailend = 0;
3911 ident = chomp_string(ident);
3912 if (!*ident) {
3913 if (nameend)
3914 ident = chomp_string(nameend + 1);
3915 if (!*ident)
3916 ident = "Unknown";
3917 }
3919 *author = get_author(ident);
3921 /* Parse epoch and timezone */
3922 if (emailend && emailend[1] == ' ') {
3923 char *secs = emailend + 2;
3924 char *zone = strchr(secs, ' ');
3926 parse_timesec(time, secs);
3928 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3929 parse_timezone(time, zone + 1);
3930 }
3931 }
3933 static bool
3934 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3935 {
3936 char rev[SIZEOF_REV];
3937 const char *revlist_argv[] = {
3938 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3939 };
3940 struct menu_item *items;
3941 char text[SIZEOF_STR];
3942 bool ok = TRUE;
3943 int i;
3945 items = calloc(*parents + 1, sizeof(*items));
3946 if (!items)
3947 return FALSE;
3949 for (i = 0; i < *parents; i++) {
3950 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3951 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3952 !(items[i].text = strdup(text))) {
3953 ok = FALSE;
3954 break;
3955 }
3956 }
3958 if (ok) {
3959 *parents = 0;
3960 ok = prompt_menu("Select parent", items, parents);
3961 }
3962 for (i = 0; items[i].text; i++)
3963 free((char *) items[i].text);
3964 free(items);
3965 return ok;
3966 }
3968 static bool
3969 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3970 {
3971 char buf[SIZEOF_STR * 4];
3972 const char *revlist_argv[] = {
3973 "git", "log", "--no-color", "-1",
3974 "--pretty=format:%P", id, "--", path, NULL
3975 };
3976 int parents;
3978 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3979 (parents = strlen(buf) / 40) < 0) {
3980 report("Failed to get parent information");
3981 return FALSE;
3983 } else if (parents == 0) {
3984 if (path)
3985 report("Path '%s' does not exist in the parent", path);
3986 else
3987 report("The selected commit has no parents");
3988 return FALSE;
3989 }
3991 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3992 return FALSE;
3994 string_copy_rev(rev, &buf[41 * parents]);
3995 return TRUE;
3996 }
3998 /*
3999 * Pager backend
4000 */
4002 static bool
4003 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4004 {
4005 char text[SIZEOF_STR];
4007 if (opt_line_number && draw_lineno(view, lineno))
4008 return TRUE;
4010 string_expand(text, sizeof(text), line->data, opt_tab_size);
4011 draw_text(view, line->type, text, TRUE);
4012 return TRUE;
4013 }
4015 static bool
4016 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4017 {
4018 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4019 char ref[SIZEOF_STR];
4021 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4022 return TRUE;
4024 /* This is the only fatal call, since it can "corrupt" the buffer. */
4025 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4026 return FALSE;
4028 return TRUE;
4029 }
4031 static void
4032 add_pager_refs(struct view *view, struct line *line)
4033 {
4034 char buf[SIZEOF_STR];
4035 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4036 struct ref_list *list;
4037 size_t bufpos = 0, i;
4038 const char *sep = "Refs: ";
4039 bool is_tag = FALSE;
4041 assert(line->type == LINE_COMMIT);
4043 list = get_ref_list(commit_id);
4044 if (!list) {
4045 if (view->type == VIEW_DIFF)
4046 goto try_add_describe_ref;
4047 return;
4048 }
4050 for (i = 0; i < list->size; i++) {
4051 struct ref *ref = list->refs[i];
4052 const char *fmt = ref->tag ? "%s[%s]" :
4053 ref->remote ? "%s<%s>" : "%s%s";
4055 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4056 return;
4057 sep = ", ";
4058 if (ref->tag)
4059 is_tag = TRUE;
4060 }
4062 if (!is_tag && view->type == VIEW_DIFF) {
4063 try_add_describe_ref:
4064 /* Add <tag>-g<commit_id> "fake" reference. */
4065 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4066 return;
4067 }
4069 if (bufpos == 0)
4070 return;
4072 add_line_text(view, buf, LINE_PP_REFS);
4073 }
4075 static bool
4076 pager_read(struct view *view, char *data)
4077 {
4078 struct line *line;
4080 if (!data)
4081 return TRUE;
4083 line = add_line_text(view, data, get_line_type(data));
4084 if (!line)
4085 return FALSE;
4087 if (line->type == LINE_COMMIT &&
4088 (view->type == VIEW_DIFF ||
4089 view->type == VIEW_LOG))
4090 add_pager_refs(view, line);
4092 return TRUE;
4093 }
4095 static enum request
4096 pager_request(struct view *view, enum request request, struct line *line)
4097 {
4098 int split = 0;
4100 if (request != REQ_ENTER)
4101 return request;
4103 if (line->type == LINE_COMMIT &&
4104 (view->type == VIEW_LOG ||
4105 view->type == VIEW_PAGER)) {
4106 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4107 split = 1;
4108 }
4110 /* Always scroll the view even if it was split. That way
4111 * you can use Enter to scroll through the log view and
4112 * split open each commit diff. */
4113 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4115 /* FIXME: A minor workaround. Scrolling the view will call report("")
4116 * but if we are scrolling a non-current view this won't properly
4117 * update the view title. */
4118 if (split)
4119 update_view_title(view);
4121 return REQ_NONE;
4122 }
4124 static bool
4125 pager_grep(struct view *view, struct line *line)
4126 {
4127 const char *text[] = { line->data, NULL };
4129 return grep_text(view, text);
4130 }
4132 static void
4133 pager_select(struct view *view, struct line *line)
4134 {
4135 if (line->type == LINE_COMMIT) {
4136 char *text = (char *)line->data + STRING_SIZE("commit ");
4138 if (view->type != VIEW_PAGER)
4139 string_copy_rev(view->ref, text);
4140 string_copy_rev(ref_commit, text);
4141 }
4142 }
4144 static struct view_ops pager_ops = {
4145 "line",
4146 NULL,
4147 NULL,
4148 pager_read,
4149 pager_draw,
4150 pager_request,
4151 pager_grep,
4152 pager_select,
4153 };
4155 static const char *log_argv[SIZEOF_ARG] = {
4156 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4157 };
4159 static enum request
4160 log_request(struct view *view, enum request request, struct line *line)
4161 {
4162 switch (request) {
4163 case REQ_REFRESH:
4164 load_refs();
4165 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4166 return REQ_NONE;
4167 default:
4168 return pager_request(view, request, line);
4169 }
4170 }
4172 static struct view_ops log_ops = {
4173 "line",
4174 log_argv,
4175 NULL,
4176 pager_read,
4177 pager_draw,
4178 log_request,
4179 pager_grep,
4180 pager_select,
4181 };
4183 static const char *diff_argv[SIZEOF_ARG] = {
4184 "git", "show", "--pretty=fuller", "--no-color", "--root",
4185 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4186 };
4188 static struct view_ops diff_ops = {
4189 "line",
4190 diff_argv,
4191 NULL,
4192 pager_read,
4193 pager_draw,
4194 pager_request,
4195 pager_grep,
4196 pager_select,
4197 };
4199 /*
4200 * Help backend
4201 */
4203 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4205 static bool
4206 help_open_keymap_title(struct view *view, enum keymap keymap)
4207 {
4208 struct line *line;
4210 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4211 help_keymap_hidden[keymap] ? '+' : '-',
4212 enum_name(keymap_table[keymap]));
4213 if (line)
4214 line->other = keymap;
4216 return help_keymap_hidden[keymap];
4217 }
4219 static void
4220 help_open_keymap(struct view *view, enum keymap keymap)
4221 {
4222 const char *group = NULL;
4223 char buf[SIZEOF_STR];
4224 size_t bufpos;
4225 bool add_title = TRUE;
4226 int i;
4228 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4229 const char *key = NULL;
4231 if (req_info[i].request == REQ_NONE)
4232 continue;
4234 if (!req_info[i].request) {
4235 group = req_info[i].help;
4236 continue;
4237 }
4239 key = get_keys(keymap, req_info[i].request, TRUE);
4240 if (!key || !*key)
4241 continue;
4243 if (add_title && help_open_keymap_title(view, keymap))
4244 return;
4245 add_title = FALSE;
4247 if (group) {
4248 add_line_text(view, group, LINE_HELP_GROUP);
4249 group = NULL;
4250 }
4252 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4253 enum_name(req_info[i]), req_info[i].help);
4254 }
4256 group = "External commands:";
4258 for (i = 0; i < run_requests; i++) {
4259 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4260 const char *key;
4261 int argc;
4263 if (!req || req->keymap != keymap)
4264 continue;
4266 key = get_key_name(req->key);
4267 if (!*key)
4268 key = "(no key defined)";
4270 if (add_title && help_open_keymap_title(view, keymap))
4271 return;
4272 if (group) {
4273 add_line_text(view, group, LINE_HELP_GROUP);
4274 group = NULL;
4275 }
4277 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4278 if (!string_format_from(buf, &bufpos, "%s%s",
4279 argc ? " " : "", req->argv[argc]))
4280 return;
4282 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4283 }
4284 }
4286 static bool
4287 help_open(struct view *view)
4288 {
4289 enum keymap keymap;
4291 reset_view(view);
4292 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4293 add_line_text(view, "", LINE_DEFAULT);
4295 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4296 help_open_keymap(view, keymap);
4298 return TRUE;
4299 }
4301 static enum request
4302 help_request(struct view *view, enum request request, struct line *line)
4303 {
4304 switch (request) {
4305 case REQ_ENTER:
4306 if (line->type == LINE_HELP_KEYMAP) {
4307 help_keymap_hidden[line->other] =
4308 !help_keymap_hidden[line->other];
4309 view->p_restore = TRUE;
4310 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4311 }
4313 return REQ_NONE;
4314 default:
4315 return pager_request(view, request, line);
4316 }
4317 }
4319 static struct view_ops help_ops = {
4320 "line",
4321 NULL,
4322 help_open,
4323 NULL,
4324 pager_draw,
4325 help_request,
4326 pager_grep,
4327 pager_select,
4328 };
4331 /*
4332 * Tree backend
4333 */
4335 struct tree_stack_entry {
4336 struct tree_stack_entry *prev; /* Entry below this in the stack */
4337 unsigned long lineno; /* Line number to restore */
4338 char *name; /* Position of name in opt_path */
4339 };
4341 /* The top of the path stack. */
4342 static struct tree_stack_entry *tree_stack = NULL;
4343 unsigned long tree_lineno = 0;
4345 static void
4346 pop_tree_stack_entry(void)
4347 {
4348 struct tree_stack_entry *entry = tree_stack;
4350 tree_lineno = entry->lineno;
4351 entry->name[0] = 0;
4352 tree_stack = entry->prev;
4353 free(entry);
4354 }
4356 static void
4357 push_tree_stack_entry(const char *name, unsigned long lineno)
4358 {
4359 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4360 size_t pathlen = strlen(opt_path);
4362 if (!entry)
4363 return;
4365 entry->prev = tree_stack;
4366 entry->name = opt_path + pathlen;
4367 tree_stack = entry;
4369 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4370 pop_tree_stack_entry();
4371 return;
4372 }
4374 /* Move the current line to the first tree entry. */
4375 tree_lineno = 1;
4376 entry->lineno = lineno;
4377 }
4379 /* Parse output from git-ls-tree(1):
4380 *
4381 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4382 */
4384 #define SIZEOF_TREE_ATTR \
4385 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4387 #define SIZEOF_TREE_MODE \
4388 STRING_SIZE("100644 ")
4390 #define TREE_ID_OFFSET \
4391 STRING_SIZE("100644 blob ")
4393 struct tree_entry {
4394 char id[SIZEOF_REV];
4395 mode_t mode;
4396 struct time time; /* Date from the author ident. */
4397 const char *author; /* Author of the commit. */
4398 char name[1];
4399 };
4401 static const char *
4402 tree_path(const struct line *line)
4403 {
4404 return ((struct tree_entry *) line->data)->name;
4405 }
4407 static int
4408 tree_compare_entry(const struct line *line1, const struct line *line2)
4409 {
4410 if (line1->type != line2->type)
4411 return line1->type == LINE_TREE_DIR ? -1 : 1;
4412 return strcmp(tree_path(line1), tree_path(line2));
4413 }
4415 static const enum sort_field tree_sort_fields[] = {
4416 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4417 };
4418 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4420 static int
4421 tree_compare(const void *l1, const void *l2)
4422 {
4423 const struct line *line1 = (const struct line *) l1;
4424 const struct line *line2 = (const struct line *) l2;
4425 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4426 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4428 if (line1->type == LINE_TREE_HEAD)
4429 return -1;
4430 if (line2->type == LINE_TREE_HEAD)
4431 return 1;
4433 switch (get_sort_field(tree_sort_state)) {
4434 case ORDERBY_DATE:
4435 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4437 case ORDERBY_AUTHOR:
4438 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4440 case ORDERBY_NAME:
4441 default:
4442 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4443 }
4444 }
4447 static struct line *
4448 tree_entry(struct view *view, enum line_type type, const char *path,
4449 const char *mode, const char *id)
4450 {
4451 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4452 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4454 if (!entry || !line) {
4455 free(entry);
4456 return NULL;
4457 }
4459 strncpy(entry->name, path, strlen(path));
4460 if (mode)
4461 entry->mode = strtoul(mode, NULL, 8);
4462 if (id)
4463 string_copy_rev(entry->id, id);
4465 return line;
4466 }
4468 static bool
4469 tree_read_date(struct view *view, char *text, bool *read_date)
4470 {
4471 static const char *author_name;
4472 static struct time author_time;
4474 if (!text && *read_date) {
4475 *read_date = FALSE;
4476 return TRUE;
4478 } else if (!text) {
4479 char *path = *opt_path ? opt_path : ".";
4480 /* Find next entry to process */
4481 const char *log_file[] = {
4482 "git", "log", "--no-color", "--pretty=raw",
4483 "--cc", "--raw", view->id, "--", path, NULL
4484 };
4485 struct io io = {};
4487 if (!view->lines) {
4488 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4489 report("Tree is empty");
4490 return TRUE;
4491 }
4493 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4494 report("Failed to load tree data");
4495 return TRUE;
4496 }
4498 io_done(view->pipe);
4499 view->io = io;
4500 *read_date = TRUE;
4501 return FALSE;
4503 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4504 parse_author_line(text + STRING_SIZE("author "),
4505 &author_name, &author_time);
4507 } else if (*text == ':') {
4508 char *pos;
4509 size_t annotated = 1;
4510 size_t i;
4512 pos = strchr(text, '\t');
4513 if (!pos)
4514 return TRUE;
4515 text = pos + 1;
4516 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4517 text += strlen(opt_path);
4518 pos = strchr(text, '/');
4519 if (pos)
4520 *pos = 0;
4522 for (i = 1; i < view->lines; i++) {
4523 struct line *line = &view->line[i];
4524 struct tree_entry *entry = line->data;
4526 annotated += !!entry->author;
4527 if (entry->author || strcmp(entry->name, text))
4528 continue;
4530 entry->author = author_name;
4531 entry->time = author_time;
4532 line->dirty = 1;
4533 break;
4534 }
4536 if (annotated == view->lines)
4537 io_kill(view->pipe);
4538 }
4539 return TRUE;
4540 }
4542 static bool
4543 tree_read(struct view *view, char *text)
4544 {
4545 static bool read_date = FALSE;
4546 struct tree_entry *data;
4547 struct line *entry, *line;
4548 enum line_type type;
4549 size_t textlen = text ? strlen(text) : 0;
4550 char *path = text + SIZEOF_TREE_ATTR;
4552 if (read_date || !text)
4553 return tree_read_date(view, text, &read_date);
4555 if (textlen <= SIZEOF_TREE_ATTR)
4556 return FALSE;
4557 if (view->lines == 0 &&
4558 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4559 return FALSE;
4561 /* Strip the path part ... */
4562 if (*opt_path) {
4563 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4564 size_t striplen = strlen(opt_path);
4566 if (pathlen > striplen)
4567 memmove(path, path + striplen,
4568 pathlen - striplen + 1);
4570 /* Insert "link" to parent directory. */
4571 if (view->lines == 1 &&
4572 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4573 return FALSE;
4574 }
4576 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4577 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4578 if (!entry)
4579 return FALSE;
4580 data = entry->data;
4582 /* Skip "Directory ..." and ".." line. */
4583 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4584 if (tree_compare_entry(line, entry) <= 0)
4585 continue;
4587 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4589 line->data = data;
4590 line->type = type;
4591 for (; line <= entry; line++)
4592 line->dirty = line->cleareol = 1;
4593 return TRUE;
4594 }
4596 if (tree_lineno > view->lineno) {
4597 view->lineno = tree_lineno;
4598 tree_lineno = 0;
4599 }
4601 return TRUE;
4602 }
4604 static bool
4605 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4606 {
4607 struct tree_entry *entry = line->data;
4609 if (line->type == LINE_TREE_HEAD) {
4610 if (draw_text(view, line->type, "Directory path /", TRUE))
4611 return TRUE;
4612 } else {
4613 if (draw_mode(view, entry->mode))
4614 return TRUE;
4616 if (opt_author && draw_author(view, entry->author))
4617 return TRUE;
4619 if (opt_date && draw_date(view, &entry->time))
4620 return TRUE;
4621 }
4622 if (draw_text(view, line->type, entry->name, TRUE))
4623 return TRUE;
4624 return TRUE;
4625 }
4627 static void
4628 open_blob_editor()
4629 {
4630 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4631 int fd = mkstemp(file);
4633 if (fd == -1)
4634 report("Failed to create temporary file");
4635 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4636 report("Failed to save blob data to file");
4637 else
4638 open_editor(file);
4639 if (fd != -1)
4640 unlink(file);
4641 }
4643 static enum request
4644 tree_request(struct view *view, enum request request, struct line *line)
4645 {
4646 enum open_flags flags;
4648 switch (request) {
4649 case REQ_VIEW_BLAME:
4650 if (line->type != LINE_TREE_FILE) {
4651 report("Blame only supported for files");
4652 return REQ_NONE;
4653 }
4655 string_copy(opt_ref, view->vid);
4656 return request;
4658 case REQ_EDIT:
4659 if (line->type != LINE_TREE_FILE) {
4660 report("Edit only supported for files");
4661 } else if (!is_head_commit(view->vid)) {
4662 open_blob_editor();
4663 } else {
4664 open_editor(opt_file);
4665 }
4666 return REQ_NONE;
4668 case REQ_TOGGLE_SORT_FIELD:
4669 case REQ_TOGGLE_SORT_ORDER:
4670 sort_view(view, request, &tree_sort_state, tree_compare);
4671 return REQ_NONE;
4673 case REQ_PARENT:
4674 if (!*opt_path) {
4675 /* quit view if at top of tree */
4676 return REQ_VIEW_CLOSE;
4677 }
4678 /* fake 'cd ..' */
4679 line = &view->line[1];
4680 break;
4682 case REQ_ENTER:
4683 break;
4685 default:
4686 return request;
4687 }
4689 /* Cleanup the stack if the tree view is at a different tree. */
4690 while (!*opt_path && tree_stack)
4691 pop_tree_stack_entry();
4693 switch (line->type) {
4694 case LINE_TREE_DIR:
4695 /* Depending on whether it is a subdirectory or parent link
4696 * mangle the path buffer. */
4697 if (line == &view->line[1] && *opt_path) {
4698 pop_tree_stack_entry();
4700 } else {
4701 const char *basename = tree_path(line);
4703 push_tree_stack_entry(basename, view->lineno);
4704 }
4706 /* Trees and subtrees share the same ID, so they are not not
4707 * unique like blobs. */
4708 flags = OPEN_RELOAD;
4709 request = REQ_VIEW_TREE;
4710 break;
4712 case LINE_TREE_FILE:
4713 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4714 request = REQ_VIEW_BLOB;
4715 break;
4717 default:
4718 return REQ_NONE;
4719 }
4721 open_view(view, request, flags);
4722 if (request == REQ_VIEW_TREE)
4723 view->lineno = tree_lineno;
4725 return REQ_NONE;
4726 }
4728 static bool
4729 tree_grep(struct view *view, struct line *line)
4730 {
4731 struct tree_entry *entry = line->data;
4732 const char *text[] = {
4733 entry->name,
4734 opt_author ? entry->author : "",
4735 mkdate(&entry->time, opt_date),
4736 NULL
4737 };
4739 return grep_text(view, text);
4740 }
4742 static void
4743 tree_select(struct view *view, struct line *line)
4744 {
4745 struct tree_entry *entry = line->data;
4747 if (line->type == LINE_TREE_FILE) {
4748 string_copy_rev(ref_blob, entry->id);
4749 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4751 } else if (line->type != LINE_TREE_DIR) {
4752 return;
4753 }
4755 string_copy_rev(view->ref, entry->id);
4756 }
4758 static bool
4759 tree_prepare(struct view *view)
4760 {
4761 if (view->lines == 0 && opt_prefix[0]) {
4762 char *pos = opt_prefix;
4764 while (pos && *pos) {
4765 char *end = strchr(pos, '/');
4767 if (end)
4768 *end = 0;
4769 push_tree_stack_entry(pos, 0);
4770 pos = end;
4771 if (end) {
4772 *end = '/';
4773 pos++;
4774 }
4775 }
4777 } else if (strcmp(view->vid, view->id)) {
4778 opt_path[0] = 0;
4779 }
4781 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4782 }
4784 static const char *tree_argv[SIZEOF_ARG] = {
4785 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4786 };
4788 static struct view_ops tree_ops = {
4789 "file",
4790 tree_argv,
4791 NULL,
4792 tree_read,
4793 tree_draw,
4794 tree_request,
4795 tree_grep,
4796 tree_select,
4797 tree_prepare,
4798 };
4800 static bool
4801 blob_read(struct view *view, char *line)
4802 {
4803 if (!line)
4804 return TRUE;
4805 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4806 }
4808 static enum request
4809 blob_request(struct view *view, enum request request, struct line *line)
4810 {
4811 switch (request) {
4812 case REQ_EDIT:
4813 open_blob_editor();
4814 return REQ_NONE;
4815 default:
4816 return pager_request(view, request, line);
4817 }
4818 }
4820 static const char *blob_argv[SIZEOF_ARG] = {
4821 "git", "cat-file", "blob", "%(blob)", NULL
4822 };
4824 static struct view_ops blob_ops = {
4825 "line",
4826 blob_argv,
4827 NULL,
4828 blob_read,
4829 pager_draw,
4830 blob_request,
4831 pager_grep,
4832 pager_select,
4833 };
4835 /*
4836 * Blame backend
4837 *
4838 * Loading the blame view is a two phase job:
4839 *
4840 * 1. File content is read either using opt_file from the
4841 * filesystem or using git-cat-file.
4842 * 2. Then blame information is incrementally added by
4843 * reading output from git-blame.
4844 */
4846 static const char *blame_head_argv[] = {
4847 "git", "blame", "--incremental", "--", "%(file)", NULL
4848 };
4850 static const char *blame_ref_argv[] = {
4851 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4852 };
4854 static const char *blame_cat_file_argv[] = {
4855 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4856 };
4858 struct blame_commit {
4859 char id[SIZEOF_REV]; /* SHA1 ID. */
4860 char title[128]; /* First line of the commit message. */
4861 const char *author; /* Author of the commit. */
4862 struct time time; /* Date from the author ident. */
4863 char filename[128]; /* Name of file. */
4864 bool has_previous; /* Was a "previous" line detected. */
4865 };
4867 struct blame {
4868 struct blame_commit *commit;
4869 unsigned long lineno;
4870 char text[1];
4871 };
4873 static bool
4874 blame_open(struct view *view)
4875 {
4876 char path[SIZEOF_STR];
4878 if (!view->parent && *opt_prefix) {
4879 string_copy(path, opt_file);
4880 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4881 return FALSE;
4882 }
4884 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4885 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4886 return FALSE;
4887 }
4889 setup_update(view, opt_file);
4890 string_format(view->ref, "%s ...", opt_file);
4892 return TRUE;
4893 }
4895 static struct blame_commit *
4896 get_blame_commit(struct view *view, const char *id)
4897 {
4898 size_t i;
4900 for (i = 0; i < view->lines; i++) {
4901 struct blame *blame = view->line[i].data;
4903 if (!blame->commit)
4904 continue;
4906 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4907 return blame->commit;
4908 }
4910 {
4911 struct blame_commit *commit = calloc(1, sizeof(*commit));
4913 if (commit)
4914 string_ncopy(commit->id, id, SIZEOF_REV);
4915 return commit;
4916 }
4917 }
4919 static bool
4920 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4921 {
4922 const char *pos = *posref;
4924 *posref = NULL;
4925 pos = strchr(pos + 1, ' ');
4926 if (!pos || !isdigit(pos[1]))
4927 return FALSE;
4928 *number = atoi(pos + 1);
4929 if (*number < min || *number > max)
4930 return FALSE;
4932 *posref = pos;
4933 return TRUE;
4934 }
4936 static struct blame_commit *
4937 parse_blame_commit(struct view *view, const char *text, int *blamed)
4938 {
4939 struct blame_commit *commit;
4940 struct blame *blame;
4941 const char *pos = text + SIZEOF_REV - 2;
4942 size_t orig_lineno = 0;
4943 size_t lineno;
4944 size_t group;
4946 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4947 return NULL;
4949 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4950 !parse_number(&pos, &lineno, 1, view->lines) ||
4951 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4952 return NULL;
4954 commit = get_blame_commit(view, text);
4955 if (!commit)
4956 return NULL;
4958 *blamed += group;
4959 while (group--) {
4960 struct line *line = &view->line[lineno + group - 1];
4962 blame = line->data;
4963 blame->commit = commit;
4964 blame->lineno = orig_lineno + group - 1;
4965 line->dirty = 1;
4966 }
4968 return commit;
4969 }
4971 static bool
4972 blame_read_file(struct view *view, const char *line, bool *read_file)
4973 {
4974 if (!line) {
4975 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4976 struct io io = {};
4978 if (view->lines == 0 && !view->parent)
4979 die("No blame exist for %s", view->vid);
4981 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4982 report("Failed to load blame data");
4983 return TRUE;
4984 }
4986 io_done(view->pipe);
4987 view->io = io;
4988 *read_file = FALSE;
4989 return FALSE;
4991 } else {
4992 size_t linelen = strlen(line);
4993 struct blame *blame = malloc(sizeof(*blame) + linelen);
4995 if (!blame)
4996 return FALSE;
4998 blame->commit = NULL;
4999 strncpy(blame->text, line, linelen);
5000 blame->text[linelen] = 0;
5001 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5002 }
5003 }
5005 static bool
5006 match_blame_header(const char *name, char **line)
5007 {
5008 size_t namelen = strlen(name);
5009 bool matched = !strncmp(name, *line, namelen);
5011 if (matched)
5012 *line += namelen;
5014 return matched;
5015 }
5017 static bool
5018 blame_read(struct view *view, char *line)
5019 {
5020 static struct blame_commit *commit = NULL;
5021 static int blamed = 0;
5022 static bool read_file = TRUE;
5024 if (read_file)
5025 return blame_read_file(view, line, &read_file);
5027 if (!line) {
5028 /* Reset all! */
5029 commit = NULL;
5030 blamed = 0;
5031 read_file = TRUE;
5032 string_format(view->ref, "%s", view->vid);
5033 if (view_is_displayed(view)) {
5034 update_view_title(view);
5035 redraw_view_from(view, 0);
5036 }
5037 return TRUE;
5038 }
5040 if (!commit) {
5041 commit = parse_blame_commit(view, line, &blamed);
5042 string_format(view->ref, "%s %2d%%", view->vid,
5043 view->lines ? blamed * 100 / view->lines : 0);
5045 } else if (match_blame_header("author ", &line)) {
5046 commit->author = get_author(line);
5048 } else if (match_blame_header("author-time ", &line)) {
5049 parse_timesec(&commit->time, line);
5051 } else if (match_blame_header("author-tz ", &line)) {
5052 parse_timezone(&commit->time, line);
5054 } else if (match_blame_header("summary ", &line)) {
5055 string_ncopy(commit->title, line, strlen(line));
5057 } else if (match_blame_header("previous ", &line)) {
5058 commit->has_previous = TRUE;
5060 } else if (match_blame_header("filename ", &line)) {
5061 string_ncopy(commit->filename, line, strlen(line));
5062 commit = NULL;
5063 }
5065 return TRUE;
5066 }
5068 static bool
5069 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5070 {
5071 struct blame *blame = line->data;
5072 struct time *time = NULL;
5073 const char *id = NULL, *author = NULL;
5074 char text[SIZEOF_STR];
5076 if (blame->commit && *blame->commit->filename) {
5077 id = blame->commit->id;
5078 author = blame->commit->author;
5079 time = &blame->commit->time;
5080 }
5082 if (opt_date && draw_date(view, time))
5083 return TRUE;
5085 if (opt_author && draw_author(view, author))
5086 return TRUE;
5088 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5089 return TRUE;
5091 if (draw_lineno(view, lineno))
5092 return TRUE;
5094 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5095 draw_text(view, LINE_DEFAULT, text, TRUE);
5096 return TRUE;
5097 }
5099 static bool
5100 check_blame_commit(struct blame *blame, bool check_null_id)
5101 {
5102 if (!blame->commit)
5103 report("Commit data not loaded yet");
5104 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5105 report("No commit exist for the selected line");
5106 else
5107 return TRUE;
5108 return FALSE;
5109 }
5111 static void
5112 setup_blame_parent_line(struct view *view, struct blame *blame)
5113 {
5114 const char *diff_tree_argv[] = {
5115 "git", "diff-tree", "-U0", blame->commit->id,
5116 "--", blame->commit->filename, NULL
5117 };
5118 struct io io = {};
5119 int parent_lineno = -1;
5120 int blamed_lineno = -1;
5121 char *line;
5123 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5124 return;
5126 while ((line = io_get(&io, '\n', TRUE))) {
5127 if (*line == '@') {
5128 char *pos = strchr(line, '+');
5130 parent_lineno = atoi(line + 4);
5131 if (pos)
5132 blamed_lineno = atoi(pos + 1);
5134 } else if (*line == '+' && parent_lineno != -1) {
5135 if (blame->lineno == blamed_lineno - 1 &&
5136 !strcmp(blame->text, line + 1)) {
5137 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5138 break;
5139 }
5140 blamed_lineno++;
5141 }
5142 }
5144 io_done(&io);
5145 }
5147 static enum request
5148 blame_request(struct view *view, enum request request, struct line *line)
5149 {
5150 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5151 struct blame *blame = line->data;
5153 switch (request) {
5154 case REQ_VIEW_BLAME:
5155 if (check_blame_commit(blame, TRUE)) {
5156 string_copy(opt_ref, blame->commit->id);
5157 string_copy(opt_file, blame->commit->filename);
5158 if (blame->lineno)
5159 view->lineno = blame->lineno;
5160 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5161 }
5162 break;
5164 case REQ_PARENT:
5165 if (check_blame_commit(blame, TRUE) &&
5166 select_commit_parent(blame->commit->id, opt_ref,
5167 blame->commit->filename)) {
5168 string_copy(opt_file, blame->commit->filename);
5169 setup_blame_parent_line(view, blame);
5170 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5171 }
5172 break;
5174 case REQ_ENTER:
5175 if (!check_blame_commit(blame, FALSE))
5176 break;
5178 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5179 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5180 break;
5182 if (!strcmp(blame->commit->id, NULL_ID)) {
5183 struct view *diff = VIEW(REQ_VIEW_DIFF);
5184 const char *diff_index_argv[] = {
5185 "git", "diff-index", "--root", "--patch-with-stat",
5186 "-C", "-M", "HEAD", "--", view->vid, NULL
5187 };
5189 if (!blame->commit->has_previous) {
5190 diff_index_argv[1] = "diff";
5191 diff_index_argv[2] = "--no-color";
5192 diff_index_argv[6] = "--";
5193 diff_index_argv[7] = "/dev/null";
5194 }
5196 if (!prepare_update(diff, diff_index_argv, NULL)) {
5197 report("Failed to allocate diff command");
5198 break;
5199 }
5200 flags |= OPEN_PREPARED;
5201 }
5203 open_view(view, REQ_VIEW_DIFF, flags);
5204 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5205 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5206 break;
5208 default:
5209 return request;
5210 }
5212 return REQ_NONE;
5213 }
5215 static bool
5216 blame_grep(struct view *view, struct line *line)
5217 {
5218 struct blame *blame = line->data;
5219 struct blame_commit *commit = blame->commit;
5220 const char *text[] = {
5221 blame->text,
5222 commit ? commit->title : "",
5223 commit ? commit->id : "",
5224 commit && opt_author ? commit->author : "",
5225 commit ? mkdate(&commit->time, opt_date) : "",
5226 NULL
5227 };
5229 return grep_text(view, text);
5230 }
5232 static void
5233 blame_select(struct view *view, struct line *line)
5234 {
5235 struct blame *blame = line->data;
5236 struct blame_commit *commit = blame->commit;
5238 if (!commit)
5239 return;
5241 if (!strcmp(commit->id, NULL_ID))
5242 string_ncopy(ref_commit, "HEAD", 4);
5243 else
5244 string_copy_rev(ref_commit, commit->id);
5245 }
5247 static struct view_ops blame_ops = {
5248 "line",
5249 NULL,
5250 blame_open,
5251 blame_read,
5252 blame_draw,
5253 blame_request,
5254 blame_grep,
5255 blame_select,
5256 };
5258 /*
5259 * Branch backend
5260 */
5262 struct branch {
5263 const char *author; /* Author of the last commit. */
5264 struct time time; /* Date of the last activity. */
5265 const struct ref *ref; /* Name and commit ID information. */
5266 };
5268 static const struct ref branch_all;
5270 static const enum sort_field branch_sort_fields[] = {
5271 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5272 };
5273 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5275 static int
5276 branch_compare(const void *l1, const void *l2)
5277 {
5278 const struct branch *branch1 = ((const struct line *) l1)->data;
5279 const struct branch *branch2 = ((const struct line *) l2)->data;
5281 switch (get_sort_field(branch_sort_state)) {
5282 case ORDERBY_DATE:
5283 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5285 case ORDERBY_AUTHOR:
5286 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5288 case ORDERBY_NAME:
5289 default:
5290 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5291 }
5292 }
5294 static bool
5295 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5296 {
5297 struct branch *branch = line->data;
5298 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5300 if (opt_date && draw_date(view, &branch->time))
5301 return TRUE;
5303 if (opt_author && draw_author(view, branch->author))
5304 return TRUE;
5306 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5307 return TRUE;
5308 }
5310 static enum request
5311 branch_request(struct view *view, enum request request, struct line *line)
5312 {
5313 struct branch *branch = line->data;
5315 switch (request) {
5316 case REQ_REFRESH:
5317 load_refs();
5318 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5319 return REQ_NONE;
5321 case REQ_TOGGLE_SORT_FIELD:
5322 case REQ_TOGGLE_SORT_ORDER:
5323 sort_view(view, request, &branch_sort_state, branch_compare);
5324 return REQ_NONE;
5326 case REQ_ENTER:
5327 if (branch->ref == &branch_all) {
5328 const char *all_branches_argv[] = {
5329 "git", "log", "--no-color", "--pretty=raw", "--parents",
5330 "--topo-order", "--all", NULL
5331 };
5332 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5334 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5335 report("Failed to load view of all branches");
5336 return REQ_NONE;
5337 }
5338 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5339 } else {
5340 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5341 }
5342 return REQ_NONE;
5344 default:
5345 return request;
5346 }
5347 }
5349 static bool
5350 branch_read(struct view *view, char *line)
5351 {
5352 static char id[SIZEOF_REV];
5353 struct branch *reference;
5354 size_t i;
5356 if (!line)
5357 return TRUE;
5359 switch (get_line_type(line)) {
5360 case LINE_COMMIT:
5361 string_copy_rev(id, line + STRING_SIZE("commit "));
5362 return TRUE;
5364 case LINE_AUTHOR:
5365 for (i = 0, reference = NULL; i < view->lines; i++) {
5366 struct branch *branch = view->line[i].data;
5368 if (strcmp(branch->ref->id, id))
5369 continue;
5371 view->line[i].dirty = TRUE;
5372 if (reference) {
5373 branch->author = reference->author;
5374 branch->time = reference->time;
5375 continue;
5376 }
5378 parse_author_line(line + STRING_SIZE("author "),
5379 &branch->author, &branch->time);
5380 reference = branch;
5381 }
5382 return TRUE;
5384 default:
5385 return TRUE;
5386 }
5388 }
5390 static bool
5391 branch_open_visitor(void *data, const struct ref *ref)
5392 {
5393 struct view *view = data;
5394 struct branch *branch;
5396 if (ref->tag || ref->ltag || ref->remote)
5397 return TRUE;
5399 branch = calloc(1, sizeof(*branch));
5400 if (!branch)
5401 return FALSE;
5403 branch->ref = ref;
5404 return !!add_line_data(view, branch, LINE_DEFAULT);
5405 }
5407 static bool
5408 branch_open(struct view *view)
5409 {
5410 const char *branch_log[] = {
5411 "git", "log", "--no-color", "--pretty=raw",
5412 "--simplify-by-decoration", "--all", NULL
5413 };
5415 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5416 report("Failed to load branch data");
5417 return TRUE;
5418 }
5420 setup_update(view, view->id);
5421 branch_open_visitor(view, &branch_all);
5422 foreach_ref(branch_open_visitor, view);
5423 view->p_restore = TRUE;
5425 return TRUE;
5426 }
5428 static bool
5429 branch_grep(struct view *view, struct line *line)
5430 {
5431 struct branch *branch = line->data;
5432 const char *text[] = {
5433 branch->ref->name,
5434 branch->author,
5435 NULL
5436 };
5438 return grep_text(view, text);
5439 }
5441 static void
5442 branch_select(struct view *view, struct line *line)
5443 {
5444 struct branch *branch = line->data;
5446 string_copy_rev(view->ref, branch->ref->id);
5447 string_copy_rev(ref_commit, branch->ref->id);
5448 string_copy_rev(ref_head, branch->ref->id);
5449 string_copy_rev(ref_branch, branch->ref->name);
5450 }
5452 static struct view_ops branch_ops = {
5453 "branch",
5454 NULL,
5455 branch_open,
5456 branch_read,
5457 branch_draw,
5458 branch_request,
5459 branch_grep,
5460 branch_select,
5461 };
5463 /*
5464 * Status backend
5465 */
5467 struct status {
5468 char status;
5469 struct {
5470 mode_t mode;
5471 char rev[SIZEOF_REV];
5472 char name[SIZEOF_STR];
5473 } old;
5474 struct {
5475 mode_t mode;
5476 char rev[SIZEOF_REV];
5477 char name[SIZEOF_STR];
5478 } new;
5479 };
5481 static char status_onbranch[SIZEOF_STR];
5482 static struct status stage_status;
5483 static enum line_type stage_line_type;
5484 static size_t stage_chunks;
5485 static int *stage_chunk;
5487 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5489 /* This should work even for the "On branch" line. */
5490 static inline bool
5491 status_has_none(struct view *view, struct line *line)
5492 {
5493 return line < view->line + view->lines && !line[1].data;
5494 }
5496 /* Get fields from the diff line:
5497 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5498 */
5499 static inline bool
5500 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5501 {
5502 const char *old_mode = buf + 1;
5503 const char *new_mode = buf + 8;
5504 const char *old_rev = buf + 15;
5505 const char *new_rev = buf + 56;
5506 const char *status = buf + 97;
5508 if (bufsize < 98 ||
5509 old_mode[-1] != ':' ||
5510 new_mode[-1] != ' ' ||
5511 old_rev[-1] != ' ' ||
5512 new_rev[-1] != ' ' ||
5513 status[-1] != ' ')
5514 return FALSE;
5516 file->status = *status;
5518 string_copy_rev(file->old.rev, old_rev);
5519 string_copy_rev(file->new.rev, new_rev);
5521 file->old.mode = strtoul(old_mode, NULL, 8);
5522 file->new.mode = strtoul(new_mode, NULL, 8);
5524 file->old.name[0] = file->new.name[0] = 0;
5526 return TRUE;
5527 }
5529 static bool
5530 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5531 {
5532 struct status *unmerged = NULL;
5533 char *buf;
5534 struct io io = {};
5536 if (!io_run(&io, argv, opt_cdup, IO_RD))
5537 return FALSE;
5539 add_line_data(view, NULL, type);
5541 while ((buf = io_get(&io, 0, TRUE))) {
5542 struct status *file = unmerged;
5544 if (!file) {
5545 file = calloc(1, sizeof(*file));
5546 if (!file || !add_line_data(view, file, type))
5547 goto error_out;
5548 }
5550 /* Parse diff info part. */
5551 if (status) {
5552 file->status = status;
5553 if (status == 'A')
5554 string_copy(file->old.rev, NULL_ID);
5556 } else if (!file->status || file == unmerged) {
5557 if (!status_get_diff(file, buf, strlen(buf)))
5558 goto error_out;
5560 buf = io_get(&io, 0, TRUE);
5561 if (!buf)
5562 break;
5564 /* Collapse all modified entries that follow an
5565 * associated unmerged entry. */
5566 if (unmerged == file) {
5567 unmerged->status = 'U';
5568 unmerged = NULL;
5569 } else if (file->status == 'U') {
5570 unmerged = file;
5571 }
5572 }
5574 /* Grab the old name for rename/copy. */
5575 if (!*file->old.name &&
5576 (file->status == 'R' || file->status == 'C')) {
5577 string_ncopy(file->old.name, buf, strlen(buf));
5579 buf = io_get(&io, 0, TRUE);
5580 if (!buf)
5581 break;
5582 }
5584 /* git-ls-files just delivers a NUL separated list of
5585 * file names similar to the second half of the
5586 * git-diff-* output. */
5587 string_ncopy(file->new.name, buf, strlen(buf));
5588 if (!*file->old.name)
5589 string_copy(file->old.name, file->new.name);
5590 file = NULL;
5591 }
5593 if (io_error(&io)) {
5594 error_out:
5595 io_done(&io);
5596 return FALSE;
5597 }
5599 if (!view->line[view->lines - 1].data)
5600 add_line_data(view, NULL, LINE_STAT_NONE);
5602 io_done(&io);
5603 return TRUE;
5604 }
5606 /* Don't show unmerged entries in the staged section. */
5607 static const char *status_diff_index_argv[] = {
5608 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5609 "--cached", "-M", "HEAD", NULL
5610 };
5612 static const char *status_diff_files_argv[] = {
5613 "git", "diff-files", "-z", NULL
5614 };
5616 static const char *status_list_other_argv[] = {
5617 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5618 };
5620 static const char *status_list_no_head_argv[] = {
5621 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5622 };
5624 static const char *update_index_argv[] = {
5625 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5626 };
5628 /* Restore the previous line number to stay in the context or select a
5629 * line with something that can be updated. */
5630 static void
5631 status_restore(struct view *view)
5632 {
5633 if (view->p_lineno >= view->lines)
5634 view->p_lineno = view->lines - 1;
5635 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5636 view->p_lineno++;
5637 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5638 view->p_lineno--;
5640 /* If the above fails, always skip the "On branch" line. */
5641 if (view->p_lineno < view->lines)
5642 view->lineno = view->p_lineno;
5643 else
5644 view->lineno = 1;
5646 if (view->lineno < view->offset)
5647 view->offset = view->lineno;
5648 else if (view->offset + view->height <= view->lineno)
5649 view->offset = view->lineno - view->height + 1;
5651 view->p_restore = FALSE;
5652 }
5654 static void
5655 status_update_onbranch(void)
5656 {
5657 static const char *paths[][2] = {
5658 { "rebase-apply/rebasing", "Rebasing" },
5659 { "rebase-apply/applying", "Applying mailbox" },
5660 { "rebase-apply/", "Rebasing mailbox" },
5661 { "rebase-merge/interactive", "Interactive rebase" },
5662 { "rebase-merge/", "Rebase merge" },
5663 { "MERGE_HEAD", "Merging" },
5664 { "BISECT_LOG", "Bisecting" },
5665 { "HEAD", "On branch" },
5666 };
5667 char buf[SIZEOF_STR];
5668 struct stat stat;
5669 int i;
5671 if (is_initial_commit()) {
5672 string_copy(status_onbranch, "Initial commit");
5673 return;
5674 }
5676 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5677 char *head = opt_head;
5679 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5680 lstat(buf, &stat) < 0)
5681 continue;
5683 if (!*opt_head) {
5684 struct io io = {};
5686 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5687 io_read_buf(&io, buf, sizeof(buf))) {
5688 head = buf;
5689 if (!prefixcmp(head, "refs/heads/"))
5690 head += STRING_SIZE("refs/heads/");
5691 }
5692 }
5694 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5695 string_copy(status_onbranch, opt_head);
5696 return;
5697 }
5699 string_copy(status_onbranch, "Not currently on any branch");
5700 }
5702 /* First parse staged info using git-diff-index(1), then parse unstaged
5703 * info using git-diff-files(1), and finally untracked files using
5704 * git-ls-files(1). */
5705 static bool
5706 status_open(struct view *view)
5707 {
5708 reset_view(view);
5710 add_line_data(view, NULL, LINE_STAT_HEAD);
5711 status_update_onbranch();
5713 io_run_bg(update_index_argv);
5715 if (is_initial_commit()) {
5716 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5717 return FALSE;
5718 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5719 return FALSE;
5720 }
5722 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5723 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5724 return FALSE;
5726 /* Restore the exact position or use the specialized restore
5727 * mode? */
5728 if (!view->p_restore)
5729 status_restore(view);
5730 return TRUE;
5731 }
5733 static bool
5734 status_draw(struct view *view, struct line *line, unsigned int lineno)
5735 {
5736 struct status *status = line->data;
5737 enum line_type type;
5738 const char *text;
5740 if (!status) {
5741 switch (line->type) {
5742 case LINE_STAT_STAGED:
5743 type = LINE_STAT_SECTION;
5744 text = "Changes to be committed:";
5745 break;
5747 case LINE_STAT_UNSTAGED:
5748 type = LINE_STAT_SECTION;
5749 text = "Changed but not updated:";
5750 break;
5752 case LINE_STAT_UNTRACKED:
5753 type = LINE_STAT_SECTION;
5754 text = "Untracked files:";
5755 break;
5757 case LINE_STAT_NONE:
5758 type = LINE_DEFAULT;
5759 text = " (no files)";
5760 break;
5762 case LINE_STAT_HEAD:
5763 type = LINE_STAT_HEAD;
5764 text = status_onbranch;
5765 break;
5767 default:
5768 return FALSE;
5769 }
5770 } else {
5771 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5773 buf[0] = status->status;
5774 if (draw_text(view, line->type, buf, TRUE))
5775 return TRUE;
5776 type = LINE_DEFAULT;
5777 text = status->new.name;
5778 }
5780 draw_text(view, type, text, TRUE);
5781 return TRUE;
5782 }
5784 static enum request
5785 status_load_error(struct view *view, struct view *stage, const char *path)
5786 {
5787 if (displayed_views() == 2 || display[current_view] != view)
5788 maximize_view(view);
5789 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5790 return REQ_NONE;
5791 }
5793 static enum request
5794 status_enter(struct view *view, struct line *line)
5795 {
5796 struct status *status = line->data;
5797 const char *oldpath = status ? status->old.name : NULL;
5798 /* Diffs for unmerged entries are empty when passing the new
5799 * path, so leave it empty. */
5800 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5801 const char *info;
5802 enum open_flags split;
5803 struct view *stage = VIEW(REQ_VIEW_STAGE);
5805 if (line->type == LINE_STAT_NONE ||
5806 (!status && line[1].type == LINE_STAT_NONE)) {
5807 report("No file to diff");
5808 return REQ_NONE;
5809 }
5811 switch (line->type) {
5812 case LINE_STAT_STAGED:
5813 if (is_initial_commit()) {
5814 const char *no_head_diff_argv[] = {
5815 "git", "diff", "--no-color", "--patch-with-stat",
5816 "--", "/dev/null", newpath, NULL
5817 };
5819 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5820 return status_load_error(view, stage, newpath);
5821 } else {
5822 const char *index_show_argv[] = {
5823 "git", "diff-index", "--root", "--patch-with-stat",
5824 "-C", "-M", "--cached", "HEAD", "--",
5825 oldpath, newpath, NULL
5826 };
5828 if (!prepare_update(stage, index_show_argv, opt_cdup))
5829 return status_load_error(view, stage, newpath);
5830 }
5832 if (status)
5833 info = "Staged changes to %s";
5834 else
5835 info = "Staged changes";
5836 break;
5838 case LINE_STAT_UNSTAGED:
5839 {
5840 const char *files_show_argv[] = {
5841 "git", "diff-files", "--root", "--patch-with-stat",
5842 "-C", "-M", "--", oldpath, newpath, NULL
5843 };
5845 if (!prepare_update(stage, files_show_argv, opt_cdup))
5846 return status_load_error(view, stage, newpath);
5847 if (status)
5848 info = "Unstaged changes to %s";
5849 else
5850 info = "Unstaged changes";
5851 break;
5852 }
5853 case LINE_STAT_UNTRACKED:
5854 if (!newpath) {
5855 report("No file to show");
5856 return REQ_NONE;
5857 }
5859 if (!suffixcmp(status->new.name, -1, "/")) {
5860 report("Cannot display a directory");
5861 return REQ_NONE;
5862 }
5864 if (!prepare_update_file(stage, newpath))
5865 return status_load_error(view, stage, newpath);
5866 info = "Untracked file %s";
5867 break;
5869 case LINE_STAT_HEAD:
5870 return REQ_NONE;
5872 default:
5873 die("line type %d not handled in switch", line->type);
5874 }
5876 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5877 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5878 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5879 if (status) {
5880 stage_status = *status;
5881 } else {
5882 memset(&stage_status, 0, sizeof(stage_status));
5883 }
5885 stage_line_type = line->type;
5886 stage_chunks = 0;
5887 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5888 }
5890 return REQ_NONE;
5891 }
5893 static bool
5894 status_exists(struct status *status, enum line_type type)
5895 {
5896 struct view *view = VIEW(REQ_VIEW_STATUS);
5897 unsigned long lineno;
5899 for (lineno = 0; lineno < view->lines; lineno++) {
5900 struct line *line = &view->line[lineno];
5901 struct status *pos = line->data;
5903 if (line->type != type)
5904 continue;
5905 if (!pos && (!status || !status->status) && line[1].data) {
5906 select_view_line(view, lineno);
5907 return TRUE;
5908 }
5909 if (pos && !strcmp(status->new.name, pos->new.name)) {
5910 select_view_line(view, lineno);
5911 return TRUE;
5912 }
5913 }
5915 return FALSE;
5916 }
5919 static bool
5920 status_update_prepare(struct io *io, enum line_type type)
5921 {
5922 const char *staged_argv[] = {
5923 "git", "update-index", "-z", "--index-info", NULL
5924 };
5925 const char *others_argv[] = {
5926 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5927 };
5929 switch (type) {
5930 case LINE_STAT_STAGED:
5931 return io_run(io, staged_argv, opt_cdup, IO_WR);
5933 case LINE_STAT_UNSTAGED:
5934 case LINE_STAT_UNTRACKED:
5935 return io_run(io, others_argv, opt_cdup, IO_WR);
5937 default:
5938 die("line type %d not handled in switch", type);
5939 return FALSE;
5940 }
5941 }
5943 static bool
5944 status_update_write(struct io *io, struct status *status, enum line_type type)
5945 {
5946 char buf[SIZEOF_STR];
5947 size_t bufsize = 0;
5949 switch (type) {
5950 case LINE_STAT_STAGED:
5951 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5952 status->old.mode,
5953 status->old.rev,
5954 status->old.name, 0))
5955 return FALSE;
5956 break;
5958 case LINE_STAT_UNSTAGED:
5959 case LINE_STAT_UNTRACKED:
5960 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5961 return FALSE;
5962 break;
5964 default:
5965 die("line type %d not handled in switch", type);
5966 }
5968 return io_write(io, buf, bufsize);
5969 }
5971 static bool
5972 status_update_file(struct status *status, enum line_type type)
5973 {
5974 struct io io = {};
5975 bool result;
5977 if (!status_update_prepare(&io, type))
5978 return FALSE;
5980 result = status_update_write(&io, status, type);
5981 return io_done(&io) && result;
5982 }
5984 static bool
5985 status_update_files(struct view *view, struct line *line)
5986 {
5987 char buf[sizeof(view->ref)];
5988 struct io io = {};
5989 bool result = TRUE;
5990 struct line *pos = view->line + view->lines;
5991 int files = 0;
5992 int file, done;
5993 int cursor_y = -1, cursor_x = -1;
5995 if (!status_update_prepare(&io, line->type))
5996 return FALSE;
5998 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5999 files++;
6001 string_copy(buf, view->ref);
6002 getsyx(cursor_y, cursor_x);
6003 for (file = 0, done = 5; result && file < files; line++, file++) {
6004 int almost_done = file * 100 / files;
6006 if (almost_done > done) {
6007 done = almost_done;
6008 string_format(view->ref, "updating file %u of %u (%d%% done)",
6009 file, files, done);
6010 update_view_title(view);
6011 setsyx(cursor_y, cursor_x);
6012 doupdate();
6013 }
6014 result = status_update_write(&io, line->data, line->type);
6015 }
6016 string_copy(view->ref, buf);
6018 return io_done(&io) && result;
6019 }
6021 static bool
6022 status_update(struct view *view)
6023 {
6024 struct line *line = &view->line[view->lineno];
6026 assert(view->lines);
6028 if (!line->data) {
6029 /* This should work even for the "On branch" line. */
6030 if (line < view->line + view->lines && !line[1].data) {
6031 report("Nothing to update");
6032 return FALSE;
6033 }
6035 if (!status_update_files(view, line + 1)) {
6036 report("Failed to update file status");
6037 return FALSE;
6038 }
6040 } else if (!status_update_file(line->data, line->type)) {
6041 report("Failed to update file status");
6042 return FALSE;
6043 }
6045 return TRUE;
6046 }
6048 static bool
6049 status_revert(struct status *status, enum line_type type, bool has_none)
6050 {
6051 if (!status || type != LINE_STAT_UNSTAGED) {
6052 if (type == LINE_STAT_STAGED) {
6053 report("Cannot revert changes to staged files");
6054 } else if (type == LINE_STAT_UNTRACKED) {
6055 report("Cannot revert changes to untracked files");
6056 } else if (has_none) {
6057 report("Nothing to revert");
6058 } else {
6059 report("Cannot revert changes to multiple files");
6060 }
6062 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6063 char mode[10] = "100644";
6064 const char *reset_argv[] = {
6065 "git", "update-index", "--cacheinfo", mode,
6066 status->old.rev, status->old.name, NULL
6067 };
6068 const char *checkout_argv[] = {
6069 "git", "checkout", "--", status->old.name, NULL
6070 };
6072 if (status->status == 'U') {
6073 string_format(mode, "%5o", status->old.mode);
6075 if (status->old.mode == 0 && status->new.mode == 0) {
6076 reset_argv[2] = "--force-remove";
6077 reset_argv[3] = status->old.name;
6078 reset_argv[4] = NULL;
6079 }
6081 if (!io_run_fg(reset_argv, opt_cdup))
6082 return FALSE;
6083 if (status->old.mode == 0 && status->new.mode == 0)
6084 return TRUE;
6085 }
6087 return io_run_fg(checkout_argv, opt_cdup);
6088 }
6090 return FALSE;
6091 }
6093 static enum request
6094 status_request(struct view *view, enum request request, struct line *line)
6095 {
6096 struct status *status = line->data;
6098 switch (request) {
6099 case REQ_STATUS_UPDATE:
6100 if (!status_update(view))
6101 return REQ_NONE;
6102 break;
6104 case REQ_STATUS_REVERT:
6105 if (!status_revert(status, line->type, status_has_none(view, line)))
6106 return REQ_NONE;
6107 break;
6109 case REQ_STATUS_MERGE:
6110 if (!status || status->status != 'U') {
6111 report("Merging only possible for files with unmerged status ('U').");
6112 return REQ_NONE;
6113 }
6114 open_mergetool(status->new.name);
6115 break;
6117 case REQ_EDIT:
6118 if (!status)
6119 return request;
6120 if (status->status == 'D') {
6121 report("File has been deleted.");
6122 return REQ_NONE;
6123 }
6125 open_editor(status->new.name);
6126 break;
6128 case REQ_VIEW_BLAME:
6129 if (status)
6130 opt_ref[0] = 0;
6131 return request;
6133 case REQ_ENTER:
6134 /* After returning the status view has been split to
6135 * show the stage view. No further reloading is
6136 * necessary. */
6137 return status_enter(view, line);
6139 case REQ_REFRESH:
6140 /* Simply reload the view. */
6141 break;
6143 default:
6144 return request;
6145 }
6147 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6149 return REQ_NONE;
6150 }
6152 static void
6153 status_select(struct view *view, struct line *line)
6154 {
6155 struct status *status = line->data;
6156 char file[SIZEOF_STR] = "all files";
6157 const char *text;
6158 const char *key;
6160 if (status && !string_format(file, "'%s'", status->new.name))
6161 return;
6163 if (!status && line[1].type == LINE_STAT_NONE)
6164 line++;
6166 switch (line->type) {
6167 case LINE_STAT_STAGED:
6168 text = "Press %s to unstage %s for commit";
6169 break;
6171 case LINE_STAT_UNSTAGED:
6172 text = "Press %s to stage %s for commit";
6173 break;
6175 case LINE_STAT_UNTRACKED:
6176 text = "Press %s to stage %s for addition";
6177 break;
6179 case LINE_STAT_HEAD:
6180 case LINE_STAT_NONE:
6181 text = "Nothing to update";
6182 break;
6184 default:
6185 die("line type %d not handled in switch", line->type);
6186 }
6188 if (status && status->status == 'U') {
6189 text = "Press %s to resolve conflict in %s";
6190 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6192 } else {
6193 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6194 }
6196 string_format(view->ref, text, key, file);
6197 if (status)
6198 string_copy(opt_file, status->new.name);
6199 }
6201 static bool
6202 status_grep(struct view *view, struct line *line)
6203 {
6204 struct status *status = line->data;
6206 if (status) {
6207 const char buf[2] = { status->status, 0 };
6208 const char *text[] = { status->new.name, buf, NULL };
6210 return grep_text(view, text);
6211 }
6213 return FALSE;
6214 }
6216 static struct view_ops status_ops = {
6217 "file",
6218 NULL,
6219 status_open,
6220 NULL,
6221 status_draw,
6222 status_request,
6223 status_grep,
6224 status_select,
6225 };
6228 static bool
6229 stage_diff_write(struct io *io, struct line *line, struct line *end)
6230 {
6231 while (line < end) {
6232 if (!io_write(io, line->data, strlen(line->data)) ||
6233 !io_write(io, "\n", 1))
6234 return FALSE;
6235 line++;
6236 if (line->type == LINE_DIFF_CHUNK ||
6237 line->type == LINE_DIFF_HEADER)
6238 break;
6239 }
6241 return TRUE;
6242 }
6244 static struct line *
6245 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6246 {
6247 for (; view->line < line; line--)
6248 if (line->type == type)
6249 return line;
6251 return NULL;
6252 }
6254 static bool
6255 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6256 {
6257 const char *apply_argv[SIZEOF_ARG] = {
6258 "git", "apply", "--whitespace=nowarn", NULL
6259 };
6260 struct line *diff_hdr;
6261 struct io io = {};
6262 int argc = 3;
6264 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6265 if (!diff_hdr)
6266 return FALSE;
6268 if (!revert)
6269 apply_argv[argc++] = "--cached";
6270 if (revert || stage_line_type == LINE_STAT_STAGED)
6271 apply_argv[argc++] = "-R";
6272 apply_argv[argc++] = "-";
6273 apply_argv[argc++] = NULL;
6274 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6275 return FALSE;
6277 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6278 !stage_diff_write(&io, chunk, view->line + view->lines))
6279 chunk = NULL;
6281 io_done(&io);
6282 io_run_bg(update_index_argv);
6284 return chunk ? TRUE : FALSE;
6285 }
6287 static bool
6288 stage_update(struct view *view, struct line *line)
6289 {
6290 struct line *chunk = NULL;
6292 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6293 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6295 if (chunk) {
6296 if (!stage_apply_chunk(view, chunk, FALSE)) {
6297 report("Failed to apply chunk");
6298 return FALSE;
6299 }
6301 } else if (!stage_status.status) {
6302 view = VIEW(REQ_VIEW_STATUS);
6304 for (line = view->line; line < view->line + view->lines; line++)
6305 if (line->type == stage_line_type)
6306 break;
6308 if (!status_update_files(view, line + 1)) {
6309 report("Failed to update files");
6310 return FALSE;
6311 }
6313 } else if (!status_update_file(&stage_status, stage_line_type)) {
6314 report("Failed to update file");
6315 return FALSE;
6316 }
6318 return TRUE;
6319 }
6321 static bool
6322 stage_revert(struct view *view, struct line *line)
6323 {
6324 struct line *chunk = NULL;
6326 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6327 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6329 if (chunk) {
6330 if (!prompt_yesno("Are you sure you want to revert changes?"))
6331 return FALSE;
6333 if (!stage_apply_chunk(view, chunk, TRUE)) {
6334 report("Failed to revert chunk");
6335 return FALSE;
6336 }
6337 return TRUE;
6339 } else {
6340 return status_revert(stage_status.status ? &stage_status : NULL,
6341 stage_line_type, FALSE);
6342 }
6343 }
6346 static void
6347 stage_next(struct view *view, struct line *line)
6348 {
6349 int i;
6351 if (!stage_chunks) {
6352 for (line = view->line; line < view->line + view->lines; line++) {
6353 if (line->type != LINE_DIFF_CHUNK)
6354 continue;
6356 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6357 report("Allocation failure");
6358 return;
6359 }
6361 stage_chunk[stage_chunks++] = line - view->line;
6362 }
6363 }
6365 for (i = 0; i < stage_chunks; i++) {
6366 if (stage_chunk[i] > view->lineno) {
6367 do_scroll_view(view, stage_chunk[i] - view->lineno);
6368 report("Chunk %d of %d", i + 1, stage_chunks);
6369 return;
6370 }
6371 }
6373 report("No next chunk found");
6374 }
6376 static enum request
6377 stage_request(struct view *view, enum request request, struct line *line)
6378 {
6379 switch (request) {
6380 case REQ_STATUS_UPDATE:
6381 if (!stage_update(view, line))
6382 return REQ_NONE;
6383 break;
6385 case REQ_STATUS_REVERT:
6386 if (!stage_revert(view, line))
6387 return REQ_NONE;
6388 break;
6390 case REQ_STAGE_NEXT:
6391 if (stage_line_type == LINE_STAT_UNTRACKED) {
6392 report("File is untracked; press %s to add",
6393 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6394 return REQ_NONE;
6395 }
6396 stage_next(view, line);
6397 return REQ_NONE;
6399 case REQ_EDIT:
6400 if (!stage_status.new.name[0])
6401 return request;
6402 if (stage_status.status == 'D') {
6403 report("File has been deleted.");
6404 return REQ_NONE;
6405 }
6407 open_editor(stage_status.new.name);
6408 break;
6410 case REQ_REFRESH:
6411 /* Reload everything ... */
6412 break;
6414 case REQ_VIEW_BLAME:
6415 if (stage_status.new.name[0]) {
6416 string_copy(opt_file, stage_status.new.name);
6417 opt_ref[0] = 0;
6418 }
6419 return request;
6421 case REQ_ENTER:
6422 return pager_request(view, request, line);
6424 default:
6425 return request;
6426 }
6428 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6429 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6431 /* Check whether the staged entry still exists, and close the
6432 * stage view if it doesn't. */
6433 if (!status_exists(&stage_status, stage_line_type)) {
6434 status_restore(VIEW(REQ_VIEW_STATUS));
6435 return REQ_VIEW_CLOSE;
6436 }
6438 if (stage_line_type == LINE_STAT_UNTRACKED) {
6439 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6440 report("Cannot display a directory");
6441 return REQ_NONE;
6442 }
6444 if (!prepare_update_file(view, stage_status.new.name)) {
6445 report("Failed to open file: %s", strerror(errno));
6446 return REQ_NONE;
6447 }
6448 }
6449 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6451 return REQ_NONE;
6452 }
6454 static struct view_ops stage_ops = {
6455 "line",
6456 NULL,
6457 NULL,
6458 pager_read,
6459 pager_draw,
6460 stage_request,
6461 pager_grep,
6462 pager_select,
6463 };
6466 /*
6467 * Revision graph
6468 */
6470 struct commit {
6471 char id[SIZEOF_REV]; /* SHA1 ID. */
6472 char title[128]; /* First line of the commit message. */
6473 const char *author; /* Author of the commit. */
6474 struct time time; /* Date from the author ident. */
6475 struct ref_list *refs; /* Repository references. */
6476 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6477 size_t graph_size; /* The width of the graph array. */
6478 bool has_parents; /* Rewritten --parents seen. */
6479 };
6481 /* Size of rev graph with no "padding" columns */
6482 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6484 struct rev_graph {
6485 struct rev_graph *prev, *next, *parents;
6486 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6487 size_t size;
6488 struct commit *commit;
6489 size_t pos;
6490 unsigned int boundary:1;
6491 };
6493 /* Parents of the commit being visualized. */
6494 static struct rev_graph graph_parents[4];
6496 /* The current stack of revisions on the graph. */
6497 static struct rev_graph graph_stacks[4] = {
6498 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6499 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6500 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6501 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6502 };
6504 static inline bool
6505 graph_parent_is_merge(struct rev_graph *graph)
6506 {
6507 return graph->parents->size > 1;
6508 }
6510 static inline void
6511 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6512 {
6513 struct commit *commit = graph->commit;
6515 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6516 commit->graph[commit->graph_size++] = symbol;
6517 }
6519 static void
6520 clear_rev_graph(struct rev_graph *graph)
6521 {
6522 graph->boundary = 0;
6523 graph->size = graph->pos = 0;
6524 graph->commit = NULL;
6525 memset(graph->parents, 0, sizeof(*graph->parents));
6526 }
6528 static void
6529 done_rev_graph(struct rev_graph *graph)
6530 {
6531 if (graph_parent_is_merge(graph) &&
6532 graph->pos < graph->size - 1 &&
6533 graph->next->size == graph->size + graph->parents->size - 1) {
6534 size_t i = graph->pos + graph->parents->size - 1;
6536 graph->commit->graph_size = i * 2;
6537 while (i < graph->next->size - 1) {
6538 append_to_rev_graph(graph, ' ');
6539 append_to_rev_graph(graph, '\\');
6540 i++;
6541 }
6542 }
6544 clear_rev_graph(graph);
6545 }
6547 static void
6548 push_rev_graph(struct rev_graph *graph, const char *parent)
6549 {
6550 int i;
6552 /* "Collapse" duplicate parents lines.
6553 *
6554 * FIXME: This needs to also update update the drawn graph but
6555 * for now it just serves as a method for pruning graph lines. */
6556 for (i = 0; i < graph->size; i++)
6557 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6558 return;
6560 if (graph->size < SIZEOF_REVITEMS) {
6561 string_copy_rev(graph->rev[graph->size++], parent);
6562 }
6563 }
6565 static chtype
6566 get_rev_graph_symbol(struct rev_graph *graph)
6567 {
6568 chtype symbol;
6570 if (graph->boundary)
6571 symbol = REVGRAPH_BOUND;
6572 else if (graph->parents->size == 0)
6573 symbol = REVGRAPH_INIT;
6574 else if (graph_parent_is_merge(graph))
6575 symbol = REVGRAPH_MERGE;
6576 else if (graph->pos >= graph->size)
6577 symbol = REVGRAPH_BRANCH;
6578 else
6579 symbol = REVGRAPH_COMMIT;
6581 return symbol;
6582 }
6584 static void
6585 draw_rev_graph(struct rev_graph *graph)
6586 {
6587 struct rev_filler {
6588 chtype separator, line;
6589 };
6590 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6591 static struct rev_filler fillers[] = {
6592 { ' ', '|' },
6593 { '`', '.' },
6594 { '\'', ' ' },
6595 { '/', ' ' },
6596 };
6597 chtype symbol = get_rev_graph_symbol(graph);
6598 struct rev_filler *filler;
6599 size_t i;
6601 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6602 filler = &fillers[DEFAULT];
6604 for (i = 0; i < graph->pos; i++) {
6605 append_to_rev_graph(graph, filler->line);
6606 if (graph_parent_is_merge(graph->prev) &&
6607 graph->prev->pos == i)
6608 filler = &fillers[RSHARP];
6610 append_to_rev_graph(graph, filler->separator);
6611 }
6613 /* Place the symbol for this revision. */
6614 append_to_rev_graph(graph, symbol);
6616 if (graph->prev->size > graph->size)
6617 filler = &fillers[RDIAG];
6618 else
6619 filler = &fillers[DEFAULT];
6621 i++;
6623 for (; i < graph->size; i++) {
6624 append_to_rev_graph(graph, filler->separator);
6625 append_to_rev_graph(graph, filler->line);
6626 if (graph_parent_is_merge(graph->prev) &&
6627 i < graph->prev->pos + graph->parents->size)
6628 filler = &fillers[RSHARP];
6629 if (graph->prev->size > graph->size)
6630 filler = &fillers[LDIAG];
6631 }
6633 if (graph->prev->size > graph->size) {
6634 append_to_rev_graph(graph, filler->separator);
6635 if (filler->line != ' ')
6636 append_to_rev_graph(graph, filler->line);
6637 }
6638 }
6640 /* Prepare the next rev graph */
6641 static void
6642 prepare_rev_graph(struct rev_graph *graph)
6643 {
6644 size_t i;
6646 /* First, traverse all lines of revisions up to the active one. */
6647 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6648 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6649 break;
6651 push_rev_graph(graph->next, graph->rev[graph->pos]);
6652 }
6654 /* Interleave the new revision parent(s). */
6655 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6656 push_rev_graph(graph->next, graph->parents->rev[i]);
6658 /* Lastly, put any remaining revisions. */
6659 for (i = graph->pos + 1; i < graph->size; i++)
6660 push_rev_graph(graph->next, graph->rev[i]);
6661 }
6663 static void
6664 update_rev_graph(struct view *view, struct rev_graph *graph)
6665 {
6666 /* If this is the finalizing update ... */
6667 if (graph->commit)
6668 prepare_rev_graph(graph);
6670 /* Graph visualization needs a one rev look-ahead,
6671 * so the first update doesn't visualize anything. */
6672 if (!graph->prev->commit)
6673 return;
6675 if (view->lines > 2)
6676 view->line[view->lines - 3].dirty = 1;
6677 if (view->lines > 1)
6678 view->line[view->lines - 2].dirty = 1;
6679 draw_rev_graph(graph->prev);
6680 done_rev_graph(graph->prev->prev);
6681 }
6684 /*
6685 * Main view backend
6686 */
6688 static const char *main_argv[SIZEOF_ARG] = {
6689 "git", "log", "--no-color", "--pretty=raw", "--parents",
6690 "--topo-order", "%(head)", NULL
6691 };
6693 static bool
6694 main_draw(struct view *view, struct line *line, unsigned int lineno)
6695 {
6696 struct commit *commit = line->data;
6698 if (!commit->author)
6699 return FALSE;
6701 if (opt_date && draw_date(view, &commit->time))
6702 return TRUE;
6704 if (opt_author && draw_author(view, commit->author))
6705 return TRUE;
6707 if (opt_rev_graph && commit->graph_size &&
6708 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6709 return TRUE;
6711 if (opt_show_refs && commit->refs) {
6712 size_t i;
6714 for (i = 0; i < commit->refs->size; i++) {
6715 struct ref *ref = commit->refs->refs[i];
6716 enum line_type type;
6718 if (ref->head)
6719 type = LINE_MAIN_HEAD;
6720 else if (ref->ltag)
6721 type = LINE_MAIN_LOCAL_TAG;
6722 else if (ref->tag)
6723 type = LINE_MAIN_TAG;
6724 else if (ref->tracked)
6725 type = LINE_MAIN_TRACKED;
6726 else if (ref->remote)
6727 type = LINE_MAIN_REMOTE;
6728 else
6729 type = LINE_MAIN_REF;
6731 if (draw_text(view, type, "[", TRUE) ||
6732 draw_text(view, type, ref->name, TRUE) ||
6733 draw_text(view, type, "]", TRUE))
6734 return TRUE;
6736 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6737 return TRUE;
6738 }
6739 }
6741 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6742 return TRUE;
6743 }
6745 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6746 static bool
6747 main_read(struct view *view, char *line)
6748 {
6749 static struct rev_graph *graph = graph_stacks;
6750 enum line_type type;
6751 struct commit *commit;
6753 if (!line) {
6754 int i;
6756 if (!view->lines && !view->parent)
6757 die("No revisions match the given arguments.");
6758 if (view->lines > 0) {
6759 commit = view->line[view->lines - 1].data;
6760 view->line[view->lines - 1].dirty = 1;
6761 if (!commit->author) {
6762 view->lines--;
6763 free(commit);
6764 graph->commit = NULL;
6765 }
6766 }
6767 update_rev_graph(view, graph);
6769 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6770 clear_rev_graph(&graph_stacks[i]);
6771 return TRUE;
6772 }
6774 type = get_line_type(line);
6775 if (type == LINE_COMMIT) {
6776 commit = calloc(1, sizeof(struct commit));
6777 if (!commit)
6778 return FALSE;
6780 line += STRING_SIZE("commit ");
6781 if (*line == '-') {
6782 graph->boundary = 1;
6783 line++;
6784 }
6786 string_copy_rev(commit->id, line);
6787 commit->refs = get_ref_list(commit->id);
6788 graph->commit = commit;
6789 add_line_data(view, commit, LINE_MAIN_COMMIT);
6791 while ((line = strchr(line, ' '))) {
6792 line++;
6793 push_rev_graph(graph->parents, line);
6794 commit->has_parents = TRUE;
6795 }
6796 return TRUE;
6797 }
6799 if (!view->lines)
6800 return TRUE;
6801 commit = view->line[view->lines - 1].data;
6803 switch (type) {
6804 case LINE_PARENT:
6805 if (commit->has_parents)
6806 break;
6807 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6808 break;
6810 case LINE_AUTHOR:
6811 parse_author_line(line + STRING_SIZE("author "),
6812 &commit->author, &commit->time);
6813 update_rev_graph(view, graph);
6814 graph = graph->next;
6815 break;
6817 default:
6818 /* Fill in the commit title if it has not already been set. */
6819 if (commit->title[0])
6820 break;
6822 /* Require titles to start with a non-space character at the
6823 * offset used by git log. */
6824 if (strncmp(line, " ", 4))
6825 break;
6826 line += 4;
6827 /* Well, if the title starts with a whitespace character,
6828 * try to be forgiving. Otherwise we end up with no title. */
6829 while (isspace(*line))
6830 line++;
6831 if (*line == '\0')
6832 break;
6833 /* FIXME: More graceful handling of titles; append "..." to
6834 * shortened titles, etc. */
6836 string_expand(commit->title, sizeof(commit->title), line, 1);
6837 view->line[view->lines - 1].dirty = 1;
6838 }
6840 return TRUE;
6841 }
6843 static enum request
6844 main_request(struct view *view, enum request request, struct line *line)
6845 {
6846 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6848 switch (request) {
6849 case REQ_ENTER:
6850 open_view(view, REQ_VIEW_DIFF, flags);
6851 break;
6852 case REQ_REFRESH:
6853 load_refs();
6854 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6855 break;
6856 default:
6857 return request;
6858 }
6860 return REQ_NONE;
6861 }
6863 static bool
6864 grep_refs(struct ref_list *list, regex_t *regex)
6865 {
6866 regmatch_t pmatch;
6867 size_t i;
6869 if (!opt_show_refs || !list)
6870 return FALSE;
6872 for (i = 0; i < list->size; i++) {
6873 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6874 return TRUE;
6875 }
6877 return FALSE;
6878 }
6880 static bool
6881 main_grep(struct view *view, struct line *line)
6882 {
6883 struct commit *commit = line->data;
6884 const char *text[] = {
6885 commit->title,
6886 opt_author ? commit->author : "",
6887 mkdate(&commit->time, opt_date),
6888 NULL
6889 };
6891 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6892 }
6894 static void
6895 main_select(struct view *view, struct line *line)
6896 {
6897 struct commit *commit = line->data;
6899 string_copy_rev(view->ref, commit->id);
6900 string_copy_rev(ref_commit, view->ref);
6901 }
6903 static struct view_ops main_ops = {
6904 "commit",
6905 main_argv,
6906 NULL,
6907 main_read,
6908 main_draw,
6909 main_request,
6910 main_grep,
6911 main_select,
6912 };
6915 /*
6916 * Status management
6917 */
6919 /* Whether or not the curses interface has been initialized. */
6920 static bool cursed = FALSE;
6922 /* Terminal hacks and workarounds. */
6923 static bool use_scroll_redrawwin;
6924 static bool use_scroll_status_wclear;
6926 /* The status window is used for polling keystrokes. */
6927 static WINDOW *status_win;
6929 /* Reading from the prompt? */
6930 static bool input_mode = FALSE;
6932 static bool status_empty = FALSE;
6934 /* Update status and title window. */
6935 static void
6936 report(const char *msg, ...)
6937 {
6938 struct view *view = display[current_view];
6940 if (input_mode)
6941 return;
6943 if (!view) {
6944 char buf[SIZEOF_STR];
6945 va_list args;
6947 va_start(args, msg);
6948 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6949 buf[sizeof(buf) - 1] = 0;
6950 buf[sizeof(buf) - 2] = '.';
6951 buf[sizeof(buf) - 3] = '.';
6952 buf[sizeof(buf) - 4] = '.';
6953 }
6954 va_end(args);
6955 die("%s", buf);
6956 }
6958 if (!status_empty || *msg) {
6959 va_list args;
6961 va_start(args, msg);
6963 wmove(status_win, 0, 0);
6964 if (view->has_scrolled && use_scroll_status_wclear)
6965 wclear(status_win);
6966 if (*msg) {
6967 vwprintw(status_win, msg, args);
6968 status_empty = FALSE;
6969 } else {
6970 status_empty = TRUE;
6971 }
6972 wclrtoeol(status_win);
6973 wnoutrefresh(status_win);
6975 va_end(args);
6976 }
6978 update_view_title(view);
6979 }
6981 static void
6982 init_display(void)
6983 {
6984 const char *term;
6985 int x, y;
6987 /* Initialize the curses library */
6988 if (isatty(STDIN_FILENO)) {
6989 cursed = !!initscr();
6990 opt_tty = stdin;
6991 } else {
6992 /* Leave stdin and stdout alone when acting as a pager. */
6993 opt_tty = fopen("/dev/tty", "r+");
6994 if (!opt_tty)
6995 die("Failed to open /dev/tty");
6996 cursed = !!newterm(NULL, opt_tty, opt_tty);
6997 }
6999 if (!cursed)
7000 die("Failed to initialize curses");
7002 nonl(); /* Disable conversion and detect newlines from input. */
7003 cbreak(); /* Take input chars one at a time, no wait for \n */
7004 noecho(); /* Don't echo input */
7005 leaveok(stdscr, FALSE);
7007 if (has_colors())
7008 init_colors();
7010 getmaxyx(stdscr, y, x);
7011 status_win = newwin(1, 0, y - 1, 0);
7012 if (!status_win)
7013 die("Failed to create status window");
7015 /* Enable keyboard mapping */
7016 keypad(status_win, TRUE);
7017 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7019 TABSIZE = opt_tab_size;
7021 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7022 if (term && !strcmp(term, "gnome-terminal")) {
7023 /* In the gnome-terminal-emulator, the message from
7024 * scrolling up one line when impossible followed by
7025 * scrolling down one line causes corruption of the
7026 * status line. This is fixed by calling wclear. */
7027 use_scroll_status_wclear = TRUE;
7028 use_scroll_redrawwin = FALSE;
7030 } else if (term && !strcmp(term, "xrvt-xpm")) {
7031 /* No problems with full optimizations in xrvt-(unicode)
7032 * and aterm. */
7033 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7035 } else {
7036 /* When scrolling in (u)xterm the last line in the
7037 * scrolling direction will update slowly. */
7038 use_scroll_redrawwin = TRUE;
7039 use_scroll_status_wclear = FALSE;
7040 }
7041 }
7043 static int
7044 get_input(int prompt_position)
7045 {
7046 struct view *view;
7047 int i, key, cursor_y, cursor_x;
7048 bool loading = FALSE;
7050 if (prompt_position)
7051 input_mode = TRUE;
7053 while (TRUE) {
7054 foreach_view (view, i) {
7055 update_view(view);
7056 if (view_is_displayed(view) && view->has_scrolled &&
7057 use_scroll_redrawwin)
7058 redrawwin(view->win);
7059 view->has_scrolled = FALSE;
7060 if (view->pipe)
7061 loading = TRUE;
7062 }
7064 /* Update the cursor position. */
7065 if (prompt_position) {
7066 getbegyx(status_win, cursor_y, cursor_x);
7067 cursor_x = prompt_position;
7068 } else {
7069 view = display[current_view];
7070 getbegyx(view->win, cursor_y, cursor_x);
7071 cursor_x = view->width - 1;
7072 cursor_y += view->lineno - view->offset;
7073 }
7074 setsyx(cursor_y, cursor_x);
7076 /* Refresh, accept single keystroke of input */
7077 doupdate();
7078 nodelay(status_win, loading);
7079 key = wgetch(status_win);
7081 /* wgetch() with nodelay() enabled returns ERR when
7082 * there's no input. */
7083 if (key == ERR) {
7085 } else if (key == KEY_RESIZE) {
7086 int height, width;
7088 getmaxyx(stdscr, height, width);
7090 wresize(status_win, 1, width);
7091 mvwin(status_win, height - 1, 0);
7092 wnoutrefresh(status_win);
7093 resize_display();
7094 redraw_display(TRUE);
7096 } else {
7097 input_mode = FALSE;
7098 return key;
7099 }
7100 }
7101 }
7103 static char *
7104 prompt_input(const char *prompt, input_handler handler, void *data)
7105 {
7106 enum input_status status = INPUT_OK;
7107 static char buf[SIZEOF_STR];
7108 size_t pos = 0;
7110 buf[pos] = 0;
7112 while (status == INPUT_OK || status == INPUT_SKIP) {
7113 int key;
7115 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7116 wclrtoeol(status_win);
7118 key = get_input(pos + 1);
7119 switch (key) {
7120 case KEY_RETURN:
7121 case KEY_ENTER:
7122 case '\n':
7123 status = pos ? INPUT_STOP : INPUT_CANCEL;
7124 break;
7126 case KEY_BACKSPACE:
7127 if (pos > 0)
7128 buf[--pos] = 0;
7129 else
7130 status = INPUT_CANCEL;
7131 break;
7133 case KEY_ESC:
7134 status = INPUT_CANCEL;
7135 break;
7137 default:
7138 if (pos >= sizeof(buf)) {
7139 report("Input string too long");
7140 return NULL;
7141 }
7143 status = handler(data, buf, key);
7144 if (status == INPUT_OK)
7145 buf[pos++] = (char) key;
7146 }
7147 }
7149 /* Clear the status window */
7150 status_empty = FALSE;
7151 report("");
7153 if (status == INPUT_CANCEL)
7154 return NULL;
7156 buf[pos++] = 0;
7158 return buf;
7159 }
7161 static enum input_status
7162 prompt_yesno_handler(void *data, char *buf, int c)
7163 {
7164 if (c == 'y' || c == 'Y')
7165 return INPUT_STOP;
7166 if (c == 'n' || c == 'N')
7167 return INPUT_CANCEL;
7168 return INPUT_SKIP;
7169 }
7171 static bool
7172 prompt_yesno(const char *prompt)
7173 {
7174 char prompt2[SIZEOF_STR];
7176 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7177 return FALSE;
7179 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7180 }
7182 static enum input_status
7183 read_prompt_handler(void *data, char *buf, int c)
7184 {
7185 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7186 }
7188 static char *
7189 read_prompt(const char *prompt)
7190 {
7191 return prompt_input(prompt, read_prompt_handler, NULL);
7192 }
7194 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7195 {
7196 enum input_status status = INPUT_OK;
7197 int size = 0;
7199 while (items[size].text)
7200 size++;
7202 while (status == INPUT_OK) {
7203 const struct menu_item *item = &items[*selected];
7204 int key;
7205 int i;
7207 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7208 prompt, *selected + 1, size);
7209 if (item->hotkey)
7210 wprintw(status_win, "[%c] ", (char) item->hotkey);
7211 wprintw(status_win, "%s", item->text);
7212 wclrtoeol(status_win);
7214 key = get_input(COLS - 1);
7215 switch (key) {
7216 case KEY_RETURN:
7217 case KEY_ENTER:
7218 case '\n':
7219 status = INPUT_STOP;
7220 break;
7222 case KEY_LEFT:
7223 case KEY_UP:
7224 *selected = *selected - 1;
7225 if (*selected < 0)
7226 *selected = size - 1;
7227 break;
7229 case KEY_RIGHT:
7230 case KEY_DOWN:
7231 *selected = (*selected + 1) % size;
7232 break;
7234 case KEY_ESC:
7235 status = INPUT_CANCEL;
7236 break;
7238 default:
7239 for (i = 0; items[i].text; i++)
7240 if (items[i].hotkey == key) {
7241 *selected = i;
7242 status = INPUT_STOP;
7243 break;
7244 }
7245 }
7246 }
7248 /* Clear the status window */
7249 status_empty = FALSE;
7250 report("");
7252 return status != INPUT_CANCEL;
7253 }
7255 /*
7256 * Repository properties
7257 */
7259 static struct ref **refs = NULL;
7260 static size_t refs_size = 0;
7261 static struct ref *refs_head = NULL;
7263 static struct ref_list **ref_lists = NULL;
7264 static size_t ref_lists_size = 0;
7266 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7267 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7268 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7270 static int
7271 compare_refs(const void *ref1_, const void *ref2_)
7272 {
7273 const struct ref *ref1 = *(const struct ref **)ref1_;
7274 const struct ref *ref2 = *(const struct ref **)ref2_;
7276 if (ref1->tag != ref2->tag)
7277 return ref2->tag - ref1->tag;
7278 if (ref1->ltag != ref2->ltag)
7279 return ref2->ltag - ref2->ltag;
7280 if (ref1->head != ref2->head)
7281 return ref2->head - ref1->head;
7282 if (ref1->tracked != ref2->tracked)
7283 return ref2->tracked - ref1->tracked;
7284 if (ref1->remote != ref2->remote)
7285 return ref2->remote - ref1->remote;
7286 return strcmp(ref1->name, ref2->name);
7287 }
7289 static void
7290 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7291 {
7292 size_t i;
7294 for (i = 0; i < refs_size; i++)
7295 if (!visitor(data, refs[i]))
7296 break;
7297 }
7299 static struct ref *
7300 get_ref_head()
7301 {
7302 return refs_head;
7303 }
7305 static struct ref_list *
7306 get_ref_list(const char *id)
7307 {
7308 struct ref_list *list;
7309 size_t i;
7311 for (i = 0; i < ref_lists_size; i++)
7312 if (!strcmp(id, ref_lists[i]->id))
7313 return ref_lists[i];
7315 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7316 return NULL;
7317 list = calloc(1, sizeof(*list));
7318 if (!list)
7319 return NULL;
7321 for (i = 0; i < refs_size; i++) {
7322 if (!strcmp(id, refs[i]->id) &&
7323 realloc_refs_list(&list->refs, list->size, 1))
7324 list->refs[list->size++] = refs[i];
7325 }
7327 if (!list->refs) {
7328 free(list);
7329 return NULL;
7330 }
7332 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7333 ref_lists[ref_lists_size++] = list;
7334 return list;
7335 }
7337 static int
7338 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7339 {
7340 struct ref *ref = NULL;
7341 bool tag = FALSE;
7342 bool ltag = FALSE;
7343 bool remote = FALSE;
7344 bool tracked = FALSE;
7345 bool head = FALSE;
7346 int from = 0, to = refs_size - 1;
7348 if (!prefixcmp(name, "refs/tags/")) {
7349 if (!suffixcmp(name, namelen, "^{}")) {
7350 namelen -= 3;
7351 name[namelen] = 0;
7352 } else {
7353 ltag = TRUE;
7354 }
7356 tag = TRUE;
7357 namelen -= STRING_SIZE("refs/tags/");
7358 name += STRING_SIZE("refs/tags/");
7360 } else if (!prefixcmp(name, "refs/remotes/")) {
7361 remote = TRUE;
7362 namelen -= STRING_SIZE("refs/remotes/");
7363 name += STRING_SIZE("refs/remotes/");
7364 tracked = !strcmp(opt_remote, name);
7366 } else if (!prefixcmp(name, "refs/heads/")) {
7367 namelen -= STRING_SIZE("refs/heads/");
7368 name += STRING_SIZE("refs/heads/");
7369 if (!strncmp(opt_head, name, namelen))
7370 return OK;
7372 } else if (!strcmp(name, "HEAD")) {
7373 head = TRUE;
7374 if (*opt_head) {
7375 namelen = strlen(opt_head);
7376 name = opt_head;
7377 }
7378 }
7380 /* If we are reloading or it's an annotated tag, replace the
7381 * previous SHA1 with the resolved commit id; relies on the fact
7382 * git-ls-remote lists the commit id of an annotated tag right
7383 * before the commit id it points to. */
7384 while (from <= to) {
7385 size_t pos = (to + from) / 2;
7386 int cmp = strcmp(name, refs[pos]->name);
7388 if (!cmp) {
7389 ref = refs[pos];
7390 break;
7391 }
7393 if (cmp < 0)
7394 to = pos - 1;
7395 else
7396 from = pos + 1;
7397 }
7399 if (!ref) {
7400 if (!realloc_refs(&refs, refs_size, 1))
7401 return ERR;
7402 ref = calloc(1, sizeof(*ref) + namelen);
7403 if (!ref)
7404 return ERR;
7405 memmove(refs + from + 1, refs + from,
7406 (refs_size - from) * sizeof(*refs));
7407 refs[from] = ref;
7408 strncpy(ref->name, name, namelen);
7409 refs_size++;
7410 }
7412 ref->head = head;
7413 ref->tag = tag;
7414 ref->ltag = ltag;
7415 ref->remote = remote;
7416 ref->tracked = tracked;
7417 string_copy_rev(ref->id, id);
7419 if (head)
7420 refs_head = ref;
7421 return OK;
7422 }
7424 static int
7425 load_refs(void)
7426 {
7427 const char *head_argv[] = {
7428 "git", "symbolic-ref", "HEAD", NULL
7429 };
7430 static const char *ls_remote_argv[SIZEOF_ARG] = {
7431 "git", "ls-remote", opt_git_dir, NULL
7432 };
7433 static bool init = FALSE;
7434 size_t i;
7436 if (!init) {
7437 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7438 die("TIG_LS_REMOTE contains too many arguments");
7439 init = TRUE;
7440 }
7442 if (!*opt_git_dir)
7443 return OK;
7445 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7446 !prefixcmp(opt_head, "refs/heads/")) {
7447 char *offset = opt_head + STRING_SIZE("refs/heads/");
7449 memmove(opt_head, offset, strlen(offset) + 1);
7450 }
7452 refs_head = NULL;
7453 for (i = 0; i < refs_size; i++)
7454 refs[i]->id[0] = 0;
7456 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7457 return ERR;
7459 /* Update the ref lists to reflect changes. */
7460 for (i = 0; i < ref_lists_size; i++) {
7461 struct ref_list *list = ref_lists[i];
7462 size_t old, new;
7464 for (old = new = 0; old < list->size; old++)
7465 if (!strcmp(list->id, list->refs[old]->id))
7466 list->refs[new++] = list->refs[old];
7467 list->size = new;
7468 }
7470 return OK;
7471 }
7473 static void
7474 set_remote_branch(const char *name, const char *value, size_t valuelen)
7475 {
7476 if (!strcmp(name, ".remote")) {
7477 string_ncopy(opt_remote, value, valuelen);
7479 } else if (*opt_remote && !strcmp(name, ".merge")) {
7480 size_t from = strlen(opt_remote);
7482 if (!prefixcmp(value, "refs/heads/"))
7483 value += STRING_SIZE("refs/heads/");
7485 if (!string_format_from(opt_remote, &from, "/%s", value))
7486 opt_remote[0] = 0;
7487 }
7488 }
7490 static void
7491 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7492 {
7493 const char *argv[SIZEOF_ARG] = { name, "=" };
7494 int argc = 1 + (cmd == option_set_command);
7495 int error = ERR;
7497 if (!argv_from_string(argv, &argc, value))
7498 config_msg = "Too many option arguments";
7499 else
7500 error = cmd(argc, argv);
7502 if (error == ERR)
7503 warn("Option 'tig.%s': %s", name, config_msg);
7504 }
7506 static bool
7507 set_environment_variable(const char *name, const char *value)
7508 {
7509 size_t len = strlen(name) + 1 + strlen(value) + 1;
7510 char *env = malloc(len);
7512 if (env &&
7513 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7514 putenv(env) == 0)
7515 return TRUE;
7516 free(env);
7517 return FALSE;
7518 }
7520 static void
7521 set_work_tree(const char *value)
7522 {
7523 char cwd[SIZEOF_STR];
7525 if (!getcwd(cwd, sizeof(cwd)))
7526 die("Failed to get cwd path: %s", strerror(errno));
7527 if (chdir(opt_git_dir) < 0)
7528 die("Failed to chdir(%s): %s", strerror(errno));
7529 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7530 die("Failed to get git path: %s", strerror(errno));
7531 if (chdir(cwd) < 0)
7532 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7533 if (chdir(value) < 0)
7534 die("Failed to chdir(%s): %s", value, strerror(errno));
7535 if (!getcwd(cwd, sizeof(cwd)))
7536 die("Failed to get cwd path: %s", strerror(errno));
7537 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7538 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7539 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7540 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7541 opt_is_inside_work_tree = TRUE;
7542 }
7544 static int
7545 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7546 {
7547 if (!strcmp(name, "i18n.commitencoding"))
7548 string_ncopy(opt_encoding, value, valuelen);
7550 else if (!strcmp(name, "core.editor"))
7551 string_ncopy(opt_editor, value, valuelen);
7553 else if (!strcmp(name, "core.worktree"))
7554 set_work_tree(value);
7556 else if (!prefixcmp(name, "tig.color."))
7557 set_repo_config_option(name + 10, value, option_color_command);
7559 else if (!prefixcmp(name, "tig.bind."))
7560 set_repo_config_option(name + 9, value, option_bind_command);
7562 else if (!prefixcmp(name, "tig."))
7563 set_repo_config_option(name + 4, value, option_set_command);
7565 else if (*opt_head && !prefixcmp(name, "branch.") &&
7566 !strncmp(name + 7, opt_head, strlen(opt_head)))
7567 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7569 return OK;
7570 }
7572 static int
7573 load_git_config(void)
7574 {
7575 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7577 return io_run_load(config_list_argv, "=", read_repo_config_option);
7578 }
7580 static int
7581 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7582 {
7583 if (!opt_git_dir[0]) {
7584 string_ncopy(opt_git_dir, name, namelen);
7586 } else if (opt_is_inside_work_tree == -1) {
7587 /* This can be 3 different values depending on the
7588 * version of git being used. If git-rev-parse does not
7589 * understand --is-inside-work-tree it will simply echo
7590 * the option else either "true" or "false" is printed.
7591 * Default to true for the unknown case. */
7592 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7594 } else if (*name == '.') {
7595 string_ncopy(opt_cdup, name, namelen);
7597 } else {
7598 string_ncopy(opt_prefix, name, namelen);
7599 }
7601 return OK;
7602 }
7604 static int
7605 load_repo_info(void)
7606 {
7607 const char *rev_parse_argv[] = {
7608 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7609 "--show-cdup", "--show-prefix", NULL
7610 };
7612 return io_run_load(rev_parse_argv, "=", read_repo_info);
7613 }
7616 /*
7617 * Main
7618 */
7620 static const char usage[] =
7621 "tig " TIG_VERSION " (" __DATE__ ")\n"
7622 "\n"
7623 "Usage: tig [options] [revs] [--] [paths]\n"
7624 " or: tig show [options] [revs] [--] [paths]\n"
7625 " or: tig blame [rev] path\n"
7626 " or: tig status\n"
7627 " or: tig < [git command output]\n"
7628 "\n"
7629 "Options:\n"
7630 " -v, --version Show version and exit\n"
7631 " -h, --help Show help message and exit";
7633 static void __NORETURN
7634 quit(int sig)
7635 {
7636 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7637 if (cursed)
7638 endwin();
7639 exit(0);
7640 }
7642 static void __NORETURN
7643 die(const char *err, ...)
7644 {
7645 va_list args;
7647 endwin();
7649 va_start(args, err);
7650 fputs("tig: ", stderr);
7651 vfprintf(stderr, err, args);
7652 fputs("\n", stderr);
7653 va_end(args);
7655 exit(1);
7656 }
7658 static void
7659 warn(const char *msg, ...)
7660 {
7661 va_list args;
7663 va_start(args, msg);
7664 fputs("tig warning: ", stderr);
7665 vfprintf(stderr, msg, args);
7666 fputs("\n", stderr);
7667 va_end(args);
7668 }
7670 static enum request
7671 parse_options(int argc, const char *argv[])
7672 {
7673 enum request request = REQ_VIEW_MAIN;
7674 const char *subcommand;
7675 bool seen_dashdash = FALSE;
7676 /* XXX: This is vulnerable to the user overriding options
7677 * required for the main view parser. */
7678 const char *custom_argv[SIZEOF_ARG] = {
7679 "git", "log", "--no-color", "--pretty=raw", "--parents",
7680 "--topo-order", NULL
7681 };
7682 int i, j = 6;
7684 if (!isatty(STDIN_FILENO)) {
7685 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7686 return REQ_VIEW_PAGER;
7687 }
7689 if (argc <= 1)
7690 return REQ_NONE;
7692 subcommand = argv[1];
7693 if (!strcmp(subcommand, "status")) {
7694 if (argc > 2)
7695 warn("ignoring arguments after `%s'", subcommand);
7696 return REQ_VIEW_STATUS;
7698 } else if (!strcmp(subcommand, "blame")) {
7699 if (argc <= 2 || argc > 4)
7700 die("invalid number of options to blame\n\n%s", usage);
7702 i = 2;
7703 if (argc == 4) {
7704 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7705 i++;
7706 }
7708 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7709 return REQ_VIEW_BLAME;
7711 } else if (!strcmp(subcommand, "show")) {
7712 request = REQ_VIEW_DIFF;
7714 } else {
7715 subcommand = NULL;
7716 }
7718 if (subcommand) {
7719 custom_argv[1] = subcommand;
7720 j = 2;
7721 }
7723 for (i = 1 + !!subcommand; i < argc; i++) {
7724 const char *opt = argv[i];
7726 if (seen_dashdash || !strcmp(opt, "--")) {
7727 seen_dashdash = TRUE;
7729 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7730 printf("tig version %s\n", TIG_VERSION);
7731 quit(0);
7733 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7734 printf("%s\n", usage);
7735 quit(0);
7736 }
7738 custom_argv[j++] = opt;
7739 if (j >= ARRAY_SIZE(custom_argv))
7740 die("command too long");
7741 }
7743 if (!prepare_update(VIEW(request), custom_argv, NULL))
7744 die("Failed to format arguments");
7746 return request;
7747 }
7749 int
7750 main(int argc, const char *argv[])
7751 {
7752 const char *codeset = "UTF-8";
7753 enum request request = parse_options(argc, argv);
7754 struct view *view;
7755 size_t i;
7757 signal(SIGINT, quit);
7758 signal(SIGPIPE, SIG_IGN);
7760 if (setlocale(LC_ALL, "")) {
7761 codeset = nl_langinfo(CODESET);
7762 }
7764 if (load_repo_info() == ERR)
7765 die("Failed to load repo info.");
7767 if (load_options() == ERR)
7768 die("Failed to load user config.");
7770 if (load_git_config() == ERR)
7771 die("Failed to load repo config.");
7773 /* Require a git repository unless when running in pager mode. */
7774 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7775 die("Not a git repository");
7777 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7778 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7779 if (opt_iconv_in == ICONV_NONE)
7780 die("Failed to initialize character set conversion");
7781 }
7783 if (codeset && strcmp(codeset, "UTF-8")) {
7784 opt_iconv_out = iconv_open(codeset, "UTF-8");
7785 if (opt_iconv_out == ICONV_NONE)
7786 die("Failed to initialize character set conversion");
7787 }
7789 if (load_refs() == ERR)
7790 die("Failed to load refs.");
7792 foreach_view (view, i)
7793 if (!argv_from_env(view->ops->argv, view->cmd_env))
7794 die("Too many arguments in the `%s` environment variable",
7795 view->cmd_env);
7797 init_display();
7799 if (request != REQ_NONE)
7800 open_view(NULL, request, OPEN_PREPARED);
7801 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7803 while (view_driver(display[current_view], request)) {
7804 int key = get_input(0);
7806 view = display[current_view];
7807 request = get_keybinding(view->keymap, key);
7809 /* Some low-level request handling. This keeps access to
7810 * status_win restricted. */
7811 switch (request) {
7812 case REQ_PROMPT:
7813 {
7814 char *cmd = read_prompt(":");
7816 if (cmd && isdigit(*cmd)) {
7817 int lineno = view->lineno + 1;
7819 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7820 select_view_line(view, lineno - 1);
7821 report("");
7822 } else {
7823 report("Unable to parse '%s' as a line number", cmd);
7824 }
7826 } else if (cmd) {
7827 struct view *next = VIEW(REQ_VIEW_PAGER);
7828 const char *argv[SIZEOF_ARG] = { "git" };
7829 int argc = 1;
7831 /* When running random commands, initially show the
7832 * command in the title. However, it maybe later be
7833 * overwritten if a commit line is selected. */
7834 string_ncopy(next->ref, cmd, strlen(cmd));
7836 if (!argv_from_string(argv, &argc, cmd)) {
7837 report("Too many arguments");
7838 } else if (!prepare_update(next, argv, NULL)) {
7839 report("Failed to format command");
7840 } else {
7841 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7842 }
7843 }
7845 request = REQ_NONE;
7846 break;
7847 }
7848 case REQ_SEARCH:
7849 case REQ_SEARCH_BACK:
7850 {
7851 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7852 char *search = read_prompt(prompt);
7854 if (search)
7855 string_ncopy(opt_search, search, strlen(search));
7856 else if (*opt_search)
7857 request = request == REQ_SEARCH ?
7858 REQ_FIND_NEXT :
7859 REQ_FIND_PREV;
7860 else
7861 request = REQ_NONE;
7862 break;
7863 }
7864 default:
7865 break;
7866 }
7867 }
7869 quit(0);
7871 return 0;
7872 }