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[])
686 {
687 int argc;
689 for (argc = 0; src[argc]; argc++)
690 if (!(dst[argc] = strdup(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 int pipe; /* Pipe end for reading or writing. */
711 pid_t pid; /* PID of spawned process. */
712 int error; /* Error status. */
713 char *buf; /* Read buffer. */
714 size_t bufalloc; /* Allocated buffer size. */
715 size_t bufsize; /* Buffer content size. */
716 char *bufpos; /* Current buffer position. */
717 unsigned int eof:1; /* Has end of file been reached. */
718 };
720 static void
721 io_init(struct io *io)
722 {
723 memset(io, 0, sizeof(*io));
724 io->pipe = -1;
725 }
727 static bool
728 io_open(struct io *io, const char *fmt, ...)
729 {
730 char name[SIZEOF_STR] = "";
731 bool fits;
732 va_list args;
734 io_init(io);
736 va_start(args, fmt);
737 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
738 va_end(args);
740 if (!fits) {
741 io->error = ENAMETOOLONG;
742 return FALSE;
743 }
744 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
745 if (io->pipe == -1)
746 io->error = errno;
747 return io->pipe != -1;
748 }
750 static bool
751 io_kill(struct io *io)
752 {
753 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
754 }
756 static bool
757 io_done(struct io *io)
758 {
759 pid_t pid = io->pid;
761 if (io->pipe != -1)
762 close(io->pipe);
763 free(io->buf);
764 io_init(io);
766 while (pid > 0) {
767 int status;
768 pid_t waiting = waitpid(pid, &status, 0);
770 if (waiting < 0) {
771 if (errno == EINTR)
772 continue;
773 io->error = errno;
774 return FALSE;
775 }
777 return waiting == pid &&
778 !WIFSIGNALED(status) &&
779 WIFEXITED(status) &&
780 !WEXITSTATUS(status);
781 }
783 return TRUE;
784 }
786 static bool
787 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
788 {
789 int pipefds[2] = { -1, -1 };
790 va_list args;
792 io_init(io);
794 if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
795 io->error = errno;
796 return FALSE;
797 } else if (type == IO_AP) {
798 va_start(args, argv);
799 pipefds[1] = va_arg(args, int);
800 va_end(args);
801 }
803 if ((io->pid = fork())) {
804 if (io->pid == -1)
805 io->error = errno;
806 if (pipefds[!(type == IO_WR)] != -1)
807 close(pipefds[!(type == IO_WR)]);
808 if (io->pid != -1) {
809 io->pipe = pipefds[!!(type == IO_WR)];
810 return TRUE;
811 }
813 } else {
814 if (type != IO_FG) {
815 int devnull = open("/dev/null", O_RDWR);
816 int readfd = type == IO_WR ? pipefds[0] : devnull;
817 int writefd = (type == IO_RD || type == IO_AP)
818 ? pipefds[1] : devnull;
820 dup2(readfd, STDIN_FILENO);
821 dup2(writefd, STDOUT_FILENO);
822 dup2(devnull, STDERR_FILENO);
824 close(devnull);
825 if (pipefds[0] != -1)
826 close(pipefds[0]);
827 if (pipefds[1] != -1)
828 close(pipefds[1]);
829 }
831 if (dir && *dir && chdir(dir) == -1)
832 exit(errno);
834 execvp(argv[0], (char *const*) argv);
835 exit(errno);
836 }
838 if (pipefds[!!(type == IO_WR)] != -1)
839 close(pipefds[!!(type == IO_WR)]);
840 return FALSE;
841 }
843 static bool
844 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
845 {
846 struct io io;
848 return io_run(&io, type, dir, argv, fd) && io_done(&io);
849 }
851 static bool
852 io_run_bg(const char **argv)
853 {
854 return io_complete(IO_BG, argv, NULL, -1);
855 }
857 static bool
858 io_run_fg(const char **argv, const char *dir)
859 {
860 return io_complete(IO_FG, argv, dir, -1);
861 }
863 static bool
864 io_run_append(const char **argv, int fd)
865 {
866 return io_complete(IO_AP, argv, NULL, fd);
867 }
869 static bool
870 io_eof(struct io *io)
871 {
872 return io->eof;
873 }
875 static int
876 io_error(struct io *io)
877 {
878 return io->error;
879 }
881 static char *
882 io_strerror(struct io *io)
883 {
884 return strerror(io->error);
885 }
887 static bool
888 io_can_read(struct io *io)
889 {
890 struct timeval tv = { 0, 500 };
891 fd_set fds;
893 FD_ZERO(&fds);
894 FD_SET(io->pipe, &fds);
896 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
897 }
899 static ssize_t
900 io_read(struct io *io, void *buf, size_t bufsize)
901 {
902 do {
903 ssize_t readsize = read(io->pipe, buf, bufsize);
905 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
906 continue;
907 else if (readsize == -1)
908 io->error = errno;
909 else if (readsize == 0)
910 io->eof = 1;
911 return readsize;
912 } while (1);
913 }
915 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
917 static char *
918 io_get(struct io *io, int c, bool can_read)
919 {
920 char *eol;
921 ssize_t readsize;
923 while (TRUE) {
924 if (io->bufsize > 0) {
925 eol = memchr(io->bufpos, c, io->bufsize);
926 if (eol) {
927 char *line = io->bufpos;
929 *eol = 0;
930 io->bufpos = eol + 1;
931 io->bufsize -= io->bufpos - line;
932 return line;
933 }
934 }
936 if (io_eof(io)) {
937 if (io->bufsize) {
938 io->bufpos[io->bufsize] = 0;
939 io->bufsize = 0;
940 return io->bufpos;
941 }
942 return NULL;
943 }
945 if (!can_read)
946 return NULL;
948 if (io->bufsize > 0 && io->bufpos > io->buf)
949 memmove(io->buf, io->bufpos, io->bufsize);
951 if (io->bufalloc == io->bufsize) {
952 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
953 return NULL;
954 io->bufalloc += BUFSIZ;
955 }
957 io->bufpos = io->buf;
958 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
959 if (io_error(io))
960 return NULL;
961 io->bufsize += readsize;
962 }
963 }
965 static bool
966 io_write(struct io *io, const void *buf, size_t bufsize)
967 {
968 size_t written = 0;
970 while (!io_error(io) && written < bufsize) {
971 ssize_t size;
973 size = write(io->pipe, buf + written, bufsize - written);
974 if (size < 0 && (errno == EAGAIN || errno == EINTR))
975 continue;
976 else if (size == -1)
977 io->error = errno;
978 else
979 written += size;
980 }
982 return written == bufsize;
983 }
985 static bool
986 io_read_buf(struct io *io, char buf[], size_t bufsize)
987 {
988 char *result = io_get(io, '\n', TRUE);
990 if (result) {
991 result = chomp_string(result);
992 string_ncopy_do(buf, bufsize, result, strlen(result));
993 }
995 return io_done(io) && result;
996 }
998 static bool
999 io_run_buf(const char **argv, char buf[], size_t bufsize)
1000 {
1001 struct io io;
1003 return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1004 }
1006 static int
1007 io_load(struct io *io, const char *separators,
1008 int (*read_property)(char *, size_t, char *, size_t))
1009 {
1010 char *name;
1011 int state = OK;
1013 while (state == OK && (name = io_get(io, '\n', TRUE))) {
1014 char *value;
1015 size_t namelen;
1016 size_t valuelen;
1018 name = chomp_string(name);
1019 namelen = strcspn(name, separators);
1021 if (name[namelen]) {
1022 name[namelen] = 0;
1023 value = chomp_string(name + namelen + 1);
1024 valuelen = strlen(value);
1026 } else {
1027 value = "";
1028 valuelen = 0;
1029 }
1031 state = read_property(name, namelen, value, valuelen);
1032 }
1034 if (state != ERR && io_error(io))
1035 state = ERR;
1036 io_done(io);
1038 return state;
1039 }
1041 static int
1042 io_run_load(const char **argv, const char *separators,
1043 int (*read_property)(char *, size_t, char *, size_t))
1044 {
1045 struct io io;
1047 if (!io_run(&io, IO_RD, NULL, argv))
1048 return ERR;
1049 return io_load(&io, separators, read_property);
1050 }
1053 /*
1054 * User requests
1055 */
1057 #define REQ_INFO \
1058 /* XXX: Keep the view request first and in sync with views[]. */ \
1059 REQ_GROUP("View switching") \
1060 REQ_(VIEW_MAIN, "Show main view"), \
1061 REQ_(VIEW_DIFF, "Show diff view"), \
1062 REQ_(VIEW_LOG, "Show log view"), \
1063 REQ_(VIEW_TREE, "Show tree view"), \
1064 REQ_(VIEW_BLOB, "Show blob view"), \
1065 REQ_(VIEW_BLAME, "Show blame view"), \
1066 REQ_(VIEW_BRANCH, "Show branch view"), \
1067 REQ_(VIEW_HELP, "Show help page"), \
1068 REQ_(VIEW_PAGER, "Show pager view"), \
1069 REQ_(VIEW_STATUS, "Show status view"), \
1070 REQ_(VIEW_STAGE, "Show stage view"), \
1071 \
1072 REQ_GROUP("View manipulation") \
1073 REQ_(ENTER, "Enter current line and scroll"), \
1074 REQ_(NEXT, "Move to next"), \
1075 REQ_(PREVIOUS, "Move to previous"), \
1076 REQ_(PARENT, "Move to parent"), \
1077 REQ_(VIEW_NEXT, "Move focus to next view"), \
1078 REQ_(REFRESH, "Reload and refresh"), \
1079 REQ_(MAXIMIZE, "Maximize the current view"), \
1080 REQ_(VIEW_CLOSE, "Close the current view"), \
1081 REQ_(QUIT, "Close all views and quit"), \
1082 \
1083 REQ_GROUP("View specific requests") \
1084 REQ_(STATUS_UPDATE, "Update file status"), \
1085 REQ_(STATUS_REVERT, "Revert file changes"), \
1086 REQ_(STATUS_MERGE, "Merge file using external tool"), \
1087 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
1088 \
1089 REQ_GROUP("Cursor navigation") \
1090 REQ_(MOVE_UP, "Move cursor one line up"), \
1091 REQ_(MOVE_DOWN, "Move cursor one line down"), \
1092 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
1093 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
1094 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
1095 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
1096 \
1097 REQ_GROUP("Scrolling") \
1098 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
1099 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
1100 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
1101 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
1102 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
1103 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
1104 \
1105 REQ_GROUP("Searching") \
1106 REQ_(SEARCH, "Search the view"), \
1107 REQ_(SEARCH_BACK, "Search backwards in the view"), \
1108 REQ_(FIND_NEXT, "Find next search match"), \
1109 REQ_(FIND_PREV, "Find previous search match"), \
1110 \
1111 REQ_GROUP("Option manipulation") \
1112 REQ_(OPTIONS, "Open option menu"), \
1113 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
1114 REQ_(TOGGLE_DATE, "Toggle date display"), \
1115 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1116 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
1117 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
1118 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
1119 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1120 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1121 \
1122 REQ_GROUP("Misc") \
1123 REQ_(PROMPT, "Bring up the prompt"), \
1124 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1125 REQ_(SHOW_VERSION, "Show version information"), \
1126 REQ_(STOP_LOADING, "Stop all loading views"), \
1127 REQ_(EDIT, "Open in editor"), \
1128 REQ_(NONE, "Do nothing")
1131 /* User action requests. */
1132 enum request {
1133 #define REQ_GROUP(help)
1134 #define REQ_(req, help) REQ_##req
1136 /* Offset all requests to avoid conflicts with ncurses getch values. */
1137 REQ_UNKNOWN = KEY_MAX + 1,
1138 REQ_OFFSET,
1139 REQ_INFO
1141 #undef REQ_GROUP
1142 #undef REQ_
1143 };
1145 struct request_info {
1146 enum request request;
1147 const char *name;
1148 int namelen;
1149 const char *help;
1150 };
1152 static const struct request_info req_info[] = {
1153 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1154 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1155 REQ_INFO
1156 #undef REQ_GROUP
1157 #undef REQ_
1158 };
1160 static enum request
1161 get_request(const char *name)
1162 {
1163 int namelen = strlen(name);
1164 int i;
1166 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1167 if (enum_equals(req_info[i], name, namelen))
1168 return req_info[i].request;
1170 return REQ_UNKNOWN;
1171 }
1174 /*
1175 * Options
1176 */
1178 /* Option and state variables. */
1179 static enum date opt_date = DATE_DEFAULT;
1180 static enum author opt_author = AUTHOR_DEFAULT;
1181 static bool opt_line_number = FALSE;
1182 static bool opt_line_graphics = TRUE;
1183 static bool opt_rev_graph = FALSE;
1184 static bool opt_show_refs = TRUE;
1185 static int opt_num_interval = 5;
1186 static double opt_hscroll = 0.50;
1187 static double opt_scale_split_view = 2.0 / 3.0;
1188 static int opt_tab_size = 8;
1189 static int opt_author_cols = AUTHOR_COLS;
1190 static char opt_path[SIZEOF_STR] = "";
1191 static char opt_file[SIZEOF_STR] = "";
1192 static char opt_ref[SIZEOF_REF] = "";
1193 static char opt_head[SIZEOF_REF] = "";
1194 static char opt_remote[SIZEOF_REF] = "";
1195 static char opt_encoding[20] = "UTF-8";
1196 static iconv_t opt_iconv_in = ICONV_NONE;
1197 static iconv_t opt_iconv_out = ICONV_NONE;
1198 static char opt_search[SIZEOF_STR] = "";
1199 static char opt_cdup[SIZEOF_STR] = "";
1200 static char opt_prefix[SIZEOF_STR] = "";
1201 static char opt_git_dir[SIZEOF_STR] = "";
1202 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1203 static char opt_editor[SIZEOF_STR] = "";
1204 static FILE *opt_tty = NULL;
1206 #define is_initial_commit() (!get_ref_head())
1207 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1210 /*
1211 * Line-oriented content detection.
1212 */
1214 #define LINE_INFO \
1215 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1216 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1217 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1218 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1219 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1220 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1221 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1222 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1223 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1224 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1225 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1226 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1227 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1228 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1229 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1230 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1231 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1232 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1233 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1234 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1235 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1236 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1237 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1238 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1239 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1240 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1241 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1242 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1243 LINE(TESTED, " Tested-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1244 LINE(REVIEWED, " Reviewed-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1245 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1246 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1247 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1248 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1249 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1250 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1251 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1252 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1253 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1254 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1255 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1256 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1257 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1258 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1259 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1260 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1261 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1262 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1263 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1264 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1265 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1266 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1267 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1268 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1269 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1270 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1271 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1272 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1273 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1275 enum line_type {
1276 #define LINE(type, line, fg, bg, attr) \
1277 LINE_##type
1278 LINE_INFO,
1279 LINE_NONE
1280 #undef LINE
1281 };
1283 struct line_info {
1284 const char *name; /* Option name. */
1285 int namelen; /* Size of option name. */
1286 const char *line; /* The start of line to match. */
1287 int linelen; /* Size of string to match. */
1288 int fg, bg, attr; /* Color and text attributes for the lines. */
1289 };
1291 static struct line_info line_info[] = {
1292 #define LINE(type, line, fg, bg, attr) \
1293 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1294 LINE_INFO
1295 #undef LINE
1296 };
1298 static enum line_type
1299 get_line_type(const char *line)
1300 {
1301 int linelen = strlen(line);
1302 enum line_type type;
1304 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1305 /* Case insensitive search matches Signed-off-by lines better. */
1306 if (linelen >= line_info[type].linelen &&
1307 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1308 return type;
1310 return LINE_DEFAULT;
1311 }
1313 static inline int
1314 get_line_attr(enum line_type type)
1315 {
1316 assert(type < ARRAY_SIZE(line_info));
1317 return COLOR_PAIR(type) | line_info[type].attr;
1318 }
1320 static struct line_info *
1321 get_line_info(const char *name)
1322 {
1323 size_t namelen = strlen(name);
1324 enum line_type type;
1326 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1327 if (enum_equals(line_info[type], name, namelen))
1328 return &line_info[type];
1330 return NULL;
1331 }
1333 static void
1334 init_colors(void)
1335 {
1336 int default_bg = line_info[LINE_DEFAULT].bg;
1337 int default_fg = line_info[LINE_DEFAULT].fg;
1338 enum line_type type;
1340 start_color();
1342 if (assume_default_colors(default_fg, default_bg) == ERR) {
1343 default_bg = COLOR_BLACK;
1344 default_fg = COLOR_WHITE;
1345 }
1347 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1348 struct line_info *info = &line_info[type];
1349 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1350 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1352 init_pair(type, fg, bg);
1353 }
1354 }
1356 struct line {
1357 enum line_type type;
1359 /* State flags */
1360 unsigned int selected:1;
1361 unsigned int dirty:1;
1362 unsigned int cleareol:1;
1363 unsigned int other:16;
1365 void *data; /* User data */
1366 };
1369 /*
1370 * Keys
1371 */
1373 struct keybinding {
1374 int alias;
1375 enum request request;
1376 };
1378 static struct keybinding default_keybindings[] = {
1379 /* View switching */
1380 { 'm', REQ_VIEW_MAIN },
1381 { 'd', REQ_VIEW_DIFF },
1382 { 'l', REQ_VIEW_LOG },
1383 { 't', REQ_VIEW_TREE },
1384 { 'f', REQ_VIEW_BLOB },
1385 { 'B', REQ_VIEW_BLAME },
1386 { 'H', REQ_VIEW_BRANCH },
1387 { 'p', REQ_VIEW_PAGER },
1388 { 'h', REQ_VIEW_HELP },
1389 { 'S', REQ_VIEW_STATUS },
1390 { 'c', REQ_VIEW_STAGE },
1392 /* View manipulation */
1393 { 'q', REQ_VIEW_CLOSE },
1394 { KEY_TAB, REQ_VIEW_NEXT },
1395 { KEY_RETURN, REQ_ENTER },
1396 { KEY_UP, REQ_PREVIOUS },
1397 { KEY_DOWN, REQ_NEXT },
1398 { 'R', REQ_REFRESH },
1399 { KEY_F(5), REQ_REFRESH },
1400 { 'O', REQ_MAXIMIZE },
1402 /* Cursor navigation */
1403 { 'k', REQ_MOVE_UP },
1404 { 'j', REQ_MOVE_DOWN },
1405 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1406 { KEY_END, REQ_MOVE_LAST_LINE },
1407 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1408 { ' ', REQ_MOVE_PAGE_DOWN },
1409 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1410 { 'b', REQ_MOVE_PAGE_UP },
1411 { '-', REQ_MOVE_PAGE_UP },
1413 /* Scrolling */
1414 { KEY_LEFT, REQ_SCROLL_LEFT },
1415 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1416 { KEY_IC, REQ_SCROLL_LINE_UP },
1417 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1418 { 'w', REQ_SCROLL_PAGE_UP },
1419 { 's', REQ_SCROLL_PAGE_DOWN },
1421 /* Searching */
1422 { '/', REQ_SEARCH },
1423 { '?', REQ_SEARCH_BACK },
1424 { 'n', REQ_FIND_NEXT },
1425 { 'N', REQ_FIND_PREV },
1427 /* Misc */
1428 { 'Q', REQ_QUIT },
1429 { 'z', REQ_STOP_LOADING },
1430 { 'v', REQ_SHOW_VERSION },
1431 { 'r', REQ_SCREEN_REDRAW },
1432 { 'o', REQ_OPTIONS },
1433 { '.', REQ_TOGGLE_LINENO },
1434 { 'D', REQ_TOGGLE_DATE },
1435 { 'A', REQ_TOGGLE_AUTHOR },
1436 { 'g', REQ_TOGGLE_REV_GRAPH },
1437 { 'F', REQ_TOGGLE_REFS },
1438 { 'I', REQ_TOGGLE_SORT_ORDER },
1439 { 'i', REQ_TOGGLE_SORT_FIELD },
1440 { ':', REQ_PROMPT },
1441 { 'u', REQ_STATUS_UPDATE },
1442 { '!', REQ_STATUS_REVERT },
1443 { 'M', REQ_STATUS_MERGE },
1444 { '@', REQ_STAGE_NEXT },
1445 { ',', REQ_PARENT },
1446 { 'e', REQ_EDIT },
1447 };
1449 #define KEYMAP_INFO \
1450 KEYMAP_(GENERIC), \
1451 KEYMAP_(MAIN), \
1452 KEYMAP_(DIFF), \
1453 KEYMAP_(LOG), \
1454 KEYMAP_(TREE), \
1455 KEYMAP_(BLOB), \
1456 KEYMAP_(BLAME), \
1457 KEYMAP_(BRANCH), \
1458 KEYMAP_(PAGER), \
1459 KEYMAP_(HELP), \
1460 KEYMAP_(STATUS), \
1461 KEYMAP_(STAGE)
1463 enum keymap {
1464 #define KEYMAP_(name) KEYMAP_##name
1465 KEYMAP_INFO
1466 #undef KEYMAP_
1467 };
1469 static const struct enum_map keymap_table[] = {
1470 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1471 KEYMAP_INFO
1472 #undef KEYMAP_
1473 };
1475 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1477 struct keybinding_table {
1478 struct keybinding *data;
1479 size_t size;
1480 };
1482 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1484 static void
1485 add_keybinding(enum keymap keymap, enum request request, int key)
1486 {
1487 struct keybinding_table *table = &keybindings[keymap];
1488 size_t i;
1490 for (i = 0; i < keybindings[keymap].size; i++) {
1491 if (keybindings[keymap].data[i].alias == key) {
1492 keybindings[keymap].data[i].request = request;
1493 return;
1494 }
1495 }
1497 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1498 if (!table->data)
1499 die("Failed to allocate keybinding");
1500 table->data[table->size].alias = key;
1501 table->data[table->size++].request = request;
1503 if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1504 int i;
1506 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1507 if (default_keybindings[i].alias == key)
1508 default_keybindings[i].request = REQ_NONE;
1509 }
1510 }
1512 /* Looks for a key binding first in the given map, then in the generic map, and
1513 * lastly in the default keybindings. */
1514 static enum request
1515 get_keybinding(enum keymap keymap, int key)
1516 {
1517 size_t i;
1519 for (i = 0; i < keybindings[keymap].size; i++)
1520 if (keybindings[keymap].data[i].alias == key)
1521 return keybindings[keymap].data[i].request;
1523 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1524 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1525 return keybindings[KEYMAP_GENERIC].data[i].request;
1527 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1528 if (default_keybindings[i].alias == key)
1529 return default_keybindings[i].request;
1531 return (enum request) key;
1532 }
1535 struct key {
1536 const char *name;
1537 int value;
1538 };
1540 static const struct key key_table[] = {
1541 { "Enter", KEY_RETURN },
1542 { "Space", ' ' },
1543 { "Backspace", KEY_BACKSPACE },
1544 { "Tab", KEY_TAB },
1545 { "Escape", KEY_ESC },
1546 { "Left", KEY_LEFT },
1547 { "Right", KEY_RIGHT },
1548 { "Up", KEY_UP },
1549 { "Down", KEY_DOWN },
1550 { "Insert", KEY_IC },
1551 { "Delete", KEY_DC },
1552 { "Hash", '#' },
1553 { "Home", KEY_HOME },
1554 { "End", KEY_END },
1555 { "PageUp", KEY_PPAGE },
1556 { "PageDown", KEY_NPAGE },
1557 { "F1", KEY_F(1) },
1558 { "F2", KEY_F(2) },
1559 { "F3", KEY_F(3) },
1560 { "F4", KEY_F(4) },
1561 { "F5", KEY_F(5) },
1562 { "F6", KEY_F(6) },
1563 { "F7", KEY_F(7) },
1564 { "F8", KEY_F(8) },
1565 { "F9", KEY_F(9) },
1566 { "F10", KEY_F(10) },
1567 { "F11", KEY_F(11) },
1568 { "F12", KEY_F(12) },
1569 };
1571 static int
1572 get_key_value(const char *name)
1573 {
1574 int i;
1576 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1577 if (!strcasecmp(key_table[i].name, name))
1578 return key_table[i].value;
1580 if (strlen(name) == 1 && isprint(*name))
1581 return (int) *name;
1583 return ERR;
1584 }
1586 static const char *
1587 get_key_name(int key_value)
1588 {
1589 static char key_char[] = "'X'";
1590 const char *seq = NULL;
1591 int key;
1593 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1594 if (key_table[key].value == key_value)
1595 seq = key_table[key].name;
1597 if (seq == NULL &&
1598 key_value < 127 &&
1599 isprint(key_value)) {
1600 key_char[1] = (char) key_value;
1601 seq = key_char;
1602 }
1604 return seq ? seq : "(no key)";
1605 }
1607 static bool
1608 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1609 {
1610 const char *sep = *pos > 0 ? ", " : "";
1611 const char *keyname = get_key_name(keybinding->alias);
1613 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1614 }
1616 static bool
1617 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1618 enum keymap keymap, bool all)
1619 {
1620 int i;
1622 for (i = 0; i < keybindings[keymap].size; i++) {
1623 if (keybindings[keymap].data[i].request == request) {
1624 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1625 return FALSE;
1626 if (!all)
1627 break;
1628 }
1629 }
1631 return TRUE;
1632 }
1634 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1636 static const char *
1637 get_keys(enum keymap keymap, enum request request, bool all)
1638 {
1639 static char buf[BUFSIZ];
1640 size_t pos = 0;
1641 int i;
1643 buf[pos] = 0;
1645 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1646 return "Too many keybindings!";
1647 if (pos > 0 && !all)
1648 return buf;
1650 if (keymap != KEYMAP_GENERIC) {
1651 /* Only the generic keymap includes the default keybindings when
1652 * listing all keys. */
1653 if (all)
1654 return buf;
1656 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1657 return "Too many keybindings!";
1658 if (pos)
1659 return buf;
1660 }
1662 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1663 if (default_keybindings[i].request == request) {
1664 if (!append_key(buf, &pos, &default_keybindings[i]))
1665 return "Too many keybindings!";
1666 if (!all)
1667 return buf;
1668 }
1669 }
1671 return buf;
1672 }
1674 struct run_request {
1675 enum keymap keymap;
1676 int key;
1677 const char *argv[SIZEOF_ARG];
1678 };
1680 static struct run_request *run_request;
1681 static size_t run_requests;
1683 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1685 static enum request
1686 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1687 {
1688 struct run_request *req;
1690 if (argc >= ARRAY_SIZE(req->argv) - 1)
1691 return REQ_NONE;
1693 if (!realloc_run_requests(&run_request, run_requests, 1))
1694 return REQ_NONE;
1696 req = &run_request[run_requests];
1697 req->keymap = keymap;
1698 req->key = key;
1699 req->argv[0] = NULL;
1701 if (!argv_copy(req->argv, argv))
1702 return REQ_NONE;
1704 return REQ_NONE + ++run_requests;
1705 }
1707 static struct run_request *
1708 get_run_request(enum request request)
1709 {
1710 if (request <= REQ_NONE)
1711 return NULL;
1712 return &run_request[request - REQ_NONE - 1];
1713 }
1715 static void
1716 add_builtin_run_requests(void)
1717 {
1718 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1719 const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1720 const char *commit[] = { "git", "commit", NULL };
1721 const char *gc[] = { "git", "gc", NULL };
1722 struct {
1723 enum keymap keymap;
1724 int key;
1725 int argc;
1726 const char **argv;
1727 } reqs[] = {
1728 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1729 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1730 { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout },
1731 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1732 };
1733 int i;
1735 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1736 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1738 if (req != reqs[i].key)
1739 continue;
1740 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1741 if (req != REQ_NONE)
1742 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1743 }
1744 }
1746 /*
1747 * User config file handling.
1748 */
1750 static int config_lineno;
1751 static bool config_errors;
1752 static const char *config_msg;
1754 static const struct enum_map color_map[] = {
1755 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1756 COLOR_MAP(DEFAULT),
1757 COLOR_MAP(BLACK),
1758 COLOR_MAP(BLUE),
1759 COLOR_MAP(CYAN),
1760 COLOR_MAP(GREEN),
1761 COLOR_MAP(MAGENTA),
1762 COLOR_MAP(RED),
1763 COLOR_MAP(WHITE),
1764 COLOR_MAP(YELLOW),
1765 };
1767 static const struct enum_map attr_map[] = {
1768 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1769 ATTR_MAP(NORMAL),
1770 ATTR_MAP(BLINK),
1771 ATTR_MAP(BOLD),
1772 ATTR_MAP(DIM),
1773 ATTR_MAP(REVERSE),
1774 ATTR_MAP(STANDOUT),
1775 ATTR_MAP(UNDERLINE),
1776 };
1778 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1780 static int parse_step(double *opt, const char *arg)
1781 {
1782 *opt = atoi(arg);
1783 if (!strchr(arg, '%'))
1784 return OK;
1786 /* "Shift down" so 100% and 1 does not conflict. */
1787 *opt = (*opt - 1) / 100;
1788 if (*opt >= 1.0) {
1789 *opt = 0.99;
1790 config_msg = "Step value larger than 100%";
1791 return ERR;
1792 }
1793 if (*opt < 0.0) {
1794 *opt = 1;
1795 config_msg = "Invalid step value";
1796 return ERR;
1797 }
1798 return OK;
1799 }
1801 static int
1802 parse_int(int *opt, const char *arg, int min, int max)
1803 {
1804 int value = atoi(arg);
1806 if (min <= value && value <= max) {
1807 *opt = value;
1808 return OK;
1809 }
1811 config_msg = "Integer value out of bound";
1812 return ERR;
1813 }
1815 static bool
1816 set_color(int *color, const char *name)
1817 {
1818 if (map_enum(color, color_map, name))
1819 return TRUE;
1820 if (!prefixcmp(name, "color"))
1821 return parse_int(color, name + 5, 0, 255) == OK;
1822 return FALSE;
1823 }
1825 /* Wants: object fgcolor bgcolor [attribute] */
1826 static int
1827 option_color_command(int argc, const char *argv[])
1828 {
1829 struct line_info *info;
1831 if (argc < 3) {
1832 config_msg = "Wrong number of arguments given to color command";
1833 return ERR;
1834 }
1836 info = get_line_info(argv[0]);
1837 if (!info) {
1838 static const struct enum_map obsolete[] = {
1839 ENUM_MAP("main-delim", LINE_DELIMITER),
1840 ENUM_MAP("main-date", LINE_DATE),
1841 ENUM_MAP("main-author", LINE_AUTHOR),
1842 };
1843 int index;
1845 if (!map_enum(&index, obsolete, argv[0])) {
1846 config_msg = "Unknown color name";
1847 return ERR;
1848 }
1849 info = &line_info[index];
1850 }
1852 if (!set_color(&info->fg, argv[1]) ||
1853 !set_color(&info->bg, argv[2])) {
1854 config_msg = "Unknown color";
1855 return ERR;
1856 }
1858 info->attr = 0;
1859 while (argc-- > 3) {
1860 int attr;
1862 if (!set_attribute(&attr, argv[argc])) {
1863 config_msg = "Unknown attribute";
1864 return ERR;
1865 }
1866 info->attr |= attr;
1867 }
1869 return OK;
1870 }
1872 static int parse_bool(bool *opt, const char *arg)
1873 {
1874 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1875 ? TRUE : FALSE;
1876 return OK;
1877 }
1879 static int parse_enum_do(unsigned int *opt, const char *arg,
1880 const struct enum_map *map, size_t map_size)
1881 {
1882 bool is_true;
1884 assert(map_size > 1);
1886 if (map_enum_do(map, map_size, (int *) opt, arg))
1887 return OK;
1889 if (parse_bool(&is_true, arg) != OK)
1890 return ERR;
1892 *opt = is_true ? map[1].value : map[0].value;
1893 return OK;
1894 }
1896 #define parse_enum(opt, arg, map) \
1897 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1899 static int
1900 parse_string(char *opt, const char *arg, size_t optsize)
1901 {
1902 int arglen = strlen(arg);
1904 switch (arg[0]) {
1905 case '\"':
1906 case '\'':
1907 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1908 config_msg = "Unmatched quotation";
1909 return ERR;
1910 }
1911 arg += 1; arglen -= 2;
1912 default:
1913 string_ncopy_do(opt, optsize, arg, arglen);
1914 return OK;
1915 }
1916 }
1918 /* Wants: name = value */
1919 static int
1920 option_set_command(int argc, const char *argv[])
1921 {
1922 if (argc != 3) {
1923 config_msg = "Wrong number of arguments given to set command";
1924 return ERR;
1925 }
1927 if (strcmp(argv[1], "=")) {
1928 config_msg = "No value assigned";
1929 return ERR;
1930 }
1932 if (!strcmp(argv[0], "show-author"))
1933 return parse_enum(&opt_author, argv[2], author_map);
1935 if (!strcmp(argv[0], "show-date"))
1936 return parse_enum(&opt_date, argv[2], date_map);
1938 if (!strcmp(argv[0], "show-rev-graph"))
1939 return parse_bool(&opt_rev_graph, argv[2]);
1941 if (!strcmp(argv[0], "show-refs"))
1942 return parse_bool(&opt_show_refs, argv[2]);
1944 if (!strcmp(argv[0], "show-line-numbers"))
1945 return parse_bool(&opt_line_number, argv[2]);
1947 if (!strcmp(argv[0], "line-graphics"))
1948 return parse_bool(&opt_line_graphics, argv[2]);
1950 if (!strcmp(argv[0], "line-number-interval"))
1951 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1953 if (!strcmp(argv[0], "author-width"))
1954 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1956 if (!strcmp(argv[0], "horizontal-scroll"))
1957 return parse_step(&opt_hscroll, argv[2]);
1959 if (!strcmp(argv[0], "split-view-height"))
1960 return parse_step(&opt_scale_split_view, argv[2]);
1962 if (!strcmp(argv[0], "tab-size"))
1963 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1965 if (!strcmp(argv[0], "commit-encoding"))
1966 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1968 config_msg = "Unknown variable name";
1969 return ERR;
1970 }
1972 /* Wants: mode request key */
1973 static int
1974 option_bind_command(int argc, const char *argv[])
1975 {
1976 enum request request;
1977 int keymap = -1;
1978 int key;
1980 if (argc < 3) {
1981 config_msg = "Wrong number of arguments given to bind command";
1982 return ERR;
1983 }
1985 if (!set_keymap(&keymap, argv[0])) {
1986 config_msg = "Unknown key map";
1987 return ERR;
1988 }
1990 key = get_key_value(argv[1]);
1991 if (key == ERR) {
1992 config_msg = "Unknown key";
1993 return ERR;
1994 }
1996 request = get_request(argv[2]);
1997 if (request == REQ_UNKNOWN) {
1998 static const struct enum_map obsolete[] = {
1999 ENUM_MAP("cherry-pick", REQ_NONE),
2000 ENUM_MAP("screen-resize", REQ_NONE),
2001 ENUM_MAP("tree-parent", REQ_PARENT),
2002 };
2003 int alias;
2005 if (map_enum(&alias, obsolete, argv[2])) {
2006 if (alias != REQ_NONE)
2007 add_keybinding(keymap, alias, key);
2008 config_msg = "Obsolete request name";
2009 return ERR;
2010 }
2011 }
2012 if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2013 request = add_run_request(keymap, key, argc - 2, argv + 2);
2014 if (request == REQ_UNKNOWN) {
2015 config_msg = "Unknown request name";
2016 return ERR;
2017 }
2019 add_keybinding(keymap, request, key);
2021 return OK;
2022 }
2024 static int
2025 set_option(const char *opt, char *value)
2026 {
2027 const char *argv[SIZEOF_ARG];
2028 int argc = 0;
2030 if (!argv_from_string(argv, &argc, value)) {
2031 config_msg = "Too many option arguments";
2032 return ERR;
2033 }
2035 if (!strcmp(opt, "color"))
2036 return option_color_command(argc, argv);
2038 if (!strcmp(opt, "set"))
2039 return option_set_command(argc, argv);
2041 if (!strcmp(opt, "bind"))
2042 return option_bind_command(argc, argv);
2044 config_msg = "Unknown option command";
2045 return ERR;
2046 }
2048 static int
2049 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2050 {
2051 int status = OK;
2053 config_lineno++;
2054 config_msg = "Internal error";
2056 /* Check for comment markers, since read_properties() will
2057 * only ensure opt and value are split at first " \t". */
2058 optlen = strcspn(opt, "#");
2059 if (optlen == 0)
2060 return OK;
2062 if (opt[optlen] != 0) {
2063 config_msg = "No option value";
2064 status = ERR;
2066 } else {
2067 /* Look for comment endings in the value. */
2068 size_t len = strcspn(value, "#");
2070 if (len < valuelen) {
2071 valuelen = len;
2072 value[valuelen] = 0;
2073 }
2075 status = set_option(opt, value);
2076 }
2078 if (status == ERR) {
2079 warn("Error on line %d, near '%.*s': %s",
2080 config_lineno, (int) optlen, opt, config_msg);
2081 config_errors = TRUE;
2082 }
2084 /* Always keep going if errors are encountered. */
2085 return OK;
2086 }
2088 static void
2089 load_option_file(const char *path)
2090 {
2091 struct io io;
2093 /* It's OK that the file doesn't exist. */
2094 if (!io_open(&io, "%s", path))
2095 return;
2097 config_lineno = 0;
2098 config_errors = FALSE;
2100 if (io_load(&io, " \t", read_option) == ERR ||
2101 config_errors == TRUE)
2102 warn("Errors while loading %s.", path);
2103 }
2105 static int
2106 load_options(void)
2107 {
2108 const char *home = getenv("HOME");
2109 const char *tigrc_user = getenv("TIGRC_USER");
2110 const char *tigrc_system = getenv("TIGRC_SYSTEM");
2111 char buf[SIZEOF_STR];
2113 if (!tigrc_system)
2114 tigrc_system = SYSCONFDIR "/tigrc";
2115 load_option_file(tigrc_system);
2117 if (!tigrc_user) {
2118 if (!home || !string_format(buf, "%s/.tigrc", home))
2119 return ERR;
2120 tigrc_user = buf;
2121 }
2122 load_option_file(tigrc_user);
2124 /* Add _after_ loading config files to avoid adding run requests
2125 * that conflict with keybindings. */
2126 add_builtin_run_requests();
2128 return OK;
2129 }
2132 /*
2133 * The viewer
2134 */
2136 struct view;
2137 struct view_ops;
2139 /* The display array of active views and the index of the current view. */
2140 static struct view *display[2];
2141 static unsigned int current_view;
2143 #define foreach_displayed_view(view, i) \
2144 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2146 #define displayed_views() (display[1] != NULL ? 2 : 1)
2148 /* Current head and commit ID */
2149 static char ref_blob[SIZEOF_REF] = "";
2150 static char ref_commit[SIZEOF_REF] = "HEAD";
2151 static char ref_head[SIZEOF_REF] = "HEAD";
2152 static char ref_branch[SIZEOF_REF] = "";
2154 enum view_type {
2155 VIEW_MAIN,
2156 VIEW_DIFF,
2157 VIEW_LOG,
2158 VIEW_TREE,
2159 VIEW_BLOB,
2160 VIEW_BLAME,
2161 VIEW_BRANCH,
2162 VIEW_HELP,
2163 VIEW_PAGER,
2164 VIEW_STATUS,
2165 VIEW_STAGE,
2166 };
2168 struct view {
2169 enum view_type type; /* View type */
2170 const char *name; /* View name */
2171 const char *cmd_env; /* Command line set via environment */
2172 const char *id; /* Points to either of ref_{head,commit,blob} */
2174 struct view_ops *ops; /* View operations */
2176 enum keymap keymap; /* What keymap does this view have */
2177 bool git_dir; /* Whether the view requires a git directory. */
2179 char ref[SIZEOF_REF]; /* Hovered commit reference */
2180 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2182 int height, width; /* The width and height of the main window */
2183 WINDOW *win; /* The main window */
2184 WINDOW *title; /* The title window living below the main window */
2186 /* Navigation */
2187 unsigned long offset; /* Offset of the window top */
2188 unsigned long yoffset; /* Offset from the window side. */
2189 unsigned long lineno; /* Current line number */
2190 unsigned long p_offset; /* Previous offset of the window top */
2191 unsigned long p_yoffset;/* Previous offset from the window side */
2192 unsigned long p_lineno; /* Previous current line number */
2193 bool p_restore; /* Should the previous position be restored. */
2195 /* Searching */
2196 char grep[SIZEOF_STR]; /* Search string */
2197 regex_t *regex; /* Pre-compiled regexp */
2199 /* If non-NULL, points to the view that opened this view. If this view
2200 * is closed tig will switch back to the parent view. */
2201 struct view *parent;
2202 struct view *prev;
2204 /* Buffering */
2205 size_t lines; /* Total number of lines */
2206 struct line *line; /* Line index */
2207 unsigned int digits; /* Number of digits in the lines member. */
2209 /* Drawing */
2210 struct line *curline; /* Line currently being drawn. */
2211 enum line_type curtype; /* Attribute currently used for drawing. */
2212 unsigned long col; /* Column when drawing. */
2213 bool has_scrolled; /* View was scrolled. */
2215 /* Loading */
2216 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
2217 const char *dir; /* Directory from which to execute. */
2218 struct io io;
2219 struct io *pipe;
2220 time_t start_time;
2221 time_t update_secs;
2222 };
2224 struct view_ops {
2225 /* What type of content being displayed. Used in the title bar. */
2226 const char *type;
2227 /* Default command arguments. */
2228 const char **argv;
2229 /* Open and reads in all view content. */
2230 bool (*open)(struct view *view);
2231 /* Read one line; updates view->line. */
2232 bool (*read)(struct view *view, char *data);
2233 /* Draw one line; @lineno must be < view->height. */
2234 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2235 /* Depending on view handle a special requests. */
2236 enum request (*request)(struct view *view, enum request request, struct line *line);
2237 /* Search for regexp in a line. */
2238 bool (*grep)(struct view *view, struct line *line);
2239 /* Select line */
2240 void (*select)(struct view *view, struct line *line);
2241 /* Prepare view for loading */
2242 bool (*prepare)(struct view *view);
2243 };
2245 static struct view_ops blame_ops;
2246 static struct view_ops blob_ops;
2247 static struct view_ops diff_ops;
2248 static struct view_ops help_ops;
2249 static struct view_ops log_ops;
2250 static struct view_ops main_ops;
2251 static struct view_ops pager_ops;
2252 static struct view_ops stage_ops;
2253 static struct view_ops status_ops;
2254 static struct view_ops tree_ops;
2255 static struct view_ops branch_ops;
2257 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2258 { type, name, #env, ref, ops, map, git }
2260 #define VIEW_(id, name, ops, git, ref) \
2261 VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2263 static struct view views[] = {
2264 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2265 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2266 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2267 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2268 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2269 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2270 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2271 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2272 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2273 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2274 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2275 };
2277 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2279 #define foreach_view(view, i) \
2280 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2282 #define view_is_displayed(view) \
2283 (view == display[0] || view == display[1])
2285 static enum request
2286 view_request(struct view *view, enum request request)
2287 {
2288 if (!view || !view->lines)
2289 return request;
2290 return view->ops->request(view, request, &view->line[view->lineno]);
2291 }
2294 /*
2295 * View drawing.
2296 */
2298 static inline void
2299 set_view_attr(struct view *view, enum line_type type)
2300 {
2301 if (!view->curline->selected && view->curtype != type) {
2302 (void) wattrset(view->win, get_line_attr(type));
2303 wchgat(view->win, -1, 0, type, NULL);
2304 view->curtype = type;
2305 }
2306 }
2308 static int
2309 draw_chars(struct view *view, enum line_type type, const char *string,
2310 int max_len, bool use_tilde)
2311 {
2312 static char out_buffer[BUFSIZ * 2];
2313 int len = 0;
2314 int col = 0;
2315 int trimmed = FALSE;
2316 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2318 if (max_len <= 0)
2319 return 0;
2321 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2323 set_view_attr(view, type);
2324 if (len > 0) {
2325 if (opt_iconv_out != ICONV_NONE) {
2326 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2327 size_t inlen = len + 1;
2329 char *outbuf = out_buffer;
2330 size_t outlen = sizeof(out_buffer);
2332 size_t ret;
2334 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2335 if (ret != (size_t) -1) {
2336 string = out_buffer;
2337 len = sizeof(out_buffer) - outlen;
2338 }
2339 }
2341 waddnstr(view->win, string, len);
2342 }
2343 if (trimmed && use_tilde) {
2344 set_view_attr(view, LINE_DELIMITER);
2345 waddch(view->win, '~');
2346 col++;
2347 }
2349 return col;
2350 }
2352 static int
2353 draw_space(struct view *view, enum line_type type, int max, int spaces)
2354 {
2355 static char space[] = " ";
2356 int col = 0;
2358 spaces = MIN(max, spaces);
2360 while (spaces > 0) {
2361 int len = MIN(spaces, sizeof(space) - 1);
2363 col += draw_chars(view, type, space, len, FALSE);
2364 spaces -= len;
2365 }
2367 return col;
2368 }
2370 static bool
2371 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2372 {
2373 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2374 return view->width + view->yoffset <= view->col;
2375 }
2377 static bool
2378 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2379 {
2380 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2381 int max = view->width + view->yoffset - view->col;
2382 int i;
2384 if (max < size)
2385 size = max;
2387 set_view_attr(view, type);
2388 /* Using waddch() instead of waddnstr() ensures that
2389 * they'll be rendered correctly for the cursor line. */
2390 for (i = skip; i < size; i++)
2391 waddch(view->win, graphic[i]);
2393 view->col += size;
2394 if (size < max && skip <= size)
2395 waddch(view->win, ' ');
2396 view->col++;
2398 return view->width + view->yoffset <= view->col;
2399 }
2401 static bool
2402 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2403 {
2404 int max = MIN(view->width + view->yoffset - view->col, len);
2405 int col;
2407 if (text)
2408 col = draw_chars(view, type, text, max - 1, trim);
2409 else
2410 col = draw_space(view, type, max - 1, max - 1);
2412 view->col += col;
2413 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2414 return view->width + view->yoffset <= view->col;
2415 }
2417 static bool
2418 draw_date(struct view *view, struct time *time)
2419 {
2420 const char *date = mkdate(time, opt_date);
2421 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2423 return draw_field(view, LINE_DATE, date, cols, FALSE);
2424 }
2426 static bool
2427 draw_author(struct view *view, const char *author)
2428 {
2429 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2430 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2432 if (abbreviate && author)
2433 author = get_author_initials(author);
2435 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2436 }
2438 static bool
2439 draw_mode(struct view *view, mode_t mode)
2440 {
2441 const char *str;
2443 if (S_ISDIR(mode))
2444 str = "drwxr-xr-x";
2445 else if (S_ISLNK(mode))
2446 str = "lrwxrwxrwx";
2447 else if (S_ISGITLINK(mode))
2448 str = "m---------";
2449 else if (S_ISREG(mode) && mode & S_IXUSR)
2450 str = "-rwxr-xr-x";
2451 else if (S_ISREG(mode))
2452 str = "-rw-r--r--";
2453 else
2454 str = "----------";
2456 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2457 }
2459 static bool
2460 draw_lineno(struct view *view, unsigned int lineno)
2461 {
2462 char number[10];
2463 int digits3 = view->digits < 3 ? 3 : view->digits;
2464 int max = MIN(view->width + view->yoffset - view->col, digits3);
2465 char *text = NULL;
2466 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2468 lineno += view->offset + 1;
2469 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2470 static char fmt[] = "%1ld";
2472 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2473 if (string_format(number, fmt, lineno))
2474 text = number;
2475 }
2476 if (text)
2477 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2478 else
2479 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2480 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2481 }
2483 static bool
2484 draw_view_line(struct view *view, unsigned int lineno)
2485 {
2486 struct line *line;
2487 bool selected = (view->offset + lineno == view->lineno);
2489 assert(view_is_displayed(view));
2491 if (view->offset + lineno >= view->lines)
2492 return FALSE;
2494 line = &view->line[view->offset + lineno];
2496 wmove(view->win, lineno, 0);
2497 if (line->cleareol)
2498 wclrtoeol(view->win);
2499 view->col = 0;
2500 view->curline = line;
2501 view->curtype = LINE_NONE;
2502 line->selected = FALSE;
2503 line->dirty = line->cleareol = 0;
2505 if (selected) {
2506 set_view_attr(view, LINE_CURSOR);
2507 line->selected = TRUE;
2508 view->ops->select(view, line);
2509 }
2511 return view->ops->draw(view, line, lineno);
2512 }
2514 static void
2515 redraw_view_dirty(struct view *view)
2516 {
2517 bool dirty = FALSE;
2518 int lineno;
2520 for (lineno = 0; lineno < view->height; lineno++) {
2521 if (view->offset + lineno >= view->lines)
2522 break;
2523 if (!view->line[view->offset + lineno].dirty)
2524 continue;
2525 dirty = TRUE;
2526 if (!draw_view_line(view, lineno))
2527 break;
2528 }
2530 if (!dirty)
2531 return;
2532 wnoutrefresh(view->win);
2533 }
2535 static void
2536 redraw_view_from(struct view *view, int lineno)
2537 {
2538 assert(0 <= lineno && lineno < view->height);
2540 for (; lineno < view->height; lineno++) {
2541 if (!draw_view_line(view, lineno))
2542 break;
2543 }
2545 wnoutrefresh(view->win);
2546 }
2548 static void
2549 redraw_view(struct view *view)
2550 {
2551 werase(view->win);
2552 redraw_view_from(view, 0);
2553 }
2556 static void
2557 update_view_title(struct view *view)
2558 {
2559 char buf[SIZEOF_STR];
2560 char state[SIZEOF_STR];
2561 size_t bufpos = 0, statelen = 0;
2563 assert(view_is_displayed(view));
2565 if (view->type != VIEW_STATUS && view->lines) {
2566 unsigned int view_lines = view->offset + view->height;
2567 unsigned int lines = view->lines
2568 ? MIN(view_lines, view->lines) * 100 / view->lines
2569 : 0;
2571 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2572 view->ops->type,
2573 view->lineno + 1,
2574 view->lines,
2575 lines);
2577 }
2579 if (view->pipe) {
2580 time_t secs = time(NULL) - view->start_time;
2582 /* Three git seconds are a long time ... */
2583 if (secs > 2)
2584 string_format_from(state, &statelen, " loading %lds", secs);
2585 }
2587 string_format_from(buf, &bufpos, "[%s]", view->name);
2588 if (*view->ref && bufpos < view->width) {
2589 size_t refsize = strlen(view->ref);
2590 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2592 if (minsize < view->width)
2593 refsize = view->width - minsize + 7;
2594 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2595 }
2597 if (statelen && bufpos < view->width) {
2598 string_format_from(buf, &bufpos, "%s", state);
2599 }
2601 if (view == display[current_view])
2602 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2603 else
2604 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2606 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2607 wclrtoeol(view->title);
2608 wnoutrefresh(view->title);
2609 }
2611 static int
2612 apply_step(double step, int value)
2613 {
2614 if (step >= 1)
2615 return (int) step;
2616 value *= step + 0.01;
2617 return value ? value : 1;
2618 }
2620 static void
2621 resize_display(void)
2622 {
2623 int offset, i;
2624 struct view *base = display[0];
2625 struct view *view = display[1] ? display[1] : display[0];
2627 /* Setup window dimensions */
2629 getmaxyx(stdscr, base->height, base->width);
2631 /* Make room for the status window. */
2632 base->height -= 1;
2634 if (view != base) {
2635 /* Horizontal split. */
2636 view->width = base->width;
2637 view->height = apply_step(opt_scale_split_view, base->height);
2638 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2639 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2640 base->height -= view->height;
2642 /* Make room for the title bar. */
2643 view->height -= 1;
2644 }
2646 /* Make room for the title bar. */
2647 base->height -= 1;
2649 offset = 0;
2651 foreach_displayed_view (view, i) {
2652 if (!view->win) {
2653 view->win = newwin(view->height, 0, offset, 0);
2654 if (!view->win)
2655 die("Failed to create %s view", view->name);
2657 scrollok(view->win, FALSE);
2659 view->title = newwin(1, 0, offset + view->height, 0);
2660 if (!view->title)
2661 die("Failed to create title window");
2663 } else {
2664 wresize(view->win, view->height, view->width);
2665 mvwin(view->win, offset, 0);
2666 mvwin(view->title, offset + view->height, 0);
2667 }
2669 offset += view->height + 1;
2670 }
2671 }
2673 static void
2674 redraw_display(bool clear)
2675 {
2676 struct view *view;
2677 int i;
2679 foreach_displayed_view (view, i) {
2680 if (clear)
2681 wclear(view->win);
2682 redraw_view(view);
2683 update_view_title(view);
2684 }
2685 }
2688 /*
2689 * Option management
2690 */
2692 static void
2693 toggle_enum_option_do(unsigned int *opt, const char *help,
2694 const struct enum_map *map, size_t size)
2695 {
2696 *opt = (*opt + 1) % size;
2697 redraw_display(FALSE);
2698 report("Displaying %s %s", enum_name(map[*opt]), help);
2699 }
2701 #define toggle_enum_option(opt, help, map) \
2702 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2704 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2705 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2707 static void
2708 toggle_view_option(bool *option, const char *help)
2709 {
2710 *option = !*option;
2711 redraw_display(FALSE);
2712 report("%sabling %s", *option ? "En" : "Dis", help);
2713 }
2715 static void
2716 open_option_menu(void)
2717 {
2718 const struct menu_item menu[] = {
2719 { '.', "line numbers", &opt_line_number },
2720 { 'D', "date display", &opt_date },
2721 { 'A', "author display", &opt_author },
2722 { 'g', "revision graph display", &opt_rev_graph },
2723 { 'F', "reference display", &opt_show_refs },
2724 { 0 }
2725 };
2726 int selected = 0;
2728 if (prompt_menu("Toggle option", menu, &selected)) {
2729 if (menu[selected].data == &opt_date)
2730 toggle_date();
2731 else if (menu[selected].data == &opt_author)
2732 toggle_author();
2733 else
2734 toggle_view_option(menu[selected].data, menu[selected].text);
2735 }
2736 }
2738 static void
2739 maximize_view(struct view *view)
2740 {
2741 memset(display, 0, sizeof(display));
2742 current_view = 0;
2743 display[current_view] = view;
2744 resize_display();
2745 redraw_display(FALSE);
2746 report("");
2747 }
2750 /*
2751 * Navigation
2752 */
2754 static bool
2755 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2756 {
2757 if (lineno >= view->lines)
2758 lineno = view->lines > 0 ? view->lines - 1 : 0;
2760 if (offset > lineno || offset + view->height <= lineno) {
2761 unsigned long half = view->height / 2;
2763 if (lineno > half)
2764 offset = lineno - half;
2765 else
2766 offset = 0;
2767 }
2769 if (offset != view->offset || lineno != view->lineno) {
2770 view->offset = offset;
2771 view->lineno = lineno;
2772 return TRUE;
2773 }
2775 return FALSE;
2776 }
2778 /* Scrolling backend */
2779 static void
2780 do_scroll_view(struct view *view, int lines)
2781 {
2782 bool redraw_current_line = FALSE;
2784 /* The rendering expects the new offset. */
2785 view->offset += lines;
2787 assert(0 <= view->offset && view->offset < view->lines);
2788 assert(lines);
2790 /* Move current line into the view. */
2791 if (view->lineno < view->offset) {
2792 view->lineno = view->offset;
2793 redraw_current_line = TRUE;
2794 } else if (view->lineno >= view->offset + view->height) {
2795 view->lineno = view->offset + view->height - 1;
2796 redraw_current_line = TRUE;
2797 }
2799 assert(view->offset <= view->lineno && view->lineno < view->lines);
2801 /* Redraw the whole screen if scrolling is pointless. */
2802 if (view->height < ABS(lines)) {
2803 redraw_view(view);
2805 } else {
2806 int line = lines > 0 ? view->height - lines : 0;
2807 int end = line + ABS(lines);
2809 scrollok(view->win, TRUE);
2810 wscrl(view->win, lines);
2811 scrollok(view->win, FALSE);
2813 while (line < end && draw_view_line(view, line))
2814 line++;
2816 if (redraw_current_line)
2817 draw_view_line(view, view->lineno - view->offset);
2818 wnoutrefresh(view->win);
2819 }
2821 view->has_scrolled = TRUE;
2822 report("");
2823 }
2825 /* Scroll frontend */
2826 static void
2827 scroll_view(struct view *view, enum request request)
2828 {
2829 int lines = 1;
2831 assert(view_is_displayed(view));
2833 switch (request) {
2834 case REQ_SCROLL_LEFT:
2835 if (view->yoffset == 0) {
2836 report("Cannot scroll beyond the first column");
2837 return;
2838 }
2839 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2840 view->yoffset = 0;
2841 else
2842 view->yoffset -= apply_step(opt_hscroll, view->width);
2843 redraw_view_from(view, 0);
2844 report("");
2845 return;
2846 case REQ_SCROLL_RIGHT:
2847 view->yoffset += apply_step(opt_hscroll, view->width);
2848 redraw_view(view);
2849 report("");
2850 return;
2851 case REQ_SCROLL_PAGE_DOWN:
2852 lines = view->height;
2853 case REQ_SCROLL_LINE_DOWN:
2854 if (view->offset + lines > view->lines)
2855 lines = view->lines - view->offset;
2857 if (lines == 0 || view->offset + view->height >= view->lines) {
2858 report("Cannot scroll beyond the last line");
2859 return;
2860 }
2861 break;
2863 case REQ_SCROLL_PAGE_UP:
2864 lines = view->height;
2865 case REQ_SCROLL_LINE_UP:
2866 if (lines > view->offset)
2867 lines = view->offset;
2869 if (lines == 0) {
2870 report("Cannot scroll beyond the first line");
2871 return;
2872 }
2874 lines = -lines;
2875 break;
2877 default:
2878 die("request %d not handled in switch", request);
2879 }
2881 do_scroll_view(view, lines);
2882 }
2884 /* Cursor moving */
2885 static void
2886 move_view(struct view *view, enum request request)
2887 {
2888 int scroll_steps = 0;
2889 int steps;
2891 switch (request) {
2892 case REQ_MOVE_FIRST_LINE:
2893 steps = -view->lineno;
2894 break;
2896 case REQ_MOVE_LAST_LINE:
2897 steps = view->lines - view->lineno - 1;
2898 break;
2900 case REQ_MOVE_PAGE_UP:
2901 steps = view->height > view->lineno
2902 ? -view->lineno : -view->height;
2903 break;
2905 case REQ_MOVE_PAGE_DOWN:
2906 steps = view->lineno + view->height >= view->lines
2907 ? view->lines - view->lineno - 1 : view->height;
2908 break;
2910 case REQ_MOVE_UP:
2911 steps = -1;
2912 break;
2914 case REQ_MOVE_DOWN:
2915 steps = 1;
2916 break;
2918 default:
2919 die("request %d not handled in switch", request);
2920 }
2922 if (steps <= 0 && view->lineno == 0) {
2923 report("Cannot move beyond the first line");
2924 return;
2926 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2927 report("Cannot move beyond the last line");
2928 return;
2929 }
2931 /* Move the current line */
2932 view->lineno += steps;
2933 assert(0 <= view->lineno && view->lineno < view->lines);
2935 /* Check whether the view needs to be scrolled */
2936 if (view->lineno < view->offset ||
2937 view->lineno >= view->offset + view->height) {
2938 scroll_steps = steps;
2939 if (steps < 0 && -steps > view->offset) {
2940 scroll_steps = -view->offset;
2942 } else if (steps > 0) {
2943 if (view->lineno == view->lines - 1 &&
2944 view->lines > view->height) {
2945 scroll_steps = view->lines - view->offset - 1;
2946 if (scroll_steps >= view->height)
2947 scroll_steps -= view->height - 1;
2948 }
2949 }
2950 }
2952 if (!view_is_displayed(view)) {
2953 view->offset += scroll_steps;
2954 assert(0 <= view->offset && view->offset < view->lines);
2955 view->ops->select(view, &view->line[view->lineno]);
2956 return;
2957 }
2959 /* Repaint the old "current" line if we be scrolling */
2960 if (ABS(steps) < view->height)
2961 draw_view_line(view, view->lineno - steps - view->offset);
2963 if (scroll_steps) {
2964 do_scroll_view(view, scroll_steps);
2965 return;
2966 }
2968 /* Draw the current line */
2969 draw_view_line(view, view->lineno - view->offset);
2971 wnoutrefresh(view->win);
2972 report("");
2973 }
2976 /*
2977 * Searching
2978 */
2980 static void search_view(struct view *view, enum request request);
2982 static bool
2983 grep_text(struct view *view, const char *text[])
2984 {
2985 regmatch_t pmatch;
2986 size_t i;
2988 for (i = 0; text[i]; i++)
2989 if (*text[i] &&
2990 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2991 return TRUE;
2992 return FALSE;
2993 }
2995 static void
2996 select_view_line(struct view *view, unsigned long lineno)
2997 {
2998 unsigned long old_lineno = view->lineno;
2999 unsigned long old_offset = view->offset;
3001 if (goto_view_line(view, view->offset, lineno)) {
3002 if (view_is_displayed(view)) {
3003 if (old_offset != view->offset) {
3004 redraw_view(view);
3005 } else {
3006 draw_view_line(view, old_lineno - view->offset);
3007 draw_view_line(view, view->lineno - view->offset);
3008 wnoutrefresh(view->win);
3009 }
3010 } else {
3011 view->ops->select(view, &view->line[view->lineno]);
3012 }
3013 }
3014 }
3016 static void
3017 find_next(struct view *view, enum request request)
3018 {
3019 unsigned long lineno = view->lineno;
3020 int direction;
3022 if (!*view->grep) {
3023 if (!*opt_search)
3024 report("No previous search");
3025 else
3026 search_view(view, request);
3027 return;
3028 }
3030 switch (request) {
3031 case REQ_SEARCH:
3032 case REQ_FIND_NEXT:
3033 direction = 1;
3034 break;
3036 case REQ_SEARCH_BACK:
3037 case REQ_FIND_PREV:
3038 direction = -1;
3039 break;
3041 default:
3042 return;
3043 }
3045 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3046 lineno += direction;
3048 /* Note, lineno is unsigned long so will wrap around in which case it
3049 * will become bigger than view->lines. */
3050 for (; lineno < view->lines; lineno += direction) {
3051 if (view->ops->grep(view, &view->line[lineno])) {
3052 select_view_line(view, lineno);
3053 report("Line %ld matches '%s'", lineno + 1, view->grep);
3054 return;
3055 }
3056 }
3058 report("No match found for '%s'", view->grep);
3059 }
3061 static void
3062 search_view(struct view *view, enum request request)
3063 {
3064 int regex_err;
3066 if (view->regex) {
3067 regfree(view->regex);
3068 *view->grep = 0;
3069 } else {
3070 view->regex = calloc(1, sizeof(*view->regex));
3071 if (!view->regex)
3072 return;
3073 }
3075 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3076 if (regex_err != 0) {
3077 char buf[SIZEOF_STR] = "unknown error";
3079 regerror(regex_err, view->regex, buf, sizeof(buf));
3080 report("Search failed: %s", buf);
3081 return;
3082 }
3084 string_copy(view->grep, opt_search);
3086 find_next(view, request);
3087 }
3089 /*
3090 * Incremental updating
3091 */
3093 static void
3094 reset_view(struct view *view)
3095 {
3096 int i;
3098 for (i = 0; i < view->lines; i++)
3099 free(view->line[i].data);
3100 free(view->line);
3102 view->p_offset = view->offset;
3103 view->p_yoffset = view->yoffset;
3104 view->p_lineno = view->lineno;
3106 view->line = NULL;
3107 view->offset = 0;
3108 view->yoffset = 0;
3109 view->lines = 0;
3110 view->lineno = 0;
3111 view->vid[0] = 0;
3112 view->update_secs = 0;
3113 }
3115 static const char *
3116 format_arg(const char *name)
3117 {
3118 static struct {
3119 const char *name;
3120 size_t namelen;
3121 const char *value;
3122 const char *value_if_empty;
3123 } vars[] = {
3124 #define FORMAT_VAR(name, value, value_if_empty) \
3125 { name, STRING_SIZE(name), value, value_if_empty }
3126 FORMAT_VAR("%(directory)", opt_path, ""),
3127 FORMAT_VAR("%(file)", opt_file, ""),
3128 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
3129 FORMAT_VAR("%(head)", ref_head, ""),
3130 FORMAT_VAR("%(commit)", ref_commit, ""),
3131 FORMAT_VAR("%(blob)", ref_blob, ""),
3132 FORMAT_VAR("%(branch)", ref_branch, ""),
3133 };
3134 int i;
3136 for (i = 0; i < ARRAY_SIZE(vars); i++)
3137 if (!strncmp(name, vars[i].name, vars[i].namelen))
3138 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3140 report("Unknown replacement: `%s`", name);
3141 return NULL;
3142 }
3144 static bool
3145 format_argv(const char *dst_argv[], const char *src_argv[], bool replace)
3146 {
3147 char buf[SIZEOF_STR];
3148 int argc;
3150 argv_free(dst_argv);
3152 for (argc = 0; src_argv[argc]; argc++) {
3153 const char *arg = src_argv[argc];
3154 size_t bufpos = 0;
3156 while (arg) {
3157 char *next = strstr(arg, "%(");
3158 int len = next - arg;
3159 const char *value;
3161 if (!next || !replace) {
3162 len = strlen(arg);
3163 value = "";
3165 } else {
3166 value = format_arg(next);
3168 if (!value) {
3169 return FALSE;
3170 }
3171 }
3173 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3174 return FALSE;
3176 arg = next && replace ? strchr(next, ')') + 1 : NULL;
3177 }
3179 dst_argv[argc] = strdup(buf);
3180 if (!dst_argv[argc])
3181 break;
3182 }
3184 dst_argv[argc] = NULL;
3186 return src_argv[argc] == NULL;
3187 }
3189 static bool
3190 restore_view_position(struct view *view)
3191 {
3192 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3193 return FALSE;
3195 /* Changing the view position cancels the restoring. */
3196 /* FIXME: Changing back to the first line is not detected. */
3197 if (view->offset != 0 || view->lineno != 0) {
3198 view->p_restore = FALSE;
3199 return FALSE;
3200 }
3202 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3203 view_is_displayed(view))
3204 werase(view->win);
3206 view->yoffset = view->p_yoffset;
3207 view->p_restore = FALSE;
3209 return TRUE;
3210 }
3212 static void
3213 end_update(struct view *view, bool force)
3214 {
3215 if (!view->pipe)
3216 return;
3217 while (!view->ops->read(view, NULL))
3218 if (!force)
3219 return;
3220 if (force)
3221 io_kill(view->pipe);
3222 io_done(view->pipe);
3223 view->pipe = NULL;
3224 }
3226 static void
3227 setup_update(struct view *view, const char *vid)
3228 {
3229 reset_view(view);
3230 string_copy_rev(view->vid, vid);
3231 view->pipe = &view->io;
3232 view->start_time = time(NULL);
3233 }
3235 static bool
3236 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3237 {
3238 view->dir = dir;
3239 return format_argv(view->argv, argv, replace);
3240 }
3242 static bool
3243 prepare_update(struct view *view, const char *argv[], const char *dir)
3244 {
3245 if (view->pipe)
3246 end_update(view, TRUE);
3247 return prepare_io(view, dir, argv, FALSE);
3248 }
3250 static bool
3251 start_update(struct view *view, const char **argv, const char *dir)
3252 {
3253 if (view->pipe)
3254 io_done(view->pipe);
3255 return prepare_io(view, dir, argv, FALSE) &&
3256 io_run(&view->io, IO_RD, dir, view->argv);
3257 }
3259 static bool
3260 prepare_update_file(struct view *view, const char *name)
3261 {
3262 if (view->pipe)
3263 end_update(view, TRUE);
3264 argv_free(view->argv);
3265 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3266 }
3268 static bool
3269 begin_update(struct view *view, bool refresh)
3270 {
3271 if (view->pipe)
3272 end_update(view, TRUE);
3274 if (!refresh) {
3275 if (view->ops->prepare) {
3276 if (!view->ops->prepare(view))
3277 return FALSE;
3278 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3279 return FALSE;
3280 }
3282 /* Put the current ref_* value to the view title ref
3283 * member. This is needed by the blob view. Most other
3284 * views sets it automatically after loading because the
3285 * first line is a commit line. */
3286 string_copy_rev(view->ref, view->id);
3287 }
3289 if (view->argv[0] && !io_run(&view->io, IO_RD, view->dir, view->argv))
3290 return FALSE;
3292 setup_update(view, view->id);
3294 return TRUE;
3295 }
3297 static bool
3298 update_view(struct view *view)
3299 {
3300 char out_buffer[BUFSIZ * 2];
3301 char *line;
3302 /* Clear the view and redraw everything since the tree sorting
3303 * might have rearranged things. */
3304 bool redraw = view->lines == 0;
3305 bool can_read = TRUE;
3307 if (!view->pipe)
3308 return TRUE;
3310 if (!io_can_read(view->pipe)) {
3311 if (view->lines == 0 && view_is_displayed(view)) {
3312 time_t secs = time(NULL) - view->start_time;
3314 if (secs > 1 && secs > view->update_secs) {
3315 if (view->update_secs == 0)
3316 redraw_view(view);
3317 update_view_title(view);
3318 view->update_secs = secs;
3319 }
3320 }
3321 return TRUE;
3322 }
3324 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3325 if (opt_iconv_in != ICONV_NONE) {
3326 ICONV_CONST char *inbuf = line;
3327 size_t inlen = strlen(line) + 1;
3329 char *outbuf = out_buffer;
3330 size_t outlen = sizeof(out_buffer);
3332 size_t ret;
3334 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3335 if (ret != (size_t) -1)
3336 line = out_buffer;
3337 }
3339 if (!view->ops->read(view, line)) {
3340 report("Allocation failure");
3341 end_update(view, TRUE);
3342 return FALSE;
3343 }
3344 }
3346 {
3347 unsigned long lines = view->lines;
3348 int digits;
3350 for (digits = 0; lines; digits++)
3351 lines /= 10;
3353 /* Keep the displayed view in sync with line number scaling. */
3354 if (digits != view->digits) {
3355 view->digits = digits;
3356 if (opt_line_number || view->type == VIEW_BLAME)
3357 redraw = TRUE;
3358 }
3359 }
3361 if (io_error(view->pipe)) {
3362 report("Failed to read: %s", io_strerror(view->pipe));
3363 end_update(view, TRUE);
3365 } else if (io_eof(view->pipe)) {
3366 if (view_is_displayed(view))
3367 report("");
3368 end_update(view, FALSE);
3369 }
3371 if (restore_view_position(view))
3372 redraw = TRUE;
3374 if (!view_is_displayed(view))
3375 return TRUE;
3377 if (redraw)
3378 redraw_view_from(view, 0);
3379 else
3380 redraw_view_dirty(view);
3382 /* Update the title _after_ the redraw so that if the redraw picks up a
3383 * commit reference in view->ref it'll be available here. */
3384 update_view_title(view);
3385 return TRUE;
3386 }
3388 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3390 static struct line *
3391 add_line_data(struct view *view, void *data, enum line_type type)
3392 {
3393 struct line *line;
3395 if (!realloc_lines(&view->line, view->lines, 1))
3396 return NULL;
3398 line = &view->line[view->lines++];
3399 memset(line, 0, sizeof(*line));
3400 line->type = type;
3401 line->data = data;
3402 line->dirty = 1;
3404 return line;
3405 }
3407 static struct line *
3408 add_line_text(struct view *view, const char *text, enum line_type type)
3409 {
3410 char *data = text ? strdup(text) : NULL;
3412 return data ? add_line_data(view, data, type) : NULL;
3413 }
3415 static struct line *
3416 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3417 {
3418 char buf[SIZEOF_STR];
3419 va_list args;
3421 va_start(args, fmt);
3422 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3423 buf[0] = 0;
3424 va_end(args);
3426 return buf[0] ? add_line_text(view, buf, type) : NULL;
3427 }
3429 /*
3430 * View opening
3431 */
3433 enum open_flags {
3434 OPEN_DEFAULT = 0, /* Use default view switching. */
3435 OPEN_SPLIT = 1, /* Split current view. */
3436 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3437 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3438 OPEN_PREPARED = 32, /* Open already prepared command. */
3439 };
3441 static void
3442 open_view(struct view *prev, enum request request, enum open_flags flags)
3443 {
3444 bool split = !!(flags & OPEN_SPLIT);
3445 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3446 bool nomaximize = !!(flags & OPEN_REFRESH);
3447 struct view *view = VIEW(request);
3448 int nviews = displayed_views();
3449 struct view *base_view = display[0];
3451 if (view == prev && nviews == 1 && !reload) {
3452 report("Already in %s view", view->name);
3453 return;
3454 }
3456 if (view->git_dir && !opt_git_dir[0]) {
3457 report("The %s view is disabled in pager view", view->name);
3458 return;
3459 }
3461 if (split) {
3462 display[1] = view;
3463 current_view = 1;
3464 view->parent = prev;
3465 } else if (!nomaximize) {
3466 /* Maximize the current view. */
3467 memset(display, 0, sizeof(display));
3468 current_view = 0;
3469 display[current_view] = view;
3470 }
3472 /* No prev signals that this is the first loaded view. */
3473 if (prev && view != prev) {
3474 view->prev = prev;
3475 }
3477 /* Resize the view when switching between split- and full-screen,
3478 * or when switching between two different full-screen views. */
3479 if (nviews != displayed_views() ||
3480 (nviews == 1 && base_view != display[0]))
3481 resize_display();
3483 if (view->ops->open) {
3484 if (view->pipe)
3485 end_update(view, TRUE);
3486 if (!view->ops->open(view)) {
3487 report("Failed to load %s view", view->name);
3488 return;
3489 }
3490 restore_view_position(view);
3492 } else if ((reload || strcmp(view->vid, view->id)) &&
3493 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3494 report("Failed to load %s view", view->name);
3495 return;
3496 }
3498 if (split && prev->lineno - prev->offset >= prev->height) {
3499 /* Take the title line into account. */
3500 int lines = prev->lineno - prev->offset - prev->height + 1;
3502 /* Scroll the view that was split if the current line is
3503 * outside the new limited view. */
3504 do_scroll_view(prev, lines);
3505 }
3507 if (prev && view != prev && split && view_is_displayed(prev)) {
3508 /* "Blur" the previous view. */
3509 update_view_title(prev);
3510 }
3512 if (view->pipe && view->lines == 0) {
3513 /* Clear the old view and let the incremental updating refill
3514 * the screen. */
3515 werase(view->win);
3516 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3517 report("");
3518 } else if (view_is_displayed(view)) {
3519 redraw_view(view);
3520 report("");
3521 }
3522 }
3524 static void
3525 open_external_viewer(const char *argv[], const char *dir)
3526 {
3527 def_prog_mode(); /* save current tty modes */
3528 endwin(); /* restore original tty modes */
3529 io_run_fg(argv, dir);
3530 fprintf(stderr, "Press Enter to continue");
3531 getc(opt_tty);
3532 reset_prog_mode();
3533 redraw_display(TRUE);
3534 }
3536 static void
3537 open_mergetool(const char *file)
3538 {
3539 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3541 open_external_viewer(mergetool_argv, opt_cdup);
3542 }
3544 static void
3545 open_editor(const char *file)
3546 {
3547 const char *editor_argv[] = { "vi", file, NULL };
3548 const char *editor;
3550 editor = getenv("GIT_EDITOR");
3551 if (!editor && *opt_editor)
3552 editor = opt_editor;
3553 if (!editor)
3554 editor = getenv("VISUAL");
3555 if (!editor)
3556 editor = getenv("EDITOR");
3557 if (!editor)
3558 editor = "vi";
3560 editor_argv[0] = editor;
3561 open_external_viewer(editor_argv, opt_cdup);
3562 }
3564 static void
3565 open_run_request(enum request request)
3566 {
3567 struct run_request *req = get_run_request(request);
3568 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3570 if (!req) {
3571 report("Unknown run request");
3572 return;
3573 }
3575 if (format_argv(argv, req->argv, TRUE))
3576 open_external_viewer(argv, NULL);
3577 argv_free(argv);
3578 }
3580 /*
3581 * User request switch noodle
3582 */
3584 static int
3585 view_driver(struct view *view, enum request request)
3586 {
3587 int i;
3589 if (request == REQ_NONE)
3590 return TRUE;
3592 if (request > REQ_NONE) {
3593 open_run_request(request);
3594 view_request(view, REQ_REFRESH);
3595 return TRUE;
3596 }
3598 request = view_request(view, request);
3599 if (request == REQ_NONE)
3600 return TRUE;
3602 switch (request) {
3603 case REQ_MOVE_UP:
3604 case REQ_MOVE_DOWN:
3605 case REQ_MOVE_PAGE_UP:
3606 case REQ_MOVE_PAGE_DOWN:
3607 case REQ_MOVE_FIRST_LINE:
3608 case REQ_MOVE_LAST_LINE:
3609 move_view(view, request);
3610 break;
3612 case REQ_SCROLL_LEFT:
3613 case REQ_SCROLL_RIGHT:
3614 case REQ_SCROLL_LINE_DOWN:
3615 case REQ_SCROLL_LINE_UP:
3616 case REQ_SCROLL_PAGE_DOWN:
3617 case REQ_SCROLL_PAGE_UP:
3618 scroll_view(view, request);
3619 break;
3621 case REQ_VIEW_BLAME:
3622 if (!opt_file[0]) {
3623 report("No file chosen, press %s to open tree view",
3624 get_key(view->keymap, REQ_VIEW_TREE));
3625 break;
3626 }
3627 open_view(view, request, OPEN_DEFAULT);
3628 break;
3630 case REQ_VIEW_BLOB:
3631 if (!ref_blob[0]) {
3632 report("No file chosen, press %s to open tree view",
3633 get_key(view->keymap, REQ_VIEW_TREE));
3634 break;
3635 }
3636 open_view(view, request, OPEN_DEFAULT);
3637 break;
3639 case REQ_VIEW_PAGER:
3640 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3641 report("No pager content, press %s to run command from prompt",
3642 get_key(view->keymap, REQ_PROMPT));
3643 break;
3644 }
3645 open_view(view, request, OPEN_DEFAULT);
3646 break;
3648 case REQ_VIEW_STAGE:
3649 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3650 report("No stage content, press %s to open the status view and choose file",
3651 get_key(view->keymap, REQ_VIEW_STATUS));
3652 break;
3653 }
3654 open_view(view, request, OPEN_DEFAULT);
3655 break;
3657 case REQ_VIEW_STATUS:
3658 if (opt_is_inside_work_tree == FALSE) {
3659 report("The status view requires a working tree");
3660 break;
3661 }
3662 open_view(view, request, OPEN_DEFAULT);
3663 break;
3665 case REQ_VIEW_MAIN:
3666 case REQ_VIEW_DIFF:
3667 case REQ_VIEW_LOG:
3668 case REQ_VIEW_TREE:
3669 case REQ_VIEW_HELP:
3670 case REQ_VIEW_BRANCH:
3671 open_view(view, request, OPEN_DEFAULT);
3672 break;
3674 case REQ_NEXT:
3675 case REQ_PREVIOUS:
3676 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3678 if (view->parent) {
3679 int line;
3681 view = view->parent;
3682 line = view->lineno;
3683 move_view(view, request);
3684 if (view_is_displayed(view))
3685 update_view_title(view);
3686 if (line != view->lineno)
3687 view_request(view, REQ_ENTER);
3688 } else {
3689 move_view(view, request);
3690 }
3691 break;
3693 case REQ_VIEW_NEXT:
3694 {
3695 int nviews = displayed_views();
3696 int next_view = (current_view + 1) % nviews;
3698 if (next_view == current_view) {
3699 report("Only one view is displayed");
3700 break;
3701 }
3703 current_view = next_view;
3704 /* Blur out the title of the previous view. */
3705 update_view_title(view);
3706 report("");
3707 break;
3708 }
3709 case REQ_REFRESH:
3710 report("Refreshing is not yet supported for the %s view", view->name);
3711 break;
3713 case REQ_MAXIMIZE:
3714 if (displayed_views() == 2)
3715 maximize_view(view);
3716 break;
3718 case REQ_OPTIONS:
3719 open_option_menu();
3720 break;
3722 case REQ_TOGGLE_LINENO:
3723 toggle_view_option(&opt_line_number, "line numbers");
3724 break;
3726 case REQ_TOGGLE_DATE:
3727 toggle_date();
3728 break;
3730 case REQ_TOGGLE_AUTHOR:
3731 toggle_author();
3732 break;
3734 case REQ_TOGGLE_REV_GRAPH:
3735 toggle_view_option(&opt_rev_graph, "revision graph display");
3736 break;
3738 case REQ_TOGGLE_REFS:
3739 toggle_view_option(&opt_show_refs, "reference display");
3740 break;
3742 case REQ_TOGGLE_SORT_FIELD:
3743 case REQ_TOGGLE_SORT_ORDER:
3744 report("Sorting is not yet supported for the %s view", view->name);
3745 break;
3747 case REQ_SEARCH:
3748 case REQ_SEARCH_BACK:
3749 search_view(view, request);
3750 break;
3752 case REQ_FIND_NEXT:
3753 case REQ_FIND_PREV:
3754 find_next(view, request);
3755 break;
3757 case REQ_STOP_LOADING:
3758 foreach_view(view, i) {
3759 if (view->pipe)
3760 report("Stopped loading the %s view", view->name),
3761 end_update(view, TRUE);
3762 }
3763 break;
3765 case REQ_SHOW_VERSION:
3766 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3767 return TRUE;
3769 case REQ_SCREEN_REDRAW:
3770 redraw_display(TRUE);
3771 break;
3773 case REQ_EDIT:
3774 report("Nothing to edit");
3775 break;
3777 case REQ_ENTER:
3778 report("Nothing to enter");
3779 break;
3781 case REQ_VIEW_CLOSE:
3782 /* XXX: Mark closed views by letting view->prev point to the
3783 * view itself. Parents to closed view should never be
3784 * followed. */
3785 if (view->prev && view->prev != view) {
3786 maximize_view(view->prev);
3787 view->prev = view;
3788 break;
3789 }
3790 /* Fall-through */
3791 case REQ_QUIT:
3792 return FALSE;
3794 default:
3795 report("Unknown key, press %s for help",
3796 get_key(view->keymap, REQ_VIEW_HELP));
3797 return TRUE;
3798 }
3800 return TRUE;
3801 }
3804 /*
3805 * View backend utilities
3806 */
3808 enum sort_field {
3809 ORDERBY_NAME,
3810 ORDERBY_DATE,
3811 ORDERBY_AUTHOR,
3812 };
3814 struct sort_state {
3815 const enum sort_field *fields;
3816 size_t size, current;
3817 bool reverse;
3818 };
3820 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3821 #define get_sort_field(state) ((state).fields[(state).current])
3822 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3824 static void
3825 sort_view(struct view *view, enum request request, struct sort_state *state,
3826 int (*compare)(const void *, const void *))
3827 {
3828 switch (request) {
3829 case REQ_TOGGLE_SORT_FIELD:
3830 state->current = (state->current + 1) % state->size;
3831 break;
3833 case REQ_TOGGLE_SORT_ORDER:
3834 state->reverse = !state->reverse;
3835 break;
3836 default:
3837 die("Not a sort request");
3838 }
3840 qsort(view->line, view->lines, sizeof(*view->line), compare);
3841 redraw_view(view);
3842 }
3844 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3846 /* Small author cache to reduce memory consumption. It uses binary
3847 * search to lookup or find place to position new entries. No entries
3848 * are ever freed. */
3849 static const char *
3850 get_author(const char *name)
3851 {
3852 static const char **authors;
3853 static size_t authors_size;
3854 int from = 0, to = authors_size - 1;
3856 while (from <= to) {
3857 size_t pos = (to + from) / 2;
3858 int cmp = strcmp(name, authors[pos]);
3860 if (!cmp)
3861 return authors[pos];
3863 if (cmp < 0)
3864 to = pos - 1;
3865 else
3866 from = pos + 1;
3867 }
3869 if (!realloc_authors(&authors, authors_size, 1))
3870 return NULL;
3871 name = strdup(name);
3872 if (!name)
3873 return NULL;
3875 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3876 authors[from] = name;
3877 authors_size++;
3879 return name;
3880 }
3882 static void
3883 parse_timesec(struct time *time, const char *sec)
3884 {
3885 time->sec = (time_t) atol(sec);
3886 }
3888 static void
3889 parse_timezone(struct time *time, const char *zone)
3890 {
3891 long tz;
3893 tz = ('0' - zone[1]) * 60 * 60 * 10;
3894 tz += ('0' - zone[2]) * 60 * 60;
3895 tz += ('0' - zone[3]) * 60 * 10;
3896 tz += ('0' - zone[4]) * 60;
3898 if (zone[0] == '-')
3899 tz = -tz;
3901 time->tz = tz;
3902 time->sec -= tz;
3903 }
3905 /* Parse author lines where the name may be empty:
3906 * author <email@address.tld> 1138474660 +0100
3907 */
3908 static void
3909 parse_author_line(char *ident, const char **author, struct time *time)
3910 {
3911 char *nameend = strchr(ident, '<');
3912 char *emailend = strchr(ident, '>');
3914 if (nameend && emailend)
3915 *nameend = *emailend = 0;
3916 ident = chomp_string(ident);
3917 if (!*ident) {
3918 if (nameend)
3919 ident = chomp_string(nameend + 1);
3920 if (!*ident)
3921 ident = "Unknown";
3922 }
3924 *author = get_author(ident);
3926 /* Parse epoch and timezone */
3927 if (emailend && emailend[1] == ' ') {
3928 char *secs = emailend + 2;
3929 char *zone = strchr(secs, ' ');
3931 parse_timesec(time, secs);
3933 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3934 parse_timezone(time, zone + 1);
3935 }
3936 }
3938 static bool
3939 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3940 {
3941 char rev[SIZEOF_REV];
3942 const char *revlist_argv[] = {
3943 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3944 };
3945 struct menu_item *items;
3946 char text[SIZEOF_STR];
3947 bool ok = TRUE;
3948 int i;
3950 items = calloc(*parents + 1, sizeof(*items));
3951 if (!items)
3952 return FALSE;
3954 for (i = 0; i < *parents; i++) {
3955 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3956 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3957 !(items[i].text = strdup(text))) {
3958 ok = FALSE;
3959 break;
3960 }
3961 }
3963 if (ok) {
3964 *parents = 0;
3965 ok = prompt_menu("Select parent", items, parents);
3966 }
3967 for (i = 0; items[i].text; i++)
3968 free((char *) items[i].text);
3969 free(items);
3970 return ok;
3971 }
3973 static bool
3974 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3975 {
3976 char buf[SIZEOF_STR * 4];
3977 const char *revlist_argv[] = {
3978 "git", "log", "--no-color", "-1",
3979 "--pretty=format:%P", id, "--", path, NULL
3980 };
3981 int parents;
3983 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3984 (parents = strlen(buf) / 40) < 0) {
3985 report("Failed to get parent information");
3986 return FALSE;
3988 } else if (parents == 0) {
3989 if (path)
3990 report("Path '%s' does not exist in the parent", path);
3991 else
3992 report("The selected commit has no parents");
3993 return FALSE;
3994 }
3996 if (parents == 1)
3997 parents = 0;
3998 else if (!open_commit_parent_menu(buf, &parents))
3999 return FALSE;
4001 string_copy_rev(rev, &buf[41 * parents]);
4002 return TRUE;
4003 }
4005 /*
4006 * Pager backend
4007 */
4009 static bool
4010 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4011 {
4012 char text[SIZEOF_STR];
4014 if (opt_line_number && draw_lineno(view, lineno))
4015 return TRUE;
4017 string_expand(text, sizeof(text), line->data, opt_tab_size);
4018 draw_text(view, line->type, text, TRUE);
4019 return TRUE;
4020 }
4022 static bool
4023 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4024 {
4025 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4026 char ref[SIZEOF_STR];
4028 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4029 return TRUE;
4031 /* This is the only fatal call, since it can "corrupt" the buffer. */
4032 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4033 return FALSE;
4035 return TRUE;
4036 }
4038 static void
4039 add_pager_refs(struct view *view, struct line *line)
4040 {
4041 char buf[SIZEOF_STR];
4042 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4043 struct ref_list *list;
4044 size_t bufpos = 0, i;
4045 const char *sep = "Refs: ";
4046 bool is_tag = FALSE;
4048 assert(line->type == LINE_COMMIT);
4050 list = get_ref_list(commit_id);
4051 if (!list) {
4052 if (view->type == VIEW_DIFF)
4053 goto try_add_describe_ref;
4054 return;
4055 }
4057 for (i = 0; i < list->size; i++) {
4058 struct ref *ref = list->refs[i];
4059 const char *fmt = ref->tag ? "%s[%s]" :
4060 ref->remote ? "%s<%s>" : "%s%s";
4062 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4063 return;
4064 sep = ", ";
4065 if (ref->tag)
4066 is_tag = TRUE;
4067 }
4069 if (!is_tag && view->type == VIEW_DIFF) {
4070 try_add_describe_ref:
4071 /* Add <tag>-g<commit_id> "fake" reference. */
4072 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4073 return;
4074 }
4076 if (bufpos == 0)
4077 return;
4079 add_line_text(view, buf, LINE_PP_REFS);
4080 }
4082 static bool
4083 pager_read(struct view *view, char *data)
4084 {
4085 struct line *line;
4087 if (!data)
4088 return TRUE;
4090 line = add_line_text(view, data, get_line_type(data));
4091 if (!line)
4092 return FALSE;
4094 if (line->type == LINE_COMMIT &&
4095 (view->type == VIEW_DIFF ||
4096 view->type == VIEW_LOG))
4097 add_pager_refs(view, line);
4099 return TRUE;
4100 }
4102 static enum request
4103 pager_request(struct view *view, enum request request, struct line *line)
4104 {
4105 int split = 0;
4107 if (request != REQ_ENTER)
4108 return request;
4110 if (line->type == LINE_COMMIT &&
4111 (view->type == VIEW_LOG ||
4112 view->type == VIEW_PAGER)) {
4113 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4114 split = 1;
4115 }
4117 /* Always scroll the view even if it was split. That way
4118 * you can use Enter to scroll through the log view and
4119 * split open each commit diff. */
4120 scroll_view(view, REQ_SCROLL_LINE_DOWN);
4122 /* FIXME: A minor workaround. Scrolling the view will call report("")
4123 * but if we are scrolling a non-current view this won't properly
4124 * update the view title. */
4125 if (split)
4126 update_view_title(view);
4128 return REQ_NONE;
4129 }
4131 static bool
4132 pager_grep(struct view *view, struct line *line)
4133 {
4134 const char *text[] = { line->data, NULL };
4136 return grep_text(view, text);
4137 }
4139 static void
4140 pager_select(struct view *view, struct line *line)
4141 {
4142 if (line->type == LINE_COMMIT) {
4143 char *text = (char *)line->data + STRING_SIZE("commit ");
4145 if (view->type != VIEW_PAGER)
4146 string_copy_rev(view->ref, text);
4147 string_copy_rev(ref_commit, text);
4148 }
4149 }
4151 static struct view_ops pager_ops = {
4152 "line",
4153 NULL,
4154 NULL,
4155 pager_read,
4156 pager_draw,
4157 pager_request,
4158 pager_grep,
4159 pager_select,
4160 };
4162 static const char *log_argv[SIZEOF_ARG] = {
4163 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4164 };
4166 static enum request
4167 log_request(struct view *view, enum request request, struct line *line)
4168 {
4169 switch (request) {
4170 case REQ_REFRESH:
4171 load_refs();
4172 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4173 return REQ_NONE;
4174 default:
4175 return pager_request(view, request, line);
4176 }
4177 }
4179 static struct view_ops log_ops = {
4180 "line",
4181 log_argv,
4182 NULL,
4183 pager_read,
4184 pager_draw,
4185 log_request,
4186 pager_grep,
4187 pager_select,
4188 };
4190 static const char *diff_argv[SIZEOF_ARG] = {
4191 "git", "show", "--pretty=fuller", "--no-color", "--root",
4192 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4193 };
4195 static struct view_ops diff_ops = {
4196 "line",
4197 diff_argv,
4198 NULL,
4199 pager_read,
4200 pager_draw,
4201 pager_request,
4202 pager_grep,
4203 pager_select,
4204 };
4206 /*
4207 * Help backend
4208 */
4210 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4212 static bool
4213 help_open_keymap_title(struct view *view, enum keymap keymap)
4214 {
4215 struct line *line;
4217 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4218 help_keymap_hidden[keymap] ? '+' : '-',
4219 enum_name(keymap_table[keymap]));
4220 if (line)
4221 line->other = keymap;
4223 return help_keymap_hidden[keymap];
4224 }
4226 static void
4227 help_open_keymap(struct view *view, enum keymap keymap)
4228 {
4229 const char *group = NULL;
4230 char buf[SIZEOF_STR];
4231 size_t bufpos;
4232 bool add_title = TRUE;
4233 int i;
4235 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4236 const char *key = NULL;
4238 if (req_info[i].request == REQ_NONE)
4239 continue;
4241 if (!req_info[i].request) {
4242 group = req_info[i].help;
4243 continue;
4244 }
4246 key = get_keys(keymap, req_info[i].request, TRUE);
4247 if (!key || !*key)
4248 continue;
4250 if (add_title && help_open_keymap_title(view, keymap))
4251 return;
4252 add_title = FALSE;
4254 if (group) {
4255 add_line_text(view, group, LINE_HELP_GROUP);
4256 group = NULL;
4257 }
4259 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4260 enum_name(req_info[i]), req_info[i].help);
4261 }
4263 group = "External commands:";
4265 for (i = 0; i < run_requests; i++) {
4266 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4267 const char *key;
4268 int argc;
4270 if (!req || req->keymap != keymap)
4271 continue;
4273 key = get_key_name(req->key);
4274 if (!*key)
4275 key = "(no key defined)";
4277 if (add_title && help_open_keymap_title(view, keymap))
4278 return;
4279 if (group) {
4280 add_line_text(view, group, LINE_HELP_GROUP);
4281 group = NULL;
4282 }
4284 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4285 if (!string_format_from(buf, &bufpos, "%s%s",
4286 argc ? " " : "", req->argv[argc]))
4287 return;
4289 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4290 }
4291 }
4293 static bool
4294 help_open(struct view *view)
4295 {
4296 enum keymap keymap;
4298 reset_view(view);
4299 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4300 add_line_text(view, "", LINE_DEFAULT);
4302 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4303 help_open_keymap(view, keymap);
4305 return TRUE;
4306 }
4308 static enum request
4309 help_request(struct view *view, enum request request, struct line *line)
4310 {
4311 switch (request) {
4312 case REQ_ENTER:
4313 if (line->type == LINE_HELP_KEYMAP) {
4314 help_keymap_hidden[line->other] =
4315 !help_keymap_hidden[line->other];
4316 view->p_restore = TRUE;
4317 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4318 }
4320 return REQ_NONE;
4321 default:
4322 return pager_request(view, request, line);
4323 }
4324 }
4326 static struct view_ops help_ops = {
4327 "line",
4328 NULL,
4329 help_open,
4330 NULL,
4331 pager_draw,
4332 help_request,
4333 pager_grep,
4334 pager_select,
4335 };
4338 /*
4339 * Tree backend
4340 */
4342 struct tree_stack_entry {
4343 struct tree_stack_entry *prev; /* Entry below this in the stack */
4344 unsigned long lineno; /* Line number to restore */
4345 char *name; /* Position of name in opt_path */
4346 };
4348 /* The top of the path stack. */
4349 static struct tree_stack_entry *tree_stack = NULL;
4350 unsigned long tree_lineno = 0;
4352 static void
4353 pop_tree_stack_entry(void)
4354 {
4355 struct tree_stack_entry *entry = tree_stack;
4357 tree_lineno = entry->lineno;
4358 entry->name[0] = 0;
4359 tree_stack = entry->prev;
4360 free(entry);
4361 }
4363 static void
4364 push_tree_stack_entry(const char *name, unsigned long lineno)
4365 {
4366 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4367 size_t pathlen = strlen(opt_path);
4369 if (!entry)
4370 return;
4372 entry->prev = tree_stack;
4373 entry->name = opt_path + pathlen;
4374 tree_stack = entry;
4376 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4377 pop_tree_stack_entry();
4378 return;
4379 }
4381 /* Move the current line to the first tree entry. */
4382 tree_lineno = 1;
4383 entry->lineno = lineno;
4384 }
4386 /* Parse output from git-ls-tree(1):
4387 *
4388 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4389 */
4391 #define SIZEOF_TREE_ATTR \
4392 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4394 #define SIZEOF_TREE_MODE \
4395 STRING_SIZE("100644 ")
4397 #define TREE_ID_OFFSET \
4398 STRING_SIZE("100644 blob ")
4400 struct tree_entry {
4401 char id[SIZEOF_REV];
4402 mode_t mode;
4403 struct time time; /* Date from the author ident. */
4404 const char *author; /* Author of the commit. */
4405 char name[1];
4406 };
4408 static const char *
4409 tree_path(const struct line *line)
4410 {
4411 return ((struct tree_entry *) line->data)->name;
4412 }
4414 static int
4415 tree_compare_entry(const struct line *line1, const struct line *line2)
4416 {
4417 if (line1->type != line2->type)
4418 return line1->type == LINE_TREE_DIR ? -1 : 1;
4419 return strcmp(tree_path(line1), tree_path(line2));
4420 }
4422 static const enum sort_field tree_sort_fields[] = {
4423 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4424 };
4425 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4427 static int
4428 tree_compare(const void *l1, const void *l2)
4429 {
4430 const struct line *line1 = (const struct line *) l1;
4431 const struct line *line2 = (const struct line *) l2;
4432 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4433 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4435 if (line1->type == LINE_TREE_HEAD)
4436 return -1;
4437 if (line2->type == LINE_TREE_HEAD)
4438 return 1;
4440 switch (get_sort_field(tree_sort_state)) {
4441 case ORDERBY_DATE:
4442 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4444 case ORDERBY_AUTHOR:
4445 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4447 case ORDERBY_NAME:
4448 default:
4449 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4450 }
4451 }
4454 static struct line *
4455 tree_entry(struct view *view, enum line_type type, const char *path,
4456 const char *mode, const char *id)
4457 {
4458 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4459 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4461 if (!entry || !line) {
4462 free(entry);
4463 return NULL;
4464 }
4466 strncpy(entry->name, path, strlen(path));
4467 if (mode)
4468 entry->mode = strtoul(mode, NULL, 8);
4469 if (id)
4470 string_copy_rev(entry->id, id);
4472 return line;
4473 }
4475 static bool
4476 tree_read_date(struct view *view, char *text, bool *read_date)
4477 {
4478 static const char *author_name;
4479 static struct time author_time;
4481 if (!text && *read_date) {
4482 *read_date = FALSE;
4483 return TRUE;
4485 } else if (!text) {
4486 char *path = *opt_path ? opt_path : ".";
4487 /* Find next entry to process */
4488 const char *log_file[] = {
4489 "git", "log", "--no-color", "--pretty=raw",
4490 "--cc", "--raw", view->id, "--", path, NULL
4491 };
4493 if (!view->lines) {
4494 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4495 report("Tree is empty");
4496 return TRUE;
4497 }
4499 if (!start_update(view, log_file, opt_cdup)) {
4500 report("Failed to load tree data");
4501 return TRUE;
4502 }
4504 *read_date = TRUE;
4505 return FALSE;
4507 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4508 parse_author_line(text + STRING_SIZE("author "),
4509 &author_name, &author_time);
4511 } else if (*text == ':') {
4512 char *pos;
4513 size_t annotated = 1;
4514 size_t i;
4516 pos = strchr(text, '\t');
4517 if (!pos)
4518 return TRUE;
4519 text = pos + 1;
4520 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4521 text += strlen(opt_path);
4522 pos = strchr(text, '/');
4523 if (pos)
4524 *pos = 0;
4526 for (i = 1; i < view->lines; i++) {
4527 struct line *line = &view->line[i];
4528 struct tree_entry *entry = line->data;
4530 annotated += !!entry->author;
4531 if (entry->author || strcmp(entry->name, text))
4532 continue;
4534 entry->author = author_name;
4535 entry->time = author_time;
4536 line->dirty = 1;
4537 break;
4538 }
4540 if (annotated == view->lines)
4541 io_kill(view->pipe);
4542 }
4543 return TRUE;
4544 }
4546 static bool
4547 tree_read(struct view *view, char *text)
4548 {
4549 static bool read_date = FALSE;
4550 struct tree_entry *data;
4551 struct line *entry, *line;
4552 enum line_type type;
4553 size_t textlen = text ? strlen(text) : 0;
4554 char *path = text + SIZEOF_TREE_ATTR;
4556 if (read_date || !text)
4557 return tree_read_date(view, text, &read_date);
4559 if (textlen <= SIZEOF_TREE_ATTR)
4560 return FALSE;
4561 if (view->lines == 0 &&
4562 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4563 return FALSE;
4565 /* Strip the path part ... */
4566 if (*opt_path) {
4567 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4568 size_t striplen = strlen(opt_path);
4570 if (pathlen > striplen)
4571 memmove(path, path + striplen,
4572 pathlen - striplen + 1);
4574 /* Insert "link" to parent directory. */
4575 if (view->lines == 1 &&
4576 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4577 return FALSE;
4578 }
4580 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4581 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4582 if (!entry)
4583 return FALSE;
4584 data = entry->data;
4586 /* Skip "Directory ..." and ".." line. */
4587 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4588 if (tree_compare_entry(line, entry) <= 0)
4589 continue;
4591 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4593 line->data = data;
4594 line->type = type;
4595 for (; line <= entry; line++)
4596 line->dirty = line->cleareol = 1;
4597 return TRUE;
4598 }
4600 if (tree_lineno > view->lineno) {
4601 view->lineno = tree_lineno;
4602 tree_lineno = 0;
4603 }
4605 return TRUE;
4606 }
4608 static bool
4609 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4610 {
4611 struct tree_entry *entry = line->data;
4613 if (line->type == LINE_TREE_HEAD) {
4614 if (draw_text(view, line->type, "Directory path /", TRUE))
4615 return TRUE;
4616 } else {
4617 if (draw_mode(view, entry->mode))
4618 return TRUE;
4620 if (opt_author && draw_author(view, entry->author))
4621 return TRUE;
4623 if (opt_date && draw_date(view, &entry->time))
4624 return TRUE;
4625 }
4626 if (draw_text(view, line->type, entry->name, TRUE))
4627 return TRUE;
4628 return TRUE;
4629 }
4631 static void
4632 open_blob_editor(const char *id)
4633 {
4634 const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4635 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4636 int fd = mkstemp(file);
4638 if (fd == -1)
4639 report("Failed to create temporary file");
4640 else if (!io_run_append(blob_argv, fd))
4641 report("Failed to save blob data to file");
4642 else
4643 open_editor(file);
4644 if (fd != -1)
4645 unlink(file);
4646 }
4648 static enum request
4649 tree_request(struct view *view, enum request request, struct line *line)
4650 {
4651 enum open_flags flags;
4652 struct tree_entry *entry = line->data;
4654 switch (request) {
4655 case REQ_VIEW_BLAME:
4656 if (line->type != LINE_TREE_FILE) {
4657 report("Blame only supported for files");
4658 return REQ_NONE;
4659 }
4661 string_copy(opt_ref, view->vid);
4662 return request;
4664 case REQ_EDIT:
4665 if (line->type != LINE_TREE_FILE) {
4666 report("Edit only supported for files");
4667 } else if (!is_head_commit(view->vid)) {
4668 open_blob_editor(entry->id);
4669 } else {
4670 open_editor(opt_file);
4671 }
4672 return REQ_NONE;
4674 case REQ_TOGGLE_SORT_FIELD:
4675 case REQ_TOGGLE_SORT_ORDER:
4676 sort_view(view, request, &tree_sort_state, tree_compare);
4677 return REQ_NONE;
4679 case REQ_PARENT:
4680 if (!*opt_path) {
4681 /* quit view if at top of tree */
4682 return REQ_VIEW_CLOSE;
4683 }
4684 /* fake 'cd ..' */
4685 line = &view->line[1];
4686 break;
4688 case REQ_ENTER:
4689 break;
4691 default:
4692 return request;
4693 }
4695 /* Cleanup the stack if the tree view is at a different tree. */
4696 while (!*opt_path && tree_stack)
4697 pop_tree_stack_entry();
4699 switch (line->type) {
4700 case LINE_TREE_DIR:
4701 /* Depending on whether it is a subdirectory or parent link
4702 * mangle the path buffer. */
4703 if (line == &view->line[1] && *opt_path) {
4704 pop_tree_stack_entry();
4706 } else {
4707 const char *basename = tree_path(line);
4709 push_tree_stack_entry(basename, view->lineno);
4710 }
4712 /* Trees and subtrees share the same ID, so they are not not
4713 * unique like blobs. */
4714 flags = OPEN_RELOAD;
4715 request = REQ_VIEW_TREE;
4716 break;
4718 case LINE_TREE_FILE:
4719 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4720 request = REQ_VIEW_BLOB;
4721 break;
4723 default:
4724 return REQ_NONE;
4725 }
4727 open_view(view, request, flags);
4728 if (request == REQ_VIEW_TREE)
4729 view->lineno = tree_lineno;
4731 return REQ_NONE;
4732 }
4734 static bool
4735 tree_grep(struct view *view, struct line *line)
4736 {
4737 struct tree_entry *entry = line->data;
4738 const char *text[] = {
4739 entry->name,
4740 opt_author ? entry->author : "",
4741 mkdate(&entry->time, opt_date),
4742 NULL
4743 };
4745 return grep_text(view, text);
4746 }
4748 static void
4749 tree_select(struct view *view, struct line *line)
4750 {
4751 struct tree_entry *entry = line->data;
4753 if (line->type == LINE_TREE_FILE) {
4754 string_copy_rev(ref_blob, entry->id);
4755 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4757 } else if (line->type != LINE_TREE_DIR) {
4758 return;
4759 }
4761 string_copy_rev(view->ref, entry->id);
4762 }
4764 static bool
4765 tree_prepare(struct view *view)
4766 {
4767 if (view->lines == 0 && opt_prefix[0]) {
4768 char *pos = opt_prefix;
4770 while (pos && *pos) {
4771 char *end = strchr(pos, '/');
4773 if (end)
4774 *end = 0;
4775 push_tree_stack_entry(pos, 0);
4776 pos = end;
4777 if (end) {
4778 *end = '/';
4779 pos++;
4780 }
4781 }
4783 } else if (strcmp(view->vid, view->id)) {
4784 opt_path[0] = 0;
4785 }
4787 return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4788 }
4790 static const char *tree_argv[SIZEOF_ARG] = {
4791 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4792 };
4794 static struct view_ops tree_ops = {
4795 "file",
4796 tree_argv,
4797 NULL,
4798 tree_read,
4799 tree_draw,
4800 tree_request,
4801 tree_grep,
4802 tree_select,
4803 tree_prepare,
4804 };
4806 static bool
4807 blob_read(struct view *view, char *line)
4808 {
4809 if (!line)
4810 return TRUE;
4811 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4812 }
4814 static enum request
4815 blob_request(struct view *view, enum request request, struct line *line)
4816 {
4817 switch (request) {
4818 case REQ_EDIT:
4819 open_blob_editor(view->vid);
4820 return REQ_NONE;
4821 default:
4822 return pager_request(view, request, line);
4823 }
4824 }
4826 static const char *blob_argv[SIZEOF_ARG] = {
4827 "git", "cat-file", "blob", "%(blob)", NULL
4828 };
4830 static struct view_ops blob_ops = {
4831 "line",
4832 blob_argv,
4833 NULL,
4834 blob_read,
4835 pager_draw,
4836 blob_request,
4837 pager_grep,
4838 pager_select,
4839 };
4841 /*
4842 * Blame backend
4843 *
4844 * Loading the blame view is a two phase job:
4845 *
4846 * 1. File content is read either using opt_file from the
4847 * filesystem or using git-cat-file.
4848 * 2. Then blame information is incrementally added by
4849 * reading output from git-blame.
4850 */
4852 struct blame_commit {
4853 char id[SIZEOF_REV]; /* SHA1 ID. */
4854 char title[128]; /* First line of the commit message. */
4855 const char *author; /* Author of the commit. */
4856 struct time time; /* Date from the author ident. */
4857 char filename[128]; /* Name of file. */
4858 bool has_previous; /* Was a "previous" line detected. */
4859 };
4861 struct blame {
4862 struct blame_commit *commit;
4863 unsigned long lineno;
4864 char text[1];
4865 };
4867 static bool
4868 blame_open(struct view *view)
4869 {
4870 char path[SIZEOF_STR];
4872 if (!view->prev && *opt_prefix) {
4873 string_copy(path, opt_file);
4874 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4875 return FALSE;
4876 }
4878 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4879 const char *blame_cat_file_argv[] = {
4880 "git", "cat-file", "blob", path, NULL
4881 };
4883 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4884 !start_update(view, blame_cat_file_argv, opt_cdup))
4885 return FALSE;
4886 }
4888 setup_update(view, opt_file);
4889 string_format(view->ref, "%s ...", opt_file);
4891 return TRUE;
4892 }
4894 static struct blame_commit *
4895 get_blame_commit(struct view *view, const char *id)
4896 {
4897 size_t i;
4899 for (i = 0; i < view->lines; i++) {
4900 struct blame *blame = view->line[i].data;
4902 if (!blame->commit)
4903 continue;
4905 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4906 return blame->commit;
4907 }
4909 {
4910 struct blame_commit *commit = calloc(1, sizeof(*commit));
4912 if (commit)
4913 string_ncopy(commit->id, id, SIZEOF_REV);
4914 return commit;
4915 }
4916 }
4918 static bool
4919 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4920 {
4921 const char *pos = *posref;
4923 *posref = NULL;
4924 pos = strchr(pos + 1, ' ');
4925 if (!pos || !isdigit(pos[1]))
4926 return FALSE;
4927 *number = atoi(pos + 1);
4928 if (*number < min || *number > max)
4929 return FALSE;
4931 *posref = pos;
4932 return TRUE;
4933 }
4935 static struct blame_commit *
4936 parse_blame_commit(struct view *view, const char *text, int *blamed)
4937 {
4938 struct blame_commit *commit;
4939 struct blame *blame;
4940 const char *pos = text + SIZEOF_REV - 2;
4941 size_t orig_lineno = 0;
4942 size_t lineno;
4943 size_t group;
4945 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4946 return NULL;
4948 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4949 !parse_number(&pos, &lineno, 1, view->lines) ||
4950 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4951 return NULL;
4953 commit = get_blame_commit(view, text);
4954 if (!commit)
4955 return NULL;
4957 *blamed += group;
4958 while (group--) {
4959 struct line *line = &view->line[lineno + group - 1];
4961 blame = line->data;
4962 blame->commit = commit;
4963 blame->lineno = orig_lineno + group - 1;
4964 line->dirty = 1;
4965 }
4967 return commit;
4968 }
4970 static bool
4971 blame_read_file(struct view *view, const char *line, bool *read_file)
4972 {
4973 if (!line) {
4974 const char *blame_argv[] = {
4975 "git", "blame", "--incremental",
4976 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4977 };
4979 if (view->lines == 0 && !view->prev)
4980 die("No blame exist for %s", view->vid);
4982 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4983 report("Failed to load blame data");
4984 return TRUE;
4985 }
4987 *read_file = FALSE;
4988 return FALSE;
4990 } else {
4991 size_t linelen = strlen(line);
4992 struct blame *blame = malloc(sizeof(*blame) + linelen);
4994 if (!blame)
4995 return FALSE;
4997 blame->commit = NULL;
4998 strncpy(blame->text, line, linelen);
4999 blame->text[linelen] = 0;
5000 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5001 }
5002 }
5004 static bool
5005 match_blame_header(const char *name, char **line)
5006 {
5007 size_t namelen = strlen(name);
5008 bool matched = !strncmp(name, *line, namelen);
5010 if (matched)
5011 *line += namelen;
5013 return matched;
5014 }
5016 static bool
5017 blame_read(struct view *view, char *line)
5018 {
5019 static struct blame_commit *commit = NULL;
5020 static int blamed = 0;
5021 static bool read_file = TRUE;
5023 if (read_file)
5024 return blame_read_file(view, line, &read_file);
5026 if (!line) {
5027 /* Reset all! */
5028 commit = NULL;
5029 blamed = 0;
5030 read_file = TRUE;
5031 string_format(view->ref, "%s", view->vid);
5032 if (view_is_displayed(view)) {
5033 update_view_title(view);
5034 redraw_view_from(view, 0);
5035 }
5036 return TRUE;
5037 }
5039 if (!commit) {
5040 commit = parse_blame_commit(view, line, &blamed);
5041 string_format(view->ref, "%s %2d%%", view->vid,
5042 view->lines ? blamed * 100 / view->lines : 0);
5044 } else if (match_blame_header("author ", &line)) {
5045 commit->author = get_author(line);
5047 } else if (match_blame_header("author-time ", &line)) {
5048 parse_timesec(&commit->time, line);
5050 } else if (match_blame_header("author-tz ", &line)) {
5051 parse_timezone(&commit->time, line);
5053 } else if (match_blame_header("summary ", &line)) {
5054 string_ncopy(commit->title, line, strlen(line));
5056 } else if (match_blame_header("previous ", &line)) {
5057 commit->has_previous = TRUE;
5059 } else if (match_blame_header("filename ", &line)) {
5060 string_ncopy(commit->filename, line, strlen(line));
5061 commit = NULL;
5062 }
5064 return TRUE;
5065 }
5067 static bool
5068 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5069 {
5070 struct blame *blame = line->data;
5071 struct time *time = NULL;
5072 const char *id = NULL, *author = NULL;
5073 char text[SIZEOF_STR];
5075 if (blame->commit && *blame->commit->filename) {
5076 id = blame->commit->id;
5077 author = blame->commit->author;
5078 time = &blame->commit->time;
5079 }
5081 if (opt_date && draw_date(view, time))
5082 return TRUE;
5084 if (opt_author && draw_author(view, author))
5085 return TRUE;
5087 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5088 return TRUE;
5090 if (draw_lineno(view, lineno))
5091 return TRUE;
5093 string_expand(text, sizeof(text), blame->text, opt_tab_size);
5094 draw_text(view, LINE_DEFAULT, text, TRUE);
5095 return TRUE;
5096 }
5098 static bool
5099 check_blame_commit(struct blame *blame, bool check_null_id)
5100 {
5101 if (!blame->commit)
5102 report("Commit data not loaded yet");
5103 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5104 report("No commit exist for the selected line");
5105 else
5106 return TRUE;
5107 return FALSE;
5108 }
5110 static void
5111 setup_blame_parent_line(struct view *view, struct blame *blame)
5112 {
5113 const char *diff_tree_argv[] = {
5114 "git", "diff-tree", "-U0", blame->commit->id,
5115 "--", blame->commit->filename, NULL
5116 };
5117 struct io io;
5118 int parent_lineno = -1;
5119 int blamed_lineno = -1;
5120 char *line;
5122 if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
5123 return;
5125 while ((line = io_get(&io, '\n', TRUE))) {
5126 if (*line == '@') {
5127 char *pos = strchr(line, '+');
5129 parent_lineno = atoi(line + 4);
5130 if (pos)
5131 blamed_lineno = atoi(pos + 1);
5133 } else if (*line == '+' && parent_lineno != -1) {
5134 if (blame->lineno == blamed_lineno - 1 &&
5135 !strcmp(blame->text, line + 1)) {
5136 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5137 break;
5138 }
5139 blamed_lineno++;
5140 }
5141 }
5143 io_done(&io);
5144 }
5146 static enum request
5147 blame_request(struct view *view, enum request request, struct line *line)
5148 {
5149 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5150 struct blame *blame = line->data;
5152 switch (request) {
5153 case REQ_VIEW_BLAME:
5154 if (check_blame_commit(blame, TRUE)) {
5155 string_copy(opt_ref, blame->commit->id);
5156 string_copy(opt_file, blame->commit->filename);
5157 if (blame->lineno)
5158 view->lineno = blame->lineno;
5159 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5160 }
5161 break;
5163 case REQ_PARENT:
5164 if (check_blame_commit(blame, TRUE) &&
5165 select_commit_parent(blame->commit->id, opt_ref,
5166 blame->commit->filename)) {
5167 string_copy(opt_file, blame->commit->filename);
5168 setup_blame_parent_line(view, blame);
5169 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5170 }
5171 break;
5173 case REQ_ENTER:
5174 if (!check_blame_commit(blame, FALSE))
5175 break;
5177 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5178 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5179 break;
5181 if (!strcmp(blame->commit->id, NULL_ID)) {
5182 struct view *diff = VIEW(REQ_VIEW_DIFF);
5183 const char *diff_index_argv[] = {
5184 "git", "diff-index", "--root", "--patch-with-stat",
5185 "-C", "-M", "HEAD", "--", view->vid, NULL
5186 };
5188 if (!blame->commit->has_previous) {
5189 diff_index_argv[1] = "diff";
5190 diff_index_argv[2] = "--no-color";
5191 diff_index_argv[6] = "--";
5192 diff_index_argv[7] = "/dev/null";
5193 }
5195 if (!prepare_update(diff, diff_index_argv, NULL)) {
5196 report("Failed to allocate diff command");
5197 break;
5198 }
5199 flags |= OPEN_PREPARED;
5200 }
5202 open_view(view, REQ_VIEW_DIFF, flags);
5203 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5204 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5205 break;
5207 default:
5208 return request;
5209 }
5211 return REQ_NONE;
5212 }
5214 static bool
5215 blame_grep(struct view *view, struct line *line)
5216 {
5217 struct blame *blame = line->data;
5218 struct blame_commit *commit = blame->commit;
5219 const char *text[] = {
5220 blame->text,
5221 commit ? commit->title : "",
5222 commit ? commit->id : "",
5223 commit && opt_author ? commit->author : "",
5224 commit ? mkdate(&commit->time, opt_date) : "",
5225 NULL
5226 };
5228 return grep_text(view, text);
5229 }
5231 static void
5232 blame_select(struct view *view, struct line *line)
5233 {
5234 struct blame *blame = line->data;
5235 struct blame_commit *commit = blame->commit;
5237 if (!commit)
5238 return;
5240 if (!strcmp(commit->id, NULL_ID))
5241 string_ncopy(ref_commit, "HEAD", 4);
5242 else
5243 string_copy_rev(ref_commit, commit->id);
5244 }
5246 static struct view_ops blame_ops = {
5247 "line",
5248 NULL,
5249 blame_open,
5250 blame_read,
5251 blame_draw,
5252 blame_request,
5253 blame_grep,
5254 blame_select,
5255 };
5257 /*
5258 * Branch backend
5259 */
5261 struct branch {
5262 const char *author; /* Author of the last commit. */
5263 struct time time; /* Date of the last activity. */
5264 const struct ref *ref; /* Name and commit ID information. */
5265 };
5267 static const struct ref branch_all;
5269 static const enum sort_field branch_sort_fields[] = {
5270 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5271 };
5272 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5274 static int
5275 branch_compare(const void *l1, const void *l2)
5276 {
5277 const struct branch *branch1 = ((const struct line *) l1)->data;
5278 const struct branch *branch2 = ((const struct line *) l2)->data;
5280 switch (get_sort_field(branch_sort_state)) {
5281 case ORDERBY_DATE:
5282 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5284 case ORDERBY_AUTHOR:
5285 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5287 case ORDERBY_NAME:
5288 default:
5289 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5290 }
5291 }
5293 static bool
5294 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5295 {
5296 struct branch *branch = line->data;
5297 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5299 if (opt_date && draw_date(view, &branch->time))
5300 return TRUE;
5302 if (opt_author && draw_author(view, branch->author))
5303 return TRUE;
5305 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5306 return TRUE;
5307 }
5309 static enum request
5310 branch_request(struct view *view, enum request request, struct line *line)
5311 {
5312 struct branch *branch = line->data;
5314 switch (request) {
5315 case REQ_REFRESH:
5316 load_refs();
5317 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5318 return REQ_NONE;
5320 case REQ_TOGGLE_SORT_FIELD:
5321 case REQ_TOGGLE_SORT_ORDER:
5322 sort_view(view, request, &branch_sort_state, branch_compare);
5323 return REQ_NONE;
5325 case REQ_ENTER:
5326 if (branch->ref == &branch_all) {
5327 const char *all_branches_argv[] = {
5328 "git", "log", "--no-color", "--pretty=raw", "--parents",
5329 "--topo-order", "--all", NULL
5330 };
5331 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5333 if (!prepare_update(main_view, all_branches_argv, NULL)) {
5334 report("Failed to load view of all branches");
5335 return REQ_NONE;
5336 }
5337 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5338 } else {
5339 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5340 }
5341 return REQ_NONE;
5343 default:
5344 return request;
5345 }
5346 }
5348 static bool
5349 branch_read(struct view *view, char *line)
5350 {
5351 static char id[SIZEOF_REV];
5352 struct branch *reference;
5353 size_t i;
5355 if (!line)
5356 return TRUE;
5358 switch (get_line_type(line)) {
5359 case LINE_COMMIT:
5360 string_copy_rev(id, line + STRING_SIZE("commit "));
5361 return TRUE;
5363 case LINE_AUTHOR:
5364 for (i = 0, reference = NULL; i < view->lines; i++) {
5365 struct branch *branch = view->line[i].data;
5367 if (strcmp(branch->ref->id, id))
5368 continue;
5370 view->line[i].dirty = TRUE;
5371 if (reference) {
5372 branch->author = reference->author;
5373 branch->time = reference->time;
5374 continue;
5375 }
5377 parse_author_line(line + STRING_SIZE("author "),
5378 &branch->author, &branch->time);
5379 reference = branch;
5380 }
5381 return TRUE;
5383 default:
5384 return TRUE;
5385 }
5387 }
5389 static bool
5390 branch_open_visitor(void *data, const struct ref *ref)
5391 {
5392 struct view *view = data;
5393 struct branch *branch;
5395 if (ref->tag || ref->ltag || ref->remote)
5396 return TRUE;
5398 branch = calloc(1, sizeof(*branch));
5399 if (!branch)
5400 return FALSE;
5402 branch->ref = ref;
5403 return !!add_line_data(view, branch, LINE_DEFAULT);
5404 }
5406 static bool
5407 branch_open(struct view *view)
5408 {
5409 const char *branch_log[] = {
5410 "git", "log", "--no-color", "--pretty=raw",
5411 "--simplify-by-decoration", "--all", NULL
5412 };
5414 if (!start_update(view, branch_log, NULL)) {
5415 report("Failed to load branch data");
5416 return TRUE;
5417 }
5419 setup_update(view, view->id);
5420 branch_open_visitor(view, &branch_all);
5421 foreach_ref(branch_open_visitor, view);
5422 view->p_restore = TRUE;
5424 return TRUE;
5425 }
5427 static bool
5428 branch_grep(struct view *view, struct line *line)
5429 {
5430 struct branch *branch = line->data;
5431 const char *text[] = {
5432 branch->ref->name,
5433 branch->author,
5434 NULL
5435 };
5437 return grep_text(view, text);
5438 }
5440 static void
5441 branch_select(struct view *view, struct line *line)
5442 {
5443 struct branch *branch = line->data;
5445 string_copy_rev(view->ref, branch->ref->id);
5446 string_copy_rev(ref_commit, branch->ref->id);
5447 string_copy_rev(ref_head, branch->ref->id);
5448 string_copy_rev(ref_branch, branch->ref->name);
5449 }
5451 static struct view_ops branch_ops = {
5452 "branch",
5453 NULL,
5454 branch_open,
5455 branch_read,
5456 branch_draw,
5457 branch_request,
5458 branch_grep,
5459 branch_select,
5460 };
5462 /*
5463 * Status backend
5464 */
5466 struct status {
5467 char status;
5468 struct {
5469 mode_t mode;
5470 char rev[SIZEOF_REV];
5471 char name[SIZEOF_STR];
5472 } old;
5473 struct {
5474 mode_t mode;
5475 char rev[SIZEOF_REV];
5476 char name[SIZEOF_STR];
5477 } new;
5478 };
5480 static char status_onbranch[SIZEOF_STR];
5481 static struct status stage_status;
5482 static enum line_type stage_line_type;
5483 static size_t stage_chunks;
5484 static int *stage_chunk;
5486 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5488 /* This should work even for the "On branch" line. */
5489 static inline bool
5490 status_has_none(struct view *view, struct line *line)
5491 {
5492 return line < view->line + view->lines && !line[1].data;
5493 }
5495 /* Get fields from the diff line:
5496 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5497 */
5498 static inline bool
5499 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5500 {
5501 const char *old_mode = buf + 1;
5502 const char *new_mode = buf + 8;
5503 const char *old_rev = buf + 15;
5504 const char *new_rev = buf + 56;
5505 const char *status = buf + 97;
5507 if (bufsize < 98 ||
5508 old_mode[-1] != ':' ||
5509 new_mode[-1] != ' ' ||
5510 old_rev[-1] != ' ' ||
5511 new_rev[-1] != ' ' ||
5512 status[-1] != ' ')
5513 return FALSE;
5515 file->status = *status;
5517 string_copy_rev(file->old.rev, old_rev);
5518 string_copy_rev(file->new.rev, new_rev);
5520 file->old.mode = strtoul(old_mode, NULL, 8);
5521 file->new.mode = strtoul(new_mode, NULL, 8);
5523 file->old.name[0] = file->new.name[0] = 0;
5525 return TRUE;
5526 }
5528 static bool
5529 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5530 {
5531 struct status *unmerged = NULL;
5532 char *buf;
5533 struct io io;
5535 if (!io_run(&io, IO_RD, opt_cdup, argv))
5536 return FALSE;
5538 add_line_data(view, NULL, type);
5540 while ((buf = io_get(&io, 0, TRUE))) {
5541 struct status *file = unmerged;
5543 if (!file) {
5544 file = calloc(1, sizeof(*file));
5545 if (!file || !add_line_data(view, file, type))
5546 goto error_out;
5547 }
5549 /* Parse diff info part. */
5550 if (status) {
5551 file->status = status;
5552 if (status == 'A')
5553 string_copy(file->old.rev, NULL_ID);
5555 } else if (!file->status || file == unmerged) {
5556 if (!status_get_diff(file, buf, strlen(buf)))
5557 goto error_out;
5559 buf = io_get(&io, 0, TRUE);
5560 if (!buf)
5561 break;
5563 /* Collapse all modified entries that follow an
5564 * associated unmerged entry. */
5565 if (unmerged == file) {
5566 unmerged->status = 'U';
5567 unmerged = NULL;
5568 } else if (file->status == 'U') {
5569 unmerged = file;
5570 }
5571 }
5573 /* Grab the old name for rename/copy. */
5574 if (!*file->old.name &&
5575 (file->status == 'R' || file->status == 'C')) {
5576 string_ncopy(file->old.name, buf, strlen(buf));
5578 buf = io_get(&io, 0, TRUE);
5579 if (!buf)
5580 break;
5581 }
5583 /* git-ls-files just delivers a NUL separated list of
5584 * file names similar to the second half of the
5585 * git-diff-* output. */
5586 string_ncopy(file->new.name, buf, strlen(buf));
5587 if (!*file->old.name)
5588 string_copy(file->old.name, file->new.name);
5589 file = NULL;
5590 }
5592 if (io_error(&io)) {
5593 error_out:
5594 io_done(&io);
5595 return FALSE;
5596 }
5598 if (!view->line[view->lines - 1].data)
5599 add_line_data(view, NULL, LINE_STAT_NONE);
5601 io_done(&io);
5602 return TRUE;
5603 }
5605 /* Don't show unmerged entries in the staged section. */
5606 static const char *status_diff_index_argv[] = {
5607 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5608 "--cached", "-M", "HEAD", NULL
5609 };
5611 static const char *status_diff_files_argv[] = {
5612 "git", "diff-files", "-z", NULL
5613 };
5615 static const char *status_list_other_argv[] = {
5616 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5617 };
5619 static const char *status_list_no_head_argv[] = {
5620 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5621 };
5623 static const char *update_index_argv[] = {
5624 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5625 };
5627 /* Restore the previous line number to stay in the context or select a
5628 * line with something that can be updated. */
5629 static void
5630 status_restore(struct view *view)
5631 {
5632 if (view->p_lineno >= view->lines)
5633 view->p_lineno = view->lines - 1;
5634 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5635 view->p_lineno++;
5636 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5637 view->p_lineno--;
5639 /* If the above fails, always skip the "On branch" line. */
5640 if (view->p_lineno < view->lines)
5641 view->lineno = view->p_lineno;
5642 else
5643 view->lineno = 1;
5645 if (view->lineno < view->offset)
5646 view->offset = view->lineno;
5647 else if (view->offset + view->height <= view->lineno)
5648 view->offset = view->lineno - view->height + 1;
5650 view->p_restore = FALSE;
5651 }
5653 static void
5654 status_update_onbranch(void)
5655 {
5656 static const char *paths[][2] = {
5657 { "rebase-apply/rebasing", "Rebasing" },
5658 { "rebase-apply/applying", "Applying mailbox" },
5659 { "rebase-apply/", "Rebasing mailbox" },
5660 { "rebase-merge/interactive", "Interactive rebase" },
5661 { "rebase-merge/", "Rebase merge" },
5662 { "MERGE_HEAD", "Merging" },
5663 { "BISECT_LOG", "Bisecting" },
5664 { "HEAD", "On branch" },
5665 };
5666 char buf[SIZEOF_STR];
5667 struct stat stat;
5668 int i;
5670 if (is_initial_commit()) {
5671 string_copy(status_onbranch, "Initial commit");
5672 return;
5673 }
5675 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5676 char *head = opt_head;
5678 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5679 lstat(buf, &stat) < 0)
5680 continue;
5682 if (!*opt_head) {
5683 struct io io;
5685 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5686 io_read_buf(&io, buf, sizeof(buf))) {
5687 head = buf;
5688 if (!prefixcmp(head, "refs/heads/"))
5689 head += STRING_SIZE("refs/heads/");
5690 }
5691 }
5693 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5694 string_copy(status_onbranch, opt_head);
5695 return;
5696 }
5698 string_copy(status_onbranch, "Not currently on any branch");
5699 }
5701 /* First parse staged info using git-diff-index(1), then parse unstaged
5702 * info using git-diff-files(1), and finally untracked files using
5703 * git-ls-files(1). */
5704 static bool
5705 status_open(struct view *view)
5706 {
5707 reset_view(view);
5709 add_line_data(view, NULL, LINE_STAT_HEAD);
5710 status_update_onbranch();
5712 io_run_bg(update_index_argv);
5714 if (is_initial_commit()) {
5715 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5716 return FALSE;
5717 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5718 return FALSE;
5719 }
5721 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5722 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5723 return FALSE;
5725 /* Restore the exact position or use the specialized restore
5726 * mode? */
5727 if (!view->p_restore)
5728 status_restore(view);
5729 return TRUE;
5730 }
5732 static bool
5733 status_draw(struct view *view, struct line *line, unsigned int lineno)
5734 {
5735 struct status *status = line->data;
5736 enum line_type type;
5737 const char *text;
5739 if (!status) {
5740 switch (line->type) {
5741 case LINE_STAT_STAGED:
5742 type = LINE_STAT_SECTION;
5743 text = "Changes to be committed:";
5744 break;
5746 case LINE_STAT_UNSTAGED:
5747 type = LINE_STAT_SECTION;
5748 text = "Changed but not updated:";
5749 break;
5751 case LINE_STAT_UNTRACKED:
5752 type = LINE_STAT_SECTION;
5753 text = "Untracked files:";
5754 break;
5756 case LINE_STAT_NONE:
5757 type = LINE_DEFAULT;
5758 text = " (no files)";
5759 break;
5761 case LINE_STAT_HEAD:
5762 type = LINE_STAT_HEAD;
5763 text = status_onbranch;
5764 break;
5766 default:
5767 return FALSE;
5768 }
5769 } else {
5770 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5772 buf[0] = status->status;
5773 if (draw_text(view, line->type, buf, TRUE))
5774 return TRUE;
5775 type = LINE_DEFAULT;
5776 text = status->new.name;
5777 }
5779 draw_text(view, type, text, TRUE);
5780 return TRUE;
5781 }
5783 static enum request
5784 status_load_error(struct view *view, struct view *stage, const char *path)
5785 {
5786 if (displayed_views() == 2 || display[current_view] != view)
5787 maximize_view(view);
5788 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5789 return REQ_NONE;
5790 }
5792 static enum request
5793 status_enter(struct view *view, struct line *line)
5794 {
5795 struct status *status = line->data;
5796 const char *oldpath = status ? status->old.name : NULL;
5797 /* Diffs for unmerged entries are empty when passing the new
5798 * path, so leave it empty. */
5799 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5800 const char *info;
5801 enum open_flags split;
5802 struct view *stage = VIEW(REQ_VIEW_STAGE);
5804 if (line->type == LINE_STAT_NONE ||
5805 (!status && line[1].type == LINE_STAT_NONE)) {
5806 report("No file to diff");
5807 return REQ_NONE;
5808 }
5810 switch (line->type) {
5811 case LINE_STAT_STAGED:
5812 if (is_initial_commit()) {
5813 const char *no_head_diff_argv[] = {
5814 "git", "diff", "--no-color", "--patch-with-stat",
5815 "--", "/dev/null", newpath, NULL
5816 };
5818 if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5819 return status_load_error(view, stage, newpath);
5820 } else {
5821 const char *index_show_argv[] = {
5822 "git", "diff-index", "--root", "--patch-with-stat",
5823 "-C", "-M", "--cached", "HEAD", "--",
5824 oldpath, newpath, NULL
5825 };
5827 if (!prepare_update(stage, index_show_argv, opt_cdup))
5828 return status_load_error(view, stage, newpath);
5829 }
5831 if (status)
5832 info = "Staged changes to %s";
5833 else
5834 info = "Staged changes";
5835 break;
5837 case LINE_STAT_UNSTAGED:
5838 {
5839 const char *files_show_argv[] = {
5840 "git", "diff-files", "--root", "--patch-with-stat",
5841 "-C", "-M", "--", oldpath, newpath, NULL
5842 };
5844 if (!prepare_update(stage, files_show_argv, opt_cdup))
5845 return status_load_error(view, stage, newpath);
5846 if (status)
5847 info = "Unstaged changes to %s";
5848 else
5849 info = "Unstaged changes";
5850 break;
5851 }
5852 case LINE_STAT_UNTRACKED:
5853 if (!newpath) {
5854 report("No file to show");
5855 return REQ_NONE;
5856 }
5858 if (!suffixcmp(status->new.name, -1, "/")) {
5859 report("Cannot display a directory");
5860 return REQ_NONE;
5861 }
5863 if (!prepare_update_file(stage, newpath))
5864 return status_load_error(view, stage, newpath);
5865 info = "Untracked file %s";
5866 break;
5868 case LINE_STAT_HEAD:
5869 return REQ_NONE;
5871 default:
5872 die("line type %d not handled in switch", line->type);
5873 }
5875 split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5876 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5877 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5878 if (status) {
5879 stage_status = *status;
5880 } else {
5881 memset(&stage_status, 0, sizeof(stage_status));
5882 }
5884 stage_line_type = line->type;
5885 stage_chunks = 0;
5886 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5887 }
5889 return REQ_NONE;
5890 }
5892 static bool
5893 status_exists(struct status *status, enum line_type type)
5894 {
5895 struct view *view = VIEW(REQ_VIEW_STATUS);
5896 unsigned long lineno;
5898 for (lineno = 0; lineno < view->lines; lineno++) {
5899 struct line *line = &view->line[lineno];
5900 struct status *pos = line->data;
5902 if (line->type != type)
5903 continue;
5904 if (!pos && (!status || !status->status) && line[1].data) {
5905 select_view_line(view, lineno);
5906 return TRUE;
5907 }
5908 if (pos && !strcmp(status->new.name, pos->new.name)) {
5909 select_view_line(view, lineno);
5910 return TRUE;
5911 }
5912 }
5914 return FALSE;
5915 }
5918 static bool
5919 status_update_prepare(struct io *io, enum line_type type)
5920 {
5921 const char *staged_argv[] = {
5922 "git", "update-index", "-z", "--index-info", NULL
5923 };
5924 const char *others_argv[] = {
5925 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5926 };
5928 switch (type) {
5929 case LINE_STAT_STAGED:
5930 return io_run(io, IO_WR, opt_cdup, staged_argv);
5932 case LINE_STAT_UNSTAGED:
5933 case LINE_STAT_UNTRACKED:
5934 return io_run(io, IO_WR, opt_cdup, others_argv);
5936 default:
5937 die("line type %d not handled in switch", type);
5938 return FALSE;
5939 }
5940 }
5942 static bool
5943 status_update_write(struct io *io, struct status *status, enum line_type type)
5944 {
5945 char buf[SIZEOF_STR];
5946 size_t bufsize = 0;
5948 switch (type) {
5949 case LINE_STAT_STAGED:
5950 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5951 status->old.mode,
5952 status->old.rev,
5953 status->old.name, 0))
5954 return FALSE;
5955 break;
5957 case LINE_STAT_UNSTAGED:
5958 case LINE_STAT_UNTRACKED:
5959 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5960 return FALSE;
5961 break;
5963 default:
5964 die("line type %d not handled in switch", type);
5965 }
5967 return io_write(io, buf, bufsize);
5968 }
5970 static bool
5971 status_update_file(struct status *status, enum line_type type)
5972 {
5973 struct io io;
5974 bool result;
5976 if (!status_update_prepare(&io, type))
5977 return FALSE;
5979 result = status_update_write(&io, status, type);
5980 return io_done(&io) && result;
5981 }
5983 static bool
5984 status_update_files(struct view *view, struct line *line)
5985 {
5986 char buf[sizeof(view->ref)];
5987 struct io io;
5988 bool result = TRUE;
5989 struct line *pos = view->line + view->lines;
5990 int files = 0;
5991 int file, done;
5992 int cursor_y = -1, cursor_x = -1;
5994 if (!status_update_prepare(&io, line->type))
5995 return FALSE;
5997 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5998 files++;
6000 string_copy(buf, view->ref);
6001 getsyx(cursor_y, cursor_x);
6002 for (file = 0, done = 5; result && file < files; line++, file++) {
6003 int almost_done = file * 100 / files;
6005 if (almost_done > done) {
6006 done = almost_done;
6007 string_format(view->ref, "updating file %u of %u (%d%% done)",
6008 file, files, done);
6009 update_view_title(view);
6010 setsyx(cursor_y, cursor_x);
6011 doupdate();
6012 }
6013 result = status_update_write(&io, line->data, line->type);
6014 }
6015 string_copy(view->ref, buf);
6017 return io_done(&io) && result;
6018 }
6020 static bool
6021 status_update(struct view *view)
6022 {
6023 struct line *line = &view->line[view->lineno];
6025 assert(view->lines);
6027 if (!line->data) {
6028 /* This should work even for the "On branch" line. */
6029 if (line < view->line + view->lines && !line[1].data) {
6030 report("Nothing to update");
6031 return FALSE;
6032 }
6034 if (!status_update_files(view, line + 1)) {
6035 report("Failed to update file status");
6036 return FALSE;
6037 }
6039 } else if (!status_update_file(line->data, line->type)) {
6040 report("Failed to update file status");
6041 return FALSE;
6042 }
6044 return TRUE;
6045 }
6047 static bool
6048 status_revert(struct status *status, enum line_type type, bool has_none)
6049 {
6050 if (!status || type != LINE_STAT_UNSTAGED) {
6051 if (type == LINE_STAT_STAGED) {
6052 report("Cannot revert changes to staged files");
6053 } else if (type == LINE_STAT_UNTRACKED) {
6054 report("Cannot revert changes to untracked files");
6055 } else if (has_none) {
6056 report("Nothing to revert");
6057 } else {
6058 report("Cannot revert changes to multiple files");
6059 }
6061 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6062 char mode[10] = "100644";
6063 const char *reset_argv[] = {
6064 "git", "update-index", "--cacheinfo", mode,
6065 status->old.rev, status->old.name, NULL
6066 };
6067 const char *checkout_argv[] = {
6068 "git", "checkout", "--", status->old.name, NULL
6069 };
6071 if (status->status == 'U') {
6072 string_format(mode, "%5o", status->old.mode);
6074 if (status->old.mode == 0 && status->new.mode == 0) {
6075 reset_argv[2] = "--force-remove";
6076 reset_argv[3] = status->old.name;
6077 reset_argv[4] = NULL;
6078 }
6080 if (!io_run_fg(reset_argv, opt_cdup))
6081 return FALSE;
6082 if (status->old.mode == 0 && status->new.mode == 0)
6083 return TRUE;
6084 }
6086 return io_run_fg(checkout_argv, opt_cdup);
6087 }
6089 return FALSE;
6090 }
6092 static enum request
6093 status_request(struct view *view, enum request request, struct line *line)
6094 {
6095 struct status *status = line->data;
6097 switch (request) {
6098 case REQ_STATUS_UPDATE:
6099 if (!status_update(view))
6100 return REQ_NONE;
6101 break;
6103 case REQ_STATUS_REVERT:
6104 if (!status_revert(status, line->type, status_has_none(view, line)))
6105 return REQ_NONE;
6106 break;
6108 case REQ_STATUS_MERGE:
6109 if (!status || status->status != 'U') {
6110 report("Merging only possible for files with unmerged status ('U').");
6111 return REQ_NONE;
6112 }
6113 open_mergetool(status->new.name);
6114 break;
6116 case REQ_EDIT:
6117 if (!status)
6118 return request;
6119 if (status->status == 'D') {
6120 report("File has been deleted.");
6121 return REQ_NONE;
6122 }
6124 open_editor(status->new.name);
6125 break;
6127 case REQ_VIEW_BLAME:
6128 if (status)
6129 opt_ref[0] = 0;
6130 return request;
6132 case REQ_ENTER:
6133 /* After returning the status view has been split to
6134 * show the stage view. No further reloading is
6135 * necessary. */
6136 return status_enter(view, line);
6138 case REQ_REFRESH:
6139 /* Simply reload the view. */
6140 break;
6142 default:
6143 return request;
6144 }
6146 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6148 return REQ_NONE;
6149 }
6151 static void
6152 status_select(struct view *view, struct line *line)
6153 {
6154 struct status *status = line->data;
6155 char file[SIZEOF_STR] = "all files";
6156 const char *text;
6157 const char *key;
6159 if (status && !string_format(file, "'%s'", status->new.name))
6160 return;
6162 if (!status && line[1].type == LINE_STAT_NONE)
6163 line++;
6165 switch (line->type) {
6166 case LINE_STAT_STAGED:
6167 text = "Press %s to unstage %s for commit";
6168 break;
6170 case LINE_STAT_UNSTAGED:
6171 text = "Press %s to stage %s for commit";
6172 break;
6174 case LINE_STAT_UNTRACKED:
6175 text = "Press %s to stage %s for addition";
6176 break;
6178 case LINE_STAT_HEAD:
6179 case LINE_STAT_NONE:
6180 text = "Nothing to update";
6181 break;
6183 default:
6184 die("line type %d not handled in switch", line->type);
6185 }
6187 if (status && status->status == 'U') {
6188 text = "Press %s to resolve conflict in %s";
6189 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6191 } else {
6192 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6193 }
6195 string_format(view->ref, text, key, file);
6196 if (status)
6197 string_copy(opt_file, status->new.name);
6198 }
6200 static bool
6201 status_grep(struct view *view, struct line *line)
6202 {
6203 struct status *status = line->data;
6205 if (status) {
6206 const char buf[2] = { status->status, 0 };
6207 const char *text[] = { status->new.name, buf, NULL };
6209 return grep_text(view, text);
6210 }
6212 return FALSE;
6213 }
6215 static struct view_ops status_ops = {
6216 "file",
6217 NULL,
6218 status_open,
6219 NULL,
6220 status_draw,
6221 status_request,
6222 status_grep,
6223 status_select,
6224 };
6227 static bool
6228 stage_diff_write(struct io *io, struct line *line, struct line *end)
6229 {
6230 while (line < end) {
6231 if (!io_write(io, line->data, strlen(line->data)) ||
6232 !io_write(io, "\n", 1))
6233 return FALSE;
6234 line++;
6235 if (line->type == LINE_DIFF_CHUNK ||
6236 line->type == LINE_DIFF_HEADER)
6237 break;
6238 }
6240 return TRUE;
6241 }
6243 static struct line *
6244 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6245 {
6246 for (; view->line < line; line--)
6247 if (line->type == type)
6248 return line;
6250 return NULL;
6251 }
6253 static bool
6254 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6255 {
6256 const char *apply_argv[SIZEOF_ARG] = {
6257 "git", "apply", "--whitespace=nowarn", NULL
6258 };
6259 struct line *diff_hdr;
6260 struct io io;
6261 int argc = 3;
6263 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6264 if (!diff_hdr)
6265 return FALSE;
6267 if (!revert)
6268 apply_argv[argc++] = "--cached";
6269 if (revert || stage_line_type == LINE_STAT_STAGED)
6270 apply_argv[argc++] = "-R";
6271 apply_argv[argc++] = "-";
6272 apply_argv[argc++] = NULL;
6273 if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6274 return FALSE;
6276 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6277 !stage_diff_write(&io, chunk, view->line + view->lines))
6278 chunk = NULL;
6280 io_done(&io);
6281 io_run_bg(update_index_argv);
6283 return chunk ? TRUE : FALSE;
6284 }
6286 static bool
6287 stage_update(struct view *view, struct line *line)
6288 {
6289 struct line *chunk = NULL;
6291 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6292 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6294 if (chunk) {
6295 if (!stage_apply_chunk(view, chunk, FALSE)) {
6296 report("Failed to apply chunk");
6297 return FALSE;
6298 }
6300 } else if (!stage_status.status) {
6301 view = VIEW(REQ_VIEW_STATUS);
6303 for (line = view->line; line < view->line + view->lines; line++)
6304 if (line->type == stage_line_type)
6305 break;
6307 if (!status_update_files(view, line + 1)) {
6308 report("Failed to update files");
6309 return FALSE;
6310 }
6312 } else if (!status_update_file(&stage_status, stage_line_type)) {
6313 report("Failed to update file");
6314 return FALSE;
6315 }
6317 return TRUE;
6318 }
6320 static bool
6321 stage_revert(struct view *view, struct line *line)
6322 {
6323 struct line *chunk = NULL;
6325 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6326 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6328 if (chunk) {
6329 if (!prompt_yesno("Are you sure you want to revert changes?"))
6330 return FALSE;
6332 if (!stage_apply_chunk(view, chunk, TRUE)) {
6333 report("Failed to revert chunk");
6334 return FALSE;
6335 }
6336 return TRUE;
6338 } else {
6339 return status_revert(stage_status.status ? &stage_status : NULL,
6340 stage_line_type, FALSE);
6341 }
6342 }
6345 static void
6346 stage_next(struct view *view, struct line *line)
6347 {
6348 int i;
6350 if (!stage_chunks) {
6351 for (line = view->line; line < view->line + view->lines; line++) {
6352 if (line->type != LINE_DIFF_CHUNK)
6353 continue;
6355 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6356 report("Allocation failure");
6357 return;
6358 }
6360 stage_chunk[stage_chunks++] = line - view->line;
6361 }
6362 }
6364 for (i = 0; i < stage_chunks; i++) {
6365 if (stage_chunk[i] > view->lineno) {
6366 do_scroll_view(view, stage_chunk[i] - view->lineno);
6367 report("Chunk %d of %d", i + 1, stage_chunks);
6368 return;
6369 }
6370 }
6372 report("No next chunk found");
6373 }
6375 static enum request
6376 stage_request(struct view *view, enum request request, struct line *line)
6377 {
6378 switch (request) {
6379 case REQ_STATUS_UPDATE:
6380 if (!stage_update(view, line))
6381 return REQ_NONE;
6382 break;
6384 case REQ_STATUS_REVERT:
6385 if (!stage_revert(view, line))
6386 return REQ_NONE;
6387 break;
6389 case REQ_STAGE_NEXT:
6390 if (stage_line_type == LINE_STAT_UNTRACKED) {
6391 report("File is untracked; press %s to add",
6392 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6393 return REQ_NONE;
6394 }
6395 stage_next(view, line);
6396 return REQ_NONE;
6398 case REQ_EDIT:
6399 if (!stage_status.new.name[0])
6400 return request;
6401 if (stage_status.status == 'D') {
6402 report("File has been deleted.");
6403 return REQ_NONE;
6404 }
6406 open_editor(stage_status.new.name);
6407 break;
6409 case REQ_REFRESH:
6410 /* Reload everything ... */
6411 break;
6413 case REQ_VIEW_BLAME:
6414 if (stage_status.new.name[0]) {
6415 string_copy(opt_file, stage_status.new.name);
6416 opt_ref[0] = 0;
6417 }
6418 return request;
6420 case REQ_ENTER:
6421 return pager_request(view, request, line);
6423 default:
6424 return request;
6425 }
6427 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6428 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6430 /* Check whether the staged entry still exists, and close the
6431 * stage view if it doesn't. */
6432 if (!status_exists(&stage_status, stage_line_type)) {
6433 status_restore(VIEW(REQ_VIEW_STATUS));
6434 return REQ_VIEW_CLOSE;
6435 }
6437 if (stage_line_type == LINE_STAT_UNTRACKED) {
6438 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6439 report("Cannot display a directory");
6440 return REQ_NONE;
6441 }
6443 if (!prepare_update_file(view, stage_status.new.name)) {
6444 report("Failed to open file: %s", strerror(errno));
6445 return REQ_NONE;
6446 }
6447 }
6448 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6450 return REQ_NONE;
6451 }
6453 static struct view_ops stage_ops = {
6454 "line",
6455 NULL,
6456 NULL,
6457 pager_read,
6458 pager_draw,
6459 stage_request,
6460 pager_grep,
6461 pager_select,
6462 };
6465 /*
6466 * Revision graph
6467 */
6469 struct commit {
6470 char id[SIZEOF_REV]; /* SHA1 ID. */
6471 char title[128]; /* First line of the commit message. */
6472 const char *author; /* Author of the commit. */
6473 struct time time; /* Date from the author ident. */
6474 struct ref_list *refs; /* Repository references. */
6475 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6476 size_t graph_size; /* The width of the graph array. */
6477 bool has_parents; /* Rewritten --parents seen. */
6478 };
6480 /* Size of rev graph with no "padding" columns */
6481 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6483 struct rev_graph {
6484 struct rev_graph *prev, *next, *parents;
6485 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6486 size_t size;
6487 struct commit *commit;
6488 size_t pos;
6489 unsigned int boundary:1;
6490 };
6492 /* Parents of the commit being visualized. */
6493 static struct rev_graph graph_parents[4];
6495 /* The current stack of revisions on the graph. */
6496 static struct rev_graph graph_stacks[4] = {
6497 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6498 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6499 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6500 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6501 };
6503 static inline bool
6504 graph_parent_is_merge(struct rev_graph *graph)
6505 {
6506 return graph->parents->size > 1;
6507 }
6509 static inline void
6510 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6511 {
6512 struct commit *commit = graph->commit;
6514 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6515 commit->graph[commit->graph_size++] = symbol;
6516 }
6518 static void
6519 clear_rev_graph(struct rev_graph *graph)
6520 {
6521 graph->boundary = 0;
6522 graph->size = graph->pos = 0;
6523 graph->commit = NULL;
6524 memset(graph->parents, 0, sizeof(*graph->parents));
6525 }
6527 static void
6528 done_rev_graph(struct rev_graph *graph)
6529 {
6530 if (graph_parent_is_merge(graph) &&
6531 graph->pos < graph->size - 1 &&
6532 graph->next->size == graph->size + graph->parents->size - 1) {
6533 size_t i = graph->pos + graph->parents->size - 1;
6535 graph->commit->graph_size = i * 2;
6536 while (i < graph->next->size - 1) {
6537 append_to_rev_graph(graph, ' ');
6538 append_to_rev_graph(graph, '\\');
6539 i++;
6540 }
6541 }
6543 clear_rev_graph(graph);
6544 }
6546 static void
6547 push_rev_graph(struct rev_graph *graph, const char *parent)
6548 {
6549 int i;
6551 /* "Collapse" duplicate parents lines.
6552 *
6553 * FIXME: This needs to also update update the drawn graph but
6554 * for now it just serves as a method for pruning graph lines. */
6555 for (i = 0; i < graph->size; i++)
6556 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6557 return;
6559 if (graph->size < SIZEOF_REVITEMS) {
6560 string_copy_rev(graph->rev[graph->size++], parent);
6561 }
6562 }
6564 static chtype
6565 get_rev_graph_symbol(struct rev_graph *graph)
6566 {
6567 chtype symbol;
6569 if (graph->boundary)
6570 symbol = REVGRAPH_BOUND;
6571 else if (graph->parents->size == 0)
6572 symbol = REVGRAPH_INIT;
6573 else if (graph_parent_is_merge(graph))
6574 symbol = REVGRAPH_MERGE;
6575 else if (graph->pos >= graph->size)
6576 symbol = REVGRAPH_BRANCH;
6577 else
6578 symbol = REVGRAPH_COMMIT;
6580 return symbol;
6581 }
6583 static void
6584 draw_rev_graph(struct rev_graph *graph)
6585 {
6586 struct rev_filler {
6587 chtype separator, line;
6588 };
6589 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6590 static struct rev_filler fillers[] = {
6591 { ' ', '|' },
6592 { '`', '.' },
6593 { '\'', ' ' },
6594 { '/', ' ' },
6595 };
6596 chtype symbol = get_rev_graph_symbol(graph);
6597 struct rev_filler *filler;
6598 size_t i;
6600 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6601 filler = &fillers[DEFAULT];
6603 for (i = 0; i < graph->pos; i++) {
6604 append_to_rev_graph(graph, filler->line);
6605 if (graph_parent_is_merge(graph->prev) &&
6606 graph->prev->pos == i)
6607 filler = &fillers[RSHARP];
6609 append_to_rev_graph(graph, filler->separator);
6610 }
6612 /* Place the symbol for this revision. */
6613 append_to_rev_graph(graph, symbol);
6615 if (graph->prev->size > graph->size)
6616 filler = &fillers[RDIAG];
6617 else
6618 filler = &fillers[DEFAULT];
6620 i++;
6622 for (; i < graph->size; i++) {
6623 append_to_rev_graph(graph, filler->separator);
6624 append_to_rev_graph(graph, filler->line);
6625 if (graph_parent_is_merge(graph->prev) &&
6626 i < graph->prev->pos + graph->parents->size)
6627 filler = &fillers[RSHARP];
6628 if (graph->prev->size > graph->size)
6629 filler = &fillers[LDIAG];
6630 }
6632 if (graph->prev->size > graph->size) {
6633 append_to_rev_graph(graph, filler->separator);
6634 if (filler->line != ' ')
6635 append_to_rev_graph(graph, filler->line);
6636 }
6637 }
6639 /* Prepare the next rev graph */
6640 static void
6641 prepare_rev_graph(struct rev_graph *graph)
6642 {
6643 size_t i;
6645 /* First, traverse all lines of revisions up to the active one. */
6646 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6647 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6648 break;
6650 push_rev_graph(graph->next, graph->rev[graph->pos]);
6651 }
6653 /* Interleave the new revision parent(s). */
6654 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6655 push_rev_graph(graph->next, graph->parents->rev[i]);
6657 /* Lastly, put any remaining revisions. */
6658 for (i = graph->pos + 1; i < graph->size; i++)
6659 push_rev_graph(graph->next, graph->rev[i]);
6660 }
6662 static void
6663 update_rev_graph(struct view *view, struct rev_graph *graph)
6664 {
6665 /* If this is the finalizing update ... */
6666 if (graph->commit)
6667 prepare_rev_graph(graph);
6669 /* Graph visualization needs a one rev look-ahead,
6670 * so the first update doesn't visualize anything. */
6671 if (!graph->prev->commit)
6672 return;
6674 if (view->lines > 2)
6675 view->line[view->lines - 3].dirty = 1;
6676 if (view->lines > 1)
6677 view->line[view->lines - 2].dirty = 1;
6678 draw_rev_graph(graph->prev);
6679 done_rev_graph(graph->prev->prev);
6680 }
6683 /*
6684 * Main view backend
6685 */
6687 static const char *main_argv[SIZEOF_ARG] = {
6688 "git", "log", "--no-color", "--pretty=raw", "--parents",
6689 "--topo-order", "%(head)", NULL
6690 };
6692 static bool
6693 main_draw(struct view *view, struct line *line, unsigned int lineno)
6694 {
6695 struct commit *commit = line->data;
6697 if (!commit->author)
6698 return FALSE;
6700 if (opt_date && draw_date(view, &commit->time))
6701 return TRUE;
6703 if (opt_author && draw_author(view, commit->author))
6704 return TRUE;
6706 if (opt_rev_graph && commit->graph_size &&
6707 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6708 return TRUE;
6710 if (opt_show_refs && commit->refs) {
6711 size_t i;
6713 for (i = 0; i < commit->refs->size; i++) {
6714 struct ref *ref = commit->refs->refs[i];
6715 enum line_type type;
6717 if (ref->head)
6718 type = LINE_MAIN_HEAD;
6719 else if (ref->ltag)
6720 type = LINE_MAIN_LOCAL_TAG;
6721 else if (ref->tag)
6722 type = LINE_MAIN_TAG;
6723 else if (ref->tracked)
6724 type = LINE_MAIN_TRACKED;
6725 else if (ref->remote)
6726 type = LINE_MAIN_REMOTE;
6727 else
6728 type = LINE_MAIN_REF;
6730 if (draw_text(view, type, "[", TRUE) ||
6731 draw_text(view, type, ref->name, TRUE) ||
6732 draw_text(view, type, "]", TRUE))
6733 return TRUE;
6735 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6736 return TRUE;
6737 }
6738 }
6740 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6741 return TRUE;
6742 }
6744 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6745 static bool
6746 main_read(struct view *view, char *line)
6747 {
6748 static struct rev_graph *graph = graph_stacks;
6749 enum line_type type;
6750 struct commit *commit;
6752 if (!line) {
6753 int i;
6755 if (!view->lines && !view->prev)
6756 die("No revisions match the given arguments.");
6757 if (view->lines > 0) {
6758 commit = view->line[view->lines - 1].data;
6759 view->line[view->lines - 1].dirty = 1;
6760 if (!commit->author) {
6761 view->lines--;
6762 free(commit);
6763 graph->commit = NULL;
6764 }
6765 }
6766 update_rev_graph(view, graph);
6768 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6769 clear_rev_graph(&graph_stacks[i]);
6770 return TRUE;
6771 }
6773 type = get_line_type(line);
6774 if (type == LINE_COMMIT) {
6775 commit = calloc(1, sizeof(struct commit));
6776 if (!commit)
6777 return FALSE;
6779 line += STRING_SIZE("commit ");
6780 if (*line == '-') {
6781 graph->boundary = 1;
6782 line++;
6783 }
6785 string_copy_rev(commit->id, line);
6786 commit->refs = get_ref_list(commit->id);
6787 graph->commit = commit;
6788 add_line_data(view, commit, LINE_MAIN_COMMIT);
6790 while ((line = strchr(line, ' '))) {
6791 line++;
6792 push_rev_graph(graph->parents, line);
6793 commit->has_parents = TRUE;
6794 }
6795 return TRUE;
6796 }
6798 if (!view->lines)
6799 return TRUE;
6800 commit = view->line[view->lines - 1].data;
6802 switch (type) {
6803 case LINE_PARENT:
6804 if (commit->has_parents)
6805 break;
6806 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6807 break;
6809 case LINE_AUTHOR:
6810 parse_author_line(line + STRING_SIZE("author "),
6811 &commit->author, &commit->time);
6812 update_rev_graph(view, graph);
6813 graph = graph->next;
6814 break;
6816 default:
6817 /* Fill in the commit title if it has not already been set. */
6818 if (commit->title[0])
6819 break;
6821 /* Require titles to start with a non-space character at the
6822 * offset used by git log. */
6823 if (strncmp(line, " ", 4))
6824 break;
6825 line += 4;
6826 /* Well, if the title starts with a whitespace character,
6827 * try to be forgiving. Otherwise we end up with no title. */
6828 while (isspace(*line))
6829 line++;
6830 if (*line == '\0')
6831 break;
6832 /* FIXME: More graceful handling of titles; append "..." to
6833 * shortened titles, etc. */
6835 string_expand(commit->title, sizeof(commit->title), line, 1);
6836 view->line[view->lines - 1].dirty = 1;
6837 }
6839 return TRUE;
6840 }
6842 static enum request
6843 main_request(struct view *view, enum request request, struct line *line)
6844 {
6845 enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6847 switch (request) {
6848 case REQ_ENTER:
6849 open_view(view, REQ_VIEW_DIFF, flags);
6850 break;
6851 case REQ_REFRESH:
6852 load_refs();
6853 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6854 break;
6855 default:
6856 return request;
6857 }
6859 return REQ_NONE;
6860 }
6862 static bool
6863 grep_refs(struct ref_list *list, regex_t *regex)
6864 {
6865 regmatch_t pmatch;
6866 size_t i;
6868 if (!opt_show_refs || !list)
6869 return FALSE;
6871 for (i = 0; i < list->size; i++) {
6872 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6873 return TRUE;
6874 }
6876 return FALSE;
6877 }
6879 static bool
6880 main_grep(struct view *view, struct line *line)
6881 {
6882 struct commit *commit = line->data;
6883 const char *text[] = {
6884 commit->title,
6885 opt_author ? commit->author : "",
6886 mkdate(&commit->time, opt_date),
6887 NULL
6888 };
6890 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6891 }
6893 static void
6894 main_select(struct view *view, struct line *line)
6895 {
6896 struct commit *commit = line->data;
6898 string_copy_rev(view->ref, commit->id);
6899 string_copy_rev(ref_commit, view->ref);
6900 }
6902 static struct view_ops main_ops = {
6903 "commit",
6904 main_argv,
6905 NULL,
6906 main_read,
6907 main_draw,
6908 main_request,
6909 main_grep,
6910 main_select,
6911 };
6914 /*
6915 * Status management
6916 */
6918 /* Whether or not the curses interface has been initialized. */
6919 static bool cursed = FALSE;
6921 /* Terminal hacks and workarounds. */
6922 static bool use_scroll_redrawwin;
6923 static bool use_scroll_status_wclear;
6925 /* The status window is used for polling keystrokes. */
6926 static WINDOW *status_win;
6928 /* Reading from the prompt? */
6929 static bool input_mode = FALSE;
6931 static bool status_empty = FALSE;
6933 /* Update status and title window. */
6934 static void
6935 report(const char *msg, ...)
6936 {
6937 struct view *view = display[current_view];
6939 if (input_mode)
6940 return;
6942 if (!view) {
6943 char buf[SIZEOF_STR];
6944 va_list args;
6946 va_start(args, msg);
6947 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6948 buf[sizeof(buf) - 1] = 0;
6949 buf[sizeof(buf) - 2] = '.';
6950 buf[sizeof(buf) - 3] = '.';
6951 buf[sizeof(buf) - 4] = '.';
6952 }
6953 va_end(args);
6954 die("%s", buf);
6955 }
6957 if (!status_empty || *msg) {
6958 va_list args;
6960 va_start(args, msg);
6962 wmove(status_win, 0, 0);
6963 if (view->has_scrolled && use_scroll_status_wclear)
6964 wclear(status_win);
6965 if (*msg) {
6966 vwprintw(status_win, msg, args);
6967 status_empty = FALSE;
6968 } else {
6969 status_empty = TRUE;
6970 }
6971 wclrtoeol(status_win);
6972 wnoutrefresh(status_win);
6974 va_end(args);
6975 }
6977 update_view_title(view);
6978 }
6980 static void
6981 init_display(void)
6982 {
6983 const char *term;
6984 int x, y;
6986 /* Initialize the curses library */
6987 if (isatty(STDIN_FILENO)) {
6988 cursed = !!initscr();
6989 opt_tty = stdin;
6990 } else {
6991 /* Leave stdin and stdout alone when acting as a pager. */
6992 opt_tty = fopen("/dev/tty", "r+");
6993 if (!opt_tty)
6994 die("Failed to open /dev/tty");
6995 cursed = !!newterm(NULL, opt_tty, opt_tty);
6996 }
6998 if (!cursed)
6999 die("Failed to initialize curses");
7001 nonl(); /* Disable conversion and detect newlines from input. */
7002 cbreak(); /* Take input chars one at a time, no wait for \n */
7003 noecho(); /* Don't echo input */
7004 leaveok(stdscr, FALSE);
7006 if (has_colors())
7007 init_colors();
7009 getmaxyx(stdscr, y, x);
7010 status_win = newwin(1, 0, y - 1, 0);
7011 if (!status_win)
7012 die("Failed to create status window");
7014 /* Enable keyboard mapping */
7015 keypad(status_win, TRUE);
7016 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7018 TABSIZE = opt_tab_size;
7020 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7021 if (term && !strcmp(term, "gnome-terminal")) {
7022 /* In the gnome-terminal-emulator, the message from
7023 * scrolling up one line when impossible followed by
7024 * scrolling down one line causes corruption of the
7025 * status line. This is fixed by calling wclear. */
7026 use_scroll_status_wclear = TRUE;
7027 use_scroll_redrawwin = FALSE;
7029 } else if (term && !strcmp(term, "xrvt-xpm")) {
7030 /* No problems with full optimizations in xrvt-(unicode)
7031 * and aterm. */
7032 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7034 } else {
7035 /* When scrolling in (u)xterm the last line in the
7036 * scrolling direction will update slowly. */
7037 use_scroll_redrawwin = TRUE;
7038 use_scroll_status_wclear = FALSE;
7039 }
7040 }
7042 static int
7043 get_input(int prompt_position)
7044 {
7045 struct view *view;
7046 int i, key, cursor_y, cursor_x;
7047 bool loading = FALSE;
7049 if (prompt_position)
7050 input_mode = TRUE;
7052 while (TRUE) {
7053 foreach_view (view, i) {
7054 update_view(view);
7055 if (view_is_displayed(view) && view->has_scrolled &&
7056 use_scroll_redrawwin)
7057 redrawwin(view->win);
7058 view->has_scrolled = FALSE;
7059 if (view->pipe)
7060 loading = TRUE;
7061 }
7063 /* Update the cursor position. */
7064 if (prompt_position) {
7065 getbegyx(status_win, cursor_y, cursor_x);
7066 cursor_x = prompt_position;
7067 } else {
7068 view = display[current_view];
7069 getbegyx(view->win, cursor_y, cursor_x);
7070 cursor_x = view->width - 1;
7071 cursor_y += view->lineno - view->offset;
7072 }
7073 setsyx(cursor_y, cursor_x);
7075 /* Refresh, accept single keystroke of input */
7076 doupdate();
7077 nodelay(status_win, loading);
7078 key = wgetch(status_win);
7080 /* wgetch() with nodelay() enabled returns ERR when
7081 * there's no input. */
7082 if (key == ERR) {
7084 } else if (key == KEY_RESIZE) {
7085 int height, width;
7087 getmaxyx(stdscr, height, width);
7089 wresize(status_win, 1, width);
7090 mvwin(status_win, height - 1, 0);
7091 wnoutrefresh(status_win);
7092 resize_display();
7093 redraw_display(TRUE);
7095 } else {
7096 input_mode = FALSE;
7097 return key;
7098 }
7099 }
7100 }
7102 static char *
7103 prompt_input(const char *prompt, input_handler handler, void *data)
7104 {
7105 enum input_status status = INPUT_OK;
7106 static char buf[SIZEOF_STR];
7107 size_t pos = 0;
7109 buf[pos] = 0;
7111 while (status == INPUT_OK || status == INPUT_SKIP) {
7112 int key;
7114 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7115 wclrtoeol(status_win);
7117 key = get_input(pos + 1);
7118 switch (key) {
7119 case KEY_RETURN:
7120 case KEY_ENTER:
7121 case '\n':
7122 status = pos ? INPUT_STOP : INPUT_CANCEL;
7123 break;
7125 case KEY_BACKSPACE:
7126 if (pos > 0)
7127 buf[--pos] = 0;
7128 else
7129 status = INPUT_CANCEL;
7130 break;
7132 case KEY_ESC:
7133 status = INPUT_CANCEL;
7134 break;
7136 default:
7137 if (pos >= sizeof(buf)) {
7138 report("Input string too long");
7139 return NULL;
7140 }
7142 status = handler(data, buf, key);
7143 if (status == INPUT_OK)
7144 buf[pos++] = (char) key;
7145 }
7146 }
7148 /* Clear the status window */
7149 status_empty = FALSE;
7150 report("");
7152 if (status == INPUT_CANCEL)
7153 return NULL;
7155 buf[pos++] = 0;
7157 return buf;
7158 }
7160 static enum input_status
7161 prompt_yesno_handler(void *data, char *buf, int c)
7162 {
7163 if (c == 'y' || c == 'Y')
7164 return INPUT_STOP;
7165 if (c == 'n' || c == 'N')
7166 return INPUT_CANCEL;
7167 return INPUT_SKIP;
7168 }
7170 static bool
7171 prompt_yesno(const char *prompt)
7172 {
7173 char prompt2[SIZEOF_STR];
7175 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7176 return FALSE;
7178 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7179 }
7181 static enum input_status
7182 read_prompt_handler(void *data, char *buf, int c)
7183 {
7184 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7185 }
7187 static char *
7188 read_prompt(const char *prompt)
7189 {
7190 return prompt_input(prompt, read_prompt_handler, NULL);
7191 }
7193 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7194 {
7195 enum input_status status = INPUT_OK;
7196 int size = 0;
7198 while (items[size].text)
7199 size++;
7201 while (status == INPUT_OK) {
7202 const struct menu_item *item = &items[*selected];
7203 int key;
7204 int i;
7206 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7207 prompt, *selected + 1, size);
7208 if (item->hotkey)
7209 wprintw(status_win, "[%c] ", (char) item->hotkey);
7210 wprintw(status_win, "%s", item->text);
7211 wclrtoeol(status_win);
7213 key = get_input(COLS - 1);
7214 switch (key) {
7215 case KEY_RETURN:
7216 case KEY_ENTER:
7217 case '\n':
7218 status = INPUT_STOP;
7219 break;
7221 case KEY_LEFT:
7222 case KEY_UP:
7223 *selected = *selected - 1;
7224 if (*selected < 0)
7225 *selected = size - 1;
7226 break;
7228 case KEY_RIGHT:
7229 case KEY_DOWN:
7230 *selected = (*selected + 1) % size;
7231 break;
7233 case KEY_ESC:
7234 status = INPUT_CANCEL;
7235 break;
7237 default:
7238 for (i = 0; items[i].text; i++)
7239 if (items[i].hotkey == key) {
7240 *selected = i;
7241 status = INPUT_STOP;
7242 break;
7243 }
7244 }
7245 }
7247 /* Clear the status window */
7248 status_empty = FALSE;
7249 report("");
7251 return status != INPUT_CANCEL;
7252 }
7254 /*
7255 * Repository properties
7256 */
7258 static struct ref **refs = NULL;
7259 static size_t refs_size = 0;
7260 static struct ref *refs_head = NULL;
7262 static struct ref_list **ref_lists = NULL;
7263 static size_t ref_lists_size = 0;
7265 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7266 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7267 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7269 static int
7270 compare_refs(const void *ref1_, const void *ref2_)
7271 {
7272 const struct ref *ref1 = *(const struct ref **)ref1_;
7273 const struct ref *ref2 = *(const struct ref **)ref2_;
7275 if (ref1->tag != ref2->tag)
7276 return ref2->tag - ref1->tag;
7277 if (ref1->ltag != ref2->ltag)
7278 return ref2->ltag - ref2->ltag;
7279 if (ref1->head != ref2->head)
7280 return ref2->head - ref1->head;
7281 if (ref1->tracked != ref2->tracked)
7282 return ref2->tracked - ref1->tracked;
7283 if (ref1->remote != ref2->remote)
7284 return ref2->remote - ref1->remote;
7285 return strcmp(ref1->name, ref2->name);
7286 }
7288 static void
7289 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7290 {
7291 size_t i;
7293 for (i = 0; i < refs_size; i++)
7294 if (!visitor(data, refs[i]))
7295 break;
7296 }
7298 static struct ref *
7299 get_ref_head()
7300 {
7301 return refs_head;
7302 }
7304 static struct ref_list *
7305 get_ref_list(const char *id)
7306 {
7307 struct ref_list *list;
7308 size_t i;
7310 for (i = 0; i < ref_lists_size; i++)
7311 if (!strcmp(id, ref_lists[i]->id))
7312 return ref_lists[i];
7314 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7315 return NULL;
7316 list = calloc(1, sizeof(*list));
7317 if (!list)
7318 return NULL;
7320 for (i = 0; i < refs_size; i++) {
7321 if (!strcmp(id, refs[i]->id) &&
7322 realloc_refs_list(&list->refs, list->size, 1))
7323 list->refs[list->size++] = refs[i];
7324 }
7326 if (!list->refs) {
7327 free(list);
7328 return NULL;
7329 }
7331 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7332 ref_lists[ref_lists_size++] = list;
7333 return list;
7334 }
7336 static int
7337 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7338 {
7339 struct ref *ref = NULL;
7340 bool tag = FALSE;
7341 bool ltag = FALSE;
7342 bool remote = FALSE;
7343 bool tracked = FALSE;
7344 bool head = FALSE;
7345 int from = 0, to = refs_size - 1;
7347 if (!prefixcmp(name, "refs/tags/")) {
7348 if (!suffixcmp(name, namelen, "^{}")) {
7349 namelen -= 3;
7350 name[namelen] = 0;
7351 } else {
7352 ltag = TRUE;
7353 }
7355 tag = TRUE;
7356 namelen -= STRING_SIZE("refs/tags/");
7357 name += STRING_SIZE("refs/tags/");
7359 } else if (!prefixcmp(name, "refs/remotes/")) {
7360 remote = TRUE;
7361 namelen -= STRING_SIZE("refs/remotes/");
7362 name += STRING_SIZE("refs/remotes/");
7363 tracked = !strcmp(opt_remote, name);
7365 } else if (!prefixcmp(name, "refs/heads/")) {
7366 namelen -= STRING_SIZE("refs/heads/");
7367 name += STRING_SIZE("refs/heads/");
7368 if (!strncmp(opt_head, name, namelen))
7369 return OK;
7371 } else if (!strcmp(name, "HEAD")) {
7372 head = TRUE;
7373 if (*opt_head) {
7374 namelen = strlen(opt_head);
7375 name = opt_head;
7376 }
7377 }
7379 /* If we are reloading or it's an annotated tag, replace the
7380 * previous SHA1 with the resolved commit id; relies on the fact
7381 * git-ls-remote lists the commit id of an annotated tag right
7382 * before the commit id it points to. */
7383 while (from <= to) {
7384 size_t pos = (to + from) / 2;
7385 int cmp = strcmp(name, refs[pos]->name);
7387 if (!cmp) {
7388 ref = refs[pos];
7389 break;
7390 }
7392 if (cmp < 0)
7393 to = pos - 1;
7394 else
7395 from = pos + 1;
7396 }
7398 if (!ref) {
7399 if (!realloc_refs(&refs, refs_size, 1))
7400 return ERR;
7401 ref = calloc(1, sizeof(*ref) + namelen);
7402 if (!ref)
7403 return ERR;
7404 memmove(refs + from + 1, refs + from,
7405 (refs_size - from) * sizeof(*refs));
7406 refs[from] = ref;
7407 strncpy(ref->name, name, namelen);
7408 refs_size++;
7409 }
7411 ref->head = head;
7412 ref->tag = tag;
7413 ref->ltag = ltag;
7414 ref->remote = remote;
7415 ref->tracked = tracked;
7416 string_copy_rev(ref->id, id);
7418 if (head)
7419 refs_head = ref;
7420 return OK;
7421 }
7423 static int
7424 load_refs(void)
7425 {
7426 const char *head_argv[] = {
7427 "git", "symbolic-ref", "HEAD", NULL
7428 };
7429 static const char *ls_remote_argv[SIZEOF_ARG] = {
7430 "git", "ls-remote", opt_git_dir, NULL
7431 };
7432 static bool init = FALSE;
7433 size_t i;
7435 if (!init) {
7436 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7437 die("TIG_LS_REMOTE contains too many arguments");
7438 init = TRUE;
7439 }
7441 if (!*opt_git_dir)
7442 return OK;
7444 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7445 !prefixcmp(opt_head, "refs/heads/")) {
7446 char *offset = opt_head + STRING_SIZE("refs/heads/");
7448 memmove(opt_head, offset, strlen(offset) + 1);
7449 }
7451 refs_head = NULL;
7452 for (i = 0; i < refs_size; i++)
7453 refs[i]->id[0] = 0;
7455 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7456 return ERR;
7458 /* Update the ref lists to reflect changes. */
7459 for (i = 0; i < ref_lists_size; i++) {
7460 struct ref_list *list = ref_lists[i];
7461 size_t old, new;
7463 for (old = new = 0; old < list->size; old++)
7464 if (!strcmp(list->id, list->refs[old]->id))
7465 list->refs[new++] = list->refs[old];
7466 list->size = new;
7467 }
7469 return OK;
7470 }
7472 static void
7473 set_remote_branch(const char *name, const char *value, size_t valuelen)
7474 {
7475 if (!strcmp(name, ".remote")) {
7476 string_ncopy(opt_remote, value, valuelen);
7478 } else if (*opt_remote && !strcmp(name, ".merge")) {
7479 size_t from = strlen(opt_remote);
7481 if (!prefixcmp(value, "refs/heads/"))
7482 value += STRING_SIZE("refs/heads/");
7484 if (!string_format_from(opt_remote, &from, "/%s", value))
7485 opt_remote[0] = 0;
7486 }
7487 }
7489 static void
7490 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7491 {
7492 const char *argv[SIZEOF_ARG] = { name, "=" };
7493 int argc = 1 + (cmd == option_set_command);
7494 int error = ERR;
7496 if (!argv_from_string(argv, &argc, value))
7497 config_msg = "Too many option arguments";
7498 else
7499 error = cmd(argc, argv);
7501 if (error == ERR)
7502 warn("Option 'tig.%s': %s", name, config_msg);
7503 }
7505 static bool
7506 set_environment_variable(const char *name, const char *value)
7507 {
7508 size_t len = strlen(name) + 1 + strlen(value) + 1;
7509 char *env = malloc(len);
7511 if (env &&
7512 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7513 putenv(env) == 0)
7514 return TRUE;
7515 free(env);
7516 return FALSE;
7517 }
7519 static void
7520 set_work_tree(const char *value)
7521 {
7522 char cwd[SIZEOF_STR];
7524 if (!getcwd(cwd, sizeof(cwd)))
7525 die("Failed to get cwd path: %s", strerror(errno));
7526 if (chdir(opt_git_dir) < 0)
7527 die("Failed to chdir(%s): %s", strerror(errno));
7528 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7529 die("Failed to get git path: %s", strerror(errno));
7530 if (chdir(cwd) < 0)
7531 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7532 if (chdir(value) < 0)
7533 die("Failed to chdir(%s): %s", value, strerror(errno));
7534 if (!getcwd(cwd, sizeof(cwd)))
7535 die("Failed to get cwd path: %s", strerror(errno));
7536 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7537 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7538 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7539 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7540 opt_is_inside_work_tree = TRUE;
7541 }
7543 static int
7544 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7545 {
7546 if (!strcmp(name, "i18n.commitencoding"))
7547 string_ncopy(opt_encoding, value, valuelen);
7549 else if (!strcmp(name, "core.editor"))
7550 string_ncopy(opt_editor, value, valuelen);
7552 else if (!strcmp(name, "core.worktree"))
7553 set_work_tree(value);
7555 else if (!prefixcmp(name, "tig.color."))
7556 set_repo_config_option(name + 10, value, option_color_command);
7558 else if (!prefixcmp(name, "tig.bind."))
7559 set_repo_config_option(name + 9, value, option_bind_command);
7561 else if (!prefixcmp(name, "tig."))
7562 set_repo_config_option(name + 4, value, option_set_command);
7564 else if (*opt_head && !prefixcmp(name, "branch.") &&
7565 !strncmp(name + 7, opt_head, strlen(opt_head)))
7566 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7568 return OK;
7569 }
7571 static int
7572 load_git_config(void)
7573 {
7574 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7576 return io_run_load(config_list_argv, "=", read_repo_config_option);
7577 }
7579 static int
7580 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7581 {
7582 if (!opt_git_dir[0]) {
7583 string_ncopy(opt_git_dir, name, namelen);
7585 } else if (opt_is_inside_work_tree == -1) {
7586 /* This can be 3 different values depending on the
7587 * version of git being used. If git-rev-parse does not
7588 * understand --is-inside-work-tree it will simply echo
7589 * the option else either "true" or "false" is printed.
7590 * Default to true for the unknown case. */
7591 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7593 } else if (*name == '.') {
7594 string_ncopy(opt_cdup, name, namelen);
7596 } else {
7597 string_ncopy(opt_prefix, name, namelen);
7598 }
7600 return OK;
7601 }
7603 static int
7604 load_repo_info(void)
7605 {
7606 const char *rev_parse_argv[] = {
7607 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7608 "--show-cdup", "--show-prefix", NULL
7609 };
7611 return io_run_load(rev_parse_argv, "=", read_repo_info);
7612 }
7615 /*
7616 * Main
7617 */
7619 static const char usage[] =
7620 "tig " TIG_VERSION " (" __DATE__ ")\n"
7621 "\n"
7622 "Usage: tig [options] [revs] [--] [paths]\n"
7623 " or: tig show [options] [revs] [--] [paths]\n"
7624 " or: tig blame [rev] path\n"
7625 " or: tig status\n"
7626 " or: tig < [git command output]\n"
7627 "\n"
7628 "Options:\n"
7629 " -v, --version Show version and exit\n"
7630 " -h, --help Show help message and exit";
7632 static void __NORETURN
7633 quit(int sig)
7634 {
7635 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7636 if (cursed)
7637 endwin();
7638 exit(0);
7639 }
7641 static void __NORETURN
7642 die(const char *err, ...)
7643 {
7644 va_list args;
7646 endwin();
7648 va_start(args, err);
7649 fputs("tig: ", stderr);
7650 vfprintf(stderr, err, args);
7651 fputs("\n", stderr);
7652 va_end(args);
7654 exit(1);
7655 }
7657 static void
7658 warn(const char *msg, ...)
7659 {
7660 va_list args;
7662 va_start(args, msg);
7663 fputs("tig warning: ", stderr);
7664 vfprintf(stderr, msg, args);
7665 fputs("\n", stderr);
7666 va_end(args);
7667 }
7669 static enum request
7670 parse_options(int argc, const char *argv[])
7671 {
7672 enum request request = REQ_VIEW_MAIN;
7673 const char *subcommand;
7674 bool seen_dashdash = FALSE;
7675 /* XXX: This is vulnerable to the user overriding options
7676 * required for the main view parser. */
7677 const char *custom_argv[SIZEOF_ARG] = {
7678 "git", "log", "--no-color", "--pretty=raw", "--parents",
7679 "--topo-order", NULL
7680 };
7681 int i, j = 6;
7683 if (!isatty(STDIN_FILENO)) {
7684 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7685 return REQ_VIEW_PAGER;
7686 }
7688 if (argc <= 1)
7689 return REQ_NONE;
7691 subcommand = argv[1];
7692 if (!strcmp(subcommand, "status")) {
7693 if (argc > 2)
7694 warn("ignoring arguments after `%s'", subcommand);
7695 return REQ_VIEW_STATUS;
7697 } else if (!strcmp(subcommand, "blame")) {
7698 if (argc <= 2 || argc > 4)
7699 die("invalid number of options to blame\n\n%s", usage);
7701 i = 2;
7702 if (argc == 4) {
7703 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7704 i++;
7705 }
7707 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7708 return REQ_VIEW_BLAME;
7710 } else if (!strcmp(subcommand, "show")) {
7711 request = REQ_VIEW_DIFF;
7713 } else {
7714 subcommand = NULL;
7715 }
7717 if (subcommand) {
7718 custom_argv[1] = subcommand;
7719 j = 2;
7720 }
7722 for (i = 1 + !!subcommand; i < argc; i++) {
7723 const char *opt = argv[i];
7725 if (seen_dashdash || !strcmp(opt, "--")) {
7726 seen_dashdash = TRUE;
7728 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7729 printf("tig version %s\n", TIG_VERSION);
7730 quit(0);
7732 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7733 printf("%s\n", usage);
7734 quit(0);
7735 }
7737 custom_argv[j++] = opt;
7738 if (j >= ARRAY_SIZE(custom_argv))
7739 die("command too long");
7740 }
7742 if (!prepare_update(VIEW(request), custom_argv, NULL))
7743 die("Failed to format arguments");
7745 return request;
7746 }
7748 int
7749 main(int argc, const char *argv[])
7750 {
7751 const char *codeset = "UTF-8";
7752 enum request request = parse_options(argc, argv);
7753 struct view *view;
7754 size_t i;
7756 signal(SIGINT, quit);
7757 signal(SIGPIPE, SIG_IGN);
7759 if (setlocale(LC_ALL, "")) {
7760 codeset = nl_langinfo(CODESET);
7761 }
7763 if (load_repo_info() == ERR)
7764 die("Failed to load repo info.");
7766 if (load_options() == ERR)
7767 die("Failed to load user config.");
7769 if (load_git_config() == ERR)
7770 die("Failed to load repo config.");
7772 /* Require a git repository unless when running in pager mode. */
7773 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7774 die("Not a git repository");
7776 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7777 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7778 if (opt_iconv_in == ICONV_NONE)
7779 die("Failed to initialize character set conversion");
7780 }
7782 if (codeset && strcmp(codeset, "UTF-8")) {
7783 opt_iconv_out = iconv_open(codeset, "UTF-8");
7784 if (opt_iconv_out == ICONV_NONE)
7785 die("Failed to initialize character set conversion");
7786 }
7788 if (load_refs() == ERR)
7789 die("Failed to load refs.");
7791 foreach_view (view, i)
7792 if (!argv_from_env(view->ops->argv, view->cmd_env))
7793 die("Too many arguments in the `%s` environment variable",
7794 view->cmd_env);
7796 init_display();
7798 if (request != REQ_NONE)
7799 open_view(NULL, request, OPEN_PREPARED);
7800 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7802 while (view_driver(display[current_view], request)) {
7803 int key = get_input(0);
7805 view = display[current_view];
7806 request = get_keybinding(view->keymap, key);
7808 /* Some low-level request handling. This keeps access to
7809 * status_win restricted. */
7810 switch (request) {
7811 case REQ_NONE:
7812 report("Unknown key, press %s for help",
7813 get_key(view->keymap, REQ_VIEW_HELP));
7814 break;
7815 case REQ_PROMPT:
7816 {
7817 char *cmd = read_prompt(":");
7819 if (cmd && isdigit(*cmd)) {
7820 int lineno = view->lineno + 1;
7822 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7823 select_view_line(view, lineno - 1);
7824 report("");
7825 } else {
7826 report("Unable to parse '%s' as a line number", cmd);
7827 }
7829 } else if (cmd) {
7830 struct view *next = VIEW(REQ_VIEW_PAGER);
7831 const char *argv[SIZEOF_ARG] = { "git" };
7832 int argc = 1;
7834 /* When running random commands, initially show the
7835 * command in the title. However, it maybe later be
7836 * overwritten if a commit line is selected. */
7837 string_ncopy(next->ref, cmd, strlen(cmd));
7839 if (!argv_from_string(argv, &argc, cmd)) {
7840 report("Too many arguments");
7841 } else if (!prepare_update(next, argv, NULL)) {
7842 report("Failed to format command");
7843 } else {
7844 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7845 }
7846 }
7848 request = REQ_NONE;
7849 break;
7850 }
7851 case REQ_SEARCH:
7852 case REQ_SEARCH_BACK:
7853 {
7854 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7855 char *search = read_prompt(prompt);
7857 if (search)
7858 string_ncopy(opt_search, search, strlen(search));
7859 else if (*opt_search)
7860 request = request == REQ_SEARCH ?
7861 REQ_FIND_NEXT :
7862 REQ_FIND_PREV;
7863 else
7864 request = REQ_NONE;
7865 break;
7866 }
7867 default:
7868 break;
7869 }
7870 }
7872 quit(0);
7874 return 0;
7875 }