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, ...);
71 static size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size);
72 static inline unsigned char utf8_char_length(const char *string, const char *end);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define MAX(x, y) ((x) > (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
86 /* Revision graph */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
100 #ifndef ICONV_CONST
101 #define ICONV_CONST /* nothing */
102 #endif
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
109 #define ID_COLS 8
110 #define AUTHOR_COLS 19
112 #define MIN_VIEW_HEIGHT 4
114 #define NULL_ID "0000000000000000000000000000000000000000"
116 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
118 /* Some ASCII-shorthands fitted into the ncurses namespace. */
119 #define KEY_TAB '\t'
120 #define KEY_RETURN '\r'
121 #define KEY_ESC 27
124 struct ref {
125 char id[SIZEOF_REV]; /* Commit SHA1 ID */
126 unsigned int head:1; /* Is it the current HEAD? */
127 unsigned int tag:1; /* Is it a tag? */
128 unsigned int ltag:1; /* If so, is the tag local? */
129 unsigned int remote:1; /* Is it a remote ref? */
130 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
131 char name[1]; /* Ref name; tag or head names are shortened. */
132 };
134 struct ref_list {
135 char id[SIZEOF_REV]; /* Commit SHA1 ID */
136 size_t size; /* Number of refs. */
137 struct ref **refs; /* References for this ID. */
138 };
140 static struct ref *get_ref_head();
141 static struct ref_list *get_ref_list(const char *id);
142 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
143 static int load_refs(void);
145 enum format_flags {
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
149 };
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
153 enum input_status {
154 INPUT_OK,
155 INPUT_SKIP,
156 INPUT_STOP,
157 INPUT_CANCEL
158 };
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
165 struct menu_item {
166 int hotkey;
167 const char *text;
168 void *data;
169 };
171 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
173 /*
174 * Allocation helpers ... Entering macro hell to never be seen again.
175 */
177 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
178 static type * \
179 name(type **mem, size_t size, size_t increase) \
180 { \
181 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
182 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
183 type *tmp = *mem; \
184 \
185 if (mem == NULL || num_chunks != num_chunks_new) { \
186 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
187 if (tmp) \
188 *mem = tmp; \
189 } \
190 \
191 return tmp; \
192 }
194 /*
195 * String helpers
196 */
198 static inline void
199 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200 {
201 if (srclen > dstlen - 1)
202 srclen = dstlen - 1;
204 strncpy(dst, src, srclen);
205 dst[srclen] = 0;
206 }
208 /* Shorthands for safely copying into a fixed buffer. */
210 #define string_copy(dst, src) \
211 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
213 #define string_ncopy(dst, src, srclen) \
214 string_ncopy_do(dst, sizeof(dst), src, srclen)
216 #define string_copy_rev(dst, src) \
217 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
219 #define string_add(dst, from, src) \
220 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
222 static void
223 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
224 {
225 size_t size, pos;
227 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
228 if (src[pos] == '\t') {
229 size_t expanded = tabsize - (size % tabsize);
231 if (expanded + size >= dstlen - 1)
232 expanded = dstlen - size - 1;
233 memcpy(dst + size, " ", expanded);
234 size += expanded;
235 } else {
236 dst[size++] = src[pos];
237 }
238 }
240 dst[size] = 0;
241 }
243 static char *
244 chomp_string(char *name)
245 {
246 int namelen;
248 while (isspace(*name))
249 name++;
251 namelen = strlen(name) - 1;
252 while (namelen > 0 && isspace(name[namelen]))
253 name[namelen--] = 0;
255 return name;
256 }
258 static bool
259 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
260 {
261 va_list args;
262 size_t pos = bufpos ? *bufpos : 0;
264 va_start(args, fmt);
265 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
266 va_end(args);
268 if (bufpos)
269 *bufpos = pos;
271 return pos >= bufsize ? FALSE : TRUE;
272 }
274 #define string_format(buf, fmt, args...) \
275 string_nformat(buf, sizeof(buf), NULL, fmt, args)
277 #define string_format_from(buf, from, fmt, args...) \
278 string_nformat(buf, sizeof(buf), from, fmt, args)
280 static int
281 string_enum_compare(const char *str1, const char *str2, int len)
282 {
283 size_t i;
285 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
287 /* Diff-Header == DIFF_HEADER */
288 for (i = 0; i < len; i++) {
289 if (toupper(str1[i]) == toupper(str2[i]))
290 continue;
292 if (string_enum_sep(str1[i]) &&
293 string_enum_sep(str2[i]))
294 continue;
296 return str1[i] - str2[i];
297 }
299 return 0;
300 }
302 #define enum_equals(entry, str, len) \
303 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
305 struct enum_map {
306 const char *name;
307 int namelen;
308 int value;
309 };
311 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
313 static char *
314 enum_map_name(const char *name, size_t namelen)
315 {
316 static char buf[SIZEOF_STR];
317 int bufpos;
319 for (bufpos = 0; bufpos <= namelen; bufpos++) {
320 buf[bufpos] = tolower(name[bufpos]);
321 if (buf[bufpos] == '_')
322 buf[bufpos] = '-';
323 }
325 buf[bufpos] = 0;
326 return buf;
327 }
329 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
331 static bool
332 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
333 {
334 size_t namelen = strlen(name);
335 int i;
337 for (i = 0; i < map_size; i++)
338 if (enum_equals(map[i], name, namelen)) {
339 *value = map[i].value;
340 return TRUE;
341 }
343 return FALSE;
344 }
346 #define map_enum(attr, map, name) \
347 map_enum_do(map, ARRAY_SIZE(map), attr, name)
349 #define prefixcmp(str1, str2) \
350 strncmp(str1, str2, STRING_SIZE(str2))
352 static inline int
353 suffixcmp(const char *str, int slen, const char *suffix)
354 {
355 size_t len = slen >= 0 ? slen : strlen(str);
356 size_t suffixlen = strlen(suffix);
358 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
359 }
362 #define DATE_INFO \
363 DATE_(NO), \
364 DATE_(DEFAULT), \
365 DATE_(RELATIVE), \
366 DATE_(SHORT)
368 enum date {
369 #define DATE_(name) DATE_##name
370 DATE_INFO
371 #undef DATE_
372 };
374 static const struct enum_map date_map[] = {
375 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
376 DATE_INFO
377 #undef DATE_
378 };
380 struct time {
381 time_t sec;
382 int tz;
383 };
385 static inline int timecmp(const struct time *t1, const struct time *t2)
386 {
387 return t1->sec - t2->sec;
388 }
390 static const char *
391 mkdate(const struct time *time, enum date date)
392 {
393 static char buf[DATE_COLS + 1];
394 static const struct enum_map reldate[] = {
395 { "second", 1, 60 * 2 },
396 { "minute", 60, 60 * 60 * 2 },
397 { "hour", 60 * 60, 60 * 60 * 24 * 2 },
398 { "day", 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
399 { "week", 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
400 { "month", 60 * 60 * 24 * 30, 60 * 60 * 24 * 30 * 12 },
401 };
402 struct tm tm;
404 if (!date || !time || !time->sec)
405 return "";
407 if (date == DATE_RELATIVE) {
408 struct timeval now;
409 time_t date = time->sec + time->tz;
410 time_t seconds;
411 int i;
413 gettimeofday(&now, NULL);
414 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
415 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
416 if (seconds >= reldate[i].value)
417 continue;
419 seconds /= reldate[i].namelen;
420 if (!string_format(buf, "%ld %s%s %s",
421 seconds, reldate[i].name,
422 seconds > 1 ? "s" : "",
423 now.tv_sec >= date ? "ago" : "ahead"))
424 break;
425 return buf;
426 }
427 }
429 gmtime_r(&time->sec, &tm);
430 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
431 }
434 #define AUTHOR_VALUES \
435 AUTHOR_(NO), \
436 AUTHOR_(FULL), \
437 AUTHOR_(ABBREVIATED)
439 enum author {
440 #define AUTHOR_(name) AUTHOR_##name
441 AUTHOR_VALUES,
442 #undef AUTHOR_
443 AUTHOR_DEFAULT = AUTHOR_FULL
444 };
446 static const struct enum_map author_map[] = {
447 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
448 AUTHOR_VALUES
449 #undef AUTHOR_
450 };
452 static const char *
453 get_author_initials(const char *author)
454 {
455 static char initials[AUTHOR_COLS * 6 + 1];
456 size_t pos = 0;
457 const char *end = strchr(author, '\0');
459 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
461 memset(initials, 0, sizeof(initials));
462 while (author < end) {
463 unsigned char bytes;
464 size_t i;
466 while (is_initial_sep(*author))
467 author++;
469 bytes = utf8_char_length(author, end);
470 if (bytes < sizeof(initials) - 1 - pos) {
471 while (bytes--) {
472 initials[pos++] = *author++;
473 }
474 }
476 for (i = pos; author < end && !is_initial_sep(*author); author++) {
477 if (i < sizeof(initials) - 1)
478 initials[i++] = *author;
479 }
481 initials[i++] = 0;
482 }
484 return initials;
485 }
488 static bool
489 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
490 {
491 int valuelen;
493 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
494 bool advance = cmd[valuelen] != 0;
496 cmd[valuelen] = 0;
497 argv[(*argc)++] = chomp_string(cmd);
498 cmd = chomp_string(cmd + valuelen + advance);
499 }
501 if (*argc < SIZEOF_ARG)
502 argv[*argc] = NULL;
503 return *argc < SIZEOF_ARG;
504 }
506 static void
507 argv_from_env(const char **argv, const char *name)
508 {
509 char *env = argv ? getenv(name) : NULL;
510 int argc = 0;
512 if (env && *env)
513 env = strdup(env);
514 if (env && !argv_from_string(argv, &argc, env))
515 die("Too many arguments in the `%s` environment variable", name);
516 }
519 /*
520 * Executing external commands.
521 */
523 enum io_type {
524 IO_FD, /* File descriptor based IO. */
525 IO_BG, /* Execute command in the background. */
526 IO_FG, /* Execute command with same std{in,out,err}. */
527 IO_RD, /* Read only fork+exec IO. */
528 IO_WR, /* Write only fork+exec IO. */
529 IO_AP, /* Append fork+exec output to file. */
530 };
532 struct io {
533 enum io_type type; /* The requested type of pipe. */
534 const char *dir; /* Directory from which to execute. */
535 pid_t pid; /* PID of spawned process. */
536 int pipe; /* Pipe end for reading or writing. */
537 int error; /* Error status. */
538 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
539 char *buf; /* Read buffer. */
540 size_t bufalloc; /* Allocated buffer size. */
541 size_t bufsize; /* Buffer content size. */
542 char *bufpos; /* Current buffer position. */
543 unsigned int eof:1; /* Has end of file been reached. */
544 };
546 static void
547 io_reset(struct io *io)
548 {
549 io->pipe = -1;
550 io->pid = 0;
551 io->buf = io->bufpos = NULL;
552 io->bufalloc = io->bufsize = 0;
553 io->error = 0;
554 io->eof = 0;
555 }
557 static void
558 io_init(struct io *io, const char *dir, enum io_type type)
559 {
560 io_reset(io);
561 io->type = type;
562 io->dir = dir;
563 }
565 static bool
566 io_init_rd(struct io *io, const char *argv[], const char *dir,
567 enum format_flags flags)
568 {
569 io_init(io, dir, IO_RD);
570 return format_argv(io->argv, argv, flags);
571 }
573 static bool
574 io_open(struct io *io, const char *fmt, ...)
575 {
576 char name[SIZEOF_STR] = "";
577 bool fits;
578 va_list args;
580 io_init(io, NULL, IO_FD);
582 va_start(args, fmt);
583 fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
584 va_end(args);
586 if (!fits) {
587 io->error = ENAMETOOLONG;
588 return FALSE;
589 }
590 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
591 if (io->pipe == -1)
592 io->error = errno;
593 return io->pipe != -1;
594 }
596 static bool
597 io_kill(struct io *io)
598 {
599 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
600 }
602 static bool
603 io_done(struct io *io)
604 {
605 pid_t pid = io->pid;
607 if (io->pipe != -1)
608 close(io->pipe);
609 free(io->buf);
610 io_reset(io);
612 while (pid > 0) {
613 int status;
614 pid_t waiting = waitpid(pid, &status, 0);
616 if (waiting < 0) {
617 if (errno == EINTR)
618 continue;
619 io->error = errno;
620 return FALSE;
621 }
623 return waiting == pid &&
624 !WIFSIGNALED(status) &&
625 WIFEXITED(status) &&
626 !WEXITSTATUS(status);
627 }
629 return TRUE;
630 }
632 static bool
633 io_start(struct io *io)
634 {
635 int pipefds[2] = { -1, -1 };
637 if (io->type == IO_FD)
638 return TRUE;
640 if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
641 io->error = errno;
642 return FALSE;
643 } else if (io->type == IO_AP) {
644 pipefds[1] = io->pipe;
645 }
647 if ((io->pid = fork())) {
648 if (io->pid == -1)
649 io->error = errno;
650 if (pipefds[!(io->type == IO_WR)] != -1)
651 close(pipefds[!(io->type == IO_WR)]);
652 if (io->pid != -1) {
653 io->pipe = pipefds[!!(io->type == IO_WR)];
654 return TRUE;
655 }
657 } else {
658 if (io->type != IO_FG) {
659 int devnull = open("/dev/null", O_RDWR);
660 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
661 int writefd = (io->type == IO_RD || io->type == IO_AP)
662 ? pipefds[1] : devnull;
664 dup2(readfd, STDIN_FILENO);
665 dup2(writefd, STDOUT_FILENO);
666 dup2(devnull, STDERR_FILENO);
668 close(devnull);
669 if (pipefds[0] != -1)
670 close(pipefds[0]);
671 if (pipefds[1] != -1)
672 close(pipefds[1]);
673 }
675 if (io->dir && *io->dir && chdir(io->dir) == -1)
676 exit(errno);
678 execvp(io->argv[0], (char *const*) io->argv);
679 exit(errno);
680 }
682 if (pipefds[!!(io->type == IO_WR)] != -1)
683 close(pipefds[!!(io->type == IO_WR)]);
684 return FALSE;
685 }
687 static bool
688 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
689 {
690 io_init(io, dir, type);
691 if (!format_argv(io->argv, argv, FORMAT_NONE))
692 return FALSE;
693 return io_start(io);
694 }
696 static int
697 io_complete(struct io *io)
698 {
699 return io_start(io) && io_done(io);
700 }
702 static int
703 io_run_bg(const char **argv)
704 {
705 struct io io = {};
707 io_init(&io, NULL, IO_BG);
708 if (!format_argv(io.argv, argv, FORMAT_NONE))
709 return FALSE;
710 return io_complete(&io);
711 }
713 static bool
714 io_run_fg(const char **argv, const char *dir)
715 {
716 struct io io = {};
718 io_init(&io, dir, IO_FG);
719 if (!format_argv(io.argv, argv, FORMAT_NONE))
720 return FALSE;
721 return io_complete(&io);
722 }
724 static bool
725 io_run_append(const char **argv, enum format_flags flags, int fd)
726 {
727 struct io io = {};
729 io_init(&io, NULL, IO_AP);
730 io.pipe = fd;
731 if (format_argv(io.argv, argv, flags))
732 return io_complete(&io);
733 close(fd);
734 return FALSE;
735 }
737 static bool
738 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
739 {
740 return io_init_rd(io, argv, dir, flags) && io_start(io);
741 }
743 static bool
744 io_eof(struct io *io)
745 {
746 return io->eof;
747 }
749 static int
750 io_error(struct io *io)
751 {
752 return io->error;
753 }
755 static char *
756 io_strerror(struct io *io)
757 {
758 return strerror(io->error);
759 }
761 static bool
762 io_can_read(struct io *io)
763 {
764 struct timeval tv = { 0, 500 };
765 fd_set fds;
767 FD_ZERO(&fds);
768 FD_SET(io->pipe, &fds);
770 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
771 }
773 static ssize_t
774 io_read(struct io *io, void *buf, size_t bufsize)
775 {
776 do {
777 ssize_t readsize = read(io->pipe, buf, bufsize);
779 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
780 continue;
781 else if (readsize == -1)
782 io->error = errno;
783 else if (readsize == 0)
784 io->eof = 1;
785 return readsize;
786 } while (1);
787 }
789 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
791 static char *
792 io_get(struct io *io, int c, bool can_read)
793 {
794 char *eol;
795 ssize_t readsize;
797 while (TRUE) {
798 if (io->bufsize > 0) {
799 eol = memchr(io->bufpos, c, io->bufsize);
800 if (eol) {
801 char *line = io->bufpos;
803 *eol = 0;
804 io->bufpos = eol + 1;
805 io->bufsize -= io->bufpos - line;
806 return line;
807 }
808 }
810 if (io_eof(io)) {
811 if (io->bufsize) {
812 io->bufpos[io->bufsize] = 0;
813 io->bufsize = 0;
814 return io->bufpos;
815 }
816 return NULL;
817 }
819 if (!can_read)
820 return NULL;
822 if (io->bufsize > 0 && io->bufpos > io->buf)
823 memmove(io->buf, io->bufpos, io->bufsize);
825 if (io->bufalloc == io->bufsize) {
826 if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
827 return NULL;
828 io->bufalloc += BUFSIZ;
829 }
831 io->bufpos = io->buf;
832 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
833 if (io_error(io))
834 return NULL;
835 io->bufsize += readsize;
836 }
837 }
839 static bool
840 io_write(struct io *io, const void *buf, size_t bufsize)
841 {
842 size_t written = 0;
844 while (!io_error(io) && written < bufsize) {
845 ssize_t size;
847 size = write(io->pipe, buf + written, bufsize - written);
848 if (size < 0 && (errno == EAGAIN || errno == EINTR))
849 continue;
850 else if (size == -1)
851 io->error = errno;
852 else
853 written += size;
854 }
856 return written == bufsize;
857 }
859 static bool
860 io_read_buf(struct io *io, char buf[], size_t bufsize)
861 {
862 char *result = io_get(io, '\n', TRUE);
864 if (result) {
865 result = chomp_string(result);
866 string_ncopy_do(buf, bufsize, result, strlen(result));
867 }
869 return io_done(io) && result;
870 }
872 static bool
873 io_run_buf(const char **argv, char buf[], size_t bufsize)
874 {
875 struct io io = {};
877 return io_run_rd(&io, argv, NULL, FORMAT_NONE)
878 && io_read_buf(&io, buf, bufsize);
879 }
881 static int
882 io_load(struct io *io, const char *separators,
883 int (*read_property)(char *, size_t, char *, size_t))
884 {
885 char *name;
886 int state = OK;
888 if (!io_start(io))
889 return ERR;
891 while (state == OK && (name = io_get(io, '\n', TRUE))) {
892 char *value;
893 size_t namelen;
894 size_t valuelen;
896 name = chomp_string(name);
897 namelen = strcspn(name, separators);
899 if (name[namelen]) {
900 name[namelen] = 0;
901 value = chomp_string(name + namelen + 1);
902 valuelen = strlen(value);
904 } else {
905 value = "";
906 valuelen = 0;
907 }
909 state = read_property(name, namelen, value, valuelen);
910 }
912 if (state != ERR && io_error(io))
913 state = ERR;
914 io_done(io);
916 return state;
917 }
919 static int
920 io_run_load(const char **argv, const char *separators,
921 int (*read_property)(char *, size_t, char *, size_t))
922 {
923 struct io io = {};
925 return io_init_rd(&io, argv, NULL, FORMAT_NONE)
926 ? io_load(&io, separators, read_property) : ERR;
927 }
930 /*
931 * User requests
932 */
934 #define REQ_INFO \
935 /* XXX: Keep the view request first and in sync with views[]. */ \
936 REQ_GROUP("View switching") \
937 REQ_(VIEW_MAIN, "Show main view"), \
938 REQ_(VIEW_DIFF, "Show diff view"), \
939 REQ_(VIEW_LOG, "Show log view"), \
940 REQ_(VIEW_TREE, "Show tree view"), \
941 REQ_(VIEW_BLOB, "Show blob view"), \
942 REQ_(VIEW_BLAME, "Show blame view"), \
943 REQ_(VIEW_BRANCH, "Show branch view"), \
944 REQ_(VIEW_HELP, "Show help page"), \
945 REQ_(VIEW_PAGER, "Show pager view"), \
946 REQ_(VIEW_STATUS, "Show status view"), \
947 REQ_(VIEW_STAGE, "Show stage view"), \
948 \
949 REQ_GROUP("View manipulation") \
950 REQ_(ENTER, "Enter current line and scroll"), \
951 REQ_(NEXT, "Move to next"), \
952 REQ_(PREVIOUS, "Move to previous"), \
953 REQ_(PARENT, "Move to parent"), \
954 REQ_(VIEW_NEXT, "Move focus to next view"), \
955 REQ_(REFRESH, "Reload and refresh"), \
956 REQ_(MAXIMIZE, "Maximize the current view"), \
957 REQ_(VIEW_CLOSE, "Close the current view"), \
958 REQ_(QUIT, "Close all views and quit"), \
959 \
960 REQ_GROUP("View specific requests") \
961 REQ_(STATUS_UPDATE, "Update file status"), \
962 REQ_(STATUS_REVERT, "Revert file changes"), \
963 REQ_(STATUS_MERGE, "Merge file using external tool"), \
964 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
965 \
966 REQ_GROUP("Cursor navigation") \
967 REQ_(MOVE_UP, "Move cursor one line up"), \
968 REQ_(MOVE_DOWN, "Move cursor one line down"), \
969 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
970 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
971 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
972 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
973 \
974 REQ_GROUP("Scrolling") \
975 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
976 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
977 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
978 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
979 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
980 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
981 \
982 REQ_GROUP("Searching") \
983 REQ_(SEARCH, "Search the view"), \
984 REQ_(SEARCH_BACK, "Search backwards in the view"), \
985 REQ_(FIND_NEXT, "Find next search match"), \
986 REQ_(FIND_PREV, "Find previous search match"), \
987 \
988 REQ_GROUP("Option manipulation") \
989 REQ_(OPTIONS, "Open option menu"), \
990 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
991 REQ_(TOGGLE_DATE, "Toggle date display"), \
992 REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
993 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
994 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
995 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
996 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
997 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
998 \
999 REQ_GROUP("Misc") \
1000 REQ_(PROMPT, "Bring up the prompt"), \
1001 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
1002 REQ_(SHOW_VERSION, "Show version information"), \
1003 REQ_(STOP_LOADING, "Stop all loading views"), \
1004 REQ_(EDIT, "Open in editor"), \
1005 REQ_(NONE, "Do nothing")
1008 /* User action requests. */
1009 enum request {
1010 #define REQ_GROUP(help)
1011 #define REQ_(req, help) REQ_##req
1013 /* Offset all requests to avoid conflicts with ncurses getch values. */
1014 REQ_OFFSET = KEY_MAX + 1,
1015 REQ_INFO
1017 #undef REQ_GROUP
1018 #undef REQ_
1019 };
1021 struct request_info {
1022 enum request request;
1023 const char *name;
1024 int namelen;
1025 const char *help;
1026 };
1028 static const struct request_info req_info[] = {
1029 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1030 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1031 REQ_INFO
1032 #undef REQ_GROUP
1033 #undef REQ_
1034 };
1036 static enum request
1037 get_request(const char *name)
1038 {
1039 int namelen = strlen(name);
1040 int i;
1042 for (i = 0; i < ARRAY_SIZE(req_info); i++)
1043 if (enum_equals(req_info[i], name, namelen))
1044 return req_info[i].request;
1046 return REQ_NONE;
1047 }
1050 /*
1051 * Options
1052 */
1054 /* Option and state variables. */
1055 static enum date opt_date = DATE_DEFAULT;
1056 static enum author opt_author = AUTHOR_DEFAULT;
1057 static bool opt_line_number = FALSE;
1058 static bool opt_line_graphics = TRUE;
1059 static bool opt_rev_graph = FALSE;
1060 static bool opt_show_refs = TRUE;
1061 static int opt_num_interval = 5;
1062 static double opt_hscroll = 0.50;
1063 static double opt_scale_split_view = 2.0 / 3.0;
1064 static int opt_tab_size = 8;
1065 static int opt_author_cols = AUTHOR_COLS;
1066 static char opt_path[SIZEOF_STR] = "";
1067 static char opt_file[SIZEOF_STR] = "";
1068 static char opt_ref[SIZEOF_REF] = "";
1069 static char opt_head[SIZEOF_REF] = "";
1070 static char opt_remote[SIZEOF_REF] = "";
1071 static char opt_encoding[20] = "UTF-8";
1072 static iconv_t opt_iconv_in = ICONV_NONE;
1073 static iconv_t opt_iconv_out = ICONV_NONE;
1074 static char opt_search[SIZEOF_STR] = "";
1075 static char opt_cdup[SIZEOF_STR] = "";
1076 static char opt_prefix[SIZEOF_STR] = "";
1077 static char opt_git_dir[SIZEOF_STR] = "";
1078 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
1079 static char opt_editor[SIZEOF_STR] = "";
1080 static FILE *opt_tty = NULL;
1082 #define is_initial_commit() (!get_ref_head())
1083 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1086 /*
1087 * Line-oriented content detection.
1088 */
1090 #define LINE_INFO \
1091 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1092 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1093 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
1094 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
1095 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1096 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1097 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1098 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1099 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1100 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1101 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1102 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1103 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1104 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1105 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
1106 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1107 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1108 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1109 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1110 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1111 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
1112 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1113 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1114 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
1115 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
1116 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1117 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1118 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1119 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1120 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
1121 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
1122 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1123 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1124 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1125 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1126 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
1127 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
1128 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1129 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
1130 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1131 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1132 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
1133 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1134 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
1135 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1136 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
1137 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
1138 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
1139 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
1140 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1141 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
1142 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1143 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1144 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
1145 LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
1146 LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
1147 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
1149 enum line_type {
1150 #define LINE(type, line, fg, bg, attr) \
1151 LINE_##type
1152 LINE_INFO,
1153 LINE_NONE
1154 #undef LINE
1155 };
1157 struct line_info {
1158 const char *name; /* Option name. */
1159 int namelen; /* Size of option name. */
1160 const char *line; /* The start of line to match. */
1161 int linelen; /* Size of string to match. */
1162 int fg, bg, attr; /* Color and text attributes for the lines. */
1163 };
1165 static struct line_info line_info[] = {
1166 #define LINE(type, line, fg, bg, attr) \
1167 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1168 LINE_INFO
1169 #undef LINE
1170 };
1172 static enum line_type
1173 get_line_type(const char *line)
1174 {
1175 int linelen = strlen(line);
1176 enum line_type type;
1178 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1179 /* Case insensitive search matches Signed-off-by lines better. */
1180 if (linelen >= line_info[type].linelen &&
1181 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1182 return type;
1184 return LINE_DEFAULT;
1185 }
1187 static inline int
1188 get_line_attr(enum line_type type)
1189 {
1190 assert(type < ARRAY_SIZE(line_info));
1191 return COLOR_PAIR(type) | line_info[type].attr;
1192 }
1194 static struct line_info *
1195 get_line_info(const char *name)
1196 {
1197 size_t namelen = strlen(name);
1198 enum line_type type;
1200 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1201 if (enum_equals(line_info[type], name, namelen))
1202 return &line_info[type];
1204 return NULL;
1205 }
1207 static void
1208 init_colors(void)
1209 {
1210 int default_bg = line_info[LINE_DEFAULT].bg;
1211 int default_fg = line_info[LINE_DEFAULT].fg;
1212 enum line_type type;
1214 start_color();
1216 if (assume_default_colors(default_fg, default_bg) == ERR) {
1217 default_bg = COLOR_BLACK;
1218 default_fg = COLOR_WHITE;
1219 }
1221 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1222 struct line_info *info = &line_info[type];
1223 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1224 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1226 init_pair(type, fg, bg);
1227 }
1228 }
1230 struct line {
1231 enum line_type type;
1233 /* State flags */
1234 unsigned int selected:1;
1235 unsigned int dirty:1;
1236 unsigned int cleareol:1;
1237 unsigned int other:16;
1239 void *data; /* User data */
1240 };
1243 /*
1244 * Keys
1245 */
1247 struct keybinding {
1248 int alias;
1249 enum request request;
1250 };
1252 static const struct keybinding default_keybindings[] = {
1253 /* View switching */
1254 { 'm', REQ_VIEW_MAIN },
1255 { 'd', REQ_VIEW_DIFF },
1256 { 'l', REQ_VIEW_LOG },
1257 { 't', REQ_VIEW_TREE },
1258 { 'f', REQ_VIEW_BLOB },
1259 { 'B', REQ_VIEW_BLAME },
1260 { 'H', REQ_VIEW_BRANCH },
1261 { 'p', REQ_VIEW_PAGER },
1262 { 'h', REQ_VIEW_HELP },
1263 { 'S', REQ_VIEW_STATUS },
1264 { 'c', REQ_VIEW_STAGE },
1266 /* View manipulation */
1267 { 'q', REQ_VIEW_CLOSE },
1268 { KEY_TAB, REQ_VIEW_NEXT },
1269 { KEY_RETURN, REQ_ENTER },
1270 { KEY_UP, REQ_PREVIOUS },
1271 { KEY_DOWN, REQ_NEXT },
1272 { 'R', REQ_REFRESH },
1273 { KEY_F(5), REQ_REFRESH },
1274 { 'O', REQ_MAXIMIZE },
1276 /* Cursor navigation */
1277 { 'k', REQ_MOVE_UP },
1278 { 'j', REQ_MOVE_DOWN },
1279 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1280 { KEY_END, REQ_MOVE_LAST_LINE },
1281 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1282 { ' ', REQ_MOVE_PAGE_DOWN },
1283 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1284 { 'b', REQ_MOVE_PAGE_UP },
1285 { '-', REQ_MOVE_PAGE_UP },
1287 /* Scrolling */
1288 { KEY_LEFT, REQ_SCROLL_LEFT },
1289 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1290 { KEY_IC, REQ_SCROLL_LINE_UP },
1291 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1292 { 'w', REQ_SCROLL_PAGE_UP },
1293 { 's', REQ_SCROLL_PAGE_DOWN },
1295 /* Searching */
1296 { '/', REQ_SEARCH },
1297 { '?', REQ_SEARCH_BACK },
1298 { 'n', REQ_FIND_NEXT },
1299 { 'N', REQ_FIND_PREV },
1301 /* Misc */
1302 { 'Q', REQ_QUIT },
1303 { 'z', REQ_STOP_LOADING },
1304 { 'v', REQ_SHOW_VERSION },
1305 { 'r', REQ_SCREEN_REDRAW },
1306 { 'o', REQ_OPTIONS },
1307 { '.', REQ_TOGGLE_LINENO },
1308 { 'D', REQ_TOGGLE_DATE },
1309 { 'A', REQ_TOGGLE_AUTHOR },
1310 { 'g', REQ_TOGGLE_REV_GRAPH },
1311 { 'F', REQ_TOGGLE_REFS },
1312 { 'I', REQ_TOGGLE_SORT_ORDER },
1313 { 'i', REQ_TOGGLE_SORT_FIELD },
1314 { ':', REQ_PROMPT },
1315 { 'u', REQ_STATUS_UPDATE },
1316 { '!', REQ_STATUS_REVERT },
1317 { 'M', REQ_STATUS_MERGE },
1318 { '@', REQ_STAGE_NEXT },
1319 { ',', REQ_PARENT },
1320 { 'e', REQ_EDIT },
1321 };
1323 #define KEYMAP_INFO \
1324 KEYMAP_(GENERIC), \
1325 KEYMAP_(MAIN), \
1326 KEYMAP_(DIFF), \
1327 KEYMAP_(LOG), \
1328 KEYMAP_(TREE), \
1329 KEYMAP_(BLOB), \
1330 KEYMAP_(BLAME), \
1331 KEYMAP_(BRANCH), \
1332 KEYMAP_(PAGER), \
1333 KEYMAP_(HELP), \
1334 KEYMAP_(STATUS), \
1335 KEYMAP_(STAGE)
1337 enum keymap {
1338 #define KEYMAP_(name) KEYMAP_##name
1339 KEYMAP_INFO
1340 #undef KEYMAP_
1341 };
1343 static const struct enum_map keymap_table[] = {
1344 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1345 KEYMAP_INFO
1346 #undef KEYMAP_
1347 };
1349 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1351 struct keybinding_table {
1352 struct keybinding *data;
1353 size_t size;
1354 };
1356 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1358 static void
1359 add_keybinding(enum keymap keymap, enum request request, int key)
1360 {
1361 struct keybinding_table *table = &keybindings[keymap];
1363 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1364 if (!table->data)
1365 die("Failed to allocate keybinding");
1366 table->data[table->size].alias = key;
1367 table->data[table->size++].request = request;
1368 }
1370 /* Looks for a key binding first in the given map, then in the generic map, and
1371 * lastly in the default keybindings. */
1372 static enum request
1373 get_keybinding(enum keymap keymap, int key)
1374 {
1375 size_t i;
1377 for (i = 0; i < keybindings[keymap].size; i++)
1378 if (keybindings[keymap].data[i].alias == key)
1379 return keybindings[keymap].data[i].request;
1381 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1382 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1383 return keybindings[KEYMAP_GENERIC].data[i].request;
1385 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1386 if (default_keybindings[i].alias == key)
1387 return default_keybindings[i].request;
1389 return (enum request) key;
1390 }
1393 struct key {
1394 const char *name;
1395 int value;
1396 };
1398 static const struct key key_table[] = {
1399 { "Enter", KEY_RETURN },
1400 { "Space", ' ' },
1401 { "Backspace", KEY_BACKSPACE },
1402 { "Tab", KEY_TAB },
1403 { "Escape", KEY_ESC },
1404 { "Left", KEY_LEFT },
1405 { "Right", KEY_RIGHT },
1406 { "Up", KEY_UP },
1407 { "Down", KEY_DOWN },
1408 { "Insert", KEY_IC },
1409 { "Delete", KEY_DC },
1410 { "Hash", '#' },
1411 { "Home", KEY_HOME },
1412 { "End", KEY_END },
1413 { "PageUp", KEY_PPAGE },
1414 { "PageDown", KEY_NPAGE },
1415 { "F1", KEY_F(1) },
1416 { "F2", KEY_F(2) },
1417 { "F3", KEY_F(3) },
1418 { "F4", KEY_F(4) },
1419 { "F5", KEY_F(5) },
1420 { "F6", KEY_F(6) },
1421 { "F7", KEY_F(7) },
1422 { "F8", KEY_F(8) },
1423 { "F9", KEY_F(9) },
1424 { "F10", KEY_F(10) },
1425 { "F11", KEY_F(11) },
1426 { "F12", KEY_F(12) },
1427 };
1429 static int
1430 get_key_value(const char *name)
1431 {
1432 int i;
1434 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1435 if (!strcasecmp(key_table[i].name, name))
1436 return key_table[i].value;
1438 if (strlen(name) == 1 && isprint(*name))
1439 return (int) *name;
1441 return ERR;
1442 }
1444 static const char *
1445 get_key_name(int key_value)
1446 {
1447 static char key_char[] = "'X'";
1448 const char *seq = NULL;
1449 int key;
1451 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1452 if (key_table[key].value == key_value)
1453 seq = key_table[key].name;
1455 if (seq == NULL &&
1456 key_value < 127 &&
1457 isprint(key_value)) {
1458 key_char[1] = (char) key_value;
1459 seq = key_char;
1460 }
1462 return seq ? seq : "(no key)";
1463 }
1465 static bool
1466 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1467 {
1468 const char *sep = *pos > 0 ? ", " : "";
1469 const char *keyname = get_key_name(keybinding->alias);
1471 return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1472 }
1474 static bool
1475 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1476 enum keymap keymap, bool all)
1477 {
1478 int i;
1480 for (i = 0; i < keybindings[keymap].size; i++) {
1481 if (keybindings[keymap].data[i].request == request) {
1482 if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1483 return FALSE;
1484 if (!all)
1485 break;
1486 }
1487 }
1489 return TRUE;
1490 }
1492 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1494 static const char *
1495 get_keys(enum keymap keymap, enum request request, bool all)
1496 {
1497 static char buf[BUFSIZ];
1498 size_t pos = 0;
1499 int i;
1501 buf[pos] = 0;
1503 if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1504 return "Too many keybindings!";
1505 if (pos > 0 && !all)
1506 return buf;
1508 if (keymap != KEYMAP_GENERIC) {
1509 /* Only the generic keymap includes the default keybindings when
1510 * listing all keys. */
1511 if (all)
1512 return buf;
1514 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1515 return "Too many keybindings!";
1516 if (pos)
1517 return buf;
1518 }
1520 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1521 if (default_keybindings[i].request == request) {
1522 if (!append_key(buf, &pos, &default_keybindings[i]))
1523 return "Too many keybindings!";
1524 if (!all)
1525 return buf;
1526 }
1527 }
1529 return buf;
1530 }
1532 struct run_request {
1533 enum keymap keymap;
1534 int key;
1535 const char *argv[SIZEOF_ARG];
1536 };
1538 static struct run_request *run_request;
1539 static size_t run_requests;
1541 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1543 static enum request
1544 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1545 {
1546 struct run_request *req;
1548 if (argc >= ARRAY_SIZE(req->argv) - 1)
1549 return REQ_NONE;
1551 if (!realloc_run_requests(&run_request, run_requests, 1))
1552 return REQ_NONE;
1554 req = &run_request[run_requests];
1555 req->keymap = keymap;
1556 req->key = key;
1557 req->argv[0] = NULL;
1559 if (!format_argv(req->argv, argv, FORMAT_NONE))
1560 return REQ_NONE;
1562 return REQ_NONE + ++run_requests;
1563 }
1565 static struct run_request *
1566 get_run_request(enum request request)
1567 {
1568 if (request <= REQ_NONE)
1569 return NULL;
1570 return &run_request[request - REQ_NONE - 1];
1571 }
1573 static void
1574 add_builtin_run_requests(void)
1575 {
1576 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1577 const char *commit[] = { "git", "commit", NULL };
1578 const char *gc[] = { "git", "gc", NULL };
1579 struct {
1580 enum keymap keymap;
1581 int key;
1582 int argc;
1583 const char **argv;
1584 } reqs[] = {
1585 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1586 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1587 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1588 };
1589 int i;
1591 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1592 enum request req;
1594 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1595 if (req != REQ_NONE)
1596 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1597 }
1598 }
1600 /*
1601 * User config file handling.
1602 */
1604 static int config_lineno;
1605 static bool config_errors;
1606 static const char *config_msg;
1608 static const struct enum_map color_map[] = {
1609 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1610 COLOR_MAP(DEFAULT),
1611 COLOR_MAP(BLACK),
1612 COLOR_MAP(BLUE),
1613 COLOR_MAP(CYAN),
1614 COLOR_MAP(GREEN),
1615 COLOR_MAP(MAGENTA),
1616 COLOR_MAP(RED),
1617 COLOR_MAP(WHITE),
1618 COLOR_MAP(YELLOW),
1619 };
1621 static const struct enum_map attr_map[] = {
1622 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1623 ATTR_MAP(NORMAL),
1624 ATTR_MAP(BLINK),
1625 ATTR_MAP(BOLD),
1626 ATTR_MAP(DIM),
1627 ATTR_MAP(REVERSE),
1628 ATTR_MAP(STANDOUT),
1629 ATTR_MAP(UNDERLINE),
1630 };
1632 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1634 static int parse_step(double *opt, const char *arg)
1635 {
1636 *opt = atoi(arg);
1637 if (!strchr(arg, '%'))
1638 return OK;
1640 /* "Shift down" so 100% and 1 does not conflict. */
1641 *opt = (*opt - 1) / 100;
1642 if (*opt >= 1.0) {
1643 *opt = 0.99;
1644 config_msg = "Step value larger than 100%";
1645 return ERR;
1646 }
1647 if (*opt < 0.0) {
1648 *opt = 1;
1649 config_msg = "Invalid step value";
1650 return ERR;
1651 }
1652 return OK;
1653 }
1655 static int
1656 parse_int(int *opt, const char *arg, int min, int max)
1657 {
1658 int value = atoi(arg);
1660 if (min <= value && value <= max) {
1661 *opt = value;
1662 return OK;
1663 }
1665 config_msg = "Integer value out of bound";
1666 return ERR;
1667 }
1669 static bool
1670 set_color(int *color, const char *name)
1671 {
1672 if (map_enum(color, color_map, name))
1673 return TRUE;
1674 if (!prefixcmp(name, "color"))
1675 return parse_int(color, name + 5, 0, 255) == OK;
1676 return FALSE;
1677 }
1679 /* Wants: object fgcolor bgcolor [attribute] */
1680 static int
1681 option_color_command(int argc, const char *argv[])
1682 {
1683 struct line_info *info;
1685 if (argc < 3) {
1686 config_msg = "Wrong number of arguments given to color command";
1687 return ERR;
1688 }
1690 info = get_line_info(argv[0]);
1691 if (!info) {
1692 static const struct enum_map obsolete[] = {
1693 ENUM_MAP("main-delim", LINE_DELIMITER),
1694 ENUM_MAP("main-date", LINE_DATE),
1695 ENUM_MAP("main-author", LINE_AUTHOR),
1696 };
1697 int index;
1699 if (!map_enum(&index, obsolete, argv[0])) {
1700 config_msg = "Unknown color name";
1701 return ERR;
1702 }
1703 info = &line_info[index];
1704 }
1706 if (!set_color(&info->fg, argv[1]) ||
1707 !set_color(&info->bg, argv[2])) {
1708 config_msg = "Unknown color";
1709 return ERR;
1710 }
1712 info->attr = 0;
1713 while (argc-- > 3) {
1714 int attr;
1716 if (!set_attribute(&attr, argv[argc])) {
1717 config_msg = "Unknown attribute";
1718 return ERR;
1719 }
1720 info->attr |= attr;
1721 }
1723 return OK;
1724 }
1726 static int parse_bool(bool *opt, const char *arg)
1727 {
1728 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1729 ? TRUE : FALSE;
1730 return OK;
1731 }
1733 static int parse_enum_do(unsigned int *opt, const char *arg,
1734 const struct enum_map *map, size_t map_size)
1735 {
1736 bool is_true;
1738 assert(map_size > 1);
1740 if (map_enum_do(map, map_size, (int *) opt, arg))
1741 return OK;
1743 if (parse_bool(&is_true, arg) != OK)
1744 return ERR;
1746 *opt = is_true ? map[1].value : map[0].value;
1747 return OK;
1748 }
1750 #define parse_enum(opt, arg, map) \
1751 parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1753 static int
1754 parse_string(char *opt, const char *arg, size_t optsize)
1755 {
1756 int arglen = strlen(arg);
1758 switch (arg[0]) {
1759 case '\"':
1760 case '\'':
1761 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1762 config_msg = "Unmatched quotation";
1763 return ERR;
1764 }
1765 arg += 1; arglen -= 2;
1766 default:
1767 string_ncopy_do(opt, optsize, arg, arglen);
1768 return OK;
1769 }
1770 }
1772 /* Wants: name = value */
1773 static int
1774 option_set_command(int argc, const char *argv[])
1775 {
1776 if (argc != 3) {
1777 config_msg = "Wrong number of arguments given to set command";
1778 return ERR;
1779 }
1781 if (strcmp(argv[1], "=")) {
1782 config_msg = "No value assigned";
1783 return ERR;
1784 }
1786 if (!strcmp(argv[0], "show-author"))
1787 return parse_enum(&opt_author, argv[2], author_map);
1789 if (!strcmp(argv[0], "show-date"))
1790 return parse_enum(&opt_date, argv[2], date_map);
1792 if (!strcmp(argv[0], "show-rev-graph"))
1793 return parse_bool(&opt_rev_graph, argv[2]);
1795 if (!strcmp(argv[0], "show-refs"))
1796 return parse_bool(&opt_show_refs, argv[2]);
1798 if (!strcmp(argv[0], "show-line-numbers"))
1799 return parse_bool(&opt_line_number, argv[2]);
1801 if (!strcmp(argv[0], "line-graphics"))
1802 return parse_bool(&opt_line_graphics, argv[2]);
1804 if (!strcmp(argv[0], "line-number-interval"))
1805 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1807 if (!strcmp(argv[0], "author-width"))
1808 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1810 if (!strcmp(argv[0], "horizontal-scroll"))
1811 return parse_step(&opt_hscroll, argv[2]);
1813 if (!strcmp(argv[0], "split-view-height"))
1814 return parse_step(&opt_scale_split_view, argv[2]);
1816 if (!strcmp(argv[0], "tab-size"))
1817 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1819 if (!strcmp(argv[0], "commit-encoding"))
1820 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1822 config_msg = "Unknown variable name";
1823 return ERR;
1824 }
1826 /* Wants: mode request key */
1827 static int
1828 option_bind_command(int argc, const char *argv[])
1829 {
1830 enum request request;
1831 int keymap = -1;
1832 int key;
1834 if (argc < 3) {
1835 config_msg = "Wrong number of arguments given to bind command";
1836 return ERR;
1837 }
1839 if (set_keymap(&keymap, argv[0]) == ERR) {
1840 config_msg = "Unknown key map";
1841 return ERR;
1842 }
1844 key = get_key_value(argv[1]);
1845 if (key == ERR) {
1846 config_msg = "Unknown key";
1847 return ERR;
1848 }
1850 request = get_request(argv[2]);
1851 if (request == REQ_NONE) {
1852 static const struct enum_map obsolete[] = {
1853 ENUM_MAP("cherry-pick", REQ_NONE),
1854 ENUM_MAP("screen-resize", REQ_NONE),
1855 ENUM_MAP("tree-parent", REQ_PARENT),
1856 };
1857 int alias;
1859 if (map_enum(&alias, obsolete, argv[2])) {
1860 if (alias != REQ_NONE)
1861 add_keybinding(keymap, alias, key);
1862 config_msg = "Obsolete request name";
1863 return ERR;
1864 }
1865 }
1866 if (request == REQ_NONE && *argv[2]++ == '!')
1867 request = add_run_request(keymap, key, argc - 2, argv + 2);
1868 if (request == REQ_NONE) {
1869 config_msg = "Unknown request name";
1870 return ERR;
1871 }
1873 add_keybinding(keymap, request, key);
1875 return OK;
1876 }
1878 static int
1879 set_option(const char *opt, char *value)
1880 {
1881 const char *argv[SIZEOF_ARG];
1882 int argc = 0;
1884 if (!argv_from_string(argv, &argc, value)) {
1885 config_msg = "Too many option arguments";
1886 return ERR;
1887 }
1889 if (!strcmp(opt, "color"))
1890 return option_color_command(argc, argv);
1892 if (!strcmp(opt, "set"))
1893 return option_set_command(argc, argv);
1895 if (!strcmp(opt, "bind"))
1896 return option_bind_command(argc, argv);
1898 config_msg = "Unknown option command";
1899 return ERR;
1900 }
1902 static int
1903 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1904 {
1905 int status = OK;
1907 config_lineno++;
1908 config_msg = "Internal error";
1910 /* Check for comment markers, since read_properties() will
1911 * only ensure opt and value are split at first " \t". */
1912 optlen = strcspn(opt, "#");
1913 if (optlen == 0)
1914 return OK;
1916 if (opt[optlen] != 0) {
1917 config_msg = "No option value";
1918 status = ERR;
1920 } else {
1921 /* Look for comment endings in the value. */
1922 size_t len = strcspn(value, "#");
1924 if (len < valuelen) {
1925 valuelen = len;
1926 value[valuelen] = 0;
1927 }
1929 status = set_option(opt, value);
1930 }
1932 if (status == ERR) {
1933 warn("Error on line %d, near '%.*s': %s",
1934 config_lineno, (int) optlen, opt, config_msg);
1935 config_errors = TRUE;
1936 }
1938 /* Always keep going if errors are encountered. */
1939 return OK;
1940 }
1942 static void
1943 load_option_file(const char *path)
1944 {
1945 struct io io = {};
1947 /* It's OK that the file doesn't exist. */
1948 if (!io_open(&io, "%s", path))
1949 return;
1951 config_lineno = 0;
1952 config_errors = FALSE;
1954 if (io_load(&io, " \t", read_option) == ERR ||
1955 config_errors == TRUE)
1956 warn("Errors while loading %s.", path);
1957 }
1959 static int
1960 load_options(void)
1961 {
1962 const char *home = getenv("HOME");
1963 const char *tigrc_user = getenv("TIGRC_USER");
1964 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1965 char buf[SIZEOF_STR];
1967 add_builtin_run_requests();
1969 if (!tigrc_system)
1970 tigrc_system = SYSCONFDIR "/tigrc";
1971 load_option_file(tigrc_system);
1973 if (!tigrc_user) {
1974 if (!home || !string_format(buf, "%s/.tigrc", home))
1975 return ERR;
1976 tigrc_user = buf;
1977 }
1978 load_option_file(tigrc_user);
1980 return OK;
1981 }
1984 /*
1985 * The viewer
1986 */
1988 struct view;
1989 struct view_ops;
1991 /* The display array of active views and the index of the current view. */
1992 static struct view *display[2];
1993 static unsigned int current_view;
1995 #define foreach_displayed_view(view, i) \
1996 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1998 #define displayed_views() (display[1] != NULL ? 2 : 1)
2000 /* Current head and commit ID */
2001 static char ref_blob[SIZEOF_REF] = "";
2002 static char ref_commit[SIZEOF_REF] = "HEAD";
2003 static char ref_head[SIZEOF_REF] = "HEAD";
2005 struct view {
2006 const char *name; /* View name */
2007 const char *cmd_env; /* Command line set via environment */
2008 const char *id; /* Points to either of ref_{head,commit,blob} */
2010 struct view_ops *ops; /* View operations */
2012 enum keymap keymap; /* What keymap does this view have */
2013 bool git_dir; /* Whether the view requires a git directory. */
2015 char ref[SIZEOF_REF]; /* Hovered commit reference */
2016 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
2018 int height, width; /* The width and height of the main window */
2019 WINDOW *win; /* The main window */
2020 WINDOW *title; /* The title window living below the main window */
2022 /* Navigation */
2023 unsigned long offset; /* Offset of the window top */
2024 unsigned long yoffset; /* Offset from the window side. */
2025 unsigned long lineno; /* Current line number */
2026 unsigned long p_offset; /* Previous offset of the window top */
2027 unsigned long p_yoffset;/* Previous offset from the window side */
2028 unsigned long p_lineno; /* Previous current line number */
2029 bool p_restore; /* Should the previous position be restored. */
2031 /* Searching */
2032 char grep[SIZEOF_STR]; /* Search string */
2033 regex_t *regex; /* Pre-compiled regexp */
2035 /* If non-NULL, points to the view that opened this view. If this view
2036 * is closed tig will switch back to the parent view. */
2037 struct view *parent;
2039 /* Buffering */
2040 size_t lines; /* Total number of lines */
2041 struct line *line; /* Line index */
2042 unsigned int digits; /* Number of digits in the lines member. */
2044 /* Drawing */
2045 struct line *curline; /* Line currently being drawn. */
2046 enum line_type curtype; /* Attribute currently used for drawing. */
2047 unsigned long col; /* Column when drawing. */
2048 bool has_scrolled; /* View was scrolled. */
2050 /* Loading */
2051 struct io io;
2052 struct io *pipe;
2053 time_t start_time;
2054 time_t update_secs;
2055 };
2057 struct view_ops {
2058 /* What type of content being displayed. Used in the title bar. */
2059 const char *type;
2060 /* Default command arguments. */
2061 const char **argv;
2062 /* Open and reads in all view content. */
2063 bool (*open)(struct view *view);
2064 /* Read one line; updates view->line. */
2065 bool (*read)(struct view *view, char *data);
2066 /* Draw one line; @lineno must be < view->height. */
2067 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2068 /* Depending on view handle a special requests. */
2069 enum request (*request)(struct view *view, enum request request, struct line *line);
2070 /* Search for regexp in a line. */
2071 bool (*grep)(struct view *view, struct line *line);
2072 /* Select line */
2073 void (*select)(struct view *view, struct line *line);
2074 /* Prepare view for loading */
2075 bool (*prepare)(struct view *view);
2076 };
2078 static struct view_ops blame_ops;
2079 static struct view_ops blob_ops;
2080 static struct view_ops diff_ops;
2081 static struct view_ops help_ops;
2082 static struct view_ops log_ops;
2083 static struct view_ops main_ops;
2084 static struct view_ops pager_ops;
2085 static struct view_ops stage_ops;
2086 static struct view_ops status_ops;
2087 static struct view_ops tree_ops;
2088 static struct view_ops branch_ops;
2090 #define VIEW_STR(name, env, ref, ops, map, git) \
2091 { name, #env, ref, ops, map, git }
2093 #define VIEW_(id, name, ops, git, ref) \
2094 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2097 static struct view views[] = {
2098 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
2099 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
2100 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
2101 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
2102 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
2103 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
2104 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
2105 VIEW_(HELP, "help", &help_ops, FALSE, ""),
2106 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
2107 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
2108 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
2109 };
2111 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
2112 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
2114 #define foreach_view(view, i) \
2115 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2117 #define view_is_displayed(view) \
2118 (view == display[0] || view == display[1])
2121 static inline void
2122 set_view_attr(struct view *view, enum line_type type)
2123 {
2124 if (!view->curline->selected && view->curtype != type) {
2125 (void) wattrset(view->win, get_line_attr(type));
2126 wchgat(view->win, -1, 0, type, NULL);
2127 view->curtype = type;
2128 }
2129 }
2131 static int
2132 draw_chars(struct view *view, enum line_type type, const char *string,
2133 int max_len, bool use_tilde)
2134 {
2135 static char out_buffer[BUFSIZ * 2];
2136 int len = 0;
2137 int col = 0;
2138 int trimmed = FALSE;
2139 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2141 if (max_len <= 0)
2142 return 0;
2144 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2146 set_view_attr(view, type);
2147 if (len > 0) {
2148 if (opt_iconv_out != ICONV_NONE) {
2149 ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2150 size_t inlen = len + 1;
2152 char *outbuf = out_buffer;
2153 size_t outlen = sizeof(out_buffer);
2155 size_t ret;
2157 ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2158 if (ret != (size_t) -1) {
2159 string = out_buffer;
2160 len = sizeof(out_buffer) - outlen;
2161 }
2162 }
2164 waddnstr(view->win, string, len);
2165 }
2166 if (trimmed && use_tilde) {
2167 set_view_attr(view, LINE_DELIMITER);
2168 waddch(view->win, '~');
2169 col++;
2170 }
2172 return col;
2173 }
2175 static int
2176 draw_space(struct view *view, enum line_type type, int max, int spaces)
2177 {
2178 static char space[] = " ";
2179 int col = 0;
2181 spaces = MIN(max, spaces);
2183 while (spaces > 0) {
2184 int len = MIN(spaces, sizeof(space) - 1);
2186 col += draw_chars(view, type, space, len, FALSE);
2187 spaces -= len;
2188 }
2190 return col;
2191 }
2193 static bool
2194 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2195 {
2196 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2197 return view->width + view->yoffset <= view->col;
2198 }
2200 static bool
2201 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2202 {
2203 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2204 int max = view->width + view->yoffset - view->col;
2205 int i;
2207 if (max < size)
2208 size = max;
2210 set_view_attr(view, type);
2211 /* Using waddch() instead of waddnstr() ensures that
2212 * they'll be rendered correctly for the cursor line. */
2213 for (i = skip; i < size; i++)
2214 waddch(view->win, graphic[i]);
2216 view->col += size;
2217 if (size < max && skip <= size)
2218 waddch(view->win, ' ');
2219 view->col++;
2221 return view->width + view->yoffset <= view->col;
2222 }
2224 static bool
2225 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2226 {
2227 int max = MIN(view->width + view->yoffset - view->col, len);
2228 int col;
2230 if (text)
2231 col = draw_chars(view, type, text, max - 1, trim);
2232 else
2233 col = draw_space(view, type, max - 1, max - 1);
2235 view->col += col;
2236 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2237 return view->width + view->yoffset <= view->col;
2238 }
2240 static bool
2241 draw_date(struct view *view, struct time *time)
2242 {
2243 const char *date = mkdate(time, opt_date);
2244 int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2246 return draw_field(view, LINE_DATE, date, cols, FALSE);
2247 }
2249 static bool
2250 draw_author(struct view *view, const char *author)
2251 {
2252 bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2253 bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2255 if (abbreviate && author)
2256 author = get_author_initials(author);
2258 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2259 }
2261 static bool
2262 draw_mode(struct view *view, mode_t mode)
2263 {
2264 const char *str;
2266 if (S_ISDIR(mode))
2267 str = "drwxr-xr-x";
2268 else if (S_ISLNK(mode))
2269 str = "lrwxrwxrwx";
2270 else if (S_ISGITLINK(mode))
2271 str = "m---------";
2272 else if (S_ISREG(mode) && mode & S_IXUSR)
2273 str = "-rwxr-xr-x";
2274 else if (S_ISREG(mode))
2275 str = "-rw-r--r--";
2276 else
2277 str = "----------";
2279 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2280 }
2282 static bool
2283 draw_lineno(struct view *view, unsigned int lineno)
2284 {
2285 char number[10];
2286 int digits3 = view->digits < 3 ? 3 : view->digits;
2287 int max = MIN(view->width + view->yoffset - view->col, digits3);
2288 char *text = NULL;
2289 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2291 lineno += view->offset + 1;
2292 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2293 static char fmt[] = "%1ld";
2295 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2296 if (string_format(number, fmt, lineno))
2297 text = number;
2298 }
2299 if (text)
2300 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2301 else
2302 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2303 return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2304 }
2306 static bool
2307 draw_view_line(struct view *view, unsigned int lineno)
2308 {
2309 struct line *line;
2310 bool selected = (view->offset + lineno == view->lineno);
2312 assert(view_is_displayed(view));
2314 if (view->offset + lineno >= view->lines)
2315 return FALSE;
2317 line = &view->line[view->offset + lineno];
2319 wmove(view->win, lineno, 0);
2320 if (line->cleareol)
2321 wclrtoeol(view->win);
2322 view->col = 0;
2323 view->curline = line;
2324 view->curtype = LINE_NONE;
2325 line->selected = FALSE;
2326 line->dirty = line->cleareol = 0;
2328 if (selected) {
2329 set_view_attr(view, LINE_CURSOR);
2330 line->selected = TRUE;
2331 view->ops->select(view, line);
2332 }
2334 return view->ops->draw(view, line, lineno);
2335 }
2337 static void
2338 redraw_view_dirty(struct view *view)
2339 {
2340 bool dirty = FALSE;
2341 int lineno;
2343 for (lineno = 0; lineno < view->height; lineno++) {
2344 if (view->offset + lineno >= view->lines)
2345 break;
2346 if (!view->line[view->offset + lineno].dirty)
2347 continue;
2348 dirty = TRUE;
2349 if (!draw_view_line(view, lineno))
2350 break;
2351 }
2353 if (!dirty)
2354 return;
2355 wnoutrefresh(view->win);
2356 }
2358 static void
2359 redraw_view_from(struct view *view, int lineno)
2360 {
2361 assert(0 <= lineno && lineno < view->height);
2363 for (; lineno < view->height; lineno++) {
2364 if (!draw_view_line(view, lineno))
2365 break;
2366 }
2368 wnoutrefresh(view->win);
2369 }
2371 static void
2372 redraw_view(struct view *view)
2373 {
2374 werase(view->win);
2375 redraw_view_from(view, 0);
2376 }
2379 static void
2380 update_view_title(struct view *view)
2381 {
2382 char buf[SIZEOF_STR];
2383 char state[SIZEOF_STR];
2384 size_t bufpos = 0, statelen = 0;
2386 assert(view_is_displayed(view));
2388 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2389 unsigned int view_lines = view->offset + view->height;
2390 unsigned int lines = view->lines
2391 ? MIN(view_lines, view->lines) * 100 / view->lines
2392 : 0;
2394 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2395 view->ops->type,
2396 view->lineno + 1,
2397 view->lines,
2398 lines);
2400 }
2402 if (view->pipe) {
2403 time_t secs = time(NULL) - view->start_time;
2405 /* Three git seconds are a long time ... */
2406 if (secs > 2)
2407 string_format_from(state, &statelen, " loading %lds", secs);
2408 }
2410 string_format_from(buf, &bufpos, "[%s]", view->name);
2411 if (*view->ref && bufpos < view->width) {
2412 size_t refsize = strlen(view->ref);
2413 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2415 if (minsize < view->width)
2416 refsize = view->width - minsize + 7;
2417 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2418 }
2420 if (statelen && bufpos < view->width) {
2421 string_format_from(buf, &bufpos, "%s", state);
2422 }
2424 if (view == display[current_view])
2425 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2426 else
2427 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2429 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2430 wclrtoeol(view->title);
2431 wnoutrefresh(view->title);
2432 }
2434 static int
2435 apply_step(double step, int value)
2436 {
2437 if (step >= 1)
2438 return (int) step;
2439 value *= step + 0.01;
2440 return value ? value : 1;
2441 }
2443 static void
2444 resize_display(void)
2445 {
2446 int offset, i;
2447 struct view *base = display[0];
2448 struct view *view = display[1] ? display[1] : display[0];
2450 /* Setup window dimensions */
2452 getmaxyx(stdscr, base->height, base->width);
2454 /* Make room for the status window. */
2455 base->height -= 1;
2457 if (view != base) {
2458 /* Horizontal split. */
2459 view->width = base->width;
2460 view->height = apply_step(opt_scale_split_view, base->height);
2461 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
2462 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2463 base->height -= view->height;
2465 /* Make room for the title bar. */
2466 view->height -= 1;
2467 }
2469 /* Make room for the title bar. */
2470 base->height -= 1;
2472 offset = 0;
2474 foreach_displayed_view (view, i) {
2475 if (!view->win) {
2476 view->win = newwin(view->height, 0, offset, 0);
2477 if (!view->win)
2478 die("Failed to create %s view", view->name);
2480 scrollok(view->win, FALSE);
2482 view->title = newwin(1, 0, offset + view->height, 0);
2483 if (!view->title)
2484 die("Failed to create title window");
2486 } else {
2487 wresize(view->win, view->height, view->width);
2488 mvwin(view->win, offset, 0);
2489 mvwin(view->title, offset + view->height, 0);
2490 }
2492 offset += view->height + 1;
2493 }
2494 }
2496 static void
2497 redraw_display(bool clear)
2498 {
2499 struct view *view;
2500 int i;
2502 foreach_displayed_view (view, i) {
2503 if (clear)
2504 wclear(view->win);
2505 redraw_view(view);
2506 update_view_title(view);
2507 }
2508 }
2510 static void
2511 toggle_enum_option_do(unsigned int *opt, const char *help,
2512 const struct enum_map *map, size_t size)
2513 {
2514 *opt = (*opt + 1) % size;
2515 redraw_display(FALSE);
2516 report("Displaying %s %s", enum_name(map[*opt]), help);
2517 }
2519 #define toggle_enum_option(opt, help, map) \
2520 toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2522 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2523 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2525 static void
2526 toggle_view_option(bool *option, const char *help)
2527 {
2528 *option = !*option;
2529 redraw_display(FALSE);
2530 report("%sabling %s", *option ? "En" : "Dis", help);
2531 }
2533 static void
2534 open_option_menu(void)
2535 {
2536 const struct menu_item menu[] = {
2537 { '.', "line numbers", &opt_line_number },
2538 { 'D', "date display", &opt_date },
2539 { 'A', "author display", &opt_author },
2540 { 'g', "revision graph display", &opt_rev_graph },
2541 { 'F', "reference display", &opt_show_refs },
2542 { 0 }
2543 };
2544 int selected = 0;
2546 if (prompt_menu("Toggle option", menu, &selected)) {
2547 if (menu[selected].data == &opt_date)
2548 toggle_date();
2549 else if (menu[selected].data == &opt_author)
2550 toggle_author();
2551 else
2552 toggle_view_option(menu[selected].data, menu[selected].text);
2553 }
2554 }
2556 static void
2557 maximize_view(struct view *view)
2558 {
2559 memset(display, 0, sizeof(display));
2560 current_view = 0;
2561 display[current_view] = view;
2562 resize_display();
2563 redraw_display(FALSE);
2564 report("");
2565 }
2568 /*
2569 * Navigation
2570 */
2572 static bool
2573 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2574 {
2575 if (lineno >= view->lines)
2576 lineno = view->lines > 0 ? view->lines - 1 : 0;
2578 if (offset > lineno || offset + view->height <= lineno) {
2579 unsigned long half = view->height / 2;
2581 if (lineno > half)
2582 offset = lineno - half;
2583 else
2584 offset = 0;
2585 }
2587 if (offset != view->offset || lineno != view->lineno) {
2588 view->offset = offset;
2589 view->lineno = lineno;
2590 return TRUE;
2591 }
2593 return FALSE;
2594 }
2596 /* Scrolling backend */
2597 static void
2598 do_scroll_view(struct view *view, int lines)
2599 {
2600 bool redraw_current_line = FALSE;
2602 /* The rendering expects the new offset. */
2603 view->offset += lines;
2605 assert(0 <= view->offset && view->offset < view->lines);
2606 assert(lines);
2608 /* Move current line into the view. */
2609 if (view->lineno < view->offset) {
2610 view->lineno = view->offset;
2611 redraw_current_line = TRUE;
2612 } else if (view->lineno >= view->offset + view->height) {
2613 view->lineno = view->offset + view->height - 1;
2614 redraw_current_line = TRUE;
2615 }
2617 assert(view->offset <= view->lineno && view->lineno < view->lines);
2619 /* Redraw the whole screen if scrolling is pointless. */
2620 if (view->height < ABS(lines)) {
2621 redraw_view(view);
2623 } else {
2624 int line = lines > 0 ? view->height - lines : 0;
2625 int end = line + ABS(lines);
2627 scrollok(view->win, TRUE);
2628 wscrl(view->win, lines);
2629 scrollok(view->win, FALSE);
2631 while (line < end && draw_view_line(view, line))
2632 line++;
2634 if (redraw_current_line)
2635 draw_view_line(view, view->lineno - view->offset);
2636 wnoutrefresh(view->win);
2637 }
2639 view->has_scrolled = TRUE;
2640 report("");
2641 }
2643 /* Scroll frontend */
2644 static void
2645 scroll_view(struct view *view, enum request request)
2646 {
2647 int lines = 1;
2649 assert(view_is_displayed(view));
2651 switch (request) {
2652 case REQ_SCROLL_LEFT:
2653 if (view->yoffset == 0) {
2654 report("Cannot scroll beyond the first column");
2655 return;
2656 }
2657 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2658 view->yoffset = 0;
2659 else
2660 view->yoffset -= apply_step(opt_hscroll, view->width);
2661 redraw_view_from(view, 0);
2662 report("");
2663 return;
2664 case REQ_SCROLL_RIGHT:
2665 view->yoffset += apply_step(opt_hscroll, view->width);
2666 redraw_view(view);
2667 report("");
2668 return;
2669 case REQ_SCROLL_PAGE_DOWN:
2670 lines = view->height;
2671 case REQ_SCROLL_LINE_DOWN:
2672 if (view->offset + lines > view->lines)
2673 lines = view->lines - view->offset;
2675 if (lines == 0 || view->offset + view->height >= view->lines) {
2676 report("Cannot scroll beyond the last line");
2677 return;
2678 }
2679 break;
2681 case REQ_SCROLL_PAGE_UP:
2682 lines = view->height;
2683 case REQ_SCROLL_LINE_UP:
2684 if (lines > view->offset)
2685 lines = view->offset;
2687 if (lines == 0) {
2688 report("Cannot scroll beyond the first line");
2689 return;
2690 }
2692 lines = -lines;
2693 break;
2695 default:
2696 die("request %d not handled in switch", request);
2697 }
2699 do_scroll_view(view, lines);
2700 }
2702 /* Cursor moving */
2703 static void
2704 move_view(struct view *view, enum request request)
2705 {
2706 int scroll_steps = 0;
2707 int steps;
2709 switch (request) {
2710 case REQ_MOVE_FIRST_LINE:
2711 steps = -view->lineno;
2712 break;
2714 case REQ_MOVE_LAST_LINE:
2715 steps = view->lines - view->lineno - 1;
2716 break;
2718 case REQ_MOVE_PAGE_UP:
2719 steps = view->height > view->lineno
2720 ? -view->lineno : -view->height;
2721 break;
2723 case REQ_MOVE_PAGE_DOWN:
2724 steps = view->lineno + view->height >= view->lines
2725 ? view->lines - view->lineno - 1 : view->height;
2726 break;
2728 case REQ_MOVE_UP:
2729 steps = -1;
2730 break;
2732 case REQ_MOVE_DOWN:
2733 steps = 1;
2734 break;
2736 default:
2737 die("request %d not handled in switch", request);
2738 }
2740 if (steps <= 0 && view->lineno == 0) {
2741 report("Cannot move beyond the first line");
2742 return;
2744 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2745 report("Cannot move beyond the last line");
2746 return;
2747 }
2749 /* Move the current line */
2750 view->lineno += steps;
2751 assert(0 <= view->lineno && view->lineno < view->lines);
2753 /* Check whether the view needs to be scrolled */
2754 if (view->lineno < view->offset ||
2755 view->lineno >= view->offset + view->height) {
2756 scroll_steps = steps;
2757 if (steps < 0 && -steps > view->offset) {
2758 scroll_steps = -view->offset;
2760 } else if (steps > 0) {
2761 if (view->lineno == view->lines - 1 &&
2762 view->lines > view->height) {
2763 scroll_steps = view->lines - view->offset - 1;
2764 if (scroll_steps >= view->height)
2765 scroll_steps -= view->height - 1;
2766 }
2767 }
2768 }
2770 if (!view_is_displayed(view)) {
2771 view->offset += scroll_steps;
2772 assert(0 <= view->offset && view->offset < view->lines);
2773 view->ops->select(view, &view->line[view->lineno]);
2774 return;
2775 }
2777 /* Repaint the old "current" line if we be scrolling */
2778 if (ABS(steps) < view->height)
2779 draw_view_line(view, view->lineno - steps - view->offset);
2781 if (scroll_steps) {
2782 do_scroll_view(view, scroll_steps);
2783 return;
2784 }
2786 /* Draw the current line */
2787 draw_view_line(view, view->lineno - view->offset);
2789 wnoutrefresh(view->win);
2790 report("");
2791 }
2794 /*
2795 * Searching
2796 */
2798 static void search_view(struct view *view, enum request request);
2800 static bool
2801 grep_text(struct view *view, const char *text[])
2802 {
2803 regmatch_t pmatch;
2804 size_t i;
2806 for (i = 0; text[i]; i++)
2807 if (*text[i] &&
2808 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2809 return TRUE;
2810 return FALSE;
2811 }
2813 static void
2814 select_view_line(struct view *view, unsigned long lineno)
2815 {
2816 unsigned long old_lineno = view->lineno;
2817 unsigned long old_offset = view->offset;
2819 if (goto_view_line(view, view->offset, lineno)) {
2820 if (view_is_displayed(view)) {
2821 if (old_offset != view->offset) {
2822 redraw_view(view);
2823 } else {
2824 draw_view_line(view, old_lineno - view->offset);
2825 draw_view_line(view, view->lineno - view->offset);
2826 wnoutrefresh(view->win);
2827 }
2828 } else {
2829 view->ops->select(view, &view->line[view->lineno]);
2830 }
2831 }
2832 }
2834 static void
2835 find_next(struct view *view, enum request request)
2836 {
2837 unsigned long lineno = view->lineno;
2838 int direction;
2840 if (!*view->grep) {
2841 if (!*opt_search)
2842 report("No previous search");
2843 else
2844 search_view(view, request);
2845 return;
2846 }
2848 switch (request) {
2849 case REQ_SEARCH:
2850 case REQ_FIND_NEXT:
2851 direction = 1;
2852 break;
2854 case REQ_SEARCH_BACK:
2855 case REQ_FIND_PREV:
2856 direction = -1;
2857 break;
2859 default:
2860 return;
2861 }
2863 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2864 lineno += direction;
2866 /* Note, lineno is unsigned long so will wrap around in which case it
2867 * will become bigger than view->lines. */
2868 for (; lineno < view->lines; lineno += direction) {
2869 if (view->ops->grep(view, &view->line[lineno])) {
2870 select_view_line(view, lineno);
2871 report("Line %ld matches '%s'", lineno + 1, view->grep);
2872 return;
2873 }
2874 }
2876 report("No match found for '%s'", view->grep);
2877 }
2879 static void
2880 search_view(struct view *view, enum request request)
2881 {
2882 int regex_err;
2884 if (view->regex) {
2885 regfree(view->regex);
2886 *view->grep = 0;
2887 } else {
2888 view->regex = calloc(1, sizeof(*view->regex));
2889 if (!view->regex)
2890 return;
2891 }
2893 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2894 if (regex_err != 0) {
2895 char buf[SIZEOF_STR] = "unknown error";
2897 regerror(regex_err, view->regex, buf, sizeof(buf));
2898 report("Search failed: %s", buf);
2899 return;
2900 }
2902 string_copy(view->grep, opt_search);
2904 find_next(view, request);
2905 }
2907 /*
2908 * Incremental updating
2909 */
2911 static void
2912 reset_view(struct view *view)
2913 {
2914 int i;
2916 for (i = 0; i < view->lines; i++)
2917 free(view->line[i].data);
2918 free(view->line);
2920 view->p_offset = view->offset;
2921 view->p_yoffset = view->yoffset;
2922 view->p_lineno = view->lineno;
2924 view->line = NULL;
2925 view->offset = 0;
2926 view->yoffset = 0;
2927 view->lines = 0;
2928 view->lineno = 0;
2929 view->vid[0] = 0;
2930 view->update_secs = 0;
2931 }
2933 static void
2934 free_argv(const char *argv[])
2935 {
2936 int argc;
2938 for (argc = 0; argv[argc]; argc++)
2939 free((void *) argv[argc]);
2940 }
2942 static const char *
2943 format_arg(const char *name)
2944 {
2945 static struct {
2946 const char *name;
2947 size_t namelen;
2948 const char *value;
2949 const char *value_if_empty;
2950 } vars[] = {
2951 #define FORMAT_VAR(name, value, value_if_empty) \
2952 { name, STRING_SIZE(name), value, value_if_empty }
2953 FORMAT_VAR("%(directory)", opt_path, ""),
2954 FORMAT_VAR("%(file)", opt_file, ""),
2955 FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
2956 FORMAT_VAR("%(head)", ref_head, ""),
2957 FORMAT_VAR("%(commit)", ref_commit, ""),
2958 FORMAT_VAR("%(blob)", ref_blob, ""),
2959 };
2960 int i;
2962 for (i = 0; i < ARRAY_SIZE(vars); i++)
2963 if (!strncmp(name, vars[i].name, vars[i].namelen))
2964 return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
2966 return NULL;
2967 }
2968 static bool
2969 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2970 {
2971 char buf[SIZEOF_STR];
2972 int argc;
2973 bool noreplace = flags == FORMAT_NONE;
2975 free_argv(dst_argv);
2977 for (argc = 0; src_argv[argc]; argc++) {
2978 const char *arg = src_argv[argc];
2979 size_t bufpos = 0;
2981 while (arg) {
2982 char *next = strstr(arg, "%(");
2983 int len = next - arg;
2984 const char *value;
2986 if (!next || noreplace) {
2987 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2988 noreplace = TRUE;
2989 len = strlen(arg);
2990 value = "";
2992 } else {
2993 value = format_arg(next);
2995 if (!value) {
2996 report("Unknown replacement: `%s`", next);
2997 return FALSE;
2998 }
2999 }
3001 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3002 return FALSE;
3004 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3005 }
3007 dst_argv[argc] = strdup(buf);
3008 if (!dst_argv[argc])
3009 break;
3010 }
3012 dst_argv[argc] = NULL;
3014 return src_argv[argc] == NULL;
3015 }
3017 static bool
3018 restore_view_position(struct view *view)
3019 {
3020 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3021 return FALSE;
3023 /* Changing the view position cancels the restoring. */
3024 /* FIXME: Changing back to the first line is not detected. */
3025 if (view->offset != 0 || view->lineno != 0) {
3026 view->p_restore = FALSE;
3027 return FALSE;
3028 }
3030 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3031 view_is_displayed(view))
3032 werase(view->win);
3034 view->yoffset = view->p_yoffset;
3035 view->p_restore = FALSE;
3037 return TRUE;
3038 }
3040 static void
3041 end_update(struct view *view, bool force)
3042 {
3043 if (!view->pipe)
3044 return;
3045 while (!view->ops->read(view, NULL))
3046 if (!force)
3047 return;
3048 if (force)
3049 io_kill(view->pipe);
3050 io_done(view->pipe);
3051 view->pipe = NULL;
3052 }
3054 static void
3055 setup_update(struct view *view, const char *vid)
3056 {
3057 reset_view(view);
3058 string_copy_rev(view->vid, vid);
3059 view->pipe = &view->io;
3060 view->start_time = time(NULL);
3061 }
3063 static bool
3064 prepare_update(struct view *view, const char *argv[], const char *dir,
3065 enum format_flags flags)
3066 {
3067 if (view->pipe)
3068 end_update(view, TRUE);
3069 return io_init_rd(&view->io, argv, dir, flags);
3070 }
3072 static bool
3073 prepare_update_file(struct view *view, const char *name)
3074 {
3075 if (view->pipe)
3076 end_update(view, TRUE);
3077 return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3078 }
3080 static bool
3081 begin_update(struct view *view, bool refresh)
3082 {
3083 if (view->pipe)
3084 end_update(view, TRUE);
3086 if (!refresh) {
3087 if (view->ops->prepare) {
3088 if (!view->ops->prepare(view))
3089 return FALSE;
3090 } else if (!io_init_rd(&view->io, view->ops->argv, NULL, FORMAT_ALL)) {
3091 return FALSE;
3092 }
3094 /* Put the current ref_* value to the view title ref
3095 * member. This is needed by the blob view. Most other
3096 * views sets it automatically after loading because the
3097 * first line is a commit line. */
3098 string_copy_rev(view->ref, view->id);
3099 }
3101 if (!io_start(&view->io))
3102 return FALSE;
3104 setup_update(view, view->id);
3106 return TRUE;
3107 }
3109 static bool
3110 update_view(struct view *view)
3111 {
3112 char out_buffer[BUFSIZ * 2];
3113 char *line;
3114 /* Clear the view and redraw everything since the tree sorting
3115 * might have rearranged things. */
3116 bool redraw = view->lines == 0;
3117 bool can_read = TRUE;
3119 if (!view->pipe)
3120 return TRUE;
3122 if (!io_can_read(view->pipe)) {
3123 if (view->lines == 0 && view_is_displayed(view)) {
3124 time_t secs = time(NULL) - view->start_time;
3126 if (secs > 1 && secs > view->update_secs) {
3127 if (view->update_secs == 0)
3128 redraw_view(view);
3129 update_view_title(view);
3130 view->update_secs = secs;
3131 }
3132 }
3133 return TRUE;
3134 }
3136 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3137 if (opt_iconv_in != ICONV_NONE) {
3138 ICONV_CONST char *inbuf = line;
3139 size_t inlen = strlen(line) + 1;
3141 char *outbuf = out_buffer;
3142 size_t outlen = sizeof(out_buffer);
3144 size_t ret;
3146 ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3147 if (ret != (size_t) -1)
3148 line = out_buffer;
3149 }
3151 if (!view->ops->read(view, line)) {
3152 report("Allocation failure");
3153 end_update(view, TRUE);
3154 return FALSE;
3155 }
3156 }
3158 {
3159 unsigned long lines = view->lines;
3160 int digits;
3162 for (digits = 0; lines; digits++)
3163 lines /= 10;
3165 /* Keep the displayed view in sync with line number scaling. */
3166 if (digits != view->digits) {
3167 view->digits = digits;
3168 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3169 redraw = TRUE;
3170 }
3171 }
3173 if (io_error(view->pipe)) {
3174 report("Failed to read: %s", io_strerror(view->pipe));
3175 end_update(view, TRUE);
3177 } else if (io_eof(view->pipe)) {
3178 report("");
3179 end_update(view, FALSE);
3180 }
3182 if (restore_view_position(view))
3183 redraw = TRUE;
3185 if (!view_is_displayed(view))
3186 return TRUE;
3188 if (redraw)
3189 redraw_view_from(view, 0);
3190 else
3191 redraw_view_dirty(view);
3193 /* Update the title _after_ the redraw so that if the redraw picks up a
3194 * commit reference in view->ref it'll be available here. */
3195 update_view_title(view);
3196 return TRUE;
3197 }
3199 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3201 static struct line *
3202 add_line_data(struct view *view, void *data, enum line_type type)
3203 {
3204 struct line *line;
3206 if (!realloc_lines(&view->line, view->lines, 1))
3207 return NULL;
3209 line = &view->line[view->lines++];
3210 memset(line, 0, sizeof(*line));
3211 line->type = type;
3212 line->data = data;
3213 line->dirty = 1;
3215 return line;
3216 }
3218 static struct line *
3219 add_line_text(struct view *view, const char *text, enum line_type type)
3220 {
3221 char *data = text ? strdup(text) : NULL;
3223 return data ? add_line_data(view, data, type) : NULL;
3224 }
3226 static struct line *
3227 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3228 {
3229 char buf[SIZEOF_STR];
3230 va_list args;
3232 va_start(args, fmt);
3233 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3234 buf[0] = 0;
3235 va_end(args);
3237 return buf[0] ? add_line_text(view, buf, type) : NULL;
3238 }
3240 /*
3241 * View opening
3242 */
3244 enum open_flags {
3245 OPEN_DEFAULT = 0, /* Use default view switching. */
3246 OPEN_SPLIT = 1, /* Split current view. */
3247 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
3248 OPEN_REFRESH = 16, /* Refresh view using previous command. */
3249 OPEN_PREPARED = 32, /* Open already prepared command. */
3250 };
3252 static void
3253 open_view(struct view *prev, enum request request, enum open_flags flags)
3254 {
3255 bool split = !!(flags & OPEN_SPLIT);
3256 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3257 bool nomaximize = !!(flags & OPEN_REFRESH);
3258 struct view *view = VIEW(request);
3259 int nviews = displayed_views();
3260 struct view *base_view = display[0];
3262 if (view == prev && nviews == 1 && !reload) {
3263 report("Already in %s view", view->name);
3264 return;
3265 }
3267 if (view->git_dir && !opt_git_dir[0]) {
3268 report("The %s view is disabled in pager view", view->name);
3269 return;
3270 }
3272 if (split) {
3273 display[1] = view;
3274 current_view = 1;
3275 } else if (!nomaximize) {
3276 /* Maximize the current view. */
3277 memset(display, 0, sizeof(display));
3278 current_view = 0;
3279 display[current_view] = view;
3280 }
3282 /* No parent signals that this is the first loaded view. */
3283 if (prev && view != prev) {
3284 view->parent = prev;
3285 }
3287 /* Resize the view when switching between split- and full-screen,
3288 * or when switching between two different full-screen views. */
3289 if (nviews != displayed_views() ||
3290 (nviews == 1 && base_view != display[0]))
3291 resize_display();
3293 if (view->ops->open) {
3294 if (view->pipe)
3295 end_update(view, TRUE);
3296 if (!view->ops->open(view)) {
3297 report("Failed to load %s view", view->name);
3298 return;
3299 }
3300 restore_view_position(view);
3302 } else if ((reload || strcmp(view->vid, view->id)) &&
3303 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3304 report("Failed to load %s view", view->name);
3305 return;
3306 }
3308 if (split && prev->lineno - prev->offset >= prev->height) {
3309 /* Take the title line into account. */
3310 int lines = prev->lineno - prev->offset - prev->height + 1;
3312 /* Scroll the view that was split if the current line is
3313 * outside the new limited view. */
3314 do_scroll_view(prev, lines);
3315 }
3317 if (prev && view != prev && split && view_is_displayed(prev)) {
3318 /* "Blur" the previous view. */
3319 update_view_title(prev);
3320 }
3322 if (view->pipe && view->lines == 0) {
3323 /* Clear the old view and let the incremental updating refill
3324 * the screen. */
3325 werase(view->win);
3326 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3327 report("");
3328 } else if (view_is_displayed(view)) {
3329 redraw_view(view);
3330 report("");
3331 }
3332 }
3334 static void
3335 open_external_viewer(const char *argv[], const char *dir)
3336 {
3337 def_prog_mode(); /* save current tty modes */
3338 endwin(); /* restore original tty modes */
3339 io_run_fg(argv, dir);
3340 fprintf(stderr, "Press Enter to continue");
3341 getc(opt_tty);
3342 reset_prog_mode();
3343 redraw_display(TRUE);
3344 }
3346 static void
3347 open_mergetool(const char *file)
3348 {
3349 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3351 open_external_viewer(mergetool_argv, opt_cdup);
3352 }
3354 static void
3355 open_editor(const char *file)
3356 {
3357 const char *editor_argv[] = { "vi", file, NULL };
3358 const char *editor;
3360 editor = getenv("GIT_EDITOR");
3361 if (!editor && *opt_editor)
3362 editor = opt_editor;
3363 if (!editor)
3364 editor = getenv("VISUAL");
3365 if (!editor)
3366 editor = getenv("EDITOR");
3367 if (!editor)
3368 editor = "vi";
3370 editor_argv[0] = editor;
3371 open_external_viewer(editor_argv, opt_cdup);
3372 }
3374 static void
3375 open_run_request(enum request request)
3376 {
3377 struct run_request *req = get_run_request(request);
3378 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3380 if (!req) {
3381 report("Unknown run request");
3382 return;
3383 }
3385 if (format_argv(argv, req->argv, FORMAT_ALL))
3386 open_external_viewer(argv, NULL);
3387 free_argv(argv);
3388 }
3390 /*
3391 * User request switch noodle
3392 */
3394 static int
3395 view_driver(struct view *view, enum request request)
3396 {
3397 int i;
3399 if (request == REQ_NONE)
3400 return TRUE;
3402 if (request > REQ_NONE) {
3403 open_run_request(request);
3404 /* FIXME: When all views can refresh always do this. */
3405 if (view == VIEW(REQ_VIEW_STATUS) ||
3406 view == VIEW(REQ_VIEW_MAIN) ||
3407 view == VIEW(REQ_VIEW_LOG) ||
3408 view == VIEW(REQ_VIEW_BRANCH) ||
3409 view == VIEW(REQ_VIEW_STAGE))
3410 request = REQ_REFRESH;
3411 else
3412 return TRUE;
3413 }
3415 if (view && view->lines) {
3416 request = view->ops->request(view, request, &view->line[view->lineno]);
3417 if (request == REQ_NONE)
3418 return TRUE;
3419 }
3421 switch (request) {
3422 case REQ_MOVE_UP:
3423 case REQ_MOVE_DOWN:
3424 case REQ_MOVE_PAGE_UP:
3425 case REQ_MOVE_PAGE_DOWN:
3426 case REQ_MOVE_FIRST_LINE:
3427 case REQ_MOVE_LAST_LINE:
3428 move_view(view, request);
3429 break;
3431 case REQ_SCROLL_LEFT:
3432 case REQ_SCROLL_RIGHT:
3433 case REQ_SCROLL_LINE_DOWN:
3434 case REQ_SCROLL_LINE_UP:
3435 case REQ_SCROLL_PAGE_DOWN:
3436 case REQ_SCROLL_PAGE_UP:
3437 scroll_view(view, request);
3438 break;
3440 case REQ_VIEW_BLAME:
3441 if (!opt_file[0]) {
3442 report("No file chosen, press %s to open tree view",
3443 get_key(view->keymap, REQ_VIEW_TREE));
3444 break;
3445 }
3446 open_view(view, request, OPEN_DEFAULT);
3447 break;
3449 case REQ_VIEW_BLOB:
3450 if (!ref_blob[0]) {
3451 report("No file chosen, press %s to open tree view",
3452 get_key(view->keymap, REQ_VIEW_TREE));
3453 break;
3454 }
3455 open_view(view, request, OPEN_DEFAULT);
3456 break;
3458 case REQ_VIEW_PAGER:
3459 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3460 report("No pager content, press %s to run command from prompt",
3461 get_key(view->keymap, REQ_PROMPT));
3462 break;
3463 }
3464 open_view(view, request, OPEN_DEFAULT);
3465 break;
3467 case REQ_VIEW_STAGE:
3468 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3469 report("No stage content, press %s to open the status view and choose file",
3470 get_key(view->keymap, REQ_VIEW_STATUS));
3471 break;
3472 }
3473 open_view(view, request, OPEN_DEFAULT);
3474 break;
3476 case REQ_VIEW_STATUS:
3477 if (opt_is_inside_work_tree == FALSE) {
3478 report("The status view requires a working tree");
3479 break;
3480 }
3481 open_view(view, request, OPEN_DEFAULT);
3482 break;
3484 case REQ_VIEW_MAIN:
3485 case REQ_VIEW_DIFF:
3486 case REQ_VIEW_LOG:
3487 case REQ_VIEW_TREE:
3488 case REQ_VIEW_HELP:
3489 case REQ_VIEW_BRANCH:
3490 open_view(view, request, OPEN_DEFAULT);
3491 break;
3493 case REQ_NEXT:
3494 case REQ_PREVIOUS:
3495 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3497 if ((view == VIEW(REQ_VIEW_DIFF) &&
3498 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3499 (view == VIEW(REQ_VIEW_DIFF) &&
3500 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3501 (view == VIEW(REQ_VIEW_STAGE) &&
3502 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3503 (view == VIEW(REQ_VIEW_BLOB) &&
3504 view->parent == VIEW(REQ_VIEW_TREE)) ||
3505 (view == VIEW(REQ_VIEW_MAIN) &&
3506 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3507 int line;
3509 view = view->parent;
3510 line = view->lineno;
3511 move_view(view, request);
3512 if (view_is_displayed(view))
3513 update_view_title(view);
3514 if (line != view->lineno)
3515 view->ops->request(view, REQ_ENTER,
3516 &view->line[view->lineno]);
3518 } else {
3519 move_view(view, request);
3520 }
3521 break;
3523 case REQ_VIEW_NEXT:
3524 {
3525 int nviews = displayed_views();
3526 int next_view = (current_view + 1) % nviews;
3528 if (next_view == current_view) {
3529 report("Only one view is displayed");
3530 break;
3531 }
3533 current_view = next_view;
3534 /* Blur out the title of the previous view. */
3535 update_view_title(view);
3536 report("");
3537 break;
3538 }
3539 case REQ_REFRESH:
3540 report("Refreshing is not yet supported for the %s view", view->name);
3541 break;
3543 case REQ_MAXIMIZE:
3544 if (displayed_views() == 2)
3545 maximize_view(view);
3546 break;
3548 case REQ_OPTIONS:
3549 open_option_menu();
3550 break;
3552 case REQ_TOGGLE_LINENO:
3553 toggle_view_option(&opt_line_number, "line numbers");
3554 break;
3556 case REQ_TOGGLE_DATE:
3557 toggle_date();
3558 break;
3560 case REQ_TOGGLE_AUTHOR:
3561 toggle_author();
3562 break;
3564 case REQ_TOGGLE_REV_GRAPH:
3565 toggle_view_option(&opt_rev_graph, "revision graph display");
3566 break;
3568 case REQ_TOGGLE_REFS:
3569 toggle_view_option(&opt_show_refs, "reference display");
3570 break;
3572 case REQ_TOGGLE_SORT_FIELD:
3573 case REQ_TOGGLE_SORT_ORDER:
3574 report("Sorting is not yet supported for the %s view", view->name);
3575 break;
3577 case REQ_SEARCH:
3578 case REQ_SEARCH_BACK:
3579 search_view(view, request);
3580 break;
3582 case REQ_FIND_NEXT:
3583 case REQ_FIND_PREV:
3584 find_next(view, request);
3585 break;
3587 case REQ_STOP_LOADING:
3588 for (i = 0; i < ARRAY_SIZE(views); i++) {
3589 view = &views[i];
3590 if (view->pipe)
3591 report("Stopped loading the %s view", view->name),
3592 end_update(view, TRUE);
3593 }
3594 break;
3596 case REQ_SHOW_VERSION:
3597 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3598 return TRUE;
3600 case REQ_SCREEN_REDRAW:
3601 redraw_display(TRUE);
3602 break;
3604 case REQ_EDIT:
3605 report("Nothing to edit");
3606 break;
3608 case REQ_ENTER:
3609 report("Nothing to enter");
3610 break;
3612 case REQ_VIEW_CLOSE:
3613 /* XXX: Mark closed views by letting view->parent point to the
3614 * view itself. Parents to closed view should never be
3615 * followed. */
3616 if (view->parent &&
3617 view->parent->parent != view->parent) {
3618 maximize_view(view->parent);
3619 view->parent = view;
3620 break;
3621 }
3622 /* Fall-through */
3623 case REQ_QUIT:
3624 return FALSE;
3626 default:
3627 report("Unknown key, press %s for help",
3628 get_key(view->keymap, REQ_VIEW_HELP));
3629 return TRUE;
3630 }
3632 return TRUE;
3633 }
3636 /*
3637 * View backend utilities
3638 */
3640 enum sort_field {
3641 ORDERBY_NAME,
3642 ORDERBY_DATE,
3643 ORDERBY_AUTHOR,
3644 };
3646 struct sort_state {
3647 const enum sort_field *fields;
3648 size_t size, current;
3649 bool reverse;
3650 };
3652 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3653 #define get_sort_field(state) ((state).fields[(state).current])
3654 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3656 static void
3657 sort_view(struct view *view, enum request request, struct sort_state *state,
3658 int (*compare)(const void *, const void *))
3659 {
3660 switch (request) {
3661 case REQ_TOGGLE_SORT_FIELD:
3662 state->current = (state->current + 1) % state->size;
3663 break;
3665 case REQ_TOGGLE_SORT_ORDER:
3666 state->reverse = !state->reverse;
3667 break;
3668 default:
3669 die("Not a sort request");
3670 }
3672 qsort(view->line, view->lines, sizeof(*view->line), compare);
3673 redraw_view(view);
3674 }
3676 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3678 /* Small author cache to reduce memory consumption. It uses binary
3679 * search to lookup or find place to position new entries. No entries
3680 * are ever freed. */
3681 static const char *
3682 get_author(const char *name)
3683 {
3684 static const char **authors;
3685 static size_t authors_size;
3686 int from = 0, to = authors_size - 1;
3688 while (from <= to) {
3689 size_t pos = (to + from) / 2;
3690 int cmp = strcmp(name, authors[pos]);
3692 if (!cmp)
3693 return authors[pos];
3695 if (cmp < 0)
3696 to = pos - 1;
3697 else
3698 from = pos + 1;
3699 }
3701 if (!realloc_authors(&authors, authors_size, 1))
3702 return NULL;
3703 name = strdup(name);
3704 if (!name)
3705 return NULL;
3707 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3708 authors[from] = name;
3709 authors_size++;
3711 return name;
3712 }
3714 static void
3715 parse_timesec(struct time *time, const char *sec)
3716 {
3717 time->sec = (time_t) atol(sec);
3718 }
3720 static void
3721 parse_timezone(struct time *time, const char *zone)
3722 {
3723 long tz;
3725 tz = ('0' - zone[1]) * 60 * 60 * 10;
3726 tz += ('0' - zone[2]) * 60 * 60;
3727 tz += ('0' - zone[3]) * 60;
3728 tz += ('0' - zone[4]);
3730 if (zone[0] == '-')
3731 tz = -tz;
3733 time->tz = tz;
3734 time->sec -= tz;
3735 }
3737 /* Parse author lines where the name may be empty:
3738 * author <email@address.tld> 1138474660 +0100
3739 */
3740 static void
3741 parse_author_line(char *ident, const char **author, struct time *time)
3742 {
3743 char *nameend = strchr(ident, '<');
3744 char *emailend = strchr(ident, '>');
3746 if (nameend && emailend)
3747 *nameend = *emailend = 0;
3748 ident = chomp_string(ident);
3749 if (!*ident) {
3750 if (nameend)
3751 ident = chomp_string(nameend + 1);
3752 if (!*ident)
3753 ident = "Unknown";
3754 }
3756 *author = get_author(ident);
3758 /* Parse epoch and timezone */
3759 if (emailend && emailend[1] == ' ') {
3760 char *secs = emailend + 2;
3761 char *zone = strchr(secs, ' ');
3763 parse_timesec(time, secs);
3765 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3766 parse_timezone(time, zone + 1);
3767 }
3768 }
3770 static bool
3771 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3772 {
3773 char rev[SIZEOF_REV];
3774 const char *revlist_argv[] = {
3775 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3776 };
3777 struct menu_item *items;
3778 char text[SIZEOF_STR];
3779 bool ok = TRUE;
3780 int i;
3782 items = calloc(*parents + 1, sizeof(*items));
3783 if (!items)
3784 return FALSE;
3786 for (i = 0; i < *parents; i++) {
3787 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3788 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3789 !(items[i].text = strdup(text))) {
3790 ok = FALSE;
3791 break;
3792 }
3793 }
3795 if (ok) {
3796 *parents = 0;
3797 ok = prompt_menu("Select parent", items, parents);
3798 }
3799 for (i = 0; items[i].text; i++)
3800 free((char *) items[i].text);
3801 free(items);
3802 return ok;
3803 }
3805 static bool
3806 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3807 {
3808 char buf[SIZEOF_STR * 4];
3809 const char *revlist_argv[] = {
3810 "git", "log", "--no-color", "-1",
3811 "--pretty=format:%P", id, "--", path, NULL
3812 };
3813 int parents;
3815 if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3816 (parents = strlen(buf) / 40) < 0) {
3817 report("Failed to get parent information");
3818 return FALSE;
3820 } else if (parents == 0) {
3821 if (path)
3822 report("Path '%s' does not exist in the parent", path);
3823 else
3824 report("The selected commit has no parents");
3825 return FALSE;
3826 }
3828 if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3829 return FALSE;
3831 string_copy_rev(rev, &buf[41 * parents]);
3832 return TRUE;
3833 }
3835 /*
3836 * Pager backend
3837 */
3839 static bool
3840 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3841 {
3842 char text[SIZEOF_STR];
3844 if (opt_line_number && draw_lineno(view, lineno))
3845 return TRUE;
3847 string_expand(text, sizeof(text), line->data, opt_tab_size);
3848 draw_text(view, line->type, text, TRUE);
3849 return TRUE;
3850 }
3852 static bool
3853 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3854 {
3855 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3856 char ref[SIZEOF_STR];
3858 if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3859 return TRUE;
3861 /* This is the only fatal call, since it can "corrupt" the buffer. */
3862 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3863 return FALSE;
3865 return TRUE;
3866 }
3868 static void
3869 add_pager_refs(struct view *view, struct line *line)
3870 {
3871 char buf[SIZEOF_STR];
3872 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3873 struct ref_list *list;
3874 size_t bufpos = 0, i;
3875 const char *sep = "Refs: ";
3876 bool is_tag = FALSE;
3878 assert(line->type == LINE_COMMIT);
3880 list = get_ref_list(commit_id);
3881 if (!list) {
3882 if (view == VIEW(REQ_VIEW_DIFF))
3883 goto try_add_describe_ref;
3884 return;
3885 }
3887 for (i = 0; i < list->size; i++) {
3888 struct ref *ref = list->refs[i];
3889 const char *fmt = ref->tag ? "%s[%s]" :
3890 ref->remote ? "%s<%s>" : "%s%s";
3892 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3893 return;
3894 sep = ", ";
3895 if (ref->tag)
3896 is_tag = TRUE;
3897 }
3899 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3900 try_add_describe_ref:
3901 /* Add <tag>-g<commit_id> "fake" reference. */
3902 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3903 return;
3904 }
3906 if (bufpos == 0)
3907 return;
3909 add_line_text(view, buf, LINE_PP_REFS);
3910 }
3912 static bool
3913 pager_read(struct view *view, char *data)
3914 {
3915 struct line *line;
3917 if (!data)
3918 return TRUE;
3920 line = add_line_text(view, data, get_line_type(data));
3921 if (!line)
3922 return FALSE;
3924 if (line->type == LINE_COMMIT &&
3925 (view == VIEW(REQ_VIEW_DIFF) ||
3926 view == VIEW(REQ_VIEW_LOG)))
3927 add_pager_refs(view, line);
3929 return TRUE;
3930 }
3932 static enum request
3933 pager_request(struct view *view, enum request request, struct line *line)
3934 {
3935 int split = 0;
3937 if (request != REQ_ENTER)
3938 return request;
3940 if (line->type == LINE_COMMIT &&
3941 (view == VIEW(REQ_VIEW_LOG) ||
3942 view == VIEW(REQ_VIEW_PAGER))) {
3943 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3944 split = 1;
3945 }
3947 /* Always scroll the view even if it was split. That way
3948 * you can use Enter to scroll through the log view and
3949 * split open each commit diff. */
3950 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3952 /* FIXME: A minor workaround. Scrolling the view will call report("")
3953 * but if we are scrolling a non-current view this won't properly
3954 * update the view title. */
3955 if (split)
3956 update_view_title(view);
3958 return REQ_NONE;
3959 }
3961 static bool
3962 pager_grep(struct view *view, struct line *line)
3963 {
3964 const char *text[] = { line->data, NULL };
3966 return grep_text(view, text);
3967 }
3969 static void
3970 pager_select(struct view *view, struct line *line)
3971 {
3972 if (line->type == LINE_COMMIT) {
3973 char *text = (char *)line->data + STRING_SIZE("commit ");
3975 if (view != VIEW(REQ_VIEW_PAGER))
3976 string_copy_rev(view->ref, text);
3977 string_copy_rev(ref_commit, text);
3978 }
3979 }
3981 static struct view_ops pager_ops = {
3982 "line",
3983 NULL,
3984 NULL,
3985 pager_read,
3986 pager_draw,
3987 pager_request,
3988 pager_grep,
3989 pager_select,
3990 };
3992 static const char *log_argv[SIZEOF_ARG] = {
3993 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3994 };
3996 static enum request
3997 log_request(struct view *view, enum request request, struct line *line)
3998 {
3999 switch (request) {
4000 case REQ_REFRESH:
4001 load_refs();
4002 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4003 return REQ_NONE;
4004 default:
4005 return pager_request(view, request, line);
4006 }
4007 }
4009 static struct view_ops log_ops = {
4010 "line",
4011 log_argv,
4012 NULL,
4013 pager_read,
4014 pager_draw,
4015 log_request,
4016 pager_grep,
4017 pager_select,
4018 };
4020 static const char *diff_argv[SIZEOF_ARG] = {
4021 "git", "show", "--pretty=fuller", "--no-color", "--root",
4022 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4023 };
4025 static struct view_ops diff_ops = {
4026 "line",
4027 diff_argv,
4028 NULL,
4029 pager_read,
4030 pager_draw,
4031 pager_request,
4032 pager_grep,
4033 pager_select,
4034 };
4036 /*
4037 * Help backend
4038 */
4040 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4042 static bool
4043 help_open_keymap_title(struct view *view, enum keymap keymap)
4044 {
4045 struct line *line;
4047 line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4048 help_keymap_hidden[keymap] ? '+' : '-',
4049 enum_name(keymap_table[keymap]));
4050 if (line)
4051 line->other = keymap;
4053 return help_keymap_hidden[keymap];
4054 }
4056 static void
4057 help_open_keymap(struct view *view, enum keymap keymap)
4058 {
4059 const char *group = NULL;
4060 char buf[SIZEOF_STR];
4061 size_t bufpos;
4062 bool add_title = TRUE;
4063 int i;
4065 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4066 const char *key = NULL;
4068 if (req_info[i].request == REQ_NONE)
4069 continue;
4071 if (!req_info[i].request) {
4072 group = req_info[i].help;
4073 continue;
4074 }
4076 key = get_keys(keymap, req_info[i].request, TRUE);
4077 if (!key || !*key)
4078 continue;
4080 if (add_title && help_open_keymap_title(view, keymap))
4081 return;
4082 add_title = FALSE;
4084 if (group) {
4085 add_line_text(view, group, LINE_HELP_GROUP);
4086 group = NULL;
4087 }
4089 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key,
4090 enum_name(req_info[i]), req_info[i].help);
4091 }
4093 group = "External commands:";
4095 for (i = 0; i < run_requests; i++) {
4096 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4097 const char *key;
4098 int argc;
4100 if (!req || req->keymap != keymap)
4101 continue;
4103 key = get_key_name(req->key);
4104 if (!*key)
4105 key = "(no key defined)";
4107 if (add_title && help_open_keymap_title(view, keymap))
4108 return;
4109 if (group) {
4110 add_line_text(view, group, LINE_HELP_GROUP);
4111 group = NULL;
4112 }
4114 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4115 if (!string_format_from(buf, &bufpos, "%s%s",
4116 argc ? " " : "", req->argv[argc]))
4117 return;
4119 add_line_format(view, LINE_DEFAULT, " %-25s `%s`", key, buf);
4120 }
4121 }
4123 static bool
4124 help_open(struct view *view)
4125 {
4126 enum keymap keymap;
4128 reset_view(view);
4129 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4130 add_line_text(view, "", LINE_DEFAULT);
4132 for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4133 help_open_keymap(view, keymap);
4135 return TRUE;
4136 }
4138 static enum request
4139 help_request(struct view *view, enum request request, struct line *line)
4140 {
4141 switch (request) {
4142 case REQ_ENTER:
4143 if (line->type == LINE_HELP_KEYMAP) {
4144 help_keymap_hidden[line->other] =
4145 !help_keymap_hidden[line->other];
4146 view->p_restore = TRUE;
4147 open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4148 }
4150 return REQ_NONE;
4151 default:
4152 return pager_request(view, request, line);
4153 }
4154 }
4156 static struct view_ops help_ops = {
4157 "line",
4158 NULL,
4159 help_open,
4160 NULL,
4161 pager_draw,
4162 help_request,
4163 pager_grep,
4164 pager_select,
4165 };
4168 /*
4169 * Tree backend
4170 */
4172 struct tree_stack_entry {
4173 struct tree_stack_entry *prev; /* Entry below this in the stack */
4174 unsigned long lineno; /* Line number to restore */
4175 char *name; /* Position of name in opt_path */
4176 };
4178 /* The top of the path stack. */
4179 static struct tree_stack_entry *tree_stack = NULL;
4180 unsigned long tree_lineno = 0;
4182 static void
4183 pop_tree_stack_entry(void)
4184 {
4185 struct tree_stack_entry *entry = tree_stack;
4187 tree_lineno = entry->lineno;
4188 entry->name[0] = 0;
4189 tree_stack = entry->prev;
4190 free(entry);
4191 }
4193 static void
4194 push_tree_stack_entry(const char *name, unsigned long lineno)
4195 {
4196 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4197 size_t pathlen = strlen(opt_path);
4199 if (!entry)
4200 return;
4202 entry->prev = tree_stack;
4203 entry->name = opt_path + pathlen;
4204 tree_stack = entry;
4206 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4207 pop_tree_stack_entry();
4208 return;
4209 }
4211 /* Move the current line to the first tree entry. */
4212 tree_lineno = 1;
4213 entry->lineno = lineno;
4214 }
4216 /* Parse output from git-ls-tree(1):
4217 *
4218 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4219 */
4221 #define SIZEOF_TREE_ATTR \
4222 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4224 #define SIZEOF_TREE_MODE \
4225 STRING_SIZE("100644 ")
4227 #define TREE_ID_OFFSET \
4228 STRING_SIZE("100644 blob ")
4230 struct tree_entry {
4231 char id[SIZEOF_REV];
4232 mode_t mode;
4233 struct time time; /* Date from the author ident. */
4234 const char *author; /* Author of the commit. */
4235 char name[1];
4236 };
4238 static const char *
4239 tree_path(const struct line *line)
4240 {
4241 return ((struct tree_entry *) line->data)->name;
4242 }
4244 static int
4245 tree_compare_entry(const struct line *line1, const struct line *line2)
4246 {
4247 if (line1->type != line2->type)
4248 return line1->type == LINE_TREE_DIR ? -1 : 1;
4249 return strcmp(tree_path(line1), tree_path(line2));
4250 }
4252 static const enum sort_field tree_sort_fields[] = {
4253 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4254 };
4255 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4257 static int
4258 tree_compare(const void *l1, const void *l2)
4259 {
4260 const struct line *line1 = (const struct line *) l1;
4261 const struct line *line2 = (const struct line *) l2;
4262 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4263 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4265 if (line1->type == LINE_TREE_HEAD)
4266 return -1;
4267 if (line2->type == LINE_TREE_HEAD)
4268 return 1;
4270 switch (get_sort_field(tree_sort_state)) {
4271 case ORDERBY_DATE:
4272 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4274 case ORDERBY_AUTHOR:
4275 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4277 case ORDERBY_NAME:
4278 default:
4279 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4280 }
4281 }
4284 static struct line *
4285 tree_entry(struct view *view, enum line_type type, const char *path,
4286 const char *mode, const char *id)
4287 {
4288 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4289 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4291 if (!entry || !line) {
4292 free(entry);
4293 return NULL;
4294 }
4296 strncpy(entry->name, path, strlen(path));
4297 if (mode)
4298 entry->mode = strtoul(mode, NULL, 8);
4299 if (id)
4300 string_copy_rev(entry->id, id);
4302 return line;
4303 }
4305 static bool
4306 tree_read_date(struct view *view, char *text, bool *read_date)
4307 {
4308 static const char *author_name;
4309 static struct time author_time;
4311 if (!text && *read_date) {
4312 *read_date = FALSE;
4313 return TRUE;
4315 } else if (!text) {
4316 char *path = *opt_path ? opt_path : ".";
4317 /* Find next entry to process */
4318 const char *log_file[] = {
4319 "git", "log", "--no-color", "--pretty=raw",
4320 "--cc", "--raw", view->id, "--", path, NULL
4321 };
4322 struct io io = {};
4324 if (!view->lines) {
4325 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4326 report("Tree is empty");
4327 return TRUE;
4328 }
4330 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4331 report("Failed to load tree data");
4332 return TRUE;
4333 }
4335 io_done(view->pipe);
4336 view->io = io;
4337 *read_date = TRUE;
4338 return FALSE;
4340 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4341 parse_author_line(text + STRING_SIZE("author "),
4342 &author_name, &author_time);
4344 } else if (*text == ':') {
4345 char *pos;
4346 size_t annotated = 1;
4347 size_t i;
4349 pos = strchr(text, '\t');
4350 if (!pos)
4351 return TRUE;
4352 text = pos + 1;
4353 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4354 text += strlen(opt_path);
4355 pos = strchr(text, '/');
4356 if (pos)
4357 *pos = 0;
4359 for (i = 1; i < view->lines; i++) {
4360 struct line *line = &view->line[i];
4361 struct tree_entry *entry = line->data;
4363 annotated += !!entry->author;
4364 if (entry->author || strcmp(entry->name, text))
4365 continue;
4367 entry->author = author_name;
4368 entry->time = author_time;
4369 line->dirty = 1;
4370 break;
4371 }
4373 if (annotated == view->lines)
4374 io_kill(view->pipe);
4375 }
4376 return TRUE;
4377 }
4379 static bool
4380 tree_read(struct view *view, char *text)
4381 {
4382 static bool read_date = FALSE;
4383 struct tree_entry *data;
4384 struct line *entry, *line;
4385 enum line_type type;
4386 size_t textlen = text ? strlen(text) : 0;
4387 char *path = text + SIZEOF_TREE_ATTR;
4389 if (read_date || !text)
4390 return tree_read_date(view, text, &read_date);
4392 if (textlen <= SIZEOF_TREE_ATTR)
4393 return FALSE;
4394 if (view->lines == 0 &&
4395 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4396 return FALSE;
4398 /* Strip the path part ... */
4399 if (*opt_path) {
4400 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4401 size_t striplen = strlen(opt_path);
4403 if (pathlen > striplen)
4404 memmove(path, path + striplen,
4405 pathlen - striplen + 1);
4407 /* Insert "link" to parent directory. */
4408 if (view->lines == 1 &&
4409 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4410 return FALSE;
4411 }
4413 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4414 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4415 if (!entry)
4416 return FALSE;
4417 data = entry->data;
4419 /* Skip "Directory ..." and ".." line. */
4420 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4421 if (tree_compare_entry(line, entry) <= 0)
4422 continue;
4424 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4426 line->data = data;
4427 line->type = type;
4428 for (; line <= entry; line++)
4429 line->dirty = line->cleareol = 1;
4430 return TRUE;
4431 }
4433 if (tree_lineno > view->lineno) {
4434 view->lineno = tree_lineno;
4435 tree_lineno = 0;
4436 }
4438 return TRUE;
4439 }
4441 static bool
4442 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4443 {
4444 struct tree_entry *entry = line->data;
4446 if (line->type == LINE_TREE_HEAD) {
4447 if (draw_text(view, line->type, "Directory path /", TRUE))
4448 return TRUE;
4449 } else {
4450 if (draw_mode(view, entry->mode))
4451 return TRUE;
4453 if (opt_author && draw_author(view, entry->author))
4454 return TRUE;
4456 if (opt_date && draw_date(view, &entry->time))
4457 return TRUE;
4458 }
4459 if (draw_text(view, line->type, entry->name, TRUE))
4460 return TRUE;
4461 return TRUE;
4462 }
4464 static void
4465 open_blob_editor()
4466 {
4467 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4468 int fd = mkstemp(file);
4470 if (fd == -1)
4471 report("Failed to create temporary file");
4472 else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4473 report("Failed to save blob data to file");
4474 else
4475 open_editor(file);
4476 if (fd != -1)
4477 unlink(file);
4478 }
4480 static enum request
4481 tree_request(struct view *view, enum request request, struct line *line)
4482 {
4483 enum open_flags flags;
4485 switch (request) {
4486 case REQ_VIEW_BLAME:
4487 if (line->type != LINE_TREE_FILE) {
4488 report("Blame only supported for files");
4489 return REQ_NONE;
4490 }
4492 string_copy(opt_ref, view->vid);
4493 return request;
4495 case REQ_EDIT:
4496 if (line->type != LINE_TREE_FILE) {
4497 report("Edit only supported for files");
4498 } else if (!is_head_commit(view->vid)) {
4499 open_blob_editor();
4500 } else {
4501 open_editor(opt_file);
4502 }
4503 return REQ_NONE;
4505 case REQ_TOGGLE_SORT_FIELD:
4506 case REQ_TOGGLE_SORT_ORDER:
4507 sort_view(view, request, &tree_sort_state, tree_compare);
4508 return REQ_NONE;
4510 case REQ_PARENT:
4511 if (!*opt_path) {
4512 /* quit view if at top of tree */
4513 return REQ_VIEW_CLOSE;
4514 }
4515 /* fake 'cd ..' */
4516 line = &view->line[1];
4517 break;
4519 case REQ_ENTER:
4520 break;
4522 default:
4523 return request;
4524 }
4526 /* Cleanup the stack if the tree view is at a different tree. */
4527 while (!*opt_path && tree_stack)
4528 pop_tree_stack_entry();
4530 switch (line->type) {
4531 case LINE_TREE_DIR:
4532 /* Depending on whether it is a subdirectory or parent link
4533 * mangle the path buffer. */
4534 if (line == &view->line[1] && *opt_path) {
4535 pop_tree_stack_entry();
4537 } else {
4538 const char *basename = tree_path(line);
4540 push_tree_stack_entry(basename, view->lineno);
4541 }
4543 /* Trees and subtrees share the same ID, so they are not not
4544 * unique like blobs. */
4545 flags = OPEN_RELOAD;
4546 request = REQ_VIEW_TREE;
4547 break;
4549 case LINE_TREE_FILE:
4550 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4551 request = REQ_VIEW_BLOB;
4552 break;
4554 default:
4555 return REQ_NONE;
4556 }
4558 open_view(view, request, flags);
4559 if (request == REQ_VIEW_TREE)
4560 view->lineno = tree_lineno;
4562 return REQ_NONE;
4563 }
4565 static bool
4566 tree_grep(struct view *view, struct line *line)
4567 {
4568 struct tree_entry *entry = line->data;
4569 const char *text[] = {
4570 entry->name,
4571 opt_author ? entry->author : "",
4572 mkdate(&entry->time, opt_date),
4573 NULL
4574 };
4576 return grep_text(view, text);
4577 }
4579 static void
4580 tree_select(struct view *view, struct line *line)
4581 {
4582 struct tree_entry *entry = line->data;
4584 if (line->type == LINE_TREE_FILE) {
4585 string_copy_rev(ref_blob, entry->id);
4586 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4588 } else if (line->type != LINE_TREE_DIR) {
4589 return;
4590 }
4592 string_copy_rev(view->ref, entry->id);
4593 }
4595 static bool
4596 tree_prepare(struct view *view)
4597 {
4598 if (view->lines == 0 && opt_prefix[0]) {
4599 char *pos = opt_prefix;
4601 while (pos && *pos) {
4602 char *end = strchr(pos, '/');
4604 if (end)
4605 *end = 0;
4606 push_tree_stack_entry(pos, 0);
4607 pos = end;
4608 if (end) {
4609 *end = '/';
4610 pos++;
4611 }
4612 }
4614 } else if (strcmp(view->vid, view->id)) {
4615 opt_path[0] = 0;
4616 }
4618 return io_init_rd(&view->io, view->ops->argv, opt_cdup, FORMAT_ALL);
4619 }
4621 static const char *tree_argv[SIZEOF_ARG] = {
4622 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4623 };
4625 static struct view_ops tree_ops = {
4626 "file",
4627 tree_argv,
4628 NULL,
4629 tree_read,
4630 tree_draw,
4631 tree_request,
4632 tree_grep,
4633 tree_select,
4634 tree_prepare,
4635 };
4637 static bool
4638 blob_read(struct view *view, char *line)
4639 {
4640 if (!line)
4641 return TRUE;
4642 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4643 }
4645 static enum request
4646 blob_request(struct view *view, enum request request, struct line *line)
4647 {
4648 switch (request) {
4649 case REQ_EDIT:
4650 open_blob_editor();
4651 return REQ_NONE;
4652 default:
4653 return pager_request(view, request, line);
4654 }
4655 }
4657 static const char *blob_argv[SIZEOF_ARG] = {
4658 "git", "cat-file", "blob", "%(blob)", NULL
4659 };
4661 static struct view_ops blob_ops = {
4662 "line",
4663 blob_argv,
4664 NULL,
4665 blob_read,
4666 pager_draw,
4667 blob_request,
4668 pager_grep,
4669 pager_select,
4670 };
4672 /*
4673 * Blame backend
4674 *
4675 * Loading the blame view is a two phase job:
4676 *
4677 * 1. File content is read either using opt_file from the
4678 * filesystem or using git-cat-file.
4679 * 2. Then blame information is incrementally added by
4680 * reading output from git-blame.
4681 */
4683 static const char *blame_head_argv[] = {
4684 "git", "blame", "--incremental", "--", "%(file)", NULL
4685 };
4687 static const char *blame_ref_argv[] = {
4688 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4689 };
4691 static const char *blame_cat_file_argv[] = {
4692 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4693 };
4695 struct blame_commit {
4696 char id[SIZEOF_REV]; /* SHA1 ID. */
4697 char title[128]; /* First line of the commit message. */
4698 const char *author; /* Author of the commit. */
4699 struct time time; /* Date from the author ident. */
4700 char filename[128]; /* Name of file. */
4701 bool has_previous; /* Was a "previous" line detected. */
4702 };
4704 struct blame {
4705 struct blame_commit *commit;
4706 unsigned long lineno;
4707 char text[1];
4708 };
4710 static bool
4711 blame_open(struct view *view)
4712 {
4713 char path[SIZEOF_STR];
4715 if (!view->parent && *opt_prefix) {
4716 string_copy(path, opt_file);
4717 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4718 return FALSE;
4719 }
4721 if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4722 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4723 return FALSE;
4724 }
4726 setup_update(view, opt_file);
4727 string_format(view->ref, "%s ...", opt_file);
4729 return TRUE;
4730 }
4732 static struct blame_commit *
4733 get_blame_commit(struct view *view, const char *id)
4734 {
4735 size_t i;
4737 for (i = 0; i < view->lines; i++) {
4738 struct blame *blame = view->line[i].data;
4740 if (!blame->commit)
4741 continue;
4743 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4744 return blame->commit;
4745 }
4747 {
4748 struct blame_commit *commit = calloc(1, sizeof(*commit));
4750 if (commit)
4751 string_ncopy(commit->id, id, SIZEOF_REV);
4752 return commit;
4753 }
4754 }
4756 static bool
4757 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4758 {
4759 const char *pos = *posref;
4761 *posref = NULL;
4762 pos = strchr(pos + 1, ' ');
4763 if (!pos || !isdigit(pos[1]))
4764 return FALSE;
4765 *number = atoi(pos + 1);
4766 if (*number < min || *number > max)
4767 return FALSE;
4769 *posref = pos;
4770 return TRUE;
4771 }
4773 static struct blame_commit *
4774 parse_blame_commit(struct view *view, const char *text, int *blamed)
4775 {
4776 struct blame_commit *commit;
4777 struct blame *blame;
4778 const char *pos = text + SIZEOF_REV - 2;
4779 size_t orig_lineno = 0;
4780 size_t lineno;
4781 size_t group;
4783 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4784 return NULL;
4786 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4787 !parse_number(&pos, &lineno, 1, view->lines) ||
4788 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4789 return NULL;
4791 commit = get_blame_commit(view, text);
4792 if (!commit)
4793 return NULL;
4795 *blamed += group;
4796 while (group--) {
4797 struct line *line = &view->line[lineno + group - 1];
4799 blame = line->data;
4800 blame->commit = commit;
4801 blame->lineno = orig_lineno + group - 1;
4802 line->dirty = 1;
4803 }
4805 return commit;
4806 }
4808 static bool
4809 blame_read_file(struct view *view, const char *line, bool *read_file)
4810 {
4811 if (!line) {
4812 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4813 struct io io = {};
4815 if (view->lines == 0 && !view->parent)
4816 die("No blame exist for %s", view->vid);
4818 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4819 report("Failed to load blame data");
4820 return TRUE;
4821 }
4823 io_done(view->pipe);
4824 view->io = io;
4825 *read_file = FALSE;
4826 return FALSE;
4828 } else {
4829 size_t linelen = strlen(line);
4830 struct blame *blame = malloc(sizeof(*blame) + linelen);
4832 if (!blame)
4833 return FALSE;
4835 blame->commit = NULL;
4836 strncpy(blame->text, line, linelen);
4837 blame->text[linelen] = 0;
4838 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4839 }
4840 }
4842 static bool
4843 match_blame_header(const char *name, char **line)
4844 {
4845 size_t namelen = strlen(name);
4846 bool matched = !strncmp(name, *line, namelen);
4848 if (matched)
4849 *line += namelen;
4851 return matched;
4852 }
4854 static bool
4855 blame_read(struct view *view, char *line)
4856 {
4857 static struct blame_commit *commit = NULL;
4858 static int blamed = 0;
4859 static bool read_file = TRUE;
4861 if (read_file)
4862 return blame_read_file(view, line, &read_file);
4864 if (!line) {
4865 /* Reset all! */
4866 commit = NULL;
4867 blamed = 0;
4868 read_file = TRUE;
4869 string_format(view->ref, "%s", view->vid);
4870 if (view_is_displayed(view)) {
4871 update_view_title(view);
4872 redraw_view_from(view, 0);
4873 }
4874 return TRUE;
4875 }
4877 if (!commit) {
4878 commit = parse_blame_commit(view, line, &blamed);
4879 string_format(view->ref, "%s %2d%%", view->vid,
4880 view->lines ? blamed * 100 / view->lines : 0);
4882 } else if (match_blame_header("author ", &line)) {
4883 commit->author = get_author(line);
4885 } else if (match_blame_header("author-time ", &line)) {
4886 parse_timesec(&commit->time, line);
4888 } else if (match_blame_header("author-tz ", &line)) {
4889 parse_timezone(&commit->time, line);
4891 } else if (match_blame_header("summary ", &line)) {
4892 string_ncopy(commit->title, line, strlen(line));
4894 } else if (match_blame_header("previous ", &line)) {
4895 commit->has_previous = TRUE;
4897 } else if (match_blame_header("filename ", &line)) {
4898 string_ncopy(commit->filename, line, strlen(line));
4899 commit = NULL;
4900 }
4902 return TRUE;
4903 }
4905 static bool
4906 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4907 {
4908 struct blame *blame = line->data;
4909 struct time *time = NULL;
4910 const char *id = NULL, *author = NULL;
4911 char text[SIZEOF_STR];
4913 if (blame->commit && *blame->commit->filename) {
4914 id = blame->commit->id;
4915 author = blame->commit->author;
4916 time = &blame->commit->time;
4917 }
4919 if (opt_date && draw_date(view, time))
4920 return TRUE;
4922 if (opt_author && draw_author(view, author))
4923 return TRUE;
4925 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4926 return TRUE;
4928 if (draw_lineno(view, lineno))
4929 return TRUE;
4931 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4932 draw_text(view, LINE_DEFAULT, text, TRUE);
4933 return TRUE;
4934 }
4936 static bool
4937 check_blame_commit(struct blame *blame, bool check_null_id)
4938 {
4939 if (!blame->commit)
4940 report("Commit data not loaded yet");
4941 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4942 report("No commit exist for the selected line");
4943 else
4944 return TRUE;
4945 return FALSE;
4946 }
4948 static void
4949 setup_blame_parent_line(struct view *view, struct blame *blame)
4950 {
4951 const char *diff_tree_argv[] = {
4952 "git", "diff-tree", "-U0", blame->commit->id,
4953 "--", blame->commit->filename, NULL
4954 };
4955 struct io io = {};
4956 int parent_lineno = -1;
4957 int blamed_lineno = -1;
4958 char *line;
4960 if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
4961 return;
4963 while ((line = io_get(&io, '\n', TRUE))) {
4964 if (*line == '@') {
4965 char *pos = strchr(line, '+');
4967 parent_lineno = atoi(line + 4);
4968 if (pos)
4969 blamed_lineno = atoi(pos + 1);
4971 } else if (*line == '+' && parent_lineno != -1) {
4972 if (blame->lineno == blamed_lineno - 1 &&
4973 !strcmp(blame->text, line + 1)) {
4974 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4975 break;
4976 }
4977 blamed_lineno++;
4978 }
4979 }
4981 io_done(&io);
4982 }
4984 static enum request
4985 blame_request(struct view *view, enum request request, struct line *line)
4986 {
4987 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4988 struct blame *blame = line->data;
4990 switch (request) {
4991 case REQ_VIEW_BLAME:
4992 if (check_blame_commit(blame, TRUE)) {
4993 string_copy(opt_ref, blame->commit->id);
4994 string_copy(opt_file, blame->commit->filename);
4995 if (blame->lineno)
4996 view->lineno = blame->lineno;
4997 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4998 }
4999 break;
5001 case REQ_PARENT:
5002 if (check_blame_commit(blame, TRUE) &&
5003 select_commit_parent(blame->commit->id, opt_ref,
5004 blame->commit->filename)) {
5005 string_copy(opt_file, blame->commit->filename);
5006 setup_blame_parent_line(view, blame);
5007 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5008 }
5009 break;
5011 case REQ_ENTER:
5012 if (!check_blame_commit(blame, FALSE))
5013 break;
5015 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5016 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5017 break;
5019 if (!strcmp(blame->commit->id, NULL_ID)) {
5020 struct view *diff = VIEW(REQ_VIEW_DIFF);
5021 const char *diff_index_argv[] = {
5022 "git", "diff-index", "--root", "--patch-with-stat",
5023 "-C", "-M", "HEAD", "--", view->vid, NULL
5024 };
5026 if (!blame->commit->has_previous) {
5027 diff_index_argv[1] = "diff";
5028 diff_index_argv[2] = "--no-color";
5029 diff_index_argv[6] = "--";
5030 diff_index_argv[7] = "/dev/null";
5031 }
5033 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
5034 report("Failed to allocate diff command");
5035 break;
5036 }
5037 flags |= OPEN_PREPARED;
5038 }
5040 open_view(view, REQ_VIEW_DIFF, flags);
5041 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5042 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5043 break;
5045 default:
5046 return request;
5047 }
5049 return REQ_NONE;
5050 }
5052 static bool
5053 blame_grep(struct view *view, struct line *line)
5054 {
5055 struct blame *blame = line->data;
5056 struct blame_commit *commit = blame->commit;
5057 const char *text[] = {
5058 blame->text,
5059 commit ? commit->title : "",
5060 commit ? commit->id : "",
5061 commit && opt_author ? commit->author : "",
5062 commit ? mkdate(&commit->time, opt_date) : "",
5063 NULL
5064 };
5066 return grep_text(view, text);
5067 }
5069 static void
5070 blame_select(struct view *view, struct line *line)
5071 {
5072 struct blame *blame = line->data;
5073 struct blame_commit *commit = blame->commit;
5075 if (!commit)
5076 return;
5078 if (!strcmp(commit->id, NULL_ID))
5079 string_ncopy(ref_commit, "HEAD", 4);
5080 else
5081 string_copy_rev(ref_commit, commit->id);
5082 }
5084 static struct view_ops blame_ops = {
5085 "line",
5086 NULL,
5087 blame_open,
5088 blame_read,
5089 blame_draw,
5090 blame_request,
5091 blame_grep,
5092 blame_select,
5093 };
5095 /*
5096 * Branch backend
5097 */
5099 struct branch {
5100 const char *author; /* Author of the last commit. */
5101 struct time time; /* Date of the last activity. */
5102 const struct ref *ref; /* Name and commit ID information. */
5103 };
5105 static const struct ref branch_all;
5107 static const enum sort_field branch_sort_fields[] = {
5108 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5109 };
5110 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5112 static int
5113 branch_compare(const void *l1, const void *l2)
5114 {
5115 const struct branch *branch1 = ((const struct line *) l1)->data;
5116 const struct branch *branch2 = ((const struct line *) l2)->data;
5118 switch (get_sort_field(branch_sort_state)) {
5119 case ORDERBY_DATE:
5120 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5122 case ORDERBY_AUTHOR:
5123 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5125 case ORDERBY_NAME:
5126 default:
5127 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5128 }
5129 }
5131 static bool
5132 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5133 {
5134 struct branch *branch = line->data;
5135 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5137 if (opt_date && draw_date(view, &branch->time))
5138 return TRUE;
5140 if (opt_author && draw_author(view, branch->author))
5141 return TRUE;
5143 draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5144 return TRUE;
5145 }
5147 static enum request
5148 branch_request(struct view *view, enum request request, struct line *line)
5149 {
5150 struct branch *branch = line->data;
5152 switch (request) {
5153 case REQ_REFRESH:
5154 load_refs();
5155 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5156 return REQ_NONE;
5158 case REQ_TOGGLE_SORT_FIELD:
5159 case REQ_TOGGLE_SORT_ORDER:
5160 sort_view(view, request, &branch_sort_state, branch_compare);
5161 return REQ_NONE;
5163 case REQ_ENTER:
5164 if (branch->ref == &branch_all) {
5165 const char *all_branches_argv[] = {
5166 "git", "log", "--no-color", "--pretty=raw", "--parents",
5167 "--topo-order", "--all", NULL
5168 };
5169 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5171 if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) {
5172 report("Failed to load view of all branches");
5173 return REQ_NONE;
5174 }
5175 open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5176 } else {
5177 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5178 }
5179 return REQ_NONE;
5181 default:
5182 return request;
5183 }
5184 }
5186 static bool
5187 branch_read(struct view *view, char *line)
5188 {
5189 static char id[SIZEOF_REV];
5190 struct branch *reference;
5191 size_t i;
5193 if (!line)
5194 return TRUE;
5196 switch (get_line_type(line)) {
5197 case LINE_COMMIT:
5198 string_copy_rev(id, line + STRING_SIZE("commit "));
5199 return TRUE;
5201 case LINE_AUTHOR:
5202 for (i = 0, reference = NULL; i < view->lines; i++) {
5203 struct branch *branch = view->line[i].data;
5205 if (strcmp(branch->ref->id, id))
5206 continue;
5208 view->line[i].dirty = TRUE;
5209 if (reference) {
5210 branch->author = reference->author;
5211 branch->time = reference->time;
5212 continue;
5213 }
5215 parse_author_line(line + STRING_SIZE("author "),
5216 &branch->author, &branch->time);
5217 reference = branch;
5218 }
5219 return TRUE;
5221 default:
5222 return TRUE;
5223 }
5225 }
5227 static bool
5228 branch_open_visitor(void *data, const struct ref *ref)
5229 {
5230 struct view *view = data;
5231 struct branch *branch;
5233 if (ref->tag || ref->ltag || ref->remote)
5234 return TRUE;
5236 branch = calloc(1, sizeof(*branch));
5237 if (!branch)
5238 return FALSE;
5240 branch->ref = ref;
5241 return !!add_line_data(view, branch, LINE_DEFAULT);
5242 }
5244 static bool
5245 branch_open(struct view *view)
5246 {
5247 const char *branch_log[] = {
5248 "git", "log", "--no-color", "--pretty=raw",
5249 "--simplify-by-decoration", "--all", NULL
5250 };
5252 if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5253 report("Failed to load branch data");
5254 return TRUE;
5255 }
5257 setup_update(view, view->id);
5258 branch_open_visitor(view, &branch_all);
5259 foreach_ref(branch_open_visitor, view);
5260 view->p_restore = TRUE;
5262 return TRUE;
5263 }
5265 static bool
5266 branch_grep(struct view *view, struct line *line)
5267 {
5268 struct branch *branch = line->data;
5269 const char *text[] = {
5270 branch->ref->name,
5271 branch->author,
5272 NULL
5273 };
5275 return grep_text(view, text);
5276 }
5278 static void
5279 branch_select(struct view *view, struct line *line)
5280 {
5281 struct branch *branch = line->data;
5283 string_copy_rev(view->ref, branch->ref->id);
5284 string_copy_rev(ref_commit, branch->ref->id);
5285 string_copy_rev(ref_head, branch->ref->id);
5286 }
5288 static struct view_ops branch_ops = {
5289 "branch",
5290 NULL,
5291 branch_open,
5292 branch_read,
5293 branch_draw,
5294 branch_request,
5295 branch_grep,
5296 branch_select,
5297 };
5299 /*
5300 * Status backend
5301 */
5303 struct status {
5304 char status;
5305 struct {
5306 mode_t mode;
5307 char rev[SIZEOF_REV];
5308 char name[SIZEOF_STR];
5309 } old;
5310 struct {
5311 mode_t mode;
5312 char rev[SIZEOF_REV];
5313 char name[SIZEOF_STR];
5314 } new;
5315 };
5317 static char status_onbranch[SIZEOF_STR];
5318 static struct status stage_status;
5319 static enum line_type stage_line_type;
5320 static size_t stage_chunks;
5321 static int *stage_chunk;
5323 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5325 /* This should work even for the "On branch" line. */
5326 static inline bool
5327 status_has_none(struct view *view, struct line *line)
5328 {
5329 return line < view->line + view->lines && !line[1].data;
5330 }
5332 /* Get fields from the diff line:
5333 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5334 */
5335 static inline bool
5336 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5337 {
5338 const char *old_mode = buf + 1;
5339 const char *new_mode = buf + 8;
5340 const char *old_rev = buf + 15;
5341 const char *new_rev = buf + 56;
5342 const char *status = buf + 97;
5344 if (bufsize < 98 ||
5345 old_mode[-1] != ':' ||
5346 new_mode[-1] != ' ' ||
5347 old_rev[-1] != ' ' ||
5348 new_rev[-1] != ' ' ||
5349 status[-1] != ' ')
5350 return FALSE;
5352 file->status = *status;
5354 string_copy_rev(file->old.rev, old_rev);
5355 string_copy_rev(file->new.rev, new_rev);
5357 file->old.mode = strtoul(old_mode, NULL, 8);
5358 file->new.mode = strtoul(new_mode, NULL, 8);
5360 file->old.name[0] = file->new.name[0] = 0;
5362 return TRUE;
5363 }
5365 static bool
5366 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5367 {
5368 struct status *unmerged = NULL;
5369 char *buf;
5370 struct io io = {};
5372 if (!io_run(&io, argv, opt_cdup, IO_RD))
5373 return FALSE;
5375 add_line_data(view, NULL, type);
5377 while ((buf = io_get(&io, 0, TRUE))) {
5378 struct status *file = unmerged;
5380 if (!file) {
5381 file = calloc(1, sizeof(*file));
5382 if (!file || !add_line_data(view, file, type))
5383 goto error_out;
5384 }
5386 /* Parse diff info part. */
5387 if (status) {
5388 file->status = status;
5389 if (status == 'A')
5390 string_copy(file->old.rev, NULL_ID);
5392 } else if (!file->status || file == unmerged) {
5393 if (!status_get_diff(file, buf, strlen(buf)))
5394 goto error_out;
5396 buf = io_get(&io, 0, TRUE);
5397 if (!buf)
5398 break;
5400 /* Collapse all modified entries that follow an
5401 * associated unmerged entry. */
5402 if (unmerged == file) {
5403 unmerged->status = 'U';
5404 unmerged = NULL;
5405 } else if (file->status == 'U') {
5406 unmerged = file;
5407 }
5408 }
5410 /* Grab the old name for rename/copy. */
5411 if (!*file->old.name &&
5412 (file->status == 'R' || file->status == 'C')) {
5413 string_ncopy(file->old.name, buf, strlen(buf));
5415 buf = io_get(&io, 0, TRUE);
5416 if (!buf)
5417 break;
5418 }
5420 /* git-ls-files just delivers a NUL separated list of
5421 * file names similar to the second half of the
5422 * git-diff-* output. */
5423 string_ncopy(file->new.name, buf, strlen(buf));
5424 if (!*file->old.name)
5425 string_copy(file->old.name, file->new.name);
5426 file = NULL;
5427 }
5429 if (io_error(&io)) {
5430 error_out:
5431 io_done(&io);
5432 return FALSE;
5433 }
5435 if (!view->line[view->lines - 1].data)
5436 add_line_data(view, NULL, LINE_STAT_NONE);
5438 io_done(&io);
5439 return TRUE;
5440 }
5442 /* Don't show unmerged entries in the staged section. */
5443 static const char *status_diff_index_argv[] = {
5444 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5445 "--cached", "-M", "HEAD", NULL
5446 };
5448 static const char *status_diff_files_argv[] = {
5449 "git", "diff-files", "-z", NULL
5450 };
5452 static const char *status_list_other_argv[] = {
5453 "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5454 };
5456 static const char *status_list_no_head_argv[] = {
5457 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5458 };
5460 static const char *update_index_argv[] = {
5461 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5462 };
5464 /* Restore the previous line number to stay in the context or select a
5465 * line with something that can be updated. */
5466 static void
5467 status_restore(struct view *view)
5468 {
5469 if (view->p_lineno >= view->lines)
5470 view->p_lineno = view->lines - 1;
5471 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5472 view->p_lineno++;
5473 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5474 view->p_lineno--;
5476 /* If the above fails, always skip the "On branch" line. */
5477 if (view->p_lineno < view->lines)
5478 view->lineno = view->p_lineno;
5479 else
5480 view->lineno = 1;
5482 if (view->lineno < view->offset)
5483 view->offset = view->lineno;
5484 else if (view->offset + view->height <= view->lineno)
5485 view->offset = view->lineno - view->height + 1;
5487 view->p_restore = FALSE;
5488 }
5490 static void
5491 status_update_onbranch(void)
5492 {
5493 static const char *paths[][2] = {
5494 { "rebase-apply/rebasing", "Rebasing" },
5495 { "rebase-apply/applying", "Applying mailbox" },
5496 { "rebase-apply/", "Rebasing mailbox" },
5497 { "rebase-merge/interactive", "Interactive rebase" },
5498 { "rebase-merge/", "Rebase merge" },
5499 { "MERGE_HEAD", "Merging" },
5500 { "BISECT_LOG", "Bisecting" },
5501 { "HEAD", "On branch" },
5502 };
5503 char buf[SIZEOF_STR];
5504 struct stat stat;
5505 int i;
5507 if (is_initial_commit()) {
5508 string_copy(status_onbranch, "Initial commit");
5509 return;
5510 }
5512 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5513 char *head = opt_head;
5515 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5516 lstat(buf, &stat) < 0)
5517 continue;
5519 if (!*opt_head) {
5520 struct io io = {};
5522 if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5523 io_read_buf(&io, buf, sizeof(buf))) {
5524 head = buf;
5525 if (!prefixcmp(head, "refs/heads/"))
5526 head += STRING_SIZE("refs/heads/");
5527 }
5528 }
5530 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5531 string_copy(status_onbranch, opt_head);
5532 return;
5533 }
5535 string_copy(status_onbranch, "Not currently on any branch");
5536 }
5538 /* First parse staged info using git-diff-index(1), then parse unstaged
5539 * info using git-diff-files(1), and finally untracked files using
5540 * git-ls-files(1). */
5541 static bool
5542 status_open(struct view *view)
5543 {
5544 reset_view(view);
5546 add_line_data(view, NULL, LINE_STAT_HEAD);
5547 status_update_onbranch();
5549 io_run_bg(update_index_argv);
5551 if (is_initial_commit()) {
5552 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5553 return FALSE;
5554 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5555 return FALSE;
5556 }
5558 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5559 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5560 return FALSE;
5562 /* Restore the exact position or use the specialized restore
5563 * mode? */
5564 if (!view->p_restore)
5565 status_restore(view);
5566 return TRUE;
5567 }
5569 static bool
5570 status_draw(struct view *view, struct line *line, unsigned int lineno)
5571 {
5572 struct status *status = line->data;
5573 enum line_type type;
5574 const char *text;
5576 if (!status) {
5577 switch (line->type) {
5578 case LINE_STAT_STAGED:
5579 type = LINE_STAT_SECTION;
5580 text = "Changes to be committed:";
5581 break;
5583 case LINE_STAT_UNSTAGED:
5584 type = LINE_STAT_SECTION;
5585 text = "Changed but not updated:";
5586 break;
5588 case LINE_STAT_UNTRACKED:
5589 type = LINE_STAT_SECTION;
5590 text = "Untracked files:";
5591 break;
5593 case LINE_STAT_NONE:
5594 type = LINE_DEFAULT;
5595 text = " (no files)";
5596 break;
5598 case LINE_STAT_HEAD:
5599 type = LINE_STAT_HEAD;
5600 text = status_onbranch;
5601 break;
5603 default:
5604 return FALSE;
5605 }
5606 } else {
5607 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5609 buf[0] = status->status;
5610 if (draw_text(view, line->type, buf, TRUE))
5611 return TRUE;
5612 type = LINE_DEFAULT;
5613 text = status->new.name;
5614 }
5616 draw_text(view, type, text, TRUE);
5617 return TRUE;
5618 }
5620 static enum request
5621 status_load_error(struct view *view, struct view *stage, const char *path)
5622 {
5623 if (displayed_views() == 2 || display[current_view] != view)
5624 maximize_view(view);
5625 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5626 return REQ_NONE;
5627 }
5629 static enum request
5630 status_enter(struct view *view, struct line *line)
5631 {
5632 struct status *status = line->data;
5633 const char *oldpath = status ? status->old.name : NULL;
5634 /* Diffs for unmerged entries are empty when passing the new
5635 * path, so leave it empty. */
5636 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5637 const char *info;
5638 enum open_flags split;
5639 struct view *stage = VIEW(REQ_VIEW_STAGE);
5641 if (line->type == LINE_STAT_NONE ||
5642 (!status && line[1].type == LINE_STAT_NONE)) {
5643 report("No file to diff");
5644 return REQ_NONE;
5645 }
5647 switch (line->type) {
5648 case LINE_STAT_STAGED:
5649 if (is_initial_commit()) {
5650 const char *no_head_diff_argv[] = {
5651 "git", "diff", "--no-color", "--patch-with-stat",
5652 "--", "/dev/null", newpath, NULL
5653 };
5655 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5656 return status_load_error(view, stage, newpath);
5657 } else {
5658 const char *index_show_argv[] = {
5659 "git", "diff-index", "--root", "--patch-with-stat",
5660 "-C", "-M", "--cached", "HEAD", "--",
5661 oldpath, newpath, NULL
5662 };
5664 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5665 return status_load_error(view, stage, newpath);
5666 }
5668 if (status)
5669 info = "Staged changes to %s";
5670 else
5671 info = "Staged changes";
5672 break;
5674 case LINE_STAT_UNSTAGED:
5675 {
5676 const char *files_show_argv[] = {
5677 "git", "diff-files", "--root", "--patch-with-stat",
5678 "-C", "-M", "--", oldpath, newpath, NULL
5679 };
5681 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5682 return status_load_error(view, stage, newpath);
5683 if (status)
5684 info = "Unstaged changes to %s";
5685 else
5686 info = "Unstaged changes";
5687 break;
5688 }
5689 case LINE_STAT_UNTRACKED:
5690 if (!newpath) {
5691 report("No file to show");
5692 return REQ_NONE;
5693 }
5695 if (!suffixcmp(status->new.name, -1, "/")) {
5696 report("Cannot display a directory");
5697 return REQ_NONE;
5698 }
5700 if (!prepare_update_file(stage, newpath))
5701 return status_load_error(view, stage, newpath);
5702 info = "Untracked file %s";
5703 break;
5705 case LINE_STAT_HEAD:
5706 return REQ_NONE;
5708 default:
5709 die("line type %d not handled in switch", line->type);
5710 }
5712 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5713 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5714 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5715 if (status) {
5716 stage_status = *status;
5717 } else {
5718 memset(&stage_status, 0, sizeof(stage_status));
5719 }
5721 stage_line_type = line->type;
5722 stage_chunks = 0;
5723 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5724 }
5726 return REQ_NONE;
5727 }
5729 static bool
5730 status_exists(struct status *status, enum line_type type)
5731 {
5732 struct view *view = VIEW(REQ_VIEW_STATUS);
5733 unsigned long lineno;
5735 for (lineno = 0; lineno < view->lines; lineno++) {
5736 struct line *line = &view->line[lineno];
5737 struct status *pos = line->data;
5739 if (line->type != type)
5740 continue;
5741 if (!pos && (!status || !status->status) && line[1].data) {
5742 select_view_line(view, lineno);
5743 return TRUE;
5744 }
5745 if (pos && !strcmp(status->new.name, pos->new.name)) {
5746 select_view_line(view, lineno);
5747 return TRUE;
5748 }
5749 }
5751 return FALSE;
5752 }
5755 static bool
5756 status_update_prepare(struct io *io, enum line_type type)
5757 {
5758 const char *staged_argv[] = {
5759 "git", "update-index", "-z", "--index-info", NULL
5760 };
5761 const char *others_argv[] = {
5762 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5763 };
5765 switch (type) {
5766 case LINE_STAT_STAGED:
5767 return io_run(io, staged_argv, opt_cdup, IO_WR);
5769 case LINE_STAT_UNSTAGED:
5770 case LINE_STAT_UNTRACKED:
5771 return io_run(io, others_argv, opt_cdup, IO_WR);
5773 default:
5774 die("line type %d not handled in switch", type);
5775 return FALSE;
5776 }
5777 }
5779 static bool
5780 status_update_write(struct io *io, struct status *status, enum line_type type)
5781 {
5782 char buf[SIZEOF_STR];
5783 size_t bufsize = 0;
5785 switch (type) {
5786 case LINE_STAT_STAGED:
5787 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5788 status->old.mode,
5789 status->old.rev,
5790 status->old.name, 0))
5791 return FALSE;
5792 break;
5794 case LINE_STAT_UNSTAGED:
5795 case LINE_STAT_UNTRACKED:
5796 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5797 return FALSE;
5798 break;
5800 default:
5801 die("line type %d not handled in switch", type);
5802 }
5804 return io_write(io, buf, bufsize);
5805 }
5807 static bool
5808 status_update_file(struct status *status, enum line_type type)
5809 {
5810 struct io io = {};
5811 bool result;
5813 if (!status_update_prepare(&io, type))
5814 return FALSE;
5816 result = status_update_write(&io, status, type);
5817 return io_done(&io) && result;
5818 }
5820 static bool
5821 status_update_files(struct view *view, struct line *line)
5822 {
5823 char buf[sizeof(view->ref)];
5824 struct io io = {};
5825 bool result = TRUE;
5826 struct line *pos = view->line + view->lines;
5827 int files = 0;
5828 int file, done;
5829 int cursor_y = -1, cursor_x = -1;
5831 if (!status_update_prepare(&io, line->type))
5832 return FALSE;
5834 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5835 files++;
5837 string_copy(buf, view->ref);
5838 getsyx(cursor_y, cursor_x);
5839 for (file = 0, done = 5; result && file < files; line++, file++) {
5840 int almost_done = file * 100 / files;
5842 if (almost_done > done) {
5843 done = almost_done;
5844 string_format(view->ref, "updating file %u of %u (%d%% done)",
5845 file, files, done);
5846 update_view_title(view);
5847 setsyx(cursor_y, cursor_x);
5848 doupdate();
5849 }
5850 result = status_update_write(&io, line->data, line->type);
5851 }
5852 string_copy(view->ref, buf);
5854 return io_done(&io) && result;
5855 }
5857 static bool
5858 status_update(struct view *view)
5859 {
5860 struct line *line = &view->line[view->lineno];
5862 assert(view->lines);
5864 if (!line->data) {
5865 /* This should work even for the "On branch" line. */
5866 if (line < view->line + view->lines && !line[1].data) {
5867 report("Nothing to update");
5868 return FALSE;
5869 }
5871 if (!status_update_files(view, line + 1)) {
5872 report("Failed to update file status");
5873 return FALSE;
5874 }
5876 } else if (!status_update_file(line->data, line->type)) {
5877 report("Failed to update file status");
5878 return FALSE;
5879 }
5881 return TRUE;
5882 }
5884 static bool
5885 status_revert(struct status *status, enum line_type type, bool has_none)
5886 {
5887 if (!status || type != LINE_STAT_UNSTAGED) {
5888 if (type == LINE_STAT_STAGED) {
5889 report("Cannot revert changes to staged files");
5890 } else if (type == LINE_STAT_UNTRACKED) {
5891 report("Cannot revert changes to untracked files");
5892 } else if (has_none) {
5893 report("Nothing to revert");
5894 } else {
5895 report("Cannot revert changes to multiple files");
5896 }
5898 } else if (prompt_yesno("Are you sure you want to revert changes?")) {
5899 char mode[10] = "100644";
5900 const char *reset_argv[] = {
5901 "git", "update-index", "--cacheinfo", mode,
5902 status->old.rev, status->old.name, NULL
5903 };
5904 const char *checkout_argv[] = {
5905 "git", "checkout", "--", status->old.name, NULL
5906 };
5908 if (status->status == 'U') {
5909 string_format(mode, "%5o", status->old.mode);
5911 if (status->old.mode == 0 && status->new.mode == 0) {
5912 reset_argv[2] = "--force-remove";
5913 reset_argv[3] = status->old.name;
5914 reset_argv[4] = NULL;
5915 }
5917 if (!io_run_fg(reset_argv, opt_cdup))
5918 return FALSE;
5919 if (status->old.mode == 0 && status->new.mode == 0)
5920 return TRUE;
5921 }
5923 return io_run_fg(checkout_argv, opt_cdup);
5924 }
5926 return FALSE;
5927 }
5929 static enum request
5930 status_request(struct view *view, enum request request, struct line *line)
5931 {
5932 struct status *status = line->data;
5934 switch (request) {
5935 case REQ_STATUS_UPDATE:
5936 if (!status_update(view))
5937 return REQ_NONE;
5938 break;
5940 case REQ_STATUS_REVERT:
5941 if (!status_revert(status, line->type, status_has_none(view, line)))
5942 return REQ_NONE;
5943 break;
5945 case REQ_STATUS_MERGE:
5946 if (!status || status->status != 'U') {
5947 report("Merging only possible for files with unmerged status ('U').");
5948 return REQ_NONE;
5949 }
5950 open_mergetool(status->new.name);
5951 break;
5953 case REQ_EDIT:
5954 if (!status)
5955 return request;
5956 if (status->status == 'D') {
5957 report("File has been deleted.");
5958 return REQ_NONE;
5959 }
5961 open_editor(status->new.name);
5962 break;
5964 case REQ_VIEW_BLAME:
5965 if (status)
5966 opt_ref[0] = 0;
5967 return request;
5969 case REQ_ENTER:
5970 /* After returning the status view has been split to
5971 * show the stage view. No further reloading is
5972 * necessary. */
5973 return status_enter(view, line);
5975 case REQ_REFRESH:
5976 /* Simply reload the view. */
5977 break;
5979 default:
5980 return request;
5981 }
5983 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5985 return REQ_NONE;
5986 }
5988 static void
5989 status_select(struct view *view, struct line *line)
5990 {
5991 struct status *status = line->data;
5992 char file[SIZEOF_STR] = "all files";
5993 const char *text;
5994 const char *key;
5996 if (status && !string_format(file, "'%s'", status->new.name))
5997 return;
5999 if (!status && line[1].type == LINE_STAT_NONE)
6000 line++;
6002 switch (line->type) {
6003 case LINE_STAT_STAGED:
6004 text = "Press %s to unstage %s for commit";
6005 break;
6007 case LINE_STAT_UNSTAGED:
6008 text = "Press %s to stage %s for commit";
6009 break;
6011 case LINE_STAT_UNTRACKED:
6012 text = "Press %s to stage %s for addition";
6013 break;
6015 case LINE_STAT_HEAD:
6016 case LINE_STAT_NONE:
6017 text = "Nothing to update";
6018 break;
6020 default:
6021 die("line type %d not handled in switch", line->type);
6022 }
6024 if (status && status->status == 'U') {
6025 text = "Press %s to resolve conflict in %s";
6026 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6028 } else {
6029 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6030 }
6032 string_format(view->ref, text, key, file);
6033 if (status)
6034 string_copy(opt_file, status->new.name);
6035 }
6037 static bool
6038 status_grep(struct view *view, struct line *line)
6039 {
6040 struct status *status = line->data;
6042 if (status) {
6043 const char buf[2] = { status->status, 0 };
6044 const char *text[] = { status->new.name, buf, NULL };
6046 return grep_text(view, text);
6047 }
6049 return FALSE;
6050 }
6052 static struct view_ops status_ops = {
6053 "file",
6054 NULL,
6055 status_open,
6056 NULL,
6057 status_draw,
6058 status_request,
6059 status_grep,
6060 status_select,
6061 };
6064 static bool
6065 stage_diff_write(struct io *io, struct line *line, struct line *end)
6066 {
6067 while (line < end) {
6068 if (!io_write(io, line->data, strlen(line->data)) ||
6069 !io_write(io, "\n", 1))
6070 return FALSE;
6071 line++;
6072 if (line->type == LINE_DIFF_CHUNK ||
6073 line->type == LINE_DIFF_HEADER)
6074 break;
6075 }
6077 return TRUE;
6078 }
6080 static struct line *
6081 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6082 {
6083 for (; view->line < line; line--)
6084 if (line->type == type)
6085 return line;
6087 return NULL;
6088 }
6090 static bool
6091 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6092 {
6093 const char *apply_argv[SIZEOF_ARG] = {
6094 "git", "apply", "--whitespace=nowarn", NULL
6095 };
6096 struct line *diff_hdr;
6097 struct io io = {};
6098 int argc = 3;
6100 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6101 if (!diff_hdr)
6102 return FALSE;
6104 if (!revert)
6105 apply_argv[argc++] = "--cached";
6106 if (revert || stage_line_type == LINE_STAT_STAGED)
6107 apply_argv[argc++] = "-R";
6108 apply_argv[argc++] = "-";
6109 apply_argv[argc++] = NULL;
6110 if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6111 return FALSE;
6113 if (!stage_diff_write(&io, diff_hdr, chunk) ||
6114 !stage_diff_write(&io, chunk, view->line + view->lines))
6115 chunk = NULL;
6117 io_done(&io);
6118 io_run_bg(update_index_argv);
6120 return chunk ? TRUE : FALSE;
6121 }
6123 static bool
6124 stage_update(struct view *view, struct line *line)
6125 {
6126 struct line *chunk = NULL;
6128 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6129 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6131 if (chunk) {
6132 if (!stage_apply_chunk(view, chunk, FALSE)) {
6133 report("Failed to apply chunk");
6134 return FALSE;
6135 }
6137 } else if (!stage_status.status) {
6138 view = VIEW(REQ_VIEW_STATUS);
6140 for (line = view->line; line < view->line + view->lines; line++)
6141 if (line->type == stage_line_type)
6142 break;
6144 if (!status_update_files(view, line + 1)) {
6145 report("Failed to update files");
6146 return FALSE;
6147 }
6149 } else if (!status_update_file(&stage_status, stage_line_type)) {
6150 report("Failed to update file");
6151 return FALSE;
6152 }
6154 return TRUE;
6155 }
6157 static bool
6158 stage_revert(struct view *view, struct line *line)
6159 {
6160 struct line *chunk = NULL;
6162 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6163 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6165 if (chunk) {
6166 if (!prompt_yesno("Are you sure you want to revert changes?"))
6167 return FALSE;
6169 if (!stage_apply_chunk(view, chunk, TRUE)) {
6170 report("Failed to revert chunk");
6171 return FALSE;
6172 }
6173 return TRUE;
6175 } else {
6176 return status_revert(stage_status.status ? &stage_status : NULL,
6177 stage_line_type, FALSE);
6178 }
6179 }
6182 static void
6183 stage_next(struct view *view, struct line *line)
6184 {
6185 int i;
6187 if (!stage_chunks) {
6188 for (line = view->line; line < view->line + view->lines; line++) {
6189 if (line->type != LINE_DIFF_CHUNK)
6190 continue;
6192 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6193 report("Allocation failure");
6194 return;
6195 }
6197 stage_chunk[stage_chunks++] = line - view->line;
6198 }
6199 }
6201 for (i = 0; i < stage_chunks; i++) {
6202 if (stage_chunk[i] > view->lineno) {
6203 do_scroll_view(view, stage_chunk[i] - view->lineno);
6204 report("Chunk %d of %d", i + 1, stage_chunks);
6205 return;
6206 }
6207 }
6209 report("No next chunk found");
6210 }
6212 static enum request
6213 stage_request(struct view *view, enum request request, struct line *line)
6214 {
6215 switch (request) {
6216 case REQ_STATUS_UPDATE:
6217 if (!stage_update(view, line))
6218 return REQ_NONE;
6219 break;
6221 case REQ_STATUS_REVERT:
6222 if (!stage_revert(view, line))
6223 return REQ_NONE;
6224 break;
6226 case REQ_STAGE_NEXT:
6227 if (stage_line_type == LINE_STAT_UNTRACKED) {
6228 report("File is untracked; press %s to add",
6229 get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6230 return REQ_NONE;
6231 }
6232 stage_next(view, line);
6233 return REQ_NONE;
6235 case REQ_EDIT:
6236 if (!stage_status.new.name[0])
6237 return request;
6238 if (stage_status.status == 'D') {
6239 report("File has been deleted.");
6240 return REQ_NONE;
6241 }
6243 open_editor(stage_status.new.name);
6244 break;
6246 case REQ_REFRESH:
6247 /* Reload everything ... */
6248 break;
6250 case REQ_VIEW_BLAME:
6251 if (stage_status.new.name[0]) {
6252 string_copy(opt_file, stage_status.new.name);
6253 opt_ref[0] = 0;
6254 }
6255 return request;
6257 case REQ_ENTER:
6258 return pager_request(view, request, line);
6260 default:
6261 return request;
6262 }
6264 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6265 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6267 /* Check whether the staged entry still exists, and close the
6268 * stage view if it doesn't. */
6269 if (!status_exists(&stage_status, stage_line_type)) {
6270 status_restore(VIEW(REQ_VIEW_STATUS));
6271 return REQ_VIEW_CLOSE;
6272 }
6274 if (stage_line_type == LINE_STAT_UNTRACKED) {
6275 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6276 report("Cannot display a directory");
6277 return REQ_NONE;
6278 }
6280 if (!prepare_update_file(view, stage_status.new.name)) {
6281 report("Failed to open file: %s", strerror(errno));
6282 return REQ_NONE;
6283 }
6284 }
6285 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6287 return REQ_NONE;
6288 }
6290 static struct view_ops stage_ops = {
6291 "line",
6292 NULL,
6293 NULL,
6294 pager_read,
6295 pager_draw,
6296 stage_request,
6297 pager_grep,
6298 pager_select,
6299 };
6302 /*
6303 * Revision graph
6304 */
6306 struct commit {
6307 char id[SIZEOF_REV]; /* SHA1 ID. */
6308 char title[128]; /* First line of the commit message. */
6309 const char *author; /* Author of the commit. */
6310 struct time time; /* Date from the author ident. */
6311 struct ref_list *refs; /* Repository references. */
6312 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
6313 size_t graph_size; /* The width of the graph array. */
6314 bool has_parents; /* Rewritten --parents seen. */
6315 };
6317 /* Size of rev graph with no "padding" columns */
6318 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6320 struct rev_graph {
6321 struct rev_graph *prev, *next, *parents;
6322 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6323 size_t size;
6324 struct commit *commit;
6325 size_t pos;
6326 unsigned int boundary:1;
6327 };
6329 /* Parents of the commit being visualized. */
6330 static struct rev_graph graph_parents[4];
6332 /* The current stack of revisions on the graph. */
6333 static struct rev_graph graph_stacks[4] = {
6334 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6335 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6336 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6337 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6338 };
6340 static inline bool
6341 graph_parent_is_merge(struct rev_graph *graph)
6342 {
6343 return graph->parents->size > 1;
6344 }
6346 static inline void
6347 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6348 {
6349 struct commit *commit = graph->commit;
6351 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6352 commit->graph[commit->graph_size++] = symbol;
6353 }
6355 static void
6356 clear_rev_graph(struct rev_graph *graph)
6357 {
6358 graph->boundary = 0;
6359 graph->size = graph->pos = 0;
6360 graph->commit = NULL;
6361 memset(graph->parents, 0, sizeof(*graph->parents));
6362 }
6364 static void
6365 done_rev_graph(struct rev_graph *graph)
6366 {
6367 if (graph_parent_is_merge(graph) &&
6368 graph->pos < graph->size - 1 &&
6369 graph->next->size == graph->size + graph->parents->size - 1) {
6370 size_t i = graph->pos + graph->parents->size - 1;
6372 graph->commit->graph_size = i * 2;
6373 while (i < graph->next->size - 1) {
6374 append_to_rev_graph(graph, ' ');
6375 append_to_rev_graph(graph, '\\');
6376 i++;
6377 }
6378 }
6380 clear_rev_graph(graph);
6381 }
6383 static void
6384 push_rev_graph(struct rev_graph *graph, const char *parent)
6385 {
6386 int i;
6388 /* "Collapse" duplicate parents lines.
6389 *
6390 * FIXME: This needs to also update update the drawn graph but
6391 * for now it just serves as a method for pruning graph lines. */
6392 for (i = 0; i < graph->size; i++)
6393 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6394 return;
6396 if (graph->size < SIZEOF_REVITEMS) {
6397 string_copy_rev(graph->rev[graph->size++], parent);
6398 }
6399 }
6401 static chtype
6402 get_rev_graph_symbol(struct rev_graph *graph)
6403 {
6404 chtype symbol;
6406 if (graph->boundary)
6407 symbol = REVGRAPH_BOUND;
6408 else if (graph->parents->size == 0)
6409 symbol = REVGRAPH_INIT;
6410 else if (graph_parent_is_merge(graph))
6411 symbol = REVGRAPH_MERGE;
6412 else if (graph->pos >= graph->size)
6413 symbol = REVGRAPH_BRANCH;
6414 else
6415 symbol = REVGRAPH_COMMIT;
6417 return symbol;
6418 }
6420 static void
6421 draw_rev_graph(struct rev_graph *graph)
6422 {
6423 struct rev_filler {
6424 chtype separator, line;
6425 };
6426 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6427 static struct rev_filler fillers[] = {
6428 { ' ', '|' },
6429 { '`', '.' },
6430 { '\'', ' ' },
6431 { '/', ' ' },
6432 };
6433 chtype symbol = get_rev_graph_symbol(graph);
6434 struct rev_filler *filler;
6435 size_t i;
6437 fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6438 filler = &fillers[DEFAULT];
6440 for (i = 0; i < graph->pos; i++) {
6441 append_to_rev_graph(graph, filler->line);
6442 if (graph_parent_is_merge(graph->prev) &&
6443 graph->prev->pos == i)
6444 filler = &fillers[RSHARP];
6446 append_to_rev_graph(graph, filler->separator);
6447 }
6449 /* Place the symbol for this revision. */
6450 append_to_rev_graph(graph, symbol);
6452 if (graph->prev->size > graph->size)
6453 filler = &fillers[RDIAG];
6454 else
6455 filler = &fillers[DEFAULT];
6457 i++;
6459 for (; i < graph->size; i++) {
6460 append_to_rev_graph(graph, filler->separator);
6461 append_to_rev_graph(graph, filler->line);
6462 if (graph_parent_is_merge(graph->prev) &&
6463 i < graph->prev->pos + graph->parents->size)
6464 filler = &fillers[RSHARP];
6465 if (graph->prev->size > graph->size)
6466 filler = &fillers[LDIAG];
6467 }
6469 if (graph->prev->size > graph->size) {
6470 append_to_rev_graph(graph, filler->separator);
6471 if (filler->line != ' ')
6472 append_to_rev_graph(graph, filler->line);
6473 }
6474 }
6476 /* Prepare the next rev graph */
6477 static void
6478 prepare_rev_graph(struct rev_graph *graph)
6479 {
6480 size_t i;
6482 /* First, traverse all lines of revisions up to the active one. */
6483 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6484 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6485 break;
6487 push_rev_graph(graph->next, graph->rev[graph->pos]);
6488 }
6490 /* Interleave the new revision parent(s). */
6491 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6492 push_rev_graph(graph->next, graph->parents->rev[i]);
6494 /* Lastly, put any remaining revisions. */
6495 for (i = graph->pos + 1; i < graph->size; i++)
6496 push_rev_graph(graph->next, graph->rev[i]);
6497 }
6499 static void
6500 update_rev_graph(struct view *view, struct rev_graph *graph)
6501 {
6502 /* If this is the finalizing update ... */
6503 if (graph->commit)
6504 prepare_rev_graph(graph);
6506 /* Graph visualization needs a one rev look-ahead,
6507 * so the first update doesn't visualize anything. */
6508 if (!graph->prev->commit)
6509 return;
6511 if (view->lines > 2)
6512 view->line[view->lines - 3].dirty = 1;
6513 if (view->lines > 1)
6514 view->line[view->lines - 2].dirty = 1;
6515 draw_rev_graph(graph->prev);
6516 done_rev_graph(graph->prev->prev);
6517 }
6520 /*
6521 * Main view backend
6522 */
6524 static const char *main_argv[SIZEOF_ARG] = {
6525 "git", "log", "--no-color", "--pretty=raw", "--parents",
6526 "--topo-order", "%(head)", NULL
6527 };
6529 static bool
6530 main_draw(struct view *view, struct line *line, unsigned int lineno)
6531 {
6532 struct commit *commit = line->data;
6534 if (!commit->author)
6535 return FALSE;
6537 if (opt_date && draw_date(view, &commit->time))
6538 return TRUE;
6540 if (opt_author && draw_author(view, commit->author))
6541 return TRUE;
6543 if (opt_rev_graph && commit->graph_size &&
6544 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6545 return TRUE;
6547 if (opt_show_refs && commit->refs) {
6548 size_t i;
6550 for (i = 0; i < commit->refs->size; i++) {
6551 struct ref *ref = commit->refs->refs[i];
6552 enum line_type type;
6554 if (ref->head)
6555 type = LINE_MAIN_HEAD;
6556 else if (ref->ltag)
6557 type = LINE_MAIN_LOCAL_TAG;
6558 else if (ref->tag)
6559 type = LINE_MAIN_TAG;
6560 else if (ref->tracked)
6561 type = LINE_MAIN_TRACKED;
6562 else if (ref->remote)
6563 type = LINE_MAIN_REMOTE;
6564 else
6565 type = LINE_MAIN_REF;
6567 if (draw_text(view, type, "[", TRUE) ||
6568 draw_text(view, type, ref->name, TRUE) ||
6569 draw_text(view, type, "]", TRUE))
6570 return TRUE;
6572 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6573 return TRUE;
6574 }
6575 }
6577 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6578 return TRUE;
6579 }
6581 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6582 static bool
6583 main_read(struct view *view, char *line)
6584 {
6585 static struct rev_graph *graph = graph_stacks;
6586 enum line_type type;
6587 struct commit *commit;
6589 if (!line) {
6590 int i;
6592 if (!view->lines && !view->parent)
6593 die("No revisions match the given arguments.");
6594 if (view->lines > 0) {
6595 commit = view->line[view->lines - 1].data;
6596 view->line[view->lines - 1].dirty = 1;
6597 if (!commit->author) {
6598 view->lines--;
6599 free(commit);
6600 graph->commit = NULL;
6601 }
6602 }
6603 update_rev_graph(view, graph);
6605 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6606 clear_rev_graph(&graph_stacks[i]);
6607 return TRUE;
6608 }
6610 type = get_line_type(line);
6611 if (type == LINE_COMMIT) {
6612 commit = calloc(1, sizeof(struct commit));
6613 if (!commit)
6614 return FALSE;
6616 line += STRING_SIZE("commit ");
6617 if (*line == '-') {
6618 graph->boundary = 1;
6619 line++;
6620 }
6622 string_copy_rev(commit->id, line);
6623 commit->refs = get_ref_list(commit->id);
6624 graph->commit = commit;
6625 add_line_data(view, commit, LINE_MAIN_COMMIT);
6627 while ((line = strchr(line, ' '))) {
6628 line++;
6629 push_rev_graph(graph->parents, line);
6630 commit->has_parents = TRUE;
6631 }
6632 return TRUE;
6633 }
6635 if (!view->lines)
6636 return TRUE;
6637 commit = view->line[view->lines - 1].data;
6639 switch (type) {
6640 case LINE_PARENT:
6641 if (commit->has_parents)
6642 break;
6643 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6644 break;
6646 case LINE_AUTHOR:
6647 parse_author_line(line + STRING_SIZE("author "),
6648 &commit->author, &commit->time);
6649 update_rev_graph(view, graph);
6650 graph = graph->next;
6651 break;
6653 default:
6654 /* Fill in the commit title if it has not already been set. */
6655 if (commit->title[0])
6656 break;
6658 /* Require titles to start with a non-space character at the
6659 * offset used by git log. */
6660 if (strncmp(line, " ", 4))
6661 break;
6662 line += 4;
6663 /* Well, if the title starts with a whitespace character,
6664 * try to be forgiving. Otherwise we end up with no title. */
6665 while (isspace(*line))
6666 line++;
6667 if (*line == '\0')
6668 break;
6669 /* FIXME: More graceful handling of titles; append "..." to
6670 * shortened titles, etc. */
6672 string_expand(commit->title, sizeof(commit->title), line, 1);
6673 view->line[view->lines - 1].dirty = 1;
6674 }
6676 return TRUE;
6677 }
6679 static enum request
6680 main_request(struct view *view, enum request request, struct line *line)
6681 {
6682 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6684 switch (request) {
6685 case REQ_ENTER:
6686 open_view(view, REQ_VIEW_DIFF, flags);
6687 break;
6688 case REQ_REFRESH:
6689 load_refs();
6690 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6691 break;
6692 default:
6693 return request;
6694 }
6696 return REQ_NONE;
6697 }
6699 static bool
6700 grep_refs(struct ref_list *list, regex_t *regex)
6701 {
6702 regmatch_t pmatch;
6703 size_t i;
6705 if (!opt_show_refs || !list)
6706 return FALSE;
6708 for (i = 0; i < list->size; i++) {
6709 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6710 return TRUE;
6711 }
6713 return FALSE;
6714 }
6716 static bool
6717 main_grep(struct view *view, struct line *line)
6718 {
6719 struct commit *commit = line->data;
6720 const char *text[] = {
6721 commit->title,
6722 opt_author ? commit->author : "",
6723 mkdate(&commit->time, opt_date),
6724 NULL
6725 };
6727 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6728 }
6730 static void
6731 main_select(struct view *view, struct line *line)
6732 {
6733 struct commit *commit = line->data;
6735 string_copy_rev(view->ref, commit->id);
6736 string_copy_rev(ref_commit, view->ref);
6737 }
6739 static struct view_ops main_ops = {
6740 "commit",
6741 main_argv,
6742 NULL,
6743 main_read,
6744 main_draw,
6745 main_request,
6746 main_grep,
6747 main_select,
6748 };
6751 /*
6752 * Unicode / UTF-8 handling
6753 *
6754 * NOTE: Much of the following code for dealing with Unicode is derived from
6755 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6756 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6757 */
6759 static inline int
6760 unicode_width(unsigned long c, int tab_size)
6761 {
6762 if (c >= 0x1100 &&
6763 (c <= 0x115f /* Hangul Jamo */
6764 || c == 0x2329
6765 || c == 0x232a
6766 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6767 /* CJK ... Yi */
6768 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6769 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6770 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6771 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6772 || (c >= 0xffe0 && c <= 0xffe6)
6773 || (c >= 0x20000 && c <= 0x2fffd)
6774 || (c >= 0x30000 && c <= 0x3fffd)))
6775 return 2;
6777 if (c == '\t')
6778 return tab_size;
6780 return 1;
6781 }
6783 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6784 * Illegal bytes are set one. */
6785 static const unsigned char utf8_bytes[256] = {
6786 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,
6787 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,
6788 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,
6789 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,
6790 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,
6791 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,
6792 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,
6793 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,
6794 };
6796 static inline unsigned char
6797 utf8_char_length(const char *string, const char *end)
6798 {
6799 int c = *(unsigned char *) string;
6801 return utf8_bytes[c];
6802 }
6804 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6805 static inline unsigned long
6806 utf8_to_unicode(const char *string, size_t length)
6807 {
6808 unsigned long unicode;
6810 switch (length) {
6811 case 1:
6812 unicode = string[0];
6813 break;
6814 case 2:
6815 unicode = (string[0] & 0x1f) << 6;
6816 unicode += (string[1] & 0x3f);
6817 break;
6818 case 3:
6819 unicode = (string[0] & 0x0f) << 12;
6820 unicode += ((string[1] & 0x3f) << 6);
6821 unicode += (string[2] & 0x3f);
6822 break;
6823 case 4:
6824 unicode = (string[0] & 0x0f) << 18;
6825 unicode += ((string[1] & 0x3f) << 12);
6826 unicode += ((string[2] & 0x3f) << 6);
6827 unicode += (string[3] & 0x3f);
6828 break;
6829 case 5:
6830 unicode = (string[0] & 0x0f) << 24;
6831 unicode += ((string[1] & 0x3f) << 18);
6832 unicode += ((string[2] & 0x3f) << 12);
6833 unicode += ((string[3] & 0x3f) << 6);
6834 unicode += (string[4] & 0x3f);
6835 break;
6836 case 6:
6837 unicode = (string[0] & 0x01) << 30;
6838 unicode += ((string[1] & 0x3f) << 24);
6839 unicode += ((string[2] & 0x3f) << 18);
6840 unicode += ((string[3] & 0x3f) << 12);
6841 unicode += ((string[4] & 0x3f) << 6);
6842 unicode += (string[5] & 0x3f);
6843 break;
6844 default:
6845 die("Invalid Unicode length");
6846 }
6848 /* Invalid characters could return the special 0xfffd value but NUL
6849 * should be just as good. */
6850 return unicode > 0xffff ? 0 : unicode;
6851 }
6853 /* Calculates how much of string can be shown within the given maximum width
6854 * and sets trimmed parameter to non-zero value if all of string could not be
6855 * shown. If the reserve flag is TRUE, it will reserve at least one
6856 * trailing character, which can be useful when drawing a delimiter.
6857 *
6858 * Returns the number of bytes to output from string to satisfy max_width. */
6859 static size_t
6860 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
6861 {
6862 const char *string = *start;
6863 const char *end = strchr(string, '\0');
6864 unsigned char last_bytes = 0;
6865 size_t last_ucwidth = 0;
6867 *width = 0;
6868 *trimmed = 0;
6870 while (string < end) {
6871 unsigned char bytes = utf8_char_length(string, end);
6872 size_t ucwidth;
6873 unsigned long unicode;
6875 if (string + bytes > end)
6876 break;
6878 /* Change representation to figure out whether
6879 * it is a single- or double-width character. */
6881 unicode = utf8_to_unicode(string, bytes);
6882 /* FIXME: Graceful handling of invalid Unicode character. */
6883 if (!unicode)
6884 break;
6886 ucwidth = unicode_width(unicode, tab_size);
6887 if (skip > 0) {
6888 skip -= ucwidth <= skip ? ucwidth : skip;
6889 *start += bytes;
6890 }
6891 *width += ucwidth;
6892 if (*width > max_width) {
6893 *trimmed = 1;
6894 *width -= ucwidth;
6895 if (reserve && *width == max_width) {
6896 string -= last_bytes;
6897 *width -= last_ucwidth;
6898 }
6899 break;
6900 }
6902 string += bytes;
6903 last_bytes = ucwidth ? bytes : 0;
6904 last_ucwidth = ucwidth;
6905 }
6907 return string - *start;
6908 }
6911 /*
6912 * Status management
6913 */
6915 /* Whether or not the curses interface has been initialized. */
6916 static bool cursed = FALSE;
6918 /* Terminal hacks and workarounds. */
6919 static bool use_scroll_redrawwin;
6920 static bool use_scroll_status_wclear;
6922 /* The status window is used for polling keystrokes. */
6923 static WINDOW *status_win;
6925 /* Reading from the prompt? */
6926 static bool input_mode = FALSE;
6928 static bool status_empty = FALSE;
6930 /* Update status and title window. */
6931 static void
6932 report(const char *msg, ...)
6933 {
6934 struct view *view = display[current_view];
6936 if (input_mode)
6937 return;
6939 if (!view) {
6940 char buf[SIZEOF_STR];
6941 va_list args;
6943 va_start(args, msg);
6944 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6945 buf[sizeof(buf) - 1] = 0;
6946 buf[sizeof(buf) - 2] = '.';
6947 buf[sizeof(buf) - 3] = '.';
6948 buf[sizeof(buf) - 4] = '.';
6949 }
6950 va_end(args);
6951 die("%s", buf);
6952 }
6954 if (!status_empty || *msg) {
6955 va_list args;
6957 va_start(args, msg);
6959 wmove(status_win, 0, 0);
6960 if (view->has_scrolled && use_scroll_status_wclear)
6961 wclear(status_win);
6962 if (*msg) {
6963 vwprintw(status_win, msg, args);
6964 status_empty = FALSE;
6965 } else {
6966 status_empty = TRUE;
6967 }
6968 wclrtoeol(status_win);
6969 wnoutrefresh(status_win);
6971 va_end(args);
6972 }
6974 update_view_title(view);
6975 }
6977 static void
6978 init_display(void)
6979 {
6980 const char *term;
6981 int x, y;
6983 /* Initialize the curses library */
6984 if (isatty(STDIN_FILENO)) {
6985 cursed = !!initscr();
6986 opt_tty = stdin;
6987 } else {
6988 /* Leave stdin and stdout alone when acting as a pager. */
6989 opt_tty = fopen("/dev/tty", "r+");
6990 if (!opt_tty)
6991 die("Failed to open /dev/tty");
6992 cursed = !!newterm(NULL, opt_tty, opt_tty);
6993 }
6995 if (!cursed)
6996 die("Failed to initialize curses");
6998 nonl(); /* Disable conversion and detect newlines from input. */
6999 cbreak(); /* Take input chars one at a time, no wait for \n */
7000 noecho(); /* Don't echo input */
7001 leaveok(stdscr, FALSE);
7003 if (has_colors())
7004 init_colors();
7006 getmaxyx(stdscr, y, x);
7007 status_win = newwin(1, 0, y - 1, 0);
7008 if (!status_win)
7009 die("Failed to create status window");
7011 /* Enable keyboard mapping */
7012 keypad(status_win, TRUE);
7013 wbkgdset(status_win, get_line_attr(LINE_STATUS));
7015 TABSIZE = opt_tab_size;
7017 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7018 if (term && !strcmp(term, "gnome-terminal")) {
7019 /* In the gnome-terminal-emulator, the message from
7020 * scrolling up one line when impossible followed by
7021 * scrolling down one line causes corruption of the
7022 * status line. This is fixed by calling wclear. */
7023 use_scroll_status_wclear = TRUE;
7024 use_scroll_redrawwin = FALSE;
7026 } else if (term && !strcmp(term, "xrvt-xpm")) {
7027 /* No problems with full optimizations in xrvt-(unicode)
7028 * and aterm. */
7029 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7031 } else {
7032 /* When scrolling in (u)xterm the last line in the
7033 * scrolling direction will update slowly. */
7034 use_scroll_redrawwin = TRUE;
7035 use_scroll_status_wclear = FALSE;
7036 }
7037 }
7039 static int
7040 get_input(int prompt_position)
7041 {
7042 struct view *view;
7043 int i, key, cursor_y, cursor_x;
7044 bool loading = FALSE;
7046 if (prompt_position)
7047 input_mode = TRUE;
7049 while (TRUE) {
7050 foreach_view (view, i) {
7051 update_view(view);
7052 if (view_is_displayed(view) && view->has_scrolled &&
7053 use_scroll_redrawwin)
7054 redrawwin(view->win);
7055 view->has_scrolled = FALSE;
7056 if (view->pipe)
7057 loading = TRUE;
7058 }
7060 /* Update the cursor position. */
7061 if (prompt_position) {
7062 getbegyx(status_win, cursor_y, cursor_x);
7063 cursor_x = prompt_position;
7064 } else {
7065 view = display[current_view];
7066 getbegyx(view->win, cursor_y, cursor_x);
7067 cursor_x = view->width - 1;
7068 cursor_y += view->lineno - view->offset;
7069 }
7070 setsyx(cursor_y, cursor_x);
7072 /* Refresh, accept single keystroke of input */
7073 doupdate();
7074 nodelay(status_win, loading);
7075 key = wgetch(status_win);
7077 /* wgetch() with nodelay() enabled returns ERR when
7078 * there's no input. */
7079 if (key == ERR) {
7081 } else if (key == KEY_RESIZE) {
7082 int height, width;
7084 getmaxyx(stdscr, height, width);
7086 wresize(status_win, 1, width);
7087 mvwin(status_win, height - 1, 0);
7088 wnoutrefresh(status_win);
7089 resize_display();
7090 redraw_display(TRUE);
7092 } else {
7093 input_mode = FALSE;
7094 return key;
7095 }
7096 }
7097 }
7099 static char *
7100 prompt_input(const char *prompt, input_handler handler, void *data)
7101 {
7102 enum input_status status = INPUT_OK;
7103 static char buf[SIZEOF_STR];
7104 size_t pos = 0;
7106 buf[pos] = 0;
7108 while (status == INPUT_OK || status == INPUT_SKIP) {
7109 int key;
7111 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7112 wclrtoeol(status_win);
7114 key = get_input(pos + 1);
7115 switch (key) {
7116 case KEY_RETURN:
7117 case KEY_ENTER:
7118 case '\n':
7119 status = pos ? INPUT_STOP : INPUT_CANCEL;
7120 break;
7122 case KEY_BACKSPACE:
7123 if (pos > 0)
7124 buf[--pos] = 0;
7125 else
7126 status = INPUT_CANCEL;
7127 break;
7129 case KEY_ESC:
7130 status = INPUT_CANCEL;
7131 break;
7133 default:
7134 if (pos >= sizeof(buf)) {
7135 report("Input string too long");
7136 return NULL;
7137 }
7139 status = handler(data, buf, key);
7140 if (status == INPUT_OK)
7141 buf[pos++] = (char) key;
7142 }
7143 }
7145 /* Clear the status window */
7146 status_empty = FALSE;
7147 report("");
7149 if (status == INPUT_CANCEL)
7150 return NULL;
7152 buf[pos++] = 0;
7154 return buf;
7155 }
7157 static enum input_status
7158 prompt_yesno_handler(void *data, char *buf, int c)
7159 {
7160 if (c == 'y' || c == 'Y')
7161 return INPUT_STOP;
7162 if (c == 'n' || c == 'N')
7163 return INPUT_CANCEL;
7164 return INPUT_SKIP;
7165 }
7167 static bool
7168 prompt_yesno(const char *prompt)
7169 {
7170 char prompt2[SIZEOF_STR];
7172 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7173 return FALSE;
7175 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7176 }
7178 static enum input_status
7179 read_prompt_handler(void *data, char *buf, int c)
7180 {
7181 return isprint(c) ? INPUT_OK : INPUT_SKIP;
7182 }
7184 static char *
7185 read_prompt(const char *prompt)
7186 {
7187 return prompt_input(prompt, read_prompt_handler, NULL);
7188 }
7190 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7191 {
7192 enum input_status status = INPUT_OK;
7193 int size = 0;
7195 while (items[size].text)
7196 size++;
7198 while (status == INPUT_OK) {
7199 const struct menu_item *item = &items[*selected];
7200 int key;
7201 int i;
7203 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7204 prompt, *selected + 1, size);
7205 if (item->hotkey)
7206 wprintw(status_win, "[%c] ", (char) item->hotkey);
7207 wprintw(status_win, "%s", item->text);
7208 wclrtoeol(status_win);
7210 key = get_input(COLS - 1);
7211 switch (key) {
7212 case KEY_RETURN:
7213 case KEY_ENTER:
7214 case '\n':
7215 status = INPUT_STOP;
7216 break;
7218 case KEY_LEFT:
7219 case KEY_UP:
7220 *selected = *selected - 1;
7221 if (*selected < 0)
7222 *selected = size - 1;
7223 break;
7225 case KEY_RIGHT:
7226 case KEY_DOWN:
7227 *selected = (*selected + 1) % size;
7228 break;
7230 case KEY_ESC:
7231 status = INPUT_CANCEL;
7232 break;
7234 default:
7235 for (i = 0; items[i].text; i++)
7236 if (items[i].hotkey == key) {
7237 *selected = i;
7238 status = INPUT_STOP;
7239 break;
7240 }
7241 }
7242 }
7244 /* Clear the status window */
7245 status_empty = FALSE;
7246 report("");
7248 return status != INPUT_CANCEL;
7249 }
7251 /*
7252 * Repository properties
7253 */
7255 static struct ref **refs = NULL;
7256 static size_t refs_size = 0;
7257 static struct ref *refs_head = NULL;
7259 static struct ref_list **ref_lists = NULL;
7260 static size_t ref_lists_size = 0;
7262 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7263 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7264 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7266 static int
7267 compare_refs(const void *ref1_, const void *ref2_)
7268 {
7269 const struct ref *ref1 = *(const struct ref **)ref1_;
7270 const struct ref *ref2 = *(const struct ref **)ref2_;
7272 if (ref1->tag != ref2->tag)
7273 return ref2->tag - ref1->tag;
7274 if (ref1->ltag != ref2->ltag)
7275 return ref2->ltag - ref2->ltag;
7276 if (ref1->head != ref2->head)
7277 return ref2->head - ref1->head;
7278 if (ref1->tracked != ref2->tracked)
7279 return ref2->tracked - ref1->tracked;
7280 if (ref1->remote != ref2->remote)
7281 return ref2->remote - ref1->remote;
7282 return strcmp(ref1->name, ref2->name);
7283 }
7285 static void
7286 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7287 {
7288 size_t i;
7290 for (i = 0; i < refs_size; i++)
7291 if (!visitor(data, refs[i]))
7292 break;
7293 }
7295 static struct ref *
7296 get_ref_head()
7297 {
7298 return refs_head;
7299 }
7301 static struct ref_list *
7302 get_ref_list(const char *id)
7303 {
7304 struct ref_list *list;
7305 size_t i;
7307 for (i = 0; i < ref_lists_size; i++)
7308 if (!strcmp(id, ref_lists[i]->id))
7309 return ref_lists[i];
7311 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7312 return NULL;
7313 list = calloc(1, sizeof(*list));
7314 if (!list)
7315 return NULL;
7317 for (i = 0; i < refs_size; i++) {
7318 if (!strcmp(id, refs[i]->id) &&
7319 realloc_refs_list(&list->refs, list->size, 1))
7320 list->refs[list->size++] = refs[i];
7321 }
7323 if (!list->refs) {
7324 free(list);
7325 return NULL;
7326 }
7328 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7329 ref_lists[ref_lists_size++] = list;
7330 return list;
7331 }
7333 static int
7334 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7335 {
7336 struct ref *ref = NULL;
7337 bool tag = FALSE;
7338 bool ltag = FALSE;
7339 bool remote = FALSE;
7340 bool tracked = FALSE;
7341 bool head = FALSE;
7342 int from = 0, to = refs_size - 1;
7344 if (!prefixcmp(name, "refs/tags/")) {
7345 if (!suffixcmp(name, namelen, "^{}")) {
7346 namelen -= 3;
7347 name[namelen] = 0;
7348 } else {
7349 ltag = TRUE;
7350 }
7352 tag = TRUE;
7353 namelen -= STRING_SIZE("refs/tags/");
7354 name += STRING_SIZE("refs/tags/");
7356 } else if (!prefixcmp(name, "refs/remotes/")) {
7357 remote = TRUE;
7358 namelen -= STRING_SIZE("refs/remotes/");
7359 name += STRING_SIZE("refs/remotes/");
7360 tracked = !strcmp(opt_remote, name);
7362 } else if (!prefixcmp(name, "refs/heads/")) {
7363 namelen -= STRING_SIZE("refs/heads/");
7364 name += STRING_SIZE("refs/heads/");
7365 if (!strncmp(opt_head, name, namelen))
7366 return OK;
7368 } else if (!strcmp(name, "HEAD")) {
7369 head = TRUE;
7370 if (*opt_head) {
7371 namelen = strlen(opt_head);
7372 name = opt_head;
7373 }
7374 }
7376 /* If we are reloading or it's an annotated tag, replace the
7377 * previous SHA1 with the resolved commit id; relies on the fact
7378 * git-ls-remote lists the commit id of an annotated tag right
7379 * before the commit id it points to. */
7380 while (from <= to) {
7381 size_t pos = (to + from) / 2;
7382 int cmp = strcmp(name, refs[pos]->name);
7384 if (!cmp) {
7385 ref = refs[pos];
7386 break;
7387 }
7389 if (cmp < 0)
7390 to = pos - 1;
7391 else
7392 from = pos + 1;
7393 }
7395 if (!ref) {
7396 if (!realloc_refs(&refs, refs_size, 1))
7397 return ERR;
7398 ref = calloc(1, sizeof(*ref) + namelen);
7399 if (!ref)
7400 return ERR;
7401 memmove(refs + from + 1, refs + from,
7402 (refs_size - from) * sizeof(*refs));
7403 refs[from] = ref;
7404 strncpy(ref->name, name, namelen);
7405 refs_size++;
7406 }
7408 ref->head = head;
7409 ref->tag = tag;
7410 ref->ltag = ltag;
7411 ref->remote = remote;
7412 ref->tracked = tracked;
7413 string_copy_rev(ref->id, id);
7415 if (head)
7416 refs_head = ref;
7417 return OK;
7418 }
7420 static int
7421 load_refs(void)
7422 {
7423 const char *head_argv[] = {
7424 "git", "symbolic-ref", "HEAD", NULL
7425 };
7426 static const char *ls_remote_argv[SIZEOF_ARG] = {
7427 "git", "ls-remote", opt_git_dir, NULL
7428 };
7429 static bool init = FALSE;
7430 size_t i;
7432 if (!init) {
7433 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
7434 init = TRUE;
7435 }
7437 if (!*opt_git_dir)
7438 return OK;
7440 if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7441 !prefixcmp(opt_head, "refs/heads/")) {
7442 char *offset = opt_head + STRING_SIZE("refs/heads/");
7444 memmove(opt_head, offset, strlen(offset) + 1);
7445 }
7447 refs_head = NULL;
7448 for (i = 0; i < refs_size; i++)
7449 refs[i]->id[0] = 0;
7451 if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7452 return ERR;
7454 /* Update the ref lists to reflect changes. */
7455 for (i = 0; i < ref_lists_size; i++) {
7456 struct ref_list *list = ref_lists[i];
7457 size_t old, new;
7459 for (old = new = 0; old < list->size; old++)
7460 if (!strcmp(list->id, list->refs[old]->id))
7461 list->refs[new++] = list->refs[old];
7462 list->size = new;
7463 }
7465 return OK;
7466 }
7468 static void
7469 set_remote_branch(const char *name, const char *value, size_t valuelen)
7470 {
7471 if (!strcmp(name, ".remote")) {
7472 string_ncopy(opt_remote, value, valuelen);
7474 } else if (*opt_remote && !strcmp(name, ".merge")) {
7475 size_t from = strlen(opt_remote);
7477 if (!prefixcmp(value, "refs/heads/"))
7478 value += STRING_SIZE("refs/heads/");
7480 if (!string_format_from(opt_remote, &from, "/%s", value))
7481 opt_remote[0] = 0;
7482 }
7483 }
7485 static void
7486 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7487 {
7488 const char *argv[SIZEOF_ARG] = { name, "=" };
7489 int argc = 1 + (cmd == option_set_command);
7490 int error = ERR;
7492 if (!argv_from_string(argv, &argc, value))
7493 config_msg = "Too many option arguments";
7494 else
7495 error = cmd(argc, argv);
7497 if (error == ERR)
7498 warn("Option 'tig.%s': %s", name, config_msg);
7499 }
7501 static bool
7502 set_environment_variable(const char *name, const char *value)
7503 {
7504 size_t len = strlen(name) + 1 + strlen(value) + 1;
7505 char *env = malloc(len);
7507 if (env &&
7508 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7509 putenv(env) == 0)
7510 return TRUE;
7511 free(env);
7512 return FALSE;
7513 }
7515 static void
7516 set_work_tree(const char *value)
7517 {
7518 char cwd[SIZEOF_STR];
7520 if (!getcwd(cwd, sizeof(cwd)))
7521 die("Failed to get cwd path: %s", strerror(errno));
7522 if (chdir(opt_git_dir) < 0)
7523 die("Failed to chdir(%s): %s", strerror(errno));
7524 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7525 die("Failed to get git path: %s", strerror(errno));
7526 if (chdir(cwd) < 0)
7527 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7528 if (chdir(value) < 0)
7529 die("Failed to chdir(%s): %s", value, strerror(errno));
7530 if (!getcwd(cwd, sizeof(cwd)))
7531 die("Failed to get cwd path: %s", strerror(errno));
7532 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7533 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7534 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7535 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7536 opt_is_inside_work_tree = TRUE;
7537 }
7539 static int
7540 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7541 {
7542 if (!strcmp(name, "i18n.commitencoding"))
7543 string_ncopy(opt_encoding, value, valuelen);
7545 else if (!strcmp(name, "core.editor"))
7546 string_ncopy(opt_editor, value, valuelen);
7548 else if (!strcmp(name, "core.worktree"))
7549 set_work_tree(value);
7551 else if (!prefixcmp(name, "tig.color."))
7552 set_repo_config_option(name + 10, value, option_color_command);
7554 else if (!prefixcmp(name, "tig.bind."))
7555 set_repo_config_option(name + 9, value, option_bind_command);
7557 else if (!prefixcmp(name, "tig."))
7558 set_repo_config_option(name + 4, value, option_set_command);
7560 else if (*opt_head && !prefixcmp(name, "branch.") &&
7561 !strncmp(name + 7, opt_head, strlen(opt_head)))
7562 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7564 return OK;
7565 }
7567 static int
7568 load_git_config(void)
7569 {
7570 const char *config_list_argv[] = { "git", "config", "--list", NULL };
7572 return io_run_load(config_list_argv, "=", read_repo_config_option);
7573 }
7575 static int
7576 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7577 {
7578 if (!opt_git_dir[0]) {
7579 string_ncopy(opt_git_dir, name, namelen);
7581 } else if (opt_is_inside_work_tree == -1) {
7582 /* This can be 3 different values depending on the
7583 * version of git being used. If git-rev-parse does not
7584 * understand --is-inside-work-tree it will simply echo
7585 * the option else either "true" or "false" is printed.
7586 * Default to true for the unknown case. */
7587 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7589 } else if (*name == '.') {
7590 string_ncopy(opt_cdup, name, namelen);
7592 } else {
7593 string_ncopy(opt_prefix, name, namelen);
7594 }
7596 return OK;
7597 }
7599 static int
7600 load_repo_info(void)
7601 {
7602 const char *rev_parse_argv[] = {
7603 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7604 "--show-cdup", "--show-prefix", NULL
7605 };
7607 return io_run_load(rev_parse_argv, "=", read_repo_info);
7608 }
7611 /*
7612 * Main
7613 */
7615 static const char usage[] =
7616 "tig " TIG_VERSION " (" __DATE__ ")\n"
7617 "\n"
7618 "Usage: tig [options] [revs] [--] [paths]\n"
7619 " or: tig show [options] [revs] [--] [paths]\n"
7620 " or: tig blame [rev] path\n"
7621 " or: tig status\n"
7622 " or: tig < [git command output]\n"
7623 "\n"
7624 "Options:\n"
7625 " -v, --version Show version and exit\n"
7626 " -h, --help Show help message and exit";
7628 static void __NORETURN
7629 quit(int sig)
7630 {
7631 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7632 if (cursed)
7633 endwin();
7634 exit(0);
7635 }
7637 static void __NORETURN
7638 die(const char *err, ...)
7639 {
7640 va_list args;
7642 endwin();
7644 va_start(args, err);
7645 fputs("tig: ", stderr);
7646 vfprintf(stderr, err, args);
7647 fputs("\n", stderr);
7648 va_end(args);
7650 exit(1);
7651 }
7653 static void
7654 warn(const char *msg, ...)
7655 {
7656 va_list args;
7658 va_start(args, msg);
7659 fputs("tig warning: ", stderr);
7660 vfprintf(stderr, msg, args);
7661 fputs("\n", stderr);
7662 va_end(args);
7663 }
7665 static enum request
7666 parse_options(int argc, const char *argv[])
7667 {
7668 enum request request = REQ_VIEW_MAIN;
7669 const char *subcommand;
7670 bool seen_dashdash = FALSE;
7671 /* XXX: This is vulnerable to the user overriding options
7672 * required for the main view parser. */
7673 const char *custom_argv[SIZEOF_ARG] = {
7674 "git", "log", "--no-color", "--pretty=raw", "--parents",
7675 "--topo-order", NULL
7676 };
7677 int i, j = 6;
7679 if (!isatty(STDIN_FILENO)) {
7680 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7681 return REQ_VIEW_PAGER;
7682 }
7684 if (argc <= 1)
7685 return REQ_NONE;
7687 subcommand = argv[1];
7688 if (!strcmp(subcommand, "status")) {
7689 if (argc > 2)
7690 warn("ignoring arguments after `%s'", subcommand);
7691 return REQ_VIEW_STATUS;
7693 } else if (!strcmp(subcommand, "blame")) {
7694 if (argc <= 2 || argc > 4)
7695 die("invalid number of options to blame\n\n%s", usage);
7697 i = 2;
7698 if (argc == 4) {
7699 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7700 i++;
7701 }
7703 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7704 return REQ_VIEW_BLAME;
7706 } else if (!strcmp(subcommand, "show")) {
7707 request = REQ_VIEW_DIFF;
7709 } else {
7710 subcommand = NULL;
7711 }
7713 if (subcommand) {
7714 custom_argv[1] = subcommand;
7715 j = 2;
7716 }
7718 for (i = 1 + !!subcommand; i < argc; i++) {
7719 const char *opt = argv[i];
7721 if (seen_dashdash || !strcmp(opt, "--")) {
7722 seen_dashdash = TRUE;
7724 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7725 printf("tig version %s\n", TIG_VERSION);
7726 quit(0);
7728 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7729 printf("%s\n", usage);
7730 quit(0);
7731 }
7733 custom_argv[j++] = opt;
7734 if (j >= ARRAY_SIZE(custom_argv))
7735 die("command too long");
7736 }
7738 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7739 die("Failed to format arguments");
7741 return request;
7742 }
7744 int
7745 main(int argc, const char *argv[])
7746 {
7747 const char *codeset = "UTF-8";
7748 enum request request = parse_options(argc, argv);
7749 struct view *view;
7750 size_t i;
7752 signal(SIGINT, quit);
7753 signal(SIGPIPE, SIG_IGN);
7755 if (setlocale(LC_ALL, "")) {
7756 codeset = nl_langinfo(CODESET);
7757 }
7759 if (load_repo_info() == ERR)
7760 die("Failed to load repo info.");
7762 if (load_options() == ERR)
7763 die("Failed to load user config.");
7765 if (load_git_config() == ERR)
7766 die("Failed to load repo config.");
7768 /* Require a git repository unless when running in pager mode. */
7769 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7770 die("Not a git repository");
7772 if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7773 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7774 if (opt_iconv_in == ICONV_NONE)
7775 die("Failed to initialize character set conversion");
7776 }
7778 if (codeset && strcmp(codeset, "UTF-8")) {
7779 opt_iconv_out = iconv_open(codeset, "UTF-8");
7780 if (opt_iconv_out == ICONV_NONE)
7781 die("Failed to initialize character set conversion");
7782 }
7784 if (load_refs() == ERR)
7785 die("Failed to load refs.");
7787 foreach_view (view, i)
7788 argv_from_env(view->ops->argv, view->cmd_env);
7790 init_display();
7792 if (request != REQ_NONE)
7793 open_view(NULL, request, OPEN_PREPARED);
7794 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7796 while (view_driver(display[current_view], request)) {
7797 int key = get_input(0);
7799 view = display[current_view];
7800 request = get_keybinding(view->keymap, key);
7802 /* Some low-level request handling. This keeps access to
7803 * status_win restricted. */
7804 switch (request) {
7805 case REQ_PROMPT:
7806 {
7807 char *cmd = read_prompt(":");
7809 if (cmd && isdigit(*cmd)) {
7810 int lineno = view->lineno + 1;
7812 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7813 select_view_line(view, lineno - 1);
7814 report("");
7815 } else {
7816 report("Unable to parse '%s' as a line number", cmd);
7817 }
7819 } else if (cmd) {
7820 struct view *next = VIEW(REQ_VIEW_PAGER);
7821 const char *argv[SIZEOF_ARG] = { "git" };
7822 int argc = 1;
7824 /* When running random commands, initially show the
7825 * command in the title. However, it maybe later be
7826 * overwritten if a commit line is selected. */
7827 string_ncopy(next->ref, cmd, strlen(cmd));
7829 if (!argv_from_string(argv, &argc, cmd)) {
7830 report("Too many arguments");
7831 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7832 report("Failed to format command");
7833 } else {
7834 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7835 }
7836 }
7838 request = REQ_NONE;
7839 break;
7840 }
7841 case REQ_SEARCH:
7842 case REQ_SEARCH_BACK:
7843 {
7844 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7845 char *search = read_prompt(prompt);
7847 if (search)
7848 string_ncopy(opt_search, search, strlen(search));
7849 else if (*opt_search)
7850 request = request == REQ_SEARCH ?
7851 REQ_FIND_NEXT :
7852 REQ_FIND_PREV;
7853 else
7854 request = REQ_NONE;
7855 break;
7856 }
7857 default:
7858 break;
7859 }
7860 }
7862 quit(0);
7864 return 0;
7865 }