1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
74 #define MAX(x, y) ((x) > (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS 8
108 #define AUTHOR_COLS 19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB '\t'
118 #define KEY_RETURN '\r'
119 #define KEY_ESC 27
122 struct ref {
123 char id[SIZEOF_REV]; /* Commit SHA1 ID */
124 unsigned int head:1; /* Is it the current HEAD? */
125 unsigned int tag:1; /* Is it a tag? */
126 unsigned int ltag:1; /* If so, is the tag local? */
127 unsigned int remote:1; /* Is it a remote ref? */
128 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129 char name[1]; /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 size_t size; /* Number of refs. */
135 struct ref **refs; /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144 INPUT_OK,
145 INPUT_SKIP,
146 INPUT_STOP,
147 INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156 int hotkey;
157 const char *text;
158 void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164 * Allocation helpers ... Entering macro hell to never be seen again.
165 */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
168 static type * \
169 name(type **mem, size_t size, size_t increase) \
170 { \
171 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
172 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173 type *tmp = *mem; \
174 \
175 if (mem == NULL || num_chunks != num_chunks_new) { \
176 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177 if (tmp) \
178 *mem = tmp; \
179 } \
180 \
181 return tmp; \
182 }
184 /*
185 * String helpers
186 */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
190 {
191 if (srclen > dstlen - 1)
192 srclen = dstlen - 1;
194 strncpy(dst, src, srclen);
195 dst[srclen] = 0;
196 }
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204 string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static void
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
214 {
215 size_t size, pos;
217 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218 if (src[pos] == '\t') {
219 size_t expanded = tabsize - (size % tabsize);
221 if (expanded + size >= dstlen - 1)
222 expanded = dstlen - size - 1;
223 memcpy(dst + size, " ", expanded);
224 size += expanded;
225 } else {
226 dst[size++] = src[pos];
227 }
228 }
230 dst[size] = 0;
231 }
233 static char *
234 chomp_string(char *name)
235 {
236 int namelen;
238 while (isspace(*name))
239 name++;
241 namelen = strlen(name) - 1;
242 while (namelen > 0 && isspace(name[namelen]))
243 name[namelen--] = 0;
245 return name;
246 }
248 static bool
249 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
250 {
251 va_list args;
252 size_t pos = bufpos ? *bufpos : 0;
254 va_start(args, fmt);
255 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
256 va_end(args);
258 if (bufpos)
259 *bufpos = pos;
261 return pos >= bufsize ? FALSE : TRUE;
262 }
264 #define string_format(buf, fmt, args...) \
265 string_nformat(buf, sizeof(buf), NULL, fmt, args)
267 #define string_format_from(buf, from, fmt, args...) \
268 string_nformat(buf, sizeof(buf), from, fmt, args)
270 static int
271 string_enum_compare(const char *str1, const char *str2, int len)
272 {
273 size_t i;
275 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
277 /* Diff-Header == DIFF_HEADER */
278 for (i = 0; i < len; i++) {
279 if (toupper(str1[i]) == toupper(str2[i]))
280 continue;
282 if (string_enum_sep(str1[i]) &&
283 string_enum_sep(str2[i]))
284 continue;
286 return str1[i] - str2[i];
287 }
289 return 0;
290 }
292 #define enum_equals(entry, str, len) \
293 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
295 struct enum_map {
296 const char *name;
297 int namelen;
298 int value;
299 };
301 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 static char *
304 enum_map_name(const char *name, size_t namelen)
305 {
306 static char buf[SIZEOF_STR];
307 int bufpos;
309 for (bufpos = 0; bufpos <= namelen; bufpos++) {
310 buf[bufpos] = tolower(name[bufpos]);
311 if (buf[bufpos] == '_')
312 buf[bufpos] = '-';
313 }
315 buf[bufpos] = 0;
316 return buf;
317 }
319 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
321 static bool
322 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
323 {
324 size_t namelen = strlen(name);
325 int i;
327 for (i = 0; i < map_size; i++)
328 if (enum_equals(map[i], name, namelen)) {
329 *value = map[i].value;
330 return TRUE;
331 }
333 return FALSE;
334 }
336 #define map_enum(attr, map, name) \
337 map_enum_do(map, ARRAY_SIZE(map), attr, name)
339 #define prefixcmp(str1, str2) \
340 strncmp(str1, str2, STRING_SIZE(str2))
342 static inline int
343 suffixcmp(const char *str, int slen, const char *suffix)
344 {
345 size_t len = slen >= 0 ? slen : strlen(str);
346 size_t suffixlen = strlen(suffix);
348 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
349 }
352 /*
353 * Unicode / UTF-8 handling
354 *
355 * NOTE: Much of the following code for dealing with Unicode is derived from
356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
358 */
360 static inline int
361 unicode_width(unsigned long c, int tab_size)
362 {
363 if (c >= 0x1100 &&
364 (c <= 0x115f /* Hangul Jamo */
365 || c == 0x2329
366 || c == 0x232a
367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
368 /* CJK ... Yi */
369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
373 || (c >= 0xffe0 && c <= 0xffe6)
374 || (c >= 0x20000 && c <= 0x2fffd)
375 || (c >= 0x30000 && c <= 0x3fffd)))
376 return 2;
378 if (c == '\t')
379 return tab_size;
381 return 1;
382 }
384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
385 * Illegal bytes are set one. */
386 static const unsigned char utf8_bytes[256] = {
387 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
388 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
394 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
395 };
397 static inline unsigned char
398 utf8_char_length(const char *string, const char *end)
399 {
400 int c = *(unsigned char *) string;
402 return utf8_bytes[c];
403 }
405 /* Decode UTF-8 multi-byte representation into a Unicode character. */
406 static inline unsigned long
407 utf8_to_unicode(const char *string, size_t length)
408 {
409 unsigned long unicode;
411 switch (length) {
412 case 1:
413 unicode = string[0];
414 break;
415 case 2:
416 unicode = (string[0] & 0x1f) << 6;
417 unicode += (string[1] & 0x3f);
418 break;
419 case 3:
420 unicode = (string[0] & 0x0f) << 12;
421 unicode += ((string[1] & 0x3f) << 6);
422 unicode += (string[2] & 0x3f);
423 break;
424 case 4:
425 unicode = (string[0] & 0x0f) << 18;
426 unicode += ((string[1] & 0x3f) << 12);
427 unicode += ((string[2] & 0x3f) << 6);
428 unicode += (string[3] & 0x3f);
429 break;
430 case 5:
431 unicode = (string[0] & 0x0f) << 24;
432 unicode += ((string[1] & 0x3f) << 18);
433 unicode += ((string[2] & 0x3f) << 12);
434 unicode += ((string[3] & 0x3f) << 6);
435 unicode += (string[4] & 0x3f);
436 break;
437 case 6:
438 unicode = (string[0] & 0x01) << 30;
439 unicode += ((string[1] & 0x3f) << 24);
440 unicode += ((string[2] & 0x3f) << 18);
441 unicode += ((string[3] & 0x3f) << 12);
442 unicode += ((string[4] & 0x3f) << 6);
443 unicode += (string[5] & 0x3f);
444 break;
445 default:
446 return 0;
447 }
449 /* Invalid characters could return the special 0xfffd value but NUL
450 * should be just as good. */
451 return unicode > 0xffff ? 0 : unicode;
452 }
454 /* Calculates how much of string can be shown within the given maximum width
455 * and sets trimmed parameter to non-zero value if all of string could not be
456 * shown. If the reserve flag is TRUE, it will reserve at least one
457 * trailing character, which can be useful when drawing a delimiter.
458 *
459 * Returns the number of bytes to output from string to satisfy max_width. */
460 static size_t
461 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
462 {
463 const char *string = *start;
464 const char *end = strchr(string, '\0');
465 unsigned char last_bytes = 0;
466 size_t last_ucwidth = 0;
468 *width = 0;
469 *trimmed = 0;
471 while (string < end) {
472 unsigned char bytes = utf8_char_length(string, end);
473 size_t ucwidth;
474 unsigned long unicode;
476 if (string + bytes > end)
477 break;
479 /* Change representation to figure out whether
480 * it is a single- or double-width character. */
482 unicode = utf8_to_unicode(string, bytes);
483 /* FIXME: Graceful handling of invalid Unicode character. */
484 if (!unicode)
485 break;
487 ucwidth = unicode_width(unicode, tab_size);
488 if (skip > 0) {
489 skip -= ucwidth <= skip ? ucwidth : skip;
490 *start += bytes;
491 }
492 *width += ucwidth;
493 if (*width > max_width) {
494 *trimmed = 1;
495 *width -= ucwidth;
496 if (reserve && *width == max_width) {
497 string -= last_bytes;
498 *width -= last_ucwidth;
499 }
500 break;
501 }
503 string += bytes;
504 last_bytes = ucwidth ? bytes : 0;
505 last_ucwidth = ucwidth;
506 }
508 return string - *start;
509 }
512 #define DATE_INFO \
513 DATE_(NO), \
514 DATE_(DEFAULT), \
515 DATE_(LOCAL), \
516 DATE_(RELATIVE), \
517 DATE_(SHORT)
519 enum date {
520 #define DATE_(name) DATE_##name
521 DATE_INFO
522 #undef DATE_
523 };
525 static const struct enum_map date_map[] = {
526 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
527 DATE_INFO
528 #undef DATE_
529 };
531 struct time {
532 time_t sec;
533 int tz;
534 };
536 static inline int timecmp(const struct time *t1, const struct time *t2)
537 {
538 return t1->sec - t2->sec;
539 }
541 static const char *
542 mkdate(const struct time *time, enum date date)
543 {
544 static char buf[DATE_COLS + 1];
545 static const struct enum_map reldate[] = {
546 { "second", 1, 60 * 2 },
547 { "minute", 60, 60 * 60 * 2 },
548 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
549 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
550 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
551 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
552 };
553 struct tm tm;
555 if (!date || !time || !time->sec)
556 return "";
558 if (date == DATE_RELATIVE) {
559 struct timeval now;
560 time_t date = time->sec + time->tz;
561 time_t seconds;
562 int i;
564 gettimeofday(&now, NULL);
565 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
566 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
567 if (seconds >= reldate[i].value)
568 continue;
570 seconds /= reldate[i].namelen;
571 if (!string_format(buf, "%ld %s%s %s",
572 seconds, reldate[i].name,
573 seconds > 1 ? "s" : "",
574 now.tv_sec >= date ? "ago" : "ahead"))
575 break;
576 return buf;
577 }
578 }
580 if (date == DATE_LOCAL) {
581 time_t date = time->sec + time->tz;
582 localtime_r(&date, &tm);
583 }
584 else {
585 gmtime_r(&time->sec, &tm);
586 }
587 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
588 }
591 #define AUTHOR_VALUES \
592 AUTHOR_(NO), \
593 AUTHOR_(FULL), \
594 AUTHOR_(ABBREVIATED)
596 enum author {
597 #define AUTHOR_(name) AUTHOR_##name
598 AUTHOR_VALUES,
599 #undef AUTHOR_
600 AUTHOR_DEFAULT = AUTHOR_FULL
601 };
603 static const struct enum_map author_map[] = {
604 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
605 AUTHOR_VALUES
606 #undef AUTHOR_
607 };
609 static const char *
610 get_author_initials(const char *author)
611 {
612 static char initials[AUTHOR_COLS * 6 + 1];
613 size_t pos = 0;
614 const char *end = strchr(author, '\0');
616 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
618 memset(initials, 0, sizeof(initials));
619 while (author < end) {
620 unsigned char bytes;
621 size_t i;
623 while (is_initial_sep(*author))
624 author++;
626 bytes = utf8_char_length(author, end);
627 if (bytes < sizeof(initials) - 1 - pos) {
628 while (bytes--) {
629 initials[pos++] = *author++;
630 }
631 }
633 for (i = pos; author < end && !is_initial_sep(*author); author++) {
634 if (i < sizeof(initials) - 1)
635 initials[i++] = *author;
636 }
638 initials[i++] = 0;
639 }
641 return initials;
642 }
645 static bool
646 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
647 {
648 int valuelen;
650 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
651 bool advance = cmd[valuelen] != 0;
653 cmd[valuelen] = 0;
654 argv[(*argc)++] = chomp_string(cmd);
655 cmd = chomp_string(cmd + valuelen + advance);
656 }
658 if (*argc < SIZEOF_ARG)
659 argv[*argc] = NULL;
660 return *argc < SIZEOF_ARG;
661 }
663 static bool
664 argv_from_env(const char **argv, const char *name)
665 {
666 char *env = argv ? getenv(name) : NULL;
667 int argc = 0;
669 if (env && *env)
670 env = strdup(env);
671 return !env || argv_from_string(argv, &argc, env);
672 }
674 static void
675 argv_free(const char *argv[])
676 {
677 int argc;
679 for (argc = 0; argv[argc]; argc++)
680 free((void *) argv[argc]);
681 argv[0] = NULL;
682 }
684 static bool
685 argv_copy(const char *dst[], const char *src[], bool allocate)
686 {
687 int argc;
689 for (argc = 0; src[argc]; argc++)
690 if (!(dst[argc] = allocate ? strdup(src[argc]) : src[argc]))
691 return FALSE;
692 return TRUE;
693 }
696 /*
697 * Executing external commands.
698 */
700 enum io_type {
701 IO_FD, /* File descriptor based IO. */
702 IO_BG, /* Execute command in the background. */
703 IO_FG, /* Execute command with same std{in,out,err}. */
704 IO_RD, /* Read only fork+exec IO. */
705 IO_WR, /* Write only fork+exec IO. */
706 IO_AP, /* Append fork+exec output to file. */
707 };
709 struct io {
710 enum io_type type; /* The requested type of pipe. */
711 const char *dir; /* Directory from which to execute. */
712 pid_t pid; /* PID of spawned process. */
713 int pipe; /* Pipe end for reading or writing. */
714 int error; /* Error status. */
715 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
716 char *buf; /* Read buffer. */
717 size_t bufalloc; /* Allocated buffer size. */
718 size_t bufsize; /* Buffer content size. */
719 char *bufpos; /* Current buffer position. */
720 unsigned int eof:1; /* Has end of file been reached. */
721 };
723 static void
724 io_reset(struct io *io)
725 {
726 io->pipe = -1;
727 io->pid = 0;
728 io->buf = io->bufpos = NULL;
729 io->bufalloc = io->bufsize = 0;
730 io->error = 0;
731 io->eof = 0;
732 }
734 static void
735 io_init(struct io *io, const char *dir, enum io_type type)
736 {
737 io_reset(io);
738 io->type = type;
739 io->dir = dir;
740 }
742 static void
743 io_prepare(struct io *io, const char *dir, enum io_type type, const char *argv[])
744 {
745 io_init(io, dir, type);
746 argv_copy(io->argv, argv, FALSE);
747 }
749 static bool
750 io_open(struct io *io, const char *fmt, ...)
751 {
752 char name[SIZEOF_STR] = "";
753 bool fits;
754 va_list args;
756 io_init(io, NULL, IO_FD);
758 va_start(args, fmt);
759 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
760 va_end(args);
762 if (!fits) {
763 io->error = ENAMETOOLONG;
764 return FALSE;
765 }
766 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
767 if (io->pipe == -1)
768 io->error = errno;
769 return io->pipe != -1;
770 }
772 static bool
773 io_kill(struct io *io)
774 {
775 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
776 }
778 static bool
779 io_done(struct io *io)
780 {
781 pid_t pid = io->pid;
783 if (io->pipe != -1)
784 close(io->pipe);
785 free(io->buf);
786 io_reset(io);
788 while (pid > 0) {
789 int status;
790 pid_t waiting = waitpid(pid, &status, 0);
792 if (waiting < 0) {
793 if (errno == EINTR)
794 continue;
795 io->error = errno;
796 return FALSE;
797 }
799 return waiting == pid &&
800 !WIFSIGNALED(status) &&
801 WIFEXITED(status) &&
802 !WEXITSTATUS(status);
803 }
805 return TRUE;
806 }
808 static bool
809 io_start(struct io *io)
810 {
811 int pipefds[2] = { -1, -1 };
813 if (io->type == IO_FD)
814 return TRUE;
816 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
817 io->error = errno;
818 return FALSE;
819 } else if (io->type == IO_AP) {
820 pipefds[1] = io->pipe;
821 }
823 if ((io->pid = fork())) {
824 if (io->pid == -1)
825 io->error = errno;
826 if (pipefds[!(io->type == IO_WR)] != -1)
827 close(pipefds[!(io->type == IO_WR)]);
828 if (io->pid != -1) {
829 io->pipe = pipefds[!!(io->type == IO_WR)];
830 return TRUE;
831 }
833 } else {
834 if (io->type != IO_FG) {
835 int devnull = open("/dev/null", O_RDWR);
836 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
837 int writefd = (io->type == IO_RD || io->type == IO_AP)
838 ? pipefds[1] : devnull;
840 dup2(readfd, STDIN_FILENO);
841 dup2(writefd, STDOUT_FILENO);
842 dup2(devnull, STDERR_FILENO);
844 close(devnull);
845 if (pipefds[0] != -1)
846 close(pipefds[0]);
847 if (pipefds[1] != -1)
848 close(pipefds[1]);
849 }
851 if (io->dir && *io->dir && chdir(io->dir) == -1)
852 exit(errno);
854 execvp(io->argv[0], (char *const*) io->argv);
855 exit(errno);
856 }
858 if (pipefds[!!(io->type == IO_WR)] != -1)
859 close(pipefds[!!(io->type == IO_WR)]);
860 return FALSE;
861 }
863 static bool
864 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
865 {
866 io_prepare(io, dir, type, argv);
867 return io_start(io);
868 }
870 static bool
871 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
872 {
873 struct io io = {};
875 io_prepare(&io, dir, type, argv);
876 io.pipe = fd;
877 return io_start(&io) && io_done(&io);
878 }
880 static bool
881 io_run_bg(const char **argv)
882 {
883 return io_complete(IO_BG, argv, NULL, -1);
884 }
886 static bool
887 io_run_fg(const char **argv, const char *dir)
888 {
889 return io_complete(IO_FG, argv, dir, -1);
890 }
892 static bool
893 io_run_append(const char **argv, int fd)
894 {
895 return io_complete(IO_AP, argv, NULL, -1);
896 }
898 static bool
899 io_eof(struct io *io)
900 {
901 return io->eof;
902 }
904 static int
905 io_error(struct io *io)
906 {
907 return io->error;
908 }
910 static char *
911 io_strerror(struct io *io)
912 {
913 return strerror(io->error);
914 }
916 static bool
917 io_can_read(struct io *io)
918 {
919 struct timeval tv = { 0, 500 };
920 fd_set fds;
922 FD_ZERO(&fds);
923 FD_SET(io->pipe, &fds);
925 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
926 }
928 static ssize_t
929 io_read(struct io *io, void *buf, size_t bufsize)
930 {
931 do {
932 ssize_t readsize = read(io->pipe, buf, bufsize);
934 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
935 continue;
936 else if (readsize == -1)
937 io->error = errno;
938 else if (readsize == 0)
939 io->eof = 1;
940 return readsize;
941 } while (1);
942 }
944 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
946 static char *
947 io_get(struct io *io, int c, bool can_read)
948 {
949 char *eol;
950 ssize_t readsize;
952 while (TRUE) {
953 if (io->bufsize > 0) {
954 eol = memchr(io->bufpos, c, io->bufsize);
955 if (eol) {
956 char *line = io->bufpos;
958 *eol = 0;
959 io->bufpos = eol + 1;
960 io->bufsize -= io->bufpos - line;
961 return line;
962 }
963 }
965 if (io_eof(io)) {
966 if (io->bufsize) {
967 io->bufpos[io->bufsize] = 0;
968 io->bufsize = 0;
969 return io->bufpos;
970 }
971 return NULL;
972 }
974 if (!can_read)
975 return NULL;
977 if (io->bufsize > 0 && io->bufpos > io->buf)
978 memmove(io->buf, io->bufpos, io->bufsize);
980 if (io->bufalloc == io->bufsize) {
981 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
982 return NULL;
983 io->bufalloc += BUFSIZ;
984 }
986 io->bufpos = io->buf;
987 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
988 if (io_error(io))
989 return NULL;
990 io->bufsize += readsize;
991 }
992 }
994 static bool
995 io_write(struct io *io, const void *buf, size_t bufsize)
996 {
997 size_t written = 0;
999 while (!io_error(io) && written < bufsize) {
1000 ssize_t size;
1002 size = write(io->pipe, buf + written, bufsize - written);
1003 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1004 continue;
1005 else if (size == -1)
1006 io->error = errno;
1007 else
1008 written += size;
1009 }
1011 return written == bufsize;
1012 }
1014 static bool
1015 io_read_buf(struct io *io, char buf[], size_t bufsize)
1016 {
1017 char *result = io_get(io, '\n', TRUE);
1019 if (result) {
1020 result = chomp_string(result);
1021 string_ncopy_do(buf, bufsize, result, strlen(result));
1022 }
1024 return io_done(io) && result;
1025 }
1027 static bool
1028 io_run_buf(const char **argv, char buf[], size_t bufsize)
1029 {
1030 struct io io = {};
1032 io_prepare(&io, NULL, IO_RD, argv);
1033 return io_start(&io) && io_read_buf(&io, buf, bufsize);
1034 }
1036 static int
1037 io_load(struct io *io, const char *separators,
1038 int (*read_property)(char *, size_t, char *, size_t))
1039 {
1040 char *name;
1041 int state = OK;
1043 if (!io_start(io))
1044 return ERR;
1046 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1047 char *value;
1048 size_t namelen;
1049 size_t valuelen;
1051 name = chomp_string(name);
1052 namelen = strcspn(name, separators);
1054 if (name[namelen]) {
1055 name[namelen] = 0;
1056 value = chomp_string(name + namelen + 1);
1057 valuelen = strlen(value);
1059 } else {
1060 value = "";
1061 valuelen = 0;
1062 }
1064 state = read_property(name, namelen, value, valuelen);
1065 }
1067 if (state != ERR && io_error(io))
1068 state = ERR;
1069 io_done(io);
1071 return state;
1072 }
1074 static int
1075 io_run_load(const char **argv, const char *separators,
1076 int (*read_property)(char *, size_t, char *, size_t))
1077 {
1078 struct io io = {};
1080 io_prepare(&io, NULL, IO_RD, argv);
1081 return io_load(&io, separators, read_property);
1082 }
1085 /*
1086 * User requests
1087 */
1089 #define REQ_INFO \
1090 /* XXX: Keep the view request first and in sync with views[]. */ \
1091 REQ_GROUP("View switching") \
1092 REQ_(VIEW_MAIN, "Show main view"), \
1093 REQ_(VIEW_DIFF, "Show diff view"), \
1094 REQ_(VIEW_LOG, "Show log view"), \
1095 REQ_(VIEW_TREE, "Show tree view"), \
1096 REQ_(VIEW_BLOB, "Show blob view"), \
1097 REQ_(VIEW_BLAME, "Show blame view"), \
1098 REQ_(VIEW_BRANCH, "Show branch view"), \
1099 REQ_(VIEW_HELP, "Show help page"), \
1100 REQ_(VIEW_PAGER, "Show pager view"), \
1101 REQ_(VIEW_STATUS, "Show status view"), \
1102 REQ_(VIEW_STAGE, "Show stage view"), \
1103 \
1104 REQ_GROUP("View manipulation") \
1105 REQ_(ENTER, "Enter current line and scroll"), \
1106 REQ_(NEXT, "Move to next"), \
1107 REQ_(PREVIOUS, "Move to previous"), \
1108 REQ_(PARENT, "Move to parent"), \
1109 REQ_(VIEW_NEXT, "Move focus to next view"), \
1110 REQ_(REFRESH, "Reload and refresh"), \
1111 REQ_(MAXIMIZE, "Maximize the current view"), \
1112 REQ_(VIEW_CLOSE, "Close the current view"), \
1113 REQ_(QUIT, "Close all views and quit"), \
1114 \
1115 REQ_GROUP("View specific requests") \
1116 REQ_(STATUS_UPDATE, "Update file status"), \
1117 REQ_(STATUS_REVERT, "Revert file changes"), \
1118 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1119 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1120 \
1121 REQ_GROUP("Cursor navigation") \
1122 REQ_(MOVE_UP, "Move cursor one line up"), \
1123 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1124 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1125 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1126 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1127 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1128 \
1129 REQ_GROUP("Scrolling") \
1130 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1131 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1132 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1133 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1134 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1135 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1136 \
1137 REQ_GROUP("Searching") \
1138 REQ_(SEARCH, "Search the view"), \
1139 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1140 REQ_(FIND_NEXT, "Find next search match"), \
1141 REQ_(FIND_PREV, "Find previous search match"), \
1142 \
1143 REQ_GROUP("Option manipulation") \
1144 REQ_(OPTIONS, "Open option menu"), \
1145 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1146 REQ_(TOGGLE_DATE, "Toggle date display"), \
1147 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1149 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1150 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1151 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153 \
1154 REQ_GROUP("Misc") \
1155 REQ_(PROMPT, "Bring up the prompt"), \
1156 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1157 REQ_(SHOW_VERSION, "Show version information"), \
1158 REQ_(STOP_LOADING, "Stop all loading views"), \
1159 REQ_(EDIT, "Open in editor"), \
1160 REQ_(NONE, "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168 /* Offset all requests to avoid conflicts with ncurses getch values. */
1169 REQ_UNKNOWN = KEY_MAX + 1,
1170 REQ_OFFSET,
1171 REQ_INFO
1173 #undef REQ_GROUP
1174 #undef REQ_
1175 };
1177 struct request_info {
1178 enum request request;
1179 const char *name;
1180 int namelen;
1181 const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187 REQ_INFO
1188 #undef REQ_GROUP
1189 #undef REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1194 {
1195 int namelen = strlen(name);
1196 int i;
1198 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199 if (enum_equals(req_info[i], name, namelen))
1200 return req_info[i].request;
1202 return REQ_UNKNOWN;
1203 }
1206 /*
1207 * Options
1208 */
1210 /* Option and state variables. */
1211 static enum date opt_date = DATE_DEFAULT;
1212 static enum author opt_author = AUTHOR_DEFAULT;
1213 static bool opt_line_number = FALSE;
1214 static bool opt_line_graphics = TRUE;
1215 static bool opt_rev_graph = FALSE;
1216 static bool opt_show_refs = TRUE;
1217 static int opt_num_interval = 5;
1218 static double opt_hscroll = 0.50;
1219 static double opt_scale_split_view = 2.0 / 3.0;
1220 static int opt_tab_size = 8;
1221 static int opt_author_cols = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR] = "";
1223 static char opt_file[SIZEOF_STR] = "";
1224 static char opt_ref[SIZEOF_REF] = "";
1225 static char opt_head[SIZEOF_REF] = "";
1226 static char opt_remote[SIZEOF_REF] = "";
1227 static char opt_encoding[20] = "UTF-8";
1228 static iconv_t opt_iconv_in = ICONV_NONE;
1229 static iconv_t opt_iconv_out = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR] = "";
1231 static char opt_cdup[SIZEOF_STR] = "";
1232 static char opt_prefix[SIZEOF_STR] = "";
1233 static char opt_git_dir[SIZEOF_STR] = "";
1234 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR] = "";
1236 static FILE *opt_tty = NULL;
1238 #define is_initial_commit() (!get_ref_head())
1239 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243 * Line-oriented content detection.
1244 */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1248 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1249 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1250 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1251 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1252 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1253 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1254 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1255 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1256 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1257 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1260 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1261 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1262 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1263 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1264 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1265 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1267 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1268 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1269 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1270 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1271 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1272 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1273 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1274 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1275 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1276 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1277 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1278 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1279 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1280 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1281 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1282 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1283 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1284 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1285 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1286 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1287 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1288 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1289 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1290 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1291 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1292 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1293 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1294 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1295 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1296 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1297 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1298 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1299 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1300 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1301 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1302 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1303 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1304 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1305 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1307 enum line_type {
1308 #define LINE(type, line, fg, bg, attr) \
1309 LINE_##type
1310 LINE_INFO,
1311 LINE_NONE
1312 #undef LINE
1313 };
1315 struct line_info {
1316 const char *name; /* Option name. */
1317 int namelen; /* Size of option name. */
1318 const char *line; /* The start of line to match. */
1319 int linelen; /* Size of string to match. */
1320 int fg, bg, attr; /* Color and text attributes for the lines. */
1321 };
1323 static struct line_info line_info[] = {
1324 #define LINE(type, line, fg, bg, attr) \
1325 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1326 LINE_INFO
1327 #undef LINE
1328 };
1330 static enum line_type
1331 get_line_type(const char *line)
1332 {
1333 int linelen = strlen(line);
1334 enum line_type type;
1336 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1337 /* Case insensitive search matches Signed-off-by lines better. */
1338 if (linelen >= line_info[type].linelen &&
1339 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1340 return type;
1342 return LINE_DEFAULT;
1343 }
1345 static inline int
1346 get_line_attr(enum line_type type)
1347 {
1348 assert(type < ARRAY_SIZE(line_info));
1349 return COLOR_PAIR(type) | line_info[type].attr;
1350 }
1352 static struct line_info *
1353 get_line_info(const char *name)
1354 {
1355 size_t namelen = strlen(name);
1356 enum line_type type;
1358 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1359 if (enum_equals(line_info[type], name, namelen))
1360 return &line_info[type];
1362 return NULL;
1363 }
1365 static void
1366 init_colors(void)
1367 {
1368 int default_bg = line_info[LINE_DEFAULT].bg;
1369 int default_fg = line_info[LINE_DEFAULT].fg;
1370 enum line_type type;
1372 start_color();
1374 if (assume_default_colors(default_fg, default_bg) == ERR) {
1375 default_bg = COLOR_BLACK;
1376 default_fg = COLOR_WHITE;
1377 }
1379 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1380 struct line_info *info = &line_info[type];
1381 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1382 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1384 init_pair(type, fg, bg);
1385 }
1386 }
1388 struct line {
1389 enum line_type type;
1391 /* State flags */
1392 unsigned int selected:1;
1393 unsigned int dirty:1;
1394 unsigned int cleareol:1;
1395 unsigned int other:16;
1397 void *data; /* User data */
1398 };
1401 /*
1402 * Keys
1403 */
1405 struct keybinding {
1406 int alias;
1407 enum request request;
1408 };
1410 static struct keybinding default_keybindings[] = {
1411 /* View switching */
1412 { 'm', REQ_VIEW_MAIN },
1413 { 'd', REQ_VIEW_DIFF },
1414 { 'l', REQ_VIEW_LOG },
1415 { 't', REQ_VIEW_TREE },
1416 { 'f', REQ_VIEW_BLOB },
1417 { 'B', REQ_VIEW_BLAME },
1418 { 'H', REQ_VIEW_BRANCH },
1419 { 'p', REQ_VIEW_PAGER },
1420 { 'h', REQ_VIEW_HELP },
1421 { 'S', REQ_VIEW_STATUS },
1422 { 'c', REQ_VIEW_STAGE },
1424 /* View manipulation */
1425 { 'q', REQ_VIEW_CLOSE },
1426 { KEY_TAB, REQ_VIEW_NEXT },
1427 { KEY_RETURN, REQ_ENTER },
1428 { KEY_UP, REQ_PREVIOUS },
1429 { KEY_DOWN, REQ_NEXT },
1430 { 'R', REQ_REFRESH },
1431 { KEY_F(5), REQ_REFRESH },
1432 { 'O', REQ_MAXIMIZE },
1434 /* Cursor navigation */
1435 { 'k', REQ_MOVE_UP },
1436 { 'j', REQ_MOVE_DOWN },
1437 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1438 { KEY_END, REQ_MOVE_LAST_LINE },
1439 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1440 { ' ', REQ_MOVE_PAGE_DOWN },
1441 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1442 { 'b', REQ_MOVE_PAGE_UP },
1443 { '-', REQ_MOVE_PAGE_UP },
1445 /* Scrolling */
1446 { KEY_LEFT, REQ_SCROLL_LEFT },
1447 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1448 { KEY_IC, REQ_SCROLL_LINE_UP },
1449 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1450 { 'w', REQ_SCROLL_PAGE_UP },
1451 { 's', REQ_SCROLL_PAGE_DOWN },
1453 /* Searching */
1454 { '/', REQ_SEARCH },
1455 { '?', REQ_SEARCH_BACK },
1456 { 'n', REQ_FIND_NEXT },
1457 { 'N', REQ_FIND_PREV },
1459 /* Misc */
1460 { 'Q', REQ_QUIT },
1461 { 'z', REQ_STOP_LOADING },
1462 { 'v', REQ_SHOW_VERSION },
1463 { 'r', REQ_SCREEN_REDRAW },
1464 { 'o', REQ_OPTIONS },
1465 { '.', REQ_TOGGLE_LINENO },
1466 { 'D', REQ_TOGGLE_DATE },
1467 { 'A', REQ_TOGGLE_AUTHOR },
1468 { 'g', REQ_TOGGLE_REV_GRAPH },
1469 { 'F', REQ_TOGGLE_REFS },
1470 { 'I', REQ_TOGGLE_SORT_ORDER },
1471 { 'i', REQ_TOGGLE_SORT_FIELD },
1472 { ':', REQ_PROMPT },
1473 { 'u', REQ_STATUS_UPDATE },
1474 { '!', REQ_STATUS_REVERT },
1475 { 'M', REQ_STATUS_MERGE },
1476 { '@', REQ_STAGE_NEXT },
1477 { ',', REQ_PARENT },
1478 { 'e', REQ_EDIT },
1479 };
1481 #define KEYMAP_INFO \
1482 KEYMAP_(GENERIC), \
1483 KEYMAP_(MAIN), \
1484 KEYMAP_(DIFF), \
1485 KEYMAP_(LOG), \
1486 KEYMAP_(TREE), \
1487 KEYMAP_(BLOB), \
1488 KEYMAP_(BLAME), \
1489 KEYMAP_(BRANCH), \
1490 KEYMAP_(PAGER), \
1491 KEYMAP_(HELP), \
1492 KEYMAP_(STATUS), \
1493 KEYMAP_(STAGE)
1495 enum keymap {
1496 #define KEYMAP_(name) KEYMAP_##name
1497 KEYMAP_INFO
1498 #undef KEYMAP_
1499 };
1501 static const struct enum_map keymap_table[] = {
1502 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1503 KEYMAP_INFO
1504 #undef KEYMAP_
1505 };
1507 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1509 struct keybinding_table {
1510 struct keybinding *data;
1511 size_t size;
1512 };
1514 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1516 static void
1517 add_keybinding(enum keymap keymap, enum request request, int key)
1518 {
1519 struct keybinding_table *table = &keybindings[keymap];
1520 size_t i;
1522 for (i = 0; i < keybindings[keymap].size; i++) {
1523 if (keybindings[keymap].data[i].alias == key) {
1524 keybindings[keymap].data[i].request = request;
1525 return;
1526 }
1527 }
1529 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1530 if (!table->data)
1531 die("Failed to allocate keybinding");
1532 table->data[table->size].alias = key;
1533 table->data[table->size++].request = request;
1535 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1536 int i;
1538 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1539 if (default_keybindings[i].alias == key)
1540 default_keybindings[i].request = REQ_NONE;
1541 }
1542 }
1544 /* Looks for a key binding first in the given map, then in the generic map, and
1545 * lastly in the default keybindings. */
1546 static enum request
1547 get_keybinding(enum keymap keymap, int key)
1548 {
1549 size_t i;
1551 for (i = 0; i < keybindings[keymap].size; i++)
1552 if (keybindings[keymap].data[i].alias == key)
1553 return keybindings[keymap].data[i].request;
1555 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1556 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1557 return keybindings[KEYMAP_GENERIC].data[i].request;
1559 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560 if (default_keybindings[i].alias == key)
1561 return default_keybindings[i].request;
1563 return (enum request) key;
1564 }
1567 struct key {
1568 const char *name;
1569 int value;
1570 };
1572 static const struct key key_table[] = {
1573 { "Enter", KEY_RETURN },
1574 { "Space", ' ' },
1575 { "Backspace", KEY_BACKSPACE },
1576 { "Tab", KEY_TAB },
1577 { "Escape", KEY_ESC },
1578 { "Left", KEY_LEFT },
1579 { "Right", KEY_RIGHT },
1580 { "Up", KEY_UP },
1581 { "Down", KEY_DOWN },
1582 { "Insert", KEY_IC },
1583 { "Delete", KEY_DC },
1584 { "Hash", '#' },
1585 { "Home", KEY_HOME },
1586 { "End", KEY_END },
1587 { "PageUp", KEY_PPAGE },
1588 { "PageDown", KEY_NPAGE },
1589 { "F1", KEY_F(1) },
1590 { "F2", KEY_F(2) },
1591 { "F3", KEY_F(3) },
1592 { "F4", KEY_F(4) },
1593 { "F5", KEY_F(5) },
1594 { "F6", KEY_F(6) },
1595 { "F7", KEY_F(7) },
1596 { "F8", KEY_F(8) },
1597 { "F9", KEY_F(9) },
1598 { "F10", KEY_F(10) },
1599 { "F11", KEY_F(11) },
1600 { "F12", KEY_F(12) },
1601 };
1603 static int
1604 get_key_value(const char *name)
1605 {
1606 int i;
1608 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1609 if (!strcasecmp(key_table[i].name, name))
1610 return key_table[i].value;
1612 if (strlen(name) == 1 && isprint(*name))
1613 return (int) *name;
1615 return ERR;
1616 }
1618 static const char *
1619 get_key_name(int key_value)
1620 {
1621 static char key_char[] = "'X'";
1622 const char *seq = NULL;
1623 int key;
1625 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1626 if (key_table[key].value == key_value)
1627 seq = key_table[key].name;
1629 if (seq == NULL &&
1630 key_value < 127 &&
1631 isprint(key_value)) {
1632 key_char[1] = (char) key_value;
1633 seq = key_char;
1634 }
1636 return seq ? seq : "(no key)";
1637 }
1639 static bool
1640 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1641 {
1642 const char *sep = *pos > 0 ? ", " : "";
1643 const char *keyname = get_key_name(keybinding->alias);
1645 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1646 }
1648 static bool
1649 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1650 enum keymap keymap, bool all)
1651 {
1652 int i;
1654 for (i = 0; i < keybindings[keymap].size; i++) {
1655 if (keybindings[keymap].data[i].request == request) {
1656 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1657 return FALSE;
1658 if (!all)
1659 break;
1660 }
1661 }
1663 return TRUE;
1664 }
1666 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1668 static const char *
1669 get_keys(enum keymap keymap, enum request request, bool all)
1670 {
1671 static char buf[BUFSIZ];
1672 size_t pos = 0;
1673 int i;
1675 buf[pos] = 0;
1677 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1678 return "Too many keybindings!";
1679 if (pos > 0 && !all)
1680 return buf;
1682 if (keymap != KEYMAP_GENERIC) {
1683 /* Only the generic keymap includes the default keybindings when
1684 * listing all keys. */
1685 if (all)
1686 return buf;
1688 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1689 return "Too many keybindings!";
1690 if (pos)
1691 return buf;
1692 }
1694 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1695 if (default_keybindings[i].request == request) {
1696 if (!append_key(buf, &pos, &default_keybindings[i]))
1697 return "Too many keybindings!";
1698 if (!all)
1699 return buf;
1700 }
1701 }
1703 return buf;
1704 }
1706 struct run_request {
1707 enum keymap keymap;
1708 int key;
1709 const char *argv[SIZEOF_ARG];
1710 };
1712 static struct run_request *run_request;
1713 static size_t run_requests;
1715 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1717 static enum request
1718 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1719 {
1720 struct run_request *req;
1722 if (argc >= ARRAY_SIZE(req->argv) - 1)
1723 return REQ_NONE;
1725 if (!realloc_run_requests(&run_request, run_requests, 1))
1726 return REQ_NONE;
1728 req = &run_request[run_requests];
1729 req->keymap = keymap;
1730 req->key = key;
1731 req->argv[0] = NULL;
1733 if (!argv_copy(req->argv, argv, TRUE))
1734 return REQ_NONE;
1736 return REQ_NONE + ++run_requests;
1737 }
1739 static struct run_request *
1740 get_run_request(enum request request)
1741 {
1742 if (request <= REQ_NONE)
1743 return NULL;
1744 return &run_request[request - REQ_NONE - 1];
1745 }
1747 static void
1748 add_builtin_run_requests(void)
1749 {
1750 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1751 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1752 const char *commit[] = { "git", "commit", NULL };
1753 const char *gc[] = { "git", "gc", NULL };
1754 struct {
1755 enum keymap keymap;
1756 int key;
1757 int argc;
1758 const char **argv;
1759 } reqs[] = {
1760 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1761 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1762 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1763 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1764 };
1765 int i;
1767 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1768 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1770 if (req != reqs[i].key)
1771 continue;
1772 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1773 if (req != REQ_NONE)
1774 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1775 }
1776 }
1778 /*
1779 * User config file handling.
1780 */
1782 static int config_lineno;
1783 static bool config_errors;
1784 static const char *config_msg;
1786 static const struct enum_map color_map[] = {
1787 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1788 COLOR_MAP(DEFAULT),
1789 COLOR_MAP(BLACK),
1790 COLOR_MAP(BLUE),
1791 COLOR_MAP(CYAN),
1792 COLOR_MAP(GREEN),
1793 COLOR_MAP(MAGENTA),
1794 COLOR_MAP(RED),
1795 COLOR_MAP(WHITE),
1796 COLOR_MAP(YELLOW),
1797 };
1799 static const struct enum_map attr_map[] = {
1800 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1801 ATTR_MAP(NORMAL),
1802 ATTR_MAP(BLINK),
1803 ATTR_MAP(BOLD),
1804 ATTR_MAP(DIM),
1805 ATTR_MAP(REVERSE),
1806 ATTR_MAP(STANDOUT),
1807 ATTR_MAP(UNDERLINE),
1808 };
1810 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1812 static int parse_step(double *opt, const char *arg)
1813 {
1814 *opt = atoi(arg);
1815 if (!strchr(arg, '%'))
1816 return OK;
1818 /* "Shift down" so 100% and 1 does not conflict. */
1819 *opt = (*opt - 1) / 100;
1820 if (*opt >= 1.0) {
1821 *opt = 0.99;
1822 config_msg = "Step value larger than 100%";
1823 return ERR;
1824 }
1825 if (*opt < 0.0) {
1826 *opt = 1;
1827 config_msg = "Invalid step value";
1828 return ERR;
1829 }
1830 return OK;
1831 }
1833 static int
1834 parse_int(int *opt, const char *arg, int min, int max)
1835 {
1836 int value = atoi(arg);
1838 if (min <= value && value <= max) {
1839 *opt = value;
1840 return OK;
1841 }
1843 config_msg = "Integer value out of bound";
1844 return ERR;
1845 }
1847 static bool
1848 set_color(int *color, const char *name)
1849 {
1850 if (map_enum(color, color_map, name))
1851 return TRUE;
1852 if (!prefixcmp(name, "color"))
1853 return parse_int(color, name + 5, 0, 255) == OK;
1854 return FALSE;
1855 }
1857 /* Wants: object fgcolor bgcolor [attribute] */
1858 static int
1859 option_color_command(int argc, const char *argv[])
1860 {
1861 struct line_info *info;
1863 if (argc < 3) {
1864 config_msg = "Wrong number of arguments given to color command";
1865 return ERR;
1866 }
1868 info = get_line_info(argv[0]);
1869 if (!info) {
1870 static const struct enum_map obsolete[] = {
1871 ENUM_MAP("main-delim", LINE_DELIMITER),
1872 ENUM_MAP("main-date", LINE_DATE),
1873 ENUM_MAP("main-author", LINE_AUTHOR),
1874 };
1875 int index;
1877 if (!map_enum(&index, obsolete, argv[0])) {
1878 config_msg = "Unknown color name";
1879 return ERR;
1880 }
1881 info = &line_info[index];
1882 }
1884 if (!set_color(&info->fg, argv[1]) ||
1885 !set_color(&info->bg, argv[2])) {
1886 config_msg = "Unknown color";
1887 return ERR;
1888 }
1890 info->attr = 0;
1891 while (argc-- > 3) {
1892 int attr;
1894 if (!set_attribute(&attr, argv[argc])) {
1895 config_msg = "Unknown attribute";
1896 return ERR;
1897 }
1898 info->attr |= attr;
1899 }
1901 return OK;
1902 }
1904 static int parse_bool(bool *opt, const char *arg)
1905 {
1906 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1907 ? TRUE : FALSE;
1908 return OK;
1909 }
1911 static int parse_enum_do(unsigned int *opt, const char *arg,
1912 const struct enum_map *map, size_t map_size)
1913 {
1914 bool is_true;
1916 assert(map_size > 1);
1918 if (map_enum_do(map, map_size, (int *) opt, arg))
1919 return OK;
1921 if (parse_bool(&is_true, arg) != OK)
1922 return ERR;
1924 *opt = is_true ? map[1].value : map[0].value;
1925 return OK;
1926 }
1928 #define parse_enum(opt, arg, map) \
1929 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1931 static int
1932 parse_string(char *opt, const char *arg, size_t optsize)
1933 {
1934 int arglen = strlen(arg);
1936 switch (arg[0]) {
1937 case '\"':
1938 case '\'':
1939 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1940 config_msg = "Unmatched quotation";
1941 return ERR;
1942 }
1943 arg += 1; arglen -= 2;
1944 default:
1945 string_ncopy_do(opt, optsize, arg, arglen);
1946 return OK;
1947 }
1948 }
1950 /* Wants: name = value */
1951 static int
1952 option_set_command(int argc, const char *argv[])
1953 {
1954 if (argc != 3) {
1955 config_msg = "Wrong number of arguments given to set command";
1956 return ERR;
1957 }
1959 if (strcmp(argv[1], "=")) {
1960 config_msg = "No value assigned";
1961 return ERR;
1962 }
1964 if (!strcmp(argv[0], "show-author"))
1965 return parse_enum(&opt_author, argv[2], author_map);
1967 if (!strcmp(argv[0], "show-date"))
1968 return parse_enum(&opt_date, argv[2], date_map);
1970 if (!strcmp(argv[0], "show-rev-graph"))
1971 return parse_bool(&opt_rev_graph, argv[2]);
1973 if (!strcmp(argv[0], "show-refs"))
1974 return parse_bool(&opt_show_refs, argv[2]);
1976 if (!strcmp(argv[0], "show-line-numbers"))
1977 return parse_bool(&opt_line_number, argv[2]);
1979 if (!strcmp(argv[0], "line-graphics"))
1980 return parse_bool(&opt_line_graphics, argv[2]);
1982 if (!strcmp(argv[0], "line-number-interval"))
1983 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1985 if (!strcmp(argv[0], "author-width"))
1986 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1988 if (!strcmp(argv[0], "horizontal-scroll"))
1989 return parse_step(&opt_hscroll, argv[2]);
1991 if (!strcmp(argv[0], "split-view-height"))
1992 return parse_step(&opt_scale_split_view, argv[2]);
1994 if (!strcmp(argv[0], "tab-size"))
1995 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1997 if (!strcmp(argv[0], "commit-encoding"))
1998 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2000 config_msg = "Unknown variable name";
2001 return ERR;
2002 }
2004 /* Wants: mode request key */
2005 static int
2006 option_bind_command(int argc, const char *argv[])
2007 {
2008 enum request request;
2009 int keymap = -1;
2010 int key;
2012 if (argc < 3) {
2013 config_msg = "Wrong number of arguments given to bind command";
2014 return ERR;
2015 }
2017 if (!set_keymap(&keymap, argv[0])) {
2018 config_msg = "Unknown key map";
2019 return ERR;
2020 }
2022 key = get_key_value(argv[1]);
2023 if (key == ERR) {
2024 config_msg = "Unknown key";
2025 return ERR;
2026 }
2028 request = get_request(argv[2]);
2029 if (request == REQ_UNKNOWN) {
2030 static const struct enum_map obsolete[] = {
2031 ENUM_MAP("cherry-pick", REQ_NONE),
2032 ENUM_MAP("screen-resize", REQ_NONE),
2033 ENUM_MAP("tree-parent", REQ_PARENT),
2034 };
2035 int alias;
2037 if (map_enum(&alias, obsolete, argv[2])) {
2038 if (alias != REQ_NONE)
2039 add_keybinding(keymap, alias, key);
2040 config_msg = "Obsolete request name";
2041 return ERR;
2042 }
2043 }
2044 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2045 request = add_run_request(keymap, key, argc - 2, argv + 2);
2046 if (request == REQ_UNKNOWN) {
2047 config_msg = "Unknown request name";
2048 return ERR;
2049 }
2051 add_keybinding(keymap, request, key);
2053 return OK;
2054 }
2056 static int
2057 set_option(const char *opt, char *value)
2058 {
2059 const char *argv[SIZEOF_ARG];
2060 int argc = 0;
2062 if (!argv_from_string(argv, &argc, value)) {
2063 config_msg = "Too many option arguments";
2064 return ERR;
2065 }
2067 if (!strcmp(opt, "color"))
2068 return option_color_command(argc, argv);
2070 if (!strcmp(opt, "set"))
2071 return option_set_command(argc, argv);
2073 if (!strcmp(opt, "bind"))
2074 return option_bind_command(argc, argv);
2076 config_msg = "Unknown option command";
2077 return ERR;
2078 }
2080 static int
2081 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2082 {
2083 int status = OK;
2085 config_lineno++;
2086 config_msg = "Internal error";
2088 /* Check for comment markers, since read_properties() will
2089 * only ensure opt and value are split at first " \t". */
2090 optlen = strcspn(opt, "#");
2091 if (optlen == 0)
2092 return OK;
2094 if (opt[optlen] != 0) {
2095 config_msg = "No option value";
2096 status = ERR;
2098 } else {
2099 /* Look for comment endings in the value. */
2100 size_t len = strcspn(value, "#");
2102 if (len < valuelen) {
2103 valuelen = len;
2104 value[valuelen] = 0;
2105 }
2107 status = set_option(opt, value);
2108 }
2110 if (status == ERR) {
2111 warn("Error on line %d, near '%.*s': %s",
2112 config_lineno, (int) optlen, opt, config_msg);
2113 config_errors = TRUE;
2114 }
2116 /* Always keep going if errors are encountered. */
2117 return OK;
2118 }
2120 static void
2121 load_option_file(const char *path)
2122 {
2123 struct io io = {};
2125 /* It's OK that the file doesn't exist. */
2126 if (!io_open(&io, "%s", path))
2127 return;
2129 config_lineno = 0;
2130 config_errors = FALSE;
2132 if (io_load(&io, " \t", read_option) == ERR ||
2133 config_errors == TRUE)
2134 warn("Errors while loading %s.", path);
2135 }
2137 static int
2138 load_options(void)
2139 {
2140 const char *home = getenv("HOME");
2141 const char *tigrc_user = getenv("TIGRC_USER");
2142 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2143 char buf[SIZEOF_STR];
2145 if (!tigrc_system)
2146 tigrc_system = SYSCONFDIR "/tigrc";
2147 load_option_file(tigrc_system);
2149 if (!tigrc_user) {
2150 if (!home || !string_format(buf, "%s/.tigrc", home))
2151 return ERR;
2152 tigrc_user = buf;
2153 }
2154 load_option_file(tigrc_user);
2156 /* Add _after_ loading config files to avoid adding run requests
2157 * that conflict with keybindings. */
2158 add_builtin_run_requests();
2160 return OK;
2161 }
2164 /*
2165 * The viewer
2166 */
2168 struct view;
2169 struct view_ops;
2171 /* The display array of active views and the index of the current view. */
2172 static struct view *display[2];
2173 static unsigned int current_view;
2175 #define foreach_displayed_view(view, i) \
2176 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2178 #define displayed_views() (display[1] != NULL ? 2 : 1)
2180 /* Current head and commit ID */
2181 static char ref_blob[SIZEOF_REF] = "";
2182 static char ref_commit[SIZEOF_REF] = "HEAD";
2183 static char ref_head[SIZEOF_REF] = "HEAD";
2184 static char ref_branch[SIZEOF_REF] = "";
2186 enum view_type {
2187 VIEW_MAIN,
2188 VIEW_DIFF,
2189 VIEW_LOG,
2190 VIEW_TREE,
2191 VIEW_BLOB,
2192 VIEW_BLAME,
2193 VIEW_BRANCH,
2194 VIEW_HELP,
2195 VIEW_PAGER,
2196 VIEW_STATUS,
2197 VIEW_STAGE,
2198 };
2200 struct view {
2201 enum view_type type; /* View type */
2202 const char *name; /* View name */
2203 const char *cmd_env; /* Command line set via environment */
2204 const char *id; /* Points to either of ref_{head,commit,blob} */
2206 struct view_ops *ops; /* View operations */
2208 enum keymap keymap; /* What keymap does this view have */
2209 bool git_dir; /* Whether the view requires a git directory. */
2211 char ref[SIZEOF_REF]; /* Hovered commit reference */
2212 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2214 int height, width; /* The width and height of the main window */
2215 WINDOW *win; /* The main window */
2216 WINDOW *title; /* The title window living below the main window */
2218 /* Navigation */
2219 unsigned long offset; /* Offset of the window top */
2220 unsigned long yoffset; /* Offset from the window side. */
2221 unsigned long lineno; /* Current line number */
2222 unsigned long p_offset; /* Previous offset of the window top */
2223 unsigned long p_yoffset;/* Previous offset from the window side */
2224 unsigned long p_lineno; /* Previous current line number */
2225 bool p_restore; /* Should the previous position be restored. */
2227 /* Searching */
2228 char grep[SIZEOF_STR]; /* Search string */
2229 regex_t *regex; /* Pre-compiled regexp */
2231 /* If non-NULL, points to the view that opened this view. If this view
2232 * is closed tig will switch back to the parent view. */
2233 struct view *parent;
2234 struct view *prev;
2236 /* Buffering */
2237 size_t lines; /* Total number of lines */
2238 struct line *line; /* Line index */
2239 unsigned int digits; /* Number of digits in the lines member. */
2241 /* Drawing */
2242 struct line *curline; /* Line currently being drawn. */
2243 enum line_type curtype; /* Attribute currently used for drawing. */
2244 unsigned long col; /* Column when drawing. */
2245 bool has_scrolled; /* View was scrolled. */
2247 /* Loading */
2248 struct io io;
2249 struct io *pipe;
2250 time_t start_time;
2251 time_t update_secs;
2252 };
2254 struct view_ops {
2255 /* What type of content being displayed. Used in the title bar. */
2256 const char *type;
2257 /* Default command arguments. */
2258 const char **argv;
2259 /* Open and reads in all view content. */
2260 bool (*open)(struct view *view);
2261 /* Read one line; updates view->line. */
2262 bool (*read)(struct view *view, char *data);
2263 /* Draw one line; @lineno must be < view->height. */
2264 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2265 /* Depending on view handle a special requests. */
2266 enum request (*request)(struct view *view, enum request request, struct line *line);
2267 /* Search for regexp in a line. */
2268 bool (*grep)(struct view *view, struct line *line);
2269 /* Select line */
2270 void (*select)(struct view *view, struct line *line);
2271 /* Prepare view for loading */
2272 bool (*prepare)(struct view *view);
2273 };
2275 static struct view_ops blame_ops;
2276 static struct view_ops blob_ops;
2277 static struct view_ops diff_ops;
2278 static struct view_ops help_ops;
2279 static struct view_ops log_ops;
2280 static struct view_ops main_ops;
2281 static struct view_ops pager_ops;
2282 static struct view_ops stage_ops;
2283 static struct view_ops status_ops;
2284 static struct view_ops tree_ops;
2285 static struct view_ops branch_ops;
2287 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2288 { type, name, #env, ref, ops, map, git }
2290 #define VIEW_(id, name, ops, git, ref) \
2291 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2293 static struct view views[] = {
2294 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2295 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2296 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2297 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2298 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2299 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2300 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2301 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2302 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2303 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2304 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2305 };
2307 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2309 #define foreach_view(view, i) \
2310 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2312 #define view_is_displayed(view) \
2313 (view == display[0] || view == display[1])
2315 static enum request
2316 view_request(struct view *view, enum request request)
2317 {
2318 if (!view || !view->lines)
2319 return request;
2320 return view->ops->request(view, request, &view->line[view->lineno]);
2321 }
2324 /*
2325 * View drawing.
2326 */
2328 static inline void
2329 set_view_attr(struct view *view, enum line_type type)
2330 {
2331 if (!view->curline->selected && view->curtype != type) {
2332 (void) wattrset(view->win, get_line_attr(type));
2333 wchgat(view->win, -1, 0, type, NULL);
2334 view->curtype = type;
2335 }
2336 }
2338 static int
2339 draw_chars(struct view *view, enum line_type type, const char *string,
2340 int max_len, bool use_tilde)
2341 {
2342 static char out_buffer[BUFSIZ * 2];
2343 int len = 0;
2344 int col = 0;
2345 int trimmed = FALSE;
2346 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2348 if (max_len <= 0)
2349 return 0;
2351 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2353 set_view_attr(view, type);
2354 if (len > 0) {
2355 if (opt_iconv_out != ICONV_NONE) {
2356 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2357 size_t inlen = len + 1;
2359 char *outbuf = out_buffer;
2360 size_t outlen = sizeof(out_buffer);
2362 size_t ret;
2364 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2365 if (ret != (size_t) -1) {
2366 string = out_buffer;
2367 len = sizeof(out_buffer) - outlen;
2368 }
2369 }
2371 waddnstr(view->win, string, len);
2372 }
2373 if (trimmed && use_tilde) {
2374 set_view_attr(view, LINE_DELIMITER);
2375 waddch(view->win, '~');
2376 col++;
2377 }
2379 return col;
2380 }
2382 static int
2383 draw_space(struct view *view, enum line_type type, int max, int spaces)
2384 {
2385 static char space[] = " ";
2386 int col = 0;
2388 spaces = MIN(max, spaces);
2390 while (spaces > 0) {
2391 int len = MIN(spaces, sizeof(space) - 1);
2393 col += draw_chars(view, type, space, len, FALSE);
2394 spaces -= len;
2395 }
2397 return col;
2398 }
2400 static bool
2401 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2402 {
2403 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2404 return view->width + view->yoffset <= view->col;
2405 }
2407 static bool
2408 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2409 {
2410 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2411 int max = view->width + view->yoffset - view->col;
2412 int i;
2414 if (max < size)
2415 size = max;
2417 set_view_attr(view, type);
2418 /* Using waddch() instead of waddnstr() ensures that
2419 * they'll be rendered correctly for the cursor line. */
2420 for (i = skip; i < size; i++)
2421 waddch(view->win, graphic[i]);
2423 view->col += size;
2424 if (size < max && skip <= size)
2425 waddch(view->win, ' ');
2426 view->col++;
2428 return view->width + view->yoffset <= view->col;
2429 }
2431 static bool
2432 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2433 {
2434 int max = MIN(view->width + view->yoffset - view->col, len);
2435 int col;
2437 if (text)
2438 col = draw_chars(view, type, text, max - 1, trim);
2439 else
2440 col = draw_space(view, type, max - 1, max - 1);
2442 view->col += col;
2443 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2444 return view->width + view->yoffset <= view->col;
2445 }
2447 static bool
2448 draw_date(struct view *view, struct time *time)
2449 {
2450 const char *date = mkdate(time, opt_date);
2451 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2453 return draw_field(view, LINE_DATE, date, cols, FALSE);
2454 }
2456 static bool
2457 draw_author(struct view *view, const char *author)
2458 {
2459 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2460 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2462 if (abbreviate && author)
2463 author = get_author_initials(author);
2465 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2466 }
2468 static bool
2469 draw_mode(struct view *view, mode_t mode)
2470 {
2471 const char *str;
2473 if (S_ISDIR(mode))
2474 str = "drwxr-xr-x";
2475 else if (S_ISLNK(mode))
2476 str = "lrwxrwxrwx";
2477 else if (S_ISGITLINK(mode))
2478 str = "m---------";
2479 else if (S_ISREG(mode) && mode & S_IXUSR)
2480 str = "-rwxr-xr-x";
2481 else if (S_ISREG(mode))
2482 str = "-rw-r--r--";
2483 else
2484 str = "----------";
2486 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2487 }
2489 static bool
2490 draw_lineno(struct view *view, unsigned int lineno)
2491 {
2492 char number[10];
2493 int digits3 = view->digits < 3 ? 3 : view->digits;
2494 int max = MIN(view->width + view->yoffset - view->col, digits3);
2495 char *text = NULL;
2496 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2498 lineno += view->offset + 1;
2499 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2500 static char fmt[] = "%1ld";
2502 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2503 if (string_format(number, fmt, lineno))
2504 text = number;
2505 }
2506 if (text)
2507 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2508 else
2509 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2510 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2511 }
2513 static bool
2514 draw_view_line(struct view *view, unsigned int lineno)
2515 {
2516 struct line *line;
2517 bool selected = (view->offset + lineno == view->lineno);
2519 assert(view_is_displayed(view));
2521 if (view->offset + lineno >= view->lines)
2522 return FALSE;
2524 line = &view->line[view->offset + lineno];
2526 wmove(view->win, lineno, 0);
2527 if (line->cleareol)
2528 wclrtoeol(view->win);
2529 view->col = 0;
2530 view->curline = line;
2531 view->curtype = LINE_NONE;
2532 line->selected = FALSE;
2533 line->dirty = line->cleareol = 0;
2535 if (selected) {
2536 set_view_attr(view, LINE_CURSOR);
2537 line->selected = TRUE;
2538 view->ops->select(view, line);
2539 }
2541 return view->ops->draw(view, line, lineno);
2542 }
2544 static void
2545 redraw_view_dirty(struct view *view)
2546 {
2547 bool dirty = FALSE;
2548 int lineno;
2550 for (lineno = 0; lineno < view->height; lineno++) {
2551 if (view->offset + lineno >= view->lines)
2552 break;
2553 if (!view->line[view->offset + lineno].dirty)
2554 continue;
2555 dirty = TRUE;
2556 if (!draw_view_line(view, lineno))
2557 break;
2558 }
2560 if (!dirty)
2561 return;
2562 wnoutrefresh(view->win);
2563 }
2565 static void
2566 redraw_view_from(struct view *view, int lineno)
2567 {
2568 assert(0 <= lineno && lineno < view->height);
2570 for (; lineno < view->height; lineno++) {
2571 if (!draw_view_line(view, lineno))
2572 break;
2573 }
2575 wnoutrefresh(view->win);
2576 }
2578 static void
2579 redraw_view(struct view *view)
2580 {
2581 werase(view->win);
2582 redraw_view_from(view, 0);
2583 }
2586 static void
2587 update_view_title(struct view *view)
2588 {
2589 char buf[SIZEOF_STR];
2590 char state[SIZEOF_STR];
2591 size_t bufpos = 0, statelen = 0;
2593 assert(view_is_displayed(view));
2595 if (view->type != VIEW_STATUS && view->lines) {
2596 unsigned int view_lines = view->offset + view->height;
2597 unsigned int lines = view->lines
2598 ? MIN(view_lines, view->lines) * 100 / view->lines
2599 : 0;
2601 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2602 view->ops->type,
2603 view->lineno + 1,
2604 view->lines,
2605 lines);
2607 }
2609 if (view->pipe) {
2610 time_t secs = time(NULL) - view->start_time;
2612 /* Three git seconds are a long time ... */
2613 if (secs > 2)
2614 string_format_from(state, &statelen, " loading %lds", secs);
2615 }
2617 string_format_from(buf, &bufpos, "[%s]", view->name);
2618 if (*view->ref && bufpos < view->width) {
2619 size_t refsize = strlen(view->ref);
2620 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2622 if (minsize < view->width)
2623 refsize = view->width - minsize + 7;
2624 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2625 }
2627 if (statelen && bufpos < view->width) {
2628 string_format_from(buf, &bufpos, "%s", state);
2629 }
2631 if (view == display[current_view])
2632 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2633 else
2634 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2636 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2637 wclrtoeol(view->title);
2638 wnoutrefresh(view->title);
2639 }
2641 static int
2642 apply_step(double step, int value)
2643 {
2644 if (step >= 1)
2645 return (int) step;
2646 value *= step + 0.01;
2647 return value ? value : 1;
2648 }
2650 static void
2651 resize_display(void)
2652 {
2653 int offset, i;
2654 struct view *base = display[0];
2655 struct view *view = display[1] ? display[1] : display[0];
2657 /* Setup window dimensions */
2659 getmaxyx(stdscr, base->height, base->width);
2661 /* Make room for the status window. */
2662 base->height -= 1;
2664 if (view != base) {
2665 /* Horizontal split. */
2666 view->width = base->width;
2667 view->height = apply_step(opt_scale_split_view, base->height);
2668 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2669 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2670 base->height -= view->height;
2672 /* Make room for the title bar. */
2673 view->height -= 1;
2674 }
2676 /* Make room for the title bar. */
2677 base->height -= 1;
2679 offset = 0;
2681 foreach_displayed_view (view, i) {
2682 if (!view->win) {
2683 view->win = newwin(view->height, 0, offset, 0);
2684 if (!view->win)
2685 die("Failed to create %s view", view->name);
2687 scrollok(view->win, FALSE);
2689 view->title = newwin(1, 0, offset + view->height, 0);
2690 if (!view->title)
2691 die("Failed to create title window");
2693 } else {
2694 wresize(view->win, view->height, view->width);
2695 mvwin(view->win, offset, 0);
2696 mvwin(view->title, offset + view->height, 0);
2697 }
2699 offset += view->height + 1;
2700 }
2701 }
2703 static void
2704 redraw_display(bool clear)
2705 {
2706 struct view *view;
2707 int i;
2709 foreach_displayed_view (view, i) {
2710 if (clear)
2711 wclear(view->win);
2712 redraw_view(view);
2713 update_view_title(view);
2714 }
2715 }
2718 /*
2719 * Option management
2720 */
2722 static void
2723 toggle_enum_option_do(unsigned int *opt, const char *help,
2724 const struct enum_map *map, size_t size)
2725 {
2726 *opt = (*opt + 1) % size;
2727 redraw_display(FALSE);
2728 report("Displaying %s %s", enum_name(map[*opt]), help);
2729 }
2731 #define toggle_enum_option(opt, help, map) \
2732 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2734 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2735 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2737 static void
2738 toggle_view_option(bool *option, const char *help)
2739 {
2740 *option = !*option;
2741 redraw_display(FALSE);
2742 report("%sabling %s", *option ? "En" : "Dis", help);
2743 }
2745 static void
2746 open_option_menu(void)
2747 {
2748 const struct menu_item menu[] = {
2749 { '.', "line numbers", &opt_line_number },
2750 { 'D', "date display", &opt_date },
2751 { 'A', "author display", &opt_author },
2752 { 'g', "revision graph display", &opt_rev_graph },
2753 { 'F', "reference display", &opt_show_refs },
2754 { 0 }
2755 };
2756 int selected = 0;
2758 if (prompt_menu("Toggle option", menu, &selected)) {
2759 if (menu[selected].data == &opt_date)
2760 toggle_date();
2761 else if (menu[selected].data == &opt_author)
2762 toggle_author();
2763 else
2764 toggle_view_option(menu[selected].data, menu[selected].text);
2765 }
2766 }
2768 static void
2769 maximize_view(struct view *view)
2770 {
2771 memset(display, 0, sizeof(display));
2772 current_view = 0;
2773 display[current_view] = view;
2774 resize_display();
2775 redraw_display(FALSE);
2776 report("");
2777 }
2780 /*
2781 * Navigation
2782 */
2784 static bool
2785 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2786 {
2787 if (lineno >= view->lines)
2788 lineno = view->lines > 0 ? view->lines - 1 : 0;
2790 if (offset > lineno || offset + view->height <= lineno) {
2791 unsigned long half = view->height / 2;
2793 if (lineno > half)
2794 offset = lineno - half;
2795 else
2796 offset = 0;
2797 }
2799 if (offset != view->offset || lineno != view->lineno) {
2800 view->offset = offset;
2801 view->lineno = lineno;
2802 return TRUE;
2803 }
2805 return FALSE;
2806 }
2808 /* Scrolling backend */
2809 static void
2810 do_scroll_view(struct view *view, int lines)
2811 {
2812 bool redraw_current_line = FALSE;
2814 /* The rendering expects the new offset. */
2815 view->offset += lines;
2817 assert(0 <= view->offset && view->offset < view->lines);
2818 assert(lines);
2820 /* Move current line into the view. */
2821 if (view->lineno < view->offset) {
2822 view->lineno = view->offset;
2823 redraw_current_line = TRUE;
2824 } else if (view->lineno >= view->offset + view->height) {
2825 view->lineno = view->offset + view->height - 1;
2826 redraw_current_line = TRUE;
2827 }
2829 assert(view->offset <= view->lineno && view->lineno < view->lines);
2831 /* Redraw the whole screen if scrolling is pointless. */
2832 if (view->height < ABS(lines)) {
2833 redraw_view(view);
2835 } else {
2836 int line = lines > 0 ? view->height - lines : 0;
2837 int end = line + ABS(lines);
2839 scrollok(view->win, TRUE);
2840 wscrl(view->win, lines);
2841 scrollok(view->win, FALSE);
2843 while (line < end && draw_view_line(view, line))
2844 line++;
2846 if (redraw_current_line)
2847 draw_view_line(view, view->lineno - view->offset);
2848 wnoutrefresh(view->win);
2849 }
2851 view->has_scrolled = TRUE;
2852 report("");
2853 }
2855 /* Scroll frontend */
2856 static void
2857 scroll_view(struct view *view, enum request request)
2858 {
2859 int lines = 1;
2861 assert(view_is_displayed(view));
2863 switch (request) {
2864 case REQ_SCROLL_LEFT:
2865 if (view->yoffset == 0) {
2866 report("Cannot scroll beyond the first column");
2867 return;
2868 }
2869 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2870 view->yoffset = 0;
2871 else
2872 view->yoffset -= apply_step(opt_hscroll, view->width);
2873 redraw_view_from(view, 0);
2874 report("");
2875 return;
2876 case REQ_SCROLL_RIGHT:
2877 view->yoffset += apply_step(opt_hscroll, view->width);
2878 redraw_view(view);
2879 report("");
2880 return;
2881 case REQ_SCROLL_PAGE_DOWN:
2882 lines = view->height;
2883 case REQ_SCROLL_LINE_DOWN:
2884 if (view->offset + lines > view->lines)
2885 lines = view->lines - view->offset;
2887 if (lines == 0 || view->offset + view->height >= view->lines) {
2888 report("Cannot scroll beyond the last line");
2889 return;
2890 }
2891 break;
2893 case REQ_SCROLL_PAGE_UP:
2894 lines = view->height;
2895 case REQ_SCROLL_LINE_UP:
2896 if (lines > view->offset)
2897 lines = view->offset;
2899 if (lines == 0) {
2900 report("Cannot scroll beyond the first line");
2901 return;
2902 }
2904 lines = -lines;
2905 break;
2907 default:
2908 die("request %d not handled in switch", request);
2909 }
2911 do_scroll_view(view, lines);
2912 }
2914 /* Cursor moving */
2915 static void
2916 move_view(struct view *view, enum request request)
2917 {
2918 int scroll_steps = 0;
2919 int steps;
2921 switch (request) {
2922 case REQ_MOVE_FIRST_LINE:
2923 steps = -view->lineno;
2924 break;
2926 case REQ_MOVE_LAST_LINE:
2927 steps = view->lines - view->lineno - 1;
2928 break;
2930 case REQ_MOVE_PAGE_UP:
2931 steps = view->height > view->lineno
2932 ? -view->lineno : -view->height;
2933 break;
2935 case REQ_MOVE_PAGE_DOWN:
2936 steps = view->lineno + view->height >= view->lines
2937 ? view->lines - view->lineno - 1 : view->height;
2938 break;
2940 case REQ_MOVE_UP:
2941 steps = -1;
2942 break;
2944 case REQ_MOVE_DOWN:
2945 steps = 1;
2946 break;
2948 default:
2949 die("request %d not handled in switch", request);
2950 }
2952 if (steps <= 0 && view->lineno == 0) {
2953 report("Cannot move beyond the first line");
2954 return;
2956 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2957 report("Cannot move beyond the last line");
2958 return;
2959 }
2961 /* Move the current line */
2962 view->lineno += steps;
2963 assert(0 <= view->lineno && view->lineno < view->lines);
2965 /* Check whether the view needs to be scrolled */
2966 if (view->lineno < view->offset ||
2967 view->lineno >= view->offset + view->height) {
2968 scroll_steps = steps;
2969 if (steps < 0 && -steps > view->offset) {
2970 scroll_steps = -view->offset;
2972 } else if (steps > 0) {
2973 if (view->lineno == view->lines - 1 &&
2974 view->lines > view->height) {
2975 scroll_steps = view->lines - view->offset - 1;
2976 if (scroll_steps >= view->height)
2977 scroll_steps -= view->height - 1;
2978 }
2979 }
2980 }
2982 if (!view_is_displayed(view)) {
2983 view->offset += scroll_steps;
2984 assert(0 <= view->offset && view->offset < view->lines);
2985 view->ops->select(view, &view->line[view->lineno]);
2986 return;
2987 }
2989 /* Repaint the old "current" line if we be scrolling */
2990 if (ABS(steps) < view->height)
2991 draw_view_line(view, view->lineno - steps - view->offset);
2993 if (scroll_steps) {
2994 do_scroll_view(view, scroll_steps);
2995 return;
2996 }
2998 /* Draw the current line */
2999 draw_view_line(view, view->lineno - view->offset);
3001 wnoutrefresh(view->win);
3002 report("");
3003 }
3006 /*
3007 * Searching
3008 */
3010 static void search_view(struct view *view, enum request request);
3012 static bool
3013 grep_text(struct view *view, const char *text[])
3014 {
3015 regmatch_t pmatch;
3016 size_t i;
3018 for (i = 0; text[i]; i++)
3019 if (*text[i] &&
3020 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3021 return TRUE;
3022 return FALSE;
3023 }
3025 static void
3026 select_view_line(struct view *view, unsigned long lineno)
3027 {
3028 unsigned long old_lineno = view->lineno;
3029 unsigned long old_offset = view->offset;
3031 if (goto_view_line(view, view->offset, lineno)) {
3032 if (view_is_displayed(view)) {
3033 if (old_offset != view->offset) {
3034 redraw_view(view);
3035 } else {
3036 draw_view_line(view, old_lineno - view->offset);
3037 draw_view_line(view, view->lineno - view->offset);
3038 wnoutrefresh(view->win);
3039 }
3040 } else {
3041 view->ops->select(view, &view->line[view->lineno]);
3042 }
3043 }
3044 }
3046 static void
3047 find_next(struct view *view, enum request request)
3048 {
3049 unsigned long lineno = view->lineno;
3050 int direction;
3052 if (!*view->grep) {
3053 if (!*opt_search)
3054 report("No previous search");
3055 else
3056 search_view(view, request);
3057 return;
3058 }
3060 switch (request) {
3061 case REQ_SEARCH:
3062 case REQ_FIND_NEXT:
3063 direction = 1;
3064 break;
3066 case REQ_SEARCH_BACK:
3067 case REQ_FIND_PREV:
3068 direction = -1;
3069 break;
3071 default:
3072 return;
3073 }
3075 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3076 lineno += direction;
3078 /* Note, lineno is unsigned long so will wrap around in which case it
3079 * will become bigger than view->lines. */
3080 for (; lineno < view->lines; lineno += direction) {
3081 if (view->ops->grep(view, &view->line[lineno])) {
3082 select_view_line(view, lineno);
3083 report("Line %ld matches '%s'", lineno + 1, view->grep);
3084 return;
3085 }
3086 }
3088 report("No match found for '%s'", view->grep);
3089 }
3091 static void
3092 search_view(struct view *view, enum request request)
3093 {
3094 int regex_err;
3096 if (view->regex) {
3097 regfree(view->regex);
3098 *view->grep = 0;
3099 } else {
3100 view->regex = calloc(1, sizeof(*view->regex));
3101 if (!view->regex)
3102 return;
3103 }
3105 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3106 if (regex_err != 0) {
3107 char buf[SIZEOF_STR] = "unknown error";
3109 regerror(regex_err, view->regex, buf, sizeof(buf));
3110 report("Search failed: %s", buf);
3111 return;
3112 }
3114 string_copy(view->grep, opt_search);
3116 find_next(view, request);
3117 }
3119 /*
3120 * Incremental updating
3121 */
3123 static void
3124 reset_view(struct view *view)
3125 {
3126 int i;
3128 for (i = 0; i < view->lines; i++)
3129 free(view->line[i].data);
3130 free(view->line);
3132 view->p_offset = view->offset;
3133 view->p_yoffset = view->yoffset;
3134 view->p_lineno = view->lineno;
3136 view->line = NULL;
3137 view->offset = 0;
3138 view->yoffset = 0;
3139 view->lines = 0;
3140 view->lineno = 0;
3141 view->vid[0] = 0;
3142 view->update_secs = 0;
3143 }
3145 static const char *
3146 format_arg(const char *name)
3147 {
3148 static struct {
3149 const char *name;
3150 size_t namelen;
3151 const char *value;
3152 const char *value_if_empty;
3153 } vars[] = {
3154 #define FORMAT_VAR(name, value, value_if_empty) \
3155 { name, STRING_SIZE(name), value, value_if_empty }
3156 FORMAT_VAR("%(directory)", opt_path, ""),
3157 FORMAT_VAR("%(file)", opt_file, ""),
3158 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3159 FORMAT_VAR("%(head)", ref_head, ""),
3160 FORMAT_VAR("%(commit)", ref_commit, ""),
3161 FORMAT_VAR("%(blob)", ref_blob, ""),
3162 FORMAT_VAR("%(branch)", ref_branch, ""),
3163 };
3164 int i;
3166 for (i = 0; i < ARRAY_SIZE(vars); i++)
3167 if (!strncmp(name, vars[i].name, vars[i].namelen))
3168 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3170 report("Unknown replacement: `%s`", name);
3171 return NULL;
3172 }
3174 static bool
3175 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3176 {
3177 char buf[SIZEOF_STR];
3178 int argc;
3180 argv_free(dst_argv);
3182 for (argc = 0; src_argv[argc]; argc++) {
3183 const char *arg = src_argv[argc];
3184 size_t bufpos = 0;
3186 while (arg) {
3187 char *next = strstr(arg, "%(");
3188 int len = next - arg;
3189 const char *value;
3191 if (!next || !replace) {
3192 len = strlen(arg);
3193 value = "";
3195 } else {
3196 value = format_arg(next);
3198 if (!value) {
3199 return FALSE;
3200 }
3201 }
3203 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3204 return FALSE;
3206 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3207 }
3209 dst_argv[argc] = strdup(buf);
3210 if (!dst_argv[argc])
3211 break;
3212 }
3214 dst_argv[argc] = NULL;
3216 return src_argv[argc] == NULL;
3217 }
3219 static bool
3220 restore_view_position(struct view *view)
3221 {
3222 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3223 return FALSE;
3225 /* Changing the view position cancels the restoring. */
3226 /* FIXME: Changing back to the first line is not detected. */
3227 if (view->offset != 0 || view->lineno != 0) {
3228 view->p_restore = FALSE;
3229 return FALSE;
3230 }
3232 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3233 view_is_displayed(view))
3234 werase(view->win);
3236 view->yoffset = view->p_yoffset;
3237 view->p_restore = FALSE;
3239 return TRUE;
3240 }
3242 static void
3243 end_update(struct view *view, bool force)
3244 {
3245 if (!view->pipe)
3246 return;
3247 while (!view->ops->read(view, NULL))
3248 if (!force)
3249 return;
3250 if (force)
3251 io_kill(view->pipe);
3252 io_done(view->pipe);
3253 view->pipe = NULL;
3254 }
3256 static void
3257 setup_update(struct view *view, const char *vid)
3258 {
3259 reset_view(view);
3260 string_copy_rev(view->vid, vid);
3261 view->pipe = &view->io;
3262 view->start_time = time(NULL);
3263 }
3265 static bool
3266 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3267 {
3268 io_init(&view->io, dir, IO_RD);
3269 return format_argv(view->io.argv, argv, replace);
3270 }
3272 static bool
3273 prepare_update(struct view *view, const char *argv[], const char *dir)
3274 {
3275 if (view->pipe)
3276 end_update(view, TRUE);
3277 return prepare_io(view, dir, argv, FALSE);
3278 }
3280 static bool
3281 start_update(struct view *view, const char **argv, const char *dir)
3282 {
3283 if (view->pipe)
3284 io_done(view->pipe);
3285 return prepare_io(view, dir, argv, FALSE) &&
3286 io_start(&view->io);
3287 }
3289 static bool
3290 prepare_update_file(struct view *view, const char *name)
3291 {
3292 if (view->pipe)
3293 end_update(view, TRUE);
3294 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3295 }
3297 static bool
3298 begin_update(struct view *view, bool refresh)
3299 {
3300 if (view->pipe)
3301 end_update(view, TRUE);
3303 if (!refresh) {
3304 if (view->ops->prepare) {
3305 if (!view->ops->prepare(view))
3306 return FALSE;
3307 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3308 return FALSE;
3309 }
3311 /* Put the current ref_* value to the view title ref
3312 * member. This is needed by the blob view. Most other
3313 * views sets it automatically after loading because the
3314 * first line is a commit line. */
3315 string_copy_rev(view->ref, view->id);
3316 }
3318 if (!io_start(&view->io))
3319 return FALSE;
3321 setup_update(view, view->id);
3323 return TRUE;
3324 }
3326 static bool
3327 update_view(struct view *view)
3328 {
3329 char out_buffer[BUFSIZ * 2];
3330 char *line;
3331 /* Clear the view and redraw everything since the tree sorting
3332 * might have rearranged things. */
3333 bool redraw = view->lines == 0;
3334 bool can_read = TRUE;
3336 if (!view->pipe)
3337 return TRUE;
3339 if (!io_can_read(view->pipe)) {
3340 if (view->lines == 0 && view_is_displayed(view)) {
3341 time_t secs = time(NULL) - view->start_time;
3343 if (secs > 1 && secs > view->update_secs) {
3344 if (view->update_secs == 0)
3345 redraw_view(view);
3346 update_view_title(view);
3347 view->update_secs = secs;
3348 }
3349 }
3350 return TRUE;
3351 }
3353 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3354 if (opt_iconv_in != ICONV_NONE) {
3355 ICONV_CONST char *inbuf = line;
3356 size_t inlen = strlen(line) + 1;
3358 char *outbuf = out_buffer;
3359 size_t outlen = sizeof(out_buffer);
3361 size_t ret;
3363 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3364 if (ret != (size_t) -1)
3365 line = out_buffer;
3366 }
3368 if (!view->ops->read(view, line)) {
3369 report("Allocation failure");
3370 end_update(view, TRUE);
3371 return FALSE;
3372 }
3373 }
3375 {
3376 unsigned long lines = view->lines;
3377 int digits;
3379 for (digits = 0; lines; digits++)
3380 lines /= 10;
3382 /* Keep the displayed view in sync with line number scaling. */
3383 if (digits != view->digits) {
3384 view->digits = digits;
3385 if (opt_line_number || view->type == VIEW_BLAME)
3386 redraw = TRUE;
3387 }
3388 }
3390 if (io_error(view->pipe)) {
3391 report("Failed to read: %s", io_strerror(view->pipe));
3392 end_update(view, TRUE);
3394 } else if (io_eof(view->pipe)) {
3395 if (view_is_displayed(view))
3396 report("");
3397 end_update(view, FALSE);
3398 }
3400 if (restore_view_position(view))
3401 redraw = TRUE;
3403 if (!view_is_displayed(view))
3404 return TRUE;
3406 if (redraw)
3407 redraw_view_from(view, 0);
3408 else
3409 redraw_view_dirty(view);
3411 /* Update the title _after_ the redraw so that if the redraw picks up a
3412 * commit reference in view->ref it'll be available here. */
3413 update_view_title(view);
3414 return TRUE;
3415 }
3417 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3419 static struct line *
3420 add_line_data(struct view *view, void *data, enum line_type type)
3421 {
3422 struct line *line;
3424 if (!realloc_lines(&view->line, view->lines, 1))
3425 return NULL;
3427 line = &view->line[view->lines++];
3428 memset(line, 0, sizeof(*line));
3429 line->type = type;
3430 line->data = data;
3431 line->dirty = 1;
3433 return line;
3434 }
3436 static struct line *
3437 add_line_text(struct view *view, const char *text, enum line_type type)
3438 {
3439 char *data = text ? strdup(text) : NULL;
3441 return data ? add_line_data(view, data, type) : NULL;
3442 }
3444 static struct line *
3445 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3446 {
3447 char buf[SIZEOF_STR];
3448 va_list args;
3450 va_start(args, fmt);
3451 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3452 buf[0] = 0;
3453 va_end(args);
3455 return buf[0] ? add_line_text(view, buf, type) : NULL;
3456 }
3458 /*
3459 * View opening
3460 */
3462 enum open_flags {
3463 OPEN_DEFAULT = 0, /* Use default view switching. */
3464 OPEN_SPLIT = 1, /* Split current view. */
3465 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3466 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3467 OPEN_PREPARED = 32, /* Open already prepared command. */
3468 };
3470 static void
3471 open_view(struct view *prev, enum request request, enum open_flags flags)
3472 {
3473 bool split = !!(flags & OPEN_SPLIT);
3474 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3475 bool nomaximize = !!(flags & OPEN_REFRESH);
3476 struct view *view = VIEW(request);
3477 int nviews = displayed_views();
3478 struct view *base_view = display[0];
3480 if (view == prev && nviews == 1 && !reload) {
3481 report("Already in %s view", view->name);
3482 return;
3483 }
3485 if (view->git_dir && !opt_git_dir[0]) {
3486 report("The %s view is disabled in pager view", view->name);
3487 return;
3488 }
3490 if (split) {
3491 display[1] = view;
3492 current_view = 1;
3493 view->parent = prev;
3494 } else if (!nomaximize) {
3495 /* Maximize the current view. */
3496 memset(display, 0, sizeof(display));
3497 current_view = 0;
3498 display[current_view] = view;
3499 }
3501 /* No prev signals that this is the first loaded view. */
3502 if (prev && view != prev) {
3503 view->prev = prev;
3504 }
3506 /* Resize the view when switching between split- and full-screen,
3507 * or when switching between two different full-screen views. */
3508 if (nviews != displayed_views() ||
3509 (nviews == 1 && base_view != display[0]))
3510 resize_display();
3512 if (view->ops->open) {
3513 if (view->pipe)
3514 end_update(view, TRUE);
3515 if (!view->ops->open(view)) {
3516 report("Failed to load %s view", view->name);
3517 return;
3518 }
3519 restore_view_position(view);
3521 } else if ((reload || strcmp(view->vid, view->id)) &&
3522 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3523 report("Failed to load %s view", view->name);
3524 return;
3525 }
3527 if (split && prev->lineno - prev->offset >= prev->height) {
3528 /* Take the title line into account. */
3529 int lines = prev->lineno - prev->offset - prev->height + 1;
3531 /* Scroll the view that was split if the current line is
3532 * outside the new limited view. */
3533 do_scroll_view(prev, lines);
3534 }
3536 if (prev && view != prev && split && view_is_displayed(prev)) {
3537 /* "Blur" the previous view. */
3538 update_view_title(prev);
3539 }
3541 if (view->pipe && view->lines == 0) {
3542 /* Clear the old view and let the incremental updating refill
3543 * the screen. */
3544 werase(view->win);
3545 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3546 report("");
3547 } else if (view_is_displayed(view)) {
3548 redraw_view(view);
3549 report("");
3550 }
3551 }
3553 static void
3554 open_external_viewer(const char *argv[], const char *dir)
3555 {
3556 def_prog_mode(); /* save current tty modes */
3557 endwin(); /* restore original tty modes */
3558 io_run_fg(argv, dir);
3559 fprintf(stderr, "Press Enter to continue");
3560 getc(opt_tty);
3561 reset_prog_mode();
3562 redraw_display(TRUE);
3563 }
3565 static void
3566 open_mergetool(const char *file)
3567 {
3568 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3570 open_external_viewer(mergetool_argv, opt_cdup);
3571 }
3573 static void
3574 open_editor(const char *file)
3575 {
3576 const char *editor_argv[] = { "vi", file, NULL };
3577 const char *editor;
3579 editor = getenv("GIT_EDITOR");
3580 if (!editor && *opt_editor)
3581 editor = opt_editor;
3582 if (!editor)
3583 editor = getenv("VISUAL");
3584 if (!editor)
3585 editor = getenv("EDITOR");
3586 if (!editor)
3587 editor = "vi";
3589 editor_argv[0] = editor;
3590 open_external_viewer(editor_argv, opt_cdup);
3591 }
3593 static void
3594 open_run_request(enum request request)
3595 {
3596 struct run_request *req = get_run_request(request);
3597 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3599 if (!req) {
3600 report("Unknown run request");
3601 return;
3602 }
3604 if (format_argv(argv, req->argv, TRUE))
3605 open_external_viewer(argv, NULL);
3606 argv_free(argv);
3607 }
3609 /*
3610 * User request switch noodle
3611 */
3613 static int
3614 view_driver(struct view *view, enum request request)
3615 {
3616 int i;
3618 if (request == REQ_NONE)
3619 return TRUE;
3621 if (request > REQ_NONE) {
3622 open_run_request(request);
3623 view_request(view, REQ_REFRESH);
3624 return TRUE;
3625 }
3627 request = view_request(view, request);
3628 if (request == REQ_NONE)
3629 return TRUE;
3631 switch (request) {
3632 case REQ_MOVE_UP:
3633 case REQ_MOVE_DOWN:
3634 case REQ_MOVE_PAGE_UP:
3635 case REQ_MOVE_PAGE_DOWN:
3636 case REQ_MOVE_FIRST_LINE:
3637 case REQ_MOVE_LAST_LINE:
3638 move_view(view, request);
3639 break;
3641 case REQ_SCROLL_LEFT:
3642 case REQ_SCROLL_RIGHT:
3643 case REQ_SCROLL_LINE_DOWN:
3644 case REQ_SCROLL_LINE_UP:
3645 case REQ_SCROLL_PAGE_DOWN:
3646 case REQ_SCROLL_PAGE_UP:
3647 scroll_view(view, request);
3648 break;
3650 case REQ_VIEW_BLAME:
3651 if (!opt_file[0]) {
3652 report("No file chosen, press %s to open tree view",
3653 get_key(view->keymap, REQ_VIEW_TREE));
3654 break;
3655 }
3656 open_view(view, request, OPEN_DEFAULT);
3657 break;
3659 case REQ_VIEW_BLOB:
3660 if (!ref_blob[0]) {
3661 report("No file chosen, press %s to open tree view",
3662 get_key(view->keymap, REQ_VIEW_TREE));
3663 break;
3664 }
3665 open_view(view, request, OPEN_DEFAULT);
3666 break;
3668 case REQ_VIEW_PAGER:
3669 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3670 report("No pager content, press %s to run command from prompt",
3671 get_key(view->keymap, REQ_PROMPT));
3672 break;
3673 }
3674 open_view(view, request, OPEN_DEFAULT);
3675 break;
3677 case REQ_VIEW_STAGE:
3678 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3679 report("No stage content, press %s to open the status view and choose file",
3680 get_key(view->keymap, REQ_VIEW_STATUS));
3681 break;
3682 }
3683 open_view(view, request, OPEN_DEFAULT);
3684 break;
3686 case REQ_VIEW_STATUS:
3687 if (opt_is_inside_work_tree == FALSE) {
3688 report("The status view requires a working tree");
3689 break;
3690 }
3691 open_view(view, request, OPEN_DEFAULT);
3692 break;
3694 case REQ_VIEW_MAIN:
3695 case REQ_VIEW_DIFF:
3696 case REQ_VIEW_LOG:
3697 case REQ_VIEW_TREE:
3698 case REQ_VIEW_HELP:
3699 case REQ_VIEW_BRANCH:
3700 open_view(view, request, OPEN_DEFAULT);
3701 break;
3703 case REQ_NEXT:
3704 case REQ_PREVIOUS:
3705 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3707 if (view->parent) {
3708 int line;
3710 view = view->parent;
3711 line = view->lineno;
3712 move_view(view, request);
3713 if (view_is_displayed(view))
3714 update_view_title(view);
3715 if (line != view->lineno)
3716 view_request(view, REQ_ENTER);
3717 } else {
3718 move_view(view, request);
3719 }
3720 break;
3722 case REQ_VIEW_NEXT:
3723 {
3724 int nviews = displayed_views();
3725 int next_view = (current_view + 1) % nviews;
3727 if (next_view == current_view) {
3728 report("Only one view is displayed");
3729 break;
3730 }
3732 current_view = next_view;
3733 /* Blur out the title of the previous view. */
3734 update_view_title(view);
3735 report("");
3736 break;
3737 }
3738 case REQ_REFRESH:
3739 report("Refreshing is not yet supported for the %s view", view->name);
3740 break;
3742 case REQ_MAXIMIZE:
3743 if (displayed_views() == 2)
3744 maximize_view(view);
3745 break;
3747 case REQ_OPTIONS:
3748 open_option_menu();
3749 break;
3751 case REQ_TOGGLE_LINENO:
3752 toggle_view_option(&opt_line_number, "line numbers");
3753 break;
3755 case REQ_TOGGLE_DATE:
3756 toggle_date();
3757 break;
3759 case REQ_TOGGLE_AUTHOR:
3760 toggle_author();
3761 break;
3763 case REQ_TOGGLE_REV_GRAPH:
3764 toggle_view_option(&opt_rev_graph, "revision graph display");
3765 break;
3767 case REQ_TOGGLE_REFS:
3768 toggle_view_option(&opt_show_refs, "reference display");
3769 break;
3771 case REQ_TOGGLE_SORT_FIELD:
3772 case REQ_TOGGLE_SORT_ORDER:
3773 report("Sorting is not yet supported for the %s view", view->name);
3774 break;
3776 case REQ_SEARCH:
3777 case REQ_SEARCH_BACK:
3778 search_view(view, request);
3779 break;
3781 case REQ_FIND_NEXT:
3782 case REQ_FIND_PREV:
3783 find_next(view, request);
3784 break;
3786 case REQ_STOP_LOADING:
3787 foreach_view(view, i) {
3788 if (view->pipe)
3789 report("Stopped loading the %s view", view->name),
3790 end_update(view, TRUE);
3791 }
3792 break;
3794 case REQ_SHOW_VERSION:
3795 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3796 return TRUE;
3798 case REQ_SCREEN_REDRAW:
3799 redraw_display(TRUE);
3800 break;
3802 case REQ_EDIT:
3803 report("Nothing to edit");
3804 break;
3806 case REQ_ENTER:
3807 report("Nothing to enter");
3808 break;
3810 case REQ_VIEW_CLOSE:
3811 /* XXX: Mark closed views by letting view->prev point to the
3812 * view itself. Parents to closed view should never be
3813 * followed. */
3814 if (view->prev && view->prev != view) {
3815 maximize_view(view->prev);
3816 view->prev = view;
3817 break;
3818 }
3819 /* Fall-through */
3820 case REQ_QUIT:
3821 return FALSE;
3823 default:
3824 report("Unknown key, press %s for help",
3825 get_key(view->keymap, REQ_VIEW_HELP));
3826 return TRUE;
3827 }
3829 return TRUE;
3830 }
3833 /*
3834 * View backend utilities
3835 */
3837 enum sort_field {
3838 ORDERBY_NAME,
3839 ORDERBY_DATE,
3840 ORDERBY_AUTHOR,
3841 };
3843 struct sort_state {
3844 const enum sort_field *fields;
3845 size_t size, current;
3846 bool reverse;
3847 };
3849 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3850 #define get_sort_field(state) ((state).fields[(state).current])
3851 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3853 static void
3854 sort_view(struct view *view, enum request request, struct sort_state *state,
3855 int (*compare)(const void *, const void *))
3856 {
3857 switch (request) {
3858 case REQ_TOGGLE_SORT_FIELD:
3859 state->current = (state->current + 1) % state->size;
3860 break;
3862 case REQ_TOGGLE_SORT_ORDER:
3863 state->reverse = !state->reverse;
3864 break;
3865 default:
3866 die("Not a sort request");
3867 }
3869 qsort(view->line, view->lines, sizeof(*view->line), compare);
3870 redraw_view(view);
3871 }
3873 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3875 /* Small author cache to reduce memory consumption. It uses binary
3876 * search to lookup or find place to position new entries. No entries
3877 * are ever freed. */
3878 static const char *
3879 get_author(const char *name)
3880 {
3881 static const char **authors;
3882 static size_t authors_size;
3883 int from = 0, to = authors_size - 1;
3885 while (from <= to) {
3886 size_t pos = (to + from) / 2;
3887 int cmp = strcmp(name, authors[pos]);
3889 if (!cmp)
3890 return authors[pos];
3892 if (cmp < 0)
3893 to = pos - 1;
3894 else
3895 from = pos + 1;
3896 }
3898 if (!realloc_authors(&authors, authors_size, 1))
3899 return NULL;
3900 name = strdup(name);
3901 if (!name)
3902 return NULL;
3904 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3905 authors[from] = name;
3906 authors_size++;
3908 return name;
3909 }
3911 static void
3912 parse_timesec(struct time *time, const char *sec)
3913 {
3914 time->sec = (time_t) atol(sec);
3915 }
3917 static void
3918 parse_timezone(struct time *time, const char *zone)
3919 {
3920 long tz;
3922 tz = ('0' - zone[1]) * 60 * 60 * 10;
3923 tz += ('0' - zone[2]) * 60 * 60;
3924 tz += ('0' - zone[3]) * 60 * 10;
3925 tz += ('0' - zone[4]) * 60;
3927 if (zone[0] == '-')
3928 tz = -tz;
3930 time->tz = tz;
3931 time->sec -= tz;
3932 }
3934 /* Parse author lines where the name may be empty:
3935 * author <email@address.tld> 1138474660 +0100
3936 */
3937 static void
3938 parse_author_line(char *ident, const char **author, struct time *time)
3939 {
3940 char *nameend = strchr(ident, '<');
3941 char *emailend = strchr(ident, '>');
3943 if (nameend && emailend)
3944 *nameend = *emailend = 0;
3945 ident = chomp_string(ident);
3946 if (!*ident) {
3947 if (nameend)
3948 ident = chomp_string(nameend + 1);
3949 if (!*ident)
3950 ident = "Unknown";
3951 }
3953 *author = get_author(ident);
3955 /* Parse epoch and timezone */
3956 if (emailend && emailend[1] == ' ') {
3957 char *secs = emailend + 2;
3958 char *zone = strchr(secs, ' ');
3960 parse_timesec(time, secs);
3962 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3963 parse_timezone(time, zone + 1);
3964 }
3965 }
3967 static bool
3968 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3969 {
3970 char rev[SIZEOF_REV];
3971 const char *revlist_argv[] = {
3972 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3973 };
3974 struct menu_item *items;
3975 char text[SIZEOF_STR];
3976 bool ok = TRUE;
3977 int i;
3979 items = calloc(*parents + 1, sizeof(*items));
3980 if (!items)
3981 return FALSE;
3983 for (i = 0; i < *parents; i++) {
3984 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3985 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3986 !(items[i].text = strdup(text))) {
3987 ok = FALSE;
3988 break;
3989 }
3990 }
3992 if (ok) {
3993 *parents = 0;
3994 ok = prompt_menu("Select parent", items, parents);
3995 }
3996 for (i = 0; items[i].text; i++)
3997 free((char *) items[i].text);
3998 free(items);
3999 return ok;
4000 }
4002 static bool
4003 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
4004 {
4005 char buf[SIZEOF_STR * 4];
4006 const char *revlist_argv[] = {
4007 "git", "log", "--no-color", "-1",
4008 "--pretty=format:%P", id, "--", path, NULL
4009 };
4010 int parents;
4012 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
4013 (parents = strlen(buf) / 40) < 0) {
4014 report("Failed to get parent information");
4015 return FALSE;
4017 } else if (parents == 0) {
4018 if (path)
4019 report("Path '%s' does not exist in the parent", path);
4020 else
4021 report("The selected commit has no parents");
4022 return FALSE;
4023 }
4025 if (parents == 1)
4026 parents = 0;
4027 else if (!open_commit_parent_menu(buf, &parents))
4028 return FALSE;
4030 string_copy_rev(rev, &buf[41 * parents]);
4031 return TRUE;
4032 }
4034 /*
4035 * Pager backend
4036 */
4038 static bool
4039 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4040 {
4041 char text[SIZEOF_STR];
4043 if (opt_line_number && draw_lineno(view, lineno))
4044 return TRUE;
4046 string_expand(text, sizeof(text), line->data, opt_tab_size);
4047 draw_text(view, line->type, text, TRUE);
4048 return TRUE;
4049 }
4051 static bool
4052 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4053 {
4054 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4055 char ref[SIZEOF_STR];
4057 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4058 return TRUE;
4060 /* This is the only fatal call, since it can "corrupt" the buffer. */
4061 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4062 return FALSE;
4064 return TRUE;
4065 }
4067 static void
4068 add_pager_refs(struct view *view, struct line *line)
4069 {
4070 char buf[SIZEOF_STR];
4071 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4072 struct ref_list *list;
4073 size_t bufpos = 0, i;
4074 const char *sep = "Refs: ";
4075 bool is_tag = FALSE;
4077 assert(line->type == LINE_COMMIT);
4079 list = get_ref_list(commit_id);
4080 if (!list) {
4081 if (view->type == VIEW_DIFF)
4082 goto try_add_describe_ref;
4083 return;
4084 }
4086 for (i = 0; i < list->size; i++) {
4087 struct ref *ref = list->refs[i];
4088 const char *fmt = ref->tag ? "%s[%s]" :
4089 ref->remote ? "%s<%s>" : "%s%s";
4091 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4092 return;
4093 sep = ", ";
4094 if (ref->tag)
4095 is_tag = TRUE;
4096 }
4098 if (!is_tag && view->type == VIEW_DIFF) {
4099 try_add_describe_ref:
4100 /* Add <tag>-g<commit_id> "fake" reference. */
4101 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4102 return;
4103 }
4105 if (bufpos == 0)
4106 return;
4108 add_line_text(view, buf, LINE_PP_REFS);
4109 }
4111 static bool
4112 pager_read(struct view *view, char *data)
4113 {
4114 struct line *line;
4116 if (!data)
4117 return TRUE;
4119 line = add_line_text(view, data, get_line_type(data));
4120 if (!line)
4121 return FALSE;
4123 if (line->type == LINE_COMMIT &&
4124 (view->type == VIEW_DIFF ||
4125 view->type == VIEW_LOG))
4126 add_pager_refs(view, line);
4128 return TRUE;
4129 }
4131 static enum request
4132 pager_request(struct view *view, enum request request, struct line *line)
4133 {
4134 int split = 0;
4136 if (request != REQ_ENTER)
4137 return request;
4139 if (line->type == LINE_COMMIT &&
4140 (view->type == VIEW_LOG ||
4141 view->type == VIEW_PAGER)) {
4142 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4143 split = 1;
4144 }
4146 /* Always scroll the view even if it was split. That way
4147 * you can use Enter to scroll through the log view and
4148 * split open each commit diff. */
4149 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4151 /* FIXME: A minor workaround. Scrolling the view will call report("")
4152 * but if we are scrolling a non-current view this won't properly
4153 * update the view title. */
4154 if (split)
4155 update_view_title(view);
4157 return REQ_NONE;
4158 }
4160 static bool
4161 pager_grep(struct view *view, struct line *line)
4162 {
4163 const char *text[] = { line->data, NULL };
4165 return grep_text(view, text);
4166 }
4168 static void
4169 pager_select(struct view *view, struct line *line)
4170 {
4171 if (line->type == LINE_COMMIT) {
4172 char *text = (char *)line->data + STRING_SIZE("commit ");
4174 if (view->type != VIEW_PAGER)
4175 string_copy_rev(view->ref, text);
4176 string_copy_rev(ref_commit, text);
4177 }
4178 }
4180 static struct view_ops pager_ops = {
4181 "line",
4182 NULL,
4183 NULL,
4184 pager_read,
4185 pager_draw,
4186 pager_request,
4187 pager_grep,
4188 pager_select,
4189 };
4191 static const char *log_argv[SIZEOF_ARG] = {
4192 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4193 };
4195 static enum request
4196 log_request(struct view *view, enum request request, struct line *line)
4197 {
4198 switch (request) {
4199 case REQ_REFRESH:
4200 load_refs();
4201 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4202 return REQ_NONE;
4203 default:
4204 return pager_request(view, request, line);
4205 }
4206 }
4208 static struct view_ops log_ops = {
4209 "line",
4210 log_argv,
4211 NULL,
4212 pager_read,
4213 pager_draw,
4214 log_request,
4215 pager_grep,
4216 pager_select,
4217 };
4219 static const char *diff_argv[SIZEOF_ARG] = {
4220 "git", "show", "--pretty=fuller", "--no-color", "--root",
4221 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4222 };
4224 static struct view_ops diff_ops = {
4225 "line",
4226 diff_argv,
4227 NULL,
4228 pager_read,
4229 pager_draw,
4230 pager_request,
4231 pager_grep,
4232 pager_select,
4233 };
4235 /*
4236 * Help backend
4237 */
4239 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4241 static bool
4242 help_open_keymap_title(struct view *view, enum keymap keymap)
4243 {
4244 struct line *line;
4246 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4247 help_keymap_hidden[keymap] ? '+' : '-',
4248 enum_name(keymap_table[keymap]));
4249 if (line)
4250 line->other = keymap;
4252 return help_keymap_hidden[keymap];
4253 }
4255 static void
4256 help_open_keymap(struct view *view, enum keymap keymap)
4257 {
4258 const char *group = NULL;
4259 char buf[SIZEOF_STR];
4260 size_t bufpos;
4261 bool add_title = TRUE;
4262 int i;
4264 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4265 const char *key = NULL;
4267 if (req_info[i].request == REQ_NONE)
4268 continue;
4270 if (!req_info[i].request) {
4271 group = req_info[i].help;
4272 continue;
4273 }
4275 key = get_keys(keymap, req_info[i].request, TRUE);
4276 if (!key || !*key)
4277 continue;
4279 if (add_title && help_open_keymap_title(view, keymap))
4280 return;
4281 add_title = FALSE;
4283 if (group) {
4284 add_line_text(view, group, LINE_HELP_GROUP);
4285 group = NULL;
4286 }
4288 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4289 enum_name(req_info[i]), req_info[i].help);
4290 }
4292 group = "External commands:";
4294 for (i = 0; i < run_requests; i++) {
4295 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4296 const char *key;
4297 int argc;
4299 if (!req || req->keymap != keymap)
4300 continue;
4302 key = get_key_name(req->key);
4303 if (!*key)
4304 key = "(no key defined)";
4306 if (add_title && help_open_keymap_title(view, keymap))
4307 return;
4308 if (group) {
4309 add_line_text(view, group, LINE_HELP_GROUP);
4310 group = NULL;
4311 }
4313 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4314 if (!string_format_from(buf, &bufpos, "%s%s",
4315 argc ? " " : "", req->argv[argc]))
4316 return;
4318 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4319 }
4320 }
4322 static bool
4323 help_open(struct view *view)
4324 {
4325 enum keymap keymap;
4327 reset_view(view);
4328 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4329 add_line_text(view, "", LINE_DEFAULT);
4331 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4332 help_open_keymap(view, keymap);
4334 return TRUE;
4335 }
4337 static enum request
4338 help_request(struct view *view, enum request request, struct line *line)
4339 {
4340 switch (request) {
4341 case REQ_ENTER:
4342 if (line->type == LINE_HELP_KEYMAP) {
4343 help_keymap_hidden[line->other] =
4344 !help_keymap_hidden[line->other];
4345 view->p_restore = TRUE;
4346 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4347 }
4349 return REQ_NONE;
4350 default:
4351 return pager_request(view, request, line);
4352 }
4353 }
4355 static struct view_ops help_ops = {
4356 "line",
4357 NULL,
4358 help_open,
4359 NULL,
4360 pager_draw,
4361 help_request,
4362 pager_grep,
4363 pager_select,
4364 };
4367 /*
4368 * Tree backend
4369 */
4371 struct tree_stack_entry {
4372 struct tree_stack_entry *prev; /* Entry below this in the stack */
4373 unsigned long lineno; /* Line number to restore */
4374 char *name; /* Position of name in opt_path */
4375 };
4377 /* The top of the path stack. */
4378 static struct tree_stack_entry *tree_stack = NULL;
4379 unsigned long tree_lineno = 0;
4381 static void
4382 pop_tree_stack_entry(void)
4383 {
4384 struct tree_stack_entry *entry = tree_stack;
4386 tree_lineno = entry->lineno;
4387 entry->name[0] = 0;
4388 tree_stack = entry->prev;
4389 free(entry);
4390 }
4392 static void
4393 push_tree_stack_entry(const char *name, unsigned long lineno)
4394 {
4395 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4396 size_t pathlen = strlen(opt_path);
4398 if (!entry)
4399 return;
4401 entry->prev = tree_stack;
4402 entry->name = opt_path + pathlen;
4403 tree_stack = entry;
4405 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4406 pop_tree_stack_entry();
4407 return;
4408 }
4410 /* Move the current line to the first tree entry. */
4411 tree_lineno = 1;
4412 entry->lineno = lineno;
4413 }
4415 /* Parse output from git-ls-tree(1):
4416 *
4417 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4418 */
4420 #define SIZEOF_TREE_ATTR \
4421 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4423 #define SIZEOF_TREE_MODE \
4424 STRING_SIZE("100644 ")
4426 #define TREE_ID_OFFSET \
4427 STRING_SIZE("100644 blob ")
4429 struct tree_entry {
4430 char id[SIZEOF_REV];
4431 mode_t mode;
4432 struct time time; /* Date from the author ident. */
4433 const char *author; /* Author of the commit. */
4434 char name[1];
4435 };
4437 static const char *
4438 tree_path(const struct line *line)
4439 {
4440 return ((struct tree_entry *) line->data)->name;
4441 }
4443 static int
4444 tree_compare_entry(const struct line *line1, const struct line *line2)
4445 {
4446 if (line1->type != line2->type)
4447 return line1->type == LINE_TREE_DIR ? -1 : 1;
4448 return strcmp(tree_path(line1), tree_path(line2));
4449 }
4451 static const enum sort_field tree_sort_fields[] = {
4452 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4453 };
4454 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4456 static int
4457 tree_compare(const void *l1, const void *l2)
4458 {
4459 const struct line *line1 = (const struct line *) l1;
4460 const struct line *line2 = (const struct line *) l2;
4461 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4462 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4464 if (line1->type == LINE_TREE_HEAD)
4465 return -1;
4466 if (line2->type == LINE_TREE_HEAD)
4467 return 1;
4469 switch (get_sort_field(tree_sort_state)) {
4470 case ORDERBY_DATE:
4471 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4473 case ORDERBY_AUTHOR:
4474 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4476 case ORDERBY_NAME:
4477 default:
4478 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4479 }
4480 }
4483 static struct line *
4484 tree_entry(struct view *view, enum line_type type, const char *path,
4485 const char *mode, const char *id)
4486 {
4487 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4488 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4490 if (!entry || !line) {
4491 free(entry);
4492 return NULL;
4493 }
4495 strncpy(entry->name, path, strlen(path));
4496 if (mode)
4497 entry->mode = strtoul(mode, NULL, 8);
4498 if (id)
4499 string_copy_rev(entry->id, id);
4501 return line;
4502 }
4504 static bool
4505 tree_read_date(struct view *view, char *text, bool *read_date)
4506 {
4507 static const char *author_name;
4508 static struct time author_time;
4510 if (!text && *read_date) {
4511 *read_date = FALSE;
4512 return TRUE;
4514 } else if (!text) {
4515 char *path = *opt_path ? opt_path : ".";
4516 /* Find next entry to process */
4517 const char *log_file[] = {
4518 "git", "log", "--no-color", "--pretty=raw",
4519 "--cc", "--raw", view->id, "--", path, NULL
4520 };
4522 if (!view->lines) {
4523 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4524 report("Tree is empty");
4525 return TRUE;
4526 }
4528 if (!start_update(view, log_file, opt_cdup)) {
4529 report("Failed to load tree data");
4530 return TRUE;
4531 }
4533 *read_date = TRUE;
4534 return FALSE;
4536 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4537 parse_author_line(text + STRING_SIZE("author "),
4538 &author_name, &author_time);
4540 } else if (*text == ':') {
4541 char *pos;
4542 size_t annotated = 1;
4543 size_t i;
4545 pos = strchr(text, '\t');
4546 if (!pos)
4547 return TRUE;
4548 text = pos + 1;
4549 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4550 text += strlen(opt_path);
4551 pos = strchr(text, '/');
4552 if (pos)
4553 *pos = 0;
4555 for (i = 1; i < view->lines; i++) {
4556 struct line *line = &view->line[i];
4557 struct tree_entry *entry = line->data;
4559 annotated += !!entry->author;
4560 if (entry->author || strcmp(entry->name, text))
4561 continue;
4563 entry->author = author_name;
4564 entry->time = author_time;
4565 line->dirty = 1;
4566 break;
4567 }
4569 if (annotated == view->lines)
4570 io_kill(view->pipe);
4571 }
4572 return TRUE;
4573 }
4575 static bool
4576 tree_read(struct view *view, char *text)
4577 {
4578 static bool read_date = FALSE;
4579 struct tree_entry *data;
4580 struct line *entry, *line;
4581 enum line_type type;
4582 size_t textlen = text ? strlen(text) : 0;
4583 char *path = text + SIZEOF_TREE_ATTR;
4585 if (read_date || !text)
4586 return tree_read_date(view, text, &read_date);
4588 if (textlen <= SIZEOF_TREE_ATTR)
4589 return FALSE;
4590 if (view->lines == 0 &&
4591 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4592 return FALSE;
4594 /* Strip the path part ... */
4595 if (*opt_path) {
4596 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4597 size_t striplen = strlen(opt_path);
4599 if (pathlen > striplen)
4600 memmove(path, path + striplen,
4601 pathlen - striplen + 1);
4603 /* Insert "link" to parent directory. */
4604 if (view->lines == 1 &&
4605 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4606 return FALSE;
4607 }
4609 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4610 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4611 if (!entry)
4612 return FALSE;
4613 data = entry->data;
4615 /* Skip "Directory ..." and ".." line. */
4616 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4617 if (tree_compare_entry(line, entry) <= 0)
4618 continue;
4620 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4622 line->data = data;
4623 line->type = type;
4624 for (; line <= entry; line++)
4625 line->dirty = line->cleareol = 1;
4626 return TRUE;
4627 }
4629 if (tree_lineno > view->lineno) {
4630 view->lineno = tree_lineno;
4631 tree_lineno = 0;
4632 }
4634 return TRUE;
4635 }
4637 static bool
4638 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4639 {
4640 struct tree_entry *entry = line->data;
4642 if (line->type == LINE_TREE_HEAD) {
4643 if (draw_text(view, line->type, "Directory path /", TRUE))
4644 return TRUE;
4645 } else {
4646 if (draw_mode(view, entry->mode))
4647 return TRUE;
4649 if (opt_author && draw_author(view, entry->author))
4650 return TRUE;
4652 if (opt_date && draw_date(view, &entry->time))
4653 return TRUE;
4654 }
4655 if (draw_text(view, line->type, entry->name, TRUE))
4656 return TRUE;
4657 return TRUE;
4658 }
4660 static void
4661 open_blob_editor(const char *id)
4662 {
4663 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4664 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4665 int fd = mkstemp(file);
4667 if (fd == -1)
4668 report("Failed to create temporary file");
4669 else if (!io_run_append(blob_argv, fd))
4670 report("Failed to save blob data to file");
4671 else
4672 open_editor(file);
4673 if (fd != -1)
4674 unlink(file);
4675 }
4677 static enum request
4678 tree_request(struct view *view, enum request request, struct line *line)
4679 {
4680 enum open_flags flags;
4681 struct tree_entry *entry = line->data;
4683 switch (request) {
4684 case REQ_VIEW_BLAME:
4685 if (line->type != LINE_TREE_FILE) {
4686 report("Blame only supported for files");
4687 return REQ_NONE;
4688 }
4690 string_copy(opt_ref, view->vid);
4691 return request;
4693 case REQ_EDIT:
4694 if (line->type != LINE_TREE_FILE) {
4695 report("Edit only supported for files");
4696 } else if (!is_head_commit(view->vid)) {
4697 open_blob_editor(entry->id);
4698 } else {
4699 open_editor(opt_file);
4700 }
4701 return REQ_NONE;
4703 case REQ_TOGGLE_SORT_FIELD:
4704 case REQ_TOGGLE_SORT_ORDER:
4705 sort_view(view, request, &tree_sort_state, tree_compare);
4706 return REQ_NONE;
4708 case REQ_PARENT:
4709 if (!*opt_path) {
4710 /* quit view if at top of tree */
4711 return REQ_VIEW_CLOSE;
4712 }
4713 /* fake 'cd ..' */
4714 line = &view->line[1];
4715 break;
4717 case REQ_ENTER:
4718 break;
4720 default:
4721 return request;
4722 }
4724 /* Cleanup the stack if the tree view is at a different tree. */
4725 while (!*opt_path && tree_stack)
4726 pop_tree_stack_entry();
4728 switch (line->type) {
4729 case LINE_TREE_DIR:
4730 /* Depending on whether it is a subdirectory or parent link
4731 * mangle the path buffer. */
4732 if (line == &view->line[1] && *opt_path) {
4733 pop_tree_stack_entry();
4735 } else {
4736 const char *basename = tree_path(line);
4738 push_tree_stack_entry(basename, view->lineno);
4739 }
4741 /* Trees and subtrees share the same ID, so they are not not
4742 * unique like blobs. */
4743 flags = OPEN_RELOAD;
4744 request = REQ_VIEW_TREE;
4745 break;
4747 case LINE_TREE_FILE:
4748 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4749 request = REQ_VIEW_BLOB;
4750 break;
4752 default:
4753 return REQ_NONE;
4754 }
4756 open_view(view, request, flags);
4757 if (request == REQ_VIEW_TREE)
4758 view->lineno = tree_lineno;
4760 return REQ_NONE;
4761 }
4763 static bool
4764 tree_grep(struct view *view, struct line *line)
4765 {
4766 struct tree_entry *entry = line->data;
4767 const char *text[] = {
4768 entry->name,
4769 opt_author ? entry->author : "",
4770 mkdate(&entry->time, opt_date),
4771 NULL
4772 };
4774 return grep_text(view, text);
4775 }
4777 static void
4778 tree_select(struct view *view, struct line *line)
4779 {
4780 struct tree_entry *entry = line->data;
4782 if (line->type == LINE_TREE_FILE) {
4783 string_copy_rev(ref_blob, entry->id);
4784 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4786 } else if (line->type != LINE_TREE_DIR) {
4787 return;
4788 }
4790 string_copy_rev(view->ref, entry->id);
4791 }
4793 static bool
4794 tree_prepare(struct view *view)
4795 {
4796 if (view->lines == 0 && opt_prefix[0]) {
4797 char *pos = opt_prefix;
4799 while (pos && *pos) {
4800 char *end = strchr(pos, '/');
4802 if (end)
4803 *end = 0;
4804 push_tree_stack_entry(pos, 0);
4805 pos = end;
4806 if (end) {
4807 *end = '/';
4808 pos++;
4809 }
4810 }
4812 } else if (strcmp(view->vid, view->id)) {
4813 opt_path[0] = 0;
4814 }
4816 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4817 }
4819 static const char *tree_argv[SIZEOF_ARG] = {
4820 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4821 };
4823 static struct view_ops tree_ops = {
4824 "file",
4825 tree_argv,
4826 NULL,
4827 tree_read,
4828 tree_draw,
4829 tree_request,
4830 tree_grep,
4831 tree_select,
4832 tree_prepare,
4833 };
4835 static bool
4836 blob_read(struct view *view, char *line)
4837 {
4838 if (!line)
4839 return TRUE;
4840 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4841 }
4843 static enum request
4844 blob_request(struct view *view, enum request request, struct line *line)
4845 {
4846 switch (request) {
4847 case REQ_EDIT:
4848 open_blob_editor(view->vid);
4849 return REQ_NONE;
4850 default:
4851 return pager_request(view, request, line);
4852 }
4853 }
4855 static const char *blob_argv[SIZEOF_ARG] = {
4856 "git", "cat-file", "blob", "%(blob)", NULL
4857 };
4859 static struct view_ops blob_ops = {
4860 "line",
4861 blob_argv,
4862 NULL,
4863 blob_read,
4864 pager_draw,
4865 blob_request,
4866 pager_grep,
4867 pager_select,
4868 };
4870 /*
4871 * Blame backend
4872 *
4873 * Loading the blame view is a two phase job:
4874 *
4875 * 1. File content is read either using opt_file from the
4876 * filesystem or using git-cat-file.
4877 * 2. Then blame information is incrementally added by
4878 * reading output from git-blame.
4879 */
4881 struct blame_commit {
4882 char id[SIZEOF_REV]; /* SHA1 ID. */
4883 char title[128]; /* First line of the commit message. */
4884 const char *author; /* Author of the commit. */
4885 struct time time; /* Date from the author ident. */
4886 char filename[128]; /* Name of file. */
4887 bool has_previous; /* Was a "previous" line detected. */
4888 };
4890 struct blame {
4891 struct blame_commit *commit;
4892 unsigned long lineno;
4893 char text[1];
4894 };
4896 static bool
4897 blame_open(struct view *view)
4898 {
4899 char path[SIZEOF_STR];
4901 if (!view->prev && *opt_prefix) {
4902 string_copy(path, opt_file);
4903 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4904 return FALSE;
4905 }
4907 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4908 const char *blame_cat_file_argv[] = {
4909 "git", "cat-file", "blob", path, NULL
4910 };
4912 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4913 !start_update(view, blame_cat_file_argv, opt_cdup))
4914 return FALSE;
4915 }
4917 setup_update(view, opt_file);
4918 string_format(view->ref, "%s ...", opt_file);
4920 return TRUE;
4921 }
4923 static struct blame_commit *
4924 get_blame_commit(struct view *view, const char *id)
4925 {
4926 size_t i;
4928 for (i = 0; i < view->lines; i++) {
4929 struct blame *blame = view->line[i].data;
4931 if (!blame->commit)
4932 continue;
4934 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4935 return blame->commit;
4936 }
4938 {
4939 struct blame_commit *commit = calloc(1, sizeof(*commit));
4941 if (commit)
4942 string_ncopy(commit->id, id, SIZEOF_REV);
4943 return commit;
4944 }
4945 }
4947 static bool
4948 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4949 {
4950 const char *pos = *posref;
4952 *posref = NULL;
4953 pos = strchr(pos + 1, ' ');
4954 if (!pos || !isdigit(pos[1]))
4955 return FALSE;
4956 *number = atoi(pos + 1);
4957 if (*number < min || *number > max)
4958 return FALSE;
4960 *posref = pos;
4961 return TRUE;
4962 }
4964 static struct blame_commit *
4965 parse_blame_commit(struct view *view, const char *text, int *blamed)
4966 {
4967 struct blame_commit *commit;
4968 struct blame *blame;
4969 const char *pos = text + SIZEOF_REV - 2;
4970 size_t orig_lineno = 0;
4971 size_t lineno;
4972 size_t group;
4974 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4975 return NULL;
4977 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4978 !parse_number(&pos, &lineno, 1, view->lines) ||
4979 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4980 return NULL;
4982 commit = get_blame_commit(view, text);
4983 if (!commit)
4984 return NULL;
4986 *blamed += group;
4987 while (group--) {
4988 struct line *line = &view->line[lineno + group - 1];
4990 blame = line->data;
4991 blame->commit = commit;
4992 blame->lineno = orig_lineno + group - 1;
4993 line->dirty = 1;
4994 }
4996 return commit;
4997 }
4999 static bool
5000 blame_read_file(struct view *view, const char *line, bool *read_file)
5001 {
5002 if (!line) {
5003 const char *blame_argv[] = {
5004 "git", "blame", "--incremental",
5005 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5006 };
5008 if (view->lines == 0 && !view->prev)
5009 die("No blame exist for %s", view->vid);
5011 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5012 report("Failed to load blame data");
5013 return TRUE;
5014 }
5016 *read_file = FALSE;
5017 return FALSE;
5019 } else {
5020 size_t linelen = strlen(line);
5021 struct blame *blame = malloc(sizeof(*blame) + linelen);
5023 if (!blame)
5024 return FALSE;
5026 blame->commit = NULL;
5027 strncpy(blame->text, line, linelen);
5028 blame->text[linelen] = 0;
5029 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5030 }
5031 }
5033 static bool
5034 match_blame_header(const char *name, char **line)
5035 {
5036 size_t namelen = strlen(name);
5037 bool matched = !strncmp(name, *line, namelen);
5039 if (matched)
5040 *line += namelen;
5042 return matched;
5043 }
5045 static bool
5046 blame_read(struct view *view, char *line)
5047 {
5048 static struct blame_commit *commit = NULL;
5049 static int blamed = 0;
5050 static bool read_file = TRUE;
5052 if (read_file)
5053 return blame_read_file(view, line, &read_file);
5055 if (!line) {
5056 /* Reset all! */
5057 commit = NULL;
5058 blamed = 0;
5059 read_file = TRUE;
5060 string_format(view->ref, "%s", view->vid);
5061 if (view_is_displayed(view)) {
5062 update_view_title(view);
5063 redraw_view_from(view, 0);
5064 }
5065 return TRUE;
5066 }
5068 if (!commit) {
5069 commit = parse_blame_commit(view, line, &blamed);
5070 string_format(view->ref, "%s %2d%%", view->vid,
5071 view->lines ? blamed * 100 / view->lines : 0);
5073 } else if (match_blame_header("author ", &line)) {
5074 commit->author = get_author(line);
5076 } else if (match_blame_header("author-time ", &line)) {
5077 parse_timesec(&commit->time, line);
5079 } else if (match_blame_header("author-tz ", &line)) {
5080 parse_timezone(&commit->time, line);
5082 } else if (match_blame_header("summary ", &line)) {
5083 string_ncopy(commit->title, line, strlen(line));
5085 } else if (match_blame_header("previous ", &line)) {
5086 commit->has_previous = TRUE;
5088 } else if (match_blame_header("filename ", &line)) {
5089 string_ncopy(commit->filename, line, strlen(line));
5090 commit = NULL;
5091 }
5093 return TRUE;
5094 }
5096 static bool
5097 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5098 {
5099 struct blame *blame = line->data;
5100 struct time *time = NULL;
5101 const char *id = NULL, *author = NULL;
5102 char text[SIZEOF_STR];
5104 if (blame->commit && *blame->commit->filename) {
5105 id = blame->commit->id;
5106 author = blame->commit->author;
5107 time = &blame->commit->time;
5108 }
5110 if (opt_date && draw_date(view, time))
5111 return TRUE;
5113 if (opt_author && draw_author(view, author))
5114 return TRUE;
5116 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5117 return TRUE;
5119 if (draw_lineno(view, lineno))
5120 return TRUE;
5122 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5123 draw_text(view, LINE_DEFAULT, text, TRUE);
5124 return TRUE;
5125 }
5127 static bool
5128 check_blame_commit(struct blame *blame, bool check_null_id)
5129 {
5130 if (!blame->commit)
5131 report("Commit data not loaded yet");
5132 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5133 report("No commit exist for the selected line");
5134 else
5135 return TRUE;
5136 return FALSE;
5137 }
5139 static void
5140 setup_blame_parent_line(struct view *view, struct blame *blame)
5141 {
5142 const char *diff_tree_argv[] = {
5143 "git", "diff-tree", "-U0", blame->commit->id,
5144 "--", blame->commit->filename, NULL
5145 };
5146 struct io io = {};
5147 int parent_lineno = -1;
5148 int blamed_lineno = -1;
5149 char *line;
5151 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5152 return;
5154 while ((line = io_get(&io, '\n', TRUE))) {
5155 if (*line == '@') {
5156 char *pos = strchr(line, '+');
5158 parent_lineno = atoi(line + 4);
5159 if (pos)
5160 blamed_lineno = atoi(pos + 1);
5162 } else if (*line == '+' && parent_lineno != -1) {
5163 if (blame->lineno == blamed_lineno - 1 &&
5164 !strcmp(blame->text, line + 1)) {
5165 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5166 break;
5167 }
5168 blamed_lineno++;
5169 }
5170 }
5172 io_done(&io);
5173 }
5175 static enum request
5176 blame_request(struct view *view, enum request request, struct line *line)
5177 {
5178 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5179 struct blame *blame = line->data;
5181 switch (request) {
5182 case REQ_VIEW_BLAME:
5183 if (check_blame_commit(blame, TRUE)) {
5184 string_copy(opt_ref, blame->commit->id);
5185 string_copy(opt_file, blame->commit->filename);
5186 if (blame->lineno)
5187 view->lineno = blame->lineno;
5188 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5189 }
5190 break;
5192 case REQ_PARENT:
5193 if (check_blame_commit(blame, TRUE) &&
5194 select_commit_parent(blame->commit->id, opt_ref,
5195 blame->commit->filename)) {
5196 string_copy(opt_file, blame->commit->filename);
5197 setup_blame_parent_line(view, blame);
5198 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5199 }
5200 break;
5202 case REQ_ENTER:
5203 if (!check_blame_commit(blame, FALSE))
5204 break;
5206 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5207 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5208 break;
5210 if (!strcmp(blame->commit->id, NULL_ID)) {
5211 struct view *diff = VIEW(REQ_VIEW_DIFF);
5212 const char *diff_index_argv[] = {
5213 "git", "diff-index", "--root", "--patch-with-stat",
5214 "-C", "-M", "HEAD", "--", view->vid, NULL
5215 };
5217 if (!blame->commit->has_previous) {
5218 diff_index_argv[1] = "diff";
5219 diff_index_argv[2] = "--no-color";
5220 diff_index_argv[6] = "--";
5221 diff_index_argv[7] = "/dev/null";
5222 }
5224 if (!prepare_update(diff, diff_index_argv, NULL)) {
5225 report("Failed to allocate diff command");
5226 break;
5227 }
5228 flags |= OPEN_PREPARED;
5229 }
5231 open_view(view, REQ_VIEW_DIFF, flags);
5232 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5233 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5234 break;
5236 default:
5237 return request;
5238 }
5240 return REQ_NONE;
5241 }
5243 static bool
5244 blame_grep(struct view *view, struct line *line)
5245 {
5246 struct blame *blame = line->data;
5247 struct blame_commit *commit = blame->commit;
5248 const char *text[] = {
5249 blame->text,
5250 commit ? commit->title : "",
5251 commit ? commit->id : "",
5252 commit && opt_author ? commit->author : "",
5253 commit ? mkdate(&commit->time, opt_date) : "",
5254 NULL
5255 };
5257 return grep_text(view, text);
5258 }
5260 static void
5261 blame_select(struct view *view, struct line *line)
5262 {
5263 struct blame *blame = line->data;
5264 struct blame_commit *commit = blame->commit;
5266 if (!commit)
5267 return;
5269 if (!strcmp(commit->id, NULL_ID))
5270 string_ncopy(ref_commit, "HEAD", 4);
5271 else
5272 string_copy_rev(ref_commit, commit->id);
5273 }
5275 static struct view_ops blame_ops = {
5276 "line",
5277 NULL,
5278 blame_open,
5279 blame_read,
5280 blame_draw,
5281 blame_request,
5282 blame_grep,
5283 blame_select,
5284 };
5286 /*
5287 * Branch backend
5288 */
5290 struct branch {
5291 const char *author; /* Author of the last commit. */
5292 struct time time; /* Date of the last activity. */
5293 const struct ref *ref; /* Name and commit ID information. */
5294 };
5296 static const struct ref branch_all;
5298 static const enum sort_field branch_sort_fields[] = {
5299 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5300 };
5301 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5303 static int
5304 branch_compare(const void *l1, const void *l2)
5305 {
5306 const struct branch *branch1 = ((const struct line *) l1)->data;
5307 const struct branch *branch2 = ((const struct line *) l2)->data;
5309 switch (get_sort_field(branch_sort_state)) {
5310 case ORDERBY_DATE:
5311 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5313 case ORDERBY_AUTHOR:
5314 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5316 case ORDERBY_NAME:
5317 default:
5318 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5319 }
5320 }
5322 static bool
5323 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5324 {
5325 struct branch *branch = line->data;
5326 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5328 if (opt_date && draw_date(view, &branch->time))
5329 return TRUE;
5331 if (opt_author && draw_author(view, branch->author))
5332 return TRUE;
5334 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5335 return TRUE;
5336 }
5338 static enum request
5339 branch_request(struct view *view, enum request request, struct line *line)
5340 {
5341 struct branch *branch = line->data;
5343 switch (request) {
5344 case REQ_REFRESH:
5345 load_refs();
5346 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5347 return REQ_NONE;
5349 case REQ_TOGGLE_SORT_FIELD:
5350 case REQ_TOGGLE_SORT_ORDER:
5351 sort_view(view, request, &branch_sort_state, branch_compare);
5352 return REQ_NONE;
5354 case REQ_ENTER:
5355 if (branch->ref == &branch_all) {
5356 const char *all_branches_argv[] = {
5357 "git", "log", "--no-color", "--pretty=raw", "--parents",
5358 "--topo-order", "--all", NULL
5359 };
5360 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5362 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5363 report("Failed to load view of all branches");
5364 return REQ_NONE;
5365 }
5366 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5367 } else {
5368 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5369 }
5370 return REQ_NONE;
5372 default:
5373 return request;
5374 }
5375 }
5377 static bool
5378 branch_read(struct view *view, char *line)
5379 {
5380 static char id[SIZEOF_REV];
5381 struct branch *reference;
5382 size_t i;
5384 if (!line)
5385 return TRUE;
5387 switch (get_line_type(line)) {
5388 case LINE_COMMIT:
5389 string_copy_rev(id, line + STRING_SIZE("commit "));
5390 return TRUE;
5392 case LINE_AUTHOR:
5393 for (i = 0, reference = NULL; i < view->lines; i++) {
5394 struct branch *branch = view->line[i].data;
5396 if (strcmp(branch->ref->id, id))
5397 continue;
5399 view->line[i].dirty = TRUE;
5400 if (reference) {
5401 branch->author = reference->author;
5402 branch->time = reference->time;
5403 continue;
5404 }
5406 parse_author_line(line + STRING_SIZE("author "),
5407 &branch->author, &branch->time);
5408 reference = branch;
5409 }
5410 return TRUE;
5412 default:
5413 return TRUE;
5414 }
5416 }
5418 static bool
5419 branch_open_visitor(void *data, const struct ref *ref)
5420 {
5421 struct view *view = data;
5422 struct branch *branch;
5424 if (ref->tag || ref->ltag || ref->remote)
5425 return TRUE;
5427 branch = calloc(1, sizeof(*branch));
5428 if (!branch)
5429 return FALSE;
5431 branch->ref = ref;
5432 return !!add_line_data(view, branch, LINE_DEFAULT);
5433 }
5435 static bool
5436 branch_open(struct view *view)
5437 {
5438 const char *branch_log[] = {
5439 "git", "log", "--no-color", "--pretty=raw",
5440 "--simplify-by-decoration", "--all", NULL
5441 };
5443 if (!start_update(view, branch_log, NULL)) {
5444 report("Failed to load branch data");
5445 return TRUE;
5446 }
5448 setup_update(view, view->id);
5449 branch_open_visitor(view, &branch_all);
5450 foreach_ref(branch_open_visitor, view);
5451 view->p_restore = TRUE;
5453 return TRUE;
5454 }
5456 static bool
5457 branch_grep(struct view *view, struct line *line)
5458 {
5459 struct branch *branch = line->data;
5460 const char *text[] = {
5461 branch->ref->name,
5462 branch->author,
5463 NULL
5464 };
5466 return grep_text(view, text);
5467 }
5469 static void
5470 branch_select(struct view *view, struct line *line)
5471 {
5472 struct branch *branch = line->data;
5474 string_copy_rev(view->ref, branch->ref->id);
5475 string_copy_rev(ref_commit, branch->ref->id);
5476 string_copy_rev(ref_head, branch->ref->id);
5477 string_copy_rev(ref_branch, branch->ref->name);
5478 }
5480 static struct view_ops branch_ops = {
5481 "branch",
5482 NULL,
5483 branch_open,
5484 branch_read,
5485 branch_draw,
5486 branch_request,
5487 branch_grep,
5488 branch_select,
5489 };
5491 /*
5492 * Status backend
5493 */
5495 struct status {
5496 char status;
5497 struct {
5498 mode_t mode;
5499 char rev[SIZEOF_REV];
5500 char name[SIZEOF_STR];
5501 } old;
5502 struct {
5503 mode_t mode;
5504 char rev[SIZEOF_REV];
5505 char name[SIZEOF_STR];
5506 } new;
5507 };
5509 static char status_onbranch[SIZEOF_STR];
5510 static struct status stage_status;
5511 static enum line_type stage_line_type;
5512 static size_t stage_chunks;
5513 static int *stage_chunk;
5515 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5517 /* This should work even for the "On branch" line. */
5518 static inline bool
5519 status_has_none(struct view *view, struct line *line)
5520 {
5521 return line < view->line + view->lines && !line[1].data;
5522 }
5524 /* Get fields from the diff line:
5525 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5526 */
5527 static inline bool
5528 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5529 {
5530 const char *old_mode = buf + 1;
5531 const char *new_mode = buf + 8;
5532 const char *old_rev = buf + 15;
5533 const char *new_rev = buf + 56;
5534 const char *status = buf + 97;
5536 if (bufsize < 98 ||
5537 old_mode[-1] != ':' ||
5538 new_mode[-1] != ' ' ||
5539 old_rev[-1] != ' ' ||
5540 new_rev[-1] != ' ' ||
5541 status[-1] != ' ')
5542 return FALSE;
5544 file->status = *status;
5546 string_copy_rev(file->old.rev, old_rev);
5547 string_copy_rev(file->new.rev, new_rev);
5549 file->old.mode = strtoul(old_mode, NULL, 8);
5550 file->new.mode = strtoul(new_mode, NULL, 8);
5552 file->old.name[0] = file->new.name[0] = 0;
5554 return TRUE;
5555 }
5557 static bool
5558 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5559 {
5560 struct status *unmerged = NULL;
5561 char *buf;
5562 struct io io = {};
5564 if (!io_run(&io, argv, opt_cdup, IO_RD))
5565 return FALSE;
5567 add_line_data(view, NULL, type);
5569 while ((buf = io_get(&io, 0, TRUE))) {
5570 struct status *file = unmerged;
5572 if (!file) {
5573 file = calloc(1, sizeof(*file));
5574 if (!file || !add_line_data(view, file, type))
5575 goto error_out;
5576 }
5578 /* Parse diff info part. */
5579 if (status) {
5580 file->status = status;
5581 if (status == 'A')
5582 string_copy(file->old.rev, NULL_ID);
5584 } else if (!file->status || file == unmerged) {
5585 if (!status_get_diff(file, buf, strlen(buf)))
5586 goto error_out;
5588 buf = io_get(&io, 0, TRUE);
5589 if (!buf)
5590 break;
5592 /* Collapse all modified entries that follow an
5593 * associated unmerged entry. */
5594 if (unmerged == file) {
5595 unmerged->status = 'U';
5596 unmerged = NULL;
5597 } else if (file->status == 'U') {
5598 unmerged = file;
5599 }
5600 }
5602 /* Grab the old name for rename/copy. */
5603 if (!*file->old.name &&
5604 (file->status == 'R' || file->status == 'C')) {
5605 string_ncopy(file->old.name, buf, strlen(buf));
5607 buf = io_get(&io, 0, TRUE);
5608 if (!buf)
5609 break;
5610 }
5612 /* git-ls-files just delivers a NUL separated list of
5613 * file names similar to the second half of the
5614 * git-diff-* output. */
5615 string_ncopy(file->new.name, buf, strlen(buf));
5616 if (!*file->old.name)
5617 string_copy(file->old.name, file->new.name);
5618 file = NULL;
5619 }
5621 if (io_error(&io)) {
5622 error_out:
5623 io_done(&io);
5624 return FALSE;
5625 }
5627 if (!view->line[view->lines - 1].data)
5628 add_line_data(view, NULL, LINE_STAT_NONE);
5630 io_done(&io);
5631 return TRUE;
5632 }
5634 /* Don't show unmerged entries in the staged section. */
5635 static const char *status_diff_index_argv[] = {
5636 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5637 "--cached", "-M", "HEAD", NULL
5638 };
5640 static const char *status_diff_files_argv[] = {
5641 "git", "diff-files", "-z", NULL
5642 };
5644 static const char *status_list_other_argv[] = {
5645 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5646 };
5648 static const char *status_list_no_head_argv[] = {
5649 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5650 };
5652 static const char *update_index_argv[] = {
5653 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5654 };
5656 /* Restore the previous line number to stay in the context or select a
5657 * line with something that can be updated. */
5658 static void
5659 status_restore(struct view *view)
5660 {
5661 if (view->p_lineno >= view->lines)
5662 view->p_lineno = view->lines - 1;
5663 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5664 view->p_lineno++;
5665 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5666 view->p_lineno--;
5668 /* If the above fails, always skip the "On branch" line. */
5669 if (view->p_lineno < view->lines)
5670 view->lineno = view->p_lineno;
5671 else
5672 view->lineno = 1;
5674 if (view->lineno < view->offset)
5675 view->offset = view->lineno;
5676 else if (view->offset + view->height <= view->lineno)
5677 view->offset = view->lineno - view->height + 1;
5679 view->p_restore = FALSE;
5680 }
5682 static void
5683 status_update_onbranch(void)
5684 {
5685 static const char *paths[][2] = {
5686 { "rebase-apply/rebasing", "Rebasing" },
5687 { "rebase-apply/applying", "Applying mailbox" },
5688 { "rebase-apply/", "Rebasing mailbox" },
5689 { "rebase-merge/interactive", "Interactive rebase" },
5690 { "rebase-merge/", "Rebase merge" },
5691 { "MERGE_HEAD", "Merging" },
5692 { "BISECT_LOG", "Bisecting" },
5693 { "HEAD", "On branch" },
5694 };
5695 char buf[SIZEOF_STR];
5696 struct stat stat;
5697 int i;
5699 if (is_initial_commit()) {
5700 string_copy(status_onbranch, "Initial commit");
5701 return;
5702 }
5704 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5705 char *head = opt_head;
5707 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5708 lstat(buf, &stat) < 0)
5709 continue;
5711 if (!*opt_head) {
5712 struct io io = {};
5714 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5715 io_read_buf(&io, buf, sizeof(buf))) {
5716 head = buf;
5717 if (!prefixcmp(head, "refs/heads/"))
5718 head += STRING_SIZE("refs/heads/");
5719 }
5720 }
5722 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5723 string_copy(status_onbranch, opt_head);
5724 return;
5725 }
5727 string_copy(status_onbranch, "Not currently on any branch");
5728 }
5730 /* First parse staged info using git-diff-index(1), then parse unstaged
5731 * info using git-diff-files(1), and finally untracked files using
5732 * git-ls-files(1). */
5733 static bool
5734 status_open(struct view *view)
5735 {
5736 reset_view(view);
5738 add_line_data(view, NULL, LINE_STAT_HEAD);
5739 status_update_onbranch();
5741 io_run_bg(update_index_argv);
5743 if (is_initial_commit()) {
5744 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5745 return FALSE;
5746 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5747 return FALSE;
5748 }
5750 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5751 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5752 return FALSE;
5754 /* Restore the exact position or use the specialized restore
5755 * mode? */
5756 if (!view->p_restore)
5757 status_restore(view);
5758 return TRUE;
5759 }
5761 static bool
5762 status_draw(struct view *view, struct line *line, unsigned int lineno)
5763 {
5764 struct status *status = line->data;
5765 enum line_type type;
5766 const char *text;
5768 if (!status) {
5769 switch (line->type) {
5770 case LINE_STAT_STAGED:
5771 type = LINE_STAT_SECTION;
5772 text = "Changes to be committed:";
5773 break;
5775 case LINE_STAT_UNSTAGED:
5776 type = LINE_STAT_SECTION;
5777 text = "Changed but not updated:";
5778 break;
5780 case LINE_STAT_UNTRACKED:
5781 type = LINE_STAT_SECTION;
5782 text = "Untracked files:";
5783 break;
5785 case LINE_STAT_NONE:
5786 type = LINE_DEFAULT;
5787 text = " (no files)";
5788 break;
5790 case LINE_STAT_HEAD:
5791 type = LINE_STAT_HEAD;
5792 text = status_onbranch;
5793 break;
5795 default:
5796 return FALSE;
5797 }
5798 } else {
5799 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5801 buf[0] = status->status;
5802 if (draw_text(view, line->type, buf, TRUE))
5803 return TRUE;
5804 type = LINE_DEFAULT;
5805 text = status->new.name;
5806 }
5808 draw_text(view, type, text, TRUE);
5809 return TRUE;
5810 }
5812 static enum request
5813 status_load_error(struct view *view, struct view *stage, const char *path)
5814 {
5815 if (displayed_views() == 2 || display[current_view] != view)
5816 maximize_view(view);
5817 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5818 return REQ_NONE;
5819 }
5821 static enum request
5822 status_enter(struct view *view, struct line *line)
5823 {
5824 struct status *status = line->data;
5825 const char *oldpath = status ? status->old.name : NULL;
5826 /* Diffs for unmerged entries are empty when passing the new
5827 * path, so leave it empty. */
5828 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5829 const char *info;
5830 enum open_flags split;
5831 struct view *stage = VIEW(REQ_VIEW_STAGE);
5833 if (line->type == LINE_STAT_NONE ||
5834 (!status && line[1].type == LINE_STAT_NONE)) {
5835 report("No file to diff");
5836 return REQ_NONE;
5837 }
5839 switch (line->type) {
5840 case LINE_STAT_STAGED:
5841 if (is_initial_commit()) {
5842 const char *no_head_diff_argv[] = {
5843 "git", "diff", "--no-color", "--patch-with-stat",
5844 "--", "/dev/null", newpath, NULL
5845 };
5847 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5848 return status_load_error(view, stage, newpath);
5849 } else {
5850 const char *index_show_argv[] = {
5851 "git", "diff-index", "--root", "--patch-with-stat",
5852 "-C", "-M", "--cached", "HEAD", "--",
5853 oldpath, newpath, NULL
5854 };
5856 if (!prepare_update(stage, index_show_argv, opt_cdup))
5857 return status_load_error(view, stage, newpath);
5858 }
5860 if (status)
5861 info = "Staged changes to %s";
5862 else
5863 info = "Staged changes";
5864 break;
5866 case LINE_STAT_UNSTAGED:
5867 {
5868 const char *files_show_argv[] = {
5869 "git", "diff-files", "--root", "--patch-with-stat",
5870 "-C", "-M", "--", oldpath, newpath, NULL
5871 };
5873 if (!prepare_update(stage, files_show_argv, opt_cdup))
5874 return status_load_error(view, stage, newpath);
5875 if (status)
5876 info = "Unstaged changes to %s";
5877 else
5878 info = "Unstaged changes";
5879 break;
5880 }
5881 case LINE_STAT_UNTRACKED:
5882 if (!newpath) {
5883 report("No file to show");
5884 return REQ_NONE;
5885 }
5887 if (!suffixcmp(status->new.name, -1, "/")) {
5888 report("Cannot display a directory");
5889 return REQ_NONE;
5890 }
5892 if (!prepare_update_file(stage, newpath))
5893 return status_load_error(view, stage, newpath);
5894 info = "Untracked file %s";
5895 break;
5897 case LINE_STAT_HEAD:
5898 return REQ_NONE;
5900 default:
5901 die("line type %d not handled in switch", line->type);
5902 }
5904 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5905 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5906 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5907 if (status) {
5908 stage_status = *status;
5909 } else {
5910 memset(&stage_status, 0, sizeof(stage_status));
5911 }
5913 stage_line_type = line->type;
5914 stage_chunks = 0;
5915 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5916 }
5918 return REQ_NONE;
5919 }
5921 static bool
5922 status_exists(struct status *status, enum line_type type)
5923 {
5924 struct view *view = VIEW(REQ_VIEW_STATUS);
5925 unsigned long lineno;
5927 for (lineno = 0; lineno < view->lines; lineno++) {
5928 struct line *line = &view->line[lineno];
5929 struct status *pos = line->data;
5931 if (line->type != type)
5932 continue;
5933 if (!pos && (!status || !status->status) && line[1].data) {
5934 select_view_line(view, lineno);
5935 return TRUE;
5936 }
5937 if (pos && !strcmp(status->new.name, pos->new.name)) {
5938 select_view_line(view, lineno);
5939 return TRUE;
5940 }
5941 }
5943 return FALSE;
5944 }
5947 static bool
5948 status_update_prepare(struct io *io, enum line_type type)
5949 {
5950 const char *staged_argv[] = {
5951 "git", "update-index", "-z", "--index-info", NULL
5952 };
5953 const char *others_argv[] = {
5954 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5955 };
5957 switch (type) {
5958 case LINE_STAT_STAGED:
5959 return io_run(io, staged_argv, opt_cdup, IO_WR);
5961 case LINE_STAT_UNSTAGED:
5962 case LINE_STAT_UNTRACKED:
5963 return io_run(io, others_argv, opt_cdup, IO_WR);
5965 default:
5966 die("line type %d not handled in switch", type);
5967 return FALSE;
5968 }
5969 }
5971 static bool
5972 status_update_write(struct io *io, struct status *status, enum line_type type)
5973 {
5974 char buf[SIZEOF_STR];
5975 size_t bufsize = 0;
5977 switch (type) {
5978 case LINE_STAT_STAGED:
5979 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5980 status->old.mode,
5981 status->old.rev,
5982 status->old.name, 0))
5983 return FALSE;
5984 break;
5986 case LINE_STAT_UNSTAGED:
5987 case LINE_STAT_UNTRACKED:
5988 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5989 return FALSE;
5990 break;
5992 default:
5993 die("line type %d not handled in switch", type);
5994 }
5996 return io_write(io, buf, bufsize);
5997 }
5999 static bool
6000 status_update_file(struct status *status, enum line_type type)
6001 {
6002 struct io io = {};
6003 bool result;
6005 if (!status_update_prepare(&io, type))
6006 return FALSE;
6008 result = status_update_write(&io, status, type);
6009 return io_done(&io) && result;
6010 }
6012 static bool
6013 status_update_files(struct view *view, struct line *line)
6014 {
6015 char buf[sizeof(view->ref)];
6016 struct io io = {};
6017 bool result = TRUE;
6018 struct line *pos = view->line + view->lines;
6019 int files = 0;
6020 int file, done;
6021 int cursor_y = -1, cursor_x = -1;
6023 if (!status_update_prepare(&io, line->type))
6024 return FALSE;
6026 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6027 files++;
6029 string_copy(buf, view->ref);
6030 getsyx(cursor_y, cursor_x);
6031 for (file = 0, done = 5; result && file < files; line++, file++) {
6032 int almost_done = file * 100 / files;
6034 if (almost_done > done) {
6035 done = almost_done;
6036 string_format(view->ref, "updating file %u of %u (%d%% done)",
6037 file, files, done);
6038 update_view_title(view);
6039 setsyx(cursor_y, cursor_x);
6040 doupdate();
6041 }
6042 result = status_update_write(&io, line->data, line->type);
6043 }
6044 string_copy(view->ref, buf);
6046 return io_done(&io) && result;
6047 }
6049 static bool
6050 status_update(struct view *view)
6051 {
6052 struct line *line = &view->line[view->lineno];
6054 assert(view->lines);
6056 if (!line->data) {
6057 /* This should work even for the "On branch" line. */
6058 if (line < view->line + view->lines && !line[1].data) {
6059 report("Nothing to update");
6060 return FALSE;
6061 }
6063 if (!status_update_files(view, line + 1)) {
6064 report("Failed to update file status");
6065 return FALSE;
6066 }
6068 } else if (!status_update_file(line->data, line->type)) {
6069 report("Failed to update file status");
6070 return FALSE;
6071 }
6073 return TRUE;
6074 }
6076 static bool
6077 status_revert(struct status *status, enum line_type type, bool has_none)
6078 {
6079 if (!status || type != LINE_STAT_UNSTAGED) {
6080 if (type == LINE_STAT_STAGED) {
6081 report("Cannot revert changes to staged files");
6082 } else if (type == LINE_STAT_UNTRACKED) {
6083 report("Cannot revert changes to untracked files");
6084 } else if (has_none) {
6085 report("Nothing to revert");
6086 } else {
6087 report("Cannot revert changes to multiple files");
6088 }
6090 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6091 char mode[10] = "100644";
6092 const char *reset_argv[] = {
6093 "git", "update-index", "--cacheinfo", mode,
6094 status->old.rev, status->old.name, NULL
6095 };
6096 const char *checkout_argv[] = {
6097 "git", "checkout", "--", status->old.name, NULL
6098 };
6100 if (status->status == 'U') {
6101 string_format(mode, "%5o", status->old.mode);
6103 if (status->old.mode == 0 && status->new.mode == 0) {
6104 reset_argv[2] = "--force-remove";
6105 reset_argv[3] = status->old.name;
6106 reset_argv[4] = NULL;
6107 }
6109 if (!io_run_fg(reset_argv, opt_cdup))
6110 return FALSE;
6111 if (status->old.mode == 0 && status->new.mode == 0)
6112 return TRUE;
6113 }
6115 return io_run_fg(checkout_argv, opt_cdup);
6116 }
6118 return FALSE;
6119 }
6121 static enum request
6122 status_request(struct view *view, enum request request, struct line *line)
6123 {
6124 struct status *status = line->data;
6126 switch (request) {
6127 case REQ_STATUS_UPDATE:
6128 if (!status_update(view))
6129 return REQ_NONE;
6130 break;
6132 case REQ_STATUS_REVERT:
6133 if (!status_revert(status, line->type, status_has_none(view, line)))
6134 return REQ_NONE;
6135 break;
6137 case REQ_STATUS_MERGE:
6138 if (!status || status->status != 'U') {
6139 report("Merging only possible for files with unmerged status ('U').");
6140 return REQ_NONE;
6141 }
6142 open_mergetool(status->new.name);
6143 break;
6145 case REQ_EDIT:
6146 if (!status)
6147 return request;
6148 if (status->status == 'D') {
6149 report("File has been deleted.");
6150 return REQ_NONE;
6151 }
6153 open_editor(status->new.name);
6154 break;
6156 case REQ_VIEW_BLAME:
6157 if (status)
6158 opt_ref[0] = 0;
6159 return request;
6161 case REQ_ENTER:
6162 /* After returning the status view has been split to
6163 * show the stage view. No further reloading is
6164 * necessary. */
6165 return status_enter(view, line);
6167 case REQ_REFRESH:
6168 /* Simply reload the view. */
6169 break;
6171 default:
6172 return request;
6173 }
6175 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6177 return REQ_NONE;
6178 }
6180 static void
6181 status_select(struct view *view, struct line *line)
6182 {
6183 struct status *status = line->data;
6184 char file[SIZEOF_STR] = "all files";
6185 const char *text;
6186 const char *key;
6188 if (status && !string_format(file, "'%s'", status->new.name))
6189 return;
6191 if (!status && line[1].type == LINE_STAT_NONE)
6192 line++;
6194 switch (line->type) {
6195 case LINE_STAT_STAGED:
6196 text = "Press %s to unstage %s for commit";
6197 break;
6199 case LINE_STAT_UNSTAGED:
6200 text = "Press %s to stage %s for commit";
6201 break;
6203 case LINE_STAT_UNTRACKED:
6204 text = "Press %s to stage %s for addition";
6205 break;
6207 case LINE_STAT_HEAD:
6208 case LINE_STAT_NONE:
6209 text = "Nothing to update";
6210 break;
6212 default:
6213 die("line type %d not handled in switch", line->type);
6214 }
6216 if (status && status->status == 'U') {
6217 text = "Press %s to resolve conflict in %s";
6218 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6220 } else {
6221 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6222 }
6224 string_format(view->ref, text, key, file);
6225 if (status)
6226 string_copy(opt_file, status->new.name);
6227 }
6229 static bool
6230 status_grep(struct view *view, struct line *line)
6231 {
6232 struct status *status = line->data;
6234 if (status) {
6235 const char buf[2] = { status->status, 0 };
6236 const char *text[] = { status->new.name, buf, NULL };
6238 return grep_text(view, text);
6239 }
6241 return FALSE;
6242 }
6244 static struct view_ops status_ops = {
6245 "file",
6246 NULL,
6247 status_open,
6248 NULL,
6249 status_draw,
6250 status_request,
6251 status_grep,
6252 status_select,
6253 };
6256 static bool
6257 stage_diff_write(struct io *io, struct line *line, struct line *end)
6258 {
6259 while (line < end) {
6260 if (!io_write(io, line->data, strlen(line->data)) ||
6261 !io_write(io, "\n", 1))
6262 return FALSE;
6263 line++;
6264 if (line->type == LINE_DIFF_CHUNK ||
6265 line->type == LINE_DIFF_HEADER)
6266 break;
6267 }
6269 return TRUE;
6270 }
6272 static struct line *
6273 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6274 {
6275 for (; view->line < line; line--)
6276 if (line->type == type)
6277 return line;
6279 return NULL;
6280 }
6282 static bool
6283 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6284 {
6285 const char *apply_argv[SIZEOF_ARG] = {
6286 "git", "apply", "--whitespace=nowarn", NULL
6287 };
6288 struct line *diff_hdr;
6289 struct io io = {};
6290 int argc = 3;
6292 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6293 if (!diff_hdr)
6294 return FALSE;
6296 if (!revert)
6297 apply_argv[argc++] = "--cached";
6298 if (revert || stage_line_type == LINE_STAT_STAGED)
6299 apply_argv[argc++] = "-R";
6300 apply_argv[argc++] = "-";
6301 apply_argv[argc++] = NULL;
6302 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6303 return FALSE;
6305 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6306 !stage_diff_write(&io, chunk, view->line + view->lines))
6307 chunk = NULL;
6309 io_done(&io);
6310 io_run_bg(update_index_argv);
6312 return chunk ? TRUE : FALSE;
6313 }
6315 static bool
6316 stage_update(struct view *view, struct line *line)
6317 {
6318 struct line *chunk = NULL;
6320 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6321 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6323 if (chunk) {
6324 if (!stage_apply_chunk(view, chunk, FALSE)) {
6325 report("Failed to apply chunk");
6326 return FALSE;
6327 }
6329 } else if (!stage_status.status) {
6330 view = VIEW(REQ_VIEW_STATUS);
6332 for (line = view->line; line < view->line + view->lines; line++)
6333 if (line->type == stage_line_type)
6334 break;
6336 if (!status_update_files(view, line + 1)) {
6337 report("Failed to update files");
6338 return FALSE;
6339 }
6341 } else if (!status_update_file(&stage_status, stage_line_type)) {
6342 report("Failed to update file");
6343 return FALSE;
6344 }
6346 return TRUE;
6347 }
6349 static bool
6350 stage_revert(struct view *view, struct line *line)
6351 {
6352 struct line *chunk = NULL;
6354 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6355 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6357 if (chunk) {
6358 if (!prompt_yesno("Are you sure you want to revert changes?"))
6359 return FALSE;
6361 if (!stage_apply_chunk(view, chunk, TRUE)) {
6362 report("Failed to revert chunk");
6363 return FALSE;
6364 }
6365 return TRUE;
6367 } else {
6368 return status_revert(stage_status.status ? &stage_status : NULL,
6369 stage_line_type, FALSE);
6370 }
6371 }
6374 static void
6375 stage_next(struct view *view, struct line *line)
6376 {
6377 int i;
6379 if (!stage_chunks) {
6380 for (line = view->line; line < view->line + view->lines; line++) {
6381 if (line->type != LINE_DIFF_CHUNK)
6382 continue;
6384 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6385 report("Allocation failure");
6386 return;
6387 }
6389 stage_chunk[stage_chunks++] = line - view->line;
6390 }
6391 }
6393 for (i = 0; i < stage_chunks; i++) {
6394 if (stage_chunk[i] > view->lineno) {
6395 do_scroll_view(view, stage_chunk[i] - view->lineno);
6396 report("Chunk %d of %d", i + 1, stage_chunks);
6397 return;
6398 }
6399 }
6401 report("No next chunk found");
6402 }
6404 static enum request
6405 stage_request(struct view *view, enum request request, struct line *line)
6406 {
6407 switch (request) {
6408 case REQ_STATUS_UPDATE:
6409 if (!stage_update(view, line))
6410 return REQ_NONE;
6411 break;
6413 case REQ_STATUS_REVERT:
6414 if (!stage_revert(view, line))
6415 return REQ_NONE;
6416 break;
6418 case REQ_STAGE_NEXT:
6419 if (stage_line_type == LINE_STAT_UNTRACKED) {
6420 report("File is untracked; press %s to add",
6421 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6422 return REQ_NONE;
6423 }
6424 stage_next(view, line);
6425 return REQ_NONE;
6427 case REQ_EDIT:
6428 if (!stage_status.new.name[0])
6429 return request;
6430 if (stage_status.status == 'D') {
6431 report("File has been deleted.");
6432 return REQ_NONE;
6433 }
6435 open_editor(stage_status.new.name);
6436 break;
6438 case REQ_REFRESH:
6439 /* Reload everything ... */
6440 break;
6442 case REQ_VIEW_BLAME:
6443 if (stage_status.new.name[0]) {
6444 string_copy(opt_file, stage_status.new.name);
6445 opt_ref[0] = 0;
6446 }
6447 return request;
6449 case REQ_ENTER:
6450 return pager_request(view, request, line);
6452 default:
6453 return request;
6454 }
6456 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6457 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6459 /* Check whether the staged entry still exists, and close the
6460 * stage view if it doesn't. */
6461 if (!status_exists(&stage_status, stage_line_type)) {
6462 status_restore(VIEW(REQ_VIEW_STATUS));
6463 return REQ_VIEW_CLOSE;
6464 }
6466 if (stage_line_type == LINE_STAT_UNTRACKED) {
6467 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6468 report("Cannot display a directory");
6469 return REQ_NONE;
6470 }
6472 if (!prepare_update_file(view, stage_status.new.name)) {
6473 report("Failed to open file: %s", strerror(errno));
6474 return REQ_NONE;
6475 }
6476 }
6477 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6479 return REQ_NONE;
6480 }
6482 static struct view_ops stage_ops = {
6483 "line",
6484 NULL,
6485 NULL,
6486 pager_read,
6487 pager_draw,
6488 stage_request,
6489 pager_grep,
6490 pager_select,
6491 };
6494 /*
6495 * Revision graph
6496 */
6498 struct commit {
6499 char id[SIZEOF_REV]; /* SHA1 ID. */
6500 char title[128]; /* First line of the commit message. */
6501 const char *author; /* Author of the commit. */
6502 struct time time; /* Date from the author ident. */
6503 struct ref_list *refs; /* Repository references. */
6504 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6505 size_t graph_size; /* The width of the graph array. */
6506 bool has_parents; /* Rewritten --parents seen. */
6507 };
6509 /* Size of rev graph with no "padding" columns */
6510 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6512 struct rev_graph {
6513 struct rev_graph *prev, *next, *parents;
6514 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6515 size_t size;
6516 struct commit *commit;
6517 size_t pos;
6518 unsigned int boundary:1;
6519 };
6521 /* Parents of the commit being visualized. */
6522 static struct rev_graph graph_parents[4];
6524 /* The current stack of revisions on the graph. */
6525 static struct rev_graph graph_stacks[4] = {
6526 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6527 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6528 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6529 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6530 };
6532 static inline bool
6533 graph_parent_is_merge(struct rev_graph *graph)
6534 {
6535 return graph->parents->size > 1;
6536 }
6538 static inline void
6539 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6540 {
6541 struct commit *commit = graph->commit;
6543 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6544 commit->graph[commit->graph_size++] = symbol;
6545 }
6547 static void
6548 clear_rev_graph(struct rev_graph *graph)
6549 {
6550 graph->boundary = 0;
6551 graph->size = graph->pos = 0;
6552 graph->commit = NULL;
6553 memset(graph->parents, 0, sizeof(*graph->parents));
6554 }
6556 static void
6557 done_rev_graph(struct rev_graph *graph)
6558 {
6559 if (graph_parent_is_merge(graph) &&
6560 graph->pos < graph->size - 1 &&
6561 graph->next->size == graph->size + graph->parents->size - 1) {
6562 size_t i = graph->pos + graph->parents->size - 1;
6564 graph->commit->graph_size = i * 2;
6565 while (i < graph->next->size - 1) {
6566 append_to_rev_graph(graph, ' ');
6567 append_to_rev_graph(graph, '\\');
6568 i++;
6569 }
6570 }
6572 clear_rev_graph(graph);
6573 }
6575 static void
6576 push_rev_graph(struct rev_graph *graph, const char *parent)
6577 {
6578 int i;
6580 /* "Collapse" duplicate parents lines.
6581 *
6582 * FIXME: This needs to also update update the drawn graph but
6583 * for now it just serves as a method for pruning graph lines. */
6584 for (i = 0; i < graph->size; i++)
6585 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6586 return;
6588 if (graph->size < SIZEOF_REVITEMS) {
6589 string_copy_rev(graph->rev[graph->size++], parent);
6590 }
6591 }
6593 static chtype
6594 get_rev_graph_symbol(struct rev_graph *graph)
6595 {
6596 chtype symbol;
6598 if (graph->boundary)
6599 symbol = REVGRAPH_BOUND;
6600 else if (graph->parents->size == 0)
6601 symbol = REVGRAPH_INIT;
6602 else if (graph_parent_is_merge(graph))
6603 symbol = REVGRAPH_MERGE;
6604 else if (graph->pos >= graph->size)
6605 symbol = REVGRAPH_BRANCH;
6606 else
6607 symbol = REVGRAPH_COMMIT;
6609 return symbol;
6610 }
6612 static void
6613 draw_rev_graph(struct rev_graph *graph)
6614 {
6615 struct rev_filler {
6616 chtype separator, line;
6617 };
6618 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6619 static struct rev_filler fillers[] = {
6620 { ' ', '|' },
6621 { '`', '.' },
6622 { '\'', ' ' },
6623 { '/', ' ' },
6624 };
6625 chtype symbol = get_rev_graph_symbol(graph);
6626 struct rev_filler *filler;
6627 size_t i;
6629 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6630 filler = &fillers[DEFAULT];
6632 for (i = 0; i < graph->pos; i++) {
6633 append_to_rev_graph(graph, filler->line);
6634 if (graph_parent_is_merge(graph->prev) &&
6635 graph->prev->pos == i)
6636 filler = &fillers[RSHARP];
6638 append_to_rev_graph(graph, filler->separator);
6639 }
6641 /* Place the symbol for this revision. */
6642 append_to_rev_graph(graph, symbol);
6644 if (graph->prev->size > graph->size)
6645 filler = &fillers[RDIAG];
6646 else
6647 filler = &fillers[DEFAULT];
6649 i++;
6651 for (; i < graph->size; i++) {
6652 append_to_rev_graph(graph, filler->separator);
6653 append_to_rev_graph(graph, filler->line);
6654 if (graph_parent_is_merge(graph->prev) &&
6655 i < graph->prev->pos + graph->parents->size)
6656 filler = &fillers[RSHARP];
6657 if (graph->prev->size > graph->size)
6658 filler = &fillers[LDIAG];
6659 }
6661 if (graph->prev->size > graph->size) {
6662 append_to_rev_graph(graph, filler->separator);
6663 if (filler->line != ' ')
6664 append_to_rev_graph(graph, filler->line);
6665 }
6666 }
6668 /* Prepare the next rev graph */
6669 static void
6670 prepare_rev_graph(struct rev_graph *graph)
6671 {
6672 size_t i;
6674 /* First, traverse all lines of revisions up to the active one. */
6675 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6676 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6677 break;
6679 push_rev_graph(graph->next, graph->rev[graph->pos]);
6680 }
6682 /* Interleave the new revision parent(s). */
6683 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6684 push_rev_graph(graph->next, graph->parents->rev[i]);
6686 /* Lastly, put any remaining revisions. */
6687 for (i = graph->pos + 1; i < graph->size; i++)
6688 push_rev_graph(graph->next, graph->rev[i]);
6689 }
6691 static void
6692 update_rev_graph(struct view *view, struct rev_graph *graph)
6693 {
6694 /* If this is the finalizing update ... */
6695 if (graph->commit)
6696 prepare_rev_graph(graph);
6698 /* Graph visualization needs a one rev look-ahead,
6699 * so the first update doesn't visualize anything. */
6700 if (!graph->prev->commit)
6701 return;
6703 if (view->lines > 2)
6704 view->line[view->lines - 3].dirty = 1;
6705 if (view->lines > 1)
6706 view->line[view->lines - 2].dirty = 1;
6707 draw_rev_graph(graph->prev);
6708 done_rev_graph(graph->prev->prev);
6709 }
6712 /*
6713 * Main view backend
6714 */
6716 static const char *main_argv[SIZEOF_ARG] = {
6717 "git", "log", "--no-color", "--pretty=raw", "--parents",
6718 "--topo-order", "%(head)", NULL
6719 };
6721 static bool
6722 main_draw(struct view *view, struct line *line, unsigned int lineno)
6723 {
6724 struct commit *commit = line->data;
6726 if (!commit->author)
6727 return FALSE;
6729 if (opt_date && draw_date(view, &commit->time))
6730 return TRUE;
6732 if (opt_author && draw_author(view, commit->author))
6733 return TRUE;
6735 if (opt_rev_graph && commit->graph_size &&
6736 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6737 return TRUE;
6739 if (opt_show_refs && commit->refs) {
6740 size_t i;
6742 for (i = 0; i < commit->refs->size; i++) {
6743 struct ref *ref = commit->refs->refs[i];
6744 enum line_type type;
6746 if (ref->head)
6747 type = LINE_MAIN_HEAD;
6748 else if (ref->ltag)
6749 type = LINE_MAIN_LOCAL_TAG;
6750 else if (ref->tag)
6751 type = LINE_MAIN_TAG;
6752 else if (ref->tracked)
6753 type = LINE_MAIN_TRACKED;
6754 else if (ref->remote)
6755 type = LINE_MAIN_REMOTE;
6756 else
6757 type = LINE_MAIN_REF;
6759 if (draw_text(view, type, "[", TRUE) ||
6760 draw_text(view, type, ref->name, TRUE) ||
6761 draw_text(view, type, "]", TRUE))
6762 return TRUE;
6764 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6765 return TRUE;
6766 }
6767 }
6769 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6770 return TRUE;
6771 }
6773 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6774 static bool
6775 main_read(struct view *view, char *line)
6776 {
6777 static struct rev_graph *graph = graph_stacks;
6778 enum line_type type;
6779 struct commit *commit;
6781 if (!line) {
6782 int i;
6784 if (!view->lines && !view->prev)
6785 die("No revisions match the given arguments.");
6786 if (view->lines > 0) {
6787 commit = view->line[view->lines - 1].data;
6788 view->line[view->lines - 1].dirty = 1;
6789 if (!commit->author) {
6790 view->lines--;
6791 free(commit);
6792 graph->commit = NULL;
6793 }
6794 }
6795 update_rev_graph(view, graph);
6797 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6798 clear_rev_graph(&graph_stacks[i]);
6799 return TRUE;
6800 }
6802 type = get_line_type(line);
6803 if (type == LINE_COMMIT) {
6804 commit = calloc(1, sizeof(struct commit));
6805 if (!commit)
6806 return FALSE;
6808 line += STRING_SIZE("commit ");
6809 if (*line == '-') {
6810 graph->boundary = 1;
6811 line++;
6812 }
6814 string_copy_rev(commit->id, line);
6815 commit->refs = get_ref_list(commit->id);
6816 graph->commit = commit;
6817 add_line_data(view, commit, LINE_MAIN_COMMIT);
6819 while ((line = strchr(line, ' '))) {
6820 line++;
6821 push_rev_graph(graph->parents, line);
6822 commit->has_parents = TRUE;
6823 }
6824 return TRUE;
6825 }
6827 if (!view->lines)
6828 return TRUE;
6829 commit = view->line[view->lines - 1].data;
6831 switch (type) {
6832 case LINE_PARENT:
6833 if (commit->has_parents)
6834 break;
6835 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6836 break;
6838 case LINE_AUTHOR:
6839 parse_author_line(line + STRING_SIZE("author "),
6840 &commit->author, &commit->time);
6841 update_rev_graph(view, graph);
6842 graph = graph->next;
6843 break;
6845 default:
6846 /* Fill in the commit title if it has not already been set. */
6847 if (commit->title[0])
6848 break;
6850 /* Require titles to start with a non-space character at the
6851 * offset used by git log. */
6852 if (strncmp(line, " ", 4))
6853 break;
6854 line += 4;
6855 /* Well, if the title starts with a whitespace character,
6856 * try to be forgiving. Otherwise we end up with no title. */
6857 while (isspace(*line))
6858 line++;
6859 if (*line == '\0')
6860 break;
6861 /* FIXME: More graceful handling of titles; append "..." to
6862 * shortened titles, etc. */
6864 string_expand(commit->title, sizeof(commit->title), line, 1);
6865 view->line[view->lines - 1].dirty = 1;
6866 }
6868 return TRUE;
6869 }
6871 static enum request
6872 main_request(struct view *view, enum request request, struct line *line)
6873 {
6874 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6876 switch (request) {
6877 case REQ_ENTER:
6878 open_view(view, REQ_VIEW_DIFF, flags);
6879 break;
6880 case REQ_REFRESH:
6881 load_refs();
6882 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6883 break;
6884 default:
6885 return request;
6886 }
6888 return REQ_NONE;
6889 }
6891 static bool
6892 grep_refs(struct ref_list *list, regex_t *regex)
6893 {
6894 regmatch_t pmatch;
6895 size_t i;
6897 if (!opt_show_refs || !list)
6898 return FALSE;
6900 for (i = 0; i < list->size; i++) {
6901 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6902 return TRUE;
6903 }
6905 return FALSE;
6906 }
6908 static bool
6909 main_grep(struct view *view, struct line *line)
6910 {
6911 struct commit *commit = line->data;
6912 const char *text[] = {
6913 commit->title,
6914 opt_author ? commit->author : "",
6915 mkdate(&commit->time, opt_date),
6916 NULL
6917 };
6919 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6920 }
6922 static void
6923 main_select(struct view *view, struct line *line)
6924 {
6925 struct commit *commit = line->data;
6927 string_copy_rev(view->ref, commit->id);
6928 string_copy_rev(ref_commit, view->ref);
6929 }
6931 static struct view_ops main_ops = {
6932 "commit",
6933 main_argv,
6934 NULL,
6935 main_read,
6936 main_draw,
6937 main_request,
6938 main_grep,
6939 main_select,
6940 };
6943 /*
6944 * Status management
6945 */
6947 /* Whether or not the curses interface has been initialized. */
6948 static bool cursed = FALSE;
6950 /* Terminal hacks and workarounds. */
6951 static bool use_scroll_redrawwin;
6952 static bool use_scroll_status_wclear;
6954 /* The status window is used for polling keystrokes. */
6955 static WINDOW *status_win;
6957 /* Reading from the prompt? */
6958 static bool input_mode = FALSE;
6960 static bool status_empty = FALSE;
6962 /* Update status and title window. */
6963 static void
6964 report(const char *msg, ...)
6965 {
6966 struct view *view = display[current_view];
6968 if (input_mode)
6969 return;
6971 if (!view) {
6972 char buf[SIZEOF_STR];
6973 va_list args;
6975 va_start(args, msg);
6976 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6977 buf[sizeof(buf) - 1] = 0;
6978 buf[sizeof(buf) - 2] = '.';
6979 buf[sizeof(buf) - 3] = '.';
6980 buf[sizeof(buf) - 4] = '.';
6981 }
6982 va_end(args);
6983 die("%s", buf);
6984 }
6986 if (!status_empty || *msg) {
6987 va_list args;
6989 va_start(args, msg);
6991 wmove(status_win, 0, 0);
6992 if (view->has_scrolled && use_scroll_status_wclear)
6993 wclear(status_win);
6994 if (*msg) {
6995 vwprintw(status_win, msg, args);
6996 status_empty = FALSE;
6997 } else {
6998 status_empty = TRUE;
6999 }
7000 wclrtoeol(status_win);
7001 wnoutrefresh(status_win);
7003 va_end(args);
7004 }
7006 update_view_title(view);
7007 }
7009 static void
7010 init_display(void)
7011 {
7012 const char *term;
7013 int x, y;
7015 /* Initialize the curses library */
7016 if (isatty(STDIN_FILENO)) {
7017 cursed = !!initscr();
7018 opt_tty = stdin;
7019 } else {
7020 /* Leave stdin and stdout alone when acting as a pager. */
7021 opt_tty = fopen("/dev/tty", "r+");
7022 if (!opt_tty)
7023 die("Failed to open /dev/tty");
7024 cursed = !!newterm(NULL, opt_tty, opt_tty);
7025 }
7027 if (!cursed)
7028 die("Failed to initialize curses");
7030 nonl(); /* Disable conversion and detect newlines from input. */
7031 cbreak(); /* Take input chars one at a time, no wait for \n */
7032 noecho(); /* Don't echo input */
7033 leaveok(stdscr, FALSE);
7035 if (has_colors())
7036 init_colors();
7038 getmaxyx(stdscr, y, x);
7039 status_win = newwin(1, 0, y - 1, 0);
7040 if (!status_win)
7041 die("Failed to create status window");
7043 /* Enable keyboard mapping */
7044 keypad(status_win, TRUE);
7045 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7047 TABSIZE = opt_tab_size;
7049 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7050 if (term && !strcmp(term, "gnome-terminal")) {
7051 /* In the gnome-terminal-emulator, the message from
7052 * scrolling up one line when impossible followed by
7053 * scrolling down one line causes corruption of the
7054 * status line. This is fixed by calling wclear. */
7055 use_scroll_status_wclear = TRUE;
7056 use_scroll_redrawwin = FALSE;
7058 } else if (term && !strcmp(term, "xrvt-xpm")) {
7059 /* No problems with full optimizations in xrvt-(unicode)
7060 * and aterm. */
7061 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7063 } else {
7064 /* When scrolling in (u)xterm the last line in the
7065 * scrolling direction will update slowly. */
7066 use_scroll_redrawwin = TRUE;
7067 use_scroll_status_wclear = FALSE;
7068 }
7069 }
7071 static int
7072 get_input(int prompt_position)
7073 {
7074 struct view *view;
7075 int i, key, cursor_y, cursor_x;
7076 bool loading = FALSE;
7078 if (prompt_position)
7079 input_mode = TRUE;
7081 while (TRUE) {
7082 foreach_view (view, i) {
7083 update_view(view);
7084 if (view_is_displayed(view) && view->has_scrolled &&
7085 use_scroll_redrawwin)
7086 redrawwin(view->win);
7087 view->has_scrolled = FALSE;
7088 if (view->pipe)
7089 loading = TRUE;
7090 }
7092 /* Update the cursor position. */
7093 if (prompt_position) {
7094 getbegyx(status_win, cursor_y, cursor_x);
7095 cursor_x = prompt_position;
7096 } else {
7097 view = display[current_view];
7098 getbegyx(view->win, cursor_y, cursor_x);
7099 cursor_x = view->width - 1;
7100 cursor_y += view->lineno - view->offset;
7101 }
7102 setsyx(cursor_y, cursor_x);
7104 /* Refresh, accept single keystroke of input */
7105 doupdate();
7106 nodelay(status_win, loading);
7107 key = wgetch(status_win);
7109 /* wgetch() with nodelay() enabled returns ERR when
7110 * there's no input. */
7111 if (key == ERR) {
7113 } else if (key == KEY_RESIZE) {
7114 int height, width;
7116 getmaxyx(stdscr, height, width);
7118 wresize(status_win, 1, width);
7119 mvwin(status_win, height - 1, 0);
7120 wnoutrefresh(status_win);
7121 resize_display();
7122 redraw_display(TRUE);
7124 } else {
7125 input_mode = FALSE;
7126 return key;
7127 }
7128 }
7129 }
7131 static char *
7132 prompt_input(const char *prompt, input_handler handler, void *data)
7133 {
7134 enum input_status status = INPUT_OK;
7135 static char buf[SIZEOF_STR];
7136 size_t pos = 0;
7138 buf[pos] = 0;
7140 while (status == INPUT_OK || status == INPUT_SKIP) {
7141 int key;
7143 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7144 wclrtoeol(status_win);
7146 key = get_input(pos + 1);
7147 switch (key) {
7148 case KEY_RETURN:
7149 case KEY_ENTER:
7150 case '\n':
7151 status = pos ? INPUT_STOP : INPUT_CANCEL;
7152 break;
7154 case KEY_BACKSPACE:
7155 if (pos > 0)
7156 buf[--pos] = 0;
7157 else
7158 status = INPUT_CANCEL;
7159 break;
7161 case KEY_ESC:
7162 status = INPUT_CANCEL;
7163 break;
7165 default:
7166 if (pos >= sizeof(buf)) {
7167 report("Input string too long");
7168 return NULL;
7169 }
7171 status = handler(data, buf, key);
7172 if (status == INPUT_OK)
7173 buf[pos++] = (char) key;
7174 }
7175 }
7177 /* Clear the status window */
7178 status_empty = FALSE;
7179 report("");
7181 if (status == INPUT_CANCEL)
7182 return NULL;
7184 buf[pos++] = 0;
7186 return buf;
7187 }
7189 static enum input_status
7190 prompt_yesno_handler(void *data, char *buf, int c)
7191 {
7192 if (c == 'y' || c == 'Y')
7193 return INPUT_STOP;
7194 if (c == 'n' || c == 'N')
7195 return INPUT_CANCEL;
7196 return INPUT_SKIP;
7197 }
7199 static bool
7200 prompt_yesno(const char *prompt)
7201 {
7202 char prompt2[SIZEOF_STR];
7204 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7205 return FALSE;
7207 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7208 }
7210 static enum input_status
7211 read_prompt_handler(void *data, char *buf, int c)
7212 {
7213 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7214 }
7216 static char *
7217 read_prompt(const char *prompt)
7218 {
7219 return prompt_input(prompt, read_prompt_handler, NULL);
7220 }
7222 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7223 {
7224 enum input_status status = INPUT_OK;
7225 int size = 0;
7227 while (items[size].text)
7228 size++;
7230 while (status == INPUT_OK) {
7231 const struct menu_item *item = &items[*selected];
7232 int key;
7233 int i;
7235 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7236 prompt, *selected + 1, size);
7237 if (item->hotkey)
7238 wprintw(status_win, "[%c] ", (char) item->hotkey);
7239 wprintw(status_win, "%s", item->text);
7240 wclrtoeol(status_win);
7242 key = get_input(COLS - 1);
7243 switch (key) {
7244 case KEY_RETURN:
7245 case KEY_ENTER:
7246 case '\n':
7247 status = INPUT_STOP;
7248 break;
7250 case KEY_LEFT:
7251 case KEY_UP:
7252 *selected = *selected - 1;
7253 if (*selected < 0)
7254 *selected = size - 1;
7255 break;
7257 case KEY_RIGHT:
7258 case KEY_DOWN:
7259 *selected = (*selected + 1) % size;
7260 break;
7262 case KEY_ESC:
7263 status = INPUT_CANCEL;
7264 break;
7266 default:
7267 for (i = 0; items[i].text; i++)
7268 if (items[i].hotkey == key) {
7269 *selected = i;
7270 status = INPUT_STOP;
7271 break;
7272 }
7273 }
7274 }
7276 /* Clear the status window */
7277 status_empty = FALSE;
7278 report("");
7280 return status != INPUT_CANCEL;
7281 }
7283 /*
7284 * Repository properties
7285 */
7287 static struct ref **refs = NULL;
7288 static size_t refs_size = 0;
7289 static struct ref *refs_head = NULL;
7291 static struct ref_list **ref_lists = NULL;
7292 static size_t ref_lists_size = 0;
7294 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7295 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7296 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7298 static int
7299 compare_refs(const void *ref1_, const void *ref2_)
7300 {
7301 const struct ref *ref1 = *(const struct ref **)ref1_;
7302 const struct ref *ref2 = *(const struct ref **)ref2_;
7304 if (ref1->tag != ref2->tag)
7305 return ref2->tag - ref1->tag;
7306 if (ref1->ltag != ref2->ltag)
7307 return ref2->ltag - ref2->ltag;
7308 if (ref1->head != ref2->head)
7309 return ref2->head - ref1->head;
7310 if (ref1->tracked != ref2->tracked)
7311 return ref2->tracked - ref1->tracked;
7312 if (ref1->remote != ref2->remote)
7313 return ref2->remote - ref1->remote;
7314 return strcmp(ref1->name, ref2->name);
7315 }
7317 static void
7318 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7319 {
7320 size_t i;
7322 for (i = 0; i < refs_size; i++)
7323 if (!visitor(data, refs[i]))
7324 break;
7325 }
7327 static struct ref *
7328 get_ref_head()
7329 {
7330 return refs_head;
7331 }
7333 static struct ref_list *
7334 get_ref_list(const char *id)
7335 {
7336 struct ref_list *list;
7337 size_t i;
7339 for (i = 0; i < ref_lists_size; i++)
7340 if (!strcmp(id, ref_lists[i]->id))
7341 return ref_lists[i];
7343 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7344 return NULL;
7345 list = calloc(1, sizeof(*list));
7346 if (!list)
7347 return NULL;
7349 for (i = 0; i < refs_size; i++) {
7350 if (!strcmp(id, refs[i]->id) &&
7351 realloc_refs_list(&list->refs, list->size, 1))
7352 list->refs[list->size++] = refs[i];
7353 }
7355 if (!list->refs) {
7356 free(list);
7357 return NULL;
7358 }
7360 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7361 ref_lists[ref_lists_size++] = list;
7362 return list;
7363 }
7365 static int
7366 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7367 {
7368 struct ref *ref = NULL;
7369 bool tag = FALSE;
7370 bool ltag = FALSE;
7371 bool remote = FALSE;
7372 bool tracked = FALSE;
7373 bool head = FALSE;
7374 int from = 0, to = refs_size - 1;
7376 if (!prefixcmp(name, "refs/tags/")) {
7377 if (!suffixcmp(name, namelen, "^{}")) {
7378 namelen -= 3;
7379 name[namelen] = 0;
7380 } else {
7381 ltag = TRUE;
7382 }
7384 tag = TRUE;
7385 namelen -= STRING_SIZE("refs/tags/");
7386 name += STRING_SIZE("refs/tags/");
7388 } else if (!prefixcmp(name, "refs/remotes/")) {
7389 remote = TRUE;
7390 namelen -= STRING_SIZE("refs/remotes/");
7391 name += STRING_SIZE("refs/remotes/");
7392 tracked = !strcmp(opt_remote, name);
7394 } else if (!prefixcmp(name, "refs/heads/")) {
7395 namelen -= STRING_SIZE("refs/heads/");
7396 name += STRING_SIZE("refs/heads/");
7397 if (!strncmp(opt_head, name, namelen))
7398 return OK;
7400 } else if (!strcmp(name, "HEAD")) {
7401 head = TRUE;
7402 if (*opt_head) {
7403 namelen = strlen(opt_head);
7404 name = opt_head;
7405 }
7406 }
7408 /* If we are reloading or it's an annotated tag, replace the
7409 * previous SHA1 with the resolved commit id; relies on the fact
7410 * git-ls-remote lists the commit id of an annotated tag right
7411 * before the commit id it points to. */
7412 while (from <= to) {
7413 size_t pos = (to + from) / 2;
7414 int cmp = strcmp(name, refs[pos]->name);
7416 if (!cmp) {
7417 ref = refs[pos];
7418 break;
7419 }
7421 if (cmp < 0)
7422 to = pos - 1;
7423 else
7424 from = pos + 1;
7425 }
7427 if (!ref) {
7428 if (!realloc_refs(&refs, refs_size, 1))
7429 return ERR;
7430 ref = calloc(1, sizeof(*ref) + namelen);
7431 if (!ref)
7432 return ERR;
7433 memmove(refs + from + 1, refs + from,
7434 (refs_size - from) * sizeof(*refs));
7435 refs[from] = ref;
7436 strncpy(ref->name, name, namelen);
7437 refs_size++;
7438 }
7440 ref->head = head;
7441 ref->tag = tag;
7442 ref->ltag = ltag;
7443 ref->remote = remote;
7444 ref->tracked = tracked;
7445 string_copy_rev(ref->id, id);
7447 if (head)
7448 refs_head = ref;
7449 return OK;
7450 }
7452 static int
7453 load_refs(void)
7454 {
7455 const char *head_argv[] = {
7456 "git", "symbolic-ref", "HEAD", NULL
7457 };
7458 static const char *ls_remote_argv[SIZEOF_ARG] = {
7459 "git", "ls-remote", opt_git_dir, NULL
7460 };
7461 static bool init = FALSE;
7462 size_t i;
7464 if (!init) {
7465 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7466 die("TIG_LS_REMOTE contains too many arguments");
7467 init = TRUE;
7468 }
7470 if (!*opt_git_dir)
7471 return OK;
7473 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7474 !prefixcmp(opt_head, "refs/heads/")) {
7475 char *offset = opt_head + STRING_SIZE("refs/heads/");
7477 memmove(opt_head, offset, strlen(offset) + 1);
7478 }
7480 refs_head = NULL;
7481 for (i = 0; i < refs_size; i++)
7482 refs[i]->id[0] = 0;
7484 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7485 return ERR;
7487 /* Update the ref lists to reflect changes. */
7488 for (i = 0; i < ref_lists_size; i++) {
7489 struct ref_list *list = ref_lists[i];
7490 size_t old, new;
7492 for (old = new = 0; old < list->size; old++)
7493 if (!strcmp(list->id, list->refs[old]->id))
7494 list->refs[new++] = list->refs[old];
7495 list->size = new;
7496 }
7498 return OK;
7499 }
7501 static void
7502 set_remote_branch(const char *name, const char *value, size_t valuelen)
7503 {
7504 if (!strcmp(name, ".remote")) {
7505 string_ncopy(opt_remote, value, valuelen);
7507 } else if (*opt_remote && !strcmp(name, ".merge")) {
7508 size_t from = strlen(opt_remote);
7510 if (!prefixcmp(value, "refs/heads/"))
7511 value += STRING_SIZE("refs/heads/");
7513 if (!string_format_from(opt_remote, &from, "/%s", value))
7514 opt_remote[0] = 0;
7515 }
7516 }
7518 static void
7519 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7520 {
7521 const char *argv[SIZEOF_ARG] = { name, "=" };
7522 int argc = 1 + (cmd == option_set_command);
7523 int error = ERR;
7525 if (!argv_from_string(argv, &argc, value))
7526 config_msg = "Too many option arguments";
7527 else
7528 error = cmd(argc, argv);
7530 if (error == ERR)
7531 warn("Option 'tig.%s': %s", name, config_msg);
7532 }
7534 static bool
7535 set_environment_variable(const char *name, const char *value)
7536 {
7537 size_t len = strlen(name) + 1 + strlen(value) + 1;
7538 char *env = malloc(len);
7540 if (env &&
7541 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7542 putenv(env) == 0)
7543 return TRUE;
7544 free(env);
7545 return FALSE;
7546 }
7548 static void
7549 set_work_tree(const char *value)
7550 {
7551 char cwd[SIZEOF_STR];
7553 if (!getcwd(cwd, sizeof(cwd)))
7554 die("Failed to get cwd path: %s", strerror(errno));
7555 if (chdir(opt_git_dir) < 0)
7556 die("Failed to chdir(%s): %s", strerror(errno));
7557 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7558 die("Failed to get git path: %s", strerror(errno));
7559 if (chdir(cwd) < 0)
7560 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7561 if (chdir(value) < 0)
7562 die("Failed to chdir(%s): %s", value, strerror(errno));
7563 if (!getcwd(cwd, sizeof(cwd)))
7564 die("Failed to get cwd path: %s", strerror(errno));
7565 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7566 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7567 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7568 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7569 opt_is_inside_work_tree = TRUE;
7570 }
7572 static int
7573 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7574 {
7575 if (!strcmp(name, "i18n.commitencoding"))
7576 string_ncopy(opt_encoding, value, valuelen);
7578 else if (!strcmp(name, "core.editor"))
7579 string_ncopy(opt_editor, value, valuelen);
7581 else if (!strcmp(name, "core.worktree"))
7582 set_work_tree(value);
7584 else if (!prefixcmp(name, "tig.color."))
7585 set_repo_config_option(name + 10, value, option_color_command);
7587 else if (!prefixcmp(name, "tig.bind."))
7588 set_repo_config_option(name + 9, value, option_bind_command);
7590 else if (!prefixcmp(name, "tig."))
7591 set_repo_config_option(name + 4, value, option_set_command);
7593 else if (*opt_head && !prefixcmp(name, "branch.") &&
7594 !strncmp(name + 7, opt_head, strlen(opt_head)))
7595 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7597 return OK;
7598 }
7600 static int
7601 load_git_config(void)
7602 {
7603 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7605 return io_run_load(config_list_argv, "=", read_repo_config_option);
7606 }
7608 static int
7609 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7610 {
7611 if (!opt_git_dir[0]) {
7612 string_ncopy(opt_git_dir, name, namelen);
7614 } else if (opt_is_inside_work_tree == -1) {
7615 /* This can be 3 different values depending on the
7616 * version of git being used. If git-rev-parse does not
7617 * understand --is-inside-work-tree it will simply echo
7618 * the option else either "true" or "false" is printed.
7619 * Default to true for the unknown case. */
7620 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7622 } else if (*name == '.') {
7623 string_ncopy(opt_cdup, name, namelen);
7625 } else {
7626 string_ncopy(opt_prefix, name, namelen);
7627 }
7629 return OK;
7630 }
7632 static int
7633 load_repo_info(void)
7634 {
7635 const char *rev_parse_argv[] = {
7636 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7637 "--show-cdup", "--show-prefix", NULL
7638 };
7640 return io_run_load(rev_parse_argv, "=", read_repo_info);
7641 }
7644 /*
7645 * Main
7646 */
7648 static const char usage[] =
7649 "tig " TIG_VERSION " (" __DATE__ ")\n"
7650 "\n"
7651 "Usage: tig [options] [revs] [--] [paths]\n"
7652 " or: tig show [options] [revs] [--] [paths]\n"
7653 " or: tig blame [rev] path\n"
7654 " or: tig status\n"
7655 " or: tig < [git command output]\n"
7656 "\n"
7657 "Options:\n"
7658 " -v, --version Show version and exit\n"
7659 " -h, --help Show help message and exit";
7661 static void __NORETURN
7662 quit(int sig)
7663 {
7664 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7665 if (cursed)
7666 endwin();
7667 exit(0);
7668 }
7670 static void __NORETURN
7671 die(const char *err, ...)
7672 {
7673 va_list args;
7675 endwin();
7677 va_start(args, err);
7678 fputs("tig: ", stderr);
7679 vfprintf(stderr, err, args);
7680 fputs("\n", stderr);
7681 va_end(args);
7683 exit(1);
7684 }
7686 static void
7687 warn(const char *msg, ...)
7688 {
7689 va_list args;
7691 va_start(args, msg);
7692 fputs("tig warning: ", stderr);
7693 vfprintf(stderr, msg, args);
7694 fputs("\n", stderr);
7695 va_end(args);
7696 }
7698 static enum request
7699 parse_options(int argc, const char *argv[])
7700 {
7701 enum request request = REQ_VIEW_MAIN;
7702 const char *subcommand;
7703 bool seen_dashdash = FALSE;
7704 /* XXX: This is vulnerable to the user overriding options
7705 * required for the main view parser. */
7706 const char *custom_argv[SIZEOF_ARG] = {
7707 "git", "log", "--no-color", "--pretty=raw", "--parents",
7708 "--topo-order", NULL
7709 };
7710 int i, j = 6;
7712 if (!isatty(STDIN_FILENO)) {
7713 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7714 return REQ_VIEW_PAGER;
7715 }
7717 if (argc <= 1)
7718 return REQ_NONE;
7720 subcommand = argv[1];
7721 if (!strcmp(subcommand, "status")) {
7722 if (argc > 2)
7723 warn("ignoring arguments after `%s'", subcommand);
7724 return REQ_VIEW_STATUS;
7726 } else if (!strcmp(subcommand, "blame")) {
7727 if (argc <= 2 || argc > 4)
7728 die("invalid number of options to blame\n\n%s", usage);
7730 i = 2;
7731 if (argc == 4) {
7732 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7733 i++;
7734 }
7736 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7737 return REQ_VIEW_BLAME;
7739 } else if (!strcmp(subcommand, "show")) {
7740 request = REQ_VIEW_DIFF;
7742 } else {
7743 subcommand = NULL;
7744 }
7746 if (subcommand) {
7747 custom_argv[1] = subcommand;
7748 j = 2;
7749 }
7751 for (i = 1 + !!subcommand; i < argc; i++) {
7752 const char *opt = argv[i];
7754 if (seen_dashdash || !strcmp(opt, "--")) {
7755 seen_dashdash = TRUE;
7757 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7758 printf("tig version %s\n", TIG_VERSION);
7759 quit(0);
7761 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7762 printf("%s\n", usage);
7763 quit(0);
7764 }
7766 custom_argv[j++] = opt;
7767 if (j >= ARRAY_SIZE(custom_argv))
7768 die("command too long");
7769 }
7771 if (!prepare_update(VIEW(request), custom_argv, NULL))
7772 die("Failed to format arguments");
7774 return request;
7775 }
7777 int
7778 main(int argc, const char *argv[])
7779 {
7780 const char *codeset = "UTF-8";
7781 enum request request = parse_options(argc, argv);
7782 struct view *view;
7783 size_t i;
7785 signal(SIGINT, quit);
7786 signal(SIGPIPE, SIG_IGN);
7788 if (setlocale(LC_ALL, "")) {
7789 codeset = nl_langinfo(CODESET);
7790 }
7792 if (load_repo_info() == ERR)
7793 die("Failed to load repo info.");
7795 if (load_options() == ERR)
7796 die("Failed to load user config.");
7798 if (load_git_config() == ERR)
7799 die("Failed to load repo config.");
7801 /* Require a git repository unless when running in pager mode. */
7802 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7803 die("Not a git repository");
7805 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7806 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7807 if (opt_iconv_in == ICONV_NONE)
7808 die("Failed to initialize character set conversion");
7809 }
7811 if (codeset && strcmp(codeset, "UTF-8")) {
7812 opt_iconv_out = iconv_open(codeset, "UTF-8");
7813 if (opt_iconv_out == ICONV_NONE)
7814 die("Failed to initialize character set conversion");
7815 }
7817 if (load_refs() == ERR)
7818 die("Failed to load refs.");
7820 foreach_view (view, i)
7821 if (!argv_from_env(view->ops->argv, view->cmd_env))
7822 die("Too many arguments in the `%s` environment variable",
7823 view->cmd_env);
7825 init_display();
7827 if (request != REQ_NONE)
7828 open_view(NULL, request, OPEN_PREPARED);
7829 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7831 while (view_driver(display[current_view], request)) {
7832 int key = get_input(0);
7834 view = display[current_view];
7835 request = get_keybinding(view->keymap, key);
7837 /* Some low-level request handling. This keeps access to
7838 * status_win restricted. */
7839 switch (request) {
7840 case REQ_NONE:
7841 report("Unknown key, press %s for help",
7842 get_key(view->keymap, REQ_VIEW_HELP));
7843 break;
7844 case REQ_PROMPT:
7845 {
7846 char *cmd = read_prompt(":");
7848 if (cmd && isdigit(*cmd)) {
7849 int lineno = view->lineno + 1;
7851 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7852 select_view_line(view, lineno - 1);
7853 report("");
7854 } else {
7855 report("Unable to parse '%s' as a line number", cmd);
7856 }
7858 } else if (cmd) {
7859 struct view *next = VIEW(REQ_VIEW_PAGER);
7860 const char *argv[SIZEOF_ARG] = { "git" };
7861 int argc = 1;
7863 /* When running random commands, initially show the
7864 * command in the title. However, it maybe later be
7865 * overwritten if a commit line is selected. */
7866 string_ncopy(next->ref, cmd, strlen(cmd));
7868 if (!argv_from_string(argv, &argc, cmd)) {
7869 report("Too many arguments");
7870 } else if (!prepare_update(next, argv, NULL)) {
7871 report("Failed to format command");
7872 } else {
7873 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7874 }
7875 }
7877 request = REQ_NONE;
7878 break;
7879 }
7880 case REQ_SEARCH:
7881 case REQ_SEARCH_BACK:
7882 {
7883 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7884 char *search = read_prompt(prompt);
7886 if (search)
7887 string_ncopy(opt_search, search, strlen(search));
7888 else if (*opt_search)
7889 request = request == REQ_SEARCH ?
7890 REQ_FIND_NEXT :
7891 REQ_FIND_PREV;
7892 else
7893 request = REQ_NONE;
7894 break;
7895 }
7896 default:
7897 break;
7898 }
7899 }
7901 quit(0);
7903 return 0;
7904 }