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_(LOCAL), \
523 DATE_(RELATIVE), \
524 DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528 DATE_INFO
529 #undef DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534 DATE_INFO
535 #undef DATE_
536 };
538 struct time {
539 time_t sec;
540 int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
544 {
545 return t1->sec - t2->sec;
546 }
548 static const char *
549 mkdate(const struct time *time, enum date date)
550 {
551 static char buf[DATE_COLS + 1];
552 static const struct enum_map reldate[] = {
553 { "second", 1, 60 * 2 },
554 { "minute", 60, 60 * 60 * 2 },
555 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
556 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
557 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
558 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
559 };
560 struct tm tm;
562 if (!date || !time || !time->sec)
563 return "";
565 if (date == DATE_RELATIVE) {
566 struct timeval now;
567 time_t date = time->sec + time->tz;
568 time_t seconds;
569 int i;
571 gettimeofday(&now, NULL);
572 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574 if (seconds >= reldate[i].value)
575 continue;
577 seconds /= reldate[i].namelen;
578 if (!string_format(buf, "%ld %s%s %s",
579 seconds, reldate[i].name,
580 seconds > 1 ? "s" : "",
581 now.tv_sec >= date ? "ago" : "ahead"))
582 break;
583 return buf;
584 }
585 }
587 if (date == DATE_LOCAL) {
588 time_t date = time->sec + time->tz;
589 localtime_r(&date, &tm);
590 }
591 else {
592 gmtime_r(&time->sec, &tm);
593 }
594 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
595 }
598 #define AUTHOR_VALUES \
599 AUTHOR_(NO), \
600 AUTHOR_(FULL), \
601 AUTHOR_(ABBREVIATED)
603 enum author {
604 #define AUTHOR_(name) AUTHOR_##name
605 AUTHOR_VALUES,
606 #undef AUTHOR_
607 AUTHOR_DEFAULT = AUTHOR_FULL
608 };
610 static const struct enum_map author_map[] = {
611 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
612 AUTHOR_VALUES
613 #undef AUTHOR_
614 };
616 static const char *
617 get_author_initials(const char *author)
618 {
619 static char initials[AUTHOR_COLS * 6 + 1];
620 size_t pos = 0;
621 const char *end = strchr(author, '\0');
623 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
625 memset(initials, 0, sizeof(initials));
626 while (author < end) {
627 unsigned char bytes;
628 size_t i;
630 while (is_initial_sep(*author))
631 author++;
633 bytes = utf8_char_length(author, end);
634 if (bytes < sizeof(initials) - 1 - pos) {
635 while (bytes--) {
636 initials[pos++] = *author++;
637 }
638 }
640 for (i = pos; author < end && !is_initial_sep(*author); author++) {
641 if (i < sizeof(initials) - 1)
642 initials[i++] = *author;
643 }
645 initials[i++] = 0;
646 }
648 return initials;
649 }
652 static bool
653 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
654 {
655 int valuelen;
657 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
658 bool advance = cmd[valuelen] != 0;
660 cmd[valuelen] = 0;
661 argv[(*argc)++] = chomp_string(cmd);
662 cmd = chomp_string(cmd + valuelen + advance);
663 }
665 if (*argc < SIZEOF_ARG)
666 argv[*argc] = NULL;
667 return *argc < SIZEOF_ARG;
668 }
670 static bool
671 argv_from_env(const char **argv, const char *name)
672 {
673 char *env = argv ? getenv(name) : NULL;
674 int argc = 0;
676 if (env && *env)
677 env = strdup(env);
678 return !env || argv_from_string(argv, &argc, env);
679 }
681 static void
682 argv_free(const char *argv[])
683 {
684 int argc;
686 for (argc = 0; argv[argc]; argc++)
687 free((void *) argv[argc]);
688 }
691 /*
692 * Executing external commands.
693 */
695 enum io_type {
696 IO_FD, /* File descriptor based IO. */
697 IO_BG, /* Execute command in the background. */
698 IO_FG, /* Execute command with same std{in,out,err}. */
699 IO_RD, /* Read only fork+exec IO. */
700 IO_WR, /* Write only fork+exec IO. */
701 IO_AP, /* Append fork+exec output to file. */
702 };
704 struct io {
705 enum io_type type; /* The requested type of pipe. */
706 const char *dir; /* Directory from which to execute. */
707 pid_t pid; /* PID of spawned process. */
708 int pipe; /* Pipe end for reading or writing. */
709 int error; /* Error status. */
710 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
711 char *buf; /* Read buffer. */
712 size_t bufalloc; /* Allocated buffer size. */
713 size_t bufsize; /* Buffer content size. */
714 char *bufpos; /* Current buffer position. */
715 unsigned int eof:1; /* Has end of file been reached. */
716 };
718 static void
719 io_reset(struct io *io)
720 {
721 io->pipe = -1;
722 io->pid = 0;
723 io->buf = io->bufpos = NULL;
724 io->bufalloc = io->bufsize = 0;
725 io->error = 0;
726 io->eof = 0;
727 }
729 static void
730 io_init(struct io *io, const char *dir, enum io_type type)
731 {
732 io_reset(io);
733 io->type = type;
734 io->dir = dir;
735 }
737 static bool
738 io_format(struct io *io, const char *dir, enum io_type type,
739 const char *argv[], enum format_flags flags)
740 {
741 io_init(io, dir, type);
742 return format_argv(io->argv, argv, flags);
743 }
745 static bool
746 io_open(struct io *io, const char *fmt, ...)
747 {
748 char name[SIZEOF_STR] = "";
749 bool fits;
750 va_list args;
752 io_init(io, NULL, IO_FD);
754 va_start(args, fmt);
755 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
756 va_end(args);
758 if (!fits) {
759 io->error = ENAMETOOLONG;
760 return FALSE;
761 }
762 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
763 if (io->pipe == -1)
764 io->error = errno;
765 return io->pipe != -1;
766 }
768 static bool
769 io_kill(struct io *io)
770 {
771 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
772 }
774 static bool
775 io_done(struct io *io)
776 {
777 pid_t pid = io->pid;
779 if (io->pipe != -1)
780 close(io->pipe);
781 free(io->buf);
782 io_reset(io);
784 while (pid > 0) {
785 int status;
786 pid_t waiting = waitpid(pid, &status, 0);
788 if (waiting < 0) {
789 if (errno == EINTR)
790 continue;
791 io->error = errno;
792 return FALSE;
793 }
795 return waiting == pid &&
796 !WIFSIGNALED(status) &&
797 WIFEXITED(status) &&
798 !WEXITSTATUS(status);
799 }
801 return TRUE;
802 }
804 static bool
805 io_start(struct io *io)
806 {
807 int pipefds[2] = { -1, -1 };
809 if (io->type == IO_FD)
810 return TRUE;
812 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
813 io->error = errno;
814 return FALSE;
815 } else if (io->type == IO_AP) {
816 pipefds[1] = io->pipe;
817 }
819 if ((io->pid = fork())) {
820 if (io->pid == -1)
821 io->error = errno;
822 if (pipefds[!(io->type == IO_WR)] != -1)
823 close(pipefds[!(io->type == IO_WR)]);
824 if (io->pid != -1) {
825 io->pipe = pipefds[!!(io->type == IO_WR)];
826 return TRUE;
827 }
829 } else {
830 if (io->type != IO_FG) {
831 int devnull = open("/dev/null", O_RDWR);
832 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
833 int writefd = (io->type == IO_RD || io->type == IO_AP)
834 ? pipefds[1] : devnull;
836 dup2(readfd, STDIN_FILENO);
837 dup2(writefd, STDOUT_FILENO);
838 dup2(devnull, STDERR_FILENO);
840 close(devnull);
841 if (pipefds[0] != -1)
842 close(pipefds[0]);
843 if (pipefds[1] != -1)
844 close(pipefds[1]);
845 }
847 if (io->dir && *io->dir && chdir(io->dir) == -1)
848 exit(errno);
850 execvp(io->argv[0], (char *const*) io->argv);
851 exit(errno);
852 }
854 if (pipefds[!!(io->type == IO_WR)] != -1)
855 close(pipefds[!!(io->type == IO_WR)]);
856 return FALSE;
857 }
859 static bool
860 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
861 {
862 io_init(io, dir, type);
863 if (!format_argv(io->argv, argv, FORMAT_NONE))
864 return FALSE;
865 return io_start(io);
866 }
868 static int
869 io_complete(struct io *io)
870 {
871 return io_start(io) && io_done(io);
872 }
874 static int
875 io_run_bg(const char **argv)
876 {
877 struct io io = {};
879 if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
880 return FALSE;
881 return io_complete(&io);
882 }
884 static bool
885 io_run_fg(const char **argv, const char *dir)
886 {
887 struct io io = {};
889 if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
890 return FALSE;
891 return io_complete(&io);
892 }
894 static bool
895 io_run_append(const char **argv, int fd)
896 {
897 struct io io = {};
899 if (!io_format(&io, NULL, IO_AP, argv, FORMAT_NONE)) {
900 close(fd);
901 return FALSE;
902 }
904 io.pipe = fd;
905 return io_complete(&io);
906 }
908 static bool
909 io_run_rd(struct io *io, const char **argv, const char *dir)
910 {
911 return io_format(io, dir, IO_RD, argv, FORMAT_NONE) && io_start(io);
912 }
914 static bool
915 io_eof(struct io *io)
916 {
917 return io->eof;
918 }
920 static int
921 io_error(struct io *io)
922 {
923 return io->error;
924 }
926 static char *
927 io_strerror(struct io *io)
928 {
929 return strerror(io->error);
930 }
932 static bool
933 io_can_read(struct io *io)
934 {
935 struct timeval tv = { 0, 500 };
936 fd_set fds;
938 FD_ZERO(&fds);
939 FD_SET(io->pipe, &fds);
941 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
942 }
944 static ssize_t
945 io_read(struct io *io, void *buf, size_t bufsize)
946 {
947 do {
948 ssize_t readsize = read(io->pipe, buf, bufsize);
950 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
951 continue;
952 else if (readsize == -1)
953 io->error = errno;
954 else if (readsize == 0)
955 io->eof = 1;
956 return readsize;
957 } while (1);
958 }
960 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
962 static char *
963 io_get(struct io *io, int c, bool can_read)
964 {
965 char *eol;
966 ssize_t readsize;
968 while (TRUE) {
969 if (io->bufsize > 0) {
970 eol = memchr(io->bufpos, c, io->bufsize);
971 if (eol) {
972 char *line = io->bufpos;
974 *eol = 0;
975 io->bufpos = eol + 1;
976 io->bufsize -= io->bufpos - line;
977 return line;
978 }
979 }
981 if (io_eof(io)) {
982 if (io->bufsize) {
983 io->bufpos[io->bufsize] = 0;
984 io->bufsize = 0;
985 return io->bufpos;
986 }
987 return NULL;
988 }
990 if (!can_read)
991 return NULL;
993 if (io->bufsize > 0 && io->bufpos > io->buf)
994 memmove(io->buf, io->bufpos, io->bufsize);
996 if (io->bufalloc == io->bufsize) {
997 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
998 return NULL;
999 io->bufalloc += BUFSIZ;
1000 }
1002 io->bufpos = io->buf;
1003 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1004 if (io_error(io))
1005 return NULL;
1006 io->bufsize += readsize;
1007 }
1008 }
1010 static bool
1011 io_write(struct io *io, const void *buf, size_t bufsize)
1012 {
1013 size_t written = 0;
1015 while (!io_error(io) && written < bufsize) {
1016 ssize_t size;
1018 size = write(io->pipe, buf + written, bufsize - written);
1019 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1020 continue;
1021 else if (size == -1)
1022 io->error = errno;
1023 else
1024 written += size;
1025 }
1027 return written == bufsize;
1028 }
1030 static bool
1031 io_read_buf(struct io *io, char buf[], size_t bufsize)
1032 {
1033 char *result = io_get(io, '\n', TRUE);
1035 if (result) {
1036 result = chomp_string(result);
1037 string_ncopy_do(buf, bufsize, result, strlen(result));
1038 }
1040 return io_done(io) && result;
1041 }
1043 static bool
1044 io_run_buf(const char **argv, char buf[], size_t bufsize)
1045 {
1046 struct io io = {};
1048 return io_run_rd(&io, argv, NULL) && io_read_buf(&io, buf, bufsize);
1049 }
1051 static int
1052 io_load(struct io *io, const char *separators,
1053 int (*read_property)(char *, size_t, char *, size_t))
1054 {
1055 char *name;
1056 int state = OK;
1058 if (!io_start(io))
1059 return ERR;
1061 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1062 char *value;
1063 size_t namelen;
1064 size_t valuelen;
1066 name = chomp_string(name);
1067 namelen = strcspn(name, separators);
1069 if (name[namelen]) {
1070 name[namelen] = 0;
1071 value = chomp_string(name + namelen + 1);
1072 valuelen = strlen(value);
1074 } else {
1075 value = "";
1076 valuelen = 0;
1077 }
1079 state = read_property(name, namelen, value, valuelen);
1080 }
1082 if (state != ERR && io_error(io))
1083 state = ERR;
1084 io_done(io);
1086 return state;
1087 }
1089 static int
1090 io_run_load(const char **argv, const char *separators,
1091 int (*read_property)(char *, size_t, char *, size_t))
1092 {
1093 struct io io = {};
1095 return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1096 ? io_load(&io, separators, read_property) : ERR;
1097 }
1100 /*
1101 * User requests
1102 */
1104 #define REQ_INFO \
1105 /* XXX: Keep the view request first and in sync with views[]. */ \
1106 REQ_GROUP("View switching") \
1107 REQ_(VIEW_MAIN, "Show main view"), \
1108 REQ_(VIEW_DIFF, "Show diff view"), \
1109 REQ_(VIEW_LOG, "Show log view"), \
1110 REQ_(VIEW_TREE, "Show tree view"), \
1111 REQ_(VIEW_BLOB, "Show blob view"), \
1112 REQ_(VIEW_BLAME, "Show blame view"), \
1113 REQ_(VIEW_BRANCH, "Show branch view"), \
1114 REQ_(VIEW_HELP, "Show help page"), \
1115 REQ_(VIEW_PAGER, "Show pager view"), \
1116 REQ_(VIEW_STATUS, "Show status view"), \
1117 REQ_(VIEW_STAGE, "Show stage view"), \
1118 \
1119 REQ_GROUP("View manipulation") \
1120 REQ_(ENTER, "Enter current line and scroll"), \
1121 REQ_(NEXT, "Move to next"), \
1122 REQ_(PREVIOUS, "Move to previous"), \
1123 REQ_(PARENT, "Move to parent"), \
1124 REQ_(VIEW_NEXT, "Move focus to next view"), \
1125 REQ_(REFRESH, "Reload and refresh"), \
1126 REQ_(MAXIMIZE, "Maximize the current view"), \
1127 REQ_(VIEW_CLOSE, "Close the current view"), \
1128 REQ_(QUIT, "Close all views and quit"), \
1129 \
1130 REQ_GROUP("View specific requests") \
1131 REQ_(STATUS_UPDATE, "Update file status"), \
1132 REQ_(STATUS_REVERT, "Revert file changes"), \
1133 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1134 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1135 \
1136 REQ_GROUP("Cursor navigation") \
1137 REQ_(MOVE_UP, "Move cursor one line up"), \
1138 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1139 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1140 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1141 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1142 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1143 \
1144 REQ_GROUP("Scrolling") \
1145 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1146 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1147 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1148 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1149 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1150 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1151 \
1152 REQ_GROUP("Searching") \
1153 REQ_(SEARCH, "Search the view"), \
1154 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1155 REQ_(FIND_NEXT, "Find next search match"), \
1156 REQ_(FIND_PREV, "Find previous search match"), \
1157 \
1158 REQ_GROUP("Option manipulation") \
1159 REQ_(OPTIONS, "Open option menu"), \
1160 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1161 REQ_(TOGGLE_DATE, "Toggle date display"), \
1162 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1163 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1164 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1165 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1166 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1167 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1168 \
1169 REQ_GROUP("Misc") \
1170 REQ_(PROMPT, "Bring up the prompt"), \
1171 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1172 REQ_(SHOW_VERSION, "Show version information"), \
1173 REQ_(STOP_LOADING, "Stop all loading views"), \
1174 REQ_(EDIT, "Open in editor"), \
1175 REQ_(NONE, "Do nothing")
1178 /* User action requests. */
1179 enum request {
1180 #define REQ_GROUP(help)
1181 #define REQ_(req, help) REQ_##req
1183 /* Offset all requests to avoid conflicts with ncurses getch values. */
1184 REQ_UNKNOWN = KEY_MAX + 1,
1185 REQ_OFFSET,
1186 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1190 };
1192 struct request_info {
1193 enum request request;
1194 const char *name;
1195 int namelen;
1196 const char *help;
1197 };
1199 static const struct request_info req_info[] = {
1200 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1201 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1202 REQ_INFO
1203 #undef REQ_GROUP
1204 #undef REQ_
1205 };
1207 static enum request
1208 get_request(const char *name)
1209 {
1210 int namelen = strlen(name);
1211 int i;
1213 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1214 if (enum_equals(req_info[i], name, namelen))
1215 return req_info[i].request;
1217 return REQ_UNKNOWN;
1218 }
1221 /*
1222 * Options
1223 */
1225 /* Option and state variables. */
1226 static enum date opt_date = DATE_DEFAULT;
1227 static enum author opt_author = AUTHOR_DEFAULT;
1228 static bool opt_line_number = FALSE;
1229 static bool opt_line_graphics = TRUE;
1230 static bool opt_rev_graph = FALSE;
1231 static bool opt_show_refs = TRUE;
1232 static int opt_num_interval = 5;
1233 static double opt_hscroll = 0.50;
1234 static double opt_scale_split_view = 2.0 / 3.0;
1235 static int opt_tab_size = 8;
1236 static int opt_author_cols = AUTHOR_COLS;
1237 static char opt_path[SIZEOF_STR] = "";
1238 static char opt_file[SIZEOF_STR] = "";
1239 static char opt_ref[SIZEOF_REF] = "";
1240 static char opt_head[SIZEOF_REF] = "";
1241 static char opt_remote[SIZEOF_REF] = "";
1242 static char opt_encoding[20] = "UTF-8";
1243 static iconv_t opt_iconv_in = ICONV_NONE;
1244 static iconv_t opt_iconv_out = ICONV_NONE;
1245 static char opt_search[SIZEOF_STR] = "";
1246 static char opt_cdup[SIZEOF_STR] = "";
1247 static char opt_prefix[SIZEOF_STR] = "";
1248 static char opt_git_dir[SIZEOF_STR] = "";
1249 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1250 static char opt_editor[SIZEOF_STR] = "";
1251 static FILE *opt_tty = NULL;
1253 #define is_initial_commit() (!get_ref_head())
1254 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1257 /*
1258 * Line-oriented content detection.
1259 */
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1263 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1264 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1265 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1266 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1267 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1270 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1271 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1272 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1273 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1276 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1277 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1278 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1279 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1280 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1281 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1282 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1283 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1284 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1285 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1286 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1287 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1288 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1289 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1291 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1293 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1294 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1295 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1297 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1298 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1300 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1301 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1305 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1306 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1307 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1309 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1310 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1311 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1312 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1313 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1314 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1315 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1316 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1317 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1318 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1319 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1320 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324 LINE_##type
1325 LINE_INFO,
1326 LINE_NONE
1327 #undef LINE
1328 };
1330 struct line_info {
1331 const char *name; /* Option name. */
1332 int namelen; /* Size of option name. */
1333 const char *line; /* The start of line to match. */
1334 int linelen; /* Size of string to match. */
1335 int fg, bg, attr; /* Color and text attributes for the lines. */
1336 };
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341 LINE_INFO
1342 #undef LINE
1343 };
1345 static enum line_type
1346 get_line_type(const char *line)
1347 {
1348 int linelen = strlen(line);
1349 enum line_type type;
1351 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352 /* Case insensitive search matches Signed-off-by lines better. */
1353 if (linelen >= line_info[type].linelen &&
1354 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355 return type;
1357 return LINE_DEFAULT;
1358 }
1360 static inline int
1361 get_line_attr(enum line_type type)
1362 {
1363 assert(type < ARRAY_SIZE(line_info));
1364 return COLOR_PAIR(type) | line_info[type].attr;
1365 }
1367 static struct line_info *
1368 get_line_info(const char *name)
1369 {
1370 size_t namelen = strlen(name);
1371 enum line_type type;
1373 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374 if (enum_equals(line_info[type], name, namelen))
1375 return &line_info[type];
1377 return NULL;
1378 }
1380 static void
1381 init_colors(void)
1382 {
1383 int default_bg = line_info[LINE_DEFAULT].bg;
1384 int default_fg = line_info[LINE_DEFAULT].fg;
1385 enum line_type type;
1387 start_color();
1389 if (assume_default_colors(default_fg, default_bg) == ERR) {
1390 default_bg = COLOR_BLACK;
1391 default_fg = COLOR_WHITE;
1392 }
1394 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395 struct line_info *info = &line_info[type];
1396 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399 init_pair(type, fg, bg);
1400 }
1401 }
1403 struct line {
1404 enum line_type type;
1406 /* State flags */
1407 unsigned int selected:1;
1408 unsigned int dirty:1;
1409 unsigned int cleareol:1;
1410 unsigned int other:16;
1412 void *data; /* User data */
1413 };
1416 /*
1417 * Keys
1418 */
1420 struct keybinding {
1421 int alias;
1422 enum request request;
1423 };
1425 static struct keybinding default_keybindings[] = {
1426 /* View switching */
1427 { 'm', REQ_VIEW_MAIN },
1428 { 'd', REQ_VIEW_DIFF },
1429 { 'l', REQ_VIEW_LOG },
1430 { 't', REQ_VIEW_TREE },
1431 { 'f', REQ_VIEW_BLOB },
1432 { 'B', REQ_VIEW_BLAME },
1433 { 'H', REQ_VIEW_BRANCH },
1434 { 'p', REQ_VIEW_PAGER },
1435 { 'h', REQ_VIEW_HELP },
1436 { 'S', REQ_VIEW_STATUS },
1437 { 'c', REQ_VIEW_STAGE },
1439 /* View manipulation */
1440 { 'q', REQ_VIEW_CLOSE },
1441 { KEY_TAB, REQ_VIEW_NEXT },
1442 { KEY_RETURN, REQ_ENTER },
1443 { KEY_UP, REQ_PREVIOUS },
1444 { KEY_DOWN, REQ_NEXT },
1445 { 'R', REQ_REFRESH },
1446 { KEY_F(5), REQ_REFRESH },
1447 { 'O', REQ_MAXIMIZE },
1449 /* Cursor navigation */
1450 { 'k', REQ_MOVE_UP },
1451 { 'j', REQ_MOVE_DOWN },
1452 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1453 { KEY_END, REQ_MOVE_LAST_LINE },
1454 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1455 { ' ', REQ_MOVE_PAGE_DOWN },
1456 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1457 { 'b', REQ_MOVE_PAGE_UP },
1458 { '-', REQ_MOVE_PAGE_UP },
1460 /* Scrolling */
1461 { KEY_LEFT, REQ_SCROLL_LEFT },
1462 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1463 { KEY_IC, REQ_SCROLL_LINE_UP },
1464 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1465 { 'w', REQ_SCROLL_PAGE_UP },
1466 { 's', REQ_SCROLL_PAGE_DOWN },
1468 /* Searching */
1469 { '/', REQ_SEARCH },
1470 { '?', REQ_SEARCH_BACK },
1471 { 'n', REQ_FIND_NEXT },
1472 { 'N', REQ_FIND_PREV },
1474 /* Misc */
1475 { 'Q', REQ_QUIT },
1476 { 'z', REQ_STOP_LOADING },
1477 { 'v', REQ_SHOW_VERSION },
1478 { 'r', REQ_SCREEN_REDRAW },
1479 { 'o', REQ_OPTIONS },
1480 { '.', REQ_TOGGLE_LINENO },
1481 { 'D', REQ_TOGGLE_DATE },
1482 { 'A', REQ_TOGGLE_AUTHOR },
1483 { 'g', REQ_TOGGLE_REV_GRAPH },
1484 { 'F', REQ_TOGGLE_REFS },
1485 { 'I', REQ_TOGGLE_SORT_ORDER },
1486 { 'i', REQ_TOGGLE_SORT_FIELD },
1487 { ':', REQ_PROMPT },
1488 { 'u', REQ_STATUS_UPDATE },
1489 { '!', REQ_STATUS_REVERT },
1490 { 'M', REQ_STATUS_MERGE },
1491 { '@', REQ_STAGE_NEXT },
1492 { ',', REQ_PARENT },
1493 { 'e', REQ_EDIT },
1494 };
1496 #define KEYMAP_INFO \
1497 KEYMAP_(GENERIC), \
1498 KEYMAP_(MAIN), \
1499 KEYMAP_(DIFF), \
1500 KEYMAP_(LOG), \
1501 KEYMAP_(TREE), \
1502 KEYMAP_(BLOB), \
1503 KEYMAP_(BLAME), \
1504 KEYMAP_(BRANCH), \
1505 KEYMAP_(PAGER), \
1506 KEYMAP_(HELP), \
1507 KEYMAP_(STATUS), \
1508 KEYMAP_(STAGE)
1510 enum keymap {
1511 #define KEYMAP_(name) KEYMAP_##name
1512 KEYMAP_INFO
1513 #undef KEYMAP_
1514 };
1516 static const struct enum_map keymap_table[] = {
1517 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1518 KEYMAP_INFO
1519 #undef KEYMAP_
1520 };
1522 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1524 struct keybinding_table {
1525 struct keybinding *data;
1526 size_t size;
1527 };
1529 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1531 static void
1532 add_keybinding(enum keymap keymap, enum request request, int key)
1533 {
1534 struct keybinding_table *table = &keybindings[keymap];
1535 size_t i;
1537 for (i = 0; i < keybindings[keymap].size; i++) {
1538 if (keybindings[keymap].data[i].alias == key) {
1539 keybindings[keymap].data[i].request = request;
1540 return;
1541 }
1542 }
1544 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1545 if (!table->data)
1546 die("Failed to allocate keybinding");
1547 table->data[table->size].alias = key;
1548 table->data[table->size++].request = request;
1550 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1551 int i;
1553 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1554 if (default_keybindings[i].alias == key)
1555 default_keybindings[i].request = REQ_NONE;
1556 }
1557 }
1559 /* Looks for a key binding first in the given map, then in the generic map, and
1560 * lastly in the default keybindings. */
1561 static enum request
1562 get_keybinding(enum keymap keymap, int key)
1563 {
1564 size_t i;
1566 for (i = 0; i < keybindings[keymap].size; i++)
1567 if (keybindings[keymap].data[i].alias == key)
1568 return keybindings[keymap].data[i].request;
1570 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1571 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1572 return keybindings[KEYMAP_GENERIC].data[i].request;
1574 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1575 if (default_keybindings[i].alias == key)
1576 return default_keybindings[i].request;
1578 return (enum request) key;
1579 }
1582 struct key {
1583 const char *name;
1584 int value;
1585 };
1587 static const struct key key_table[] = {
1588 { "Enter", KEY_RETURN },
1589 { "Space", ' ' },
1590 { "Backspace", KEY_BACKSPACE },
1591 { "Tab", KEY_TAB },
1592 { "Escape", KEY_ESC },
1593 { "Left", KEY_LEFT },
1594 { "Right", KEY_RIGHT },
1595 { "Up", KEY_UP },
1596 { "Down", KEY_DOWN },
1597 { "Insert", KEY_IC },
1598 { "Delete", KEY_DC },
1599 { "Hash", '#' },
1600 { "Home", KEY_HOME },
1601 { "End", KEY_END },
1602 { "PageUp", KEY_PPAGE },
1603 { "PageDown", KEY_NPAGE },
1604 { "F1", KEY_F(1) },
1605 { "F2", KEY_F(2) },
1606 { "F3", KEY_F(3) },
1607 { "F4", KEY_F(4) },
1608 { "F5", KEY_F(5) },
1609 { "F6", KEY_F(6) },
1610 { "F7", KEY_F(7) },
1611 { "F8", KEY_F(8) },
1612 { "F9", KEY_F(9) },
1613 { "F10", KEY_F(10) },
1614 { "F11", KEY_F(11) },
1615 { "F12", KEY_F(12) },
1616 };
1618 static int
1619 get_key_value(const char *name)
1620 {
1621 int i;
1623 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1624 if (!strcasecmp(key_table[i].name, name))
1625 return key_table[i].value;
1627 if (strlen(name) == 1 && isprint(*name))
1628 return (int) *name;
1630 return ERR;
1631 }
1633 static const char *
1634 get_key_name(int key_value)
1635 {
1636 static char key_char[] = "'X'";
1637 const char *seq = NULL;
1638 int key;
1640 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1641 if (key_table[key].value == key_value)
1642 seq = key_table[key].name;
1644 if (seq == NULL &&
1645 key_value < 127 &&
1646 isprint(key_value)) {
1647 key_char[1] = (char) key_value;
1648 seq = key_char;
1649 }
1651 return seq ? seq : "(no key)";
1652 }
1654 static bool
1655 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1656 {
1657 const char *sep = *pos > 0 ? ", " : "";
1658 const char *keyname = get_key_name(keybinding->alias);
1660 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1661 }
1663 static bool
1664 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1665 enum keymap keymap, bool all)
1666 {
1667 int i;
1669 for (i = 0; i < keybindings[keymap].size; i++) {
1670 if (keybindings[keymap].data[i].request == request) {
1671 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1672 return FALSE;
1673 if (!all)
1674 break;
1675 }
1676 }
1678 return TRUE;
1679 }
1681 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1683 static const char *
1684 get_keys(enum keymap keymap, enum request request, bool all)
1685 {
1686 static char buf[BUFSIZ];
1687 size_t pos = 0;
1688 int i;
1690 buf[pos] = 0;
1692 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1693 return "Too many keybindings!";
1694 if (pos > 0 && !all)
1695 return buf;
1697 if (keymap != KEYMAP_GENERIC) {
1698 /* Only the generic keymap includes the default keybindings when
1699 * listing all keys. */
1700 if (all)
1701 return buf;
1703 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1704 return "Too many keybindings!";
1705 if (pos)
1706 return buf;
1707 }
1709 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1710 if (default_keybindings[i].request == request) {
1711 if (!append_key(buf, &pos, &default_keybindings[i]))
1712 return "Too many keybindings!";
1713 if (!all)
1714 return buf;
1715 }
1716 }
1718 return buf;
1719 }
1721 struct run_request {
1722 enum keymap keymap;
1723 int key;
1724 const char *argv[SIZEOF_ARG];
1725 };
1727 static struct run_request *run_request;
1728 static size_t run_requests;
1730 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1732 static enum request
1733 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1734 {
1735 struct run_request *req;
1737 if (argc >= ARRAY_SIZE(req->argv) - 1)
1738 return REQ_NONE;
1740 if (!realloc_run_requests(&run_request, run_requests, 1))
1741 return REQ_NONE;
1743 req = &run_request[run_requests];
1744 req->keymap = keymap;
1745 req->key = key;
1746 req->argv[0] = NULL;
1748 if (!format_argv(req->argv, argv, FORMAT_NONE))
1749 return REQ_NONE;
1751 return REQ_NONE + ++run_requests;
1752 }
1754 static struct run_request *
1755 get_run_request(enum request request)
1756 {
1757 if (request <= REQ_NONE)
1758 return NULL;
1759 return &run_request[request - REQ_NONE - 1];
1760 }
1762 static void
1763 add_builtin_run_requests(void)
1764 {
1765 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1766 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1767 const char *commit[] = { "git", "commit", NULL };
1768 const char *gc[] = { "git", "gc", NULL };
1769 struct {
1770 enum keymap keymap;
1771 int key;
1772 int argc;
1773 const char **argv;
1774 } reqs[] = {
1775 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1776 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1777 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1778 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1779 };
1780 int i;
1782 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1783 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1785 if (req != reqs[i].key)
1786 continue;
1787 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1788 if (req != REQ_NONE)
1789 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1790 }
1791 }
1793 /*
1794 * User config file handling.
1795 */
1797 static int config_lineno;
1798 static bool config_errors;
1799 static const char *config_msg;
1801 static const struct enum_map color_map[] = {
1802 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1803 COLOR_MAP(DEFAULT),
1804 COLOR_MAP(BLACK),
1805 COLOR_MAP(BLUE),
1806 COLOR_MAP(CYAN),
1807 COLOR_MAP(GREEN),
1808 COLOR_MAP(MAGENTA),
1809 COLOR_MAP(RED),
1810 COLOR_MAP(WHITE),
1811 COLOR_MAP(YELLOW),
1812 };
1814 static const struct enum_map attr_map[] = {
1815 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1816 ATTR_MAP(NORMAL),
1817 ATTR_MAP(BLINK),
1818 ATTR_MAP(BOLD),
1819 ATTR_MAP(DIM),
1820 ATTR_MAP(REVERSE),
1821 ATTR_MAP(STANDOUT),
1822 ATTR_MAP(UNDERLINE),
1823 };
1825 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1827 static int parse_step(double *opt, const char *arg)
1828 {
1829 *opt = atoi(arg);
1830 if (!strchr(arg, '%'))
1831 return OK;
1833 /* "Shift down" so 100% and 1 does not conflict. */
1834 *opt = (*opt - 1) / 100;
1835 if (*opt >= 1.0) {
1836 *opt = 0.99;
1837 config_msg = "Step value larger than 100%";
1838 return ERR;
1839 }
1840 if (*opt < 0.0) {
1841 *opt = 1;
1842 config_msg = "Invalid step value";
1843 return ERR;
1844 }
1845 return OK;
1846 }
1848 static int
1849 parse_int(int *opt, const char *arg, int min, int max)
1850 {
1851 int value = atoi(arg);
1853 if (min <= value && value <= max) {
1854 *opt = value;
1855 return OK;
1856 }
1858 config_msg = "Integer value out of bound";
1859 return ERR;
1860 }
1862 static bool
1863 set_color(int *color, const char *name)
1864 {
1865 if (map_enum(color, color_map, name))
1866 return TRUE;
1867 if (!prefixcmp(name, "color"))
1868 return parse_int(color, name + 5, 0, 255) == OK;
1869 return FALSE;
1870 }
1872 /* Wants: object fgcolor bgcolor [attribute] */
1873 static int
1874 option_color_command(int argc, const char *argv[])
1875 {
1876 struct line_info *info;
1878 if (argc < 3) {
1879 config_msg = "Wrong number of arguments given to color command";
1880 return ERR;
1881 }
1883 info = get_line_info(argv[0]);
1884 if (!info) {
1885 static const struct enum_map obsolete[] = {
1886 ENUM_MAP("main-delim", LINE_DELIMITER),
1887 ENUM_MAP("main-date", LINE_DATE),
1888 ENUM_MAP("main-author", LINE_AUTHOR),
1889 };
1890 int index;
1892 if (!map_enum(&index, obsolete, argv[0])) {
1893 config_msg = "Unknown color name";
1894 return ERR;
1895 }
1896 info = &line_info[index];
1897 }
1899 if (!set_color(&info->fg, argv[1]) ||
1900 !set_color(&info->bg, argv[2])) {
1901 config_msg = "Unknown color";
1902 return ERR;
1903 }
1905 info->attr = 0;
1906 while (argc-- > 3) {
1907 int attr;
1909 if (!set_attribute(&attr, argv[argc])) {
1910 config_msg = "Unknown attribute";
1911 return ERR;
1912 }
1913 info->attr |= attr;
1914 }
1916 return OK;
1917 }
1919 static int parse_bool(bool *opt, const char *arg)
1920 {
1921 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1922 ? TRUE : FALSE;
1923 return OK;
1924 }
1926 static int parse_enum_do(unsigned int *opt, const char *arg,
1927 const struct enum_map *map, size_t map_size)
1928 {
1929 bool is_true;
1931 assert(map_size > 1);
1933 if (map_enum_do(map, map_size, (int *) opt, arg))
1934 return OK;
1936 if (parse_bool(&is_true, arg) != OK)
1937 return ERR;
1939 *opt = is_true ? map[1].value : map[0].value;
1940 return OK;
1941 }
1943 #define parse_enum(opt, arg, map) \
1944 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1946 static int
1947 parse_string(char *opt, const char *arg, size_t optsize)
1948 {
1949 int arglen = strlen(arg);
1951 switch (arg[0]) {
1952 case '\"':
1953 case '\'':
1954 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1955 config_msg = "Unmatched quotation";
1956 return ERR;
1957 }
1958 arg += 1; arglen -= 2;
1959 default:
1960 string_ncopy_do(opt, optsize, arg, arglen);
1961 return OK;
1962 }
1963 }
1965 /* Wants: name = value */
1966 static int
1967 option_set_command(int argc, const char *argv[])
1968 {
1969 if (argc != 3) {
1970 config_msg = "Wrong number of arguments given to set command";
1971 return ERR;
1972 }
1974 if (strcmp(argv[1], "=")) {
1975 config_msg = "No value assigned";
1976 return ERR;
1977 }
1979 if (!strcmp(argv[0], "show-author"))
1980 return parse_enum(&opt_author, argv[2], author_map);
1982 if (!strcmp(argv[0], "show-date"))
1983 return parse_enum(&opt_date, argv[2], date_map);
1985 if (!strcmp(argv[0], "show-rev-graph"))
1986 return parse_bool(&opt_rev_graph, argv[2]);
1988 if (!strcmp(argv[0], "show-refs"))
1989 return parse_bool(&opt_show_refs, argv[2]);
1991 if (!strcmp(argv[0], "show-line-numbers"))
1992 return parse_bool(&opt_line_number, argv[2]);
1994 if (!strcmp(argv[0], "line-graphics"))
1995 return parse_bool(&opt_line_graphics, argv[2]);
1997 if (!strcmp(argv[0], "line-number-interval"))
1998 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2000 if (!strcmp(argv[0], "author-width"))
2001 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2003 if (!strcmp(argv[0], "horizontal-scroll"))
2004 return parse_step(&opt_hscroll, argv[2]);
2006 if (!strcmp(argv[0], "split-view-height"))
2007 return parse_step(&opt_scale_split_view, argv[2]);
2009 if (!strcmp(argv[0], "tab-size"))
2010 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2012 if (!strcmp(argv[0], "commit-encoding"))
2013 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2015 config_msg = "Unknown variable name";
2016 return ERR;
2017 }
2019 /* Wants: mode request key */
2020 static int
2021 option_bind_command(int argc, const char *argv[])
2022 {
2023 enum request request;
2024 int keymap = -1;
2025 int key;
2027 if (argc < 3) {
2028 config_msg = "Wrong number of arguments given to bind command";
2029 return ERR;
2030 }
2032 if (!set_keymap(&keymap, argv[0])) {
2033 config_msg = "Unknown key map";
2034 return ERR;
2035 }
2037 key = get_key_value(argv[1]);
2038 if (key == ERR) {
2039 config_msg = "Unknown key";
2040 return ERR;
2041 }
2043 request = get_request(argv[2]);
2044 if (request == REQ_UNKNOWN) {
2045 static const struct enum_map obsolete[] = {
2046 ENUM_MAP("cherry-pick", REQ_NONE),
2047 ENUM_MAP("screen-resize", REQ_NONE),
2048 ENUM_MAP("tree-parent", REQ_PARENT),
2049 };
2050 int alias;
2052 if (map_enum(&alias, obsolete, argv[2])) {
2053 if (alias != REQ_NONE)
2054 add_keybinding(keymap, alias, key);
2055 config_msg = "Obsolete request name";
2056 return ERR;
2057 }
2058 }
2059 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2060 request = add_run_request(keymap, key, argc - 2, argv + 2);
2061 if (request == REQ_UNKNOWN) {
2062 config_msg = "Unknown request name";
2063 return ERR;
2064 }
2066 add_keybinding(keymap, request, key);
2068 return OK;
2069 }
2071 static int
2072 set_option(const char *opt, char *value)
2073 {
2074 const char *argv[SIZEOF_ARG];
2075 int argc = 0;
2077 if (!argv_from_string(argv, &argc, value)) {
2078 config_msg = "Too many option arguments";
2079 return ERR;
2080 }
2082 if (!strcmp(opt, "color"))
2083 return option_color_command(argc, argv);
2085 if (!strcmp(opt, "set"))
2086 return option_set_command(argc, argv);
2088 if (!strcmp(opt, "bind"))
2089 return option_bind_command(argc, argv);
2091 config_msg = "Unknown option command";
2092 return ERR;
2093 }
2095 static int
2096 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2097 {
2098 int status = OK;
2100 config_lineno++;
2101 config_msg = "Internal error";
2103 /* Check for comment markers, since read_properties() will
2104 * only ensure opt and value are split at first " \t". */
2105 optlen = strcspn(opt, "#");
2106 if (optlen == 0)
2107 return OK;
2109 if (opt[optlen] != 0) {
2110 config_msg = "No option value";
2111 status = ERR;
2113 } else {
2114 /* Look for comment endings in the value. */
2115 size_t len = strcspn(value, "#");
2117 if (len < valuelen) {
2118 valuelen = len;
2119 value[valuelen] = 0;
2120 }
2122 status = set_option(opt, value);
2123 }
2125 if (status == ERR) {
2126 warn("Error on line %d, near '%.*s': %s",
2127 config_lineno, (int) optlen, opt, config_msg);
2128 config_errors = TRUE;
2129 }
2131 /* Always keep going if errors are encountered. */
2132 return OK;
2133 }
2135 static void
2136 load_option_file(const char *path)
2137 {
2138 struct io io = {};
2140 /* It's OK that the file doesn't exist. */
2141 if (!io_open(&io, "%s", path))
2142 return;
2144 config_lineno = 0;
2145 config_errors = FALSE;
2147 if (io_load(&io, " \t", read_option) == ERR ||
2148 config_errors == TRUE)
2149 warn("Errors while loading %s.", path);
2150 }
2152 static int
2153 load_options(void)
2154 {
2155 const char *home = getenv("HOME");
2156 const char *tigrc_user = getenv("TIGRC_USER");
2157 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2158 char buf[SIZEOF_STR];
2160 if (!tigrc_system)
2161 tigrc_system = SYSCONFDIR "/tigrc";
2162 load_option_file(tigrc_system);
2164 if (!tigrc_user) {
2165 if (!home || !string_format(buf, "%s/.tigrc", home))
2166 return ERR;
2167 tigrc_user = buf;
2168 }
2169 load_option_file(tigrc_user);
2171 /* Add _after_ loading config files to avoid adding run requests
2172 * that conflict with keybindings. */
2173 add_builtin_run_requests();
2175 return OK;
2176 }
2179 /*
2180 * The viewer
2181 */
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views() (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF] = "";
2197 static char ref_commit[SIZEOF_REF] = "HEAD";
2198 static char ref_head[SIZEOF_REF] = "HEAD";
2199 static char ref_branch[SIZEOF_REF] = "";
2201 enum view_type {
2202 VIEW_MAIN,
2203 VIEW_DIFF,
2204 VIEW_LOG,
2205 VIEW_TREE,
2206 VIEW_BLOB,
2207 VIEW_BLAME,
2208 VIEW_BRANCH,
2209 VIEW_HELP,
2210 VIEW_PAGER,
2211 VIEW_STATUS,
2212 VIEW_STAGE,
2213 };
2215 struct view {
2216 enum view_type type; /* View type */
2217 const char *name; /* View name */
2218 const char *cmd_env; /* Command line set via environment */
2219 const char *id; /* Points to either of ref_{head,commit,blob} */
2221 struct view_ops *ops; /* View operations */
2223 enum keymap keymap; /* What keymap does this view have */
2224 bool git_dir; /* Whether the view requires a git directory. */
2226 char ref[SIZEOF_REF]; /* Hovered commit reference */
2227 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2229 int height, width; /* The width and height of the main window */
2230 WINDOW *win; /* The main window */
2231 WINDOW *title; /* The title window living below the main window */
2233 /* Navigation */
2234 unsigned long offset; /* Offset of the window top */
2235 unsigned long yoffset; /* Offset from the window side. */
2236 unsigned long lineno; /* Current line number */
2237 unsigned long p_offset; /* Previous offset of the window top */
2238 unsigned long p_yoffset;/* Previous offset from the window side */
2239 unsigned long p_lineno; /* Previous current line number */
2240 bool p_restore; /* Should the previous position be restored. */
2242 /* Searching */
2243 char grep[SIZEOF_STR]; /* Search string */
2244 regex_t *regex; /* Pre-compiled regexp */
2246 /* If non-NULL, points to the view that opened this view. If this view
2247 * is closed tig will switch back to the parent view. */
2248 struct view *parent;
2249 struct view *prev;
2251 /* Buffering */
2252 size_t lines; /* Total number of lines */
2253 struct line *line; /* Line index */
2254 unsigned int digits; /* Number of digits in the lines member. */
2256 /* Drawing */
2257 struct line *curline; /* Line currently being drawn. */
2258 enum line_type curtype; /* Attribute currently used for drawing. */
2259 unsigned long col; /* Column when drawing. */
2260 bool has_scrolled; /* View was scrolled. */
2262 /* Loading */
2263 struct io io;
2264 struct io *pipe;
2265 time_t start_time;
2266 time_t update_secs;
2267 };
2269 struct view_ops {
2270 /* What type of content being displayed. Used in the title bar. */
2271 const char *type;
2272 /* Default command arguments. */
2273 const char **argv;
2274 /* Open and reads in all view content. */
2275 bool (*open)(struct view *view);
2276 /* Read one line; updates view->line. */
2277 bool (*read)(struct view *view, char *data);
2278 /* Draw one line; @lineno must be < view->height. */
2279 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2280 /* Depending on view handle a special requests. */
2281 enum request (*request)(struct view *view, enum request request, struct line *line);
2282 /* Search for regexp in a line. */
2283 bool (*grep)(struct view *view, struct line *line);
2284 /* Select line */
2285 void (*select)(struct view *view, struct line *line);
2286 /* Prepare view for loading */
2287 bool (*prepare)(struct view *view);
2288 };
2290 static struct view_ops blame_ops;
2291 static struct view_ops blob_ops;
2292 static struct view_ops diff_ops;
2293 static struct view_ops help_ops;
2294 static struct view_ops log_ops;
2295 static struct view_ops main_ops;
2296 static struct view_ops pager_ops;
2297 static struct view_ops stage_ops;
2298 static struct view_ops status_ops;
2299 static struct view_ops tree_ops;
2300 static struct view_ops branch_ops;
2302 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2303 { type, name, #env, ref, ops, map, git }
2305 #define VIEW_(id, name, ops, git, ref) \
2306 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2308 static struct view views[] = {
2309 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2310 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2311 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2312 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2313 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2314 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2315 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2316 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2317 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2318 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2319 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2320 };
2322 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2324 #define foreach_view(view, i) \
2325 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2327 #define view_is_displayed(view) \
2328 (view == display[0] || view == display[1])
2330 static enum request
2331 view_request(struct view *view, enum request request)
2332 {
2333 if (!view || !view->lines)
2334 return request;
2335 return view->ops->request(view, request, &view->line[view->lineno]);
2336 }
2339 /*
2340 * View drawing.
2341 */
2343 static inline void
2344 set_view_attr(struct view *view, enum line_type type)
2345 {
2346 if (!view->curline->selected && view->curtype != type) {
2347 (void) wattrset(view->win, get_line_attr(type));
2348 wchgat(view->win, -1, 0, type, NULL);
2349 view->curtype = type;
2350 }
2351 }
2353 static int
2354 draw_chars(struct view *view, enum line_type type, const char *string,
2355 int max_len, bool use_tilde)
2356 {
2357 static char out_buffer[BUFSIZ * 2];
2358 int len = 0;
2359 int col = 0;
2360 int trimmed = FALSE;
2361 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363 if (max_len <= 0)
2364 return 0;
2366 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2368 set_view_attr(view, type);
2369 if (len > 0) {
2370 if (opt_iconv_out != ICONV_NONE) {
2371 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2372 size_t inlen = len + 1;
2374 char *outbuf = out_buffer;
2375 size_t outlen = sizeof(out_buffer);
2377 size_t ret;
2379 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2380 if (ret != (size_t) -1) {
2381 string = out_buffer;
2382 len = sizeof(out_buffer) - outlen;
2383 }
2384 }
2386 waddnstr(view->win, string, len);
2387 }
2388 if (trimmed && use_tilde) {
2389 set_view_attr(view, LINE_DELIMITER);
2390 waddch(view->win, '~');
2391 col++;
2392 }
2394 return col;
2395 }
2397 static int
2398 draw_space(struct view *view, enum line_type type, int max, int spaces)
2399 {
2400 static char space[] = " ";
2401 int col = 0;
2403 spaces = MIN(max, spaces);
2405 while (spaces > 0) {
2406 int len = MIN(spaces, sizeof(space) - 1);
2408 col += draw_chars(view, type, space, len, FALSE);
2409 spaces -= len;
2410 }
2412 return col;
2413 }
2415 static bool
2416 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2417 {
2418 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2419 return view->width + view->yoffset <= view->col;
2420 }
2422 static bool
2423 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2424 {
2425 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2426 int max = view->width + view->yoffset - view->col;
2427 int i;
2429 if (max < size)
2430 size = max;
2432 set_view_attr(view, type);
2433 /* Using waddch() instead of waddnstr() ensures that
2434 * they'll be rendered correctly for the cursor line. */
2435 for (i = skip; i < size; i++)
2436 waddch(view->win, graphic[i]);
2438 view->col += size;
2439 if (size < max && skip <= size)
2440 waddch(view->win, ' ');
2441 view->col++;
2443 return view->width + view->yoffset <= view->col;
2444 }
2446 static bool
2447 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2448 {
2449 int max = MIN(view->width + view->yoffset - view->col, len);
2450 int col;
2452 if (text)
2453 col = draw_chars(view, type, text, max - 1, trim);
2454 else
2455 col = draw_space(view, type, max - 1, max - 1);
2457 view->col += col;
2458 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2459 return view->width + view->yoffset <= view->col;
2460 }
2462 static bool
2463 draw_date(struct view *view, struct time *time)
2464 {
2465 const char *date = mkdate(time, opt_date);
2466 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2468 return draw_field(view, LINE_DATE, date, cols, FALSE);
2469 }
2471 static bool
2472 draw_author(struct view *view, const char *author)
2473 {
2474 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2475 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2477 if (abbreviate && author)
2478 author = get_author_initials(author);
2480 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2481 }
2483 static bool
2484 draw_mode(struct view *view, mode_t mode)
2485 {
2486 const char *str;
2488 if (S_ISDIR(mode))
2489 str = "drwxr-xr-x";
2490 else if (S_ISLNK(mode))
2491 str = "lrwxrwxrwx";
2492 else if (S_ISGITLINK(mode))
2493 str = "m---------";
2494 else if (S_ISREG(mode) && mode & S_IXUSR)
2495 str = "-rwxr-xr-x";
2496 else if (S_ISREG(mode))
2497 str = "-rw-r--r--";
2498 else
2499 str = "----------";
2501 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2502 }
2504 static bool
2505 draw_lineno(struct view *view, unsigned int lineno)
2506 {
2507 char number[10];
2508 int digits3 = view->digits < 3 ? 3 : view->digits;
2509 int max = MIN(view->width + view->yoffset - view->col, digits3);
2510 char *text = NULL;
2511 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2513 lineno += view->offset + 1;
2514 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2515 static char fmt[] = "%1ld";
2517 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2518 if (string_format(number, fmt, lineno))
2519 text = number;
2520 }
2521 if (text)
2522 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2523 else
2524 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2525 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2526 }
2528 static bool
2529 draw_view_line(struct view *view, unsigned int lineno)
2530 {
2531 struct line *line;
2532 bool selected = (view->offset + lineno == view->lineno);
2534 assert(view_is_displayed(view));
2536 if (view->offset + lineno >= view->lines)
2537 return FALSE;
2539 line = &view->line[view->offset + lineno];
2541 wmove(view->win, lineno, 0);
2542 if (line->cleareol)
2543 wclrtoeol(view->win);
2544 view->col = 0;
2545 view->curline = line;
2546 view->curtype = LINE_NONE;
2547 line->selected = FALSE;
2548 line->dirty = line->cleareol = 0;
2550 if (selected) {
2551 set_view_attr(view, LINE_CURSOR);
2552 line->selected = TRUE;
2553 view->ops->select(view, line);
2554 }
2556 return view->ops->draw(view, line, lineno);
2557 }
2559 static void
2560 redraw_view_dirty(struct view *view)
2561 {
2562 bool dirty = FALSE;
2563 int lineno;
2565 for (lineno = 0; lineno < view->height; lineno++) {
2566 if (view->offset + lineno >= view->lines)
2567 break;
2568 if (!view->line[view->offset + lineno].dirty)
2569 continue;
2570 dirty = TRUE;
2571 if (!draw_view_line(view, lineno))
2572 break;
2573 }
2575 if (!dirty)
2576 return;
2577 wnoutrefresh(view->win);
2578 }
2580 static void
2581 redraw_view_from(struct view *view, int lineno)
2582 {
2583 assert(0 <= lineno && lineno < view->height);
2585 for (; lineno < view->height; lineno++) {
2586 if (!draw_view_line(view, lineno))
2587 break;
2588 }
2590 wnoutrefresh(view->win);
2591 }
2593 static void
2594 redraw_view(struct view *view)
2595 {
2596 werase(view->win);
2597 redraw_view_from(view, 0);
2598 }
2601 static void
2602 update_view_title(struct view *view)
2603 {
2604 char buf[SIZEOF_STR];
2605 char state[SIZEOF_STR];
2606 size_t bufpos = 0, statelen = 0;
2608 assert(view_is_displayed(view));
2610 if (view->type != VIEW_STATUS && view->lines) {
2611 unsigned int view_lines = view->offset + view->height;
2612 unsigned int lines = view->lines
2613 ? MIN(view_lines, view->lines) * 100 / view->lines
2614 : 0;
2616 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2617 view->ops->type,
2618 view->lineno + 1,
2619 view->lines,
2620 lines);
2622 }
2624 if (view->pipe) {
2625 time_t secs = time(NULL) - view->start_time;
2627 /* Three git seconds are a long time ... */
2628 if (secs > 2)
2629 string_format_from(state, &statelen, " loading %lds", secs);
2630 }
2632 string_format_from(buf, &bufpos, "[%s]", view->name);
2633 if (*view->ref && bufpos < view->width) {
2634 size_t refsize = strlen(view->ref);
2635 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2637 if (minsize < view->width)
2638 refsize = view->width - minsize + 7;
2639 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2640 }
2642 if (statelen && bufpos < view->width) {
2643 string_format_from(buf, &bufpos, "%s", state);
2644 }
2646 if (view == display[current_view])
2647 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2648 else
2649 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2651 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2652 wclrtoeol(view->title);
2653 wnoutrefresh(view->title);
2654 }
2656 static int
2657 apply_step(double step, int value)
2658 {
2659 if (step >= 1)
2660 return (int) step;
2661 value *= step + 0.01;
2662 return value ? value : 1;
2663 }
2665 static void
2666 resize_display(void)
2667 {
2668 int offset, i;
2669 struct view *base = display[0];
2670 struct view *view = display[1] ? display[1] : display[0];
2672 /* Setup window dimensions */
2674 getmaxyx(stdscr, base->height, base->width);
2676 /* Make room for the status window. */
2677 base->height -= 1;
2679 if (view != base) {
2680 /* Horizontal split. */
2681 view->width = base->width;
2682 view->height = apply_step(opt_scale_split_view, base->height);
2683 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2684 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2685 base->height -= view->height;
2687 /* Make room for the title bar. */
2688 view->height -= 1;
2689 }
2691 /* Make room for the title bar. */
2692 base->height -= 1;
2694 offset = 0;
2696 foreach_displayed_view (view, i) {
2697 if (!view->win) {
2698 view->win = newwin(view->height, 0, offset, 0);
2699 if (!view->win)
2700 die("Failed to create %s view", view->name);
2702 scrollok(view->win, FALSE);
2704 view->title = newwin(1, 0, offset + view->height, 0);
2705 if (!view->title)
2706 die("Failed to create title window");
2708 } else {
2709 wresize(view->win, view->height, view->width);
2710 mvwin(view->win, offset, 0);
2711 mvwin(view->title, offset + view->height, 0);
2712 }
2714 offset += view->height + 1;
2715 }
2716 }
2718 static void
2719 redraw_display(bool clear)
2720 {
2721 struct view *view;
2722 int i;
2724 foreach_displayed_view (view, i) {
2725 if (clear)
2726 wclear(view->win);
2727 redraw_view(view);
2728 update_view_title(view);
2729 }
2730 }
2733 /*
2734 * Option management
2735 */
2737 static void
2738 toggle_enum_option_do(unsigned int *opt, const char *help,
2739 const struct enum_map *map, size_t size)
2740 {
2741 *opt = (*opt + 1) % size;
2742 redraw_display(FALSE);
2743 report("Displaying %s %s", enum_name(map[*opt]), help);
2744 }
2746 #define toggle_enum_option(opt, help, map) \
2747 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2749 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2750 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2752 static void
2753 toggle_view_option(bool *option, const char *help)
2754 {
2755 *option = !*option;
2756 redraw_display(FALSE);
2757 report("%sabling %s", *option ? "En" : "Dis", help);
2758 }
2760 static void
2761 open_option_menu(void)
2762 {
2763 const struct menu_item menu[] = {
2764 { '.', "line numbers", &opt_line_number },
2765 { 'D', "date display", &opt_date },
2766 { 'A', "author display", &opt_author },
2767 { 'g', "revision graph display", &opt_rev_graph },
2768 { 'F', "reference display", &opt_show_refs },
2769 { 0 }
2770 };
2771 int selected = 0;
2773 if (prompt_menu("Toggle option", menu, &selected)) {
2774 if (menu[selected].data == &opt_date)
2775 toggle_date();
2776 else if (menu[selected].data == &opt_author)
2777 toggle_author();
2778 else
2779 toggle_view_option(menu[selected].data, menu[selected].text);
2780 }
2781 }
2783 static void
2784 maximize_view(struct view *view)
2785 {
2786 memset(display, 0, sizeof(display));
2787 current_view = 0;
2788 display[current_view] = view;
2789 resize_display();
2790 redraw_display(FALSE);
2791 report("");
2792 }
2795 /*
2796 * Navigation
2797 */
2799 static bool
2800 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2801 {
2802 if (lineno >= view->lines)
2803 lineno = view->lines > 0 ? view->lines - 1 : 0;
2805 if (offset > lineno || offset + view->height <= lineno) {
2806 unsigned long half = view->height / 2;
2808 if (lineno > half)
2809 offset = lineno - half;
2810 else
2811 offset = 0;
2812 }
2814 if (offset != view->offset || lineno != view->lineno) {
2815 view->offset = offset;
2816 view->lineno = lineno;
2817 return TRUE;
2818 }
2820 return FALSE;
2821 }
2823 /* Scrolling backend */
2824 static void
2825 do_scroll_view(struct view *view, int lines)
2826 {
2827 bool redraw_current_line = FALSE;
2829 /* The rendering expects the new offset. */
2830 view->offset += lines;
2832 assert(0 <= view->offset && view->offset < view->lines);
2833 assert(lines);
2835 /* Move current line into the view. */
2836 if (view->lineno < view->offset) {
2837 view->lineno = view->offset;
2838 redraw_current_line = TRUE;
2839 } else if (view->lineno >= view->offset + view->height) {
2840 view->lineno = view->offset + view->height - 1;
2841 redraw_current_line = TRUE;
2842 }
2844 assert(view->offset <= view->lineno && view->lineno < view->lines);
2846 /* Redraw the whole screen if scrolling is pointless. */
2847 if (view->height < ABS(lines)) {
2848 redraw_view(view);
2850 } else {
2851 int line = lines > 0 ? view->height - lines : 0;
2852 int end = line + ABS(lines);
2854 scrollok(view->win, TRUE);
2855 wscrl(view->win, lines);
2856 scrollok(view->win, FALSE);
2858 while (line < end && draw_view_line(view, line))
2859 line++;
2861 if (redraw_current_line)
2862 draw_view_line(view, view->lineno - view->offset);
2863 wnoutrefresh(view->win);
2864 }
2866 view->has_scrolled = TRUE;
2867 report("");
2868 }
2870 /* Scroll frontend */
2871 static void
2872 scroll_view(struct view *view, enum request request)
2873 {
2874 int lines = 1;
2876 assert(view_is_displayed(view));
2878 switch (request) {
2879 case REQ_SCROLL_LEFT:
2880 if (view->yoffset == 0) {
2881 report("Cannot scroll beyond the first column");
2882 return;
2883 }
2884 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2885 view->yoffset = 0;
2886 else
2887 view->yoffset -= apply_step(opt_hscroll, view->width);
2888 redraw_view_from(view, 0);
2889 report("");
2890 return;
2891 case REQ_SCROLL_RIGHT:
2892 view->yoffset += apply_step(opt_hscroll, view->width);
2893 redraw_view(view);
2894 report("");
2895 return;
2896 case REQ_SCROLL_PAGE_DOWN:
2897 lines = view->height;
2898 case REQ_SCROLL_LINE_DOWN:
2899 if (view->offset + lines > view->lines)
2900 lines = view->lines - view->offset;
2902 if (lines == 0 || view->offset + view->height >= view->lines) {
2903 report("Cannot scroll beyond the last line");
2904 return;
2905 }
2906 break;
2908 case REQ_SCROLL_PAGE_UP:
2909 lines = view->height;
2910 case REQ_SCROLL_LINE_UP:
2911 if (lines > view->offset)
2912 lines = view->offset;
2914 if (lines == 0) {
2915 report("Cannot scroll beyond the first line");
2916 return;
2917 }
2919 lines = -lines;
2920 break;
2922 default:
2923 die("request %d not handled in switch", request);
2924 }
2926 do_scroll_view(view, lines);
2927 }
2929 /* Cursor moving */
2930 static void
2931 move_view(struct view *view, enum request request)
2932 {
2933 int scroll_steps = 0;
2934 int steps;
2936 switch (request) {
2937 case REQ_MOVE_FIRST_LINE:
2938 steps = -view->lineno;
2939 break;
2941 case REQ_MOVE_LAST_LINE:
2942 steps = view->lines - view->lineno - 1;
2943 break;
2945 case REQ_MOVE_PAGE_UP:
2946 steps = view->height > view->lineno
2947 ? -view->lineno : -view->height;
2948 break;
2950 case REQ_MOVE_PAGE_DOWN:
2951 steps = view->lineno + view->height >= view->lines
2952 ? view->lines - view->lineno - 1 : view->height;
2953 break;
2955 case REQ_MOVE_UP:
2956 steps = -1;
2957 break;
2959 case REQ_MOVE_DOWN:
2960 steps = 1;
2961 break;
2963 default:
2964 die("request %d not handled in switch", request);
2965 }
2967 if (steps <= 0 && view->lineno == 0) {
2968 report("Cannot move beyond the first line");
2969 return;
2971 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2972 report("Cannot move beyond the last line");
2973 return;
2974 }
2976 /* Move the current line */
2977 view->lineno += steps;
2978 assert(0 <= view->lineno && view->lineno < view->lines);
2980 /* Check whether the view needs to be scrolled */
2981 if (view->lineno < view->offset ||
2982 view->lineno >= view->offset + view->height) {
2983 scroll_steps = steps;
2984 if (steps < 0 && -steps > view->offset) {
2985 scroll_steps = -view->offset;
2987 } else if (steps > 0) {
2988 if (view->lineno == view->lines - 1 &&
2989 view->lines > view->height) {
2990 scroll_steps = view->lines - view->offset - 1;
2991 if (scroll_steps >= view->height)
2992 scroll_steps -= view->height - 1;
2993 }
2994 }
2995 }
2997 if (!view_is_displayed(view)) {
2998 view->offset += scroll_steps;
2999 assert(0 <= view->offset && view->offset < view->lines);
3000 view->ops->select(view, &view->line[view->lineno]);
3001 return;
3002 }
3004 /* Repaint the old "current" line if we be scrolling */
3005 if (ABS(steps) < view->height)
3006 draw_view_line(view, view->lineno - steps - view->offset);
3008 if (scroll_steps) {
3009 do_scroll_view(view, scroll_steps);
3010 return;
3011 }
3013 /* Draw the current line */
3014 draw_view_line(view, view->lineno - view->offset);
3016 wnoutrefresh(view->win);
3017 report("");
3018 }
3021 /*
3022 * Searching
3023 */
3025 static void search_view(struct view *view, enum request request);
3027 static bool
3028 grep_text(struct view *view, const char *text[])
3029 {
3030 regmatch_t pmatch;
3031 size_t i;
3033 for (i = 0; text[i]; i++)
3034 if (*text[i] &&
3035 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3036 return TRUE;
3037 return FALSE;
3038 }
3040 static void
3041 select_view_line(struct view *view, unsigned long lineno)
3042 {
3043 unsigned long old_lineno = view->lineno;
3044 unsigned long old_offset = view->offset;
3046 if (goto_view_line(view, view->offset, lineno)) {
3047 if (view_is_displayed(view)) {
3048 if (old_offset != view->offset) {
3049 redraw_view(view);
3050 } else {
3051 draw_view_line(view, old_lineno - view->offset);
3052 draw_view_line(view, view->lineno - view->offset);
3053 wnoutrefresh(view->win);
3054 }
3055 } else {
3056 view->ops->select(view, &view->line[view->lineno]);
3057 }
3058 }
3059 }
3061 static void
3062 find_next(struct view *view, enum request request)
3063 {
3064 unsigned long lineno = view->lineno;
3065 int direction;
3067 if (!*view->grep) {
3068 if (!*opt_search)
3069 report("No previous search");
3070 else
3071 search_view(view, request);
3072 return;
3073 }
3075 switch (request) {
3076 case REQ_SEARCH:
3077 case REQ_FIND_NEXT:
3078 direction = 1;
3079 break;
3081 case REQ_SEARCH_BACK:
3082 case REQ_FIND_PREV:
3083 direction = -1;
3084 break;
3086 default:
3087 return;
3088 }
3090 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3091 lineno += direction;
3093 /* Note, lineno is unsigned long so will wrap around in which case it
3094 * will become bigger than view->lines. */
3095 for (; lineno < view->lines; lineno += direction) {
3096 if (view->ops->grep(view, &view->line[lineno])) {
3097 select_view_line(view, lineno);
3098 report("Line %ld matches '%s'", lineno + 1, view->grep);
3099 return;
3100 }
3101 }
3103 report("No match found for '%s'", view->grep);
3104 }
3106 static void
3107 search_view(struct view *view, enum request request)
3108 {
3109 int regex_err;
3111 if (view->regex) {
3112 regfree(view->regex);
3113 *view->grep = 0;
3114 } else {
3115 view->regex = calloc(1, sizeof(*view->regex));
3116 if (!view->regex)
3117 return;
3118 }
3120 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3121 if (regex_err != 0) {
3122 char buf[SIZEOF_STR] = "unknown error";
3124 regerror(regex_err, view->regex, buf, sizeof(buf));
3125 report("Search failed: %s", buf);
3126 return;
3127 }
3129 string_copy(view->grep, opt_search);
3131 find_next(view, request);
3132 }
3134 /*
3135 * Incremental updating
3136 */
3138 static void
3139 reset_view(struct view *view)
3140 {
3141 int i;
3143 for (i = 0; i < view->lines; i++)
3144 free(view->line[i].data);
3145 free(view->line);
3147 view->p_offset = view->offset;
3148 view->p_yoffset = view->yoffset;
3149 view->p_lineno = view->lineno;
3151 view->line = NULL;
3152 view->offset = 0;
3153 view->yoffset = 0;
3154 view->lines = 0;
3155 view->lineno = 0;
3156 view->vid[0] = 0;
3157 view->update_secs = 0;
3158 }
3160 static const char *
3161 format_arg(const char *name)
3162 {
3163 static struct {
3164 const char *name;
3165 size_t namelen;
3166 const char *value;
3167 const char *value_if_empty;
3168 } vars[] = {
3169 #define FORMAT_VAR(name, value, value_if_empty) \
3170 { name, STRING_SIZE(name), value, value_if_empty }
3171 FORMAT_VAR("%(directory)", opt_path, ""),
3172 FORMAT_VAR("%(file)", opt_file, ""),
3173 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3174 FORMAT_VAR("%(head)", ref_head, ""),
3175 FORMAT_VAR("%(commit)", ref_commit, ""),
3176 FORMAT_VAR("%(blob)", ref_blob, ""),
3177 FORMAT_VAR("%(branch)", ref_branch, ""),
3178 };
3179 int i;
3181 for (i = 0; i < ARRAY_SIZE(vars); i++)
3182 if (!strncmp(name, vars[i].name, vars[i].namelen))
3183 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3185 report("Unknown replacement: `%s`", name);
3186 return NULL;
3187 }
3189 static bool
3190 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3191 {
3192 char buf[SIZEOF_STR];
3193 int argc;
3194 bool noreplace = flags == FORMAT_NONE;
3196 argv_free(dst_argv);
3198 for (argc = 0; src_argv[argc]; argc++) {
3199 const char *arg = src_argv[argc];
3200 size_t bufpos = 0;
3202 while (arg) {
3203 char *next = strstr(arg, "%(");
3204 int len = next - arg;
3205 const char *value;
3207 if (!next || noreplace) {
3208 len = strlen(arg);
3209 value = "";
3211 } else {
3212 value = format_arg(next);
3214 if (!value) {
3215 return FALSE;
3216 }
3217 }
3219 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3220 return FALSE;
3222 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3223 }
3225 dst_argv[argc] = strdup(buf);
3226 if (!dst_argv[argc])
3227 break;
3228 }
3230 dst_argv[argc] = NULL;
3232 return src_argv[argc] == NULL;
3233 }
3235 static bool
3236 restore_view_position(struct view *view)
3237 {
3238 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3239 return FALSE;
3241 /* Changing the view position cancels the restoring. */
3242 /* FIXME: Changing back to the first line is not detected. */
3243 if (view->offset != 0 || view->lineno != 0) {
3244 view->p_restore = FALSE;
3245 return FALSE;
3246 }
3248 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3249 view_is_displayed(view))
3250 werase(view->win);
3252 view->yoffset = view->p_yoffset;
3253 view->p_restore = FALSE;
3255 return TRUE;
3256 }
3258 static void
3259 end_update(struct view *view, bool force)
3260 {
3261 if (!view->pipe)
3262 return;
3263 while (!view->ops->read(view, NULL))
3264 if (!force)
3265 return;
3266 if (force)
3267 io_kill(view->pipe);
3268 io_done(view->pipe);
3269 view->pipe = NULL;
3270 }
3272 static void
3273 setup_update(struct view *view, const char *vid)
3274 {
3275 reset_view(view);
3276 string_copy_rev(view->vid, vid);
3277 view->pipe = &view->io;
3278 view->start_time = time(NULL);
3279 }
3281 static bool
3282 prepare_update(struct view *view, const char *argv[], const char *dir)
3283 {
3284 if (view->pipe)
3285 end_update(view, TRUE);
3286 return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3287 }
3289 static bool
3290 prepare_update_file(struct view *view, const char *name)
3291 {
3292 if (view->pipe)
3293 end_update(view, TRUE);
3294 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3295 }
3297 static bool
3298 begin_update(struct view *view, bool refresh)
3299 {
3300 if (view->pipe)
3301 end_update(view, TRUE);
3303 if (!refresh) {
3304 if (view->ops->prepare) {
3305 if (!view->ops->prepare(view))
3306 return FALSE;
3307 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3308 return FALSE;
3309 }
3311 /* Put the current ref_* value to the view title ref
3312 * member. This is needed by the blob view. Most other
3313 * views sets it automatically after loading because the
3314 * first line is a commit line. */
3315 string_copy_rev(view->ref, view->id);
3316 }
3318 if (!io_start(&view->io))
3319 return FALSE;
3321 setup_update(view, view->id);
3323 return TRUE;
3324 }
3326 static bool
3327 update_view(struct view *view)
3328 {
3329 char out_buffer[BUFSIZ * 2];
3330 char *line;
3331 /* Clear the view and redraw everything since the tree sorting
3332 * might have rearranged things. */
3333 bool redraw = view->lines == 0;
3334 bool can_read = TRUE;
3336 if (!view->pipe)
3337 return TRUE;
3339 if (!io_can_read(view->pipe)) {
3340 if (view->lines == 0 && view_is_displayed(view)) {
3341 time_t secs = time(NULL) - view->start_time;
3343 if (secs > 1 && secs > view->update_secs) {
3344 if (view->update_secs == 0)
3345 redraw_view(view);
3346 update_view_title(view);
3347 view->update_secs = secs;
3348 }
3349 }
3350 return TRUE;
3351 }
3353 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3354 if (opt_iconv_in != ICONV_NONE) {
3355 ICONV_CONST char *inbuf = line;
3356 size_t inlen = strlen(line) + 1;
3358 char *outbuf = out_buffer;
3359 size_t outlen = sizeof(out_buffer);
3361 size_t ret;
3363 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3364 if (ret != (size_t) -1)
3365 line = out_buffer;
3366 }
3368 if (!view->ops->read(view, line)) {
3369 report("Allocation failure");
3370 end_update(view, TRUE);
3371 return FALSE;
3372 }
3373 }
3375 {
3376 unsigned long lines = view->lines;
3377 int digits;
3379 for (digits = 0; lines; digits++)
3380 lines /= 10;
3382 /* Keep the displayed view in sync with line number scaling. */
3383 if (digits != view->digits) {
3384 view->digits = digits;
3385 if (opt_line_number || view->type == VIEW_BLAME)
3386 redraw = TRUE;
3387 }
3388 }
3390 if (io_error(view->pipe)) {
3391 report("Failed to read: %s", io_strerror(view->pipe));
3392 end_update(view, TRUE);
3394 } else if (io_eof(view->pipe)) {
3395 if (view_is_displayed(view))
3396 report("");
3397 end_update(view, FALSE);
3398 }
3400 if (restore_view_position(view))
3401 redraw = TRUE;
3403 if (!view_is_displayed(view))
3404 return TRUE;
3406 if (redraw)
3407 redraw_view_from(view, 0);
3408 else
3409 redraw_view_dirty(view);
3411 /* Update the title _after_ the redraw so that if the redraw picks up a
3412 * commit reference in view->ref it'll be available here. */
3413 update_view_title(view);
3414 return TRUE;
3415 }
3417 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3419 static struct line *
3420 add_line_data(struct view *view, void *data, enum line_type type)
3421 {
3422 struct line *line;
3424 if (!realloc_lines(&view->line, view->lines, 1))
3425 return NULL;
3427 line = &view->line[view->lines++];
3428 memset(line, 0, sizeof(*line));
3429 line->type = type;
3430 line->data = data;
3431 line->dirty = 1;
3433 return line;
3434 }
3436 static struct line *
3437 add_line_text(struct view *view, const char *text, enum line_type type)
3438 {
3439 char *data = text ? strdup(text) : NULL;
3441 return data ? add_line_data(view, data, type) : NULL;
3442 }
3444 static struct line *
3445 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3446 {
3447 char buf[SIZEOF_STR];
3448 va_list args;
3450 va_start(args, fmt);
3451 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3452 buf[0] = 0;
3453 va_end(args);
3455 return buf[0] ? add_line_text(view, buf, type) : NULL;
3456 }
3458 /*
3459 * View opening
3460 */
3462 enum open_flags {
3463 OPEN_DEFAULT = 0, /* Use default view switching. */
3464 OPEN_SPLIT = 1, /* Split current view. */
3465 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3466 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3467 OPEN_PREPARED = 32, /* Open already prepared command. */
3468 };
3470 static void
3471 open_view(struct view *prev, enum request request, enum open_flags flags)
3472 {
3473 bool split = !!(flags & OPEN_SPLIT);
3474 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3475 bool nomaximize = !!(flags & OPEN_REFRESH);
3476 struct view *view = VIEW(request);
3477 int nviews = displayed_views();
3478 struct view *base_view = display[0];
3480 if (view == prev && nviews == 1 && !reload) {
3481 report("Already in %s view", view->name);
3482 return;
3483 }
3485 if (view->git_dir && !opt_git_dir[0]) {
3486 report("The %s view is disabled in pager view", view->name);
3487 return;
3488 }
3490 if (split) {
3491 display[1] = view;
3492 current_view = 1;
3493 view->parent = prev;
3494 } else if (!nomaximize) {
3495 /* Maximize the current view. */
3496 memset(display, 0, sizeof(display));
3497 current_view = 0;
3498 display[current_view] = view;
3499 }
3501 /* No prev signals that this is the first loaded view. */
3502 if (prev && view != prev) {
3503 view->prev = prev;
3504 }
3506 /* Resize the view when switching between split- and full-screen,
3507 * or when switching between two different full-screen views. */
3508 if (nviews != displayed_views() ||
3509 (nviews == 1 && base_view != display[0]))
3510 resize_display();
3512 if (view->ops->open) {
3513 if (view->pipe)
3514 end_update(view, TRUE);
3515 if (!view->ops->open(view)) {
3516 report("Failed to load %s view", view->name);
3517 return;
3518 }
3519 restore_view_position(view);
3521 } else if ((reload || strcmp(view->vid, view->id)) &&
3522 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3523 report("Failed to load %s view", view->name);
3524 return;
3525 }
3527 if (split && prev->lineno - prev->offset >= prev->height) {
3528 /* Take the title line into account. */
3529 int lines = prev->lineno - prev->offset - prev->height + 1;
3531 /* Scroll the view that was split if the current line is
3532 * outside the new limited view. */
3533 do_scroll_view(prev, lines);
3534 }
3536 if (prev && view != prev && split && view_is_displayed(prev)) {
3537 /* "Blur" the previous view. */
3538 update_view_title(prev);
3539 }
3541 if (view->pipe && view->lines == 0) {
3542 /* Clear the old view and let the incremental updating refill
3543 * the screen. */
3544 werase(view->win);
3545 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3546 report("");
3547 } else if (view_is_displayed(view)) {
3548 redraw_view(view);
3549 report("");
3550 }
3551 }
3553 static void
3554 open_external_viewer(const char *argv[], const char *dir)
3555 {
3556 def_prog_mode(); /* save current tty modes */
3557 endwin(); /* restore original tty modes */
3558 io_run_fg(argv, dir);
3559 fprintf(stderr, "Press Enter to continue");
3560 getc(opt_tty);
3561 reset_prog_mode();
3562 redraw_display(TRUE);
3563 }
3565 static void
3566 open_mergetool(const char *file)
3567 {
3568 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3570 open_external_viewer(mergetool_argv, opt_cdup);
3571 }
3573 static void
3574 open_editor(const char *file)
3575 {
3576 const char *editor_argv[] = { "vi", file, NULL };
3577 const char *editor;
3579 editor = getenv("GIT_EDITOR");
3580 if (!editor && *opt_editor)
3581 editor = opt_editor;
3582 if (!editor)
3583 editor = getenv("VISUAL");
3584 if (!editor)
3585 editor = getenv("EDITOR");
3586 if (!editor)
3587 editor = "vi";
3589 editor_argv[0] = editor;
3590 open_external_viewer(editor_argv, opt_cdup);
3591 }
3593 static void
3594 open_run_request(enum request request)
3595 {
3596 struct run_request *req = get_run_request(request);
3597 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3599 if (!req) {
3600 report("Unknown run request");
3601 return;
3602 }
3604 if (format_argv(argv, req->argv, FORMAT_ALL))
3605 open_external_viewer(argv, NULL);
3606 argv_free(argv);
3607 }
3609 /*
3610 * User request switch noodle
3611 */
3613 static int
3614 view_driver(struct view *view, enum request request)
3615 {
3616 int i;
3618 if (request == REQ_NONE)
3619 return TRUE;
3621 if (request > REQ_NONE) {
3622 open_run_request(request);
3623 view_request(view, REQ_REFRESH);
3624 return TRUE;
3625 }
3627 request = view_request(view, request);
3628 if (request == REQ_NONE)
3629 return TRUE;
3631 switch (request) {
3632 case REQ_MOVE_UP:
3633 case REQ_MOVE_DOWN:
3634 case REQ_MOVE_PAGE_UP:
3635 case REQ_MOVE_PAGE_DOWN:
3636 case REQ_MOVE_FIRST_LINE:
3637 case REQ_MOVE_LAST_LINE:
3638 move_view(view, request);
3639 break;
3641 case REQ_SCROLL_LEFT:
3642 case REQ_SCROLL_RIGHT:
3643 case REQ_SCROLL_LINE_DOWN:
3644 case REQ_SCROLL_LINE_UP:
3645 case REQ_SCROLL_PAGE_DOWN:
3646 case REQ_SCROLL_PAGE_UP:
3647 scroll_view(view, request);
3648 break;
3650 case REQ_VIEW_BLAME:
3651 if (!opt_file[0]) {
3652 report("No file chosen, press %s to open tree view",
3653 get_key(view->keymap, REQ_VIEW_TREE));
3654 break;
3655 }
3656 open_view(view, request, OPEN_DEFAULT);
3657 break;
3659 case REQ_VIEW_BLOB:
3660 if (!ref_blob[0]) {
3661 report("No file chosen, press %s to open tree view",
3662 get_key(view->keymap, REQ_VIEW_TREE));
3663 break;
3664 }
3665 open_view(view, request, OPEN_DEFAULT);
3666 break;
3668 case REQ_VIEW_PAGER:
3669 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3670 report("No pager content, press %s to run command from prompt",
3671 get_key(view->keymap, REQ_PROMPT));
3672 break;
3673 }
3674 open_view(view, request, OPEN_DEFAULT);
3675 break;
3677 case REQ_VIEW_STAGE:
3678 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3679 report("No stage content, press %s to open the status view and choose file",
3680 get_key(view->keymap, REQ_VIEW_STATUS));
3681 break;
3682 }
3683 open_view(view, request, OPEN_DEFAULT);
3684 break;
3686 case REQ_VIEW_STATUS:
3687 if (opt_is_inside_work_tree == FALSE) {
3688 report("The status view requires a working tree");
3689 break;
3690 }
3691 open_view(view, request, OPEN_DEFAULT);
3692 break;
3694 case REQ_VIEW_MAIN:
3695 case REQ_VIEW_DIFF:
3696 case REQ_VIEW_LOG:
3697 case REQ_VIEW_TREE:
3698 case REQ_VIEW_HELP:
3699 case REQ_VIEW_BRANCH:
3700 open_view(view, request, OPEN_DEFAULT);
3701 break;
3703 case REQ_NEXT:
3704 case REQ_PREVIOUS:
3705 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3707 if (view->parent) {
3708 int line;
3710 view = view->parent;
3711 line = view->lineno;
3712 move_view(view, request);
3713 if (view_is_displayed(view))
3714 update_view_title(view);
3715 if (line != view->lineno)
3716 view_request(view, REQ_ENTER);
3717 } else {
3718 move_view(view, request);
3719 }
3720 break;
3722 case REQ_VIEW_NEXT:
3723 {
3724 int nviews = displayed_views();
3725 int next_view = (current_view + 1) % nviews;
3727 if (next_view == current_view) {
3728 report("Only one view is displayed");
3729 break;
3730 }
3732 current_view = next_view;
3733 /* Blur out the title of the previous view. */
3734 update_view_title(view);
3735 report("");
3736 break;
3737 }
3738 case REQ_REFRESH:
3739 report("Refreshing is not yet supported for the %s view", view->name);
3740 break;
3742 case REQ_MAXIMIZE:
3743 if (displayed_views() == 2)
3744 maximize_view(view);
3745 break;
3747 case REQ_OPTIONS:
3748 open_option_menu();
3749 break;
3751 case REQ_TOGGLE_LINENO:
3752 toggle_view_option(&opt_line_number, "line numbers");
3753 break;
3755 case REQ_TOGGLE_DATE:
3756 toggle_date();
3757 break;
3759 case REQ_TOGGLE_AUTHOR:
3760 toggle_author();
3761 break;
3763 case REQ_TOGGLE_REV_GRAPH:
3764 toggle_view_option(&opt_rev_graph, "revision graph display");
3765 break;
3767 case REQ_TOGGLE_REFS:
3768 toggle_view_option(&opt_show_refs, "reference display");
3769 break;
3771 case REQ_TOGGLE_SORT_FIELD:
3772 case REQ_TOGGLE_SORT_ORDER:
3773 report("Sorting is not yet supported for the %s view", view->name);
3774 break;
3776 case REQ_SEARCH:
3777 case REQ_SEARCH_BACK:
3778 search_view(view, request);
3779 break;
3781 case REQ_FIND_NEXT:
3782 case REQ_FIND_PREV:
3783 find_next(view, request);
3784 break;
3786 case REQ_STOP_LOADING:
3787 foreach_view(view, i) {
3788 if (view->pipe)
3789 report("Stopped loading the %s view", view->name),
3790 end_update(view, TRUE);
3791 }
3792 break;
3794 case REQ_SHOW_VERSION:
3795 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3796 return TRUE;
3798 case REQ_SCREEN_REDRAW:
3799 redraw_display(TRUE);
3800 break;
3802 case REQ_EDIT:
3803 report("Nothing to edit");
3804 break;
3806 case REQ_ENTER:
3807 report("Nothing to enter");
3808 break;
3810 case REQ_VIEW_CLOSE:
3811 /* XXX: Mark closed views by letting view->prev point to the
3812 * view itself. Parents to closed view should never be
3813 * followed. */
3814 if (view->prev && view->prev != view) {
3815 maximize_view(view->prev);
3816 view->prev = view;
3817 break;
3818 }
3819 /* Fall-through */
3820 case REQ_QUIT:
3821 return FALSE;
3823 default:
3824 report("Unknown key, press %s for help",
3825 get_key(view->keymap, REQ_VIEW_HELP));
3826 return TRUE;
3827 }
3829 return TRUE;
3830 }
3833 /*
3834 * View backend utilities
3835 */
3837 enum sort_field {
3838 ORDERBY_NAME,
3839 ORDERBY_DATE,
3840 ORDERBY_AUTHOR,
3841 };
3843 struct sort_state {
3844 const enum sort_field *fields;
3845 size_t size, current;
3846 bool reverse;
3847 };
3849 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3850 #define get_sort_field(state) ((state).fields[(state).current])
3851 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3853 static void
3854 sort_view(struct view *view, enum request request, struct sort_state *state,
3855 int (*compare)(const void *, const void *))
3856 {
3857 switch (request) {
3858 case REQ_TOGGLE_SORT_FIELD:
3859 state->current = (state->current + 1) % state->size;
3860 break;
3862 case REQ_TOGGLE_SORT_ORDER:
3863 state->reverse = !state->reverse;
3864 break;
3865 default:
3866 die("Not a sort request");
3867 }
3869 qsort(view->line, view->lines, sizeof(*view->line), compare);
3870 redraw_view(view);
3871 }
3873 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3875 /* Small author cache to reduce memory consumption. It uses binary
3876 * search to lookup or find place to position new entries. No entries
3877 * are ever freed. */
3878 static const char *
3879 get_author(const char *name)
3880 {
3881 static const char **authors;
3882 static size_t authors_size;
3883 int from = 0, to = authors_size - 1;
3885 while (from <= to) {
3886 size_t pos = (to + from) / 2;
3887 int cmp = strcmp(name, authors[pos]);
3889 if (!cmp)
3890 return authors[pos];
3892 if (cmp < 0)
3893 to = pos - 1;
3894 else
3895 from = pos + 1;
3896 }
3898 if (!realloc_authors(&authors, authors_size, 1))
3899 return NULL;
3900 name = strdup(name);
3901 if (!name)
3902 return NULL;
3904 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3905 authors[from] = name;
3906 authors_size++;
3908 return name;
3909 }
3911 static void
3912 parse_timesec(struct time *time, const char *sec)
3913 {
3914 time->sec = (time_t) atol(sec);
3915 }
3917 static void
3918 parse_timezone(struct time *time, const char *zone)
3919 {
3920 long tz;
3922 tz = ('0' - zone[1]) * 60 * 60 * 10;
3923 tz += ('0' - zone[2]) * 60 * 60;
3924 tz += ('0' - zone[3]) * 60 * 10;
3925 tz += ('0' - zone[4]) * 60;
3927 if (zone[0] == '-')
3928 tz = -tz;
3930 time->tz = tz;
3931 time->sec -= tz;
3932 }
3934 /* Parse author lines where the name may be empty:
3935 * author <email@address.tld> 1138474660 +0100
3936 */
3937 static void
3938 parse_author_line(char *ident, const char **author, struct time *time)
3939 {
3940 char *nameend = strchr(ident, '<');
3941 char *emailend = strchr(ident, '>');
3943 if (nameend && emailend)
3944 *nameend = *emailend = 0;
3945 ident = chomp_string(ident);
3946 if (!*ident) {
3947 if (nameend)
3948 ident = chomp_string(nameend + 1);
3949 if (!*ident)
3950 ident = "Unknown";
3951 }
3953 *author = get_author(ident);
3955 /* Parse epoch and timezone */
3956 if (emailend && emailend[1] == ' ') {
3957 char *secs = emailend + 2;
3958 char *zone = strchr(secs, ' ');
3960 parse_timesec(time, secs);
3962 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3963 parse_timezone(time, zone + 1);
3964 }
3965 }
3967 static bool
3968 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3969 {
3970 char rev[SIZEOF_REV];
3971 const char *revlist_argv[] = {
3972 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3973 };
3974 struct menu_item *items;
3975 char text[SIZEOF_STR];
3976 bool ok = TRUE;
3977 int i;
3979 items = calloc(*parents + 1, sizeof(*items));
3980 if (!items)
3981 return FALSE;
3983 for (i = 0; i < *parents; i++) {
3984 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3985 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3986 !(items[i].text = strdup(text))) {
3987 ok = FALSE;
3988 break;
3989 }
3990 }
3992 if (ok) {
3993 *parents = 0;
3994 ok = prompt_menu("Select parent", items, parents);
3995 }
3996 for (i = 0; items[i].text; i++)
3997 free((char *) items[i].text);
3998 free(items);
3999 return ok;
4000 }
4002 static bool
4003 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4004 {
4005 char buf[SIZEOF_STR * 4];
4006 const char *revlist_argv[] = {
4007 "git", "log", "--no-color", "-1",
4008 "--pretty=format:%P", id, "--", path, NULL
4009 };
4010 int parents;
4012 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4013 (parents = strlen(buf) / 40) < 0) {
4014 report("Failed to get parent information");
4015 return FALSE;
4017 } else if (parents == 0) {
4018 if (path)
4019 report("Path '%s' does not exist in the parent", path);
4020 else
4021 report("The selected commit has no parents");
4022 return FALSE;
4023 }
4025 if (parents == 1)
4026 parents = 0;
4027 else if (!open_commit_parent_menu(buf, &parents))
4028 return FALSE;
4030 string_copy_rev(rev, &buf[41 * parents]);
4031 return TRUE;
4032 }
4034 /*
4035 * Pager backend
4036 */
4038 static bool
4039 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4040 {
4041 char text[SIZEOF_STR];
4043 if (opt_line_number && draw_lineno(view, lineno))
4044 return TRUE;
4046 string_expand(text, sizeof(text), line->data, opt_tab_size);
4047 draw_text(view, line->type, text, TRUE);
4048 return TRUE;
4049 }
4051 static bool
4052 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4053 {
4054 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4055 char ref[SIZEOF_STR];
4057 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4058 return TRUE;
4060 /* This is the only fatal call, since it can "corrupt" the buffer. */
4061 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4062 return FALSE;
4064 return TRUE;
4065 }
4067 static void
4068 add_pager_refs(struct view *view, struct line *line)
4069 {
4070 char buf[SIZEOF_STR];
4071 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4072 struct ref_list *list;
4073 size_t bufpos = 0, i;
4074 const char *sep = "Refs: ";
4075 bool is_tag = FALSE;
4077 assert(line->type == LINE_COMMIT);
4079 list = get_ref_list(commit_id);
4080 if (!list) {
4081 if (view->type == VIEW_DIFF)
4082 goto try_add_describe_ref;
4083 return;
4084 }
4086 for (i = 0; i < list->size; i++) {
4087 struct ref *ref = list->refs[i];
4088 const char *fmt = ref->tag ? "%s[%s]" :
4089 ref->remote ? "%s<%s>" : "%s%s";
4091 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4092 return;
4093 sep = ", ";
4094 if (ref->tag)
4095 is_tag = TRUE;
4096 }
4098 if (!is_tag && view->type == VIEW_DIFF) {
4099 try_add_describe_ref:
4100 /* Add <tag>-g<commit_id> "fake" reference. */
4101 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4102 return;
4103 }
4105 if (bufpos == 0)
4106 return;
4108 add_line_text(view, buf, LINE_PP_REFS);
4109 }
4111 static bool
4112 pager_read(struct view *view, char *data)
4113 {
4114 struct line *line;
4116 if (!data)
4117 return TRUE;
4119 line = add_line_text(view, data, get_line_type(data));
4120 if (!line)
4121 return FALSE;
4123 if (line->type == LINE_COMMIT &&
4124 (view->type == VIEW_DIFF ||
4125 view->type == VIEW_LOG))
4126 add_pager_refs(view, line);
4128 return TRUE;
4129 }
4131 static enum request
4132 pager_request(struct view *view, enum request request, struct line *line)
4133 {
4134 int split = 0;
4136 if (request != REQ_ENTER)
4137 return request;
4139 if (line->type == LINE_COMMIT &&
4140 (view->type == VIEW_LOG ||
4141 view->type == VIEW_PAGER)) {
4142 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4143 split = 1;
4144 }
4146 /* Always scroll the view even if it was split. That way
4147 * you can use Enter to scroll through the log view and
4148 * split open each commit diff. */
4149 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4151 /* FIXME: A minor workaround. Scrolling the view will call report("")
4152 * but if we are scrolling a non-current view this won't properly
4153 * update the view title. */
4154 if (split)
4155 update_view_title(view);
4157 return REQ_NONE;
4158 }
4160 static bool
4161 pager_grep(struct view *view, struct line *line)
4162 {
4163 const char *text[] = { line->data, NULL };
4165 return grep_text(view, text);
4166 }
4168 static void
4169 pager_select(struct view *view, struct line *line)
4170 {
4171 if (line->type == LINE_COMMIT) {
4172 char *text = (char *)line->data + STRING_SIZE("commit ");
4174 if (view->type != VIEW_PAGER)
4175 string_copy_rev(view->ref, text);
4176 string_copy_rev(ref_commit, text);
4177 }
4178 }
4180 static struct view_ops pager_ops = {
4181 "line",
4182 NULL,
4183 NULL,
4184 pager_read,
4185 pager_draw,
4186 pager_request,
4187 pager_grep,
4188 pager_select,
4189 };
4191 static const char *log_argv[SIZEOF_ARG] = {
4192 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4193 };
4195 static enum request
4196 log_request(struct view *view, enum request request, struct line *line)
4197 {
4198 switch (request) {
4199 case REQ_REFRESH:
4200 load_refs();
4201 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4202 return REQ_NONE;
4203 default:
4204 return pager_request(view, request, line);
4205 }
4206 }
4208 static struct view_ops log_ops = {
4209 "line",
4210 log_argv,
4211 NULL,
4212 pager_read,
4213 pager_draw,
4214 log_request,
4215 pager_grep,
4216 pager_select,
4217 };
4219 static const char *diff_argv[SIZEOF_ARG] = {
4220 "git", "show", "--pretty=fuller", "--no-color", "--root",
4221 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4222 };
4224 static struct view_ops diff_ops = {
4225 "line",
4226 diff_argv,
4227 NULL,
4228 pager_read,
4229 pager_draw,
4230 pager_request,
4231 pager_grep,
4232 pager_select,
4233 };
4235 /*
4236 * Help backend
4237 */
4239 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4241 static bool
4242 help_open_keymap_title(struct view *view, enum keymap keymap)
4243 {
4244 struct line *line;
4246 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4247 help_keymap_hidden[keymap] ? '+' : '-',
4248 enum_name(keymap_table[keymap]));
4249 if (line)
4250 line->other = keymap;
4252 return help_keymap_hidden[keymap];
4253 }
4255 static void
4256 help_open_keymap(struct view *view, enum keymap keymap)
4257 {
4258 const char *group = NULL;
4259 char buf[SIZEOF_STR];
4260 size_t bufpos;
4261 bool add_title = TRUE;
4262 int i;
4264 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4265 const char *key = NULL;
4267 if (req_info[i].request == REQ_NONE)
4268 continue;
4270 if (!req_info[i].request) {
4271 group = req_info[i].help;
4272 continue;
4273 }
4275 key = get_keys(keymap, req_info[i].request, TRUE);
4276 if (!key || !*key)
4277 continue;
4279 if (add_title && help_open_keymap_title(view, keymap))
4280 return;
4281 add_title = FALSE;
4283 if (group) {
4284 add_line_text(view, group, LINE_HELP_GROUP);
4285 group = NULL;
4286 }
4288 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4289 enum_name(req_info[i]), req_info[i].help);
4290 }
4292 group = "External commands:";
4294 for (i = 0; i < run_requests; i++) {
4295 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4296 const char *key;
4297 int argc;
4299 if (!req || req->keymap != keymap)
4300 continue;
4302 key = get_key_name(req->key);
4303 if (!*key)
4304 key = "(no key defined)";
4306 if (add_title && help_open_keymap_title(view, keymap))
4307 return;
4308 if (group) {
4309 add_line_text(view, group, LINE_HELP_GROUP);
4310 group = NULL;
4311 }
4313 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4314 if (!string_format_from(buf, &bufpos, "%s%s",
4315 argc ? " " : "", req->argv[argc]))
4316 return;
4318 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4319 }
4320 }
4322 static bool
4323 help_open(struct view *view)
4324 {
4325 enum keymap keymap;
4327 reset_view(view);
4328 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4329 add_line_text(view, "", LINE_DEFAULT);
4331 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4332 help_open_keymap(view, keymap);
4334 return TRUE;
4335 }
4337 static enum request
4338 help_request(struct view *view, enum request request, struct line *line)
4339 {
4340 switch (request) {
4341 case REQ_ENTER:
4342 if (line->type == LINE_HELP_KEYMAP) {
4343 help_keymap_hidden[line->other] =
4344 !help_keymap_hidden[line->other];
4345 view->p_restore = TRUE;
4346 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4347 }
4349 return REQ_NONE;
4350 default:
4351 return pager_request(view, request, line);
4352 }
4353 }
4355 static struct view_ops help_ops = {
4356 "line",
4357 NULL,
4358 help_open,
4359 NULL,
4360 pager_draw,
4361 help_request,
4362 pager_grep,
4363 pager_select,
4364 };
4367 /*
4368 * Tree backend
4369 */
4371 struct tree_stack_entry {
4372 struct tree_stack_entry *prev; /* Entry below this in the stack */
4373 unsigned long lineno; /* Line number to restore */
4374 char *name; /* Position of name in opt_path */
4375 };
4377 /* The top of the path stack. */
4378 static struct tree_stack_entry *tree_stack = NULL;
4379 unsigned long tree_lineno = 0;
4381 static void
4382 pop_tree_stack_entry(void)
4383 {
4384 struct tree_stack_entry *entry = tree_stack;
4386 tree_lineno = entry->lineno;
4387 entry->name[0] = 0;
4388 tree_stack = entry->prev;
4389 free(entry);
4390 }
4392 static void
4393 push_tree_stack_entry(const char *name, unsigned long lineno)
4394 {
4395 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4396 size_t pathlen = strlen(opt_path);
4398 if (!entry)
4399 return;
4401 entry->prev = tree_stack;
4402 entry->name = opt_path + pathlen;
4403 tree_stack = entry;
4405 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4406 pop_tree_stack_entry();
4407 return;
4408 }
4410 /* Move the current line to the first tree entry. */
4411 tree_lineno = 1;
4412 entry->lineno = lineno;
4413 }
4415 /* Parse output from git-ls-tree(1):
4416 *
4417 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4418 */
4420 #define SIZEOF_TREE_ATTR \
4421 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4423 #define SIZEOF_TREE_MODE \
4424 STRING_SIZE("100644 ")
4426 #define TREE_ID_OFFSET \
4427 STRING_SIZE("100644 blob ")
4429 struct tree_entry {
4430 char id[SIZEOF_REV];
4431 mode_t mode;
4432 struct time time; /* Date from the author ident. */
4433 const char *author; /* Author of the commit. */
4434 char name[1];
4435 };
4437 static const char *
4438 tree_path(const struct line *line)
4439 {
4440 return ((struct tree_entry *) line->data)->name;
4441 }
4443 static int
4444 tree_compare_entry(const struct line *line1, const struct line *line2)
4445 {
4446 if (line1->type != line2->type)
4447 return line1->type == LINE_TREE_DIR ? -1 : 1;
4448 return strcmp(tree_path(line1), tree_path(line2));
4449 }
4451 static const enum sort_field tree_sort_fields[] = {
4452 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4453 };
4454 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4456 static int
4457 tree_compare(const void *l1, const void *l2)
4458 {
4459 const struct line *line1 = (const struct line *) l1;
4460 const struct line *line2 = (const struct line *) l2;
4461 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4462 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4464 if (line1->type == LINE_TREE_HEAD)
4465 return -1;
4466 if (line2->type == LINE_TREE_HEAD)
4467 return 1;
4469 switch (get_sort_field(tree_sort_state)) {
4470 case ORDERBY_DATE:
4471 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4473 case ORDERBY_AUTHOR:
4474 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4476 case ORDERBY_NAME:
4477 default:
4478 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4479 }
4480 }
4483 static struct line *
4484 tree_entry(struct view *view, enum line_type type, const char *path,
4485 const char *mode, const char *id)
4486 {
4487 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4488 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4490 if (!entry || !line) {
4491 free(entry);
4492 return NULL;
4493 }
4495 strncpy(entry->name, path, strlen(path));
4496 if (mode)
4497 entry->mode = strtoul(mode, NULL, 8);
4498 if (id)
4499 string_copy_rev(entry->id, id);
4501 return line;
4502 }
4504 static bool
4505 tree_read_date(struct view *view, char *text, bool *read_date)
4506 {
4507 static const char *author_name;
4508 static struct time author_time;
4510 if (!text && *read_date) {
4511 *read_date = FALSE;
4512 return TRUE;
4514 } else if (!text) {
4515 char *path = *opt_path ? opt_path : ".";
4516 /* Find next entry to process */
4517 const char *log_file[] = {
4518 "git", "log", "--no-color", "--pretty=raw",
4519 "--cc", "--raw", view->id, "--", path, NULL
4520 };
4521 struct io io = {};
4523 if (!view->lines) {
4524 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4525 report("Tree is empty");
4526 return TRUE;
4527 }
4529 if (!io_run_rd(&io, log_file, opt_cdup)) {
4530 report("Failed to load tree data");
4531 return TRUE;
4532 }
4534 io_done(view->pipe);
4535 view->io = io;
4536 *read_date = TRUE;
4537 return FALSE;
4539 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4540 parse_author_line(text + STRING_SIZE("author "),
4541 &author_name, &author_time);
4543 } else if (*text == ':') {
4544 char *pos;
4545 size_t annotated = 1;
4546 size_t i;
4548 pos = strchr(text, '\t');
4549 if (!pos)
4550 return TRUE;
4551 text = pos + 1;
4552 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4553 text += strlen(opt_path);
4554 pos = strchr(text, '/');
4555 if (pos)
4556 *pos = 0;
4558 for (i = 1; i < view->lines; i++) {
4559 struct line *line = &view->line[i];
4560 struct tree_entry *entry = line->data;
4562 annotated += !!entry->author;
4563 if (entry->author || strcmp(entry->name, text))
4564 continue;
4566 entry->author = author_name;
4567 entry->time = author_time;
4568 line->dirty = 1;
4569 break;
4570 }
4572 if (annotated == view->lines)
4573 io_kill(view->pipe);
4574 }
4575 return TRUE;
4576 }
4578 static bool
4579 tree_read(struct view *view, char *text)
4580 {
4581 static bool read_date = FALSE;
4582 struct tree_entry *data;
4583 struct line *entry, *line;
4584 enum line_type type;
4585 size_t textlen = text ? strlen(text) : 0;
4586 char *path = text + SIZEOF_TREE_ATTR;
4588 if (read_date || !text)
4589 return tree_read_date(view, text, &read_date);
4591 if (textlen <= SIZEOF_TREE_ATTR)
4592 return FALSE;
4593 if (view->lines == 0 &&
4594 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4595 return FALSE;
4597 /* Strip the path part ... */
4598 if (*opt_path) {
4599 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4600 size_t striplen = strlen(opt_path);
4602 if (pathlen > striplen)
4603 memmove(path, path + striplen,
4604 pathlen - striplen + 1);
4606 /* Insert "link" to parent directory. */
4607 if (view->lines == 1 &&
4608 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4609 return FALSE;
4610 }
4612 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4613 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4614 if (!entry)
4615 return FALSE;
4616 data = entry->data;
4618 /* Skip "Directory ..." and ".." line. */
4619 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4620 if (tree_compare_entry(line, entry) <= 0)
4621 continue;
4623 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4625 line->data = data;
4626 line->type = type;
4627 for (; line <= entry; line++)
4628 line->dirty = line->cleareol = 1;
4629 return TRUE;
4630 }
4632 if (tree_lineno > view->lineno) {
4633 view->lineno = tree_lineno;
4634 tree_lineno = 0;
4635 }
4637 return TRUE;
4638 }
4640 static bool
4641 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4642 {
4643 struct tree_entry *entry = line->data;
4645 if (line->type == LINE_TREE_HEAD) {
4646 if (draw_text(view, line->type, "Directory path /", TRUE))
4647 return TRUE;
4648 } else {
4649 if (draw_mode(view, entry->mode))
4650 return TRUE;
4652 if (opt_author && draw_author(view, entry->author))
4653 return TRUE;
4655 if (opt_date && draw_date(view, &entry->time))
4656 return TRUE;
4657 }
4658 if (draw_text(view, line->type, entry->name, TRUE))
4659 return TRUE;
4660 return TRUE;
4661 }
4663 static void
4664 open_blob_editor(const char *id)
4665 {
4666 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4667 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4668 int fd = mkstemp(file);
4670 if (fd == -1)
4671 report("Failed to create temporary file");
4672 else if (!io_run_append(blob_argv, fd))
4673 report("Failed to save blob data to file");
4674 else
4675 open_editor(file);
4676 if (fd != -1)
4677 unlink(file);
4678 }
4680 static enum request
4681 tree_request(struct view *view, enum request request, struct line *line)
4682 {
4683 enum open_flags flags;
4684 struct tree_entry *entry = line->data;
4686 switch (request) {
4687 case REQ_VIEW_BLAME:
4688 if (line->type != LINE_TREE_FILE) {
4689 report("Blame only supported for files");
4690 return REQ_NONE;
4691 }
4693 string_copy(opt_ref, view->vid);
4694 return request;
4696 case REQ_EDIT:
4697 if (line->type != LINE_TREE_FILE) {
4698 report("Edit only supported for files");
4699 } else if (!is_head_commit(view->vid)) {
4700 open_blob_editor(entry->id);
4701 } else {
4702 open_editor(opt_file);
4703 }
4704 return REQ_NONE;
4706 case REQ_TOGGLE_SORT_FIELD:
4707 case REQ_TOGGLE_SORT_ORDER:
4708 sort_view(view, request, &tree_sort_state, tree_compare);
4709 return REQ_NONE;
4711 case REQ_PARENT:
4712 if (!*opt_path) {
4713 /* quit view if at top of tree */
4714 return REQ_VIEW_CLOSE;
4715 }
4716 /* fake 'cd ..' */
4717 line = &view->line[1];
4718 break;
4720 case REQ_ENTER:
4721 break;
4723 default:
4724 return request;
4725 }
4727 /* Cleanup the stack if the tree view is at a different tree. */
4728 while (!*opt_path && tree_stack)
4729 pop_tree_stack_entry();
4731 switch (line->type) {
4732 case LINE_TREE_DIR:
4733 /* Depending on whether it is a subdirectory or parent link
4734 * mangle the path buffer. */
4735 if (line == &view->line[1] && *opt_path) {
4736 pop_tree_stack_entry();
4738 } else {
4739 const char *basename = tree_path(line);
4741 push_tree_stack_entry(basename, view->lineno);
4742 }
4744 /* Trees and subtrees share the same ID, so they are not not
4745 * unique like blobs. */
4746 flags = OPEN_RELOAD;
4747 request = REQ_VIEW_TREE;
4748 break;
4750 case LINE_TREE_FILE:
4751 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4752 request = REQ_VIEW_BLOB;
4753 break;
4755 default:
4756 return REQ_NONE;
4757 }
4759 open_view(view, request, flags);
4760 if (request == REQ_VIEW_TREE)
4761 view->lineno = tree_lineno;
4763 return REQ_NONE;
4764 }
4766 static bool
4767 tree_grep(struct view *view, struct line *line)
4768 {
4769 struct tree_entry *entry = line->data;
4770 const char *text[] = {
4771 entry->name,
4772 opt_author ? entry->author : "",
4773 mkdate(&entry->time, opt_date),
4774 NULL
4775 };
4777 return grep_text(view, text);
4778 }
4780 static void
4781 tree_select(struct view *view, struct line *line)
4782 {
4783 struct tree_entry *entry = line->data;
4785 if (line->type == LINE_TREE_FILE) {
4786 string_copy_rev(ref_blob, entry->id);
4787 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4789 } else if (line->type != LINE_TREE_DIR) {
4790 return;
4791 }
4793 string_copy_rev(view->ref, entry->id);
4794 }
4796 static bool
4797 tree_prepare(struct view *view)
4798 {
4799 if (view->lines == 0 && opt_prefix[0]) {
4800 char *pos = opt_prefix;
4802 while (pos && *pos) {
4803 char *end = strchr(pos, '/');
4805 if (end)
4806 *end = 0;
4807 push_tree_stack_entry(pos, 0);
4808 pos = end;
4809 if (end) {
4810 *end = '/';
4811 pos++;
4812 }
4813 }
4815 } else if (strcmp(view->vid, view->id)) {
4816 opt_path[0] = 0;
4817 }
4819 return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4820 }
4822 static const char *tree_argv[SIZEOF_ARG] = {
4823 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4824 };
4826 static struct view_ops tree_ops = {
4827 "file",
4828 tree_argv,
4829 NULL,
4830 tree_read,
4831 tree_draw,
4832 tree_request,
4833 tree_grep,
4834 tree_select,
4835 tree_prepare,
4836 };
4838 static bool
4839 blob_read(struct view *view, char *line)
4840 {
4841 if (!line)
4842 return TRUE;
4843 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4844 }
4846 static enum request
4847 blob_request(struct view *view, enum request request, struct line *line)
4848 {
4849 switch (request) {
4850 case REQ_EDIT:
4851 open_blob_editor(view->vid);
4852 return REQ_NONE;
4853 default:
4854 return pager_request(view, request, line);
4855 }
4856 }
4858 static const char *blob_argv[SIZEOF_ARG] = {
4859 "git", "cat-file", "blob", "%(blob)", NULL
4860 };
4862 static struct view_ops blob_ops = {
4863 "line",
4864 blob_argv,
4865 NULL,
4866 blob_read,
4867 pager_draw,
4868 blob_request,
4869 pager_grep,
4870 pager_select,
4871 };
4873 /*
4874 * Blame backend
4875 *
4876 * Loading the blame view is a two phase job:
4877 *
4878 * 1. File content is read either using opt_file from the
4879 * filesystem or using git-cat-file.
4880 * 2. Then blame information is incrementally added by
4881 * reading output from git-blame.
4882 */
4884 struct blame_commit {
4885 char id[SIZEOF_REV]; /* SHA1 ID. */
4886 char title[128]; /* First line of the commit message. */
4887 const char *author; /* Author of the commit. */
4888 struct time time; /* Date from the author ident. */
4889 char filename[128]; /* Name of file. */
4890 bool has_previous; /* Was a "previous" line detected. */
4891 };
4893 struct blame {
4894 struct blame_commit *commit;
4895 unsigned long lineno;
4896 char text[1];
4897 };
4899 static bool
4900 blame_open(struct view *view)
4901 {
4902 char path[SIZEOF_STR];
4904 if (!view->prev && *opt_prefix) {
4905 string_copy(path, opt_file);
4906 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4907 return FALSE;
4908 }
4910 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4911 const char *blame_cat_file_argv[] = {
4912 "git", "cat-file", "blob", path, NULL
4913 };
4915 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4916 !io_run_rd(&view->io, blame_cat_file_argv, opt_cdup))
4917 return FALSE;
4918 }
4920 setup_update(view, opt_file);
4921 string_format(view->ref, "%s ...", opt_file);
4923 return TRUE;
4924 }
4926 static struct blame_commit *
4927 get_blame_commit(struct view *view, const char *id)
4928 {
4929 size_t i;
4931 for (i = 0; i < view->lines; i++) {
4932 struct blame *blame = view->line[i].data;
4934 if (!blame->commit)
4935 continue;
4937 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4938 return blame->commit;
4939 }
4941 {
4942 struct blame_commit *commit = calloc(1, sizeof(*commit));
4944 if (commit)
4945 string_ncopy(commit->id, id, SIZEOF_REV);
4946 return commit;
4947 }
4948 }
4950 static bool
4951 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4952 {
4953 const char *pos = *posref;
4955 *posref = NULL;
4956 pos = strchr(pos + 1, ' ');
4957 if (!pos || !isdigit(pos[1]))
4958 return FALSE;
4959 *number = atoi(pos + 1);
4960 if (*number < min || *number > max)
4961 return FALSE;
4963 *posref = pos;
4964 return TRUE;
4965 }
4967 static struct blame_commit *
4968 parse_blame_commit(struct view *view, const char *text, int *blamed)
4969 {
4970 struct blame_commit *commit;
4971 struct blame *blame;
4972 const char *pos = text + SIZEOF_REV - 2;
4973 size_t orig_lineno = 0;
4974 size_t lineno;
4975 size_t group;
4977 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4978 return NULL;
4980 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4981 !parse_number(&pos, &lineno, 1, view->lines) ||
4982 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4983 return NULL;
4985 commit = get_blame_commit(view, text);
4986 if (!commit)
4987 return NULL;
4989 *blamed += group;
4990 while (group--) {
4991 struct line *line = &view->line[lineno + group - 1];
4993 blame = line->data;
4994 blame->commit = commit;
4995 blame->lineno = orig_lineno + group - 1;
4996 line->dirty = 1;
4997 }
4999 return commit;
5000 }
5002 static bool
5003 blame_read_file(struct view *view, const char *line, bool *read_file)
5004 {
5005 if (!line) {
5006 const char *blame_argv[] = {
5007 "git", "blame", "--incremental",
5008 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5009 };
5010 struct io io = {};
5012 if (view->lines == 0 && !view->prev)
5013 die("No blame exist for %s", view->vid);
5015 if (view->lines == 0 || !io_run_rd(&io, blame_argv, opt_cdup)) {
5016 report("Failed to load blame data");
5017 return TRUE;
5018 }
5020 io_done(view->pipe);
5021 view->io = io;
5022 *read_file = FALSE;
5023 return FALSE;
5025 } else {
5026 size_t linelen = strlen(line);
5027 struct blame *blame = malloc(sizeof(*blame) + linelen);
5029 if (!blame)
5030 return FALSE;
5032 blame->commit = NULL;
5033 strncpy(blame->text, line, linelen);
5034 blame->text[linelen] = 0;
5035 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5036 }
5037 }
5039 static bool
5040 match_blame_header(const char *name, char **line)
5041 {
5042 size_t namelen = strlen(name);
5043 bool matched = !strncmp(name, *line, namelen);
5045 if (matched)
5046 *line += namelen;
5048 return matched;
5049 }
5051 static bool
5052 blame_read(struct view *view, char *line)
5053 {
5054 static struct blame_commit *commit = NULL;
5055 static int blamed = 0;
5056 static bool read_file = TRUE;
5058 if (read_file)
5059 return blame_read_file(view, line, &read_file);
5061 if (!line) {
5062 /* Reset all! */
5063 commit = NULL;
5064 blamed = 0;
5065 read_file = TRUE;
5066 string_format(view->ref, "%s", view->vid);
5067 if (view_is_displayed(view)) {
5068 update_view_title(view);
5069 redraw_view_from(view, 0);
5070 }
5071 return TRUE;
5072 }
5074 if (!commit) {
5075 commit = parse_blame_commit(view, line, &blamed);
5076 string_format(view->ref, "%s %2d%%", view->vid,
5077 view->lines ? blamed * 100 / view->lines : 0);
5079 } else if (match_blame_header("author ", &line)) {
5080 commit->author = get_author(line);
5082 } else if (match_blame_header("author-time ", &line)) {
5083 parse_timesec(&commit->time, line);
5085 } else if (match_blame_header("author-tz ", &line)) {
5086 parse_timezone(&commit->time, line);
5088 } else if (match_blame_header("summary ", &line)) {
5089 string_ncopy(commit->title, line, strlen(line));
5091 } else if (match_blame_header("previous ", &line)) {
5092 commit->has_previous = TRUE;
5094 } else if (match_blame_header("filename ", &line)) {
5095 string_ncopy(commit->filename, line, strlen(line));
5096 commit = NULL;
5097 }
5099 return TRUE;
5100 }
5102 static bool
5103 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5104 {
5105 struct blame *blame = line->data;
5106 struct time *time = NULL;
5107 const char *id = NULL, *author = NULL;
5108 char text[SIZEOF_STR];
5110 if (blame->commit && *blame->commit->filename) {
5111 id = blame->commit->id;
5112 author = blame->commit->author;
5113 time = &blame->commit->time;
5114 }
5116 if (opt_date && draw_date(view, time))
5117 return TRUE;
5119 if (opt_author && draw_author(view, author))
5120 return TRUE;
5122 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5123 return TRUE;
5125 if (draw_lineno(view, lineno))
5126 return TRUE;
5128 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5129 draw_text(view, LINE_DEFAULT, text, TRUE);
5130 return TRUE;
5131 }
5133 static bool
5134 check_blame_commit(struct blame *blame, bool check_null_id)
5135 {
5136 if (!blame->commit)
5137 report("Commit data not loaded yet");
5138 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5139 report("No commit exist for the selected line");
5140 else
5141 return TRUE;
5142 return FALSE;
5143 }
5145 static void
5146 setup_blame_parent_line(struct view *view, struct blame *blame)
5147 {
5148 const char *diff_tree_argv[] = {
5149 "git", "diff-tree", "-U0", blame->commit->id,
5150 "--", blame->commit->filename, NULL
5151 };
5152 struct io io = {};
5153 int parent_lineno = -1;
5154 int blamed_lineno = -1;
5155 char *line;
5157 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5158 return;
5160 while ((line = io_get(&io, '\n', TRUE))) {
5161 if (*line == '@') {
5162 char *pos = strchr(line, '+');
5164 parent_lineno = atoi(line + 4);
5165 if (pos)
5166 blamed_lineno = atoi(pos + 1);
5168 } else if (*line == '+' && parent_lineno != -1) {
5169 if (blame->lineno == blamed_lineno - 1 &&
5170 !strcmp(blame->text, line + 1)) {
5171 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5172 break;
5173 }
5174 blamed_lineno++;
5175 }
5176 }
5178 io_done(&io);
5179 }
5181 static enum request
5182 blame_request(struct view *view, enum request request, struct line *line)
5183 {
5184 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5185 struct blame *blame = line->data;
5187 switch (request) {
5188 case REQ_VIEW_BLAME:
5189 if (check_blame_commit(blame, TRUE)) {
5190 string_copy(opt_ref, blame->commit->id);
5191 string_copy(opt_file, blame->commit->filename);
5192 if (blame->lineno)
5193 view->lineno = blame->lineno;
5194 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5195 }
5196 break;
5198 case REQ_PARENT:
5199 if (check_blame_commit(blame, TRUE) &&
5200 select_commit_parent(blame->commit->id, opt_ref,
5201 blame->commit->filename)) {
5202 string_copy(opt_file, blame->commit->filename);
5203 setup_blame_parent_line(view, blame);
5204 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5205 }
5206 break;
5208 case REQ_ENTER:
5209 if (!check_blame_commit(blame, FALSE))
5210 break;
5212 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5213 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5214 break;
5216 if (!strcmp(blame->commit->id, NULL_ID)) {
5217 struct view *diff = VIEW(REQ_VIEW_DIFF);
5218 const char *diff_index_argv[] = {
5219 "git", "diff-index", "--root", "--patch-with-stat",
5220 "-C", "-M", "HEAD", "--", view->vid, NULL
5221 };
5223 if (!blame->commit->has_previous) {
5224 diff_index_argv[1] = "diff";
5225 diff_index_argv[2] = "--no-color";
5226 diff_index_argv[6] = "--";
5227 diff_index_argv[7] = "/dev/null";
5228 }
5230 if (!prepare_update(diff, diff_index_argv, NULL)) {
5231 report("Failed to allocate diff command");
5232 break;
5233 }
5234 flags |= OPEN_PREPARED;
5235 }
5237 open_view(view, REQ_VIEW_DIFF, flags);
5238 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5239 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5240 break;
5242 default:
5243 return request;
5244 }
5246 return REQ_NONE;
5247 }
5249 static bool
5250 blame_grep(struct view *view, struct line *line)
5251 {
5252 struct blame *blame = line->data;
5253 struct blame_commit *commit = blame->commit;
5254 const char *text[] = {
5255 blame->text,
5256 commit ? commit->title : "",
5257 commit ? commit->id : "",
5258 commit && opt_author ? commit->author : "",
5259 commit ? mkdate(&commit->time, opt_date) : "",
5260 NULL
5261 };
5263 return grep_text(view, text);
5264 }
5266 static void
5267 blame_select(struct view *view, struct line *line)
5268 {
5269 struct blame *blame = line->data;
5270 struct blame_commit *commit = blame->commit;
5272 if (!commit)
5273 return;
5275 if (!strcmp(commit->id, NULL_ID))
5276 string_ncopy(ref_commit, "HEAD", 4);
5277 else
5278 string_copy_rev(ref_commit, commit->id);
5279 }
5281 static struct view_ops blame_ops = {
5282 "line",
5283 NULL,
5284 blame_open,
5285 blame_read,
5286 blame_draw,
5287 blame_request,
5288 blame_grep,
5289 blame_select,
5290 };
5292 /*
5293 * Branch backend
5294 */
5296 struct branch {
5297 const char *author; /* Author of the last commit. */
5298 struct time time; /* Date of the last activity. */
5299 const struct ref *ref; /* Name and commit ID information. */
5300 };
5302 static const struct ref branch_all;
5304 static const enum sort_field branch_sort_fields[] = {
5305 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5306 };
5307 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5309 static int
5310 branch_compare(const void *l1, const void *l2)
5311 {
5312 const struct branch *branch1 = ((const struct line *) l1)->data;
5313 const struct branch *branch2 = ((const struct line *) l2)->data;
5315 switch (get_sort_field(branch_sort_state)) {
5316 case ORDERBY_DATE:
5317 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5319 case ORDERBY_AUTHOR:
5320 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5322 case ORDERBY_NAME:
5323 default:
5324 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5325 }
5326 }
5328 static bool
5329 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5330 {
5331 struct branch *branch = line->data;
5332 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5334 if (opt_date && draw_date(view, &branch->time))
5335 return TRUE;
5337 if (opt_author && draw_author(view, branch->author))
5338 return TRUE;
5340 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5341 return TRUE;
5342 }
5344 static enum request
5345 branch_request(struct view *view, enum request request, struct line *line)
5346 {
5347 struct branch *branch = line->data;
5349 switch (request) {
5350 case REQ_REFRESH:
5351 load_refs();
5352 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5353 return REQ_NONE;
5355 case REQ_TOGGLE_SORT_FIELD:
5356 case REQ_TOGGLE_SORT_ORDER:
5357 sort_view(view, request, &branch_sort_state, branch_compare);
5358 return REQ_NONE;
5360 case REQ_ENTER:
5361 if (branch->ref == &branch_all) {
5362 const char *all_branches_argv[] = {
5363 "git", "log", "--no-color", "--pretty=raw", "--parents",
5364 "--topo-order", "--all", NULL
5365 };
5366 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5368 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5369 report("Failed to load view of all branches");
5370 return REQ_NONE;
5371 }
5372 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5373 } else {
5374 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5375 }
5376 return REQ_NONE;
5378 default:
5379 return request;
5380 }
5381 }
5383 static bool
5384 branch_read(struct view *view, char *line)
5385 {
5386 static char id[SIZEOF_REV];
5387 struct branch *reference;
5388 size_t i;
5390 if (!line)
5391 return TRUE;
5393 switch (get_line_type(line)) {
5394 case LINE_COMMIT:
5395 string_copy_rev(id, line + STRING_SIZE("commit "));
5396 return TRUE;
5398 case LINE_AUTHOR:
5399 for (i = 0, reference = NULL; i < view->lines; i++) {
5400 struct branch *branch = view->line[i].data;
5402 if (strcmp(branch->ref->id, id))
5403 continue;
5405 view->line[i].dirty = TRUE;
5406 if (reference) {
5407 branch->author = reference->author;
5408 branch->time = reference->time;
5409 continue;
5410 }
5412 parse_author_line(line + STRING_SIZE("author "),
5413 &branch->author, &branch->time);
5414 reference = branch;
5415 }
5416 return TRUE;
5418 default:
5419 return TRUE;
5420 }
5422 }
5424 static bool
5425 branch_open_visitor(void *data, const struct ref *ref)
5426 {
5427 struct view *view = data;
5428 struct branch *branch;
5430 if (ref->tag || ref->ltag || ref->remote)
5431 return TRUE;
5433 branch = calloc(1, sizeof(*branch));
5434 if (!branch)
5435 return FALSE;
5437 branch->ref = ref;
5438 return !!add_line_data(view, branch, LINE_DEFAULT);
5439 }
5441 static bool
5442 branch_open(struct view *view)
5443 {
5444 const char *branch_log[] = {
5445 "git", "log", "--no-color", "--pretty=raw",
5446 "--simplify-by-decoration", "--all", NULL
5447 };
5449 if (!io_run_rd(&view->io, branch_log, NULL)) {
5450 report("Failed to load branch data");
5451 return TRUE;
5452 }
5454 setup_update(view, view->id);
5455 branch_open_visitor(view, &branch_all);
5456 foreach_ref(branch_open_visitor, view);
5457 view->p_restore = TRUE;
5459 return TRUE;
5460 }
5462 static bool
5463 branch_grep(struct view *view, struct line *line)
5464 {
5465 struct branch *branch = line->data;
5466 const char *text[] = {
5467 branch->ref->name,
5468 branch->author,
5469 NULL
5470 };
5472 return grep_text(view, text);
5473 }
5475 static void
5476 branch_select(struct view *view, struct line *line)
5477 {
5478 struct branch *branch = line->data;
5480 string_copy_rev(view->ref, branch->ref->id);
5481 string_copy_rev(ref_commit, branch->ref->id);
5482 string_copy_rev(ref_head, branch->ref->id);
5483 string_copy_rev(ref_branch, branch->ref->name);
5484 }
5486 static struct view_ops branch_ops = {
5487 "branch",
5488 NULL,
5489 branch_open,
5490 branch_read,
5491 branch_draw,
5492 branch_request,
5493 branch_grep,
5494 branch_select,
5495 };
5497 /*
5498 * Status backend
5499 */
5501 struct status {
5502 char status;
5503 struct {
5504 mode_t mode;
5505 char rev[SIZEOF_REV];
5506 char name[SIZEOF_STR];
5507 } old;
5508 struct {
5509 mode_t mode;
5510 char rev[SIZEOF_REV];
5511 char name[SIZEOF_STR];
5512 } new;
5513 };
5515 static char status_onbranch[SIZEOF_STR];
5516 static struct status stage_status;
5517 static enum line_type stage_line_type;
5518 static size_t stage_chunks;
5519 static int *stage_chunk;
5521 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5523 /* This should work even for the "On branch" line. */
5524 static inline bool
5525 status_has_none(struct view *view, struct line *line)
5526 {
5527 return line < view->line + view->lines && !line[1].data;
5528 }
5530 /* Get fields from the diff line:
5531 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5532 */
5533 static inline bool
5534 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5535 {
5536 const char *old_mode = buf + 1;
5537 const char *new_mode = buf + 8;
5538 const char *old_rev = buf + 15;
5539 const char *new_rev = buf + 56;
5540 const char *status = buf + 97;
5542 if (bufsize < 98 ||
5543 old_mode[-1] != ':' ||
5544 new_mode[-1] != ' ' ||
5545 old_rev[-1] != ' ' ||
5546 new_rev[-1] != ' ' ||
5547 status[-1] != ' ')
5548 return FALSE;
5550 file->status = *status;
5552 string_copy_rev(file->old.rev, old_rev);
5553 string_copy_rev(file->new.rev, new_rev);
5555 file->old.mode = strtoul(old_mode, NULL, 8);
5556 file->new.mode = strtoul(new_mode, NULL, 8);
5558 file->old.name[0] = file->new.name[0] = 0;
5560 return TRUE;
5561 }
5563 static bool
5564 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5565 {
5566 struct status *unmerged = NULL;
5567 char *buf;
5568 struct io io = {};
5570 if (!io_run(&io, argv, opt_cdup, IO_RD))
5571 return FALSE;
5573 add_line_data(view, NULL, type);
5575 while ((buf = io_get(&io, 0, TRUE))) {
5576 struct status *file = unmerged;
5578 if (!file) {
5579 file = calloc(1, sizeof(*file));
5580 if (!file || !add_line_data(view, file, type))
5581 goto error_out;
5582 }
5584 /* Parse diff info part. */
5585 if (status) {
5586 file->status = status;
5587 if (status == 'A')
5588 string_copy(file->old.rev, NULL_ID);
5590 } else if (!file->status || file == unmerged) {
5591 if (!status_get_diff(file, buf, strlen(buf)))
5592 goto error_out;
5594 buf = io_get(&io, 0, TRUE);
5595 if (!buf)
5596 break;
5598 /* Collapse all modified entries that follow an
5599 * associated unmerged entry. */
5600 if (unmerged == file) {
5601 unmerged->status = 'U';
5602 unmerged = NULL;
5603 } else if (file->status == 'U') {
5604 unmerged = file;
5605 }
5606 }
5608 /* Grab the old name for rename/copy. */
5609 if (!*file->old.name &&
5610 (file->status == 'R' || file->status == 'C')) {
5611 string_ncopy(file->old.name, buf, strlen(buf));
5613 buf = io_get(&io, 0, TRUE);
5614 if (!buf)
5615 break;
5616 }
5618 /* git-ls-files just delivers a NUL separated list of
5619 * file names similar to the second half of the
5620 * git-diff-* output. */
5621 string_ncopy(file->new.name, buf, strlen(buf));
5622 if (!*file->old.name)
5623 string_copy(file->old.name, file->new.name);
5624 file = NULL;
5625 }
5627 if (io_error(&io)) {
5628 error_out:
5629 io_done(&io);
5630 return FALSE;
5631 }
5633 if (!view->line[view->lines - 1].data)
5634 add_line_data(view, NULL, LINE_STAT_NONE);
5636 io_done(&io);
5637 return TRUE;
5638 }
5640 /* Don't show unmerged entries in the staged section. */
5641 static const char *status_diff_index_argv[] = {
5642 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5643 "--cached", "-M", "HEAD", NULL
5644 };
5646 static const char *status_diff_files_argv[] = {
5647 "git", "diff-files", "-z", NULL
5648 };
5650 static const char *status_list_other_argv[] = {
5651 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5652 };
5654 static const char *status_list_no_head_argv[] = {
5655 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5656 };
5658 static const char *update_index_argv[] = {
5659 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5660 };
5662 /* Restore the previous line number to stay in the context or select a
5663 * line with something that can be updated. */
5664 static void
5665 status_restore(struct view *view)
5666 {
5667 if (view->p_lineno >= view->lines)
5668 view->p_lineno = view->lines - 1;
5669 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5670 view->p_lineno++;
5671 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5672 view->p_lineno--;
5674 /* If the above fails, always skip the "On branch" line. */
5675 if (view->p_lineno < view->lines)
5676 view->lineno = view->p_lineno;
5677 else
5678 view->lineno = 1;
5680 if (view->lineno < view->offset)
5681 view->offset = view->lineno;
5682 else if (view->offset + view->height <= view->lineno)
5683 view->offset = view->lineno - view->height + 1;
5685 view->p_restore = FALSE;
5686 }
5688 static void
5689 status_update_onbranch(void)
5690 {
5691 static const char *paths[][2] = {
5692 { "rebase-apply/rebasing", "Rebasing" },
5693 { "rebase-apply/applying", "Applying mailbox" },
5694 { "rebase-apply/", "Rebasing mailbox" },
5695 { "rebase-merge/interactive", "Interactive rebase" },
5696 { "rebase-merge/", "Rebase merge" },
5697 { "MERGE_HEAD", "Merging" },
5698 { "BISECT_LOG", "Bisecting" },
5699 { "HEAD", "On branch" },
5700 };
5701 char buf[SIZEOF_STR];
5702 struct stat stat;
5703 int i;
5705 if (is_initial_commit()) {
5706 string_copy(status_onbranch, "Initial commit");
5707 return;
5708 }
5710 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5711 char *head = opt_head;
5713 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5714 lstat(buf, &stat) < 0)
5715 continue;
5717 if (!*opt_head) {
5718 struct io io = {};
5720 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5721 io_read_buf(&io, buf, sizeof(buf))) {
5722 head = buf;
5723 if (!prefixcmp(head, "refs/heads/"))
5724 head += STRING_SIZE("refs/heads/");
5725 }
5726 }
5728 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5729 string_copy(status_onbranch, opt_head);
5730 return;
5731 }
5733 string_copy(status_onbranch, "Not currently on any branch");
5734 }
5736 /* First parse staged info using git-diff-index(1), then parse unstaged
5737 * info using git-diff-files(1), and finally untracked files using
5738 * git-ls-files(1). */
5739 static bool
5740 status_open(struct view *view)
5741 {
5742 reset_view(view);
5744 add_line_data(view, NULL, LINE_STAT_HEAD);
5745 status_update_onbranch();
5747 io_run_bg(update_index_argv);
5749 if (is_initial_commit()) {
5750 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5751 return FALSE;
5752 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5753 return FALSE;
5754 }
5756 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5757 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5758 return FALSE;
5760 /* Restore the exact position or use the specialized restore
5761 * mode? */
5762 if (!view->p_restore)
5763 status_restore(view);
5764 return TRUE;
5765 }
5767 static bool
5768 status_draw(struct view *view, struct line *line, unsigned int lineno)
5769 {
5770 struct status *status = line->data;
5771 enum line_type type;
5772 const char *text;
5774 if (!status) {
5775 switch (line->type) {
5776 case LINE_STAT_STAGED:
5777 type = LINE_STAT_SECTION;
5778 text = "Changes to be committed:";
5779 break;
5781 case LINE_STAT_UNSTAGED:
5782 type = LINE_STAT_SECTION;
5783 text = "Changed but not updated:";
5784 break;
5786 case LINE_STAT_UNTRACKED:
5787 type = LINE_STAT_SECTION;
5788 text = "Untracked files:";
5789 break;
5791 case LINE_STAT_NONE:
5792 type = LINE_DEFAULT;
5793 text = " (no files)";
5794 break;
5796 case LINE_STAT_HEAD:
5797 type = LINE_STAT_HEAD;
5798 text = status_onbranch;
5799 break;
5801 default:
5802 return FALSE;
5803 }
5804 } else {
5805 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5807 buf[0] = status->status;
5808 if (draw_text(view, line->type, buf, TRUE))
5809 return TRUE;
5810 type = LINE_DEFAULT;
5811 text = status->new.name;
5812 }
5814 draw_text(view, type, text, TRUE);
5815 return TRUE;
5816 }
5818 static enum request
5819 status_load_error(struct view *view, struct view *stage, const char *path)
5820 {
5821 if (displayed_views() == 2 || display[current_view] != view)
5822 maximize_view(view);
5823 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5824 return REQ_NONE;
5825 }
5827 static enum request
5828 status_enter(struct view *view, struct line *line)
5829 {
5830 struct status *status = line->data;
5831 const char *oldpath = status ? status->old.name : NULL;
5832 /* Diffs for unmerged entries are empty when passing the new
5833 * path, so leave it empty. */
5834 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5835 const char *info;
5836 enum open_flags split;
5837 struct view *stage = VIEW(REQ_VIEW_STAGE);
5839 if (line->type == LINE_STAT_NONE ||
5840 (!status && line[1].type == LINE_STAT_NONE)) {
5841 report("No file to diff");
5842 return REQ_NONE;
5843 }
5845 switch (line->type) {
5846 case LINE_STAT_STAGED:
5847 if (is_initial_commit()) {
5848 const char *no_head_diff_argv[] = {
5849 "git", "diff", "--no-color", "--patch-with-stat",
5850 "--", "/dev/null", newpath, NULL
5851 };
5853 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5854 return status_load_error(view, stage, newpath);
5855 } else {
5856 const char *index_show_argv[] = {
5857 "git", "diff-index", "--root", "--patch-with-stat",
5858 "-C", "-M", "--cached", "HEAD", "--",
5859 oldpath, newpath, NULL
5860 };
5862 if (!prepare_update(stage, index_show_argv, opt_cdup))
5863 return status_load_error(view, stage, newpath);
5864 }
5866 if (status)
5867 info = "Staged changes to %s";
5868 else
5869 info = "Staged changes";
5870 break;
5872 case LINE_STAT_UNSTAGED:
5873 {
5874 const char *files_show_argv[] = {
5875 "git", "diff-files", "--root", "--patch-with-stat",
5876 "-C", "-M", "--", oldpath, newpath, NULL
5877 };
5879 if (!prepare_update(stage, files_show_argv, opt_cdup))
5880 return status_load_error(view, stage, newpath);
5881 if (status)
5882 info = "Unstaged changes to %s";
5883 else
5884 info = "Unstaged changes";
5885 break;
5886 }
5887 case LINE_STAT_UNTRACKED:
5888 if (!newpath) {
5889 report("No file to show");
5890 return REQ_NONE;
5891 }
5893 if (!suffixcmp(status->new.name, -1, "/")) {
5894 report("Cannot display a directory");
5895 return REQ_NONE;
5896 }
5898 if (!prepare_update_file(stage, newpath))
5899 return status_load_error(view, stage, newpath);
5900 info = "Untracked file %s";
5901 break;
5903 case LINE_STAT_HEAD:
5904 return REQ_NONE;
5906 default:
5907 die("line type %d not handled in switch", line->type);
5908 }
5910 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5911 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5912 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5913 if (status) {
5914 stage_status = *status;
5915 } else {
5916 memset(&stage_status, 0, sizeof(stage_status));
5917 }
5919 stage_line_type = line->type;
5920 stage_chunks = 0;
5921 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5922 }
5924 return REQ_NONE;
5925 }
5927 static bool
5928 status_exists(struct status *status, enum line_type type)
5929 {
5930 struct view *view = VIEW(REQ_VIEW_STATUS);
5931 unsigned long lineno;
5933 for (lineno = 0; lineno < view->lines; lineno++) {
5934 struct line *line = &view->line[lineno];
5935 struct status *pos = line->data;
5937 if (line->type != type)
5938 continue;
5939 if (!pos && (!status || !status->status) && line[1].data) {
5940 select_view_line(view, lineno);
5941 return TRUE;
5942 }
5943 if (pos && !strcmp(status->new.name, pos->new.name)) {
5944 select_view_line(view, lineno);
5945 return TRUE;
5946 }
5947 }
5949 return FALSE;
5950 }
5953 static bool
5954 status_update_prepare(struct io *io, enum line_type type)
5955 {
5956 const char *staged_argv[] = {
5957 "git", "update-index", "-z", "--index-info", NULL
5958 };
5959 const char *others_argv[] = {
5960 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5961 };
5963 switch (type) {
5964 case LINE_STAT_STAGED:
5965 return io_run(io, staged_argv, opt_cdup, IO_WR);
5967 case LINE_STAT_UNSTAGED:
5968 case LINE_STAT_UNTRACKED:
5969 return io_run(io, others_argv, opt_cdup, IO_WR);
5971 default:
5972 die("line type %d not handled in switch", type);
5973 return FALSE;
5974 }
5975 }
5977 static bool
5978 status_update_write(struct io *io, struct status *status, enum line_type type)
5979 {
5980 char buf[SIZEOF_STR];
5981 size_t bufsize = 0;
5983 switch (type) {
5984 case LINE_STAT_STAGED:
5985 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5986 status->old.mode,
5987 status->old.rev,
5988 status->old.name, 0))
5989 return FALSE;
5990 break;
5992 case LINE_STAT_UNSTAGED:
5993 case LINE_STAT_UNTRACKED:
5994 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5995 return FALSE;
5996 break;
5998 default:
5999 die("line type %d not handled in switch", type);
6000 }
6002 return io_write(io, buf, bufsize);
6003 }
6005 static bool
6006 status_update_file(struct status *status, enum line_type type)
6007 {
6008 struct io io = {};
6009 bool result;
6011 if (!status_update_prepare(&io, type))
6012 return FALSE;
6014 result = status_update_write(&io, status, type);
6015 return io_done(&io) && result;
6016 }
6018 static bool
6019 status_update_files(struct view *view, struct line *line)
6020 {
6021 char buf[sizeof(view->ref)];
6022 struct io io = {};
6023 bool result = TRUE;
6024 struct line *pos = view->line + view->lines;
6025 int files = 0;
6026 int file, done;
6027 int cursor_y = -1, cursor_x = -1;
6029 if (!status_update_prepare(&io, line->type))
6030 return FALSE;
6032 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6033 files++;
6035 string_copy(buf, view->ref);
6036 getsyx(cursor_y, cursor_x);
6037 for (file = 0, done = 5; result && file < files; line++, file++) {
6038 int almost_done = file * 100 / files;
6040 if (almost_done > done) {
6041 done = almost_done;
6042 string_format(view->ref, "updating file %u of %u (%d%% done)",
6043 file, files, done);
6044 update_view_title(view);
6045 setsyx(cursor_y, cursor_x);
6046 doupdate();
6047 }
6048 result = status_update_write(&io, line->data, line->type);
6049 }
6050 string_copy(view->ref, buf);
6052 return io_done(&io) && result;
6053 }
6055 static bool
6056 status_update(struct view *view)
6057 {
6058 struct line *line = &view->line[view->lineno];
6060 assert(view->lines);
6062 if (!line->data) {
6063 /* This should work even for the "On branch" line. */
6064 if (line < view->line + view->lines && !line[1].data) {
6065 report("Nothing to update");
6066 return FALSE;
6067 }
6069 if (!status_update_files(view, line + 1)) {
6070 report("Failed to update file status");
6071 return FALSE;
6072 }
6074 } else if (!status_update_file(line->data, line->type)) {
6075 report("Failed to update file status");
6076 return FALSE;
6077 }
6079 return TRUE;
6080 }
6082 static bool
6083 status_revert(struct status *status, enum line_type type, bool has_none)
6084 {
6085 if (!status || type != LINE_STAT_UNSTAGED) {
6086 if (type == LINE_STAT_STAGED) {
6087 report("Cannot revert changes to staged files");
6088 } else if (type == LINE_STAT_UNTRACKED) {
6089 report("Cannot revert changes to untracked files");
6090 } else if (has_none) {
6091 report("Nothing to revert");
6092 } else {
6093 report("Cannot revert changes to multiple files");
6094 }
6096 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6097 char mode[10] = "100644";
6098 const char *reset_argv[] = {
6099 "git", "update-index", "--cacheinfo", mode,
6100 status->old.rev, status->old.name, NULL
6101 };
6102 const char *checkout_argv[] = {
6103 "git", "checkout", "--", status->old.name, NULL
6104 };
6106 if (status->status == 'U') {
6107 string_format(mode, "%5o", status->old.mode);
6109 if (status->old.mode == 0 && status->new.mode == 0) {
6110 reset_argv[2] = "--force-remove";
6111 reset_argv[3] = status->old.name;
6112 reset_argv[4] = NULL;
6113 }
6115 if (!io_run_fg(reset_argv, opt_cdup))
6116 return FALSE;
6117 if (status->old.mode == 0 && status->new.mode == 0)
6118 return TRUE;
6119 }
6121 return io_run_fg(checkout_argv, opt_cdup);
6122 }
6124 return FALSE;
6125 }
6127 static enum request
6128 status_request(struct view *view, enum request request, struct line *line)
6129 {
6130 struct status *status = line->data;
6132 switch (request) {
6133 case REQ_STATUS_UPDATE:
6134 if (!status_update(view))
6135 return REQ_NONE;
6136 break;
6138 case REQ_STATUS_REVERT:
6139 if (!status_revert(status, line->type, status_has_none(view, line)))
6140 return REQ_NONE;
6141 break;
6143 case REQ_STATUS_MERGE:
6144 if (!status || status->status != 'U') {
6145 report("Merging only possible for files with unmerged status ('U').");
6146 return REQ_NONE;
6147 }
6148 open_mergetool(status->new.name);
6149 break;
6151 case REQ_EDIT:
6152 if (!status)
6153 return request;
6154 if (status->status == 'D') {
6155 report("File has been deleted.");
6156 return REQ_NONE;
6157 }
6159 open_editor(status->new.name);
6160 break;
6162 case REQ_VIEW_BLAME:
6163 if (status)
6164 opt_ref[0] = 0;
6165 return request;
6167 case REQ_ENTER:
6168 /* After returning the status view has been split to
6169 * show the stage view. No further reloading is
6170 * necessary. */
6171 return status_enter(view, line);
6173 case REQ_REFRESH:
6174 /* Simply reload the view. */
6175 break;
6177 default:
6178 return request;
6179 }
6181 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6183 return REQ_NONE;
6184 }
6186 static void
6187 status_select(struct view *view, struct line *line)
6188 {
6189 struct status *status = line->data;
6190 char file[SIZEOF_STR] = "all files";
6191 const char *text;
6192 const char *key;
6194 if (status && !string_format(file, "'%s'", status->new.name))
6195 return;
6197 if (!status && line[1].type == LINE_STAT_NONE)
6198 line++;
6200 switch (line->type) {
6201 case LINE_STAT_STAGED:
6202 text = "Press %s to unstage %s for commit";
6203 break;
6205 case LINE_STAT_UNSTAGED:
6206 text = "Press %s to stage %s for commit";
6207 break;
6209 case LINE_STAT_UNTRACKED:
6210 text = "Press %s to stage %s for addition";
6211 break;
6213 case LINE_STAT_HEAD:
6214 case LINE_STAT_NONE:
6215 text = "Nothing to update";
6216 break;
6218 default:
6219 die("line type %d not handled in switch", line->type);
6220 }
6222 if (status && status->status == 'U') {
6223 text = "Press %s to resolve conflict in %s";
6224 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6226 } else {
6227 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6228 }
6230 string_format(view->ref, text, key, file);
6231 if (status)
6232 string_copy(opt_file, status->new.name);
6233 }
6235 static bool
6236 status_grep(struct view *view, struct line *line)
6237 {
6238 struct status *status = line->data;
6240 if (status) {
6241 const char buf[2] = { status->status, 0 };
6242 const char *text[] = { status->new.name, buf, NULL };
6244 return grep_text(view, text);
6245 }
6247 return FALSE;
6248 }
6250 static struct view_ops status_ops = {
6251 "file",
6252 NULL,
6253 status_open,
6254 NULL,
6255 status_draw,
6256 status_request,
6257 status_grep,
6258 status_select,
6259 };
6262 static bool
6263 stage_diff_write(struct io *io, struct line *line, struct line *end)
6264 {
6265 while (line < end) {
6266 if (!io_write(io, line->data, strlen(line->data)) ||
6267 !io_write(io, "\n", 1))
6268 return FALSE;
6269 line++;
6270 if (line->type == LINE_DIFF_CHUNK ||
6271 line->type == LINE_DIFF_HEADER)
6272 break;
6273 }
6275 return TRUE;
6276 }
6278 static struct line *
6279 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6280 {
6281 for (; view->line < line; line--)
6282 if (line->type == type)
6283 return line;
6285 return NULL;
6286 }
6288 static bool
6289 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6290 {
6291 const char *apply_argv[SIZEOF_ARG] = {
6292 "git", "apply", "--whitespace=nowarn", NULL
6293 };
6294 struct line *diff_hdr;
6295 struct io io = {};
6296 int argc = 3;
6298 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6299 if (!diff_hdr)
6300 return FALSE;
6302 if (!revert)
6303 apply_argv[argc++] = "--cached";
6304 if (revert || stage_line_type == LINE_STAT_STAGED)
6305 apply_argv[argc++] = "-R";
6306 apply_argv[argc++] = "-";
6307 apply_argv[argc++] = NULL;
6308 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6309 return FALSE;
6311 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6312 !stage_diff_write(&io, chunk, view->line + view->lines))
6313 chunk = NULL;
6315 io_done(&io);
6316 io_run_bg(update_index_argv);
6318 return chunk ? TRUE : FALSE;
6319 }
6321 static bool
6322 stage_update(struct view *view, struct line *line)
6323 {
6324 struct line *chunk = NULL;
6326 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6327 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6329 if (chunk) {
6330 if (!stage_apply_chunk(view, chunk, FALSE)) {
6331 report("Failed to apply chunk");
6332 return FALSE;
6333 }
6335 } else if (!stage_status.status) {
6336 view = VIEW(REQ_VIEW_STATUS);
6338 for (line = view->line; line < view->line + view->lines; line++)
6339 if (line->type == stage_line_type)
6340 break;
6342 if (!status_update_files(view, line + 1)) {
6343 report("Failed to update files");
6344 return FALSE;
6345 }
6347 } else if (!status_update_file(&stage_status, stage_line_type)) {
6348 report("Failed to update file");
6349 return FALSE;
6350 }
6352 return TRUE;
6353 }
6355 static bool
6356 stage_revert(struct view *view, struct line *line)
6357 {
6358 struct line *chunk = NULL;
6360 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6361 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6363 if (chunk) {
6364 if (!prompt_yesno("Are you sure you want to revert changes?"))
6365 return FALSE;
6367 if (!stage_apply_chunk(view, chunk, TRUE)) {
6368 report("Failed to revert chunk");
6369 return FALSE;
6370 }
6371 return TRUE;
6373 } else {
6374 return status_revert(stage_status.status ? &stage_status : NULL,
6375 stage_line_type, FALSE);
6376 }
6377 }
6380 static void
6381 stage_next(struct view *view, struct line *line)
6382 {
6383 int i;
6385 if (!stage_chunks) {
6386 for (line = view->line; line < view->line + view->lines; line++) {
6387 if (line->type != LINE_DIFF_CHUNK)
6388 continue;
6390 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6391 report("Allocation failure");
6392 return;
6393 }
6395 stage_chunk[stage_chunks++] = line - view->line;
6396 }
6397 }
6399 for (i = 0; i < stage_chunks; i++) {
6400 if (stage_chunk[i] > view->lineno) {
6401 do_scroll_view(view, stage_chunk[i] - view->lineno);
6402 report("Chunk %d of %d", i + 1, stage_chunks);
6403 return;
6404 }
6405 }
6407 report("No next chunk found");
6408 }
6410 static enum request
6411 stage_request(struct view *view, enum request request, struct line *line)
6412 {
6413 switch (request) {
6414 case REQ_STATUS_UPDATE:
6415 if (!stage_update(view, line))
6416 return REQ_NONE;
6417 break;
6419 case REQ_STATUS_REVERT:
6420 if (!stage_revert(view, line))
6421 return REQ_NONE;
6422 break;
6424 case REQ_STAGE_NEXT:
6425 if (stage_line_type == LINE_STAT_UNTRACKED) {
6426 report("File is untracked; press %s to add",
6427 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6428 return REQ_NONE;
6429 }
6430 stage_next(view, line);
6431 return REQ_NONE;
6433 case REQ_EDIT:
6434 if (!stage_status.new.name[0])
6435 return request;
6436 if (stage_status.status == 'D') {
6437 report("File has been deleted.");
6438 return REQ_NONE;
6439 }
6441 open_editor(stage_status.new.name);
6442 break;
6444 case REQ_REFRESH:
6445 /* Reload everything ... */
6446 break;
6448 case REQ_VIEW_BLAME:
6449 if (stage_status.new.name[0]) {
6450 string_copy(opt_file, stage_status.new.name);
6451 opt_ref[0] = 0;
6452 }
6453 return request;
6455 case REQ_ENTER:
6456 return pager_request(view, request, line);
6458 default:
6459 return request;
6460 }
6462 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6463 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6465 /* Check whether the staged entry still exists, and close the
6466 * stage view if it doesn't. */
6467 if (!status_exists(&stage_status, stage_line_type)) {
6468 status_restore(VIEW(REQ_VIEW_STATUS));
6469 return REQ_VIEW_CLOSE;
6470 }
6472 if (stage_line_type == LINE_STAT_UNTRACKED) {
6473 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6474 report("Cannot display a directory");
6475 return REQ_NONE;
6476 }
6478 if (!prepare_update_file(view, stage_status.new.name)) {
6479 report("Failed to open file: %s", strerror(errno));
6480 return REQ_NONE;
6481 }
6482 }
6483 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6485 return REQ_NONE;
6486 }
6488 static struct view_ops stage_ops = {
6489 "line",
6490 NULL,
6491 NULL,
6492 pager_read,
6493 pager_draw,
6494 stage_request,
6495 pager_grep,
6496 pager_select,
6497 };
6500 /*
6501 * Revision graph
6502 */
6504 struct commit {
6505 char id[SIZEOF_REV]; /* SHA1 ID. */
6506 char title[128]; /* First line of the commit message. */
6507 const char *author; /* Author of the commit. */
6508 struct time time; /* Date from the author ident. */
6509 struct ref_list *refs; /* Repository references. */
6510 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6511 size_t graph_size; /* The width of the graph array. */
6512 bool has_parents; /* Rewritten --parents seen. */
6513 };
6515 /* Size of rev graph with no "padding" columns */
6516 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6518 struct rev_graph {
6519 struct rev_graph *prev, *next, *parents;
6520 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6521 size_t size;
6522 struct commit *commit;
6523 size_t pos;
6524 unsigned int boundary:1;
6525 };
6527 /* Parents of the commit being visualized. */
6528 static struct rev_graph graph_parents[4];
6530 /* The current stack of revisions on the graph. */
6531 static struct rev_graph graph_stacks[4] = {
6532 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6533 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6534 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6535 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6536 };
6538 static inline bool
6539 graph_parent_is_merge(struct rev_graph *graph)
6540 {
6541 return graph->parents->size > 1;
6542 }
6544 static inline void
6545 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6546 {
6547 struct commit *commit = graph->commit;
6549 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6550 commit->graph[commit->graph_size++] = symbol;
6551 }
6553 static void
6554 clear_rev_graph(struct rev_graph *graph)
6555 {
6556 graph->boundary = 0;
6557 graph->size = graph->pos = 0;
6558 graph->commit = NULL;
6559 memset(graph->parents, 0, sizeof(*graph->parents));
6560 }
6562 static void
6563 done_rev_graph(struct rev_graph *graph)
6564 {
6565 if (graph_parent_is_merge(graph) &&
6566 graph->pos < graph->size - 1 &&
6567 graph->next->size == graph->size + graph->parents->size - 1) {
6568 size_t i = graph->pos + graph->parents->size - 1;
6570 graph->commit->graph_size = i * 2;
6571 while (i < graph->next->size - 1) {
6572 append_to_rev_graph(graph, ' ');
6573 append_to_rev_graph(graph, '\\');
6574 i++;
6575 }
6576 }
6578 clear_rev_graph(graph);
6579 }
6581 static void
6582 push_rev_graph(struct rev_graph *graph, const char *parent)
6583 {
6584 int i;
6586 /* "Collapse" duplicate parents lines.
6587 *
6588 * FIXME: This needs to also update update the drawn graph but
6589 * for now it just serves as a method for pruning graph lines. */
6590 for (i = 0; i < graph->size; i++)
6591 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6592 return;
6594 if (graph->size < SIZEOF_REVITEMS) {
6595 string_copy_rev(graph->rev[graph->size++], parent);
6596 }
6597 }
6599 static chtype
6600 get_rev_graph_symbol(struct rev_graph *graph)
6601 {
6602 chtype symbol;
6604 if (graph->boundary)
6605 symbol = REVGRAPH_BOUND;
6606 else if (graph->parents->size == 0)
6607 symbol = REVGRAPH_INIT;
6608 else if (graph_parent_is_merge(graph))
6609 symbol = REVGRAPH_MERGE;
6610 else if (graph->pos >= graph->size)
6611 symbol = REVGRAPH_BRANCH;
6612 else
6613 symbol = REVGRAPH_COMMIT;
6615 return symbol;
6616 }
6618 static void
6619 draw_rev_graph(struct rev_graph *graph)
6620 {
6621 struct rev_filler {
6622 chtype separator, line;
6623 };
6624 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6625 static struct rev_filler fillers[] = {
6626 { ' ', '|' },
6627 { '`', '.' },
6628 { '\'', ' ' },
6629 { '/', ' ' },
6630 };
6631 chtype symbol = get_rev_graph_symbol(graph);
6632 struct rev_filler *filler;
6633 size_t i;
6635 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6636 filler = &fillers[DEFAULT];
6638 for (i = 0; i < graph->pos; i++) {
6639 append_to_rev_graph(graph, filler->line);
6640 if (graph_parent_is_merge(graph->prev) &&
6641 graph->prev->pos == i)
6642 filler = &fillers[RSHARP];
6644 append_to_rev_graph(graph, filler->separator);
6645 }
6647 /* Place the symbol for this revision. */
6648 append_to_rev_graph(graph, symbol);
6650 if (graph->prev->size > graph->size)
6651 filler = &fillers[RDIAG];
6652 else
6653 filler = &fillers[DEFAULT];
6655 i++;
6657 for (; i < graph->size; i++) {
6658 append_to_rev_graph(graph, filler->separator);
6659 append_to_rev_graph(graph, filler->line);
6660 if (graph_parent_is_merge(graph->prev) &&
6661 i < graph->prev->pos + graph->parents->size)
6662 filler = &fillers[RSHARP];
6663 if (graph->prev->size > graph->size)
6664 filler = &fillers[LDIAG];
6665 }
6667 if (graph->prev->size > graph->size) {
6668 append_to_rev_graph(graph, filler->separator);
6669 if (filler->line != ' ')
6670 append_to_rev_graph(graph, filler->line);
6671 }
6672 }
6674 /* Prepare the next rev graph */
6675 static void
6676 prepare_rev_graph(struct rev_graph *graph)
6677 {
6678 size_t i;
6680 /* First, traverse all lines of revisions up to the active one. */
6681 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6682 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6683 break;
6685 push_rev_graph(graph->next, graph->rev[graph->pos]);
6686 }
6688 /* Interleave the new revision parent(s). */
6689 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6690 push_rev_graph(graph->next, graph->parents->rev[i]);
6692 /* Lastly, put any remaining revisions. */
6693 for (i = graph->pos + 1; i < graph->size; i++)
6694 push_rev_graph(graph->next, graph->rev[i]);
6695 }
6697 static void
6698 update_rev_graph(struct view *view, struct rev_graph *graph)
6699 {
6700 /* If this is the finalizing update ... */
6701 if (graph->commit)
6702 prepare_rev_graph(graph);
6704 /* Graph visualization needs a one rev look-ahead,
6705 * so the first update doesn't visualize anything. */
6706 if (!graph->prev->commit)
6707 return;
6709 if (view->lines > 2)
6710 view->line[view->lines - 3].dirty = 1;
6711 if (view->lines > 1)
6712 view->line[view->lines - 2].dirty = 1;
6713 draw_rev_graph(graph->prev);
6714 done_rev_graph(graph->prev->prev);
6715 }
6718 /*
6719 * Main view backend
6720 */
6722 static const char *main_argv[SIZEOF_ARG] = {
6723 "git", "log", "--no-color", "--pretty=raw", "--parents",
6724 "--topo-order", "%(head)", NULL
6725 };
6727 static bool
6728 main_draw(struct view *view, struct line *line, unsigned int lineno)
6729 {
6730 struct commit *commit = line->data;
6732 if (!commit->author)
6733 return FALSE;
6735 if (opt_date && draw_date(view, &commit->time))
6736 return TRUE;
6738 if (opt_author && draw_author(view, commit->author))
6739 return TRUE;
6741 if (opt_rev_graph && commit->graph_size &&
6742 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6743 return TRUE;
6745 if (opt_show_refs && commit->refs) {
6746 size_t i;
6748 for (i = 0; i < commit->refs->size; i++) {
6749 struct ref *ref = commit->refs->refs[i];
6750 enum line_type type;
6752 if (ref->head)
6753 type = LINE_MAIN_HEAD;
6754 else if (ref->ltag)
6755 type = LINE_MAIN_LOCAL_TAG;
6756 else if (ref->tag)
6757 type = LINE_MAIN_TAG;
6758 else if (ref->tracked)
6759 type = LINE_MAIN_TRACKED;
6760 else if (ref->remote)
6761 type = LINE_MAIN_REMOTE;
6762 else
6763 type = LINE_MAIN_REF;
6765 if (draw_text(view, type, "[", TRUE) ||
6766 draw_text(view, type, ref->name, TRUE) ||
6767 draw_text(view, type, "]", TRUE))
6768 return TRUE;
6770 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6771 return TRUE;
6772 }
6773 }
6775 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6776 return TRUE;
6777 }
6779 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6780 static bool
6781 main_read(struct view *view, char *line)
6782 {
6783 static struct rev_graph *graph = graph_stacks;
6784 enum line_type type;
6785 struct commit *commit;
6787 if (!line) {
6788 int i;
6790 if (!view->lines && !view->prev)
6791 die("No revisions match the given arguments.");
6792 if (view->lines > 0) {
6793 commit = view->line[view->lines - 1].data;
6794 view->line[view->lines - 1].dirty = 1;
6795 if (!commit->author) {
6796 view->lines--;
6797 free(commit);
6798 graph->commit = NULL;
6799 }
6800 }
6801 update_rev_graph(view, graph);
6803 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6804 clear_rev_graph(&graph_stacks[i]);
6805 return TRUE;
6806 }
6808 type = get_line_type(line);
6809 if (type == LINE_COMMIT) {
6810 commit = calloc(1, sizeof(struct commit));
6811 if (!commit)
6812 return FALSE;
6814 line += STRING_SIZE("commit ");
6815 if (*line == '-') {
6816 graph->boundary = 1;
6817 line++;
6818 }
6820 string_copy_rev(commit->id, line);
6821 commit->refs = get_ref_list(commit->id);
6822 graph->commit = commit;
6823 add_line_data(view, commit, LINE_MAIN_COMMIT);
6825 while ((line = strchr(line, ' '))) {
6826 line++;
6827 push_rev_graph(graph->parents, line);
6828 commit->has_parents = TRUE;
6829 }
6830 return TRUE;
6831 }
6833 if (!view->lines)
6834 return TRUE;
6835 commit = view->line[view->lines - 1].data;
6837 switch (type) {
6838 case LINE_PARENT:
6839 if (commit->has_parents)
6840 break;
6841 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6842 break;
6844 case LINE_AUTHOR:
6845 parse_author_line(line + STRING_SIZE("author "),
6846 &commit->author, &commit->time);
6847 update_rev_graph(view, graph);
6848 graph = graph->next;
6849 break;
6851 default:
6852 /* Fill in the commit title if it has not already been set. */
6853 if (commit->title[0])
6854 break;
6856 /* Require titles to start with a non-space character at the
6857 * offset used by git log. */
6858 if (strncmp(line, " ", 4))
6859 break;
6860 line += 4;
6861 /* Well, if the title starts with a whitespace character,
6862 * try to be forgiving. Otherwise we end up with no title. */
6863 while (isspace(*line))
6864 line++;
6865 if (*line == '\0')
6866 break;
6867 /* FIXME: More graceful handling of titles; append "..." to
6868 * shortened titles, etc. */
6870 string_expand(commit->title, sizeof(commit->title), line, 1);
6871 view->line[view->lines - 1].dirty = 1;
6872 }
6874 return TRUE;
6875 }
6877 static enum request
6878 main_request(struct view *view, enum request request, struct line *line)
6879 {
6880 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6882 switch (request) {
6883 case REQ_ENTER:
6884 open_view(view, REQ_VIEW_DIFF, flags);
6885 break;
6886 case REQ_REFRESH:
6887 load_refs();
6888 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6889 break;
6890 default:
6891 return request;
6892 }
6894 return REQ_NONE;
6895 }
6897 static bool
6898 grep_refs(struct ref_list *list, regex_t *regex)
6899 {
6900 regmatch_t pmatch;
6901 size_t i;
6903 if (!opt_show_refs || !list)
6904 return FALSE;
6906 for (i = 0; i < list->size; i++) {
6907 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6908 return TRUE;
6909 }
6911 return FALSE;
6912 }
6914 static bool
6915 main_grep(struct view *view, struct line *line)
6916 {
6917 struct commit *commit = line->data;
6918 const char *text[] = {
6919 commit->title,
6920 opt_author ? commit->author : "",
6921 mkdate(&commit->time, opt_date),
6922 NULL
6923 };
6925 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6926 }
6928 static void
6929 main_select(struct view *view, struct line *line)
6930 {
6931 struct commit *commit = line->data;
6933 string_copy_rev(view->ref, commit->id);
6934 string_copy_rev(ref_commit, view->ref);
6935 }
6937 static struct view_ops main_ops = {
6938 "commit",
6939 main_argv,
6940 NULL,
6941 main_read,
6942 main_draw,
6943 main_request,
6944 main_grep,
6945 main_select,
6946 };
6949 /*
6950 * Status management
6951 */
6953 /* Whether or not the curses interface has been initialized. */
6954 static bool cursed = FALSE;
6956 /* Terminal hacks and workarounds. */
6957 static bool use_scroll_redrawwin;
6958 static bool use_scroll_status_wclear;
6960 /* The status window is used for polling keystrokes. */
6961 static WINDOW *status_win;
6963 /* Reading from the prompt? */
6964 static bool input_mode = FALSE;
6966 static bool status_empty = FALSE;
6968 /* Update status and title window. */
6969 static void
6970 report(const char *msg, ...)
6971 {
6972 struct view *view = display[current_view];
6974 if (input_mode)
6975 return;
6977 if (!view) {
6978 char buf[SIZEOF_STR];
6979 va_list args;
6981 va_start(args, msg);
6982 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6983 buf[sizeof(buf) - 1] = 0;
6984 buf[sizeof(buf) - 2] = '.';
6985 buf[sizeof(buf) - 3] = '.';
6986 buf[sizeof(buf) - 4] = '.';
6987 }
6988 va_end(args);
6989 die("%s", buf);
6990 }
6992 if (!status_empty || *msg) {
6993 va_list args;
6995 va_start(args, msg);
6997 wmove(status_win, 0, 0);
6998 if (view->has_scrolled && use_scroll_status_wclear)
6999 wclear(status_win);
7000 if (*msg) {
7001 vwprintw(status_win, msg, args);
7002 status_empty = FALSE;
7003 } else {
7004 status_empty = TRUE;
7005 }
7006 wclrtoeol(status_win);
7007 wnoutrefresh(status_win);
7009 va_end(args);
7010 }
7012 update_view_title(view);
7013 }
7015 static void
7016 init_display(void)
7017 {
7018 const char *term;
7019 int x, y;
7021 /* Initialize the curses library */
7022 if (isatty(STDIN_FILENO)) {
7023 cursed = !!initscr();
7024 opt_tty = stdin;
7025 } else {
7026 /* Leave stdin and stdout alone when acting as a pager. */
7027 opt_tty = fopen("/dev/tty", "r+");
7028 if (!opt_tty)
7029 die("Failed to open /dev/tty");
7030 cursed = !!newterm(NULL, opt_tty, opt_tty);
7031 }
7033 if (!cursed)
7034 die("Failed to initialize curses");
7036 nonl(); /* Disable conversion and detect newlines from input. */
7037 cbreak(); /* Take input chars one at a time, no wait for \n */
7038 noecho(); /* Don't echo input */
7039 leaveok(stdscr, FALSE);
7041 if (has_colors())
7042 init_colors();
7044 getmaxyx(stdscr, y, x);
7045 status_win = newwin(1, 0, y - 1, 0);
7046 if (!status_win)
7047 die("Failed to create status window");
7049 /* Enable keyboard mapping */
7050 keypad(status_win, TRUE);
7051 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7053 TABSIZE = opt_tab_size;
7055 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7056 if (term && !strcmp(term, "gnome-terminal")) {
7057 /* In the gnome-terminal-emulator, the message from
7058 * scrolling up one line when impossible followed by
7059 * scrolling down one line causes corruption of the
7060 * status line. This is fixed by calling wclear. */
7061 use_scroll_status_wclear = TRUE;
7062 use_scroll_redrawwin = FALSE;
7064 } else if (term && !strcmp(term, "xrvt-xpm")) {
7065 /* No problems with full optimizations in xrvt-(unicode)
7066 * and aterm. */
7067 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7069 } else {
7070 /* When scrolling in (u)xterm the last line in the
7071 * scrolling direction will update slowly. */
7072 use_scroll_redrawwin = TRUE;
7073 use_scroll_status_wclear = FALSE;
7074 }
7075 }
7077 static int
7078 get_input(int prompt_position)
7079 {
7080 struct view *view;
7081 int i, key, cursor_y, cursor_x;
7082 bool loading = FALSE;
7084 if (prompt_position)
7085 input_mode = TRUE;
7087 while (TRUE) {
7088 foreach_view (view, i) {
7089 update_view(view);
7090 if (view_is_displayed(view) && view->has_scrolled &&
7091 use_scroll_redrawwin)
7092 redrawwin(view->win);
7093 view->has_scrolled = FALSE;
7094 if (view->pipe)
7095 loading = TRUE;
7096 }
7098 /* Update the cursor position. */
7099 if (prompt_position) {
7100 getbegyx(status_win, cursor_y, cursor_x);
7101 cursor_x = prompt_position;
7102 } else {
7103 view = display[current_view];
7104 getbegyx(view->win, cursor_y, cursor_x);
7105 cursor_x = view->width - 1;
7106 cursor_y += view->lineno - view->offset;
7107 }
7108 setsyx(cursor_y, cursor_x);
7110 /* Refresh, accept single keystroke of input */
7111 doupdate();
7112 nodelay(status_win, loading);
7113 key = wgetch(status_win);
7115 /* wgetch() with nodelay() enabled returns ERR when
7116 * there's no input. */
7117 if (key == ERR) {
7119 } else if (key == KEY_RESIZE) {
7120 int height, width;
7122 getmaxyx(stdscr, height, width);
7124 wresize(status_win, 1, width);
7125 mvwin(status_win, height - 1, 0);
7126 wnoutrefresh(status_win);
7127 resize_display();
7128 redraw_display(TRUE);
7130 } else {
7131 input_mode = FALSE;
7132 return key;
7133 }
7134 }
7135 }
7137 static char *
7138 prompt_input(const char *prompt, input_handler handler, void *data)
7139 {
7140 enum input_status status = INPUT_OK;
7141 static char buf[SIZEOF_STR];
7142 size_t pos = 0;
7144 buf[pos] = 0;
7146 while (status == INPUT_OK || status == INPUT_SKIP) {
7147 int key;
7149 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7150 wclrtoeol(status_win);
7152 key = get_input(pos + 1);
7153 switch (key) {
7154 case KEY_RETURN:
7155 case KEY_ENTER:
7156 case '\n':
7157 status = pos ? INPUT_STOP : INPUT_CANCEL;
7158 break;
7160 case KEY_BACKSPACE:
7161 if (pos > 0)
7162 buf[--pos] = 0;
7163 else
7164 status = INPUT_CANCEL;
7165 break;
7167 case KEY_ESC:
7168 status = INPUT_CANCEL;
7169 break;
7171 default:
7172 if (pos >= sizeof(buf)) {
7173 report("Input string too long");
7174 return NULL;
7175 }
7177 status = handler(data, buf, key);
7178 if (status == INPUT_OK)
7179 buf[pos++] = (char) key;
7180 }
7181 }
7183 /* Clear the status window */
7184 status_empty = FALSE;
7185 report("");
7187 if (status == INPUT_CANCEL)
7188 return NULL;
7190 buf[pos++] = 0;
7192 return buf;
7193 }
7195 static enum input_status
7196 prompt_yesno_handler(void *data, char *buf, int c)
7197 {
7198 if (c == 'y' || c == 'Y')
7199 return INPUT_STOP;
7200 if (c == 'n' || c == 'N')
7201 return INPUT_CANCEL;
7202 return INPUT_SKIP;
7203 }
7205 static bool
7206 prompt_yesno(const char *prompt)
7207 {
7208 char prompt2[SIZEOF_STR];
7210 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7211 return FALSE;
7213 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7214 }
7216 static enum input_status
7217 read_prompt_handler(void *data, char *buf, int c)
7218 {
7219 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7220 }
7222 static char *
7223 read_prompt(const char *prompt)
7224 {
7225 return prompt_input(prompt, read_prompt_handler, NULL);
7226 }
7228 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7229 {
7230 enum input_status status = INPUT_OK;
7231 int size = 0;
7233 while (items[size].text)
7234 size++;
7236 while (status == INPUT_OK) {
7237 const struct menu_item *item = &items[*selected];
7238 int key;
7239 int i;
7241 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7242 prompt, *selected + 1, size);
7243 if (item->hotkey)
7244 wprintw(status_win, "[%c] ", (char) item->hotkey);
7245 wprintw(status_win, "%s", item->text);
7246 wclrtoeol(status_win);
7248 key = get_input(COLS - 1);
7249 switch (key) {
7250 case KEY_RETURN:
7251 case KEY_ENTER:
7252 case '\n':
7253 status = INPUT_STOP;
7254 break;
7256 case KEY_LEFT:
7257 case KEY_UP:
7258 *selected = *selected - 1;
7259 if (*selected < 0)
7260 *selected = size - 1;
7261 break;
7263 case KEY_RIGHT:
7264 case KEY_DOWN:
7265 *selected = (*selected + 1) % size;
7266 break;
7268 case KEY_ESC:
7269 status = INPUT_CANCEL;
7270 break;
7272 default:
7273 for (i = 0; items[i].text; i++)
7274 if (items[i].hotkey == key) {
7275 *selected = i;
7276 status = INPUT_STOP;
7277 break;
7278 }
7279 }
7280 }
7282 /* Clear the status window */
7283 status_empty = FALSE;
7284 report("");
7286 return status != INPUT_CANCEL;
7287 }
7289 /*
7290 * Repository properties
7291 */
7293 static struct ref **refs = NULL;
7294 static size_t refs_size = 0;
7295 static struct ref *refs_head = NULL;
7297 static struct ref_list **ref_lists = NULL;
7298 static size_t ref_lists_size = 0;
7300 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7301 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7302 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7304 static int
7305 compare_refs(const void *ref1_, const void *ref2_)
7306 {
7307 const struct ref *ref1 = *(const struct ref **)ref1_;
7308 const struct ref *ref2 = *(const struct ref **)ref2_;
7310 if (ref1->tag != ref2->tag)
7311 return ref2->tag - ref1->tag;
7312 if (ref1->ltag != ref2->ltag)
7313 return ref2->ltag - ref2->ltag;
7314 if (ref1->head != ref2->head)
7315 return ref2->head - ref1->head;
7316 if (ref1->tracked != ref2->tracked)
7317 return ref2->tracked - ref1->tracked;
7318 if (ref1->remote != ref2->remote)
7319 return ref2->remote - ref1->remote;
7320 return strcmp(ref1->name, ref2->name);
7321 }
7323 static void
7324 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7325 {
7326 size_t i;
7328 for (i = 0; i < refs_size; i++)
7329 if (!visitor(data, refs[i]))
7330 break;
7331 }
7333 static struct ref *
7334 get_ref_head()
7335 {
7336 return refs_head;
7337 }
7339 static struct ref_list *
7340 get_ref_list(const char *id)
7341 {
7342 struct ref_list *list;
7343 size_t i;
7345 for (i = 0; i < ref_lists_size; i++)
7346 if (!strcmp(id, ref_lists[i]->id))
7347 return ref_lists[i];
7349 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7350 return NULL;
7351 list = calloc(1, sizeof(*list));
7352 if (!list)
7353 return NULL;
7355 for (i = 0; i < refs_size; i++) {
7356 if (!strcmp(id, refs[i]->id) &&
7357 realloc_refs_list(&list->refs, list->size, 1))
7358 list->refs[list->size++] = refs[i];
7359 }
7361 if (!list->refs) {
7362 free(list);
7363 return NULL;
7364 }
7366 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7367 ref_lists[ref_lists_size++] = list;
7368 return list;
7369 }
7371 static int
7372 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7373 {
7374 struct ref *ref = NULL;
7375 bool tag = FALSE;
7376 bool ltag = FALSE;
7377 bool remote = FALSE;
7378 bool tracked = FALSE;
7379 bool head = FALSE;
7380 int from = 0, to = refs_size - 1;
7382 if (!prefixcmp(name, "refs/tags/")) {
7383 if (!suffixcmp(name, namelen, "^{}")) {
7384 namelen -= 3;
7385 name[namelen] = 0;
7386 } else {
7387 ltag = TRUE;
7388 }
7390 tag = TRUE;
7391 namelen -= STRING_SIZE("refs/tags/");
7392 name += STRING_SIZE("refs/tags/");
7394 } else if (!prefixcmp(name, "refs/remotes/")) {
7395 remote = TRUE;
7396 namelen -= STRING_SIZE("refs/remotes/");
7397 name += STRING_SIZE("refs/remotes/");
7398 tracked = !strcmp(opt_remote, name);
7400 } else if (!prefixcmp(name, "refs/heads/")) {
7401 namelen -= STRING_SIZE("refs/heads/");
7402 name += STRING_SIZE("refs/heads/");
7403 if (!strncmp(opt_head, name, namelen))
7404 return OK;
7406 } else if (!strcmp(name, "HEAD")) {
7407 head = TRUE;
7408 if (*opt_head) {
7409 namelen = strlen(opt_head);
7410 name = opt_head;
7411 }
7412 }
7414 /* If we are reloading or it's an annotated tag, replace the
7415 * previous SHA1 with the resolved commit id; relies on the fact
7416 * git-ls-remote lists the commit id of an annotated tag right
7417 * before the commit id it points to. */
7418 while (from <= to) {
7419 size_t pos = (to + from) / 2;
7420 int cmp = strcmp(name, refs[pos]->name);
7422 if (!cmp) {
7423 ref = refs[pos];
7424 break;
7425 }
7427 if (cmp < 0)
7428 to = pos - 1;
7429 else
7430 from = pos + 1;
7431 }
7433 if (!ref) {
7434 if (!realloc_refs(&refs, refs_size, 1))
7435 return ERR;
7436 ref = calloc(1, sizeof(*ref) + namelen);
7437 if (!ref)
7438 return ERR;
7439 memmove(refs + from + 1, refs + from,
7440 (refs_size - from) * sizeof(*refs));
7441 refs[from] = ref;
7442 strncpy(ref->name, name, namelen);
7443 refs_size++;
7444 }
7446 ref->head = head;
7447 ref->tag = tag;
7448 ref->ltag = ltag;
7449 ref->remote = remote;
7450 ref->tracked = tracked;
7451 string_copy_rev(ref->id, id);
7453 if (head)
7454 refs_head = ref;
7455 return OK;
7456 }
7458 static int
7459 load_refs(void)
7460 {
7461 const char *head_argv[] = {
7462 "git", "symbolic-ref", "HEAD", NULL
7463 };
7464 static const char *ls_remote_argv[SIZEOF_ARG] = {
7465 "git", "ls-remote", opt_git_dir, NULL
7466 };
7467 static bool init = FALSE;
7468 size_t i;
7470 if (!init) {
7471 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7472 die("TIG_LS_REMOTE contains too many arguments");
7473 init = TRUE;
7474 }
7476 if (!*opt_git_dir)
7477 return OK;
7479 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7480 !prefixcmp(opt_head, "refs/heads/")) {
7481 char *offset = opt_head + STRING_SIZE("refs/heads/");
7483 memmove(opt_head, offset, strlen(offset) + 1);
7484 }
7486 refs_head = NULL;
7487 for (i = 0; i < refs_size; i++)
7488 refs[i]->id[0] = 0;
7490 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7491 return ERR;
7493 /* Update the ref lists to reflect changes. */
7494 for (i = 0; i < ref_lists_size; i++) {
7495 struct ref_list *list = ref_lists[i];
7496 size_t old, new;
7498 for (old = new = 0; old < list->size; old++)
7499 if (!strcmp(list->id, list->refs[old]->id))
7500 list->refs[new++] = list->refs[old];
7501 list->size = new;
7502 }
7504 return OK;
7505 }
7507 static void
7508 set_remote_branch(const char *name, const char *value, size_t valuelen)
7509 {
7510 if (!strcmp(name, ".remote")) {
7511 string_ncopy(opt_remote, value, valuelen);
7513 } else if (*opt_remote && !strcmp(name, ".merge")) {
7514 size_t from = strlen(opt_remote);
7516 if (!prefixcmp(value, "refs/heads/"))
7517 value += STRING_SIZE("refs/heads/");
7519 if (!string_format_from(opt_remote, &from, "/%s", value))
7520 opt_remote[0] = 0;
7521 }
7522 }
7524 static void
7525 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7526 {
7527 const char *argv[SIZEOF_ARG] = { name, "=" };
7528 int argc = 1 + (cmd == option_set_command);
7529 int error = ERR;
7531 if (!argv_from_string(argv, &argc, value))
7532 config_msg = "Too many option arguments";
7533 else
7534 error = cmd(argc, argv);
7536 if (error == ERR)
7537 warn("Option 'tig.%s': %s", name, config_msg);
7538 }
7540 static bool
7541 set_environment_variable(const char *name, const char *value)
7542 {
7543 size_t len = strlen(name) + 1 + strlen(value) + 1;
7544 char *env = malloc(len);
7546 if (env &&
7547 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7548 putenv(env) == 0)
7549 return TRUE;
7550 free(env);
7551 return FALSE;
7552 }
7554 static void
7555 set_work_tree(const char *value)
7556 {
7557 char cwd[SIZEOF_STR];
7559 if (!getcwd(cwd, sizeof(cwd)))
7560 die("Failed to get cwd path: %s", strerror(errno));
7561 if (chdir(opt_git_dir) < 0)
7562 die("Failed to chdir(%s): %s", strerror(errno));
7563 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7564 die("Failed to get git path: %s", strerror(errno));
7565 if (chdir(cwd) < 0)
7566 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7567 if (chdir(value) < 0)
7568 die("Failed to chdir(%s): %s", value, strerror(errno));
7569 if (!getcwd(cwd, sizeof(cwd)))
7570 die("Failed to get cwd path: %s", strerror(errno));
7571 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7572 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7573 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7574 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7575 opt_is_inside_work_tree = TRUE;
7576 }
7578 static int
7579 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7580 {
7581 if (!strcmp(name, "i18n.commitencoding"))
7582 string_ncopy(opt_encoding, value, valuelen);
7584 else if (!strcmp(name, "core.editor"))
7585 string_ncopy(opt_editor, value, valuelen);
7587 else if (!strcmp(name, "core.worktree"))
7588 set_work_tree(value);
7590 else if (!prefixcmp(name, "tig.color."))
7591 set_repo_config_option(name + 10, value, option_color_command);
7593 else if (!prefixcmp(name, "tig.bind."))
7594 set_repo_config_option(name + 9, value, option_bind_command);
7596 else if (!prefixcmp(name, "tig."))
7597 set_repo_config_option(name + 4, value, option_set_command);
7599 else if (*opt_head && !prefixcmp(name, "branch.") &&
7600 !strncmp(name + 7, opt_head, strlen(opt_head)))
7601 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7603 return OK;
7604 }
7606 static int
7607 load_git_config(void)
7608 {
7609 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7611 return io_run_load(config_list_argv, "=", read_repo_config_option);
7612 }
7614 static int
7615 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7616 {
7617 if (!opt_git_dir[0]) {
7618 string_ncopy(opt_git_dir, name, namelen);
7620 } else if (opt_is_inside_work_tree == -1) {
7621 /* This can be 3 different values depending on the
7622 * version of git being used. If git-rev-parse does not
7623 * understand --is-inside-work-tree it will simply echo
7624 * the option else either "true" or "false" is printed.
7625 * Default to true for the unknown case. */
7626 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7628 } else if (*name == '.') {
7629 string_ncopy(opt_cdup, name, namelen);
7631 } else {
7632 string_ncopy(opt_prefix, name, namelen);
7633 }
7635 return OK;
7636 }
7638 static int
7639 load_repo_info(void)
7640 {
7641 const char *rev_parse_argv[] = {
7642 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7643 "--show-cdup", "--show-prefix", NULL
7644 };
7646 return io_run_load(rev_parse_argv, "=", read_repo_info);
7647 }
7650 /*
7651 * Main
7652 */
7654 static const char usage[] =
7655 "tig " TIG_VERSION " (" __DATE__ ")\n"
7656 "\n"
7657 "Usage: tig [options] [revs] [--] [paths]\n"
7658 " or: tig show [options] [revs] [--] [paths]\n"
7659 " or: tig blame [rev] path\n"
7660 " or: tig status\n"
7661 " or: tig < [git command output]\n"
7662 "\n"
7663 "Options:\n"
7664 " -v, --version Show version and exit\n"
7665 " -h, --help Show help message and exit";
7667 static void __NORETURN
7668 quit(int sig)
7669 {
7670 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7671 if (cursed)
7672 endwin();
7673 exit(0);
7674 }
7676 static void __NORETURN
7677 die(const char *err, ...)
7678 {
7679 va_list args;
7681 endwin();
7683 va_start(args, err);
7684 fputs("tig: ", stderr);
7685 vfprintf(stderr, err, args);
7686 fputs("\n", stderr);
7687 va_end(args);
7689 exit(1);
7690 }
7692 static void
7693 warn(const char *msg, ...)
7694 {
7695 va_list args;
7697 va_start(args, msg);
7698 fputs("tig warning: ", stderr);
7699 vfprintf(stderr, msg, args);
7700 fputs("\n", stderr);
7701 va_end(args);
7702 }
7704 static enum request
7705 parse_options(int argc, const char *argv[])
7706 {
7707 enum request request = REQ_VIEW_MAIN;
7708 const char *subcommand;
7709 bool seen_dashdash = FALSE;
7710 /* XXX: This is vulnerable to the user overriding options
7711 * required for the main view parser. */
7712 const char *custom_argv[SIZEOF_ARG] = {
7713 "git", "log", "--no-color", "--pretty=raw", "--parents",
7714 "--topo-order", NULL
7715 };
7716 int i, j = 6;
7718 if (!isatty(STDIN_FILENO)) {
7719 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7720 return REQ_VIEW_PAGER;
7721 }
7723 if (argc <= 1)
7724 return REQ_NONE;
7726 subcommand = argv[1];
7727 if (!strcmp(subcommand, "status")) {
7728 if (argc > 2)
7729 warn("ignoring arguments after `%s'", subcommand);
7730 return REQ_VIEW_STATUS;
7732 } else if (!strcmp(subcommand, "blame")) {
7733 if (argc <= 2 || argc > 4)
7734 die("invalid number of options to blame\n\n%s", usage);
7736 i = 2;
7737 if (argc == 4) {
7738 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7739 i++;
7740 }
7742 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7743 return REQ_VIEW_BLAME;
7745 } else if (!strcmp(subcommand, "show")) {
7746 request = REQ_VIEW_DIFF;
7748 } else {
7749 subcommand = NULL;
7750 }
7752 if (subcommand) {
7753 custom_argv[1] = subcommand;
7754 j = 2;
7755 }
7757 for (i = 1 + !!subcommand; i < argc; i++) {
7758 const char *opt = argv[i];
7760 if (seen_dashdash || !strcmp(opt, "--")) {
7761 seen_dashdash = TRUE;
7763 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7764 printf("tig version %s\n", TIG_VERSION);
7765 quit(0);
7767 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7768 printf("%s\n", usage);
7769 quit(0);
7770 }
7772 custom_argv[j++] = opt;
7773 if (j >= ARRAY_SIZE(custom_argv))
7774 die("command too long");
7775 }
7777 if (!prepare_update(VIEW(request), custom_argv, NULL))
7778 die("Failed to format arguments");
7780 return request;
7781 }
7783 int
7784 main(int argc, const char *argv[])
7785 {
7786 const char *codeset = "UTF-8";
7787 enum request request = parse_options(argc, argv);
7788 struct view *view;
7789 size_t i;
7791 signal(SIGINT, quit);
7792 signal(SIGPIPE, SIG_IGN);
7794 if (setlocale(LC_ALL, "")) {
7795 codeset = nl_langinfo(CODESET);
7796 }
7798 if (load_repo_info() == ERR)
7799 die("Failed to load repo info.");
7801 if (load_options() == ERR)
7802 die("Failed to load user config.");
7804 if (load_git_config() == ERR)
7805 die("Failed to load repo config.");
7807 /* Require a git repository unless when running in pager mode. */
7808 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7809 die("Not a git repository");
7811 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7812 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7813 if (opt_iconv_in == ICONV_NONE)
7814 die("Failed to initialize character set conversion");
7815 }
7817 if (codeset && strcmp(codeset, "UTF-8")) {
7818 opt_iconv_out = iconv_open(codeset, "UTF-8");
7819 if (opt_iconv_out == ICONV_NONE)
7820 die("Failed to initialize character set conversion");
7821 }
7823 if (load_refs() == ERR)
7824 die("Failed to load refs.");
7826 foreach_view (view, i)
7827 if (!argv_from_env(view->ops->argv, view->cmd_env))
7828 die("Too many arguments in the `%s` environment variable",
7829 view->cmd_env);
7831 init_display();
7833 if (request != REQ_NONE)
7834 open_view(NULL, request, OPEN_PREPARED);
7835 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7837 while (view_driver(display[current_view], request)) {
7838 int key = get_input(0);
7840 view = display[current_view];
7841 request = get_keybinding(view->keymap, key);
7843 /* Some low-level request handling. This keeps access to
7844 * status_win restricted. */
7845 switch (request) {
7846 case REQ_NONE:
7847 report("Unknown key, press %s for help",
7848 get_key(view->keymap, REQ_VIEW_HELP));
7849 break;
7850 case REQ_PROMPT:
7851 {
7852 char *cmd = read_prompt(":");
7854 if (cmd && isdigit(*cmd)) {
7855 int lineno = view->lineno + 1;
7857 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7858 select_view_line(view, lineno - 1);
7859 report("");
7860 } else {
7861 report("Unable to parse '%s' as a line number", cmd);
7862 }
7864 } else if (cmd) {
7865 struct view *next = VIEW(REQ_VIEW_PAGER);
7866 const char *argv[SIZEOF_ARG] = { "git" };
7867 int argc = 1;
7869 /* When running random commands, initially show the
7870 * command in the title. However, it maybe later be
7871 * overwritten if a commit line is selected. */
7872 string_ncopy(next->ref, cmd, strlen(cmd));
7874 if (!argv_from_string(argv, &argc, cmd)) {
7875 report("Too many arguments");
7876 } else if (!prepare_update(next, argv, NULL)) {
7877 report("Failed to format command");
7878 } else {
7879 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7880 }
7881 }
7883 request = REQ_NONE;
7884 break;
7885 }
7886 case REQ_SEARCH:
7887 case REQ_SEARCH_BACK:
7888 {
7889 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7890 char *search = read_prompt(prompt);
7892 if (search)
7893 string_ncopy(opt_search, search, strlen(search));
7894 else if (*opt_search)
7895 request = request == REQ_SEARCH ?
7896 REQ_FIND_NEXT :
7897 REQ_FIND_PREV;
7898 else
7899 request = REQ_NONE;
7900 break;
7901 }
7902 default:
7903 break;
7904 }
7905 }
7907 quit(0);
7909 return 0;
7910 }