dd155e35e23a6b11fd3aedc9e5883085372ebdb7
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 }
233 static char *
234 chomp_string(char *name)
235 {
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
246 }
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
250 {
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
262 }
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
272 {
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
287 }
289 return 0;
290 }
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
305 {
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
313 }
315 buf[bufpos] = 0;
316 return buf;
317 }
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
323 {
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
331 }
333 return FALSE;
334 }
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
344 {
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 }
352 /*
353 * Unicode / UTF-8 handling
354 *
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358 */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
362 {
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
382 }
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
394 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
399 {
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
403 }
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
408 {
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
447 }
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
452 }
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
458 *
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
462 {
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
491 }
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
499 }
500 break;
501 }
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
506 }
508 return string - *start;
509 }
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
529 };
531 struct time {
532 time_t sec;
533 int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
537 {
538 return t1->sec - t2->sec;
539 }
541 static const char *
542 mkdate(const struct time *time, enum date date)
543 {
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
552 };
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
577 }
578 }
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
583 }
584 else {
585 gmtime_r(&time->sec, &tm);
586 }
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
674 static void
675 argv_free(const char *argv[])
676 {
677 int argc;
679 if (!argv)
680 return;
681 for (argc = 0; argv[argc]; argc++)
682 free((void *) argv[argc]);
683 argv[0] = NULL;
684 }
686 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
688 static bool
689 argv_append(const char ***argv, const char *arg)
690 {
691 int argc = 0;
693 while (*argv && (*argv)[argc])
694 argc++;
696 if (!argv_realloc(argv, argc, 2))
697 return FALSE;
699 (*argv)[argc++] = strdup(arg);
700 (*argv)[argc] = NULL;
701 return TRUE;
702 }
704 static bool
705 argv_append_array(const char ***dst_argv, const char *src_argv[])
706 {
707 int i;
709 for (i = 0; src_argv && src_argv[i]; i++)
710 if (!argv_append(dst_argv, src_argv[i]))
711 return FALSE;
712 return TRUE;
713 }
715 static bool
716 argv_copy(const char ***dst, const char *src[])
717 {
718 int argc;
720 for (argc = 0; src[argc]; argc++)
721 if (!argv_append(dst, src[argc]))
722 return FALSE;
723 return TRUE;
724 }
727 /*
728 * Executing external commands.
729 */
731 enum io_type {
732 IO_FD, /* File descriptor based IO. */
733 IO_BG, /* Execute command in the background. */
734 IO_FG, /* Execute command with same std{in,out,err}. */
735 IO_RD, /* Read only fork+exec IO. */
736 IO_WR, /* Write only fork+exec IO. */
737 IO_AP, /* Append fork+exec output to file. */
738 };
740 struct io {
741 int pipe; /* Pipe end for reading or writing. */
742 pid_t pid; /* PID of spawned process. */
743 int error; /* Error status. */
744 char *buf; /* Read buffer. */
745 size_t bufalloc; /* Allocated buffer size. */
746 size_t bufsize; /* Buffer content size. */
747 char *bufpos; /* Current buffer position. */
748 unsigned int eof:1; /* Has end of file been reached. */
749 };
751 static void
752 io_init(struct io *io)
753 {
754 memset(io, 0, sizeof(*io));
755 io->pipe = -1;
756 }
758 static bool
759 io_open(struct io *io, const char *fmt, ...)
760 {
761 char name[SIZEOF_STR] = "";
762 bool fits;
763 va_list args;
765 io_init(io);
767 va_start(args, fmt);
768 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
769 va_end(args);
771 if (!fits) {
772 io->error = ENAMETOOLONG;
773 return FALSE;
774 }
775 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
776 if (io->pipe == -1)
777 io->error = errno;
778 return io->pipe != -1;
779 }
781 static bool
782 io_kill(struct io *io)
783 {
784 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
785 }
787 static bool
788 io_done(struct io *io)
789 {
790 pid_t pid = io->pid;
792 if (io->pipe != -1)
793 close(io->pipe);
794 free(io->buf);
795 io_init(io);
797 while (pid > 0) {
798 int status;
799 pid_t waiting = waitpid(pid, &status, 0);
801 if (waiting < 0) {
802 if (errno == EINTR)
803 continue;
804 io->error = errno;
805 return FALSE;
806 }
808 return waiting == pid &&
809 !WIFSIGNALED(status) &&
810 WIFEXITED(status) &&
811 !WEXITSTATUS(status);
812 }
814 return TRUE;
815 }
817 static bool
818 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
819 {
820 int pipefds[2] = { -1, -1 };
821 va_list args;
823 io_init(io);
825 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
826 io->error = errno;
827 return FALSE;
828 } else if (type == IO_AP) {
829 va_start(args, argv);
830 pipefds[1] = va_arg(args, int);
831 va_end(args);
832 }
834 if ((io->pid = fork())) {
835 if (io->pid == -1)
836 io->error = errno;
837 if (pipefds[!(type == IO_WR)] != -1)
838 close(pipefds[!(type == IO_WR)]);
839 if (io->pid != -1) {
840 io->pipe = pipefds[!!(type == IO_WR)];
841 return TRUE;
842 }
844 } else {
845 if (type != IO_FG) {
846 int devnull = open("/dev/null", O_RDWR);
847 int readfd = type == IO_WR ? pipefds[0] : devnull;
848 int writefd = (type == IO_RD || type == IO_AP)
849 ? pipefds[1] : devnull;
851 dup2(readfd, STDIN_FILENO);
852 dup2(writefd, STDOUT_FILENO);
853 dup2(devnull, STDERR_FILENO);
855 close(devnull);
856 if (pipefds[0] != -1)
857 close(pipefds[0]);
858 if (pipefds[1] != -1)
859 close(pipefds[1]);
860 }
862 if (dir && *dir && chdir(dir) == -1)
863 exit(errno);
865 execvp(argv[0], (char *const*) argv);
866 exit(errno);
867 }
869 if (pipefds[!!(type == IO_WR)] != -1)
870 close(pipefds[!!(type == IO_WR)]);
871 return FALSE;
872 }
874 static bool
875 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
876 {
877 struct io io;
879 return io_run(&io, type, dir, argv, fd) && io_done(&io);
880 }
882 static bool
883 io_run_bg(const char **argv)
884 {
885 return io_complete(IO_BG, argv, NULL, -1);
886 }
888 static bool
889 io_run_fg(const char **argv, const char *dir)
890 {
891 return io_complete(IO_FG, argv, dir, -1);
892 }
894 static bool
895 io_run_append(const char **argv, int fd)
896 {
897 return io_complete(IO_AP, argv, NULL, fd);
898 }
900 static bool
901 io_eof(struct io *io)
902 {
903 return io->eof;
904 }
906 static int
907 io_error(struct io *io)
908 {
909 return io->error;
910 }
912 static char *
913 io_strerror(struct io *io)
914 {
915 return strerror(io->error);
916 }
918 static bool
919 io_can_read(struct io *io)
920 {
921 struct timeval tv = { 0, 500 };
922 fd_set fds;
924 FD_ZERO(&fds);
925 FD_SET(io->pipe, &fds);
927 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
928 }
930 static ssize_t
931 io_read(struct io *io, void *buf, size_t bufsize)
932 {
933 do {
934 ssize_t readsize = read(io->pipe, buf, bufsize);
936 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
937 continue;
938 else if (readsize == -1)
939 io->error = errno;
940 else if (readsize == 0)
941 io->eof = 1;
942 return readsize;
943 } while (1);
944 }
946 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
948 static char *
949 io_get(struct io *io, int c, bool can_read)
950 {
951 char *eol;
952 ssize_t readsize;
954 while (TRUE) {
955 if (io->bufsize > 0) {
956 eol = memchr(io->bufpos, c, io->bufsize);
957 if (eol) {
958 char *line = io->bufpos;
960 *eol = 0;
961 io->bufpos = eol + 1;
962 io->bufsize -= io->bufpos - line;
963 return line;
964 }
965 }
967 if (io_eof(io)) {
968 if (io->bufsize) {
969 io->bufpos[io->bufsize] = 0;
970 io->bufsize = 0;
971 return io->bufpos;
972 }
973 return NULL;
974 }
976 if (!can_read)
977 return NULL;
979 if (io->bufsize > 0 && io->bufpos > io->buf)
980 memmove(io->buf, io->bufpos, io->bufsize);
982 if (io->bufalloc == io->bufsize) {
983 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
984 return NULL;
985 io->bufalloc += BUFSIZ;
986 }
988 io->bufpos = io->buf;
989 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
990 if (io_error(io))
991 return NULL;
992 io->bufsize += readsize;
993 }
994 }
996 static bool
997 io_write(struct io *io, const void *buf, size_t bufsize)
998 {
999 size_t written = 0;
1001 while (!io_error(io) && written < bufsize) {
1002 ssize_t size;
1004 size = write(io->pipe, buf + written, bufsize - written);
1005 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1006 continue;
1007 else if (size == -1)
1008 io->error = errno;
1009 else
1010 written += size;
1011 }
1013 return written == bufsize;
1014 }
1016 static bool
1017 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018 {
1019 char *result = io_get(io, '\n', TRUE);
1021 if (result) {
1022 result = chomp_string(result);
1023 string_ncopy_do(buf, bufsize, result, strlen(result));
1024 }
1026 return io_done(io) && result;
1027 }
1029 static bool
1030 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031 {
1032 struct io io;
1034 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1035 }
1037 static int
1038 io_load(struct io *io, const char *separators,
1039 int (*read_property)(char *, size_t, char *, size_t))
1040 {
1041 char *name;
1042 int state = OK;
1044 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1045 char *value;
1046 size_t namelen;
1047 size_t valuelen;
1049 name = chomp_string(name);
1050 namelen = strcspn(name, separators);
1052 if (name[namelen]) {
1053 name[namelen] = 0;
1054 value = chomp_string(name + namelen + 1);
1055 valuelen = strlen(value);
1057 } else {
1058 value = "";
1059 valuelen = 0;
1060 }
1062 state = read_property(name, namelen, value, valuelen);
1063 }
1065 if (state != ERR && io_error(io))
1066 state = ERR;
1067 io_done(io);
1069 return state;
1070 }
1072 static int
1073 io_run_load(const char **argv, const char *separators,
1074 int (*read_property)(char *, size_t, char *, size_t))
1075 {
1076 struct io io;
1078 if (!io_run(&io, IO_RD, NULL, argv))
1079 return ERR;
1080 return io_load(&io, separators, read_property);
1081 }
1084 /*
1085 * User requests
1086 */
1088 #define REQ_INFO \
1089 /* XXX: Keep the view request first and in sync with views[]. */ \
1090 REQ_GROUP("View switching") \
1091 REQ_(VIEW_MAIN, "Show main view"), \
1092 REQ_(VIEW_DIFF, "Show diff view"), \
1093 REQ_(VIEW_LOG, "Show log view"), \
1094 REQ_(VIEW_TREE, "Show tree view"), \
1095 REQ_(VIEW_BLOB, "Show blob view"), \
1096 REQ_(VIEW_BLAME, "Show blame view"), \
1097 REQ_(VIEW_BRANCH, "Show branch view"), \
1098 REQ_(VIEW_HELP, "Show help page"), \
1099 REQ_(VIEW_PAGER, "Show pager view"), \
1100 REQ_(VIEW_STATUS, "Show status view"), \
1101 REQ_(VIEW_STAGE, "Show stage view"), \
1102 \
1103 REQ_GROUP("View manipulation") \
1104 REQ_(ENTER, "Enter current line and scroll"), \
1105 REQ_(NEXT, "Move to next"), \
1106 REQ_(PREVIOUS, "Move to previous"), \
1107 REQ_(PARENT, "Move to parent"), \
1108 REQ_(VIEW_NEXT, "Move focus to next view"), \
1109 REQ_(REFRESH, "Reload and refresh"), \
1110 REQ_(MAXIMIZE, "Maximize the current view"), \
1111 REQ_(VIEW_CLOSE, "Close the current view"), \
1112 REQ_(QUIT, "Close all views and quit"), \
1113 \
1114 REQ_GROUP("View specific requests") \
1115 REQ_(STATUS_UPDATE, "Update file status"), \
1116 REQ_(STATUS_REVERT, "Revert file changes"), \
1117 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1118 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1119 \
1120 REQ_GROUP("Cursor navigation") \
1121 REQ_(MOVE_UP, "Move cursor one line up"), \
1122 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1123 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1124 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1125 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1126 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1127 \
1128 REQ_GROUP("Scrolling") \
1129 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1130 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1131 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1132 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1133 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1134 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1135 \
1136 REQ_GROUP("Searching") \
1137 REQ_(SEARCH, "Search the view"), \
1138 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1139 REQ_(FIND_NEXT, "Find next search match"), \
1140 REQ_(FIND_PREV, "Find previous search match"), \
1141 \
1142 REQ_GROUP("Option manipulation") \
1143 REQ_(OPTIONS, "Open option menu"), \
1144 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1145 REQ_(TOGGLE_DATE, "Toggle date display"), \
1146 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1147 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1148 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1149 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1150 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1151 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1152 \
1153 REQ_GROUP("Misc") \
1154 REQ_(PROMPT, "Bring up the prompt"), \
1155 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1156 REQ_(SHOW_VERSION, "Show version information"), \
1157 REQ_(STOP_LOADING, "Stop all loading views"), \
1158 REQ_(EDIT, "Open in editor"), \
1159 REQ_(NONE, "Do nothing")
1162 /* User action requests. */
1163 enum request {
1164 #define REQ_GROUP(help)
1165 #define REQ_(req, help) REQ_##req
1167 /* Offset all requests to avoid conflicts with ncurses getch values. */
1168 REQ_UNKNOWN = KEY_MAX + 1,
1169 REQ_OFFSET,
1170 REQ_INFO
1172 #undef REQ_GROUP
1173 #undef REQ_
1174 };
1176 struct request_info {
1177 enum request request;
1178 const char *name;
1179 int namelen;
1180 const char *help;
1181 };
1183 static const struct request_info req_info[] = {
1184 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1185 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1186 REQ_INFO
1187 #undef REQ_GROUP
1188 #undef REQ_
1189 };
1191 static enum request
1192 get_request(const char *name)
1193 {
1194 int namelen = strlen(name);
1195 int i;
1197 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1198 if (enum_equals(req_info[i], name, namelen))
1199 return req_info[i].request;
1201 return REQ_UNKNOWN;
1202 }
1205 /*
1206 * Options
1207 */
1209 /* Option and state variables. */
1210 static enum date opt_date = DATE_DEFAULT;
1211 static enum author opt_author = AUTHOR_DEFAULT;
1212 static bool opt_line_number = FALSE;
1213 static bool opt_line_graphics = TRUE;
1214 static bool opt_rev_graph = FALSE;
1215 static bool opt_show_refs = TRUE;
1216 static int opt_num_interval = 5;
1217 static double opt_hscroll = 0.50;
1218 static double opt_scale_split_view = 2.0 / 3.0;
1219 static int opt_tab_size = 8;
1220 static int opt_author_cols = AUTHOR_COLS;
1221 static char opt_path[SIZEOF_STR] = "";
1222 static char opt_file[SIZEOF_STR] = "";
1223 static char opt_ref[SIZEOF_REF] = "";
1224 static char opt_head[SIZEOF_REF] = "";
1225 static char opt_remote[SIZEOF_REF] = "";
1226 static char opt_encoding[20] = "UTF-8";
1227 static iconv_t opt_iconv_in = ICONV_NONE;
1228 static iconv_t opt_iconv_out = ICONV_NONE;
1229 static char opt_search[SIZEOF_STR] = "";
1230 static char opt_cdup[SIZEOF_STR] = "";
1231 static char opt_prefix[SIZEOF_STR] = "";
1232 static char opt_git_dir[SIZEOF_STR] = "";
1233 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1234 static char opt_editor[SIZEOF_STR] = "";
1235 static FILE *opt_tty = NULL;
1236 static const char **opt_diff_args = NULL;
1237 static const char **opt_rev_args = NULL;
1238 static const char **opt_file_args = NULL;
1240 #define is_initial_commit() (!get_ref_head())
1241 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1244 /*
1245 * Line-oriented content detection.
1246 */
1248 #define LINE_INFO \
1249 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1261 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1262 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1263 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1264 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1265 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1266 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1268 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1269 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1270 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1271 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1272 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1274 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1275 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1278 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1279 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1280 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1281 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1282 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1283 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1284 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1285 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1286 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1287 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1288 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1290 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1291 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1294 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1295 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1296 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1297 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1298 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1299 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1300 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1301 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1302 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1303 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1304 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1305 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1306 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1307 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1309 enum line_type {
1310 #define LINE(type, line, fg, bg, attr) \
1311 LINE_##type
1312 LINE_INFO,
1313 LINE_NONE
1314 #undef LINE
1315 };
1317 struct line_info {
1318 const char *name; /* Option name. */
1319 int namelen; /* Size of option name. */
1320 const char *line; /* The start of line to match. */
1321 int linelen; /* Size of string to match. */
1322 int fg, bg, attr; /* Color and text attributes for the lines. */
1323 };
1325 static struct line_info line_info[] = {
1326 #define LINE(type, line, fg, bg, attr) \
1327 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1328 LINE_INFO
1329 #undef LINE
1330 };
1332 static enum line_type
1333 get_line_type(const char *line)
1334 {
1335 int linelen = strlen(line);
1336 enum line_type type;
1338 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1339 /* Case insensitive search matches Signed-off-by lines better. */
1340 if (linelen >= line_info[type].linelen &&
1341 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1342 return type;
1344 return LINE_DEFAULT;
1345 }
1347 static inline int
1348 get_line_attr(enum line_type type)
1349 {
1350 assert(type < ARRAY_SIZE(line_info));
1351 return COLOR_PAIR(type) | line_info[type].attr;
1352 }
1354 static struct line_info *
1355 get_line_info(const char *name)
1356 {
1357 size_t namelen = strlen(name);
1358 enum line_type type;
1360 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1361 if (enum_equals(line_info[type], name, namelen))
1362 return &line_info[type];
1364 return NULL;
1365 }
1367 static void
1368 init_colors(void)
1369 {
1370 int default_bg = line_info[LINE_DEFAULT].bg;
1371 int default_fg = line_info[LINE_DEFAULT].fg;
1372 enum line_type type;
1374 start_color();
1376 if (assume_default_colors(default_fg, default_bg) == ERR) {
1377 default_bg = COLOR_BLACK;
1378 default_fg = COLOR_WHITE;
1379 }
1381 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1382 struct line_info *info = &line_info[type];
1383 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1384 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1386 init_pair(type, fg, bg);
1387 }
1388 }
1390 struct line {
1391 enum line_type type;
1393 /* State flags */
1394 unsigned int selected:1;
1395 unsigned int dirty:1;
1396 unsigned int cleareol:1;
1397 unsigned int other:16;
1399 void *data; /* User data */
1400 };
1403 /*
1404 * Keys
1405 */
1407 struct keybinding {
1408 int alias;
1409 enum request request;
1410 };
1412 static struct keybinding default_keybindings[] = {
1413 /* View switching */
1414 { 'm', REQ_VIEW_MAIN },
1415 { 'd', REQ_VIEW_DIFF },
1416 { 'l', REQ_VIEW_LOG },
1417 { 't', REQ_VIEW_TREE },
1418 { 'f', REQ_VIEW_BLOB },
1419 { 'B', REQ_VIEW_BLAME },
1420 { 'H', REQ_VIEW_BRANCH },
1421 { 'p', REQ_VIEW_PAGER },
1422 { 'h', REQ_VIEW_HELP },
1423 { 'S', REQ_VIEW_STATUS },
1424 { 'c', REQ_VIEW_STAGE },
1426 /* View manipulation */
1427 { 'q', REQ_VIEW_CLOSE },
1428 { KEY_TAB, REQ_VIEW_NEXT },
1429 { KEY_RETURN, REQ_ENTER },
1430 { KEY_UP, REQ_PREVIOUS },
1431 { KEY_DOWN, REQ_NEXT },
1432 { 'R', REQ_REFRESH },
1433 { KEY_F(5), REQ_REFRESH },
1434 { 'O', REQ_MAXIMIZE },
1436 /* Cursor navigation */
1437 { 'k', REQ_MOVE_UP },
1438 { 'j', REQ_MOVE_DOWN },
1439 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1440 { KEY_END, REQ_MOVE_LAST_LINE },
1441 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1442 { ' ', REQ_MOVE_PAGE_DOWN },
1443 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1444 { 'b', REQ_MOVE_PAGE_UP },
1445 { '-', REQ_MOVE_PAGE_UP },
1447 /* Scrolling */
1448 { KEY_LEFT, REQ_SCROLL_LEFT },
1449 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1450 { KEY_IC, REQ_SCROLL_LINE_UP },
1451 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1452 { 'w', REQ_SCROLL_PAGE_UP },
1453 { 's', REQ_SCROLL_PAGE_DOWN },
1455 /* Searching */
1456 { '/', REQ_SEARCH },
1457 { '?', REQ_SEARCH_BACK },
1458 { 'n', REQ_FIND_NEXT },
1459 { 'N', REQ_FIND_PREV },
1461 /* Misc */
1462 { 'Q', REQ_QUIT },
1463 { 'z', REQ_STOP_LOADING },
1464 { 'v', REQ_SHOW_VERSION },
1465 { 'r', REQ_SCREEN_REDRAW },
1466 { 'o', REQ_OPTIONS },
1467 { '.', REQ_TOGGLE_LINENO },
1468 { 'D', REQ_TOGGLE_DATE },
1469 { 'A', REQ_TOGGLE_AUTHOR },
1470 { 'g', REQ_TOGGLE_REV_GRAPH },
1471 { 'F', REQ_TOGGLE_REFS },
1472 { 'I', REQ_TOGGLE_SORT_ORDER },
1473 { 'i', REQ_TOGGLE_SORT_FIELD },
1474 { ':', REQ_PROMPT },
1475 { 'u', REQ_STATUS_UPDATE },
1476 { '!', REQ_STATUS_REVERT },
1477 { 'M', REQ_STATUS_MERGE },
1478 { '@', REQ_STAGE_NEXT },
1479 { ',', REQ_PARENT },
1480 { 'e', REQ_EDIT },
1481 };
1483 #define KEYMAP_INFO \
1484 KEYMAP_(GENERIC), \
1485 KEYMAP_(MAIN), \
1486 KEYMAP_(DIFF), \
1487 KEYMAP_(LOG), \
1488 KEYMAP_(TREE), \
1489 KEYMAP_(BLOB), \
1490 KEYMAP_(BLAME), \
1491 KEYMAP_(BRANCH), \
1492 KEYMAP_(PAGER), \
1493 KEYMAP_(HELP), \
1494 KEYMAP_(STATUS), \
1495 KEYMAP_(STAGE)
1497 enum keymap {
1498 #define KEYMAP_(name) KEYMAP_##name
1499 KEYMAP_INFO
1500 #undef KEYMAP_
1501 };
1503 static const struct enum_map keymap_table[] = {
1504 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1505 KEYMAP_INFO
1506 #undef KEYMAP_
1507 };
1509 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1511 struct keybinding_table {
1512 struct keybinding *data;
1513 size_t size;
1514 };
1516 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1518 static void
1519 add_keybinding(enum keymap keymap, enum request request, int key)
1520 {
1521 struct keybinding_table *table = &keybindings[keymap];
1522 size_t i;
1524 for (i = 0; i < keybindings[keymap].size; i++) {
1525 if (keybindings[keymap].data[i].alias == key) {
1526 keybindings[keymap].data[i].request = request;
1527 return;
1528 }
1529 }
1531 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1532 if (!table->data)
1533 die("Failed to allocate keybinding");
1534 table->data[table->size].alias = key;
1535 table->data[table->size++].request = request;
1537 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1538 int i;
1540 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1541 if (default_keybindings[i].alias == key)
1542 default_keybindings[i].request = REQ_NONE;
1543 }
1544 }
1546 /* Looks for a key binding first in the given map, then in the generic map, and
1547 * lastly in the default keybindings. */
1548 static enum request
1549 get_keybinding(enum keymap keymap, int key)
1550 {
1551 size_t i;
1553 for (i = 0; i < keybindings[keymap].size; i++)
1554 if (keybindings[keymap].data[i].alias == key)
1555 return keybindings[keymap].data[i].request;
1557 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1558 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1559 return keybindings[KEYMAP_GENERIC].data[i].request;
1561 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562 if (default_keybindings[i].alias == key)
1563 return default_keybindings[i].request;
1565 return (enum request) key;
1566 }
1569 struct key {
1570 const char *name;
1571 int value;
1572 };
1574 static const struct key key_table[] = {
1575 { "Enter", KEY_RETURN },
1576 { "Space", ' ' },
1577 { "Backspace", KEY_BACKSPACE },
1578 { "Tab", KEY_TAB },
1579 { "Escape", KEY_ESC },
1580 { "Left", KEY_LEFT },
1581 { "Right", KEY_RIGHT },
1582 { "Up", KEY_UP },
1583 { "Down", KEY_DOWN },
1584 { "Insert", KEY_IC },
1585 { "Delete", KEY_DC },
1586 { "Hash", '#' },
1587 { "Home", KEY_HOME },
1588 { "End", KEY_END },
1589 { "PageUp", KEY_PPAGE },
1590 { "PageDown", KEY_NPAGE },
1591 { "F1", KEY_F(1) },
1592 { "F2", KEY_F(2) },
1593 { "F3", KEY_F(3) },
1594 { "F4", KEY_F(4) },
1595 { "F5", KEY_F(5) },
1596 { "F6", KEY_F(6) },
1597 { "F7", KEY_F(7) },
1598 { "F8", KEY_F(8) },
1599 { "F9", KEY_F(9) },
1600 { "F10", KEY_F(10) },
1601 { "F11", KEY_F(11) },
1602 { "F12", KEY_F(12) },
1603 };
1605 static int
1606 get_key_value(const char *name)
1607 {
1608 int i;
1610 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1611 if (!strcasecmp(key_table[i].name, name))
1612 return key_table[i].value;
1614 if (strlen(name) == 1 && isprint(*name))
1615 return (int) *name;
1617 return ERR;
1618 }
1620 static const char *
1621 get_key_name(int key_value)
1622 {
1623 static char key_char[] = "'X'";
1624 const char *seq = NULL;
1625 int key;
1627 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1628 if (key_table[key].value == key_value)
1629 seq = key_table[key].name;
1631 if (seq == NULL &&
1632 key_value < 127 &&
1633 isprint(key_value)) {
1634 key_char[1] = (char) key_value;
1635 seq = key_char;
1636 }
1638 return seq ? seq : "(no key)";
1639 }
1641 static bool
1642 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1643 {
1644 const char *sep = *pos > 0 ? ", " : "";
1645 const char *keyname = get_key_name(keybinding->alias);
1647 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1648 }
1650 static bool
1651 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1652 enum keymap keymap, bool all)
1653 {
1654 int i;
1656 for (i = 0; i < keybindings[keymap].size; i++) {
1657 if (keybindings[keymap].data[i].request == request) {
1658 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1659 return FALSE;
1660 if (!all)
1661 break;
1662 }
1663 }
1665 return TRUE;
1666 }
1668 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1670 static const char *
1671 get_keys(enum keymap keymap, enum request request, bool all)
1672 {
1673 static char buf[BUFSIZ];
1674 size_t pos = 0;
1675 int i;
1677 buf[pos] = 0;
1679 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1680 return "Too many keybindings!";
1681 if (pos > 0 && !all)
1682 return buf;
1684 if (keymap != KEYMAP_GENERIC) {
1685 /* Only the generic keymap includes the default keybindings when
1686 * listing all keys. */
1687 if (all)
1688 return buf;
1690 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1691 return "Too many keybindings!";
1692 if (pos)
1693 return buf;
1694 }
1696 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1697 if (default_keybindings[i].request == request) {
1698 if (!append_key(buf, &pos, &default_keybindings[i]))
1699 return "Too many keybindings!";
1700 if (!all)
1701 return buf;
1702 }
1703 }
1705 return buf;
1706 }
1708 struct run_request {
1709 enum keymap keymap;
1710 int key;
1711 const char **argv;
1712 };
1714 static struct run_request *run_request;
1715 static size_t run_requests;
1717 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1719 static enum request
1720 add_run_request(enum keymap keymap, int key, const char **argv)
1721 {
1722 struct run_request *req;
1724 if (!realloc_run_requests(&run_request, run_requests, 1))
1725 return REQ_NONE;
1727 req = &run_request[run_requests];
1728 req->keymap = keymap;
1729 req->key = key;
1730 req->argv = NULL;
1732 if (!argv_copy(&req->argv, argv))
1733 return REQ_NONE;
1735 return REQ_NONE + ++run_requests;
1736 }
1738 static struct run_request *
1739 get_run_request(enum request request)
1740 {
1741 if (request <= REQ_NONE)
1742 return NULL;
1743 return &run_request[request - REQ_NONE - 1];
1744 }
1746 static void
1747 add_builtin_run_requests(void)
1748 {
1749 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1750 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1751 const char *commit[] = { "git", "commit", NULL };
1752 const char *gc[] = { "git", "gc", NULL };
1753 struct run_request reqs[] = {
1754 { KEYMAP_MAIN, 'C', cherry_pick },
1755 { KEYMAP_STATUS, 'C', commit },
1756 { KEYMAP_BRANCH, 'C', checkout },
1757 { KEYMAP_GENERIC, 'G', gc },
1758 };
1759 int i;
1761 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1762 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1764 if (req != reqs[i].key)
1765 continue;
1766 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1767 if (req != REQ_NONE)
1768 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1769 }
1770 }
1772 /*
1773 * User config file handling.
1774 */
1776 static int config_lineno;
1777 static bool config_errors;
1778 static const char *config_msg;
1780 static const struct enum_map color_map[] = {
1781 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1782 COLOR_MAP(DEFAULT),
1783 COLOR_MAP(BLACK),
1784 COLOR_MAP(BLUE),
1785 COLOR_MAP(CYAN),
1786 COLOR_MAP(GREEN),
1787 COLOR_MAP(MAGENTA),
1788 COLOR_MAP(RED),
1789 COLOR_MAP(WHITE),
1790 COLOR_MAP(YELLOW),
1791 };
1793 static const struct enum_map attr_map[] = {
1794 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1795 ATTR_MAP(NORMAL),
1796 ATTR_MAP(BLINK),
1797 ATTR_MAP(BOLD),
1798 ATTR_MAP(DIM),
1799 ATTR_MAP(REVERSE),
1800 ATTR_MAP(STANDOUT),
1801 ATTR_MAP(UNDERLINE),
1802 };
1804 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1806 static int parse_step(double *opt, const char *arg)
1807 {
1808 *opt = atoi(arg);
1809 if (!strchr(arg, '%'))
1810 return OK;
1812 /* "Shift down" so 100% and 1 does not conflict. */
1813 *opt = (*opt - 1) / 100;
1814 if (*opt >= 1.0) {
1815 *opt = 0.99;
1816 config_msg = "Step value larger than 100%";
1817 return ERR;
1818 }
1819 if (*opt < 0.0) {
1820 *opt = 1;
1821 config_msg = "Invalid step value";
1822 return ERR;
1823 }
1824 return OK;
1825 }
1827 static int
1828 parse_int(int *opt, const char *arg, int min, int max)
1829 {
1830 int value = atoi(arg);
1832 if (min <= value && value <= max) {
1833 *opt = value;
1834 return OK;
1835 }
1837 config_msg = "Integer value out of bound";
1838 return ERR;
1839 }
1841 static bool
1842 set_color(int *color, const char *name)
1843 {
1844 if (map_enum(color, color_map, name))
1845 return TRUE;
1846 if (!prefixcmp(name, "color"))
1847 return parse_int(color, name + 5, 0, 255) == OK;
1848 return FALSE;
1849 }
1851 /* Wants: object fgcolor bgcolor [attribute] */
1852 static int
1853 option_color_command(int argc, const char *argv[])
1854 {
1855 struct line_info *info;
1857 if (argc < 3) {
1858 config_msg = "Wrong number of arguments given to color command";
1859 return ERR;
1860 }
1862 info = get_line_info(argv[0]);
1863 if (!info) {
1864 static const struct enum_map obsolete[] = {
1865 ENUM_MAP("main-delim", LINE_DELIMITER),
1866 ENUM_MAP("main-date", LINE_DATE),
1867 ENUM_MAP("main-author", LINE_AUTHOR),
1868 };
1869 int index;
1871 if (!map_enum(&index, obsolete, argv[0])) {
1872 config_msg = "Unknown color name";
1873 return ERR;
1874 }
1875 info = &line_info[index];
1876 }
1878 if (!set_color(&info->fg, argv[1]) ||
1879 !set_color(&info->bg, argv[2])) {
1880 config_msg = "Unknown color";
1881 return ERR;
1882 }
1884 info->attr = 0;
1885 while (argc-- > 3) {
1886 int attr;
1888 if (!set_attribute(&attr, argv[argc])) {
1889 config_msg = "Unknown attribute";
1890 return ERR;
1891 }
1892 info->attr |= attr;
1893 }
1895 return OK;
1896 }
1898 static int parse_bool(bool *opt, const char *arg)
1899 {
1900 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1901 ? TRUE : FALSE;
1902 return OK;
1903 }
1905 static int parse_enum_do(unsigned int *opt, const char *arg,
1906 const struct enum_map *map, size_t map_size)
1907 {
1908 bool is_true;
1910 assert(map_size > 1);
1912 if (map_enum_do(map, map_size, (int *) opt, arg))
1913 return OK;
1915 if (parse_bool(&is_true, arg) != OK)
1916 return ERR;
1918 *opt = is_true ? map[1].value : map[0].value;
1919 return OK;
1920 }
1922 #define parse_enum(opt, arg, map) \
1923 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1925 static int
1926 parse_string(char *opt, const char *arg, size_t optsize)
1927 {
1928 int arglen = strlen(arg);
1930 switch (arg[0]) {
1931 case '\"':
1932 case '\'':
1933 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1934 config_msg = "Unmatched quotation";
1935 return ERR;
1936 }
1937 arg += 1; arglen -= 2;
1938 default:
1939 string_ncopy_do(opt, optsize, arg, arglen);
1940 return OK;
1941 }
1942 }
1944 /* Wants: name = value */
1945 static int
1946 option_set_command(int argc, const char *argv[])
1947 {
1948 if (argc != 3) {
1949 config_msg = "Wrong number of arguments given to set command";
1950 return ERR;
1951 }
1953 if (strcmp(argv[1], "=")) {
1954 config_msg = "No value assigned";
1955 return ERR;
1956 }
1958 if (!strcmp(argv[0], "show-author"))
1959 return parse_enum(&opt_author, argv[2], author_map);
1961 if (!strcmp(argv[0], "show-date"))
1962 return parse_enum(&opt_date, argv[2], date_map);
1964 if (!strcmp(argv[0], "show-rev-graph"))
1965 return parse_bool(&opt_rev_graph, argv[2]);
1967 if (!strcmp(argv[0], "show-refs"))
1968 return parse_bool(&opt_show_refs, argv[2]);
1970 if (!strcmp(argv[0], "show-line-numbers"))
1971 return parse_bool(&opt_line_number, argv[2]);
1973 if (!strcmp(argv[0], "line-graphics"))
1974 return parse_bool(&opt_line_graphics, argv[2]);
1976 if (!strcmp(argv[0], "line-number-interval"))
1977 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1979 if (!strcmp(argv[0], "author-width"))
1980 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1982 if (!strcmp(argv[0], "horizontal-scroll"))
1983 return parse_step(&opt_hscroll, argv[2]);
1985 if (!strcmp(argv[0], "split-view-height"))
1986 return parse_step(&opt_scale_split_view, argv[2]);
1988 if (!strcmp(argv[0], "tab-size"))
1989 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1991 if (!strcmp(argv[0], "commit-encoding"))
1992 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1994 config_msg = "Unknown variable name";
1995 return ERR;
1996 }
1998 /* Wants: mode request key */
1999 static int
2000 option_bind_command(int argc, const char *argv[])
2001 {
2002 enum request request;
2003 int keymap = -1;
2004 int key;
2006 if (argc < 3) {
2007 config_msg = "Wrong number of arguments given to bind command";
2008 return ERR;
2009 }
2011 if (!set_keymap(&keymap, argv[0])) {
2012 config_msg = "Unknown key map";
2013 return ERR;
2014 }
2016 key = get_key_value(argv[1]);
2017 if (key == ERR) {
2018 config_msg = "Unknown key";
2019 return ERR;
2020 }
2022 request = get_request(argv[2]);
2023 if (request == REQ_UNKNOWN) {
2024 static const struct enum_map obsolete[] = {
2025 ENUM_MAP("cherry-pick", REQ_NONE),
2026 ENUM_MAP("screen-resize", REQ_NONE),
2027 ENUM_MAP("tree-parent", REQ_PARENT),
2028 };
2029 int alias;
2031 if (map_enum(&alias, obsolete, argv[2])) {
2032 if (alias != REQ_NONE)
2033 add_keybinding(keymap, alias, key);
2034 config_msg = "Obsolete request name";
2035 return ERR;
2036 }
2037 }
2038 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2039 request = add_run_request(keymap, key, argv + 2);
2040 if (request == REQ_UNKNOWN) {
2041 config_msg = "Unknown request name";
2042 return ERR;
2043 }
2045 add_keybinding(keymap, request, key);
2047 return OK;
2048 }
2050 static int
2051 set_option(const char *opt, char *value)
2052 {
2053 const char *argv[SIZEOF_ARG];
2054 int argc = 0;
2056 if (!argv_from_string(argv, &argc, value)) {
2057 config_msg = "Too many option arguments";
2058 return ERR;
2059 }
2061 if (!strcmp(opt, "color"))
2062 return option_color_command(argc, argv);
2064 if (!strcmp(opt, "set"))
2065 return option_set_command(argc, argv);
2067 if (!strcmp(opt, "bind"))
2068 return option_bind_command(argc, argv);
2070 config_msg = "Unknown option command";
2071 return ERR;
2072 }
2074 static int
2075 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2076 {
2077 int status = OK;
2079 config_lineno++;
2080 config_msg = "Internal error";
2082 /* Check for comment markers, since read_properties() will
2083 * only ensure opt and value are split at first " \t". */
2084 optlen = strcspn(opt, "#");
2085 if (optlen == 0)
2086 return OK;
2088 if (opt[optlen] != 0) {
2089 config_msg = "No option value";
2090 status = ERR;
2092 } else {
2093 /* Look for comment endings in the value. */
2094 size_t len = strcspn(value, "#");
2096 if (len < valuelen) {
2097 valuelen = len;
2098 value[valuelen] = 0;
2099 }
2101 status = set_option(opt, value);
2102 }
2104 if (status == ERR) {
2105 warn("Error on line %d, near '%.*s': %s",
2106 config_lineno, (int) optlen, opt, config_msg);
2107 config_errors = TRUE;
2108 }
2110 /* Always keep going if errors are encountered. */
2111 return OK;
2112 }
2114 static void
2115 load_option_file(const char *path)
2116 {
2117 struct io io;
2119 /* It's OK that the file doesn't exist. */
2120 if (!io_open(&io, "%s", path))
2121 return;
2123 config_lineno = 0;
2124 config_errors = FALSE;
2126 if (io_load(&io, " \t", read_option) == ERR ||
2127 config_errors == TRUE)
2128 warn("Errors while loading %s.", path);
2129 }
2131 static int
2132 load_options(void)
2133 {
2134 const char *home = getenv("HOME");
2135 const char *tigrc_user = getenv("TIGRC_USER");
2136 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2137 char buf[SIZEOF_STR];
2139 if (!tigrc_system)
2140 tigrc_system = SYSCONFDIR "/tigrc";
2141 load_option_file(tigrc_system);
2143 if (!tigrc_user) {
2144 if (!home || !string_format(buf, "%s/.tigrc", home))
2145 return ERR;
2146 tigrc_user = buf;
2147 }
2148 load_option_file(tigrc_user);
2150 /* Add _after_ loading config files to avoid adding run requests
2151 * that conflict with keybindings. */
2152 add_builtin_run_requests();
2154 return OK;
2155 }
2158 /*
2159 * The viewer
2160 */
2162 struct view;
2163 struct view_ops;
2165 /* The display array of active views and the index of the current view. */
2166 static struct view *display[2];
2167 static unsigned int current_view;
2169 #define foreach_displayed_view(view, i) \
2170 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2172 #define displayed_views() (display[1] != NULL ? 2 : 1)
2174 /* Current head and commit ID */
2175 static char ref_blob[SIZEOF_REF] = "";
2176 static char ref_commit[SIZEOF_REF] = "HEAD";
2177 static char ref_head[SIZEOF_REF] = "HEAD";
2178 static char ref_branch[SIZEOF_REF] = "";
2180 enum view_type {
2181 VIEW_MAIN,
2182 VIEW_DIFF,
2183 VIEW_LOG,
2184 VIEW_TREE,
2185 VIEW_BLOB,
2186 VIEW_BLAME,
2187 VIEW_BRANCH,
2188 VIEW_HELP,
2189 VIEW_PAGER,
2190 VIEW_STATUS,
2191 VIEW_STAGE,
2192 };
2194 struct view {
2195 enum view_type type; /* View type */
2196 const char *name; /* View name */
2197 const char *cmd_env; /* Command line set via environment */
2198 const char *id; /* Points to either of ref_{head,commit,blob} */
2200 struct view_ops *ops; /* View operations */
2202 enum keymap keymap; /* What keymap does this view have */
2203 bool git_dir; /* Whether the view requires a git directory. */
2205 char ref[SIZEOF_REF]; /* Hovered commit reference */
2206 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2208 int height, width; /* The width and height of the main window */
2209 WINDOW *win; /* The main window */
2210 WINDOW *title; /* The title window living below the main window */
2212 /* Navigation */
2213 unsigned long offset; /* Offset of the window top */
2214 unsigned long yoffset; /* Offset from the window side. */
2215 unsigned long lineno; /* Current line number */
2216 unsigned long p_offset; /* Previous offset of the window top */
2217 unsigned long p_yoffset;/* Previous offset from the window side */
2218 unsigned long p_lineno; /* Previous current line number */
2219 bool p_restore; /* Should the previous position be restored. */
2221 /* Searching */
2222 char grep[SIZEOF_STR]; /* Search string */
2223 regex_t *regex; /* Pre-compiled regexp */
2225 /* If non-NULL, points to the view that opened this view. If this view
2226 * is closed tig will switch back to the parent view. */
2227 struct view *parent;
2228 struct view *prev;
2230 /* Buffering */
2231 size_t lines; /* Total number of lines */
2232 struct line *line; /* Line index */
2233 unsigned int digits; /* Number of digits in the lines member. */
2235 /* Drawing */
2236 struct line *curline; /* Line currently being drawn. */
2237 enum line_type curtype; /* Attribute currently used for drawing. */
2238 unsigned long col; /* Column when drawing. */
2239 bool has_scrolled; /* View was scrolled. */
2241 /* Loading */
2242 const char **argv; /* Shell command arguments. */
2243 const char *dir; /* Directory from which to execute. */
2244 struct io io;
2245 struct io *pipe;
2246 time_t start_time;
2247 time_t update_secs;
2248 };
2250 struct view_ops {
2251 /* What type of content being displayed. Used in the title bar. */
2252 const char *type;
2253 /* Default command arguments. */
2254 const char **argv;
2255 /* Open and reads in all view content. */
2256 bool (*open)(struct view *view);
2257 /* Read one line; updates view->line. */
2258 bool (*read)(struct view *view, char *data);
2259 /* Draw one line; @lineno must be < view->height. */
2260 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2261 /* Depending on view handle a special requests. */
2262 enum request (*request)(struct view *view, enum request request, struct line *line);
2263 /* Search for regexp in a line. */
2264 bool (*grep)(struct view *view, struct line *line);
2265 /* Select line */
2266 void (*select)(struct view *view, struct line *line);
2267 /* Prepare view for loading */
2268 bool (*prepare)(struct view *view);
2269 };
2271 static struct view_ops blame_ops;
2272 static struct view_ops blob_ops;
2273 static struct view_ops diff_ops;
2274 static struct view_ops help_ops;
2275 static struct view_ops log_ops;
2276 static struct view_ops main_ops;
2277 static struct view_ops pager_ops;
2278 static struct view_ops stage_ops;
2279 static struct view_ops status_ops;
2280 static struct view_ops tree_ops;
2281 static struct view_ops branch_ops;
2283 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2284 { type, name, #env, ref, ops, map, git }
2286 #define VIEW_(id, name, ops, git, ref) \
2287 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2289 static struct view views[] = {
2290 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2291 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2292 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2293 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2294 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2295 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2296 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2297 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2298 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2299 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2300 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2301 };
2303 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2305 #define foreach_view(view, i) \
2306 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2308 #define view_is_displayed(view) \
2309 (view == display[0] || view == display[1])
2311 static enum request
2312 view_request(struct view *view, enum request request)
2313 {
2314 if (!view || !view->lines)
2315 return request;
2316 return view->ops->request(view, request, &view->line[view->lineno]);
2317 }
2320 /*
2321 * View drawing.
2322 */
2324 static inline void
2325 set_view_attr(struct view *view, enum line_type type)
2326 {
2327 if (!view->curline->selected && view->curtype != type) {
2328 (void) wattrset(view->win, get_line_attr(type));
2329 wchgat(view->win, -1, 0, type, NULL);
2330 view->curtype = type;
2331 }
2332 }
2334 static int
2335 draw_chars(struct view *view, enum line_type type, const char *string,
2336 int max_len, bool use_tilde)
2337 {
2338 static char out_buffer[BUFSIZ * 2];
2339 int len = 0;
2340 int col = 0;
2341 int trimmed = FALSE;
2342 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2344 if (max_len <= 0)
2345 return 0;
2347 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2349 set_view_attr(view, type);
2350 if (len > 0) {
2351 if (opt_iconv_out != ICONV_NONE) {
2352 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2353 size_t inlen = len + 1;
2355 char *outbuf = out_buffer;
2356 size_t outlen = sizeof(out_buffer);
2358 size_t ret;
2360 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2361 if (ret != (size_t) -1) {
2362 string = out_buffer;
2363 len = sizeof(out_buffer) - outlen;
2364 }
2365 }
2367 waddnstr(view->win, string, len);
2368 }
2369 if (trimmed && use_tilde) {
2370 set_view_attr(view, LINE_DELIMITER);
2371 waddch(view->win, '~');
2372 col++;
2373 }
2375 return col;
2376 }
2378 static int
2379 draw_space(struct view *view, enum line_type type, int max, int spaces)
2380 {
2381 static char space[] = " ";
2382 int col = 0;
2384 spaces = MIN(max, spaces);
2386 while (spaces > 0) {
2387 int len = MIN(spaces, sizeof(space) - 1);
2389 col += draw_chars(view, type, space, len, FALSE);
2390 spaces -= len;
2391 }
2393 return col;
2394 }
2396 static bool
2397 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2398 {
2399 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2400 return view->width + view->yoffset <= view->col;
2401 }
2403 static bool
2404 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2405 {
2406 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2407 int max = view->width + view->yoffset - view->col;
2408 int i;
2410 if (max < size)
2411 size = max;
2413 set_view_attr(view, type);
2414 /* Using waddch() instead of waddnstr() ensures that
2415 * they'll be rendered correctly for the cursor line. */
2416 for (i = skip; i < size; i++)
2417 waddch(view->win, graphic[i]);
2419 view->col += size;
2420 if (size < max && skip <= size)
2421 waddch(view->win, ' ');
2422 view->col++;
2424 return view->width + view->yoffset <= view->col;
2425 }
2427 static bool
2428 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2429 {
2430 int max = MIN(view->width + view->yoffset - view->col, len);
2431 int col;
2433 if (text)
2434 col = draw_chars(view, type, text, max - 1, trim);
2435 else
2436 col = draw_space(view, type, max - 1, max - 1);
2438 view->col += col;
2439 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2440 return view->width + view->yoffset <= view->col;
2441 }
2443 static bool
2444 draw_date(struct view *view, struct time *time)
2445 {
2446 const char *date = mkdate(time, opt_date);
2447 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2449 return draw_field(view, LINE_DATE, date, cols, FALSE);
2450 }
2452 static bool
2453 draw_author(struct view *view, const char *author)
2454 {
2455 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2456 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2458 if (abbreviate && author)
2459 author = get_author_initials(author);
2461 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2462 }
2464 static bool
2465 draw_mode(struct view *view, mode_t mode)
2466 {
2467 const char *str;
2469 if (S_ISDIR(mode))
2470 str = "drwxr-xr-x";
2471 else if (S_ISLNK(mode))
2472 str = "lrwxrwxrwx";
2473 else if (S_ISGITLINK(mode))
2474 str = "m---------";
2475 else if (S_ISREG(mode) && mode & S_IXUSR)
2476 str = "-rwxr-xr-x";
2477 else if (S_ISREG(mode))
2478 str = "-rw-r--r--";
2479 else
2480 str = "----------";
2482 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2483 }
2485 static bool
2486 draw_lineno(struct view *view, unsigned int lineno)
2487 {
2488 char number[10];
2489 int digits3 = view->digits < 3 ? 3 : view->digits;
2490 int max = MIN(view->width + view->yoffset - view->col, digits3);
2491 char *text = NULL;
2492 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2494 lineno += view->offset + 1;
2495 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2496 static char fmt[] = "%1ld";
2498 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2499 if (string_format(number, fmt, lineno))
2500 text = number;
2501 }
2502 if (text)
2503 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2504 else
2505 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2506 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2507 }
2509 static bool
2510 draw_view_line(struct view *view, unsigned int lineno)
2511 {
2512 struct line *line;
2513 bool selected = (view->offset + lineno == view->lineno);
2515 assert(view_is_displayed(view));
2517 if (view->offset + lineno >= view->lines)
2518 return FALSE;
2520 line = &view->line[view->offset + lineno];
2522 wmove(view->win, lineno, 0);
2523 if (line->cleareol)
2524 wclrtoeol(view->win);
2525 view->col = 0;
2526 view->curline = line;
2527 view->curtype = LINE_NONE;
2528 line->selected = FALSE;
2529 line->dirty = line->cleareol = 0;
2531 if (selected) {
2532 set_view_attr(view, LINE_CURSOR);
2533 line->selected = TRUE;
2534 view->ops->select(view, line);
2535 }
2537 return view->ops->draw(view, line, lineno);
2538 }
2540 static void
2541 redraw_view_dirty(struct view *view)
2542 {
2543 bool dirty = FALSE;
2544 int lineno;
2546 for (lineno = 0; lineno < view->height; lineno++) {
2547 if (view->offset + lineno >= view->lines)
2548 break;
2549 if (!view->line[view->offset + lineno].dirty)
2550 continue;
2551 dirty = TRUE;
2552 if (!draw_view_line(view, lineno))
2553 break;
2554 }
2556 if (!dirty)
2557 return;
2558 wnoutrefresh(view->win);
2559 }
2561 static void
2562 redraw_view_from(struct view *view, int lineno)
2563 {
2564 assert(0 <= lineno && lineno < view->height);
2566 for (; lineno < view->height; lineno++) {
2567 if (!draw_view_line(view, lineno))
2568 break;
2569 }
2571 wnoutrefresh(view->win);
2572 }
2574 static void
2575 redraw_view(struct view *view)
2576 {
2577 werase(view->win);
2578 redraw_view_from(view, 0);
2579 }
2582 static void
2583 update_view_title(struct view *view)
2584 {
2585 char buf[SIZEOF_STR];
2586 char state[SIZEOF_STR];
2587 size_t bufpos = 0, statelen = 0;
2589 assert(view_is_displayed(view));
2591 if (view->type != VIEW_STATUS && view->lines) {
2592 unsigned int view_lines = view->offset + view->height;
2593 unsigned int lines = view->lines
2594 ? MIN(view_lines, view->lines) * 100 / view->lines
2595 : 0;
2597 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2598 view->ops->type,
2599 view->lineno + 1,
2600 view->lines,
2601 lines);
2603 }
2605 if (view->pipe) {
2606 time_t secs = time(NULL) - view->start_time;
2608 /* Three git seconds are a long time ... */
2609 if (secs > 2)
2610 string_format_from(state, &statelen, " loading %lds", secs);
2611 }
2613 string_format_from(buf, &bufpos, "[%s]", view->name);
2614 if (*view->ref && bufpos < view->width) {
2615 size_t refsize = strlen(view->ref);
2616 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2618 if (minsize < view->width)
2619 refsize = view->width - minsize + 7;
2620 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2621 }
2623 if (statelen && bufpos < view->width) {
2624 string_format_from(buf, &bufpos, "%s", state);
2625 }
2627 if (view == display[current_view])
2628 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2629 else
2630 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2632 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2633 wclrtoeol(view->title);
2634 wnoutrefresh(view->title);
2635 }
2637 static int
2638 apply_step(double step, int value)
2639 {
2640 if (step >= 1)
2641 return (int) step;
2642 value *= step + 0.01;
2643 return value ? value : 1;
2644 }
2646 static void
2647 resize_display(void)
2648 {
2649 int offset, i;
2650 struct view *base = display[0];
2651 struct view *view = display[1] ? display[1] : display[0];
2653 /* Setup window dimensions */
2655 getmaxyx(stdscr, base->height, base->width);
2657 /* Make room for the status window. */
2658 base->height -= 1;
2660 if (view != base) {
2661 /* Horizontal split. */
2662 view->width = base->width;
2663 view->height = apply_step(opt_scale_split_view, base->height);
2664 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2665 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2666 base->height -= view->height;
2668 /* Make room for the title bar. */
2669 view->height -= 1;
2670 }
2672 /* Make room for the title bar. */
2673 base->height -= 1;
2675 offset = 0;
2677 foreach_displayed_view (view, i) {
2678 if (!view->win) {
2679 view->win = newwin(view->height, 0, offset, 0);
2680 if (!view->win)
2681 die("Failed to create %s view", view->name);
2683 scrollok(view->win, FALSE);
2685 view->title = newwin(1, 0, offset + view->height, 0);
2686 if (!view->title)
2687 die("Failed to create title window");
2689 } else {
2690 wresize(view->win, view->height, view->width);
2691 mvwin(view->win, offset, 0);
2692 mvwin(view->title, offset + view->height, 0);
2693 }
2695 offset += view->height + 1;
2696 }
2697 }
2699 static void
2700 redraw_display(bool clear)
2701 {
2702 struct view *view;
2703 int i;
2705 foreach_displayed_view (view, i) {
2706 if (clear)
2707 wclear(view->win);
2708 redraw_view(view);
2709 update_view_title(view);
2710 }
2711 }
2714 /*
2715 * Option management
2716 */
2718 static void
2719 toggle_enum_option_do(unsigned int *opt, const char *help,
2720 const struct enum_map *map, size_t size)
2721 {
2722 *opt = (*opt + 1) % size;
2723 redraw_display(FALSE);
2724 report("Displaying %s %s", enum_name(map[*opt]), help);
2725 }
2727 #define toggle_enum_option(opt, help, map) \
2728 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2730 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2731 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2733 static void
2734 toggle_view_option(bool *option, const char *help)
2735 {
2736 *option = !*option;
2737 redraw_display(FALSE);
2738 report("%sabling %s", *option ? "En" : "Dis", help);
2739 }
2741 static void
2742 open_option_menu(void)
2743 {
2744 const struct menu_item menu[] = {
2745 { '.', "line numbers", &opt_line_number },
2746 { 'D', "date display", &opt_date },
2747 { 'A', "author display", &opt_author },
2748 { 'g', "revision graph display", &opt_rev_graph },
2749 { 'F', "reference display", &opt_show_refs },
2750 { 0 }
2751 };
2752 int selected = 0;
2754 if (prompt_menu("Toggle option", menu, &selected)) {
2755 if (menu[selected].data == &opt_date)
2756 toggle_date();
2757 else if (menu[selected].data == &opt_author)
2758 toggle_author();
2759 else
2760 toggle_view_option(menu[selected].data, menu[selected].text);
2761 }
2762 }
2764 static void
2765 maximize_view(struct view *view)
2766 {
2767 memset(display, 0, sizeof(display));
2768 current_view = 0;
2769 display[current_view] = view;
2770 resize_display();
2771 redraw_display(FALSE);
2772 report("");
2773 }
2776 /*
2777 * Navigation
2778 */
2780 static bool
2781 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2782 {
2783 if (lineno >= view->lines)
2784 lineno = view->lines > 0 ? view->lines - 1 : 0;
2786 if (offset > lineno || offset + view->height <= lineno) {
2787 unsigned long half = view->height / 2;
2789 if (lineno > half)
2790 offset = lineno - half;
2791 else
2792 offset = 0;
2793 }
2795 if (offset != view->offset || lineno != view->lineno) {
2796 view->offset = offset;
2797 view->lineno = lineno;
2798 return TRUE;
2799 }
2801 return FALSE;
2802 }
2804 /* Scrolling backend */
2805 static void
2806 do_scroll_view(struct view *view, int lines)
2807 {
2808 bool redraw_current_line = FALSE;
2810 /* The rendering expects the new offset. */
2811 view->offset += lines;
2813 assert(0 <= view->offset && view->offset < view->lines);
2814 assert(lines);
2816 /* Move current line into the view. */
2817 if (view->lineno < view->offset) {
2818 view->lineno = view->offset;
2819 redraw_current_line = TRUE;
2820 } else if (view->lineno >= view->offset + view->height) {
2821 view->lineno = view->offset + view->height - 1;
2822 redraw_current_line = TRUE;
2823 }
2825 assert(view->offset <= view->lineno && view->lineno < view->lines);
2827 /* Redraw the whole screen if scrolling is pointless. */
2828 if (view->height < ABS(lines)) {
2829 redraw_view(view);
2831 } else {
2832 int line = lines > 0 ? view->height - lines : 0;
2833 int end = line + ABS(lines);
2835 scrollok(view->win, TRUE);
2836 wscrl(view->win, lines);
2837 scrollok(view->win, FALSE);
2839 while (line < end && draw_view_line(view, line))
2840 line++;
2842 if (redraw_current_line)
2843 draw_view_line(view, view->lineno - view->offset);
2844 wnoutrefresh(view->win);
2845 }
2847 view->has_scrolled = TRUE;
2848 report("");
2849 }
2851 /* Scroll frontend */
2852 static void
2853 scroll_view(struct view *view, enum request request)
2854 {
2855 int lines = 1;
2857 assert(view_is_displayed(view));
2859 switch (request) {
2860 case REQ_SCROLL_LEFT:
2861 if (view->yoffset == 0) {
2862 report("Cannot scroll beyond the first column");
2863 return;
2864 }
2865 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2866 view->yoffset = 0;
2867 else
2868 view->yoffset -= apply_step(opt_hscroll, view->width);
2869 redraw_view_from(view, 0);
2870 report("");
2871 return;
2872 case REQ_SCROLL_RIGHT:
2873 view->yoffset += apply_step(opt_hscroll, view->width);
2874 redraw_view(view);
2875 report("");
2876 return;
2877 case REQ_SCROLL_PAGE_DOWN:
2878 lines = view->height;
2879 case REQ_SCROLL_LINE_DOWN:
2880 if (view->offset + lines > view->lines)
2881 lines = view->lines - view->offset;
2883 if (lines == 0 || view->offset + view->height >= view->lines) {
2884 report("Cannot scroll beyond the last line");
2885 return;
2886 }
2887 break;
2889 case REQ_SCROLL_PAGE_UP:
2890 lines = view->height;
2891 case REQ_SCROLL_LINE_UP:
2892 if (lines > view->offset)
2893 lines = view->offset;
2895 if (lines == 0) {
2896 report("Cannot scroll beyond the first line");
2897 return;
2898 }
2900 lines = -lines;
2901 break;
2903 default:
2904 die("request %d not handled in switch", request);
2905 }
2907 do_scroll_view(view, lines);
2908 }
2910 /* Cursor moving */
2911 static void
2912 move_view(struct view *view, enum request request)
2913 {
2914 int scroll_steps = 0;
2915 int steps;
2917 switch (request) {
2918 case REQ_MOVE_FIRST_LINE:
2919 steps = -view->lineno;
2920 break;
2922 case REQ_MOVE_LAST_LINE:
2923 steps = view->lines - view->lineno - 1;
2924 break;
2926 case REQ_MOVE_PAGE_UP:
2927 steps = view->height > view->lineno
2928 ? -view->lineno : -view->height;
2929 break;
2931 case REQ_MOVE_PAGE_DOWN:
2932 steps = view->lineno + view->height >= view->lines
2933 ? view->lines - view->lineno - 1 : view->height;
2934 break;
2936 case REQ_MOVE_UP:
2937 steps = -1;
2938 break;
2940 case REQ_MOVE_DOWN:
2941 steps = 1;
2942 break;
2944 default:
2945 die("request %d not handled in switch", request);
2946 }
2948 if (steps <= 0 && view->lineno == 0) {
2949 report("Cannot move beyond the first line");
2950 return;
2952 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2953 report("Cannot move beyond the last line");
2954 return;
2955 }
2957 /* Move the current line */
2958 view->lineno += steps;
2959 assert(0 <= view->lineno && view->lineno < view->lines);
2961 /* Check whether the view needs to be scrolled */
2962 if (view->lineno < view->offset ||
2963 view->lineno >= view->offset + view->height) {
2964 scroll_steps = steps;
2965 if (steps < 0 && -steps > view->offset) {
2966 scroll_steps = -view->offset;
2968 } else if (steps > 0) {
2969 if (view->lineno == view->lines - 1 &&
2970 view->lines > view->height) {
2971 scroll_steps = view->lines - view->offset - 1;
2972 if (scroll_steps >= view->height)
2973 scroll_steps -= view->height - 1;
2974 }
2975 }
2976 }
2978 if (!view_is_displayed(view)) {
2979 view->offset += scroll_steps;
2980 assert(0 <= view->offset && view->offset < view->lines);
2981 view->ops->select(view, &view->line[view->lineno]);
2982 return;
2983 }
2985 /* Repaint the old "current" line if we be scrolling */
2986 if (ABS(steps) < view->height)
2987 draw_view_line(view, view->lineno - steps - view->offset);
2989 if (scroll_steps) {
2990 do_scroll_view(view, scroll_steps);
2991 return;
2992 }
2994 /* Draw the current line */
2995 draw_view_line(view, view->lineno - view->offset);
2997 wnoutrefresh(view->win);
2998 report("");
2999 }
3002 /*
3003 * Searching
3004 */
3006 static void search_view(struct view *view, enum request request);
3008 static bool
3009 grep_text(struct view *view, const char *text[])
3010 {
3011 regmatch_t pmatch;
3012 size_t i;
3014 for (i = 0; text[i]; i++)
3015 if (*text[i] &&
3016 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3017 return TRUE;
3018 return FALSE;
3019 }
3021 static void
3022 select_view_line(struct view *view, unsigned long lineno)
3023 {
3024 unsigned long old_lineno = view->lineno;
3025 unsigned long old_offset = view->offset;
3027 if (goto_view_line(view, view->offset, lineno)) {
3028 if (view_is_displayed(view)) {
3029 if (old_offset != view->offset) {
3030 redraw_view(view);
3031 } else {
3032 draw_view_line(view, old_lineno - view->offset);
3033 draw_view_line(view, view->lineno - view->offset);
3034 wnoutrefresh(view->win);
3035 }
3036 } else {
3037 view->ops->select(view, &view->line[view->lineno]);
3038 }
3039 }
3040 }
3042 static void
3043 find_next(struct view *view, enum request request)
3044 {
3045 unsigned long lineno = view->lineno;
3046 int direction;
3048 if (!*view->grep) {
3049 if (!*opt_search)
3050 report("No previous search");
3051 else
3052 search_view(view, request);
3053 return;
3054 }
3056 switch (request) {
3057 case REQ_SEARCH:
3058 case REQ_FIND_NEXT:
3059 direction = 1;
3060 break;
3062 case REQ_SEARCH_BACK:
3063 case REQ_FIND_PREV:
3064 direction = -1;
3065 break;
3067 default:
3068 return;
3069 }
3071 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3072 lineno += direction;
3074 /* Note, lineno is unsigned long so will wrap around in which case it
3075 * will become bigger than view->lines. */
3076 for (; lineno < view->lines; lineno += direction) {
3077 if (view->ops->grep(view, &view->line[lineno])) {
3078 select_view_line(view, lineno);
3079 report("Line %ld matches '%s'", lineno + 1, view->grep);
3080 return;
3081 }
3082 }
3084 report("No match found for '%s'", view->grep);
3085 }
3087 static void
3088 search_view(struct view *view, enum request request)
3089 {
3090 int regex_err;
3092 if (view->regex) {
3093 regfree(view->regex);
3094 *view->grep = 0;
3095 } else {
3096 view->regex = calloc(1, sizeof(*view->regex));
3097 if (!view->regex)
3098 return;
3099 }
3101 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3102 if (regex_err != 0) {
3103 char buf[SIZEOF_STR] = "unknown error";
3105 regerror(regex_err, view->regex, buf, sizeof(buf));
3106 report("Search failed: %s", buf);
3107 return;
3108 }
3110 string_copy(view->grep, opt_search);
3112 find_next(view, request);
3113 }
3115 /*
3116 * Incremental updating
3117 */
3119 static void
3120 reset_view(struct view *view)
3121 {
3122 int i;
3124 for (i = 0; i < view->lines; i++)
3125 free(view->line[i].data);
3126 free(view->line);
3128 view->p_offset = view->offset;
3129 view->p_yoffset = view->yoffset;
3130 view->p_lineno = view->lineno;
3132 view->line = NULL;
3133 view->offset = 0;
3134 view->yoffset = 0;
3135 view->lines = 0;
3136 view->lineno = 0;
3137 view->vid[0] = 0;
3138 view->update_secs = 0;
3139 }
3141 static const char *
3142 format_arg(const char *name)
3143 {
3144 static struct {
3145 const char *name;
3146 size_t namelen;
3147 const char *value;
3148 const char *value_if_empty;
3149 } vars[] = {
3150 #define FORMAT_VAR(name, value, value_if_empty) \
3151 { name, STRING_SIZE(name), value, value_if_empty }
3152 FORMAT_VAR("%(directory)", opt_path, ""),
3153 FORMAT_VAR("%(file)", opt_file, ""),
3154 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3155 FORMAT_VAR("%(head)", ref_head, ""),
3156 FORMAT_VAR("%(commit)", ref_commit, ""),
3157 FORMAT_VAR("%(blob)", ref_blob, ""),
3158 FORMAT_VAR("%(branch)", ref_branch, ""),
3159 };
3160 int i;
3162 for (i = 0; i < ARRAY_SIZE(vars); i++)
3163 if (!strncmp(name, vars[i].name, vars[i].namelen))
3164 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3166 report("Unknown replacement: `%s`", name);
3167 return NULL;
3168 }
3170 static bool
3171 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3172 {
3173 char buf[SIZEOF_STR];
3174 int argc;
3176 argv_free(*dst_argv);
3178 for (argc = 0; src_argv[argc]; argc++) {
3179 const char *arg = src_argv[argc];
3180 size_t bufpos = 0;
3182 if (!strcmp(arg, "%(file-args)")) {
3183 if (!argv_append_array(dst_argv, opt_file_args))
3184 break;
3185 continue;
3187 } else if (!strcmp(arg, "%(diff-args)")) {
3188 if (!argv_append_array(dst_argv, opt_diff_args))
3189 break;
3190 continue;
3192 } else if (!strcmp(arg, "%(rev-args)")) {
3193 if (!argv_append_array(dst_argv, opt_rev_args))
3194 break;
3195 continue;
3196 }
3198 while (arg) {
3199 char *next = strstr(arg, "%(");
3200 int len = next - arg;
3201 const char *value;
3203 if (!next || !replace) {
3204 len = strlen(arg);
3205 value = "";
3207 } else {
3208 value = format_arg(next);
3210 if (!value) {
3211 return FALSE;
3212 }
3213 }
3215 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3216 return FALSE;
3218 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3219 }
3221 if (!argv_append(dst_argv, buf))
3222 break;
3223 }
3225 return src_argv[argc] == NULL;
3226 }
3228 static bool
3229 restore_view_position(struct view *view)
3230 {
3231 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3232 return FALSE;
3234 /* Changing the view position cancels the restoring. */
3235 /* FIXME: Changing back to the first line is not detected. */
3236 if (view->offset != 0 || view->lineno != 0) {
3237 view->p_restore = FALSE;
3238 return FALSE;
3239 }
3241 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3242 view_is_displayed(view))
3243 werase(view->win);
3245 view->yoffset = view->p_yoffset;
3246 view->p_restore = FALSE;
3248 return TRUE;
3249 }
3251 static void
3252 end_update(struct view *view, bool force)
3253 {
3254 if (!view->pipe)
3255 return;
3256 while (!view->ops->read(view, NULL))
3257 if (!force)
3258 return;
3259 if (force)
3260 io_kill(view->pipe);
3261 io_done(view->pipe);
3262 view->pipe = NULL;
3263 }
3265 static void
3266 setup_update(struct view *view, const char *vid)
3267 {
3268 reset_view(view);
3269 string_copy_rev(view->vid, vid);
3270 view->pipe = &view->io;
3271 view->start_time = time(NULL);
3272 }
3274 static bool
3275 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3276 {
3277 view->dir = dir;
3278 return format_argv(&view->argv, argv, replace);
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 prepare_io(view, dir, argv, FALSE);
3287 }
3289 static bool
3290 start_update(struct view *view, const char **argv, const char *dir)
3291 {
3292 if (view->pipe)
3293 io_done(view->pipe);
3294 return prepare_io(view, dir, argv, FALSE) &&
3295 io_run(&view->io, IO_RD, dir, view->argv);
3296 }
3298 static bool
3299 prepare_update_file(struct view *view, const char *name)
3300 {
3301 if (view->pipe)
3302 end_update(view, TRUE);
3303 argv_free(view->argv);
3304 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3305 }
3307 static bool
3308 begin_update(struct view *view, bool refresh)
3309 {
3310 if (view->pipe)
3311 end_update(view, TRUE);
3313 if (!refresh) {
3314 if (view->ops->prepare) {
3315 if (!view->ops->prepare(view))
3316 return FALSE;
3317 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3318 return FALSE;
3319 }
3321 /* Put the current ref_* value to the view title ref
3322 * member. This is needed by the blob view. Most other
3323 * views sets it automatically after loading because the
3324 * first line is a commit line. */
3325 string_copy_rev(view->ref, view->id);
3326 }
3328 if (view->argv && view->argv[0] &&
3329 !io_run(&view->io, IO_RD, view->dir, view->argv))
3330 return FALSE;
3332 setup_update(view, view->id);
3334 return TRUE;
3335 }
3337 static bool
3338 update_view(struct view *view)
3339 {
3340 char out_buffer[BUFSIZ * 2];
3341 char *line;
3342 /* Clear the view and redraw everything since the tree sorting
3343 * might have rearranged things. */
3344 bool redraw = view->lines == 0;
3345 bool can_read = TRUE;
3347 if (!view->pipe)
3348 return TRUE;
3350 if (!io_can_read(view->pipe)) {
3351 if (view->lines == 0 && view_is_displayed(view)) {
3352 time_t secs = time(NULL) - view->start_time;
3354 if (secs > 1 && secs > view->update_secs) {
3355 if (view->update_secs == 0)
3356 redraw_view(view);
3357 update_view_title(view);
3358 view->update_secs = secs;
3359 }
3360 }
3361 return TRUE;
3362 }
3364 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3365 if (opt_iconv_in != ICONV_NONE) {
3366 ICONV_CONST char *inbuf = line;
3367 size_t inlen = strlen(line) + 1;
3369 char *outbuf = out_buffer;
3370 size_t outlen = sizeof(out_buffer);
3372 size_t ret;
3374 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3375 if (ret != (size_t) -1)
3376 line = out_buffer;
3377 }
3379 if (!view->ops->read(view, line)) {
3380 report("Allocation failure");
3381 end_update(view, TRUE);
3382 return FALSE;
3383 }
3384 }
3386 {
3387 unsigned long lines = view->lines;
3388 int digits;
3390 for (digits = 0; lines; digits++)
3391 lines /= 10;
3393 /* Keep the displayed view in sync with line number scaling. */
3394 if (digits != view->digits) {
3395 view->digits = digits;
3396 if (opt_line_number || view->type == VIEW_BLAME)
3397 redraw = TRUE;
3398 }
3399 }
3401 if (io_error(view->pipe)) {
3402 report("Failed to read: %s", io_strerror(view->pipe));
3403 end_update(view, TRUE);
3405 } else if (io_eof(view->pipe)) {
3406 if (view_is_displayed(view))
3407 report("");
3408 end_update(view, FALSE);
3409 }
3411 if (restore_view_position(view))
3412 redraw = TRUE;
3414 if (!view_is_displayed(view))
3415 return TRUE;
3417 if (redraw)
3418 redraw_view_from(view, 0);
3419 else
3420 redraw_view_dirty(view);
3422 /* Update the title _after_ the redraw so that if the redraw picks up a
3423 * commit reference in view->ref it'll be available here. */
3424 update_view_title(view);
3425 return TRUE;
3426 }
3428 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3430 static struct line *
3431 add_line_data(struct view *view, void *data, enum line_type type)
3432 {
3433 struct line *line;
3435 if (!realloc_lines(&view->line, view->lines, 1))
3436 return NULL;
3438 line = &view->line[view->lines++];
3439 memset(line, 0, sizeof(*line));
3440 line->type = type;
3441 line->data = data;
3442 line->dirty = 1;
3444 return line;
3445 }
3447 static struct line *
3448 add_line_text(struct view *view, const char *text, enum line_type type)
3449 {
3450 char *data = text ? strdup(text) : NULL;
3452 return data ? add_line_data(view, data, type) : NULL;
3453 }
3455 static struct line *
3456 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3457 {
3458 char buf[SIZEOF_STR];
3459 va_list args;
3461 va_start(args, fmt);
3462 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3463 buf[0] = 0;
3464 va_end(args);
3466 return buf[0] ? add_line_text(view, buf, type) : NULL;
3467 }
3469 /*
3470 * View opening
3471 */
3473 enum open_flags {
3474 OPEN_DEFAULT = 0, /* Use default view switching. */
3475 OPEN_SPLIT = 1, /* Split current view. */
3476 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3477 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3478 OPEN_PREPARED = 32, /* Open already prepared command. */
3479 };
3481 static void
3482 open_view(struct view *prev, enum request request, enum open_flags flags)
3483 {
3484 bool split = !!(flags & OPEN_SPLIT);
3485 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3486 bool nomaximize = !!(flags & OPEN_REFRESH);
3487 struct view *view = VIEW(request);
3488 int nviews = displayed_views();
3489 struct view *base_view = display[0];
3491 if (view == prev && nviews == 1 && !reload) {
3492 report("Already in %s view", view->name);
3493 return;
3494 }
3496 if (view->git_dir && !opt_git_dir[0]) {
3497 report("The %s view is disabled in pager view", view->name);
3498 return;
3499 }
3501 if (split) {
3502 display[1] = view;
3503 current_view = 1;
3504 view->parent = prev;
3505 } else if (!nomaximize) {
3506 /* Maximize the current view. */
3507 memset(display, 0, sizeof(display));
3508 current_view = 0;
3509 display[current_view] = view;
3510 }
3512 /* No prev signals that this is the first loaded view. */
3513 if (prev && view != prev) {
3514 view->prev = prev;
3515 }
3517 /* Resize the view when switching between split- and full-screen,
3518 * or when switching between two different full-screen views. */
3519 if (nviews != displayed_views() ||
3520 (nviews == 1 && base_view != display[0]))
3521 resize_display();
3523 if (view->ops->open) {
3524 if (view->pipe)
3525 end_update(view, TRUE);
3526 if (!view->ops->open(view)) {
3527 report("Failed to load %s view", view->name);
3528 return;
3529 }
3530 restore_view_position(view);
3532 } else if ((reload || strcmp(view->vid, view->id)) &&
3533 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3534 report("Failed to load %s view", view->name);
3535 return;
3536 }
3538 if (split && prev->lineno - prev->offset >= prev->height) {
3539 /* Take the title line into account. */
3540 int lines = prev->lineno - prev->offset - prev->height + 1;
3542 /* Scroll the view that was split if the current line is
3543 * outside the new limited view. */
3544 do_scroll_view(prev, lines);
3545 }
3547 if (prev && view != prev && split && view_is_displayed(prev)) {
3548 /* "Blur" the previous view. */
3549 update_view_title(prev);
3550 }
3552 if (view->pipe && view->lines == 0) {
3553 /* Clear the old view and let the incremental updating refill
3554 * the screen. */
3555 werase(view->win);
3556 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3557 report("");
3558 } else if (view_is_displayed(view)) {
3559 redraw_view(view);
3560 report("");
3561 }
3562 }
3564 static void
3565 open_external_viewer(const char *argv[], const char *dir)
3566 {
3567 def_prog_mode(); /* save current tty modes */
3568 endwin(); /* restore original tty modes */
3569 io_run_fg(argv, dir);
3570 fprintf(stderr, "Press Enter to continue");
3571 getc(opt_tty);
3572 reset_prog_mode();
3573 redraw_display(TRUE);
3574 }
3576 static void
3577 open_mergetool(const char *file)
3578 {
3579 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3581 open_external_viewer(mergetool_argv, opt_cdup);
3582 }
3584 static void
3585 open_editor(const char *file)
3586 {
3587 const char *editor_argv[] = { "vi", file, NULL };
3588 const char *editor;
3590 editor = getenv("GIT_EDITOR");
3591 if (!editor && *opt_editor)
3592 editor = opt_editor;
3593 if (!editor)
3594 editor = getenv("VISUAL");
3595 if (!editor)
3596 editor = getenv("EDITOR");
3597 if (!editor)
3598 editor = "vi";
3600 editor_argv[0] = editor;
3601 open_external_viewer(editor_argv, opt_cdup);
3602 }
3604 static void
3605 open_run_request(enum request request)
3606 {
3607 struct run_request *req = get_run_request(request);
3608 const char **argv = NULL;
3610 if (!req) {
3611 report("Unknown run request");
3612 return;
3613 }
3615 if (format_argv(&argv, req->argv, TRUE))
3616 open_external_viewer(argv, NULL);
3617 if (argv)
3618 argv_free(argv);
3619 free(argv);
3620 }
3622 /*
3623 * User request switch noodle
3624 */
3626 static int
3627 view_driver(struct view *view, enum request request)
3628 {
3629 int i;
3631 if (request == REQ_NONE)
3632 return TRUE;
3634 if (request > REQ_NONE) {
3635 open_run_request(request);
3636 view_request(view, REQ_REFRESH);
3637 return TRUE;
3638 }
3640 request = view_request(view, request);
3641 if (request == REQ_NONE)
3642 return TRUE;
3644 switch (request) {
3645 case REQ_MOVE_UP:
3646 case REQ_MOVE_DOWN:
3647 case REQ_MOVE_PAGE_UP:
3648 case REQ_MOVE_PAGE_DOWN:
3649 case REQ_MOVE_FIRST_LINE:
3650 case REQ_MOVE_LAST_LINE:
3651 move_view(view, request);
3652 break;
3654 case REQ_SCROLL_LEFT:
3655 case REQ_SCROLL_RIGHT:
3656 case REQ_SCROLL_LINE_DOWN:
3657 case REQ_SCROLL_LINE_UP:
3658 case REQ_SCROLL_PAGE_DOWN:
3659 case REQ_SCROLL_PAGE_UP:
3660 scroll_view(view, request);
3661 break;
3663 case REQ_VIEW_BLAME:
3664 if (!opt_file[0]) {
3665 report("No file chosen, press %s to open tree view",
3666 get_key(view->keymap, REQ_VIEW_TREE));
3667 break;
3668 }
3669 open_view(view, request, OPEN_DEFAULT);
3670 break;
3672 case REQ_VIEW_BLOB:
3673 if (!ref_blob[0]) {
3674 report("No file chosen, press %s to open tree view",
3675 get_key(view->keymap, REQ_VIEW_TREE));
3676 break;
3677 }
3678 open_view(view, request, OPEN_DEFAULT);
3679 break;
3681 case REQ_VIEW_PAGER:
3682 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3683 report("No pager content, press %s to run command from prompt",
3684 get_key(view->keymap, REQ_PROMPT));
3685 break;
3686 }
3687 open_view(view, request, OPEN_DEFAULT);
3688 break;
3690 case REQ_VIEW_STAGE:
3691 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3692 report("No stage content, press %s to open the status view and choose file",
3693 get_key(view->keymap, REQ_VIEW_STATUS));
3694 break;
3695 }
3696 open_view(view, request, OPEN_DEFAULT);
3697 break;
3699 case REQ_VIEW_STATUS:
3700 if (opt_is_inside_work_tree == FALSE) {
3701 report("The status view requires a working tree");
3702 break;
3703 }
3704 open_view(view, request, OPEN_DEFAULT);
3705 break;
3707 case REQ_VIEW_MAIN:
3708 case REQ_VIEW_DIFF:
3709 case REQ_VIEW_LOG:
3710 case REQ_VIEW_TREE:
3711 case REQ_VIEW_HELP:
3712 case REQ_VIEW_BRANCH:
3713 open_view(view, request, OPEN_DEFAULT);
3714 break;
3716 case REQ_NEXT:
3717 case REQ_PREVIOUS:
3718 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3720 if (view->parent) {
3721 int line;
3723 view = view->parent;
3724 line = view->lineno;
3725 move_view(view, request);
3726 if (view_is_displayed(view))
3727 update_view_title(view);
3728 if (line != view->lineno)
3729 view_request(view, REQ_ENTER);
3730 } else {
3731 move_view(view, request);
3732 }
3733 break;
3735 case REQ_VIEW_NEXT:
3736 {
3737 int nviews = displayed_views();
3738 int next_view = (current_view + 1) % nviews;
3740 if (next_view == current_view) {
3741 report("Only one view is displayed");
3742 break;
3743 }
3745 current_view = next_view;
3746 /* Blur out the title of the previous view. */
3747 update_view_title(view);
3748 report("");
3749 break;
3750 }
3751 case REQ_REFRESH:
3752 report("Refreshing is not yet supported for the %s view", view->name);
3753 break;
3755 case REQ_MAXIMIZE:
3756 if (displayed_views() == 2)
3757 maximize_view(view);
3758 break;
3760 case REQ_OPTIONS:
3761 open_option_menu();
3762 break;
3764 case REQ_TOGGLE_LINENO:
3765 toggle_view_option(&opt_line_number, "line numbers");
3766 break;
3768 case REQ_TOGGLE_DATE:
3769 toggle_date();
3770 break;
3772 case REQ_TOGGLE_AUTHOR:
3773 toggle_author();
3774 break;
3776 case REQ_TOGGLE_REV_GRAPH:
3777 toggle_view_option(&opt_rev_graph, "revision graph display");
3778 break;
3780 case REQ_TOGGLE_REFS:
3781 toggle_view_option(&opt_show_refs, "reference display");
3782 break;
3784 case REQ_TOGGLE_SORT_FIELD:
3785 case REQ_TOGGLE_SORT_ORDER:
3786 report("Sorting is not yet supported for the %s view", view->name);
3787 break;
3789 case REQ_SEARCH:
3790 case REQ_SEARCH_BACK:
3791 search_view(view, request);
3792 break;
3794 case REQ_FIND_NEXT:
3795 case REQ_FIND_PREV:
3796 find_next(view, request);
3797 break;
3799 case REQ_STOP_LOADING:
3800 foreach_view(view, i) {
3801 if (view->pipe)
3802 report("Stopped loading the %s view", view->name),
3803 end_update(view, TRUE);
3804 }
3805 break;
3807 case REQ_SHOW_VERSION:
3808 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3809 return TRUE;
3811 case REQ_SCREEN_REDRAW:
3812 redraw_display(TRUE);
3813 break;
3815 case REQ_EDIT:
3816 report("Nothing to edit");
3817 break;
3819 case REQ_ENTER:
3820 report("Nothing to enter");
3821 break;
3823 case REQ_VIEW_CLOSE:
3824 /* XXX: Mark closed views by letting view->prev point to the
3825 * view itself. Parents to closed view should never be
3826 * followed. */
3827 if (view->prev && view->prev != view) {
3828 maximize_view(view->prev);
3829 view->prev = view;
3830 break;
3831 }
3832 /* Fall-through */
3833 case REQ_QUIT:
3834 return FALSE;
3836 default:
3837 report("Unknown key, press %s for help",
3838 get_key(view->keymap, REQ_VIEW_HELP));
3839 return TRUE;
3840 }
3842 return TRUE;
3843 }
3846 /*
3847 * View backend utilities
3848 */
3850 enum sort_field {
3851 ORDERBY_NAME,
3852 ORDERBY_DATE,
3853 ORDERBY_AUTHOR,
3854 };
3856 struct sort_state {
3857 const enum sort_field *fields;
3858 size_t size, current;
3859 bool reverse;
3860 };
3862 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3863 #define get_sort_field(state) ((state).fields[(state).current])
3864 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3866 static void
3867 sort_view(struct view *view, enum request request, struct sort_state *state,
3868 int (*compare)(const void *, const void *))
3869 {
3870 switch (request) {
3871 case REQ_TOGGLE_SORT_FIELD:
3872 state->current = (state->current + 1) % state->size;
3873 break;
3875 case REQ_TOGGLE_SORT_ORDER:
3876 state->reverse = !state->reverse;
3877 break;
3878 default:
3879 die("Not a sort request");
3880 }
3882 qsort(view->line, view->lines, sizeof(*view->line), compare);
3883 redraw_view(view);
3884 }
3886 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3888 /* Small author cache to reduce memory consumption. It uses binary
3889 * search to lookup or find place to position new entries. No entries
3890 * are ever freed. */
3891 static const char *
3892 get_author(const char *name)
3893 {
3894 static const char **authors;
3895 static size_t authors_size;
3896 int from = 0, to = authors_size - 1;
3898 while (from <= to) {
3899 size_t pos = (to + from) / 2;
3900 int cmp = strcmp(name, authors[pos]);
3902 if (!cmp)
3903 return authors[pos];
3905 if (cmp < 0)
3906 to = pos - 1;
3907 else
3908 from = pos + 1;
3909 }
3911 if (!realloc_authors(&authors, authors_size, 1))
3912 return NULL;
3913 name = strdup(name);
3914 if (!name)
3915 return NULL;
3917 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3918 authors[from] = name;
3919 authors_size++;
3921 return name;
3922 }
3924 static void
3925 parse_timesec(struct time *time, const char *sec)
3926 {
3927 time->sec = (time_t) atol(sec);
3928 }
3930 static void
3931 parse_timezone(struct time *time, const char *zone)
3932 {
3933 long tz;
3935 tz = ('0' - zone[1]) * 60 * 60 * 10;
3936 tz += ('0' - zone[2]) * 60 * 60;
3937 tz += ('0' - zone[3]) * 60 * 10;
3938 tz += ('0' - zone[4]) * 60;
3940 if (zone[0] == '-')
3941 tz = -tz;
3943 time->tz = tz;
3944 time->sec -= tz;
3945 }
3947 /* Parse author lines where the name may be empty:
3948 * author <email@address.tld> 1138474660 +0100
3949 */
3950 static void
3951 parse_author_line(char *ident, const char **author, struct time *time)
3952 {
3953 char *nameend = strchr(ident, '<');
3954 char *emailend = strchr(ident, '>');
3956 if (nameend && emailend)
3957 *nameend = *emailend = 0;
3958 ident = chomp_string(ident);
3959 if (!*ident) {
3960 if (nameend)
3961 ident = chomp_string(nameend + 1);
3962 if (!*ident)
3963 ident = "Unknown";
3964 }
3966 *author = get_author(ident);
3968 /* Parse epoch and timezone */
3969 if (emailend && emailend[1] == ' ') {
3970 char *secs = emailend + 2;
3971 char *zone = strchr(secs, ' ');
3973 parse_timesec(time, secs);
3975 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3976 parse_timezone(time, zone + 1);
3977 }
3978 }
3980 /*
3981 * Pager backend
3982 */
3984 static bool
3985 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3986 {
3987 char text[SIZEOF_STR];
3989 if (opt_line_number && draw_lineno(view, lineno))
3990 return TRUE;
3992 string_expand(text, sizeof(text), line->data, opt_tab_size);
3993 draw_text(view, line->type, text, TRUE);
3994 return TRUE;
3995 }
3997 static bool
3998 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3999 {
4000 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4001 char ref[SIZEOF_STR];
4003 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4004 return TRUE;
4006 /* This is the only fatal call, since it can "corrupt" the buffer. */
4007 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4008 return FALSE;
4010 return TRUE;
4011 }
4013 static void
4014 add_pager_refs(struct view *view, struct line *line)
4015 {
4016 char buf[SIZEOF_STR];
4017 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4018 struct ref_list *list;
4019 size_t bufpos = 0, i;
4020 const char *sep = "Refs: ";
4021 bool is_tag = FALSE;
4023 assert(line->type == LINE_COMMIT);
4025 list = get_ref_list(commit_id);
4026 if (!list) {
4027 if (view->type == VIEW_DIFF)
4028 goto try_add_describe_ref;
4029 return;
4030 }
4032 for (i = 0; i < list->size; i++) {
4033 struct ref *ref = list->refs[i];
4034 const char *fmt = ref->tag ? "%s[%s]" :
4035 ref->remote ? "%s<%s>" : "%s%s";
4037 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4038 return;
4039 sep = ", ";
4040 if (ref->tag)
4041 is_tag = TRUE;
4042 }
4044 if (!is_tag && view->type == VIEW_DIFF) {
4045 try_add_describe_ref:
4046 /* Add <tag>-g<commit_id> "fake" reference. */
4047 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4048 return;
4049 }
4051 if (bufpos == 0)
4052 return;
4054 add_line_text(view, buf, LINE_PP_REFS);
4055 }
4057 static bool
4058 pager_read(struct view *view, char *data)
4059 {
4060 struct line *line;
4062 if (!data)
4063 return TRUE;
4065 line = add_line_text(view, data, get_line_type(data));
4066 if (!line)
4067 return FALSE;
4069 if (line->type == LINE_COMMIT &&
4070 (view->type == VIEW_DIFF ||
4071 view->type == VIEW_LOG))
4072 add_pager_refs(view, line);
4074 return TRUE;
4075 }
4077 static enum request
4078 pager_request(struct view *view, enum request request, struct line *line)
4079 {
4080 int split = 0;
4082 if (request != REQ_ENTER)
4083 return request;
4085 if (line->type == LINE_COMMIT &&
4086 (view->type == VIEW_LOG ||
4087 view->type == VIEW_PAGER)) {
4088 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4089 split = 1;
4090 }
4092 /* Always scroll the view even if it was split. That way
4093 * you can use Enter to scroll through the log view and
4094 * split open each commit diff. */
4095 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4097 /* FIXME: A minor workaround. Scrolling the view will call report("")
4098 * but if we are scrolling a non-current view this won't properly
4099 * update the view title. */
4100 if (split)
4101 update_view_title(view);
4103 return REQ_NONE;
4104 }
4106 static bool
4107 pager_grep(struct view *view, struct line *line)
4108 {
4109 const char *text[] = { line->data, NULL };
4111 return grep_text(view, text);
4112 }
4114 static void
4115 pager_select(struct view *view, struct line *line)
4116 {
4117 if (line->type == LINE_COMMIT) {
4118 char *text = (char *)line->data + STRING_SIZE("commit ");
4120 if (view->type != VIEW_PAGER)
4121 string_copy_rev(view->ref, text);
4122 string_copy_rev(ref_commit, text);
4123 }
4124 }
4126 static struct view_ops pager_ops = {
4127 "line",
4128 NULL,
4129 NULL,
4130 pager_read,
4131 pager_draw,
4132 pager_request,
4133 pager_grep,
4134 pager_select,
4135 };
4137 static const char *log_argv[SIZEOF_ARG] = {
4138 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4139 };
4141 static enum request
4142 log_request(struct view *view, enum request request, struct line *line)
4143 {
4144 switch (request) {
4145 case REQ_REFRESH:
4146 load_refs();
4147 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4148 return REQ_NONE;
4149 default:
4150 return pager_request(view, request, line);
4151 }
4152 }
4154 static struct view_ops log_ops = {
4155 "line",
4156 log_argv,
4157 NULL,
4158 pager_read,
4159 pager_draw,
4160 log_request,
4161 pager_grep,
4162 pager_select,
4163 };
4165 static const char *diff_argv[SIZEOF_ARG] = {
4166 "git", "show", "--pretty=fuller", "--no-color", "--root",
4167 "--patch-with-stat", "--find-copies-harder", "-C",
4168 "%(diff-args)", "%(commit)", "--", "%(file-args)", NULL
4169 };
4171 static struct view_ops diff_ops = {
4172 "line",
4173 diff_argv,
4174 NULL,
4175 pager_read,
4176 pager_draw,
4177 pager_request,
4178 pager_grep,
4179 pager_select,
4180 };
4182 /*
4183 * Help backend
4184 */
4186 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4188 static bool
4189 help_open_keymap_title(struct view *view, enum keymap keymap)
4190 {
4191 struct line *line;
4193 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4194 help_keymap_hidden[keymap] ? '+' : '-',
4195 enum_name(keymap_table[keymap]));
4196 if (line)
4197 line->other = keymap;
4199 return help_keymap_hidden[keymap];
4200 }
4202 static void
4203 help_open_keymap(struct view *view, enum keymap keymap)
4204 {
4205 const char *group = NULL;
4206 char buf[SIZEOF_STR];
4207 size_t bufpos;
4208 bool add_title = TRUE;
4209 int i;
4211 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4212 const char *key = NULL;
4214 if (req_info[i].request == REQ_NONE)
4215 continue;
4217 if (!req_info[i].request) {
4218 group = req_info[i].help;
4219 continue;
4220 }
4222 key = get_keys(keymap, req_info[i].request, TRUE);
4223 if (!key || !*key)
4224 continue;
4226 if (add_title && help_open_keymap_title(view, keymap))
4227 return;
4228 add_title = FALSE;
4230 if (group) {
4231 add_line_text(view, group, LINE_HELP_GROUP);
4232 group = NULL;
4233 }
4235 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4236 enum_name(req_info[i]), req_info[i].help);
4237 }
4239 group = "External commands:";
4241 for (i = 0; i < run_requests; i++) {
4242 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4243 const char *key;
4244 int argc;
4246 if (!req || req->keymap != keymap)
4247 continue;
4249 key = get_key_name(req->key);
4250 if (!*key)
4251 key = "(no key defined)";
4253 if (add_title && help_open_keymap_title(view, keymap))
4254 return;
4255 if (group) {
4256 add_line_text(view, group, LINE_HELP_GROUP);
4257 group = NULL;
4258 }
4260 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4261 if (!string_format_from(buf, &bufpos, "%s%s",
4262 argc ? " " : "", req->argv[argc]))
4263 return;
4265 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4266 }
4267 }
4269 static bool
4270 help_open(struct view *view)
4271 {
4272 enum keymap keymap;
4274 reset_view(view);
4275 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4276 add_line_text(view, "", LINE_DEFAULT);
4278 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4279 help_open_keymap(view, keymap);
4281 return TRUE;
4282 }
4284 static enum request
4285 help_request(struct view *view, enum request request, struct line *line)
4286 {
4287 switch (request) {
4288 case REQ_ENTER:
4289 if (line->type == LINE_HELP_KEYMAP) {
4290 help_keymap_hidden[line->other] =
4291 !help_keymap_hidden[line->other];
4292 view->p_restore = TRUE;
4293 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4294 }
4296 return REQ_NONE;
4297 default:
4298 return pager_request(view, request, line);
4299 }
4300 }
4302 static struct view_ops help_ops = {
4303 "line",
4304 NULL,
4305 help_open,
4306 NULL,
4307 pager_draw,
4308 help_request,
4309 pager_grep,
4310 pager_select,
4311 };
4314 /*
4315 * Tree backend
4316 */
4318 struct tree_stack_entry {
4319 struct tree_stack_entry *prev; /* Entry below this in the stack */
4320 unsigned long lineno; /* Line number to restore */
4321 char *name; /* Position of name in opt_path */
4322 };
4324 /* The top of the path stack. */
4325 static struct tree_stack_entry *tree_stack = NULL;
4326 unsigned long tree_lineno = 0;
4328 static void
4329 pop_tree_stack_entry(void)
4330 {
4331 struct tree_stack_entry *entry = tree_stack;
4333 tree_lineno = entry->lineno;
4334 entry->name[0] = 0;
4335 tree_stack = entry->prev;
4336 free(entry);
4337 }
4339 static void
4340 push_tree_stack_entry(const char *name, unsigned long lineno)
4341 {
4342 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4343 size_t pathlen = strlen(opt_path);
4345 if (!entry)
4346 return;
4348 entry->prev = tree_stack;
4349 entry->name = opt_path + pathlen;
4350 tree_stack = entry;
4352 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4353 pop_tree_stack_entry();
4354 return;
4355 }
4357 /* Move the current line to the first tree entry. */
4358 tree_lineno = 1;
4359 entry->lineno = lineno;
4360 }
4362 /* Parse output from git-ls-tree(1):
4363 *
4364 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4365 */
4367 #define SIZEOF_TREE_ATTR \
4368 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4370 #define SIZEOF_TREE_MODE \
4371 STRING_SIZE("100644 ")
4373 #define TREE_ID_OFFSET \
4374 STRING_SIZE("100644 blob ")
4376 struct tree_entry {
4377 char id[SIZEOF_REV];
4378 mode_t mode;
4379 struct time time; /* Date from the author ident. */
4380 const char *author; /* Author of the commit. */
4381 char name[1];
4382 };
4384 static const char *
4385 tree_path(const struct line *line)
4386 {
4387 return ((struct tree_entry *) line->data)->name;
4388 }
4390 static int
4391 tree_compare_entry(const struct line *line1, const struct line *line2)
4392 {
4393 if (line1->type != line2->type)
4394 return line1->type == LINE_TREE_DIR ? -1 : 1;
4395 return strcmp(tree_path(line1), tree_path(line2));
4396 }
4398 static const enum sort_field tree_sort_fields[] = {
4399 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4400 };
4401 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4403 static int
4404 tree_compare(const void *l1, const void *l2)
4405 {
4406 const struct line *line1 = (const struct line *) l1;
4407 const struct line *line2 = (const struct line *) l2;
4408 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4409 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4411 if (line1->type == LINE_TREE_HEAD)
4412 return -1;
4413 if (line2->type == LINE_TREE_HEAD)
4414 return 1;
4416 switch (get_sort_field(tree_sort_state)) {
4417 case ORDERBY_DATE:
4418 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4420 case ORDERBY_AUTHOR:
4421 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4423 case ORDERBY_NAME:
4424 default:
4425 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4426 }
4427 }
4430 static struct line *
4431 tree_entry(struct view *view, enum line_type type, const char *path,
4432 const char *mode, const char *id)
4433 {
4434 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4435 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4437 if (!entry || !line) {
4438 free(entry);
4439 return NULL;
4440 }
4442 strncpy(entry->name, path, strlen(path));
4443 if (mode)
4444 entry->mode = strtoul(mode, NULL, 8);
4445 if (id)
4446 string_copy_rev(entry->id, id);
4448 return line;
4449 }
4451 static bool
4452 tree_read_date(struct view *view, char *text, bool *read_date)
4453 {
4454 static const char *author_name;
4455 static struct time author_time;
4457 if (!text && *read_date) {
4458 *read_date = FALSE;
4459 return TRUE;
4461 } else if (!text) {
4462 char *path = *opt_path ? opt_path : ".";
4463 /* Find next entry to process */
4464 const char *log_file[] = {
4465 "git", "log", "--no-color", "--pretty=raw",
4466 "--cc", "--raw", view->id, "--", path, NULL
4467 };
4469 if (!view->lines) {
4470 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4471 report("Tree is empty");
4472 return TRUE;
4473 }
4475 if (!start_update(view, log_file, opt_cdup)) {
4476 report("Failed to load tree data");
4477 return TRUE;
4478 }
4480 *read_date = TRUE;
4481 return FALSE;
4483 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4484 parse_author_line(text + STRING_SIZE("author "),
4485 &author_name, &author_time);
4487 } else if (*text == ':') {
4488 char *pos;
4489 size_t annotated = 1;
4490 size_t i;
4492 pos = strchr(text, '\t');
4493 if (!pos)
4494 return TRUE;
4495 text = pos + 1;
4496 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4497 text += strlen(opt_path);
4498 pos = strchr(text, '/');
4499 if (pos)
4500 *pos = 0;
4502 for (i = 1; i < view->lines; i++) {
4503 struct line *line = &view->line[i];
4504 struct tree_entry *entry = line->data;
4506 annotated += !!entry->author;
4507 if (entry->author || strcmp(entry->name, text))
4508 continue;
4510 entry->author = author_name;
4511 entry->time = author_time;
4512 line->dirty = 1;
4513 break;
4514 }
4516 if (annotated == view->lines)
4517 io_kill(view->pipe);
4518 }
4519 return TRUE;
4520 }
4522 static bool
4523 tree_read(struct view *view, char *text)
4524 {
4525 static bool read_date = FALSE;
4526 struct tree_entry *data;
4527 struct line *entry, *line;
4528 enum line_type type;
4529 size_t textlen = text ? strlen(text) : 0;
4530 char *path = text + SIZEOF_TREE_ATTR;
4532 if (read_date || !text)
4533 return tree_read_date(view, text, &read_date);
4535 if (textlen <= SIZEOF_TREE_ATTR)
4536 return FALSE;
4537 if (view->lines == 0 &&
4538 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4539 return FALSE;
4541 /* Strip the path part ... */
4542 if (*opt_path) {
4543 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4544 size_t striplen = strlen(opt_path);
4546 if (pathlen > striplen)
4547 memmove(path, path + striplen,
4548 pathlen - striplen + 1);
4550 /* Insert "link" to parent directory. */
4551 if (view->lines == 1 &&
4552 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4553 return FALSE;
4554 }
4556 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4557 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4558 if (!entry)
4559 return FALSE;
4560 data = entry->data;
4562 /* Skip "Directory ..." and ".." line. */
4563 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4564 if (tree_compare_entry(line, entry) <= 0)
4565 continue;
4567 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4569 line->data = data;
4570 line->type = type;
4571 for (; line <= entry; line++)
4572 line->dirty = line->cleareol = 1;
4573 return TRUE;
4574 }
4576 if (tree_lineno > view->lineno) {
4577 view->lineno = tree_lineno;
4578 tree_lineno = 0;
4579 }
4581 return TRUE;
4582 }
4584 static bool
4585 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4586 {
4587 struct tree_entry *entry = line->data;
4589 if (line->type == LINE_TREE_HEAD) {
4590 if (draw_text(view, line->type, "Directory path /", TRUE))
4591 return TRUE;
4592 } else {
4593 if (draw_mode(view, entry->mode))
4594 return TRUE;
4596 if (opt_author && draw_author(view, entry->author))
4597 return TRUE;
4599 if (opt_date && draw_date(view, &entry->time))
4600 return TRUE;
4601 }
4602 if (draw_text(view, line->type, entry->name, TRUE))
4603 return TRUE;
4604 return TRUE;
4605 }
4607 static void
4608 open_blob_editor(const char *id)
4609 {
4610 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4611 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4612 int fd = mkstemp(file);
4614 if (fd == -1)
4615 report("Failed to create temporary file");
4616 else if (!io_run_append(blob_argv, fd))
4617 report("Failed to save blob data to file");
4618 else
4619 open_editor(file);
4620 if (fd != -1)
4621 unlink(file);
4622 }
4624 static enum request
4625 tree_request(struct view *view, enum request request, struct line *line)
4626 {
4627 enum open_flags flags;
4628 struct tree_entry *entry = line->data;
4630 switch (request) {
4631 case REQ_VIEW_BLAME:
4632 if (line->type != LINE_TREE_FILE) {
4633 report("Blame only supported for files");
4634 return REQ_NONE;
4635 }
4637 string_copy(opt_ref, view->vid);
4638 return request;
4640 case REQ_EDIT:
4641 if (line->type != LINE_TREE_FILE) {
4642 report("Edit only supported for files");
4643 } else if (!is_head_commit(view->vid)) {
4644 open_blob_editor(entry->id);
4645 } else {
4646 open_editor(opt_file);
4647 }
4648 return REQ_NONE;
4650 case REQ_TOGGLE_SORT_FIELD:
4651 case REQ_TOGGLE_SORT_ORDER:
4652 sort_view(view, request, &tree_sort_state, tree_compare);
4653 return REQ_NONE;
4655 case REQ_PARENT:
4656 if (!*opt_path) {
4657 /* quit view if at top of tree */
4658 return REQ_VIEW_CLOSE;
4659 }
4660 /* fake 'cd ..' */
4661 line = &view->line[1];
4662 break;
4664 case REQ_ENTER:
4665 break;
4667 default:
4668 return request;
4669 }
4671 /* Cleanup the stack if the tree view is at a different tree. */
4672 while (!*opt_path && tree_stack)
4673 pop_tree_stack_entry();
4675 switch (line->type) {
4676 case LINE_TREE_DIR:
4677 /* Depending on whether it is a subdirectory or parent link
4678 * mangle the path buffer. */
4679 if (line == &view->line[1] && *opt_path) {
4680 pop_tree_stack_entry();
4682 } else {
4683 const char *basename = tree_path(line);
4685 push_tree_stack_entry(basename, view->lineno);
4686 }
4688 /* Trees and subtrees share the same ID, so they are not not
4689 * unique like blobs. */
4690 flags = OPEN_RELOAD;
4691 request = REQ_VIEW_TREE;
4692 break;
4694 case LINE_TREE_FILE:
4695 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4696 request = REQ_VIEW_BLOB;
4697 break;
4699 default:
4700 return REQ_NONE;
4701 }
4703 open_view(view, request, flags);
4704 if (request == REQ_VIEW_TREE)
4705 view->lineno = tree_lineno;
4707 return REQ_NONE;
4708 }
4710 static bool
4711 tree_grep(struct view *view, struct line *line)
4712 {
4713 struct tree_entry *entry = line->data;
4714 const char *text[] = {
4715 entry->name,
4716 opt_author ? entry->author : "",
4717 mkdate(&entry->time, opt_date),
4718 NULL
4719 };
4721 return grep_text(view, text);
4722 }
4724 static void
4725 tree_select(struct view *view, struct line *line)
4726 {
4727 struct tree_entry *entry = line->data;
4729 if (line->type == LINE_TREE_FILE) {
4730 string_copy_rev(ref_blob, entry->id);
4731 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4733 } else if (line->type != LINE_TREE_DIR) {
4734 return;
4735 }
4737 string_copy_rev(view->ref, entry->id);
4738 }
4740 static bool
4741 tree_prepare(struct view *view)
4742 {
4743 if (view->lines == 0 && opt_prefix[0]) {
4744 char *pos = opt_prefix;
4746 while (pos && *pos) {
4747 char *end = strchr(pos, '/');
4749 if (end)
4750 *end = 0;
4751 push_tree_stack_entry(pos, 0);
4752 pos = end;
4753 if (end) {
4754 *end = '/';
4755 pos++;
4756 }
4757 }
4759 } else if (strcmp(view->vid, view->id)) {
4760 opt_path[0] = 0;
4761 }
4763 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4764 }
4766 static const char *tree_argv[SIZEOF_ARG] = {
4767 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4768 };
4770 static struct view_ops tree_ops = {
4771 "file",
4772 tree_argv,
4773 NULL,
4774 tree_read,
4775 tree_draw,
4776 tree_request,
4777 tree_grep,
4778 tree_select,
4779 tree_prepare,
4780 };
4782 static bool
4783 blob_read(struct view *view, char *line)
4784 {
4785 if (!line)
4786 return TRUE;
4787 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4788 }
4790 static enum request
4791 blob_request(struct view *view, enum request request, struct line *line)
4792 {
4793 switch (request) {
4794 case REQ_EDIT:
4795 open_blob_editor(view->vid);
4796 return REQ_NONE;
4797 default:
4798 return pager_request(view, request, line);
4799 }
4800 }
4802 static const char *blob_argv[SIZEOF_ARG] = {
4803 "git", "cat-file", "blob", "%(blob)", NULL
4804 };
4806 static struct view_ops blob_ops = {
4807 "line",
4808 blob_argv,
4809 NULL,
4810 blob_read,
4811 pager_draw,
4812 blob_request,
4813 pager_grep,
4814 pager_select,
4815 };
4817 /*
4818 * Blame backend
4819 *
4820 * Loading the blame view is a two phase job:
4821 *
4822 * 1. File content is read either using opt_file from the
4823 * filesystem or using git-cat-file.
4824 * 2. Then blame information is incrementally added by
4825 * reading output from git-blame.
4826 */
4828 struct blame_commit {
4829 char id[SIZEOF_REV]; /* SHA1 ID. */
4830 char title[128]; /* First line of the commit message. */
4831 const char *author; /* Author of the commit. */
4832 struct time time; /* Date from the author ident. */
4833 char filename[128]; /* Name of file. */
4834 char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */
4835 char parent_filename[128]; /* Parent/previous name of file. */
4836 };
4838 struct blame {
4839 struct blame_commit *commit;
4840 unsigned long lineno;
4841 char text[1];
4842 };
4844 static bool
4845 blame_open(struct view *view)
4846 {
4847 char path[SIZEOF_STR];
4848 size_t i;
4850 if (!view->prev && *opt_prefix) {
4851 string_copy(path, opt_file);
4852 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4853 return FALSE;
4854 }
4856 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4857 const char *blame_cat_file_argv[] = {
4858 "git", "cat-file", "blob", path, NULL
4859 };
4861 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4862 !start_update(view, blame_cat_file_argv, opt_cdup))
4863 return FALSE;
4864 }
4866 /* First pass: remove multiple references to the same commit. */
4867 for (i = 0; i < view->lines; i++) {
4868 struct blame *blame = view->line[i].data;
4870 if (blame->commit && blame->commit->id[0])
4871 blame->commit->id[0] = 0;
4872 else
4873 blame->commit = NULL;
4874 }
4876 /* Second pass: free existing references. */
4877 for (i = 0; i < view->lines; i++) {
4878 struct blame *blame = view->line[i].data;
4880 if (blame->commit)
4881 free(blame->commit);
4882 }
4884 setup_update(view, opt_file);
4885 string_format(view->ref, "%s ...", opt_file);
4887 return TRUE;
4888 }
4890 static struct blame_commit *
4891 get_blame_commit(struct view *view, const char *id)
4892 {
4893 size_t i;
4895 for (i = 0; i < view->lines; i++) {
4896 struct blame *blame = view->line[i].data;
4898 if (!blame->commit)
4899 continue;
4901 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4902 return blame->commit;
4903 }
4905 {
4906 struct blame_commit *commit = calloc(1, sizeof(*commit));
4908 if (commit)
4909 string_ncopy(commit->id, id, SIZEOF_REV);
4910 return commit;
4911 }
4912 }
4914 static bool
4915 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4916 {
4917 const char *pos = *posref;
4919 *posref = NULL;
4920 pos = strchr(pos + 1, ' ');
4921 if (!pos || !isdigit(pos[1]))
4922 return FALSE;
4923 *number = atoi(pos + 1);
4924 if (*number < min || *number > max)
4925 return FALSE;
4927 *posref = pos;
4928 return TRUE;
4929 }
4931 static struct blame_commit *
4932 parse_blame_commit(struct view *view, const char *text, int *blamed)
4933 {
4934 struct blame_commit *commit;
4935 struct blame *blame;
4936 const char *pos = text + SIZEOF_REV - 2;
4937 size_t orig_lineno = 0;
4938 size_t lineno;
4939 size_t group;
4941 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4942 return NULL;
4944 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4945 !parse_number(&pos, &lineno, 1, view->lines) ||
4946 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4947 return NULL;
4949 commit = get_blame_commit(view, text);
4950 if (!commit)
4951 return NULL;
4953 *blamed += group;
4954 while (group--) {
4955 struct line *line = &view->line[lineno + group - 1];
4957 blame = line->data;
4958 blame->commit = commit;
4959 blame->lineno = orig_lineno + group - 1;
4960 line->dirty = 1;
4961 }
4963 return commit;
4964 }
4966 static bool
4967 blame_read_file(struct view *view, const char *line, bool *read_file)
4968 {
4969 if (!line) {
4970 const char *blame_argv[] = {
4971 "git", "blame", "--incremental",
4972 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4973 };
4975 if (view->lines == 0 && !view->prev)
4976 die("No blame exist for %s", view->vid);
4978 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4979 report("Failed to load blame data");
4980 return TRUE;
4981 }
4983 *read_file = FALSE;
4984 return FALSE;
4986 } else {
4987 size_t linelen = strlen(line);
4988 struct blame *blame = malloc(sizeof(*blame) + linelen);
4990 if (!blame)
4991 return FALSE;
4993 blame->commit = NULL;
4994 strncpy(blame->text, line, linelen);
4995 blame->text[linelen] = 0;
4996 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4997 }
4998 }
5000 static bool
5001 match_blame_header(const char *name, char **line)
5002 {
5003 size_t namelen = strlen(name);
5004 bool matched = !strncmp(name, *line, namelen);
5006 if (matched)
5007 *line += namelen;
5009 return matched;
5010 }
5012 static bool
5013 blame_read(struct view *view, char *line)
5014 {
5015 static struct blame_commit *commit = NULL;
5016 static int blamed = 0;
5017 static bool read_file = TRUE;
5019 if (read_file)
5020 return blame_read_file(view, line, &read_file);
5022 if (!line) {
5023 /* Reset all! */
5024 commit = NULL;
5025 blamed = 0;
5026 read_file = TRUE;
5027 string_format(view->ref, "%s", view->vid);
5028 if (view_is_displayed(view)) {
5029 update_view_title(view);
5030 redraw_view_from(view, 0);
5031 }
5032 return TRUE;
5033 }
5035 if (!commit) {
5036 commit = parse_blame_commit(view, line, &blamed);
5037 string_format(view->ref, "%s %2d%%", view->vid,
5038 view->lines ? blamed * 100 / view->lines : 0);
5040 } else if (match_blame_header("author ", &line)) {
5041 commit->author = get_author(line);
5043 } else if (match_blame_header("author-time ", &line)) {
5044 parse_timesec(&commit->time, line);
5046 } else if (match_blame_header("author-tz ", &line)) {
5047 parse_timezone(&commit->time, line);
5049 } else if (match_blame_header("summary ", &line)) {
5050 string_ncopy(commit->title, line, strlen(line));
5052 } else if (match_blame_header("previous ", &line)) {
5053 if (strlen(line) <= SIZEOF_REV)
5054 return FALSE;
5055 string_copy_rev(commit->parent_id, line);
5056 line += SIZEOF_REV;
5057 string_ncopy(commit->parent_filename, line, strlen(line));
5059 } else if (match_blame_header("filename ", &line)) {
5060 string_ncopy(commit->filename, line, strlen(line));
5061 commit = NULL;
5062 }
5064 return TRUE;
5065 }
5067 static bool
5068 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5069 {
5070 struct blame *blame = line->data;
5071 struct time *time = NULL;
5072 const char *id = NULL, *author = NULL;
5073 char text[SIZEOF_STR];
5075 if (blame->commit && *blame->commit->filename) {
5076 id = blame->commit->id;
5077 author = blame->commit->author;
5078 time = &blame->commit->time;
5079 }
5081 if (opt_date && draw_date(view, time))
5082 return TRUE;
5084 if (opt_author && draw_author(view, author))
5085 return TRUE;
5087 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5088 return TRUE;
5090 if (draw_lineno(view, lineno))
5091 return TRUE;
5093 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5094 draw_text(view, LINE_DEFAULT, text, TRUE);
5095 return TRUE;
5096 }
5098 static bool
5099 check_blame_commit(struct blame *blame, bool check_null_id)
5100 {
5101 if (!blame->commit)
5102 report("Commit data not loaded yet");
5103 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5104 report("No commit exist for the selected line");
5105 else
5106 return TRUE;
5107 return FALSE;
5108 }
5110 static void
5111 setup_blame_parent_line(struct view *view, struct blame *blame)
5112 {
5113 char from[SIZEOF_REF + SIZEOF_STR];
5114 char to[SIZEOF_REF + SIZEOF_STR];
5115 const char *diff_tree_argv[] = {
5116 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5117 "-U0", from, to, "--", NULL
5118 };
5119 struct io io;
5120 int parent_lineno = -1;
5121 int blamed_lineno = -1;
5122 char *line;
5124 if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5125 !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5126 !io_run(&io, IO_RD, NULL, diff_tree_argv))
5127 return;
5129 while ((line = io_get(&io, '\n', TRUE))) {
5130 if (*line == '@') {
5131 char *pos = strchr(line, '+');
5133 parent_lineno = atoi(line + 4);
5134 if (pos)
5135 blamed_lineno = atoi(pos + 1);
5137 } else if (*line == '+' && parent_lineno != -1) {
5138 if (blame->lineno == blamed_lineno - 1 &&
5139 !strcmp(blame->text, line + 1)) {
5140 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5141 break;
5142 }
5143 blamed_lineno++;
5144 }
5145 }
5147 io_done(&io);
5148 }
5150 static enum request
5151 blame_request(struct view *view, enum request request, struct line *line)
5152 {
5153 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5154 struct blame *blame = line->data;
5156 switch (request) {
5157 case REQ_VIEW_BLAME:
5158 if (check_blame_commit(blame, TRUE)) {
5159 string_copy(opt_ref, blame->commit->id);
5160 string_copy(opt_file, blame->commit->filename);
5161 if (blame->lineno)
5162 view->lineno = blame->lineno;
5163 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5164 }
5165 break;
5167 case REQ_PARENT:
5168 if (!check_blame_commit(blame, TRUE))
5169 break;
5170 if (!*blame->commit->parent_id) {
5171 report("The selected commit has no parents");
5172 } else {
5173 string_copy_rev(opt_ref, blame->commit->parent_id);
5174 string_copy(opt_file, blame->commit->parent_filename);
5175 setup_blame_parent_line(view, blame);
5176 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5177 }
5178 break;
5180 case REQ_ENTER:
5181 if (!check_blame_commit(blame, FALSE))
5182 break;
5184 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5185 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5186 break;
5188 if (!strcmp(blame->commit->id, NULL_ID)) {
5189 struct view *diff = VIEW(REQ_VIEW_DIFF);
5190 const char *diff_index_argv[] = {
5191 "git", "diff-index", "--root", "--patch-with-stat",
5192 "-C", "-M", "HEAD", "--", view->vid, NULL
5193 };
5195 if (!*blame->commit->parent_id) {
5196 diff_index_argv[1] = "diff";
5197 diff_index_argv[2] = "--no-color";
5198 diff_index_argv[6] = "--";
5199 diff_index_argv[7] = "/dev/null";
5200 }
5202 if (!prepare_update(diff, diff_index_argv, NULL)) {
5203 report("Failed to allocate diff command");
5204 break;
5205 }
5206 flags |= OPEN_PREPARED;
5207 }
5209 open_view(view, REQ_VIEW_DIFF, flags);
5210 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5211 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5212 break;
5214 default:
5215 return request;
5216 }
5218 return REQ_NONE;
5219 }
5221 static bool
5222 blame_grep(struct view *view, struct line *line)
5223 {
5224 struct blame *blame = line->data;
5225 struct blame_commit *commit = blame->commit;
5226 const char *text[] = {
5227 blame->text,
5228 commit ? commit->title : "",
5229 commit ? commit->id : "",
5230 commit && opt_author ? commit->author : "",
5231 commit ? mkdate(&commit->time, opt_date) : "",
5232 NULL
5233 };
5235 return grep_text(view, text);
5236 }
5238 static void
5239 blame_select(struct view *view, struct line *line)
5240 {
5241 struct blame *blame = line->data;
5242 struct blame_commit *commit = blame->commit;
5244 if (!commit)
5245 return;
5247 if (!strcmp(commit->id, NULL_ID))
5248 string_ncopy(ref_commit, "HEAD", 4);
5249 else
5250 string_copy_rev(ref_commit, commit->id);
5251 }
5253 static struct view_ops blame_ops = {
5254 "line",
5255 NULL,
5256 blame_open,
5257 blame_read,
5258 blame_draw,
5259 blame_request,
5260 blame_grep,
5261 blame_select,
5262 };
5264 /*
5265 * Branch backend
5266 */
5268 struct branch {
5269 const char *author; /* Author of the last commit. */
5270 struct time time; /* Date of the last activity. */
5271 const struct ref *ref; /* Name and commit ID information. */
5272 };
5274 static const struct ref branch_all;
5276 static const enum sort_field branch_sort_fields[] = {
5277 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5278 };
5279 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5281 static int
5282 branch_compare(const void *l1, const void *l2)
5283 {
5284 const struct branch *branch1 = ((const struct line *) l1)->data;
5285 const struct branch *branch2 = ((const struct line *) l2)->data;
5287 switch (get_sort_field(branch_sort_state)) {
5288 case ORDERBY_DATE:
5289 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5291 case ORDERBY_AUTHOR:
5292 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5294 case ORDERBY_NAME:
5295 default:
5296 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5297 }
5298 }
5300 static bool
5301 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5302 {
5303 struct branch *branch = line->data;
5304 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5306 if (opt_date && draw_date(view, &branch->time))
5307 return TRUE;
5309 if (opt_author && draw_author(view, branch->author))
5310 return TRUE;
5312 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5313 return TRUE;
5314 }
5316 static enum request
5317 branch_request(struct view *view, enum request request, struct line *line)
5318 {
5319 struct branch *branch = line->data;
5321 switch (request) {
5322 case REQ_REFRESH:
5323 load_refs();
5324 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5325 return REQ_NONE;
5327 case REQ_TOGGLE_SORT_FIELD:
5328 case REQ_TOGGLE_SORT_ORDER:
5329 sort_view(view, request, &branch_sort_state, branch_compare);
5330 return REQ_NONE;
5332 case REQ_ENTER:
5333 {
5334 const struct ref *ref = branch->ref;
5335 const char *all_branches_argv[] = {
5336 "git", "log", "--no-color", "--pretty=raw", "--parents",
5337 "--topo-order",
5338 ref == &branch_all ? "--all" : ref->name, NULL
5339 };
5340 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5342 if (!prepare_update(main_view, all_branches_argv, NULL))
5343 report("Failed to load view of all branches");
5344 else
5345 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5346 return REQ_NONE;
5347 }
5348 default:
5349 return request;
5350 }
5351 }
5353 static bool
5354 branch_read(struct view *view, char *line)
5355 {
5356 static char id[SIZEOF_REV];
5357 struct branch *reference;
5358 size_t i;
5360 if (!line)
5361 return TRUE;
5363 switch (get_line_type(line)) {
5364 case LINE_COMMIT:
5365 string_copy_rev(id, line + STRING_SIZE("commit "));
5366 return TRUE;
5368 case LINE_AUTHOR:
5369 for (i = 0, reference = NULL; i < view->lines; i++) {
5370 struct branch *branch = view->line[i].data;
5372 if (strcmp(branch->ref->id, id))
5373 continue;
5375 view->line[i].dirty = TRUE;
5376 if (reference) {
5377 branch->author = reference->author;
5378 branch->time = reference->time;
5379 continue;
5380 }
5382 parse_author_line(line + STRING_SIZE("author "),
5383 &branch->author, &branch->time);
5384 reference = branch;
5385 }
5386 return TRUE;
5388 default:
5389 return TRUE;
5390 }
5392 }
5394 static bool
5395 branch_open_visitor(void *data, const struct ref *ref)
5396 {
5397 struct view *view = data;
5398 struct branch *branch;
5400 if (ref->tag || ref->ltag || ref->remote)
5401 return TRUE;
5403 branch = calloc(1, sizeof(*branch));
5404 if (!branch)
5405 return FALSE;
5407 branch->ref = ref;
5408 return !!add_line_data(view, branch, LINE_DEFAULT);
5409 }
5411 static bool
5412 branch_open(struct view *view)
5413 {
5414 const char *branch_log[] = {
5415 "git", "log", "--no-color", "--pretty=raw",
5416 "--simplify-by-decoration", "--all", NULL
5417 };
5419 if (!start_update(view, branch_log, NULL)) {
5420 report("Failed to load branch data");
5421 return TRUE;
5422 }
5424 setup_update(view, view->id);
5425 branch_open_visitor(view, &branch_all);
5426 foreach_ref(branch_open_visitor, view);
5427 view->p_restore = TRUE;
5429 return TRUE;
5430 }
5432 static bool
5433 branch_grep(struct view *view, struct line *line)
5434 {
5435 struct branch *branch = line->data;
5436 const char *text[] = {
5437 branch->ref->name,
5438 branch->author,
5439 NULL
5440 };
5442 return grep_text(view, text);
5443 }
5445 static void
5446 branch_select(struct view *view, struct line *line)
5447 {
5448 struct branch *branch = line->data;
5450 string_copy_rev(view->ref, branch->ref->id);
5451 string_copy_rev(ref_commit, branch->ref->id);
5452 string_copy_rev(ref_head, branch->ref->id);
5453 string_copy_rev(ref_branch, branch->ref->name);
5454 }
5456 static struct view_ops branch_ops = {
5457 "branch",
5458 NULL,
5459 branch_open,
5460 branch_read,
5461 branch_draw,
5462 branch_request,
5463 branch_grep,
5464 branch_select,
5465 };
5467 /*
5468 * Status backend
5469 */
5471 struct status {
5472 char status;
5473 struct {
5474 mode_t mode;
5475 char rev[SIZEOF_REV];
5476 char name[SIZEOF_STR];
5477 } old;
5478 struct {
5479 mode_t mode;
5480 char rev[SIZEOF_REV];
5481 char name[SIZEOF_STR];
5482 } new;
5483 };
5485 static char status_onbranch[SIZEOF_STR];
5486 static struct status stage_status;
5487 static enum line_type stage_line_type;
5488 static size_t stage_chunks;
5489 static int *stage_chunk;
5491 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5493 /* This should work even for the "On branch" line. */
5494 static inline bool
5495 status_has_none(struct view *view, struct line *line)
5496 {
5497 return line < view->line + view->lines && !line[1].data;
5498 }
5500 /* Get fields from the diff line:
5501 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5502 */
5503 static inline bool
5504 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5505 {
5506 const char *old_mode = buf + 1;
5507 const char *new_mode = buf + 8;
5508 const char *old_rev = buf + 15;
5509 const char *new_rev = buf + 56;
5510 const char *status = buf + 97;
5512 if (bufsize < 98 ||
5513 old_mode[-1] != ':' ||
5514 new_mode[-1] != ' ' ||
5515 old_rev[-1] != ' ' ||
5516 new_rev[-1] != ' ' ||
5517 status[-1] != ' ')
5518 return FALSE;
5520 file->status = *status;
5522 string_copy_rev(file->old.rev, old_rev);
5523 string_copy_rev(file->new.rev, new_rev);
5525 file->old.mode = strtoul(old_mode, NULL, 8);
5526 file->new.mode = strtoul(new_mode, NULL, 8);
5528 file->old.name[0] = file->new.name[0] = 0;
5530 return TRUE;
5531 }
5533 static bool
5534 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5535 {
5536 struct status *unmerged = NULL;
5537 char *buf;
5538 struct io io;
5540 if (!io_run(&io, IO_RD, opt_cdup, argv))
5541 return FALSE;
5543 add_line_data(view, NULL, type);
5545 while ((buf = io_get(&io, 0, TRUE))) {
5546 struct status *file = unmerged;
5548 if (!file) {
5549 file = calloc(1, sizeof(*file));
5550 if (!file || !add_line_data(view, file, type))
5551 goto error_out;
5552 }
5554 /* Parse diff info part. */
5555 if (status) {
5556 file->status = status;
5557 if (status == 'A')
5558 string_copy(file->old.rev, NULL_ID);
5560 } else if (!file->status || file == unmerged) {
5561 if (!status_get_diff(file, buf, strlen(buf)))
5562 goto error_out;
5564 buf = io_get(&io, 0, TRUE);
5565 if (!buf)
5566 break;
5568 /* Collapse all modified entries that follow an
5569 * associated unmerged entry. */
5570 if (unmerged == file) {
5571 unmerged->status = 'U';
5572 unmerged = NULL;
5573 } else if (file->status == 'U') {
5574 unmerged = file;
5575 }
5576 }
5578 /* Grab the old name for rename/copy. */
5579 if (!*file->old.name &&
5580 (file->status == 'R' || file->status == 'C')) {
5581 string_ncopy(file->old.name, buf, strlen(buf));
5583 buf = io_get(&io, 0, TRUE);
5584 if (!buf)
5585 break;
5586 }
5588 /* git-ls-files just delivers a NUL separated list of
5589 * file names similar to the second half of the
5590 * git-diff-* output. */
5591 string_ncopy(file->new.name, buf, strlen(buf));
5592 if (!*file->old.name)
5593 string_copy(file->old.name, file->new.name);
5594 file = NULL;
5595 }
5597 if (io_error(&io)) {
5598 error_out:
5599 io_done(&io);
5600 return FALSE;
5601 }
5603 if (!view->line[view->lines - 1].data)
5604 add_line_data(view, NULL, LINE_STAT_NONE);
5606 io_done(&io);
5607 return TRUE;
5608 }
5610 /* Don't show unmerged entries in the staged section. */
5611 static const char *status_diff_index_argv[] = {
5612 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5613 "--cached", "-M", "HEAD", NULL
5614 };
5616 static const char *status_diff_files_argv[] = {
5617 "git", "diff-files", "-z", NULL
5618 };
5620 static const char *status_list_other_argv[] = {
5621 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5622 };
5624 static const char *status_list_no_head_argv[] = {
5625 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5626 };
5628 static const char *update_index_argv[] = {
5629 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5630 };
5632 /* Restore the previous line number to stay in the context or select a
5633 * line with something that can be updated. */
5634 static void
5635 status_restore(struct view *view)
5636 {
5637 if (view->p_lineno >= view->lines)
5638 view->p_lineno = view->lines - 1;
5639 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5640 view->p_lineno++;
5641 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5642 view->p_lineno--;
5644 /* If the above fails, always skip the "On branch" line. */
5645 if (view->p_lineno < view->lines)
5646 view->lineno = view->p_lineno;
5647 else
5648 view->lineno = 1;
5650 if (view->lineno < view->offset)
5651 view->offset = view->lineno;
5652 else if (view->offset + view->height <= view->lineno)
5653 view->offset = view->lineno - view->height + 1;
5655 view->p_restore = FALSE;
5656 }
5658 static void
5659 status_update_onbranch(void)
5660 {
5661 static const char *paths[][2] = {
5662 { "rebase-apply/rebasing", "Rebasing" },
5663 { "rebase-apply/applying", "Applying mailbox" },
5664 { "rebase-apply/", "Rebasing mailbox" },
5665 { "rebase-merge/interactive", "Interactive rebase" },
5666 { "rebase-merge/", "Rebase merge" },
5667 { "MERGE_HEAD", "Merging" },
5668 { "BISECT_LOG", "Bisecting" },
5669 { "HEAD", "On branch" },
5670 };
5671 char buf[SIZEOF_STR];
5672 struct stat stat;
5673 int i;
5675 if (is_initial_commit()) {
5676 string_copy(status_onbranch, "Initial commit");
5677 return;
5678 }
5680 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5681 char *head = opt_head;
5683 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5684 lstat(buf, &stat) < 0)
5685 continue;
5687 if (!*opt_head) {
5688 struct io io;
5690 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5691 io_read_buf(&io, buf, sizeof(buf))) {
5692 head = buf;
5693 if (!prefixcmp(head, "refs/heads/"))
5694 head += STRING_SIZE("refs/heads/");
5695 }
5696 }
5698 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5699 string_copy(status_onbranch, opt_head);
5700 return;
5701 }
5703 string_copy(status_onbranch, "Not currently on any branch");
5704 }
5706 /* First parse staged info using git-diff-index(1), then parse unstaged
5707 * info using git-diff-files(1), and finally untracked files using
5708 * git-ls-files(1). */
5709 static bool
5710 status_open(struct view *view)
5711 {
5712 reset_view(view);
5714 add_line_data(view, NULL, LINE_STAT_HEAD);
5715 status_update_onbranch();
5717 io_run_bg(update_index_argv);
5719 if (is_initial_commit()) {
5720 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5721 return FALSE;
5722 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5723 return FALSE;
5724 }
5726 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5727 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5728 return FALSE;
5730 /* Restore the exact position or use the specialized restore
5731 * mode? */
5732 if (!view->p_restore)
5733 status_restore(view);
5734 return TRUE;
5735 }
5737 static bool
5738 status_draw(struct view *view, struct line *line, unsigned int lineno)
5739 {
5740 struct status *status = line->data;
5741 enum line_type type;
5742 const char *text;
5744 if (!status) {
5745 switch (line->type) {
5746 case LINE_STAT_STAGED:
5747 type = LINE_STAT_SECTION;
5748 text = "Changes to be committed:";
5749 break;
5751 case LINE_STAT_UNSTAGED:
5752 type = LINE_STAT_SECTION;
5753 text = "Changed but not updated:";
5754 break;
5756 case LINE_STAT_UNTRACKED:
5757 type = LINE_STAT_SECTION;
5758 text = "Untracked files:";
5759 break;
5761 case LINE_STAT_NONE:
5762 type = LINE_DEFAULT;
5763 text = " (no files)";
5764 break;
5766 case LINE_STAT_HEAD:
5767 type = LINE_STAT_HEAD;
5768 text = status_onbranch;
5769 break;
5771 default:
5772 return FALSE;
5773 }
5774 } else {
5775 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5777 buf[0] = status->status;
5778 if (draw_text(view, line->type, buf, TRUE))
5779 return TRUE;
5780 type = LINE_DEFAULT;
5781 text = status->new.name;
5782 }
5784 draw_text(view, type, text, TRUE);
5785 return TRUE;
5786 }
5788 static enum request
5789 status_load_error(struct view *view, struct view *stage, const char *path)
5790 {
5791 if (displayed_views() == 2 || display[current_view] != view)
5792 maximize_view(view);
5793 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5794 return REQ_NONE;
5795 }
5797 static enum request
5798 status_enter(struct view *view, struct line *line)
5799 {
5800 struct status *status = line->data;
5801 const char *oldpath = status ? status->old.name : NULL;
5802 /* Diffs for unmerged entries are empty when passing the new
5803 * path, so leave it empty. */
5804 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5805 const char *info;
5806 enum open_flags split;
5807 struct view *stage = VIEW(REQ_VIEW_STAGE);
5809 if (line->type == LINE_STAT_NONE ||
5810 (!status && line[1].type == LINE_STAT_NONE)) {
5811 report("No file to diff");
5812 return REQ_NONE;
5813 }
5815 switch (line->type) {
5816 case LINE_STAT_STAGED:
5817 if (is_initial_commit()) {
5818 const char *no_head_diff_argv[] = {
5819 "git", "diff", "--no-color", "--patch-with-stat",
5820 "--", "/dev/null", newpath, NULL
5821 };
5823 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5824 return status_load_error(view, stage, newpath);
5825 } else {
5826 const char *index_show_argv[] = {
5827 "git", "diff-index", "--root", "--patch-with-stat",
5828 "-C", "-M", "--cached", "HEAD", "--",
5829 oldpath, newpath, NULL
5830 };
5832 if (!prepare_update(stage, index_show_argv, opt_cdup))
5833 return status_load_error(view, stage, newpath);
5834 }
5836 if (status)
5837 info = "Staged changes to %s";
5838 else
5839 info = "Staged changes";
5840 break;
5842 case LINE_STAT_UNSTAGED:
5843 {
5844 const char *files_show_argv[] = {
5845 "git", "diff-files", "--root", "--patch-with-stat",
5846 "-C", "-M", "--", oldpath, newpath, NULL
5847 };
5849 if (!prepare_update(stage, files_show_argv, opt_cdup))
5850 return status_load_error(view, stage, newpath);
5851 if (status)
5852 info = "Unstaged changes to %s";
5853 else
5854 info = "Unstaged changes";
5855 break;
5856 }
5857 case LINE_STAT_UNTRACKED:
5858 if (!newpath) {
5859 report("No file to show");
5860 return REQ_NONE;
5861 }
5863 if (!suffixcmp(status->new.name, -1, "/")) {
5864 report("Cannot display a directory");
5865 return REQ_NONE;
5866 }
5868 if (!prepare_update_file(stage, newpath))
5869 return status_load_error(view, stage, newpath);
5870 info = "Untracked file %s";
5871 break;
5873 case LINE_STAT_HEAD:
5874 return REQ_NONE;
5876 default:
5877 die("line type %d not handled in switch", line->type);
5878 }
5880 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5881 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5882 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5883 if (status) {
5884 stage_status = *status;
5885 } else {
5886 memset(&stage_status, 0, sizeof(stage_status));
5887 }
5889 stage_line_type = line->type;
5890 stage_chunks = 0;
5891 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5892 }
5894 return REQ_NONE;
5895 }
5897 static bool
5898 status_exists(struct status *status, enum line_type type)
5899 {
5900 struct view *view = VIEW(REQ_VIEW_STATUS);
5901 unsigned long lineno;
5903 for (lineno = 0; lineno < view->lines; lineno++) {
5904 struct line *line = &view->line[lineno];
5905 struct status *pos = line->data;
5907 if (line->type != type)
5908 continue;
5909 if (!pos && (!status || !status->status) && line[1].data) {
5910 select_view_line(view, lineno);
5911 return TRUE;
5912 }
5913 if (pos && !strcmp(status->new.name, pos->new.name)) {
5914 select_view_line(view, lineno);
5915 return TRUE;
5916 }
5917 }
5919 return FALSE;
5920 }
5923 static bool
5924 status_update_prepare(struct io *io, enum line_type type)
5925 {
5926 const char *staged_argv[] = {
5927 "git", "update-index", "-z", "--index-info", NULL
5928 };
5929 const char *others_argv[] = {
5930 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5931 };
5933 switch (type) {
5934 case LINE_STAT_STAGED:
5935 return io_run(io, IO_WR, opt_cdup, staged_argv);
5937 case LINE_STAT_UNSTAGED:
5938 case LINE_STAT_UNTRACKED:
5939 return io_run(io, IO_WR, opt_cdup, others_argv);
5941 default:
5942 die("line type %d not handled in switch", type);
5943 return FALSE;
5944 }
5945 }
5947 static bool
5948 status_update_write(struct io *io, struct status *status, enum line_type type)
5949 {
5950 char buf[SIZEOF_STR];
5951 size_t bufsize = 0;
5953 switch (type) {
5954 case LINE_STAT_STAGED:
5955 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5956 status->old.mode,
5957 status->old.rev,
5958 status->old.name, 0))
5959 return FALSE;
5960 break;
5962 case LINE_STAT_UNSTAGED:
5963 case LINE_STAT_UNTRACKED:
5964 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5965 return FALSE;
5966 break;
5968 default:
5969 die("line type %d not handled in switch", type);
5970 }
5972 return io_write(io, buf, bufsize);
5973 }
5975 static bool
5976 status_update_file(struct status *status, enum line_type type)
5977 {
5978 struct io io;
5979 bool result;
5981 if (!status_update_prepare(&io, type))
5982 return FALSE;
5984 result = status_update_write(&io, status, type);
5985 return io_done(&io) && result;
5986 }
5988 static bool
5989 status_update_files(struct view *view, struct line *line)
5990 {
5991 char buf[sizeof(view->ref)];
5992 struct io io;
5993 bool result = TRUE;
5994 struct line *pos = view->line + view->lines;
5995 int files = 0;
5996 int file, done;
5997 int cursor_y = -1, cursor_x = -1;
5999 if (!status_update_prepare(&io, line->type))
6000 return FALSE;
6002 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6003 files++;
6005 string_copy(buf, view->ref);
6006 getsyx(cursor_y, cursor_x);
6007 for (file = 0, done = 5; result && file < files; line++, file++) {
6008 int almost_done = file * 100 / files;
6010 if (almost_done > done) {
6011 done = almost_done;
6012 string_format(view->ref, "updating file %u of %u (%d%% done)",
6013 file, files, done);
6014 update_view_title(view);
6015 setsyx(cursor_y, cursor_x);
6016 doupdate();
6017 }
6018 result = status_update_write(&io, line->data, line->type);
6019 }
6020 string_copy(view->ref, buf);
6022 return io_done(&io) && result;
6023 }
6025 static bool
6026 status_update(struct view *view)
6027 {
6028 struct line *line = &view->line[view->lineno];
6030 assert(view->lines);
6032 if (!line->data) {
6033 /* This should work even for the "On branch" line. */
6034 if (line < view->line + view->lines && !line[1].data) {
6035 report("Nothing to update");
6036 return FALSE;
6037 }
6039 if (!status_update_files(view, line + 1)) {
6040 report("Failed to update file status");
6041 return FALSE;
6042 }
6044 } else if (!status_update_file(line->data, line->type)) {
6045 report("Failed to update file status");
6046 return FALSE;
6047 }
6049 return TRUE;
6050 }
6052 static bool
6053 status_revert(struct status *status, enum line_type type, bool has_none)
6054 {
6055 if (!status || type != LINE_STAT_UNSTAGED) {
6056 if (type == LINE_STAT_STAGED) {
6057 report("Cannot revert changes to staged files");
6058 } else if (type == LINE_STAT_UNTRACKED) {
6059 report("Cannot revert changes to untracked files");
6060 } else if (has_none) {
6061 report("Nothing to revert");
6062 } else {
6063 report("Cannot revert changes to multiple files");
6064 }
6066 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6067 char mode[10] = "100644";
6068 const char *reset_argv[] = {
6069 "git", "update-index", "--cacheinfo", mode,
6070 status->old.rev, status->old.name, NULL
6071 };
6072 const char *checkout_argv[] = {
6073 "git", "checkout", "--", status->old.name, NULL
6074 };
6076 if (status->status == 'U') {
6077 string_format(mode, "%5o", status->old.mode);
6079 if (status->old.mode == 0 && status->new.mode == 0) {
6080 reset_argv[2] = "--force-remove";
6081 reset_argv[3] = status->old.name;
6082 reset_argv[4] = NULL;
6083 }
6085 if (!io_run_fg(reset_argv, opt_cdup))
6086 return FALSE;
6087 if (status->old.mode == 0 && status->new.mode == 0)
6088 return TRUE;
6089 }
6091 return io_run_fg(checkout_argv, opt_cdup);
6092 }
6094 return FALSE;
6095 }
6097 static enum request
6098 status_request(struct view *view, enum request request, struct line *line)
6099 {
6100 struct status *status = line->data;
6102 switch (request) {
6103 case REQ_STATUS_UPDATE:
6104 if (!status_update(view))
6105 return REQ_NONE;
6106 break;
6108 case REQ_STATUS_REVERT:
6109 if (!status_revert(status, line->type, status_has_none(view, line)))
6110 return REQ_NONE;
6111 break;
6113 case REQ_STATUS_MERGE:
6114 if (!status || status->status != 'U') {
6115 report("Merging only possible for files with unmerged status ('U').");
6116 return REQ_NONE;
6117 }
6118 open_mergetool(status->new.name);
6119 break;
6121 case REQ_EDIT:
6122 if (!status)
6123 return request;
6124 if (status->status == 'D') {
6125 report("File has been deleted.");
6126 return REQ_NONE;
6127 }
6129 open_editor(status->new.name);
6130 break;
6132 case REQ_VIEW_BLAME:
6133 if (status)
6134 opt_ref[0] = 0;
6135 return request;
6137 case REQ_ENTER:
6138 /* After returning the status view has been split to
6139 * show the stage view. No further reloading is
6140 * necessary. */
6141 return status_enter(view, line);
6143 case REQ_REFRESH:
6144 /* Simply reload the view. */
6145 break;
6147 default:
6148 return request;
6149 }
6151 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6153 return REQ_NONE;
6154 }
6156 static void
6157 status_select(struct view *view, struct line *line)
6158 {
6159 struct status *status = line->data;
6160 char file[SIZEOF_STR] = "all files";
6161 const char *text;
6162 const char *key;
6164 if (status && !string_format(file, "'%s'", status->new.name))
6165 return;
6167 if (!status && line[1].type == LINE_STAT_NONE)
6168 line++;
6170 switch (line->type) {
6171 case LINE_STAT_STAGED:
6172 text = "Press %s to unstage %s for commit";
6173 break;
6175 case LINE_STAT_UNSTAGED:
6176 text = "Press %s to stage %s for commit";
6177 break;
6179 case LINE_STAT_UNTRACKED:
6180 text = "Press %s to stage %s for addition";
6181 break;
6183 case LINE_STAT_HEAD:
6184 case LINE_STAT_NONE:
6185 text = "Nothing to update";
6186 break;
6188 default:
6189 die("line type %d not handled in switch", line->type);
6190 }
6192 if (status && status->status == 'U') {
6193 text = "Press %s to resolve conflict in %s";
6194 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6196 } else {
6197 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6198 }
6200 string_format(view->ref, text, key, file);
6201 if (status)
6202 string_copy(opt_file, status->new.name);
6203 }
6205 static bool
6206 status_grep(struct view *view, struct line *line)
6207 {
6208 struct status *status = line->data;
6210 if (status) {
6211 const char buf[2] = { status->status, 0 };
6212 const char *text[] = { status->new.name, buf, NULL };
6214 return grep_text(view, text);
6215 }
6217 return FALSE;
6218 }
6220 static struct view_ops status_ops = {
6221 "file",
6222 NULL,
6223 status_open,
6224 NULL,
6225 status_draw,
6226 status_request,
6227 status_grep,
6228 status_select,
6229 };
6232 static bool
6233 stage_diff_write(struct io *io, struct line *line, struct line *end)
6234 {
6235 while (line < end) {
6236 if (!io_write(io, line->data, strlen(line->data)) ||
6237 !io_write(io, "\n", 1))
6238 return FALSE;
6239 line++;
6240 if (line->type == LINE_DIFF_CHUNK ||
6241 line->type == LINE_DIFF_HEADER)
6242 break;
6243 }
6245 return TRUE;
6246 }
6248 static struct line *
6249 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6250 {
6251 for (; view->line < line; line--)
6252 if (line->type == type)
6253 return line;
6255 return NULL;
6256 }
6258 static bool
6259 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6260 {
6261 const char *apply_argv[SIZEOF_ARG] = {
6262 "git", "apply", "--whitespace=nowarn", NULL
6263 };
6264 struct line *diff_hdr;
6265 struct io io;
6266 int argc = 3;
6268 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6269 if (!diff_hdr)
6270 return FALSE;
6272 if (!revert)
6273 apply_argv[argc++] = "--cached";
6274 if (revert || stage_line_type == LINE_STAT_STAGED)
6275 apply_argv[argc++] = "-R";
6276 apply_argv[argc++] = "-";
6277 apply_argv[argc++] = NULL;
6278 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6279 return FALSE;
6281 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6282 !stage_diff_write(&io, chunk, view->line + view->lines))
6283 chunk = NULL;
6285 io_done(&io);
6286 io_run_bg(update_index_argv);
6288 return chunk ? TRUE : FALSE;
6289 }
6291 static bool
6292 stage_update(struct view *view, struct line *line)
6293 {
6294 struct line *chunk = NULL;
6296 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6297 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6299 if (chunk) {
6300 if (!stage_apply_chunk(view, chunk, FALSE)) {
6301 report("Failed to apply chunk");
6302 return FALSE;
6303 }
6305 } else if (!stage_status.status) {
6306 view = VIEW(REQ_VIEW_STATUS);
6308 for (line = view->line; line < view->line + view->lines; line++)
6309 if (line->type == stage_line_type)
6310 break;
6312 if (!status_update_files(view, line + 1)) {
6313 report("Failed to update files");
6314 return FALSE;
6315 }
6317 } else if (!status_update_file(&stage_status, stage_line_type)) {
6318 report("Failed to update file");
6319 return FALSE;
6320 }
6322 return TRUE;
6323 }
6325 static bool
6326 stage_revert(struct view *view, struct line *line)
6327 {
6328 struct line *chunk = NULL;
6330 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6331 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6333 if (chunk) {
6334 if (!prompt_yesno("Are you sure you want to revert changes?"))
6335 return FALSE;
6337 if (!stage_apply_chunk(view, chunk, TRUE)) {
6338 report("Failed to revert chunk");
6339 return FALSE;
6340 }
6341 return TRUE;
6343 } else {
6344 return status_revert(stage_status.status ? &stage_status : NULL,
6345 stage_line_type, FALSE);
6346 }
6347 }
6350 static void
6351 stage_next(struct view *view, struct line *line)
6352 {
6353 int i;
6355 if (!stage_chunks) {
6356 for (line = view->line; line < view->line + view->lines; line++) {
6357 if (line->type != LINE_DIFF_CHUNK)
6358 continue;
6360 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6361 report("Allocation failure");
6362 return;
6363 }
6365 stage_chunk[stage_chunks++] = line - view->line;
6366 }
6367 }
6369 for (i = 0; i < stage_chunks; i++) {
6370 if (stage_chunk[i] > view->lineno) {
6371 do_scroll_view(view, stage_chunk[i] - view->lineno);
6372 report("Chunk %d of %d", i + 1, stage_chunks);
6373 return;
6374 }
6375 }
6377 report("No next chunk found");
6378 }
6380 static enum request
6381 stage_request(struct view *view, enum request request, struct line *line)
6382 {
6383 switch (request) {
6384 case REQ_STATUS_UPDATE:
6385 if (!stage_update(view, line))
6386 return REQ_NONE;
6387 break;
6389 case REQ_STATUS_REVERT:
6390 if (!stage_revert(view, line))
6391 return REQ_NONE;
6392 break;
6394 case REQ_STAGE_NEXT:
6395 if (stage_line_type == LINE_STAT_UNTRACKED) {
6396 report("File is untracked; press %s to add",
6397 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6398 return REQ_NONE;
6399 }
6400 stage_next(view, line);
6401 return REQ_NONE;
6403 case REQ_EDIT:
6404 if (!stage_status.new.name[0])
6405 return request;
6406 if (stage_status.status == 'D') {
6407 report("File has been deleted.");
6408 return REQ_NONE;
6409 }
6411 open_editor(stage_status.new.name);
6412 break;
6414 case REQ_REFRESH:
6415 /* Reload everything ... */
6416 break;
6418 case REQ_VIEW_BLAME:
6419 if (stage_status.new.name[0]) {
6420 string_copy(opt_file, stage_status.new.name);
6421 opt_ref[0] = 0;
6422 }
6423 return request;
6425 case REQ_ENTER:
6426 return pager_request(view, request, line);
6428 default:
6429 return request;
6430 }
6432 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6433 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6435 /* Check whether the staged entry still exists, and close the
6436 * stage view if it doesn't. */
6437 if (!status_exists(&stage_status, stage_line_type)) {
6438 status_restore(VIEW(REQ_VIEW_STATUS));
6439 return REQ_VIEW_CLOSE;
6440 }
6442 if (stage_line_type == LINE_STAT_UNTRACKED) {
6443 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6444 report("Cannot display a directory");
6445 return REQ_NONE;
6446 }
6448 if (!prepare_update_file(view, stage_status.new.name)) {
6449 report("Failed to open file: %s", strerror(errno));
6450 return REQ_NONE;
6451 }
6452 }
6453 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6455 return REQ_NONE;
6456 }
6458 static struct view_ops stage_ops = {
6459 "line",
6460 NULL,
6461 NULL,
6462 pager_read,
6463 pager_draw,
6464 stage_request,
6465 pager_grep,
6466 pager_select,
6467 };
6470 /*
6471 * Revision graph
6472 */
6474 struct commit {
6475 char id[SIZEOF_REV]; /* SHA1 ID. */
6476 char title[128]; /* First line of the commit message. */
6477 const char *author; /* Author of the commit. */
6478 struct time time; /* Date from the author ident. */
6479 struct ref_list *refs; /* Repository references. */
6480 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6481 size_t graph_size; /* The width of the graph array. */
6482 bool has_parents; /* Rewritten --parents seen. */
6483 };
6485 /* Size of rev graph with no "padding" columns */
6486 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6488 struct rev_graph {
6489 struct rev_graph *prev, *next, *parents;
6490 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6491 size_t size;
6492 struct commit *commit;
6493 size_t pos;
6494 unsigned int boundary:1;
6495 };
6497 /* Parents of the commit being visualized. */
6498 static struct rev_graph graph_parents[4];
6500 /* The current stack of revisions on the graph. */
6501 static struct rev_graph graph_stacks[4] = {
6502 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6503 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6504 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6505 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6506 };
6508 static inline bool
6509 graph_parent_is_merge(struct rev_graph *graph)
6510 {
6511 return graph->parents->size > 1;
6512 }
6514 static inline void
6515 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6516 {
6517 struct commit *commit = graph->commit;
6519 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6520 commit->graph[commit->graph_size++] = symbol;
6521 }
6523 static void
6524 clear_rev_graph(struct rev_graph *graph)
6525 {
6526 graph->boundary = 0;
6527 graph->size = graph->pos = 0;
6528 graph->commit = NULL;
6529 memset(graph->parents, 0, sizeof(*graph->parents));
6530 }
6532 static void
6533 done_rev_graph(struct rev_graph *graph)
6534 {
6535 if (graph_parent_is_merge(graph) &&
6536 graph->pos < graph->size - 1 &&
6537 graph->next->size == graph->size + graph->parents->size - 1) {
6538 size_t i = graph->pos + graph->parents->size - 1;
6540 graph->commit->graph_size = i * 2;
6541 while (i < graph->next->size - 1) {
6542 append_to_rev_graph(graph, ' ');
6543 append_to_rev_graph(graph, '\\');
6544 i++;
6545 }
6546 }
6548 clear_rev_graph(graph);
6549 }
6551 static void
6552 push_rev_graph(struct rev_graph *graph, const char *parent)
6553 {
6554 int i;
6556 /* "Collapse" duplicate parents lines.
6557 *
6558 * FIXME: This needs to also update update the drawn graph but
6559 * for now it just serves as a method for pruning graph lines. */
6560 for (i = 0; i < graph->size; i++)
6561 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6562 return;
6564 if (graph->size < SIZEOF_REVITEMS) {
6565 string_copy_rev(graph->rev[graph->size++], parent);
6566 }
6567 }
6569 static chtype
6570 get_rev_graph_symbol(struct rev_graph *graph)
6571 {
6572 chtype symbol;
6574 if (graph->boundary)
6575 symbol = REVGRAPH_BOUND;
6576 else if (graph->parents->size == 0)
6577 symbol = REVGRAPH_INIT;
6578 else if (graph_parent_is_merge(graph))
6579 symbol = REVGRAPH_MERGE;
6580 else if (graph->pos >= graph->size)
6581 symbol = REVGRAPH_BRANCH;
6582 else
6583 symbol = REVGRAPH_COMMIT;
6585 return symbol;
6586 }
6588 static void
6589 draw_rev_graph(struct rev_graph *graph)
6590 {
6591 struct rev_filler {
6592 chtype separator, line;
6593 };
6594 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6595 static struct rev_filler fillers[] = {
6596 { ' ', '|' },
6597 { '`', '.' },
6598 { '\'', ' ' },
6599 { '/', ' ' },
6600 };
6601 chtype symbol = get_rev_graph_symbol(graph);
6602 struct rev_filler *filler;
6603 size_t i;
6605 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6606 filler = &fillers[DEFAULT];
6608 for (i = 0; i < graph->pos; i++) {
6609 append_to_rev_graph(graph, filler->line);
6610 if (graph_parent_is_merge(graph->prev) &&
6611 graph->prev->pos == i)
6612 filler = &fillers[RSHARP];
6614 append_to_rev_graph(graph, filler->separator);
6615 }
6617 /* Place the symbol for this revision. */
6618 append_to_rev_graph(graph, symbol);
6620 if (graph->prev->size > graph->size)
6621 filler = &fillers[RDIAG];
6622 else
6623 filler = &fillers[DEFAULT];
6625 i++;
6627 for (; i < graph->size; i++) {
6628 append_to_rev_graph(graph, filler->separator);
6629 append_to_rev_graph(graph, filler->line);
6630 if (graph_parent_is_merge(graph->prev) &&
6631 i < graph->prev->pos + graph->parents->size)
6632 filler = &fillers[RSHARP];
6633 if (graph->prev->size > graph->size)
6634 filler = &fillers[LDIAG];
6635 }
6637 if (graph->prev->size > graph->size) {
6638 append_to_rev_graph(graph, filler->separator);
6639 if (filler->line != ' ')
6640 append_to_rev_graph(graph, filler->line);
6641 }
6642 }
6644 /* Prepare the next rev graph */
6645 static void
6646 prepare_rev_graph(struct rev_graph *graph)
6647 {
6648 size_t i;
6650 /* First, traverse all lines of revisions up to the active one. */
6651 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6652 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6653 break;
6655 push_rev_graph(graph->next, graph->rev[graph->pos]);
6656 }
6658 /* Interleave the new revision parent(s). */
6659 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6660 push_rev_graph(graph->next, graph->parents->rev[i]);
6662 /* Lastly, put any remaining revisions. */
6663 for (i = graph->pos + 1; i < graph->size; i++)
6664 push_rev_graph(graph->next, graph->rev[i]);
6665 }
6667 static void
6668 update_rev_graph(struct view *view, struct rev_graph *graph)
6669 {
6670 /* If this is the finalizing update ... */
6671 if (graph->commit)
6672 prepare_rev_graph(graph);
6674 /* Graph visualization needs a one rev look-ahead,
6675 * so the first update doesn't visualize anything. */
6676 if (!graph->prev->commit)
6677 return;
6679 if (view->lines > 2)
6680 view->line[view->lines - 3].dirty = 1;
6681 if (view->lines > 1)
6682 view->line[view->lines - 2].dirty = 1;
6683 draw_rev_graph(graph->prev);
6684 done_rev_graph(graph->prev->prev);
6685 }
6688 /*
6689 * Main view backend
6690 */
6692 static const char *main_argv[SIZEOF_ARG] = {
6693 "git", "log", "--no-color", "--pretty=raw", "--parents",
6694 "--topo-order", "%(diff-args)", "%(rev-args)",
6695 "--", "%(file-args)", NULL
6696 };
6698 static bool
6699 main_draw(struct view *view, struct line *line, unsigned int lineno)
6700 {
6701 struct commit *commit = line->data;
6703 if (!commit->author)
6704 return FALSE;
6706 if (opt_date && draw_date(view, &commit->time))
6707 return TRUE;
6709 if (opt_author && draw_author(view, commit->author))
6710 return TRUE;
6712 if (opt_rev_graph && commit->graph_size &&
6713 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6714 return TRUE;
6716 if (opt_show_refs && commit->refs) {
6717 size_t i;
6719 for (i = 0; i < commit->refs->size; i++) {
6720 struct ref *ref = commit->refs->refs[i];
6721 enum line_type type;
6723 if (ref->head)
6724 type = LINE_MAIN_HEAD;
6725 else if (ref->ltag)
6726 type = LINE_MAIN_LOCAL_TAG;
6727 else if (ref->tag)
6728 type = LINE_MAIN_TAG;
6729 else if (ref->tracked)
6730 type = LINE_MAIN_TRACKED;
6731 else if (ref->remote)
6732 type = LINE_MAIN_REMOTE;
6733 else
6734 type = LINE_MAIN_REF;
6736 if (draw_text(view, type, "[", TRUE) ||
6737 draw_text(view, type, ref->name, TRUE) ||
6738 draw_text(view, type, "]", TRUE))
6739 return TRUE;
6741 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6742 return TRUE;
6743 }
6744 }
6746 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6747 return TRUE;
6748 }
6750 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6751 static bool
6752 main_read(struct view *view, char *line)
6753 {
6754 static struct rev_graph *graph = graph_stacks;
6755 enum line_type type;
6756 struct commit *commit;
6758 if (!line) {
6759 int i;
6761 if (!view->lines && !view->prev)
6762 die("No revisions match the given arguments.");
6763 if (view->lines > 0) {
6764 commit = view->line[view->lines - 1].data;
6765 view->line[view->lines - 1].dirty = 1;
6766 if (!commit->author) {
6767 view->lines--;
6768 free(commit);
6769 graph->commit = NULL;
6770 }
6771 }
6772 update_rev_graph(view, graph);
6774 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6775 clear_rev_graph(&graph_stacks[i]);
6776 return TRUE;
6777 }
6779 type = get_line_type(line);
6780 if (type == LINE_COMMIT) {
6781 commit = calloc(1, sizeof(struct commit));
6782 if (!commit)
6783 return FALSE;
6785 line += STRING_SIZE("commit ");
6786 if (*line == '-') {
6787 graph->boundary = 1;
6788 line++;
6789 }
6791 string_copy_rev(commit->id, line);
6792 commit->refs = get_ref_list(commit->id);
6793 graph->commit = commit;
6794 add_line_data(view, commit, LINE_MAIN_COMMIT);
6796 while ((line = strchr(line, ' '))) {
6797 line++;
6798 push_rev_graph(graph->parents, line);
6799 commit->has_parents = TRUE;
6800 }
6801 return TRUE;
6802 }
6804 if (!view->lines)
6805 return TRUE;
6806 commit = view->line[view->lines - 1].data;
6808 switch (type) {
6809 case LINE_PARENT:
6810 if (commit->has_parents)
6811 break;
6812 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6813 break;
6815 case LINE_AUTHOR:
6816 parse_author_line(line + STRING_SIZE("author "),
6817 &commit->author, &commit->time);
6818 update_rev_graph(view, graph);
6819 graph = graph->next;
6820 break;
6822 default:
6823 /* Fill in the commit title if it has not already been set. */
6824 if (commit->title[0])
6825 break;
6827 /* Require titles to start with a non-space character at the
6828 * offset used by git log. */
6829 if (strncmp(line, " ", 4))
6830 break;
6831 line += 4;
6832 /* Well, if the title starts with a whitespace character,
6833 * try to be forgiving. Otherwise we end up with no title. */
6834 while (isspace(*line))
6835 line++;
6836 if (*line == '\0')
6837 break;
6838 /* FIXME: More graceful handling of titles; append "..." to
6839 * shortened titles, etc. */
6841 string_expand(commit->title, sizeof(commit->title), line, 1);
6842 view->line[view->lines - 1].dirty = 1;
6843 }
6845 return TRUE;
6846 }
6848 static enum request
6849 main_request(struct view *view, enum request request, struct line *line)
6850 {
6851 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6853 switch (request) {
6854 case REQ_ENTER:
6855 open_view(view, REQ_VIEW_DIFF, flags);
6856 break;
6857 case REQ_REFRESH:
6858 load_refs();
6859 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6860 break;
6861 default:
6862 return request;
6863 }
6865 return REQ_NONE;
6866 }
6868 static bool
6869 grep_refs(struct ref_list *list, regex_t *regex)
6870 {
6871 regmatch_t pmatch;
6872 size_t i;
6874 if (!opt_show_refs || !list)
6875 return FALSE;
6877 for (i = 0; i < list->size; i++) {
6878 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6879 return TRUE;
6880 }
6882 return FALSE;
6883 }
6885 static bool
6886 main_grep(struct view *view, struct line *line)
6887 {
6888 struct commit *commit = line->data;
6889 const char *text[] = {
6890 commit->title,
6891 opt_author ? commit->author : "",
6892 mkdate(&commit->time, opt_date),
6893 NULL
6894 };
6896 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6897 }
6899 static void
6900 main_select(struct view *view, struct line *line)
6901 {
6902 struct commit *commit = line->data;
6904 string_copy_rev(view->ref, commit->id);
6905 string_copy_rev(ref_commit, view->ref);
6906 }
6908 static struct view_ops main_ops = {
6909 "commit",
6910 main_argv,
6911 NULL,
6912 main_read,
6913 main_draw,
6914 main_request,
6915 main_grep,
6916 main_select,
6917 };
6920 /*
6921 * Status management
6922 */
6924 /* Whether or not the curses interface has been initialized. */
6925 static bool cursed = FALSE;
6927 /* Terminal hacks and workarounds. */
6928 static bool use_scroll_redrawwin;
6929 static bool use_scroll_status_wclear;
6931 /* The status window is used for polling keystrokes. */
6932 static WINDOW *status_win;
6934 /* Reading from the prompt? */
6935 static bool input_mode = FALSE;
6937 static bool status_empty = FALSE;
6939 /* Update status and title window. */
6940 static void
6941 report(const char *msg, ...)
6942 {
6943 struct view *view = display[current_view];
6945 if (input_mode)
6946 return;
6948 if (!view) {
6949 char buf[SIZEOF_STR];
6950 va_list args;
6952 va_start(args, msg);
6953 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6954 buf[sizeof(buf) - 1] = 0;
6955 buf[sizeof(buf) - 2] = '.';
6956 buf[sizeof(buf) - 3] = '.';
6957 buf[sizeof(buf) - 4] = '.';
6958 }
6959 va_end(args);
6960 die("%s", buf);
6961 }
6963 if (!status_empty || *msg) {
6964 va_list args;
6966 va_start(args, msg);
6968 wmove(status_win, 0, 0);
6969 if (view->has_scrolled && use_scroll_status_wclear)
6970 wclear(status_win);
6971 if (*msg) {
6972 vwprintw(status_win, msg, args);
6973 status_empty = FALSE;
6974 } else {
6975 status_empty = TRUE;
6976 }
6977 wclrtoeol(status_win);
6978 wnoutrefresh(status_win);
6980 va_end(args);
6981 }
6983 update_view_title(view);
6984 }
6986 static void
6987 init_display(void)
6988 {
6989 const char *term;
6990 int x, y;
6992 /* Initialize the curses library */
6993 if (isatty(STDIN_FILENO)) {
6994 cursed = !!initscr();
6995 opt_tty = stdin;
6996 } else {
6997 /* Leave stdin and stdout alone when acting as a pager. */
6998 opt_tty = fopen("/dev/tty", "r+");
6999 if (!opt_tty)
7000 die("Failed to open /dev/tty");
7001 cursed = !!newterm(NULL, opt_tty, opt_tty);
7002 }
7004 if (!cursed)
7005 die("Failed to initialize curses");
7007 nonl(); /* Disable conversion and detect newlines from input. */
7008 cbreak(); /* Take input chars one at a time, no wait for \n */
7009 noecho(); /* Don't echo input */
7010 leaveok(stdscr, FALSE);
7012 if (has_colors())
7013 init_colors();
7015 getmaxyx(stdscr, y, x);
7016 status_win = newwin(1, 0, y - 1, 0);
7017 if (!status_win)
7018 die("Failed to create status window");
7020 /* Enable keyboard mapping */
7021 keypad(status_win, TRUE);
7022 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7024 TABSIZE = opt_tab_size;
7026 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7027 if (term && !strcmp(term, "gnome-terminal")) {
7028 /* In the gnome-terminal-emulator, the message from
7029 * scrolling up one line when impossible followed by
7030 * scrolling down one line causes corruption of the
7031 * status line. This is fixed by calling wclear. */
7032 use_scroll_status_wclear = TRUE;
7033 use_scroll_redrawwin = FALSE;
7035 } else if (term && !strcmp(term, "xrvt-xpm")) {
7036 /* No problems with full optimizations in xrvt-(unicode)
7037 * and aterm. */
7038 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7040 } else {
7041 /* When scrolling in (u)xterm the last line in the
7042 * scrolling direction will update slowly. */
7043 use_scroll_redrawwin = TRUE;
7044 use_scroll_status_wclear = FALSE;
7045 }
7046 }
7048 static int
7049 get_input(int prompt_position)
7050 {
7051 struct view *view;
7052 int i, key, cursor_y, cursor_x;
7054 if (prompt_position)
7055 input_mode = TRUE;
7057 while (TRUE) {
7058 bool loading = FALSE;
7060 foreach_view (view, i) {
7061 update_view(view);
7062 if (view_is_displayed(view) && view->has_scrolled &&
7063 use_scroll_redrawwin)
7064 redrawwin(view->win);
7065 view->has_scrolled = FALSE;
7066 if (view->pipe)
7067 loading = TRUE;
7068 }
7070 /* Update the cursor position. */
7071 if (prompt_position) {
7072 getbegyx(status_win, cursor_y, cursor_x);
7073 cursor_x = prompt_position;
7074 } else {
7075 view = display[current_view];
7076 getbegyx(view->win, cursor_y, cursor_x);
7077 cursor_x = view->width - 1;
7078 cursor_y += view->lineno - view->offset;
7079 }
7080 setsyx(cursor_y, cursor_x);
7082 /* Refresh, accept single keystroke of input */
7083 doupdate();
7084 nodelay(status_win, loading);
7085 key = wgetch(status_win);
7087 /* wgetch() with nodelay() enabled returns ERR when
7088 * there's no input. */
7089 if (key == ERR) {
7091 } else if (key == KEY_RESIZE) {
7092 int height, width;
7094 getmaxyx(stdscr, height, width);
7096 wresize(status_win, 1, width);
7097 mvwin(status_win, height - 1, 0);
7098 wnoutrefresh(status_win);
7099 resize_display();
7100 redraw_display(TRUE);
7102 } else {
7103 input_mode = FALSE;
7104 return key;
7105 }
7106 }
7107 }
7109 static char *
7110 prompt_input(const char *prompt, input_handler handler, void *data)
7111 {
7112 enum input_status status = INPUT_OK;
7113 static char buf[SIZEOF_STR];
7114 size_t pos = 0;
7116 buf[pos] = 0;
7118 while (status == INPUT_OK || status == INPUT_SKIP) {
7119 int key;
7121 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7122 wclrtoeol(status_win);
7124 key = get_input(pos + 1);
7125 switch (key) {
7126 case KEY_RETURN:
7127 case KEY_ENTER:
7128 case '\n':
7129 status = pos ? INPUT_STOP : INPUT_CANCEL;
7130 break;
7132 case KEY_BACKSPACE:
7133 if (pos > 0)
7134 buf[--pos] = 0;
7135 else
7136 status = INPUT_CANCEL;
7137 break;
7139 case KEY_ESC:
7140 status = INPUT_CANCEL;
7141 break;
7143 default:
7144 if (pos >= sizeof(buf)) {
7145 report("Input string too long");
7146 return NULL;
7147 }
7149 status = handler(data, buf, key);
7150 if (status == INPUT_OK)
7151 buf[pos++] = (char) key;
7152 }
7153 }
7155 /* Clear the status window */
7156 status_empty = FALSE;
7157 report("");
7159 if (status == INPUT_CANCEL)
7160 return NULL;
7162 buf[pos++] = 0;
7164 return buf;
7165 }
7167 static enum input_status
7168 prompt_yesno_handler(void *data, char *buf, int c)
7169 {
7170 if (c == 'y' || c == 'Y')
7171 return INPUT_STOP;
7172 if (c == 'n' || c == 'N')
7173 return INPUT_CANCEL;
7174 return INPUT_SKIP;
7175 }
7177 static bool
7178 prompt_yesno(const char *prompt)
7179 {
7180 char prompt2[SIZEOF_STR];
7182 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7183 return FALSE;
7185 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7186 }
7188 static enum input_status
7189 read_prompt_handler(void *data, char *buf, int c)
7190 {
7191 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7192 }
7194 static char *
7195 read_prompt(const char *prompt)
7196 {
7197 return prompt_input(prompt, read_prompt_handler, NULL);
7198 }
7200 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7201 {
7202 enum input_status status = INPUT_OK;
7203 int size = 0;
7205 while (items[size].text)
7206 size++;
7208 while (status == INPUT_OK) {
7209 const struct menu_item *item = &items[*selected];
7210 int key;
7211 int i;
7213 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7214 prompt, *selected + 1, size);
7215 if (item->hotkey)
7216 wprintw(status_win, "[%c] ", (char) item->hotkey);
7217 wprintw(status_win, "%s", item->text);
7218 wclrtoeol(status_win);
7220 key = get_input(COLS - 1);
7221 switch (key) {
7222 case KEY_RETURN:
7223 case KEY_ENTER:
7224 case '\n':
7225 status = INPUT_STOP;
7226 break;
7228 case KEY_LEFT:
7229 case KEY_UP:
7230 *selected = *selected - 1;
7231 if (*selected < 0)
7232 *selected = size - 1;
7233 break;
7235 case KEY_RIGHT:
7236 case KEY_DOWN:
7237 *selected = (*selected + 1) % size;
7238 break;
7240 case KEY_ESC:
7241 status = INPUT_CANCEL;
7242 break;
7244 default:
7245 for (i = 0; items[i].text; i++)
7246 if (items[i].hotkey == key) {
7247 *selected = i;
7248 status = INPUT_STOP;
7249 break;
7250 }
7251 }
7252 }
7254 /* Clear the status window */
7255 status_empty = FALSE;
7256 report("");
7258 return status != INPUT_CANCEL;
7259 }
7261 /*
7262 * Repository properties
7263 */
7265 static struct ref **refs = NULL;
7266 static size_t refs_size = 0;
7267 static struct ref *refs_head = NULL;
7269 static struct ref_list **ref_lists = NULL;
7270 static size_t ref_lists_size = 0;
7272 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7273 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7274 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7276 static int
7277 compare_refs(const void *ref1_, const void *ref2_)
7278 {
7279 const struct ref *ref1 = *(const struct ref **)ref1_;
7280 const struct ref *ref2 = *(const struct ref **)ref2_;
7282 if (ref1->tag != ref2->tag)
7283 return ref2->tag - ref1->tag;
7284 if (ref1->ltag != ref2->ltag)
7285 return ref2->ltag - ref2->ltag;
7286 if (ref1->head != ref2->head)
7287 return ref2->head - ref1->head;
7288 if (ref1->tracked != ref2->tracked)
7289 return ref2->tracked - ref1->tracked;
7290 if (ref1->remote != ref2->remote)
7291 return ref2->remote - ref1->remote;
7292 return strcmp(ref1->name, ref2->name);
7293 }
7295 static void
7296 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7297 {
7298 size_t i;
7300 for (i = 0; i < refs_size; i++)
7301 if (!visitor(data, refs[i]))
7302 break;
7303 }
7305 static struct ref *
7306 get_ref_head()
7307 {
7308 return refs_head;
7309 }
7311 static struct ref_list *
7312 get_ref_list(const char *id)
7313 {
7314 struct ref_list *list;
7315 size_t i;
7317 for (i = 0; i < ref_lists_size; i++)
7318 if (!strcmp(id, ref_lists[i]->id))
7319 return ref_lists[i];
7321 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7322 return NULL;
7323 list = calloc(1, sizeof(*list));
7324 if (!list)
7325 return NULL;
7327 for (i = 0; i < refs_size; i++) {
7328 if (!strcmp(id, refs[i]->id) &&
7329 realloc_refs_list(&list->refs, list->size, 1))
7330 list->refs[list->size++] = refs[i];
7331 }
7333 if (!list->refs) {
7334 free(list);
7335 return NULL;
7336 }
7338 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7339 ref_lists[ref_lists_size++] = list;
7340 return list;
7341 }
7343 static int
7344 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7345 {
7346 struct ref *ref = NULL;
7347 bool tag = FALSE;
7348 bool ltag = FALSE;
7349 bool remote = FALSE;
7350 bool tracked = FALSE;
7351 bool head = FALSE;
7352 int from = 0, to = refs_size - 1;
7354 if (!prefixcmp(name, "refs/tags/")) {
7355 if (!suffixcmp(name, namelen, "^{}")) {
7356 namelen -= 3;
7357 name[namelen] = 0;
7358 } else {
7359 ltag = TRUE;
7360 }
7362 tag = TRUE;
7363 namelen -= STRING_SIZE("refs/tags/");
7364 name += STRING_SIZE("refs/tags/");
7366 } else if (!prefixcmp(name, "refs/remotes/")) {
7367 remote = TRUE;
7368 namelen -= STRING_SIZE("refs/remotes/");
7369 name += STRING_SIZE("refs/remotes/");
7370 tracked = !strcmp(opt_remote, name);
7372 } else if (!prefixcmp(name, "refs/heads/")) {
7373 namelen -= STRING_SIZE("refs/heads/");
7374 name += STRING_SIZE("refs/heads/");
7375 if (!strncmp(opt_head, name, namelen))
7376 return OK;
7378 } else if (!strcmp(name, "HEAD")) {
7379 head = TRUE;
7380 if (*opt_head) {
7381 namelen = strlen(opt_head);
7382 name = opt_head;
7383 }
7384 }
7386 /* If we are reloading or it's an annotated tag, replace the
7387 * previous SHA1 with the resolved commit id; relies on the fact
7388 * git-ls-remote lists the commit id of an annotated tag right
7389 * before the commit id it points to. */
7390 while (from <= to) {
7391 size_t pos = (to + from) / 2;
7392 int cmp = strcmp(name, refs[pos]->name);
7394 if (!cmp) {
7395 ref = refs[pos];
7396 break;
7397 }
7399 if (cmp < 0)
7400 to = pos - 1;
7401 else
7402 from = pos + 1;
7403 }
7405 if (!ref) {
7406 if (!realloc_refs(&refs, refs_size, 1))
7407 return ERR;
7408 ref = calloc(1, sizeof(*ref) + namelen);
7409 if (!ref)
7410 return ERR;
7411 memmove(refs + from + 1, refs + from,
7412 (refs_size - from) * sizeof(*refs));
7413 refs[from] = ref;
7414 strncpy(ref->name, name, namelen);
7415 refs_size++;
7416 }
7418 ref->head = head;
7419 ref->tag = tag;
7420 ref->ltag = ltag;
7421 ref->remote = remote;
7422 ref->tracked = tracked;
7423 string_copy_rev(ref->id, id);
7425 if (head)
7426 refs_head = ref;
7427 return OK;
7428 }
7430 static int
7431 load_refs(void)
7432 {
7433 const char *head_argv[] = {
7434 "git", "symbolic-ref", "HEAD", NULL
7435 };
7436 static const char *ls_remote_argv[SIZEOF_ARG] = {
7437 "git", "ls-remote", opt_git_dir, NULL
7438 };
7439 static bool init = FALSE;
7440 size_t i;
7442 if (!init) {
7443 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7444 die("TIG_LS_REMOTE contains too many arguments");
7445 init = TRUE;
7446 }
7448 if (!*opt_git_dir)
7449 return OK;
7451 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7452 !prefixcmp(opt_head, "refs/heads/")) {
7453 char *offset = opt_head + STRING_SIZE("refs/heads/");
7455 memmove(opt_head, offset, strlen(offset) + 1);
7456 }
7458 refs_head = NULL;
7459 for (i = 0; i < refs_size; i++)
7460 refs[i]->id[0] = 0;
7462 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7463 return ERR;
7465 /* Update the ref lists to reflect changes. */
7466 for (i = 0; i < ref_lists_size; i++) {
7467 struct ref_list *list = ref_lists[i];
7468 size_t old, new;
7470 for (old = new = 0; old < list->size; old++)
7471 if (!strcmp(list->id, list->refs[old]->id))
7472 list->refs[new++] = list->refs[old];
7473 list->size = new;
7474 }
7476 return OK;
7477 }
7479 static void
7480 set_remote_branch(const char *name, const char *value, size_t valuelen)
7481 {
7482 if (!strcmp(name, ".remote")) {
7483 string_ncopy(opt_remote, value, valuelen);
7485 } else if (*opt_remote && !strcmp(name, ".merge")) {
7486 size_t from = strlen(opt_remote);
7488 if (!prefixcmp(value, "refs/heads/"))
7489 value += STRING_SIZE("refs/heads/");
7491 if (!string_format_from(opt_remote, &from, "/%s", value))
7492 opt_remote[0] = 0;
7493 }
7494 }
7496 static void
7497 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7498 {
7499 const char *argv[SIZEOF_ARG] = { name, "=" };
7500 int argc = 1 + (cmd == option_set_command);
7501 int error = ERR;
7503 if (!argv_from_string(argv, &argc, value))
7504 config_msg = "Too many option arguments";
7505 else
7506 error = cmd(argc, argv);
7508 if (error == ERR)
7509 warn("Option 'tig.%s': %s", name, config_msg);
7510 }
7512 static bool
7513 set_environment_variable(const char *name, const char *value)
7514 {
7515 size_t len = strlen(name) + 1 + strlen(value) + 1;
7516 char *env = malloc(len);
7518 if (env &&
7519 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7520 putenv(env) == 0)
7521 return TRUE;
7522 free(env);
7523 return FALSE;
7524 }
7526 static void
7527 set_work_tree(const char *value)
7528 {
7529 char cwd[SIZEOF_STR];
7531 if (!getcwd(cwd, sizeof(cwd)))
7532 die("Failed to get cwd path: %s", strerror(errno));
7533 if (chdir(opt_git_dir) < 0)
7534 die("Failed to chdir(%s): %s", strerror(errno));
7535 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7536 die("Failed to get git path: %s", strerror(errno));
7537 if (chdir(cwd) < 0)
7538 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7539 if (chdir(value) < 0)
7540 die("Failed to chdir(%s): %s", value, strerror(errno));
7541 if (!getcwd(cwd, sizeof(cwd)))
7542 die("Failed to get cwd path: %s", strerror(errno));
7543 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7544 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7545 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7546 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7547 opt_is_inside_work_tree = TRUE;
7548 }
7550 static int
7551 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7552 {
7553 if (!strcmp(name, "i18n.commitencoding"))
7554 string_ncopy(opt_encoding, value, valuelen);
7556 else if (!strcmp(name, "core.editor"))
7557 string_ncopy(opt_editor, value, valuelen);
7559 else if (!strcmp(name, "core.worktree"))
7560 set_work_tree(value);
7562 else if (!prefixcmp(name, "tig.color."))
7563 set_repo_config_option(name + 10, value, option_color_command);
7565 else if (!prefixcmp(name, "tig.bind."))
7566 set_repo_config_option(name + 9, value, option_bind_command);
7568 else if (!prefixcmp(name, "tig."))
7569 set_repo_config_option(name + 4, value, option_set_command);
7571 else if (*opt_head && !prefixcmp(name, "branch.") &&
7572 !strncmp(name + 7, opt_head, strlen(opt_head)))
7573 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7575 return OK;
7576 }
7578 static int
7579 load_git_config(void)
7580 {
7581 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7583 return io_run_load(config_list_argv, "=", read_repo_config_option);
7584 }
7586 static int
7587 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7588 {
7589 if (!opt_git_dir[0]) {
7590 string_ncopy(opt_git_dir, name, namelen);
7592 } else if (opt_is_inside_work_tree == -1) {
7593 /* This can be 3 different values depending on the
7594 * version of git being used. If git-rev-parse does not
7595 * understand --is-inside-work-tree it will simply echo
7596 * the option else either "true" or "false" is printed.
7597 * Default to true for the unknown case. */
7598 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7600 } else if (*name == '.') {
7601 string_ncopy(opt_cdup, name, namelen);
7603 } else {
7604 string_ncopy(opt_prefix, name, namelen);
7605 }
7607 return OK;
7608 }
7610 static int
7611 load_repo_info(void)
7612 {
7613 const char *rev_parse_argv[] = {
7614 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7615 "--show-cdup", "--show-prefix", NULL
7616 };
7618 return io_run_load(rev_parse_argv, "=", read_repo_info);
7619 }
7622 /*
7623 * Main
7624 */
7626 static const char usage[] =
7627 "tig " TIG_VERSION " (" __DATE__ ")\n"
7628 "\n"
7629 "Usage: tig [options] [revs] [--] [paths]\n"
7630 " or: tig show [options] [revs] [--] [paths]\n"
7631 " or: tig blame [rev] path\n"
7632 " or: tig status\n"
7633 " or: tig < [git command output]\n"
7634 "\n"
7635 "Options:\n"
7636 " -v, --version Show version and exit\n"
7637 " -h, --help Show help message and exit";
7639 static void __NORETURN
7640 quit(int sig)
7641 {
7642 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7643 if (cursed)
7644 endwin();
7645 exit(0);
7646 }
7648 static void __NORETURN
7649 die(const char *err, ...)
7650 {
7651 va_list args;
7653 endwin();
7655 va_start(args, err);
7656 fputs("tig: ", stderr);
7657 vfprintf(stderr, err, args);
7658 fputs("\n", stderr);
7659 va_end(args);
7661 exit(1);
7662 }
7664 static void
7665 warn(const char *msg, ...)
7666 {
7667 va_list args;
7669 va_start(args, msg);
7670 fputs("tig warning: ", stderr);
7671 vfprintf(stderr, msg, args);
7672 fputs("\n", stderr);
7673 va_end(args);
7674 }
7676 static const char ***filter_args;
7678 static int
7679 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7680 {
7681 return argv_append(filter_args, name) ? OK : ERR;
7682 }
7684 static void
7685 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7686 {
7687 const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7688 const char **all_argv = NULL;
7690 filter_args = args;
7691 if (!argv_append_array(&all_argv, rev_parse_argv) ||
7692 !argv_append_array(&all_argv, argv) ||
7693 !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7694 die("Failed to split arguments");
7695 argv_free(all_argv);
7696 free(all_argv);
7697 }
7699 static void
7700 filter_options(const char *argv[])
7701 {
7702 filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7703 filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7704 filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7705 }
7707 static enum request
7708 parse_options(int argc, const char *argv[])
7709 {
7710 enum request request = REQ_VIEW_MAIN;
7711 const char *subcommand;
7712 bool seen_dashdash = FALSE;
7713 const char **filter_argv = NULL;
7714 int i;
7716 if (!isatty(STDIN_FILENO)) {
7717 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7718 return REQ_VIEW_PAGER;
7719 }
7721 if (argc <= 1)
7722 return REQ_VIEW_MAIN;
7724 subcommand = argv[1];
7725 if (!strcmp(subcommand, "status")) {
7726 if (argc > 2)
7727 warn("ignoring arguments after `%s'", subcommand);
7728 return REQ_VIEW_STATUS;
7730 } else if (!strcmp(subcommand, "blame")) {
7731 if (argc <= 2 || argc > 4)
7732 die("invalid number of options to blame\n\n%s", usage);
7734 i = 2;
7735 if (argc == 4) {
7736 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7737 i++;
7738 }
7740 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7741 return REQ_VIEW_BLAME;
7743 } else if (!strcmp(subcommand, "show")) {
7744 request = REQ_VIEW_DIFF;
7746 } else {
7747 subcommand = NULL;
7748 }
7750 for (i = 1 + !!subcommand; i < argc; i++) {
7751 const char *opt = argv[i];
7753 if (seen_dashdash) {
7754 argv_append(&opt_file_args, opt);
7755 continue;
7757 } else if (!strcmp(opt, "--")) {
7758 seen_dashdash = TRUE;
7759 continue;
7761 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7762 printf("tig version %s\n", TIG_VERSION);
7763 quit(0);
7765 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7766 printf("%s\n", usage);
7767 quit(0);
7769 } else if (!strcmp(opt, "--all")) {
7770 argv_append(&opt_rev_args, opt);
7771 continue;
7772 }
7774 if (!argv_append(&filter_argv, opt))
7775 die("command too long");
7776 }
7778 if (filter_argv)
7779 filter_options(filter_argv);
7781 return request;
7782 }
7784 int
7785 main(int argc, const char *argv[])
7786 {
7787 const char *codeset = "UTF-8";
7788 enum request request = parse_options(argc, argv);
7789 struct view *view;
7790 size_t i;
7792 signal(SIGINT, quit);
7793 signal(SIGPIPE, SIG_IGN);
7795 if (setlocale(LC_ALL, "")) {
7796 codeset = nl_langinfo(CODESET);
7797 }
7799 if (load_repo_info() == ERR)
7800 die("Failed to load repo info.");
7802 if (load_options() == ERR)
7803 die("Failed to load user config.");
7805 if (load_git_config() == ERR)
7806 die("Failed to load repo config.");
7808 /* Require a git repository unless when running in pager mode. */
7809 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7810 die("Not a git repository");
7812 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7813 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7814 if (opt_iconv_in == ICONV_NONE)
7815 die("Failed to initialize character set conversion");
7816 }
7818 if (codeset && strcmp(codeset, "UTF-8")) {
7819 opt_iconv_out = iconv_open(codeset, "UTF-8");
7820 if (opt_iconv_out == ICONV_NONE)
7821 die("Failed to initialize character set conversion");
7822 }
7824 if (load_refs() == ERR)
7825 die("Failed to load refs.");
7827 foreach_view (view, i) {
7828 if (getenv(view->cmd_env))
7829 warn("Use of the %s environment variable is deprecated,"
7830 " use options or TIG_DIFF_ARGS instead",
7831 view->cmd_env);
7832 if (!argv_from_env(view->ops->argv, view->cmd_env))
7833 die("Too many arguments in the `%s` environment variable",
7834 view->cmd_env);
7835 }
7837 init_display();
7839 while (view_driver(display[current_view], request)) {
7840 int key = get_input(0);
7842 view = display[current_view];
7843 request = get_keybinding(view->keymap, key);
7845 /* Some low-level request handling. This keeps access to
7846 * status_win restricted. */
7847 switch (request) {
7848 case REQ_NONE:
7849 report("Unknown key, press %s for help",
7850 get_key(view->keymap, REQ_VIEW_HELP));
7851 break;
7852 case REQ_PROMPT:
7853 {
7854 char *cmd = read_prompt(":");
7856 if (cmd && isdigit(*cmd)) {
7857 int lineno = view->lineno + 1;
7859 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7860 select_view_line(view, lineno - 1);
7861 report("");
7862 } else {
7863 report("Unable to parse '%s' as a line number", cmd);
7864 }
7866 } else if (cmd) {
7867 struct view *next = VIEW(REQ_VIEW_PAGER);
7868 const char *argv[SIZEOF_ARG] = { "git" };
7869 int argc = 1;
7871 /* When running random commands, initially show the
7872 * command in the title. However, it maybe later be
7873 * overwritten if a commit line is selected. */
7874 string_ncopy(next->ref, cmd, strlen(cmd));
7876 if (!argv_from_string(argv, &argc, cmd)) {
7877 report("Too many arguments");
7878 } else if (!prepare_update(next, argv, NULL)) {
7879 report("Failed to format command");
7880 } else {
7881 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7882 }
7883 }
7885 request = REQ_NONE;
7886 break;
7887 }
7888 case REQ_SEARCH:
7889 case REQ_SEARCH_BACK:
7890 {
7891 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7892 char *search = read_prompt(prompt);
7894 if (search)
7895 string_ncopy(opt_search, search, strlen(search));
7896 else if (*opt_search)
7897 request = request == REQ_SEARCH ?
7898 REQ_FIND_NEXT :
7899 REQ_FIND_PREV;
7900 else
7901 request = REQ_NONE;
7902 break;
7903 }
7904 default:
7905 break;
7906 }
7907 }
7909 quit(0);
7911 return 0;
7912 }