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